复习总结最终版:C语言
一、C语言代码如何被运行起来
1.编写好的二进制代码存放在外存中
2.代码运行是,将二进制代码加载到内存中执行
3.内存将指令和数据加载到CPU中执行
二、基本数据类型
(一)整数类型:signed、unsigned,默认为有符号类型(int、short、long)
1.有符号数存储:二进制补码形式
2.无符号存储:二进制形式
3.获取数据类型所占空间大小:sizeof
(二)字符类型:char(本质也是一种整数类型)
1.ascii码32位之前符号用于通信控制,多数无法显示,可能会显示乱码
2.unsigned char
3.char
(三)浮点数类型:均为有符号类型(float(4字节)、double(8字节))
1.float存储(32)位:符号位(1)+指数位(8)+尾数位(23)
2.double存储(64)位:符号位(1)+指数位(8)+尾数位(52)
3.float类型精度6-8位,double精度12-16位
(四)-8.75存储
1.符号位:1
2.转换为dinary:1000.11
3.移动小数点使左边只有一位得到指数位(3):1000.11 = 1.00011\2^3
4.指数位处理,转换为二进制:127+3 = 130,dinary(130) = 10000010
5.位数位处理(23位):取出1.00011的小数部分,后面补0补齐23位
5.结果:1100 0001 0000 1100 0000 0000 0000 0000 -> 0xC10C0000
(五)缺省类型:void
(六)泊尔类型(逻辑类型):bool(1字节),取值范围:ture(1),false(0)
三、常量与变量
(一)常量:程序运行中值不会改变、可以直接使用
1.'\八进制数' 八进制数为3位
2.'\x十六位进制数' 十六位进制数为2位
3.strlen计算:"hi\123bc",结果为5,不算 '\0'
4.标识常量:define 标识 常量
5.#ifndef _NAME_H_
#define _NAME_H_
...
endif //头文件保护:防止头文件被重复包含
如果 main.c 同时包含了 a.h 和 b.h。
而 b.h 本身又包含了 a.h。
如果没有头文件保护,a.h 的内容会在 main.cpp 中被包含两次。
这会导致重复定义的错误(例如同一个函数或类被定义了两次)。
头文件保护的作用就是确保同一个头文件在同一个编译单元(.c文件)中只被包含一次。
6.宏只是代码的替换
7.带参宏:只是代码的替换,没有传参过程,所以执行效率高。程序编译预处理阶段宏被展开,代码体积会增大
(二)变量:程序运行中值可以发生改变
1.变量定义要求:字母、数字(不能以数字开头)、下划线
2.未经初始化为随机值,则定义时需要初始化
四、表达式
(一)混合运算数据类型:不同数据类型计算时,低精度转换为高精度
1.浮点类型精度 > 整数类型精度
2.相同类型所占空间越大精度范围越高
3.整数类型无符号精度 > 有符号精度
4.char和short运算会统一转换为int类型
5.float进行数据运算会转换为double类型
(二)强制类型转换(显示类型转换)
五、运算符
(一)逗号运算符:从左到右依次执行,最后部分结果作为整体结果
num = (a+b, a-b, a+c, c+b);
(二)sizeof:获得变量或者数据类型在内存中所占字节数
(三)运算符优先级:括号>单目>双目>三目>赋值>逗号
1.双目:算数>移位>关系>位>逻辑
2.结合方向(3)个自右到左:单目运算符、赋值运算符、三目运算符
六、输入输出函数
(一)对单个字符输入输出
1.getchar:向终端输出一个字符,传入一个字符ASCII码值,会将对应的字符显示在屏幕上
2.putchar:从终端接收一个字符,表达式结果为接到字符的ASCII码值
(二)格式化字符串输入输出
1.printf
格式: %nd: n:域宽,不足域宽,高位用空格补齐
%0nd: 0:不足域宽用0补齐
%.mf: m:小数点后保留m位有效数字
%-nd: -:左对齐
2.scanf
格式要求:
如果接收非输入控制符,需要在屏幕将非输入字符原封不动的输入
scanf认为' '和'\n'是用户结束的标志,所以不会读取到代码中
除了%s不用加&,其余都需要加&符号
_%c :从终端接收一个非空格和\n的字符
(三)字符串的输入输出
1.puts:向终端打印一行字符串,会多打印一个\n字符
2.gets:从终端接收一个字符串,可以接收待' '的字符串
七、流程控制
(一)逻辑表达式
1.截断特性:
逻辑与(&&):左边为假,右边不再计算
逻辑或(||):左边为真,右边不再计算
2.三目运算符:自左至右结合
x > y x : y > z y : z
(二)分支结构
1.if分支
2.switch分支:switch后面必须为整型表达式
(三)循环结构
1.while
2.do{
语句块;
}while(表达式);
3.for
4.goto:一般用于出错处理
(四)辅助控制语句
1.break:直接跳出循环
2.continue:屏蔽某几次循环使用
3.return:结束当前函数
八、数组(数组名在作用域为常量指针不能偏移,作为函数参数传入后,退化为普通指针变量可以偏移)
(一)一维整型数组
1.形式:int arr[5] = {1,2,3,4,5};
局部初始化:未给定初值元素全部默认为0,int arr[5] = {0};
默认初始化:为给定元素个数,通过给定初值个数决定元素个数
2.元素个数:必须为常量(2),常量表达式(2 + 3)
3.数组元素的访问
数组不能对整体操作,只能对单个元素操作
元素下标可以是常量,变量或表达式,必须为整型(遍历时下标给的 i )
数组元素个数计算:sizeof(arr) / sizeof(arr\[0])
4.冒泡排序
5.选择排序
(二)二维整型数组
1.初始化:行能省略,列不能省略
2.存储顺序:先存第0行所有元素,再存第一行...
(三)一维字符型数组
1.定义大小:必须存放的下包括 '\0'
2.初始化:为给定初值,默认为 0
3.区别:
0 整数0 存储对应二进制数
'\0' 字符'\0' ASCII值为0,存放 '\0' 等价于存放0(与 0 等价)
'0' 字符0 ASCII值为48,存放 '0' 等价于存放48(与48等价)
4.strlen与sizeof区别:
①sizeof:获取数组所占字节空间大小,包含'\0'
②strlen:获取字符串长度
5.字符串的操作
①strlen:计算字符串长度
②strcpy:拷贝字符串
③strcat:字符串拼接
④strcmp:字符串比较,相等返回0 。比较原则:从左到右依次比较字符,不相同字符谁的ASCII码值大,字符串就大。
(四)二维字符型数组
1.字符串的遍历
2.不使用strlen、strcpy、strcat、strcmp完成对应功能
九、函数
(一)变量的作用周期和生存周期
1.作用域
①局部变量:作用域在离定义该变量最近的大括号内
②全局变量:作用域为整个工程代码中
2.生存周期:变量从被开辟到到空间被回收的过程
①全局变量: 开辟:执行到变量定义时开辟
回收:变量超过作用域时
②全局变量: 开辟:编译时分配空间
回收:代码执行结束后回收
3.存储类型
auto int b; int b;
register int b;
extern int b;
static int b;
①auto(默认):自动型存储,将变量存放在栈空间中
②register:寄存器存储,将变量存放在CPU内部的寄存器中,如果寄存器存放等价于auto类型
③extern:声明一个变量是外部存放的
④static:静态存储,将变量存放在静态区中
4.代码运行时内存空间分配
①.text:存放函数、代码和指令
②.stack:存放局部变量(auto)
栈区空间有上限,默认8M,不能定义太大
栈区为操作系统管理区域,频繁被申请释放,所以未经初始化为随机值
代码执行到变量定义时开辟空间(栈空间),代码执行超过变量作用域回收空间(栈空间)
③.data:存放已初始化全局变量/静态变量
④.bss:存放未初始化全局变量/静态变量,启动代码时初始化为0
⑤.rodata:存放常量(字符串等),此区域内容不能修改
⑥static关键字:也存放在数据段,不是栈区,未经初始化值为0
作用:延长变量的生存周期,从函数定义到变量超过作用域被回收,但用static修饰,会在程序结束时回收空间
5.函数的传参
①值传参:形参改变,实参不会改变
②指针(数组名):通过*p可修改指针指向空间的内容
十、指针
(一)指针特点
1.让代码更加简洁高效
2.提供直接访问内存的操作
3.利用指针可以直接操作硬件
(二)指针概念
1.指针:指针就是地址,指针比地址多向了指向的概念
2.指针变量:存放指针(地址)的变量(64位操作系统中所有指针变量均为8个字节)
3.eg:int *p,p以8字节形式存储int数据的第一个字节地址,
(三)内存大小端
1.小端:内存低地址存放低数据位(PC,51),ARM可自选
2.大端:内存高地址存放高数据位
3.指针判断:int num = 0x1234类型强制转换为short类型用指针char *型指针指着,因为指针指向数据低地址位,用指针则可判断
4.共用体判断(union):它允许不同的数据类型共享同一块内存空间。即在联合体的多个成员,它们的起始地址是相同的,实际占用的内存空间大小是由其中占用内存最大的成员决定。
eg:
union s {
char a;
int b;
};
int main(void)
{
union s s1;
s1.b = 1;
if(s1.a)
printf("小端存储");
else
printf("大端存储");
return 0;
}
(四)指针相关运算符: int num(&num获得变量在空间的首地址,int->int *),int *p(*p,int * ->int)
(五)指针变量定义
1.野指针:未经初始化或者已经指向被释放空间的指针
2.空指针:指向内存地址为0x0的指针。内存地址0x0为只读空间,不能赋值,NULL不能指向*P = value的操作
(六)指针的算数运算
1.指针偏移大小由指针指向的数据类型大小决定
2.两指针相减得到的结果,两个地址相差的数据类型元素个数
(七)指针常见操作
1.修改指针变量p:改变了指针的指向
2.修改指针指向内容*p:改变了指针指向空间的内容
3.地址传参:可修改函数体外部变量的值
4.值传参:实参将值传给形参,形参值改变,实参不会改变
(八)指针和数组
1.数组的数组名:指向数组的第一个元素的指针常量
2.数组的数组名arr可理解为int *型,除以下两种情况:
①sizeof:sizeof(arr)== 数组整体空间大小,sizeof(int *) == 8
②&arr == int(*)[5],&(int *) == int **
(十)const指针
1.const int *p; int const *p(const修饰*p):指针变量p可变,p指向的空间内容不可变
2.int *const p(const修饰p):指针变量p不可变,p指向的空间内容可变。p必须初始化,不然后续无法赋值
3.const int *const p; int const *const p(const修饰*p和p):*p和p均不可变,p必须初始化,不然后续无法赋值
(十一)函数指针和指针函数
1.指针函数:是函数,函数返回值是指针
2.函数指针:是指针,指向函数的指针
(十二)指针数组和数组指针
1.指针数组(int *a[5]):是数组,数组每个元素为指针
2.数组指针(int (*a)[5]):是指针,指针指向整个数组
3.对一维数组数组名&:值不变,类型升级为指向整个数组的指针
4.对指针数组*:值不变,类型降级为指向数组第一个元素的指针
5.二维数组的的数组名:是指向第一行元素的数组指针
十一、构造数据类型
(一)结构体
1.结构体成员访问
① . :直接访问
② -> :指针访问
2.内存对齐:结构体的大小必须为自身最大类型长度的整数倍
3.结构体传参:传地址更好,因为实参将8字节拷贝给形参,避免结构体大空间的拷贝
(二)共用体:与结构体结构定义一样,成员后(;)结束,可用来判断内存大小端。使用为(,)
(三)枚举:成员后(,)结束,且最后一个数据无(,)。
1.枚举常量均为int类型,且第一个枚举常量的值默认为0,后续枚举常量的值总是前一个常量的值+1
2.枚举常量可以在定义时被赋值
十二、malloc
(一)功能:申请一段堆区空间,成功返回申请堆区空间首地址,失败返回NULL
(二)free:释放申请的堆区空间
(三)内存泄漏:只申请空间,使用完毕后,不释放空间,导致可用空间越来越少
(四)解决方法:valgrind工具检测(valgrind --tool=memcheck --leak-check=full ./a.out )
(五)原理
1.修改源程序连接的库,使得用户malloc调用valgrind自己写的malloc函数(具备内存管理和分析功能)
2.valgrind工具能够将没有释放的代码,或者有问题的代码标注出来,形成一份报告,显示在终端
(六)库:通过ldd(ldd a.out)可以查看可执行程序链接的库文件,文件名.a结尾
1.静态库:编译阶段将库中所有被依赖的代码完整复制到目标程序中,最终生成独立可执行文件
会使可执行程序代码体积增大
编译成可移植性程序后,删除库文件,对程序代码没有任何影响
程序代码编译好后(因为本身包含库文件),可以脱离库文件使用,免安装直接可以使用可执行程序
多个程序复用库时,内存冗余(每个程序都加载一份库代码
2.动态库:编译阶段仅记录库的 “引用关系”,程序运行时才加载库文件并调用其代码,文件名以.so结尾
不会使可执行文件体积增大
编译成可移植性程序后,删除库文件,对程序代码没有任何影响
程序代码编译好后,无法脱离库文件使用
多个程序代码可以共用一个库文件
十三、代码调试
(一)语法错误
(二)逻辑错误
1.打印
2.GDB调试
①gdb是Linux系统常用的调试工具
②gcc filename -g(file a.out可查看是否具有调试功能)
③gdb a.out
3.gdb常用命令
命令 功能
l 行号 查看代码
b 行号/函数名 设置断点
r 运行代码
n 单步执行
s 进入函数内部执行
p 变量名 查看变量名中的值
c 运行到下一处断点为止
q 退出调试
(三)段错误:操作非法内存空间,导致程序崩溃
1.打印
2.gdb调试段错误:程序出现段错误时会生成一个包含堆栈调用信息的core文件,通过core文件可以查找出错的位置
①配置系统让系统中允许产生core文件(需要修改系统配置,只需要第一次修改,后续无需再次修改)
②ulimit -c unlimited(允许生成core文件)
③gcc filename.c -g
④./a.out (查看是否产生core文件)
⑤gdb a.out core