C语言知识体系梳理-第一篇

C常见概念方面
简单错误梳理
C 数据类型和变量方面
注意事项梳理
char 类型的注意事项
scanf 函数的注意事项
占位符列举的注意事项
分支循环语句方面
注意事项梳理
if 语句的注意事项
关系操作符的注意事项
switch 语句的注意事项
区别
if 和 while 的对比
while 循环和for 循环中的continue的对比
数组方面
注意事项梳理
数组传参的注意事项
函数方面
注意事项梳理
return 语句的注意事项
static的注意事项
程序调试方面
内存布局
常见错误归类
编译时错误
链接时错误
运⾏时错误
函数递归方面
注意事项梳理
函数递归的注意事项
操作符方面
两个问题
数据在内存中为什么是以补码的形式存放的?
整型提升的意义是什么?
一个总结
指针方面
指针的本质
指针变量的大小
指针变量类型的意义
指针+=整数
void*指针
void*指针的作用
const 修饰指针变量
导致野指针的情况
规避野指针的方法
指针初始化
小心指针越界访问
指针变量不再使用时,及时置NULL,指针使用之前检查有效性
避免返回局部变量的地址
传值调用与传址调用的区别
数组名的意义
数组传参
函数指针变量
数组指针与函数指针写法的区别
什么是回调函数
sizeof 与strlen 的区别
以下是关于C的概念梳理、知识要点、易混淆概念区别的总结
C常见概念方面
简单错误梳理
- main 被写成了mian
- main后边的()漏掉了
- 代码中不能使用中文符号,比如括号和分号
- ⼀条语句结束后,有分号
#include <iostream>
int main(){return 0;
} C 数据类型和变量方面
注意事项梳理
char 类型的注意事项
这⼀点与 int 不同, int 就是等同于 signed int 。
signed char c; // 范围为 -128 到 127
unsigned char c; // 范围为0 到 255 scanf 函数的注意事项
scanf函数原型如下图所示:
scanf("%d", &i); 变量前面必须加上 & 运算符(指针变量除外),因为 scanf() 传递的不是值,而是地址,即将变量 i 的地址指向用户输⼊的值。 如果这里的变量是指针变量(比如字符串变量),那就不⽤加 & 运算符。
占位符列举的注意事项
分支循环语句方面
注意事项梳理
if 语句的注意事项
#include <iostream>
int main(){if(表达式1){//逻辑处理}else if(表达式2){//逻辑处理}else{//逻辑处理}return 0;
} 关系操作符的注意事项
if(a = b) {}
if(a == b) {} 以上的两句代码是不同的!!一个是赋值运算符,一个是比较大小的相等运算符。
switch 语句的注意事项
- case 和后边的数字之间必须有空格
- 每⼀个 case 语句中的代码执行完成后,需要加上 break ,才能跳出这个 switch 语句。
switch (number){case 0://逻辑处理break;case 1://逻辑处理break;default://逻辑处理break;
} 区别
if 和 while 的对比
if(表达式)语句;
while(表达式)语句;//如果循环体想包含更多的语句,可以加上⼤括号 while 语句可以实现循环效果,if 语句不可以。
while 循环和for 循环中的continue的对比
while循环




数组方面
注意事项梳理
数组传参的注意事项
- 函数的形式参数要和函数的实参个数匹配
- 函数的实参是数组,形参也是可以写成数组形式的(一般不这么做,这么做传参涉及开空间,效率低下,一般传参使用指针解决)
- 形参如果是⼀维数组,数组大小可以省略不写
- 形参如果是⼆维数组,行可以省略,但是列不能省略
- 数组传参,形参是不会创建新的数组的
- 形参操作的数组和实参的数组是同⼀个数组
void set_arr(int arr[], int sz);
void set_arr(int* arr, int sz);//推荐使用 函数方面
注意事项梳理
return 语句的注意事项
- return 后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执行表达式,再返回表达式的结果。
- return 后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是 void 的情况。
- return 返回的值和函数返回类型不⼀致,系统会自动将返回的值隐式转换为函数的返回类型。
- return 语句执行后,函数就彻底返回,后边的代码不再执行。
- 如果函数中存在 if 等分⽀的语句,则要保证每种情况下都有 return 返回,否则会出现编译错误。
static的注意事项
- 如果未来⼀个变量出了函数作用域后,我们还想保留值,等下次进⼊函数继续使用,就可以使用 static 修饰。
- 如果⼀个全局变量,只想在所在的源⽂件内部使用,不想被其他⽂件发现,就可以使用 static 修饰。
- 如果⼀个函数只想在所在的源文件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修 饰。

程序调试方面
内存布局
计算机的内存是如何布局的?

#include <stdio.h>
int main()
{int i = 0;int arr[10] = {1,2,3,4,5,6,7,8,9,10};for(i=0; i<=12; i++){arr[i] = 0;printf("hehe\n");}return 0;
} 

栈区:栈区内存的使⽤习惯是从高地址向低地址使⽤的,所以变量i的地址是较大的。arr 数组的地址整体是小于 i 的地址。
栈区的默认的使用习惯是先使用高地址,再使用低地址的空间,但是这个具体还是要编译器的实现,比如:在VS上切换到X64,这个使⽤的顺序就是相反的,在Release版本的程序中,这个使用的顺序也是相反的,但Relase版本的程序不支持调试,这个顺序也就没有意义了。
常见错误归类
编译时错误

链接时错误


运⾏时错误
运⾏时错误,是千变万化的,需要借助调试,逐步定位问题,调试解决的是运行时问题。
函数递归方面
注意事项梳理
函数递归的注意事项
一句话:能用迭代的地方尽量使用迭代,但该用递归的地方,还得使用递归。但使用递归时要注意:递归函数调⽤的过程中涉及⼀些运⾏时的开销。
在C语⾔中每⼀次函数调用,都要需要为本次函数调用在栈区申请⼀块内存空间来保存函数调用间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就⼀直占用,所以如果函数调⽤中存在递归调用的话,每⼀次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题。

操作符方面
两个问题
数据在内存中为什么是以补码的形式存放的?
在计算机系统中,数值⼀律⽤补码来表示和存储。原因在于:使⽤补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算 过程是相同的,不需要额外的硬件电路。
整型提升的意义是什么?
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度⼀ 般就是int的字节长度,同时也是CPU的通⽤寄存器的长度。因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准长度。通⽤CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执行运算。
一个总结
int ret = (++i) + (++i) + (++i); 指针方面
指针的本质

指针变量的大小
- 32位平台下地址是32个bit位,指针变量大小是4个字节
- 64位平台下地址是64个bit位,指针变量大小是8个字节
- 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
指针变量类型的意义
指针+=整数
void*指针
但是也有局限性, void* 类型的指针不能直接进行指针的+-整数和解引用的运算。
void*指针的作用

const 修饰指针变量
- 如果const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。(底层const)
- 如果const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。(顶层const)
导致野指针的情况
- 指针未初始化
- 指针越界访问
- 指针指向的空间释放
规避野指针的方法
指针初始化
#include <stdio.h>
int main()
{int num = 10;int*p1 = #int*p2 = NULL;return 0;
} 小心指针越界访问
指针变量不再使用时,及时置NULL,指针使用之前检查有效性
避免返回局部变量的地址
传值调用与传址调用的区别
传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。所以未来函数中如果只是需要主调函数中的变量值来实现计算,就可以采⽤传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。
数组名的意义
- sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表示整个数组,计算的是整个数组的大大小,单位是字节
- &数组名,这⾥的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
数组传参
void test(int arr[]);//参数写成数组形式,本质上还是指针
void test(int* arr);//参数写成指针形式 二维数组传参的本质也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。
因此,⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。
void test(int p[3][5], int r, int c);//数组形式
void test(int (*p)[5], int r, int c);//指针形式 函数指针变量
数组指针与函数指针写法的区别
typedef int(*parr_t)[5]; //新的类型名必须在*的右边 函数指针void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void(*pfun_t)(int);//新的类型名必须在*的右边 什么是回调函数
sizeof 与strlen 的区别
strlen 是C语言库函数,功能是求字符串长度。
函数原型:
| sizeof | strlen |
| 1、sizeof 是操作符 2、计算的是操作数所占内存的大小,单位是字节 3、不关注内存中存放的是什么数据 | 1、strlen是库函数使用需要包含头文件string.h / cstring 2、strlen是求字符串长度的,统计的是‘\0’之前字符的个数 3、关注内存中是否有‘\0’,如果没有‘\0’,就会持续往后找,可能会越界 |
