Bootstrap

C语言高级进阶(全)

  • C语言高级
    • 一、指针和数组
      • 1.两种访问方式
        • 直接访问:按变量的地址存取变量的值(通过数组名访问)
        • 间接访问:通过存放变量地址的指针访问(通过指针访问)
      • 2.指针和一维数组
        • 2.1 用法
          • 通过数组名直接访问
            • int a[5] ={1,2,3,4,5}; //a是数组名也是数组的首地址
            • int *p = a;
            • 元素: a[i]和*(a+i)
            • 地址: &a[i] 和 a+i
          • 通过指针间接访问
            • int a[5] ={1,2,3,4,5}; //a是数组名也是数组的首地址
            • int *p = a;  
            • 元素: p[i]和*(p+i)
            • 地址:&p[i]和p+i
          • 注意:a和p数值上一样,但是本质不同。
          • (1)a是地址常量,p是指针变量。
          • (2)a不能执行++操作,但是p可以
        • 2.2 ++和*的运算方法
          • (1)++和*单目运算符
          • (2)单目运算符从右向左运算
          • (3)++在前面先++再取值,++在后面先取值再++。
          • (4)++结合的是内容则内容加加,++结合的是指针则指针加加。
      • 3.指针和二维数组
        • 3.1 二维数组名表示
          • int a[2][3] ={1,2,3,4,5,6};
          • a是数组名,表示第一行的地址,a+1表示第二行的地址
          • 在行地址前面加*表示将行地址降级为列地址。                                                                                                                     
          • *a: 表示的是第一行第一列的地址也就是a[0][0]的地址
          • *a+1: 表示的是第一行第二列的地址也就是a[0][1]的地址
          • *(a+1): 表示的是第二行第一列的地址也就是a[1][0]的地址
          • *(a+1)+1:表示的是第二行第二列的地址也就是a[1][1]的地址 
          • 元素:a[i][j]    *(*(a+i)+j)    *(a[i]+j)
          • 地址:  &a[i][j]  *(a+i)+j     a[i]+j
    • 二、数组指针
      • 1.定义
        • 本质是指针,指向的是数组。(又称行指针)
      • 2.格式
        • 存储类型 数据类型 (* 指针变量名)[列数];
        • 例如: int a[2][3] = {1,2,3,4,5,6}; int (*p)[3] = a; //p和a的类型int (*)[3];
          • 访问地址
            • &p[i][j]   
            • *(p+i)+j
            • p[i]+j
          • 访问元素
            • p[i][j]
            • *(*(p+i)+j)
            • *(p[i]+j)
          • 注意: p可以代替a进行元素的访问,但是本质不同,p是指针变量,而a是地址常量不能更改。
      • 3.大小
        • sizeof(p)=8
        • 因为本质还是指针,所以大小是8字节,如果是32位系统是4字节。
    • 三、指针数组
      • 1.定义
        • 所谓指针数组就是指由若干个具有相同存储类型和数据类型的指针变量构成的集合。 本质是数组,里面存放的是指针。
      • 2.定义格式
        • 存储类型 数据类型 *数组名[元素个数];
        • 例子: int *a[2];
      • 3.应用
        • 3.1 用于存放普通变量的地址
          • 例如: int a=10,b=20,c=30; int *p[3]={&a, &b, &c};
            • 访问b的值: *p[1]      **(p+1) //*(p+1)代表p[1]也就是&b
            • 访问b的地址: p[1]      *(p+1)
            • 访问abc的值:*p[i]    **(p+i)
            • 访问地址: p[i]    *(p+i)
        • 3.2 用于存放二维数组中每行第一列的地址(列地址)
          • 例如: int a[2][3] = {1,2,3,4,5,6}; int *p[2] = {*a,*(a+1)}; //或者a[0], a[1]
            • 访问地址: &p[i][j]   *(p+i)+j       p[i]+j
            • 访问元素:p[i][j]    *(*(p+i)+j)   *(p[i]+j)
        • 3.3 用于存放字符串
          • char *p[3] = {"hello","world","hq"};
            • p+1是p[1]的地址==>*(p+1)是p[1]也就是"world"的首地址
        • 3.4 命令行参数
          • int main(int argc, char const *argv[]) {  printf("%s\n", argv[0]); }
          • argv:就是一个指针数组,里面存放的是命令行传递的字符串
          • argc:表示argv指针数组里面存储数据的个数,即命令行传递字符串的个数
    • 四、函数
      • 1.函数基本用法
        • 1.1 定义和三要素
          • 函数是一个完成特定功能的代码模块,其程序代码独立,通常要求有返回值,也可以是空值。
          • 函数三要素: 功能、参数和返回值
          • 功能:函数要实现的功能
          • 参数:参数就是在函数声明时和函数调用时定义的变量。它用于传递信息给函数。
          • 参数分为实参和形式参数: 【1】实参是在函数调用中实际传递给函数的参数;而形式参数是函数声明中接受实参的参数。实参是有具体的数据。 【2】形参只是一个声明的形式,可以理解为一个符号名字。 返回值:函数返回值是函数调用后唯一留下的右值。(右值:只能放在运算符右边)
        • 1.2 函数的声明和定义
          • 1.2.1 函数声明
            • 存储类型 数据类型 函数名(数据类型 形参1, 数据类型 形参2, ...);
          • 1.2.2 函数定义格式
            • 存储类型 数据类型 函数名(数据类型 形参1, 数据类型 形参2, ...) {  函数体; }
            • 其中:
            • 函数名称:是一个标识符,要求符合标识符的名命规则。
            • 数据类型: 是整个函数返回值类型,如果没有返回值应该写为void。
            • 形式参数说明:是逗号分隔的多个变量的说明形式,通常简称为形参。
            • 形参:形式参数就是声明函数时指函数名后括号中的变量,因为形式参数只有在函数调用的过程中才实例化(分配内存单元),所以就叫形式参数。 大括弧对 {语句序列 },称为函数体,语句序列是大于等于零个语句构成的。
            • 注意:在函数体中,表达式语句里使用的变量必须事先已有说明,否则不能使用。 return后面加表达式,语句中表达式的值,要和函数的<数据类型>保持一致。
          • 1.2.3总结函数的数据
            • (1)没有参数:参数列表可以省略,也可以写成void
            • (2)没有返回值:数据类型为void, 函数内部没有return语句。
            • (3)有返回值:要根据返回值的数据类型定义函数的数据类型,函数内用return返回返回值。
            • (4)定义子函数时可以直接定义在函数上面,如果定义在函数下面了需要提前在上面声明。 类型为void可以省略或者无表达式结果返回。(即写成return;)
        • 1.3 函数调用
          • (1)没有返回值: 直接调用: 函数名(实参);
          • (2)有返回值: 如果需要接收返回值,就要在函数内定义一个和返回值类型相同的变量然后用return返回值出去,如果不需要接收返回值直接调用就可以了。
          • 实参:在调用有参函数时,函数名后面括号中的参数称为“实参”,是我们真实传给函数的参数,实参可以是:常量、变量、表达式、函数等。
          • 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
        • 1.4函数传参
          • (1)值传递
            • 单向传递,将实参这个数据传递给形参,函数内改变形参函数外的实参是不会受影响的,因为他们属于不同的空间。
          • (2)地址传递
            • 双向传递,在函数内通过传递的地址修改地址所指空间里面的内容,实参会随之变化。
          • (3)数组传递
            • 和地址传递是一样的,参数是地址也就是指针。
        • 1.5 函数和栈区(stack)
          • 1.5.1 栈区的概念
            • 栈用来存储函数内部的变量(包括main()函数)。它是一个FILO(First In Last Out,先进后出)的结构。每当一个函数声明一个新的变量它将被压入栈中。当一个函数运行结束后,这个函数所有在栈中相关的变量都将被删除,而且它们所占用的内存将会被释放。这就产生了函数内部的局部变量。栈区是一段非常特殊的内存区,它由CPU自动管理,所以你不必手动申请和释放内存。
          • 1.5.2 栈区的总结
            • (1)栈由CPU管理,无法修改
            • (2)变量自动地分配和释放
            • (3)栈区是有边界的
            • (4)栈区的变量只有在函数创建他们的时候存在
      • 2.开辟堆区(heap)空间
        • 2.1 堆区的概念
          • 申请的空间种分为五个区域栈区(堆栈区),堆区,全局区,常量区,代码区,我们之前讲的这些定义变量、数组都是在内存的栈区存储。
          • 堆区的特点:由我们程序员随时申请,由我们自己随时释放。
          • 堆区和栈区的区别:
          • 1、栈由系统自动分配,而堆是人为申请开辟;
          • 2、栈获得的空间较小,而堆获得的空间较大;
          • 3、栈由系统自动分配,速度较快,而堆一般速度比较慢;
          • 4、栈是连续的空间,而堆是不连续的空间是随机分配的。
        • 2.2 malloc函数
          • 2.2.1定义
            • #include <stdlib.h>
            • void *malloc(size_t size);
            • 功能:在堆区开辟大小为size的空间
            • 参数:size:开辟空间的大小(单位:字节)
            • 返回值: 成功:返回开辟空间的首地址 失败:NULL
            • malloc()要和free()搭配使用
            • size_t大小:printf("%d\n",sizeof(size_t)); //4字节
          • 2.2.2 用法
            • malloc内的参数是需要动态分配的字节数,而不是可以存储的元素个数!
            • 代码格式: 数据类型 *指针名 = (数据类型 *)malloc(n*sizeof(数据类型));
            • 注意:malloc开辟大小为元素个数*一个元素的字节数
          • 2.2.3 free函数用法
            • #include <stdlib.h>
            • void free(void *ptr);
            • 功能:释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。
            • 参数:ptr:堆区空间首地址
            • 返回值:无
            • 可以释放完以后赋值为空指针: free(p); //此时p变成野指针, p=NULL; //防止使用野指针
        • 2.3 函数中开辟堆区空间
          • 错误写法
          • 正确写法
      • 3.string 函数族
        • 3.1 strcpy
          • #include <string.h>
          • char *strcpy(char *dest, const char *src);
          • 功能:把整个字符串复制,包括\0。
          • 参数:char *dest :目标字符串首地址  ,const char *src:源字符串首地址
          • 返回值:目标字符串首地址
        • 3.2 strcat
          • #include <string.h>
          • char *strcat(char *dest, const char *src);
          • 功能:用于字符串拼接
          • 参数: char *dest:目标字符串首地址 ,const char *src: 源字符串首地址
          • 返回值:目标字符串首地址
        • 3.3 strcmp
          • #include <string.h>
          • int strcmp(const char *s1, const char *s2);
          • 功能:用于字符串比较
          • 参数:s1 s2用于比较多字符串
          • 返回值: 从字符串首个字符开始比较字符ASCII的大小,如果相等继续向后判断。 正数: s1>s2 0 : s1== s2 负数: s1<s2
          • 补充: int strncmp(const char *s1, const char *s2, size_t n); 比较两个字符串前n个字符的大小
        • 3.4 strlen
          • #include <string.h>
          • size_t strlen(const char *s);
          • 功能:计算字符串长度
          • 参数:s:字符串的首地址
          • 返回值:返回字符串实际长度,不包括‘\0’在内。
      • 4.递归函数
        • 4.1 概念
          • 什么是递归函数?
            • 所谓递归函数是指一个函数的函数体中直接调用或间接调用了该函数自身的函数。这里的直接调用是指一个函数的函数体中含有调用自身的语句,间接调用是指一个函数在函数体里有调用了其它函数,而其它函数又反过来调用了该函数的情况。
          • 如何理解
            • 1.从调用自身层面:函数递归就是函数自己调用自己。
            • 2.从编程技巧层面:一种方法(把一个大型复杂的程序转换为一个类似的小型简单的程序),这种方法的主要思想就是把大事化小,化繁为简。
        • 4.2执行过程
          • 递归函数调用的执行过程分为两个阶段:
          • (1)递推阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件。 就是从最里层开始算,然后一层一层算,直到终止。
          • (2)回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归到原问题求解。
    • 五、结构体struct
      • 1.结构体的基本用法
        • 1.1 定义
          • 结构体是由一批数据组合而成的结构型数据。组成结构型数据的每个数据称为结构型数据的“成员” ,其描述了一块内存区间的大小及解释意义。
        • 1.2 结构体定义格式
          • struct 结构体名 {  数据类型 成员变量1;  数据类型 成员变量2;  数据类型 成员变量3; };
        • 1.3 结构体变量
          • 1.3.1 概念
            • 通过结构体类型定义的变量
          • 1.3.2 格式
            • struct 结构体名 变量名;
          • 1.3.3 定义结构体变量
            • (1)先定义结构体,再定义结构体变量 struct 结构体名 {  成员变量; }; struct 结构体名 变量名;
            • (2)定义结构体的同时定义结构体变量 struct 结构体名 {  成员变量; }变量名;
            • (3)也可以缺省结构体名定义结构体变量 struct {  成员变量; }变量名;
          • 1.3.4 结构体变量赋值
            • 先定义一个结构体 struct student { char name[32]; int age; int score; };
              • (1)定义结构体变量的同时直接用花括号赋值
              • (2) 定义结构体变量的同时用点等法赋值
              • (3)定义结构体变量时未初始化赋值
          • 1.3.5 访问结构体成员变量
            • 通过.访问: 结构体变量名.成员变量名
        • 1.4 重定义typedef
          • 1.4.1 基本用法
            • (1).给普通变量重命名 typedef int size4;                                                                    size4 a = 10; //等同于int a=10;
            • (2). 给指针类型重命名 typedef int *int_p;                                                                    int_p p = &a; //等同于int *p=&a;
            • (3). 给数组类型重名 typedef int intArr5[5];                                                              intArr5 arr = {1, 2, 3, 4, 5}; //等同于int arr[5]={1,2,3,4,5};
          • 1.4.2 定义结构体的同时重定义
          • 1.4.3 先定义结构体,然后重定义
      • 2.结构体数组
        • 2.1 概念
          • 结构体类相同的变量组成的数组
        • 2.2 定义格式
          • (1)定义结构体同时定义结构体数组
            • struct student {  char name[32];  int age; }stu[3];
          • (2)先定义结构体,然后定义结构体数组
            • struct student {  char name[32];  int age; }; struct student stu[3];
        • 2.3 初始化
          • (1)定义结构体数组同时赋值
          • (2)先定义结构体数组,在对数组的每一个元素分别赋值
        • 2.4 结构体数组输入输出(for循环)
      • 3.结构体大小以及对齐原则
        • 3.1 结构体大小可以用sizeof得出
          • sizeof(struct 结构体名) 或者 sizeof(结构体变量名) 结构体数组:结构体类型大小*元素个数,也可以sizeof(数组名);
        • 3.2 字节对齐原则
          • (1).第一个成员在相对于结构体变量起始位置偏移量offset为0的地址处(通俗点来说,就是第一个成员变量的地址与结构体起始位置的地址是相同的),以后每个成员按定义顺序依次存放在offset为该数据成员大小的整数倍的地方,只有double类型存放在4的整数倍的地方(比如int在32位机器位4字节,要从4的整数倍开始存储)。
          • (2).用结构体里面最大的数据类型的大小和8(32位系统和4)进行比较,按照字节数小的为单位开辟空间。
            • 例如:
            • struct stu {     char a;     char b;     int c; }; //sizoef(struct stu) 等于8   
            • struct stu {     char a;     int c;     char b; };//sizoef(struct stu) 等于12
        • 3.3 节省空间原则
          • 为了减少空间浪费,把占用空间小的成员集中到一起。
      • 4.结构体指针
        • 4.1 概念
          • 指向结构体变量的指针
        • 4.2 定义格式
          • struct 结构体名* 结构体指针名;
        • 4.3 结构体指针成员变量赋值
          • 格式:指针变量名->成员变量名 p->age=10; strcpy(p->name,"xiaowang"); 或者:(*p).age=20; //不常用
        • 4.4 大小
          • 本质是指针,8字节(32位系统4字节)
        • 4.5用typedef同时给结构体以及结构体指针类型重命名
      • 5.结构体内结构体
        • 5.1概念
          • 如果成员变量本身属于另一种结构类型,用若干个成员运算符.一级一级找到最低级的成员变量。
        • 5.2关于结构体总结
          • 1.不能把结构体类型变量作为整体引用,只能对结构体类型变量中的各个成员变量分别引用。
          • 2.如果成员变量本身属于另一种结构体类型,用若干个成员运算符.一级级找到最低级的成员变量。
          • 3.可以把成员变量当成普通变量运算
    • 六、共用体
      • 1.概念
        • 共用体有时候也称为联合体,用法类似结构体struct,区别在于结构体所占内存大于等于所有成员占用内存总和。而共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。 共用体大小是其中最大的成员大小,如果修改其中某个成员的值其他成员也随之修改。
      • 2.格式
        • union 共用体名 {  成员列表; };
      • 3.定义共用体union变量
      • 4.特性
        • (1)共用体成员共用一块地址空间
        • (2)赋值顺序以最后一次赋值为准
        • (3)共用体大小为成员类型最大的数据大小
    • 七、枚举
      • 1.定义
        • 用户自定义数据类型,可以用于声明一组常数。
          • 以每周七天为例,我们可以使用#define命令来给每天指定一个名字:  
          • #define Mon 1  
          • #define Tues 2  
          • #define Wed 3  
          • #define Thurs 4  
          • #define Fri 5  
          • #define Sat 6  
          • #define Sun 7
          • #define命令虽然能解决问题,但也带来了不小的副作用,导致宏名过多,代码松散,看起来总有点不舒服。C语言提供了一种枚举(Enum)类型,能够列出所有可能的取值,并给它们取一个名字。
      • 2.格式
        • enum 枚举名 {  value1,  value2, value3, ... };
        • 未赋初值时,第一个常数会默认为0,依次加一。可以每个元素都赋值,如果有元素没有赋值则默认值为上一个元素的值加一。
    • 八、数据类型总结
      • 1.基本数据类型
        • 基本数据类型最主要的特点是其值不可以再分解为其它类型。也就是说,基本数据类型是自我说明的。如char, short, int, long, float, double, signed, unsigned。
      • 2.构造数据类型
        • 构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。如struct结构体、union联合体、enum枚举和数组。
      • 3.指针类型
        • 指针是一种特殊的数据类型。其值用来表示某个变量在内存中的地址。虽然指针变量的取值类似于整型量,但这是两个类型完全不同的量,因此不能混为一谈,定义时要用星号*。
      • 4.空类型
        • 空类型void用来定义任意类型的指针和无返回值的函数。
    • 九、存储类型
      • 1.存储类型:auto static extern register
        • 1.1 auto
          • 修饰变量,一般省略时会认为是auto类型。其特点是当执行流程进入该语句块的时候初始化可为任何表达式。
          • auto 数据类型变量名/指针/数组;
          • (auto) int a;
          • 放在栈区。
        • 1.2 static
          • 可以修饰变量函数
            • 1.2.1 修饰变量
              • 1. 变量的存放位置在全局区(静态区) 如果静态变量有初值,存放.data区,没有初值存放在.bss区域
              • 2. 生命周期为整个程序
              • 3. 限制作用域: 修饰局部变量,和普通局部变量作用域没有区别,但是生命周期被延长为整个程序。 修饰全局变量,限制在本文件中使用。
              • 4.只初始化一次,初值赋值0。
            • 1.2.2 修饰函数
              • static修饰函数,限制在本文件使用。 总的来说,使用 static 定义的变量存储在静态存储区(全局区)中,具有静态持续性,即使函数返回,它的值也会保留。而且,static 变量只能在当前文件中访问,不能被其他文件访问。
        • 1.3 extern
          • 外部引用: 通过extern可以引用其他文件的全局变量或者函数
            • 注意:编译的时候要同时编译包含外部引用内容的文件
        • 1.4 register寄存器类型
          • register是寄存器类型,顾名思义,是把修饰的变量放到寄存器中,目的是为了提高程序的运行效率。但要记住,不管是单片机也好,计算机也罢,任何CPU的寄存器资源都是有限的,如果寄存器满了,被修饰的变量就会默认回到auto类型。
    • 十、分文件编程
      • 1.头文件: .h结尾的文件
        • 函数声明、头文件、宏定义、typedef、结构体、共用体以及枚举的定义和外部引用。 全局变量一般不会放在头文件中。
      • 2.源文件: .c结尾的文件
        • 包含main函数的.c文件: main函数
        • 包含子函数的.c文件: 子函数
    • 十一、编译工具
      • 1.gcc 编译工具
        • 预处理: 展开头文件,替换宏定义,删除注释,不会进行语法检查,处理#开头的语句。 gcc -E xx.c -o xx.i
        • 编译: 检查语法错误,将.i文件转换成.s汇编文件。 gcc -S xx.i -o xx.s
        • 汇编:将汇编文件生成二进制文件(不可执行) 。gcc -c xx.s -o xx.o
        • 链接: 链接库文件,将不可执行的二进制文件转换成可执行的二进制文件。    gcc xx.o -o xx,   gcc xx.c 默认生成a.out,  gcc xx.c -o xx生成一个新名字的可执行文件。
      • 2.gdb调试工具
        • gcc -g xx.c 生成a.out
        • gdb a.out      
        • r:运行代码    
        •  l: 查看文件    
        •  b 行号或者函数名:添加断点      
        • info b:查看断点情况      
        • d num(断点编号):删除断点      
        • p 变量名:查看变量的值    
        •  s/n: 单步运行,s会进入子函数顺序执行;单步跳过,n不会进入子函数    
        •  c: 执行到下一个断点的位置    
        •  help:帮助      
        • q: 退出
        • 步骤:代码写好后,先通过gcc -g进行编译代码,没有错误后,可以通过gdb a.out进行调试,输入l查看文件内容,通过b设置断点,输入r进行运行,n或s进行单步调试,q退出。
      • 3.make工具
        • 3.1 定义
          • make:工程管理器,顾名思义,是指管理较多的文件。
          • make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件文件的内容来执行大量的编译工作。
          • Makefile或makefile时make读取的唯一配置文件。
        • 3.2 Makefile格式
          • 目标:  依赖          命令
          • 注意:命令前面要用TAB键
          • 理解:通俗一点讲就是 依赖(相当于原材料) -> 命令(加工)-> 目标(产物)
          • makefile根据以下步骤写: gcc -c xx.c -o xx.o gcc xx.o -o xx
          • 使用make命令
            • 目标加:是伪目标
            • 伪目标:它的目的并不是创建目标文件(所以称作“伪”),而是想去执行这个目标下面的命令。
              • 执行: make clean
              • 规则中 rm 命令不是为了创建 clean 这个文件,而是执行删除某些文件的任务。当工作目录中不存在以 clean 命令的文件时,输入 make clean 命令,命令 rm *.o test会被执行。
              • 如果避免同名文件可以加:.PHONY:clean 
        • 3.3 用make管理多个文件
          • 先分文件编程test.c和add.c, 再建立makefile管理:
            • 执行:make
            • 清除中间文件:make clean
            • .PHONY:clean 为了避免路径中出现同名文件
        • 3.4 makefile变量
          • 3.4.1 自定义变量
            • 自己定义的变量: 一般用大写表示变量名, 取变量值用$(变量名)
            • (1) = 递归方式展开
                • 加-s只输出结果不输出命令
            • (2):= 直接赋值(当前的值是什么就立即赋值)
            • (3)+= 追加新的值
            • (4)?= 判断之前是否定义,如果定义了,不重新赋值,否则赋值
          • 3.4.2 预定义变量
            • 系统预先定义好的一些变量,可能有默认值可能没有默认
            • RM 文件删除程序的名称,默认值为rm -f
            • CC C编译器的名称,默认值为cc。
            • CPP C预编译器的名称,默认值为$(CC) –E。
            • CFLAGS C编译器的选项,无默认值。
            • OBJS 生成的二进制文件或目标文件,自己定义
          • 3.4.3 自动变量
            • $< 第一个依赖文件的名称
            • $@ 目标文件的完整名称
            • $^ 所有不重复的依赖文件,以空格分开
            • 可以用%.c和%.o代替每一个.c和.o文件: 相当于让每个.c生成各自的.o
        • 3.5 make指令
          • make -s: 隐藏执行的命令
          • make -C 路径:进入指定的路径去执行make指令
    • 十二、指针函数
      • 1.概念
        • 本质是函数,函数的返回值是指针。
      • 2.定义格式
        • 数据类型 *函数名(参数列表) {  函数体;  return 地址; //当失败时一般返回NULL }
      • 3.函数内开辟堆区空间
        • 案例一:
            • 结果未知,因为返回局部变量地址,函数调用结束后没有权限使用栈区的空间了所以结果是不可控的。如果被其他进程占用,可能会报段错误或者打印奇怪的东西。
          • 修改:可以返回堆区空间的地址
        • 案例二:
            • 报段错误:因为q指向NULL, 函数内改变形参p的指向不会影响到q,函数调用结束q还是指向NULL。
          • 修改:传递二级指针
        • 案例三: 数组传递
            • 传递数组形式是为了让用户知道要传递的是一个数组,但本质还是传递数组首地址也就是本质是指针。
    • 十三、函数指针
      • 1.概念
        • 本质是指针,指向了函数。
      • 2.定义格式
        • 数据类型 (*指针名)(参数列表);
      • 3.案例
        • 3.1函数名: 函数地址
        • 3.2把函数指针当成参数传递给函数。一种接口, 多种方法。
        • 3.3结构体内成员是函数指针(不常用但是看内核原码可能会见到)
    • 十四、函数指针数组
      • 1.概念
        • 本质是数组,数组中元素是函数指针。
      • 2.格式
        • 数据类型 (*数组名[元素个数])(形参列表);
        • 数据类型: 和数组中函数指针指向的函数的返回值一致
        • 形参列表: 和数组中函数指针指向的函数的形参一致
      • 3.赋值
        • int (*arr[3])(int,int)={函数名列表};
      • 4.练习
        • (a) 一个整型数   int a;
        • (b) 一个指向整型的指针  int *p;
        • (c)一个指向指针的指针,它指向的指针是一个指向一个整型       int **p;
        • (d)一个有10个整型数的数组  int a[10];
        • (e)一个有10个指针的数组,该指针是指向一个整型数的  int *arr[10];
        • (f)一个指向有10个整型数数组的指针 int a[10]; int (*p)[10]=&a;//相当于升级,可以把列地址升级成行地址。或者: int arr[1][10]; int (*p)[10]=arr;
        • (g)一个指向函数的指针, 该函数有一个整型参数并返回一个整型数  int (*p)(int);
        • (h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数  int (*p[10])(int);
    • 十五、条件编译
      • 1.概念
        • 按照条件是否满足决定代码是否被编译,是预处理指令。 简单来说就是 后面的条件语句(condition)如果执行结果不为0,则该#if语句块内的代码会被编译,否则就不会被编译。
      • 2.根据宏是否定义
        • #define 宏名
        • #ifdef 宏名  代码块1
        • #else  代码块2
        • #endif
        • 执行顺序: 宏名如果链接定义则编译代码块1,否则编译代码块2。
      • 3.根据宏值
        • #define 宏 值
        • #if 宏名  代码块1
        • #else  代码块2
        • #endif
        • 执行顺序:宏的值为非0也就是真则编译代码块1,否则编译代码块2。
      • 4.防止头文件重复包含
        • 放在头文件中:
        • #ifndef 宏名
        • #define 宏名  头文件中语句
        • #endif
        • 例子:
            • 报错是因为结构体stu重复定义,因为包含结构体定义的head.h被多次引用。
          • 修改:加上防止头文集重复包含