C 程序编译完整流程
C源文件(.c,ASCII文本) → 预处理 → 编译 → 目标文件 (.obj Windows / .o Linux) → 链接 → 可执行文件 (.exe)
(.c源文件属于文本文件,代码里的字符(字母、分号等)都是以 ASCII 码保存,不是二进制机器码)
GCC编译过程通常分为以下几个阶段:
预处理:预处理器(如cpp)会处理源代码中的宏定义、文件包含(#include)、条件编译等指令,将它们展开,并生成一个纯粹的C语言代码文件。
编译:编译器将经过预处理的源代码转换成汇编语言代码。这个阶段主要是语法分析、语义分析和生成汇编代码。
汇编:汇编器将汇编代码转换为机器代码,也就是目标文件(.o)文件。
链接:链接器将目标文件和库文件连接,生成最终的可执行文件。
结构体和联合体
结构体 (struct) 是一种聚合数据结构,它能够将不同类型的数据组合在一起,每个成员都有独立的内存空间。
联合体 (union) 也是一种聚合数据结构,它与结构体类似,但联合体的所有成员共享同一块内存空间,即所有成员共用同一内存地址,只有一个成员能够在任何时刻存储值。
结构体适用于需要同时存储多个不同数据类型的情况,而联合体适用于在同一内存空间中存储不同类型的数据,但一次只能使用其中一个成员。
const 和 static 的区别
一句话表述:
const 用来修饰【只读】;static 用来修饰【作用域 / 生命周期】
1. const(只读,常量属性)
作用:限定变量的值不能被修改,本质还是变量,只是只读。
1.修饰局部变量:该变量在栈上,不能赋值修改;
const int a = 10;
a = 20; // 编译报错,不能修改
2.修饰指针:区分 const int *p / int * const p
3.修饰全局变量:全局只读变量
2. static(静态,控制生命周期 + 作用域)
两个作用,分场景:
1.修饰局部变量(函数内部)
生命周期:程序整个运行期间都存在,不再是函数调用时创建、退出销毁,只初始化一次
作用域:仍然只能在本函数内部访问
voidfunc(){staticinta=0;// 只初始化1次a++;printf("%d",a);}2.修饰全局变量 / 函数(外部)
作用域限制:只能在当前.c 源文件内使用,其他文件extern也无法访问(内部链接属性)
生命周期:全局,程序运行全程存在
static不限制读写,不加 const 的 static 变量可以正常修改。
## 运算符小技巧 用一个char控制8个开关 #define OPEN_FILE = 0x1; #define DROP_FILE = 0x2; #define OFFICE_FILE = 0x4; 定义一个char, char pro = 0000 0000; 打开一个开关 pro = pro | DROP_FILE; 打开多个开关 pro = pro |(DROP_FILE|OFFICE_FILE); 关闭一个开关 pro = pro &~DROP_FILE; 关闭多个开关 pro = pro &~(DROP_FILE|OFFICE_FILE); ## 数组 1.数组的基本 数组的名字的含义是指数组第一个元素的地址, int arr[5]; arr 和 &arr什么区别? arr是第一个元素的地址 &arr整个数组的首地址 数据是相同的; 2.二维数组 二维数组的每一项都是一个数组 arr[2][3] = {1,2,3},{4,5,6}; arr[1][1] = 5; 同理为 *(*(arr+1)+1) 3.字符数组和字符串的区别?字符串一定是\0结尾 strcpy //字符串复制 strcmp //字符串比较函数 strcat //字符串拼接函数 以上三个函数能够自己编码实现,使用都有风险 建议使用strncpy strncat strncmp strlen //计算字符串长度 memset //内存设置函数,一般用来置零 memset(buf, 0, sizeof(buf)); 4.聚合数据类型: 结构体:多个类型打包成一个整体,用来表示复杂类型 定义结构体: struct Student { char name[20], int age, int id; }st2, *st3; struct Student st; 结构体名字: Student 结构体类型:struct Student 结构体变量:st、st2、st3 st3是指针类型 typedef struct Student { char name[20], int age, int id; }St, *St2; St stu; St2 stu2 结构体名字:Student 结构体类型:1、struct Student 2、St 3、St2 (指针类型) 结构体变量:stu、stu2 //stu2是指针 结构体大小计算: 1、结构体的总大小一定是结构体内最大的内置类型的倍数; 2、结构体的每个元素位置一定是元素类型大小的倍数,要求起始位置是最大的内置类型的倍数; 联合体:多个成员共享同一个内存空间,大小按最大的算 枚举:整数,从0开始递增 ## 函数 组成:返回值 + 函数名 + 参数列表 + 函数体 函数名:函数的入口地址 函数的设计: 1、函数的执行结果 1、返回值带回 2、出参 2、函数需要的参数 1、入参 3、函数是否运行成功 1、返回值带回 2、出参 递归函数: 自己调用自己,要有明确的返回 如果函数一直调用自己,不反回,会发生段错误:栈溢出 ## 指针 概念:保存地址的变量 指针的运算:指针+1 实际加的数 是指针指向类型的字节大小 野指针:保存的地址不知道是谁 使用野指针的后果:不可预计的后果(严重的内存运行问题) 野指针规避:使用完指针后置 NULL==》(void *)0 定义指针要初始化 二级指针:保证指针的地址 指针函数:返回值是指针的函数 (少见概念) 函数指针:保存函数的地址 int Show(int); int (*funcP)(int) = Show;//定义函数指针funcP,保存Show函数地址 funcP(20); //等价于Show(20); 指针数组:数组的每一项都是一个指针 int *p[5]; 数组指针:指向数组的指针 int (*p)[5]; 指针的用法: 1、堆区内存管理 malloc/calloc申请空间 free释放 2、管理数组 函数传递数组首地址,遍历数组 3、函数的地址传递 用于出参 4、函数指针 调用未知函数名但已知地址的函数 代替switch菜单选择