C语言提高学习及总结

    科技2022-08-08  92

    1 导读

    该提高部分主要从基本知识的强化入手,对C语言的基础知识难点进行进一步加强。主要包括:内存管理(内存四区)、指针的强化(指针是C语言的半壁江山)、多级指针、多维数组(数组指针、指针数组)、结构体(结构体内嵌套指针的深浅拷贝)、数据结构的入门(链表)及函数指针(回调函数:C语言的另外半壁江山)和函数的递归。

    2 C语言提高

    提高部分需要对基础知识掌握后再去学习,学习的判断标准可以自我检测(基础知识需要铭记于心),也可以通过基本算法进行判读(基本排序:冒泡和选择法排序的思想及代码需要非常熟练)才可以学习这一部分。

    2.1 内存管理(内存四区)

    2.1.1、标准 需要自己熟练掌握冒泡和选择排序的思想及代码、会封装函数及函数参数中的数组退化问题。 void fun(int a[]);// 4字节 int a[] = {1, 2, 3}; // 12个字节 2.1.2、数据类型 需要掌握类型的本质和作用,自定义类型和sizeof关键字,void类型的使用。 a)如果函数没有返回值,必须用void修饰: void fun(int a); b)如果函数没有参数,参数可以用void修饰: int fun(void); c)不能定义void类型的普通变量, void a; //error,不能确定分配多大空间 d)void p; //ok, 万能指针, 指针类型都是 4 个指针,函数参数,函数返回值 int b[10]; //b, &b的数组类型不一样 //b, 数组首元素地址, 一个元素4字节,+1, +4 //&b, 整个数组的首地址,一个数组410 = 40字节,+1, +40

    2.1.3、变量 需要掌握变量的本质、定义和赋值。 4)变量的赋值:1)直接 2)通过指针间接 4)重点:没有变量,哪来内存,没有内存,哪里内存首地址 5)变量三要素(名称、大小、作用域),变量的生命周期

    2.1.4、内存四区(栈区、堆区、全局区、代码区) 1)栈区:系统分配空间,系统自动回收,函数内部定义的变量,函数参数,函数结束,其内部变量生命周期结束 2)堆区:程序员动态分配空间,由程序员手动释放,没有手动释放,分配的空间一直可用 3)全局区(全局变量和静态变量,里面又分为初始化区和未初始化区,文字常量区:字符常量):整个程序运行完毕,系统自动回收 4)内存四区模型图(C语言学好指针的关键) 5)a) 栈区地址生长方向:地址由上往下递减 b) 堆区地址生成方向:地址由下往上递增 c) 数组buf, buf+1 地址永远递增

    2.1.5、函数调用模型 1)程序各个函数运行流程(压栈弹栈,入栈出栈,先进后出)

    2.2 指针的强化

    2.2.1、指针强化 需要掌握指针的本质、定义和使用。还有就是指针变量和指针指向的内存、使用指针操作写内存时需要注意该内存是否可写(定义在文字常量区)。 通过指针进行间接赋值的三大条件。 重要:如果想通过函数形参改变实参的值,必须传地址 1、值传递,形参的任何修改不会影响到实参 2、地址传递,形参(通过*操作符号)的任何修改会影响到实参 3、不允许向NULL和未知非法地址拷贝内存 4、void *指针的使用:可以指向任何指针和地址,但使用时尽量进行转换。 5、栈区返回变量的值和变量的地址区别 6、.c -> 可执行程序过程 预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法 编译:检查语法,将预处理后文件编译生成汇编文件 汇编:将汇编文件生成目标文件(二进制文件) 链接:将目标文件链接为可执行程序 程序只有在运行才加载到内存(由系统完成),但是某个变量具体分配多大,是在编译阶段就已经确定了,换句话说,在编译阶段做完处理后,程序运行时系统才知道分配多大的空间,所以,很多时候说,这个变量的空间在编译时就分配(确定)了。

    7、 指针做函数参数的输入输出特性 输入:主调函数分配内存 输出:被调用函数分配内存

    8、变量内存的值和变量的地址 int a = 0; a变量内存的值为0 a变量的地址(&a)绝对不为0,只要定义了变量,系统会自动为其分配空间(一个合法不为0的地址)

    2.2.2、字符串操作 需要掌握字符串的初始化以及字符串与字符数组的区别、sizeof和strlen的区别、数组法和指针法操作字符串、字符串拷贝函数、一些常用字符串应用模型:使用标准库函数找字符串中另一个字符(串)的个数,两头堵模型。

    2.3 多级指针

    2.3.1、const的使用:常量指针和指针常量的使用与注意事项。

    2.3.2、多级指针 1)如何定义合适类型的指针变量 //某个变量的地址需要定义一个怎么样类型的变量保存 //在这个类型的基础上加一个*

    2)二级指针做输出 输入:主调函数分配内存 输出:被调用函数分配内存

    3)二级指针做输入的三种内存模型 指针数组做输入、二维数组做输入、在堆上打造二维数组做输入。

    2.4 多维数组

    2.4.1 需要掌握一维数组的定义、初始化和使用。数组类型和数组名及数组名取地址的含义、数组类型的定义。 2.4.2 数组指针变量的定义(三种):后两种常用。 1、先定义数组类型,再定义数组指针变量。 2、先定义数组指针类型,再定义变量。 3、直接定义数组指针变量。 2.4.3 多维数组的本质、定义、初始化及使用。 int a[3][5] = { 0 }; a: 二维数组首元素地址 代表首行地址,相当于一维数组整个数组的地址,相当于上面的 &b,本来就是一个二级指针 //(重要)首行地址 --> 首行首元素地址(加*) a:首行首元素地址,相当于一维数组首元素地址,相当于上面的 b a + i -> &a[i]: 第i行地址 //(重要)某行地址 --> 某行首元素地址(加) *(a+i) -> *&a[i] -> a[i]: 第i行首元素地址 //第i行j列元素的地址,某行首元素地址 + 偏移量 *(a+i)+j -> a[i]+j -> &a[i][j]: 第i行j列元素的地址 //第i行j列元素的值,第i行j列元素的地址的基础上(加 *) ((a+i)+j) -> a[i][j]: 第i行j列元素的值 int a[3][5] = { 0 }; sizeof(a): 二维数组整个数组长度,4 * 3 * 5 = 60 sizeof(a[0]):a[0]为第0行首元素地址,相当于测第0行一维数组的长度:4 * 5 = 20 sizeof(a[0][0]):a[0][0]为第0第0列元素(是元素,不是地址),测某个元素长度:4字节 多维数组名,实际上是一个数组指针,指向数组的指针,步长为一行字节长度。

    2.4.4 二维数组做形参的三种形式: //一维数组做函数参数退化为一级指针 //二维数组(多维数组)做函数参数,退化为数组指针 int a[3][5] = { 0 }; void print_array1(int a[3][5]); //第一维的数组,可以不写 //第二维必须写,代表步长,确定指针+1的步长 5*4 void print_array2(int a[][5])

    //形参为数组指针变量,[]的数字代表步长 void print_array3(int (*a)[5]); //a+1和二维数组的步长不一样 //这里的步长为4 //上面二维数组的步长为 5 * 4 = 20 void print_array3(int **a); //err

    2.4.5 指针数组(它是数组,每个元素都是指针)的定义、初始化和使用。 1)指针数组的定义 //指针数组变量 //[]优先级比*高,它是数组,每个元素都是指针(char *) char *str[] = { “111”, “2222222” }; char **str = { “111”, “2222222” }; //err

    2)指针数组做形参 void fun(char *str[]); void fun(char **str); //str[] -> *str 3)main函数的指针数组 //argc: 传参数的个数(包含可执行程序) //argv:指针数组,指向输入的参数 int main(int argc, char *argv[]);

    2.5 结构体

    2.5.1、结构体类型基本操作:定义(struct关键字、使用typedef定义别名);结构体变量的定义:先定义类型再定义变量、定义类型的同时定义变量;结构体变量初始化(定义时初始化);结构体的使用:变量法和指针法操作结构体成员;结构体数组的定义及初始化。

    2.5.2 结构体嵌套指针、结构体赋值、结构体的浅拷贝和深拷贝。

    2.5.3、结构体字节对齐(以空间换时间)。

    2.6 链表入门及函数指针

    2.6.1、链表 1、数组和链表的区别 数组:一次性分配一块连续的存储区域 优点: 随机访问元素效率高 缺点: 需要分配一块连续的存储区域(很大区域,有可能分配失败) 删除和插入某个元素效率低

    链表:现实生活中的灯笼 优点: 不需要一块连续的存储区域 删除和插入某个元素效率高 缺点: 随机访问元素效率低

    2、相关概念 节点:链表的每个节点实际上是一个结构体变量,节点,既有 数据域 也有 指针域 typedef struct Node { int id; //数据域 struct Node *next; //指针域 }SLIST;

    尾结点:next指针指向NULL

    3、结构体套结构体 typedef struct A { int b; }A; /* 1、结构体可以嵌套另外一个结构体的任何类型变量 2、结构体嵌套本结构体普通变量(不可以) 本结构体的类型大小无法确定,类型本质:固定大小内存块别名 3、结构体嵌套本结构体指针变量(可以) 指针变量的空间能确定,32位, 4字节, 64位, 8字节 */ typedef struct B { int a; A tmp1; //ok A *p1; //ok

    //struct B tmp2; struct B *next; //32位, 4字节, 64位, 8字节 }B;

    2.6.2、函数指针 1、指针函数,它是函数,返回指针类型的函数 //指针函数 //()优先级比*高,它是函数,返回值是指针类型的函数 //返回指针类型的函数

    2、函数指针,它是指针,指向函数的指针,(对比数组指针的用法)

    一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。函数指针变量,它也是变量,和int a变量的本质是一样的。 //定义函数指针变量有3种方式: (1)先定义函数类型,根据类型定义指针变量(不常用) (2)先定义函数指针类型,根据类型定义指针变量(常用) (3)直接定义函数指针变量(常用)

    3、函数指针数组,它是数组,每个元素都是函数指针类型 4、回调函数,函数的形参为:函数指针变量

    2.6.3、函数的递归 递归:函数可以调用函数本身(不要用main()调用main(),不是不行,是没有这么做,往往得不到你想要的结果) (1)普通函数调用(栈结构,先进后出,先调用,后结束) (2)函数递归调用(调用流程和上面是一样,换种模式,都是函数的调用而已)

    3 总结

    其它总结见: https://download.csdn.net/download/zhaoyinlo3/12912070

    Processed: 0.009, SQL: 8