养老网站建设 中企动力,佛山做网站业务工资,网页一键建站,wordpress文章添加链接C语言 -- 深入理解指针#xff08;二#xff09; 1. 数组名的理解2. 使用指针访问数组3. 一维数组传参的本质4. 冒泡排序5. 二级指针6. 指针数组7. 指针数组模拟二维数组8. 字符指针变量9. 数组指针变量2.1数组指针变量是什么#xff1f;2.2 数组指针变量怎么初始化 10. 二维… C语言 -- 深入理解指针二 1. 数组名的理解2. 使用指针访问数组3. 一维数组传参的本质4. 冒泡排序5. 二级指针6. 指针数组7. 指针数组模拟二维数组8. 字符指针变量9. 数组指针变量2.1数组指针变量是什么2.2 数组指针变量怎么初始化 10. 二维数组传参的本质11. 函数指针变量4.1 函数指针变量的创建4.2 函数指针变量的使用4.3 两段有趣的代码4.3.1 typedef关键字 12. 函数指针数组13. 转移表14.回调函数是什么 1. 数组名的理解
使用指针访问数组的内容时有这样的代码 arr[0] 的方式拿到了数组第一个元素的地址 但是其实数组名arr本来就是地址而且是数组首元素的地址 下面我们来看个代码 我们发现数组名和数组首元素的地址打印出的结果一模一样数组名就是数组首元素(第一个元素)的地址。
这时候有同学会有疑问数组名如果是数组首元素的地址那下面的代码怎么理解呢 输出的结果是40如果arr是数组首元素的地址那输出应该的应该是4/8才对。 其实数组名就是数组首元素(第一个元素)的地址是对的但是有两个例外
sizeof(数组名)sizeof中单独放数组名这里的数组名表示整个数组计算的是整个数组的大小单位是字节数组名这里的数组名表示整个数组取出的是整个数组的地址整个数组的地址和数组首元素的地址是有区别的除此之外任何地方使用数组名数组名都表示首元素的地址。
这时有好奇的同学再试一下这个代码 三个打印结果一模一样这时候又纳闷了那arr和arr有啥区别呢 请看下面代码 运行结果 这里我们发现arr[0]和arr[0]1相差4个字节arr和arr1 相差4个字节是因为arr[0] 和 arr 都是首元素的地址1就是跳过一个元素。但是arr 和 arr1相差40个字节这就是因为arr是数组的地址1 操作是跳过整个数组的。到这里大家应该搞清楚数组名的意义了吧。 数组名是数组首元素的地址但是有2个例外。
2. 使用指针访问数组
1.因为数组在内存中是连续存放的 2.数组名就是首元素的地址方便找到起始位置 所以可以用指针来访问数组 代码如下
这个代码搞明白后我们再试一下如果我们再分析一下数组名arr是数组首元素的地址可以赋值给p其实数组名arr和p在这里是等价的。
同理arr[i] 应该等价于 *(arri)数组元素的访问在编译器处理的时候也是转换成首元素的地址偏移量求出元素的地址然后解引用来访问的。
3. 一维数组传参的本质
首先从一个问题开始我们之前都是在函数外部计算数组的元素个数那我们可以把函数传给一个函数后函数内部求数组的元素个数吗 代码如下 我们发现在函数内部是没有正确获得数组的元素个数。 这就要学习数组传参的本质了在上面我们学习了数组名是数组首元素的地址那么在数组传参的时候传递的是数组名也就是说本质上数组传参本质上传递的是数组首元素的地址。所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写sizeof(arr) 计算的是一个地址的大小单位字节而不是数组的大小单位字节。正是因为函数的参数部分是本质是指针所以在函数内部是没办法求的数组元素个数的。 再看一组打印数组的代码如下 结论数组传参本质上传递的是数组首元素的地址 一维数组传参形参的部分可以写成数组的形式也可以写成指针的形式。 4. 冒泡排序
冒泡排序的核心思想就是两两相邻的元素进行比较如果不满足顺序就交换。 练习写一个函数对一个整形数组的数据进行排序。 n个元素n-1趟 第一趟过程中排n个元素n-1对比较 第二趟过程中排n-1个元素n-2对比较 以此类推… 方法一 运行结果 方法二优化上面代码 已经有序或者接近有序的数字就没必要重复比较了。 运行结果
5. 二级指针
指针变量也是变量是变量就有地址二级指针变量是用来存放一级指针变量的地址。 对于二级指针的运算有
*ppa 通过对ppa中的地址进行解引用这样找到的是 pa *ppa 其实访问的就是 pa .**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作 *pa 那找到的是 a .
6. 指针数组
指针数组是指针还是数组 我们类比一下
整型数组 - 存放整型的数组数组中每个元素是整形类型字符数组 - 存放字符的数组数组中每个元素是字符类型指针数组- 存放指针的数组数组中每个元素是指针类型 int arr [10] 整形数组 char ch [5] 字符数组 希望有一个数组数组有4个元素每个元素是整形指针 int * arr [4]; 下面请看图例 指针数组的每个元素都是用来存放地址指针的。 如下图 指针数组的每个元素是地址又可以指向一块区域。
7. 指针数组模拟二维数组
模拟出二维数组的效果但不是二维数组。 二维数组的每一行是一个一维数组 看下面代码 图示
parr[i]是访问parr数组的元素parr[i]找到的数组元素指向了整型一维数组parr[i][j]就是整型一维数组中的元素。上述的代码模拟出二维数组的效果实际上并非完全是二维数组因为每一行并非是连续的。
8. 字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针 char* ; 一般使用: 还有一种使用方式如下 你可以把字符串想象为一个字符数组但是这个数组是不能修改的当常量字符串出现在表达式中的时候他的值是第一个字符的地址常量字符串是不能被修改的 所以要加const 4.const char* p “abcdef”;//不是把字符串abcdef\0存放在p中而是把第一个字符的地址存放在p中 《剑指offer》中收录了一道和字符串相关的笔试题我们一起来学习一下 运行结果 这里str3和str4指向的是一个同一个常量字符串。C/C会把常量字符串存储到单独的一个内存区域当几个指针指向同一个字符串的时候他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同str3和str4相同。
9. 数组指针变量
2.1数组指针变量是什么
整形指针变量 int * pint; 存放的是整形变量的地址能够指向整形数据的指针。浮点型指针变量 float * pf; 存放浮点型变量的地址能够指向浮点型数据的指针。数组指针变量应该是存放的应该是数组的地址能够指向数组的指针变量。 int *p1[10]; p1是数组数组10个元素每个元素的类型是int *,所以p1是指针数组 int (*p2)[10]; p2是指针指向数组 数组有10个元素每个元素的类型是intp2是数组指针 数组指针变量 int (*p)[10]; p先和*结合说明p是一个指针变量然后指向的是一个大小为10个整型的数组。所以p是一个指针指向一个数组叫 数组指针。这里要注意[]的优先级要高于号的所以必须加上来保证p先和结合。
2.2 数组指针变量怎么初始化
数组指针变量是用来存放数组地址的那怎么获得数组的地址呢就是我们之前学习的 数组名 如果要存放个数组的地址就得存放在数组指针变量中如下 例子
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int* p1 arr;//int* int*int* p2 arr[0];//int* int*int (*p3)[10] arr;//p3是数组指针 //arr 和 p3的类型是完全一致的//int (*)[10] int (*)[10] //return 0;
}数组指针类型解析
10. 二维数组传参的本质
过去我们有一个二维数组的需要传参给一个函数的时候我们是这样写的
#include stdio.h
void test(int a[3][5], int r, int c)
{int i 0;int j 0;for (i 0; i r; i){for (j 0; j c; j){printf(%d , a[i][j]);}printf(\n);}
}
int main()
{int arr[3][5] { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}运行结果
这里实参是二维数组形参也写成二维数组的形式那还有什么其他的写法吗首先我们再次理解一下二维数组二维数组起始可以看做是每个元素是一维数组的数组也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行是个一维数组。
如下图
二维数组的数组名表示的就是第一行的地址是一 维数组的地址。第一行的一维数组的类型就是 int [5] 所以第一行的地址的类型就是数组指针类型 int(*)[5] 1.二维数组传参本质上也是传递了地址传递的是第一行这个一维数组的地址那么形参也是可以写成指针形式的。 2.二维数组传参形参的部分可以写成数组也可以写成指针形式。 代码如下
#include stdio.h
void Print_arr(int(*arr)[5], int r, int c)
{int i 0;for (i 0; i r; i){int j 0;for (j 0; j c; j){//arr[i][j]printf(%d , *(*(arr i) j)); }printf(\n);}
}
int main()
{int arr[3][5] { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };Print_arr(arr, 3, 5); //传递的是第一行一维数组的地址return 0;
}运行结果
11. 函数指针变量
4.1 函数指针变量的创建
函数指针变量应该是用来存放函数地址的未来通过地址能够调用函数的。 那么函数是否有地址呢我们做个测试
#include stdio.h
int Add(int x, int y)
{return x y;
}
int main()
{printf(%p\n, Add);printf(%p\n, Add);return 0;
}运行结果
确实打印出来了地址所以函数是有地址的函数名就是函数的地址当然也可以通过 函数名 的方式获得函数的地址。 数组名–数组首元素的地址 数组名 --整个数组的地址 函数名 --函数的地址 函数名 --函数的地址 如果我们要将函数的地址存放起来就得创建函数指针变量咯函数指针变量的写法其实和数组指针 非常类似。如下
#include stdio.h
int Add(int x, int y)
{return x y;
}
int main()
{int (*p)(int, int) Add;return 0;
}4.2 函数指针变量的使用
通过函数指针调用指针指向的函数。
#include stdio.h
int Add(int x, int y)
{return x y;
}
int main()
{/*printf(%p\n, Add);printf(%p\n, Add);*/int (*p)(int, int) Add; // Addint ret Add(3, 5);printf(%d\n, ret);int ret1 (*p)(4, 5);int ret2 p(5, 5);printf(%d\n, ret1);printf(%d\n, ret2);return 0;
}运行结果 1.把一个函数地址放到一个函数指针变量里面函数指针变量指向的对象是一个函数解引用就可以得到函数。 2.函数名 函数的地址Add ,就可以写成 函数的地址(3,5) 函数指针变量里面放的就是函数地址 也就可以写成 函数指针变量35因此Add(3,5) (*p)(3,5) p(3,5) 3. Add(3,5) (*p)(3,5) p(3,5) 叫法通过函数指针来调用它所对应的函数。 4.3 两段有趣的代码
代码1
(*(void (*)())0)(); //把0当作一个函数的地址代码2
void (*signal(int , void(*)(int)))(int);
typedef void(*pf_t)(int); //函数指针类型重命名
pf_t signal(int, pf_t);上述代码是函数声明 1.signal是一个函数 2.signal函数的参数有2个第一个是int类型第二个是函数指针类型该指针指向的函数参数是int返回类型是void 3.signal函数的返回类型是这种类型的void(*)(int)函数指针 该指针指向的函数参数是int返回类型是void 4.3.1 typedef关键字
typedef 是用来类型重命名的可以将复杂的类型简单化。 比如你觉得 unsigned int 写起来不方便如果能写成 uint 就方便多了那么我们可以使用 如果是指针类型能否重命名呢其实也是可以的比如将 int* 重命名为 ptr_t ,这样写 但是对于数组指针和函数指针稍微有点区别 比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t 那可以这样写 函数指针类型的重命名也是⼀样的比如将 void( * )(int) 类型重命名为 pf_t ,就可以这样写 例子
typedef unsigned int uint; typedef int(*pArr_t)[10] ; //数组指针类型重命名typedef int (*pf_t)(int, int) ; //函数指针类型重命名int main()
{unsigned int num;uint num2;pArr_t pa; //数组指针变量int(*pb)[10]; //数组指针变量pf_t pf; //函数指针变量int (*pf2)(int, int); //函数指针变量return 0;
}12. 函数指针数组
数组是⼀个存放相同类型数据的存储空间我们已经学习了指针数组 比如 那要把函数的地址存到一个数组中那这个数组就叫函数指针数组那函数指针的数组如何定义呢 答案是parr1 parr1 先和 [] 结合说明parr1是数组数组的内容是什么呢 是 int (*)() 类型的函数指针。 应用
#include stdio.h
int Add(int x, int y)
{return x y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}int main()
{int (*pf)(int, int) Add;//pf是函数指针int (* pfArr[4])(int, int) {Add, Sub, Mul, Div};//存放函数指针的数组-函数指针数组//0 1 2 3int i 0;for (i0; i 4; i){int ret pfArr[i](6, 2);printf(%d\n, ret);}return 0;
}运行结果
13. 转移表
函数指针数组的用途转移表 举例计算器的一般实现
#include stdio.h
void menu()
{printf(******************************\n);printf(**** 1. add 2. sub ****\n);printf(**** 3. mul 4. div ****\n);printf(**** 0. exit ****\n);printf(******************************\n);
}int Add(int x, int y)
{return x y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}int main()
{int input 0;int x 0;int y 0;int ret 0;do{menu();printf(请选择:);scanf(%d, input);switch (input){case 1:printf(请输入两个操作数:);scanf(%d %d, x, y);ret Add(x, y);printf(%d\n, ret);break;case 2:printf(请输入两个操作数:);scanf(%d %d, x, y);ret Sub(x, y);printf(%d\n, ret);break;case 3:printf(请输入两个操作数:);scanf(%d %d, x, y);ret Mul(x, y);printf(%d\n, ret);break;case 4:printf(请输入两个操作数:);scanf(%d %d, x, y);ret Div(x, y);printf(%d\n, ret);break;case 0:printf(退出计算器\n);break;default:printf(选择错误重新选择\n);break;}} while (input);return 0;
}
运行结果 使用函数指针数组的实现
#include stdio.h
void menu()
{printf(******************************\n);printf(**** 1. add 2. sub ****\n);printf(**** 3. mul 4. div ****\n);printf(**** 0. exit ****\n);printf(******************************\n);
}int Add(int x, int y)
{return x y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}int main()
{int input 0;int x 0;int y 0;int ret 0;do{menu();//函数指针数组的方式解决一下//这里的函数指针数组我们称为转移表//int (*pfArr[])(int, int) { NULL, Add, Sub, Mul, Div };// 0 1 2 3 4printf(请选择:);scanf(%d, input);if (input 0){printf(退出计算器\n);}else if (input 1 input 4){printf(请输入两个操作数:);scanf(%d %d, x, y);ret pfArr[input](x, y);printf(%d\n, ret);}else{printf(选择错误重新选择\n);}} while (input);return 0;
}运行结果
14.回调函数是什么
函数指针可以用来实现回调函数。
如果你把函数的指针地址作为参数传递给另一个函数通过这个指针函数去调用对应的函数时被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用而是在特定的事件或条 件发生时由另外的一方调用的用于对该事件或条件进行响应。 这里我们可以把调用的函数的地址以参数的形式传递过去使用函数指针接收函数指针指向什么函数就调用什么函数这里其实使用的就是回调函数的功能。
#include stdio.h
void menu()
{printf(******************************\n);printf(**** 1. add 2. sub ****\n);printf(**** 3. mul 4. div ****\n);printf(**** 0. exit ****\n);printf(******************************\n);
}int Add(int x, int y)
{return x y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}//calc功能强大了void calc(int (*pf)(int,int))
{int x 0;int y 0;int ret 0;printf(请输入两个操作数:);scanf(%d %d, x, y);ret pf(x, y);printf(%d\n, ret);
}int main()
{int input 0;do{menu();printf(请选择:);scanf(%d, input);switch (input){case 1:calc(Add);//完成计算 break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 0:printf(退出计算器\n);break;default:printf(选择错误重新选择\n);break;}} while (input);return 0;
}
代码实现过程图解
运行结果 完