概述
C语言——只看这一篇就够了!
C语言知识点保姆级总结,这不得进你的收藏夹吃灰?!
拖了很久的C语言所学知识的简单小结,内容有点多,第一次总结也可能有错误或者不全面,欢迎随时补充说明!
数据类型
用不同数据类型所定义的变量所占空间大小不一样,定义的变量不是保存于数据类型中,而是因为只有定义了该数据类型的变量才能保存数据。
一、整型
1、整型(int) 四字节,默认有符号(-231-231-1),无符号加unsigned(0-232-1)(十位数);
2、短整型(short int) ,两字节(-215-215-1)(五位数);
3、长整型(long int) ,四字节(同int,在目前的操作系统中几乎没有区别);
4、长长整型(long long int), 八字节(-263-263-1),无符号(0-264-1);
二、浮点型
1、单精度浮点数(float),四字节,保留小数点后6位
2、双精度浮点数(double),八字节,保留小数点后15位
int为一个32为的存储单元,long long为64为的存储单元
1 B/byte(字节) = 8 bit(比特)(位)
1 KB(千字节) = 1024 B/byte(字节)
1 MB = 1024 KB
1 GB = 1024 MB
1TB =1024 GB
1 PB = 1024 TB
1 EB = 1024 PB
三、字符型
char,用于储存字符,和int很像,可用ASCII码来储存字符,
eg:
char grade=’A’;
char grade=65;
' '单引号为字符,eg:char a='a';
" "双引号为字符串,eg:char* a=“asd”;编译器会自动给字符串结尾添加’ ‘来作为字符结束标识,strlen函数中不统计 ,但是 在内存中占据空间。
除此之外,还有转义字符,通过反斜杠来完成相关操作,如果要特殊字符转字面字符需要另外添加反斜杠,转义字符在字符串中占空间,但是只计算一个长度, 不计长度。
四、变量和常量
作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
生命周期:变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
#include <stdio.h>
int global = 2019;//全局变量
int main()
{
int local = 2018;//局部变量
return 0;
}
分支及循环语句
一、分支语句
1、if语句
语法结构:
if(表达式)
语句;
if(表达式){
语句列表1
}
else{
语句列表2;
}
//多分支
if(表达式1){
语句列表1;
}
else if(表达式2){
语句列表2;
}
else{
语句列表3;
}
表达式部分为真则执行语句(0为假,非0为真),尽量在每个分支语句后都加{},否则只会执行分支后第一条语句。
else在没有括号的情况下遵循就近原则所以在多重if语句嵌套使用情况下一定要加括号!
2、switch语句
switch作为分支结构常用于多分支的情况,可以简化多重if语句嵌套的情况。
语法结构
switch(表达式A){
case 常量表达式1:
语句1;
break;
case 常量表达式2:
语句2;
break;
……
case 常量表达式n:
语句n;
break;
default:
语句n+1;
break;
}
其中
1、case后第一句不可定义变量,必须跟常量或者常量表达式,并且不可相同;
2、break在语句中可以起到划分作用,不可省略,否则无法实现分支功能;
3、default语句不应该省略,一般推荐位语句列表末尾;
4、switch语句结束条件:①遇到break;②执行到语句列表末尾。
二、循环语句
1、while语句
语法结构
while(表达式){
循环语句;
}
注:在循环语句中break的作用是停止后期所有循环,continue的作用是终止本次循环,开始下一次循环的判断。
2、for语句
for(表达式1;表达式2;表达式3){
循环语句;
}
表达式1为初始化部分,用于初始化循环变量,当表达式1为空时直接进入循环;
表达式2 为条件判断部分,用于判断循环是否终止,当表达式2为空时为死循环;
表达式3为调整部分,用于循环条件的调整 。
注:建议使用“前闭后开”来限定区间范围。
for(i=0; i<10; i++){
a[i]=i;
}
3、do while语句
do{
循环语句;
}while(表达式);
循环体至少执行一次,while之后记得加分号。
二分查找函数循环实现范例:
int bin_search(int arr[], int left, int right, int key)
{
int mid = 0;
while(left<=right){
mid = (left+right)>>1;
if(arr[mid]>key)
{
right = mid-1;
}
else if(arr[mid] < key)
{
left = mid+1;
}
else
{
return mid;//找到了,返回下标
}
}
return -1;//找不到
}
函数
一、库函数
C语言基础库中的函数,在添加头文件后可直接调用。
二、自定义函数
1、函数组成
由函数名、返回值类型、函数参数以及函数体组成。
实参:真实的传入函数的变量,**在被调用时会发生临时拷贝,并非把原来的变量直接放入函数中,**只是把实参的数据拷贝给形参。
形参:函数名括号后的变量,因为形参只有在被调用的时候才被实例化并分配空间(形参实例化),**在被调用过后即被销毁,只在该函数中有效(局部变量),**所以叫形参。
函数声明,要满足先声明后使用的原则,由返回值类型、函数名与函数参数组成(需要加分号), 当我们用到很多函数声明的时候,为了方便我们的调用,我们可以创建一个头文件.h(比如test.h),将函数声明放在头文件当中 ,在写头文件时,要注意加上#pragma once 。
//函数定义
double Add(double x, double y){
return x+y;
}
//函数声明
double Add(double x, double y);
2、函数调用
分为传值调用与传址调用,其中传址调用是把函数外部创建的内存地址传递给函数,可以真正与原值建立起联系,直接操纵函数外部的变量。
函数也可以进行嵌套调用以及链式访问。
嵌套调用样例:
#include <stdio.h>
void new_line()
{
printf("hehen");
}
void three_line()
{
int i = 0;
for(i=0; i<3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
链式访问样例:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[20] = "hello";
int ret = strlen(strcat(arr,"bit"));
printf("%dn", ret);
return 0;
}
3、函数递归
程序自身调用被称为递归,把复杂问题层层转化为与原问题类似的小问题,函数在调用自身的情况下存在不合法递归(即无限次的递归导致栈溢出)。
所以在使用递归的时候一定要有递归出口,否则会陷入死循环导致栈溢出!
**注:**栈结构为电脑存储的一部分,从高地址处向下开辟存储空间,与用于开辟动态存储空间的堆相向开辟(堆为从低地址出向上开辟存储空间),而函数调用将会形成栈帧,函数返回后自动释放栈帧结构,在此过程中,该函数定义的所有局部变量都在该函数的栈帧内进行空间开辟。
样例:求n的阶乘
int factorial(int n)
{
if(n <= 1)
return 1;
else
return n* factorial(n-1);
}
递归与迭代
递归在使用过程中由于频繁进行函数调用,且每次调用都需要时间成本以及空间成本,所以递归程序简单,但是可能导致递归效率变低的问题,而迭代方案通过对变量值进行替换所以不会造成栈溢出,解决了效率低下的问题。
样例(求斐波那契数列第n个的值):
//递归实现
int fibrec(int n){
if(n<=2) retuen 1;
else return fibrec(n-1)+fibrec(n-2);
}
//迭代实现
int fibite(int n){
int fir=1,sec=1,thd=2;
if(n<=2) return 1;
else{
while(n>2){
fir=sec;
sec=thd;
thd=fir+sec;
n--;
}
return thd;
}
}
数组
一、一维数组的创建与初始化
创建数组时数组空间为整体开辟整体释放,在内存中是连续存放,在定义时就已经确定数组大小(下标不可为0),且不可被整体赋值。在数组的创建过程中,如果进行了初始化则可不指定数组的大小,多维数组按照一维数组进行理解。
数组传参发生降维,降维成指向其(数组)内部元素类型的指针。
数组名一般情况下都指的是首元素的地址,但如果sizeof()单独出现以及&后跟数组名时表示的是整个数组
int s[5];
//表示数组首元素地址
printf("%dn", sizeof(s+1));//结果为4/8,指针的具体大小根据编译器的不同大小不同
//表示整个数组
printf("%dn", sizeof(s));//结果为20
二、数组传参(函数)
由于在传参过程中如果拷贝整个指针会导致效率大大降低甚至导致栈溢出,所以数组传参要发降维问题,函数内数组作为参数时,实参为首元素地址,形参为指针。
在访问结构体成员时也同样要发生类似的操作,用指向结构体的指针来指代结构体。
typedef struct node{
int a;
int b;
}point;
void pop(int* p){
}
int main(){
point a;
int* p=a;
pop(p);
return 0;
}
传参样例:
//用数组的形式传递参数,不需要指定参数的大小,传的只是数组首元素的地址。
void test(int arr[])
{}
//也可以传入数组大小
void test(int arr[10])
{}
//数组降维,用指针进行接收,传的是数组首元素的地址
void test(int *arr)
{}
//二维数组传参,第一个方括号可以空,但是第二个不可以空
void test(int arr[][5])
{}
void test(int arr[4][5])
{}
//传过去的是二维数组的数组名,即数组首元素的地址,也就是第一行的地址,第一行也是个数组,用一个数组指针接收(比较少用)
void test(int (*arr)[5])
{}
三、字符数组
char a[]={'a','x','d'};
//此处由于结尾没有'