当前位置:网站首页>深度剖析C语言指针
深度剖析C语言指针
2022-07-06 08:28:00 【终为—NULL】
每天进步一点点,坚持带来大改变!!!
目录
前言:
之前我们已经学过指针的一些简单知识:
1.指针就是地址,是标识内存空间的编号。
2.指针变量是用来存放变量的地址,可以通过指针找到变量在内存中存储。
3.指针的大小占4/8个字节(32/64).
4.指针的类型决定了指针加减整数的时候跳过几个字节,解引用的时候能够访问几个字节。
5.指针的运算:指针减指针得到中间元素的个数。
接下来我们继续探讨指针的更多知识,深入C语言指针相关的内容。
1.字符指针
char*
第一种使用:
内存布局:
第二种使用:
内存布局:
关于字符指针的一道面试题:
#include<stdio.h> int main() { char* p1 = "abcdef"; char* p2 = "abcdef"; char arr1[] = "abcdef"; char arr2[] = "abcdef"; if (p1 == p2) printf("p1 == p2\n"); else printf("p1 != p2\n"); if (arr1 == arr2) printf("arr1 == arr2\n"); else printf("arr1 != arr2\n"); return 0; }
原因解释: 因为"abcdef"是常量字符串,放在字符常量区,不能被修改,因此在内存中只有一份,所以p1和p2都是存放首字符a的地址,而arr1和arr2在内存中开辟两块不同的空间存放"abcdef",因此地址不相同。
2.指针数组
概念:类比推理
整形数组,用来存放整形的数字,每个元素的类型是int
字符数组,用来存放字符的数组,每个元素的类型是char指针数组,用来存放指针的数组,每个元素的类型是 数据类型*
应用:打印一个二维数组
#include<stdio.h> int main() { int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 2,3,4,5,6 }; int arr3[] = { 3,4,5,6,7 }; int* parr[3] = { arr1,arr2,arr3 }; //parr是一个整形指针数组,每个元素的类型是int* int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", *(*(parr + i) + j)); } printf("\n"); } return 0; }
内存布局:
3.数组指针
概念:类比推理:
整形指针,用来存放整形元素的地址 int*p;
字符指针,用来存放字符元素的地址 char*p;数组指针,用来存放数组的地址 数据类型(*p)[X];
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int(*p)[10] = &arr; //p先和*结合说明p是一个指针变量,然后指向一个大小为10元素的数组 return 0; }
注:&数组名代表的是数组的地址
数组指针的使用:不恰当的使用
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int(*p)[10] = &arr; int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(*p + i));//等价于arr[i] //*p得到数组首元素的地址+i得到下个元素的地址,*得到这个值 } return 0; }
打印一维数组使用数组指针,比较麻烦,不建议使用。
打印二维数组
#include<stdio.h> void print(int(*p)[5], int row, int col) { int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", *(*(p + i) + j)); //*(p+i)得到二维数组每一行首元素的地址+j解引用得到每一个元素。 //等价与p[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, 3, 5); //数组名代表数组首元素的地址,二维数组首元素又是一个一维数组 //所以用数组指针接受,存放数组的地址。 return 0; }
总结:
int arr[5]; int *parr1[10]; int (*parr2)[10]; int (*parr3[10])[5];
int arr[5] : 是一个整形数组,数组有五个元素,每个元素的类型是int;
int* parr1[10]:是一个指针数组,数组有10个元素,每个元素的类型是int*;
int(*parr2)[10]:是一个数组指针,该指针指向的数组有10个元素,每个元素的类型是int
该指针变量的类型是int(*)[10];
int(*parr3[10])[5]:parr3先和[]结合说明这是一个数组,该数组有10个元素,每个元素的类型是int(*)[5](数组指针);
4.数组传参,指针传参
1.数组传参
一维数组传参:
void test1(int arr[])//ok {} void test1(int arr[10])//ok {} void test1(int*arr)//ok {} void test2(int*arr[20])//ok {} void test2(int**arr)//ok arr2是一个指针用二级指针接受 {} int main() { int arr1[10] = { 0 }; int* arr2[20] = { 0 }; test1(arr1); test2(arr2); return 0; }
二维数组传参:
void test(int arr[3][5])//ok {} void test(int arr[][])//err 二维数组传参行可以省略,列不能省略 {} void test(int arr[][5])//ok {} void test(int(*arr)[5])//ok 数组名表示首元素的地址,而二维数组首元素表示第一行数组, //因此可以用数组指针来接受 {} void test(int**arr)//err 二级指针变量是存放一级指针变量的地址 {} void test(int*arr[5])//err指针数组用来接受指针,不能接受数组的地址 {} int main() { int arr[3][5] = { 0 }; test(arr); return 0; }
2.指针传参:
一级指针传参
void test(int* p)//ok {} int main() { int a = 10; int* p = &a; test(p); return 0; }
二级指针传参:
void test(int** pp) {} int main() { int a = 10; int* p = &a; int** pp = &p; test(pp);//ok test(&p);//ok return 0; }
5.函数指针
用来存放函数的地址
#include<stdio.h> int Add(int a, int b) { return a + b; } int main() { int a = 10; int b = 20; int ret = Add(a, b); printf("%p\n", Add); printf("%p\n", &Add); return 0; }
如何将一个函数的地址存起来呢?
#include<stdio.h> int Add(int a, int b) { return a + b; } int main() { int (*pf)(int, int) = &Add;//Add; //pf就是一个函数指针(int,int)函数参数的类型 int ret = (*pf)(2, 3); //等价与(pf)(2,3); printf("%d\n", ret); return 0; }
下面通过两段代码深入理解函数指针:
第二段代码比较复杂,但是有相同的代码,函数指针,因此可以通过typedef重命名简化代码
typedef void(*pf_t)(int); int main() { void (*signal(int, void(*)(int)))(int); pf_t signal(int pf_t); 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 a = 0; int b = 0; int ret = 0; int input = 0; do { menu(); printf("请选择:"); scanf("%d",&input); switch (input) { case 0: printf("退出计算器\n"); break; case 1: printf("请输入两个操作数:"); scanf("%d%d", &a, &b); ret = Add(a, b); printf("%d\n", ret); break; case 2: printf("请输入两个操作数:"); scanf("%d%d", &a, &b); ret = Sub(a, b); printf("%d\n", ret); break; case 3: printf("请输入两个操作数:"); scanf("%d%d", &a, &b); ret = Mul(a, b); printf("%d\n", ret); break; case 4: printf("请输入两个操作数:"); scanf("%d%d", &a, &b); ret = Div(a, b); printf("%d\n", ret); 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; } void Calc(int(*pf)(int, int)) { int a = 0; int b = 0; int ret = 0; printf("请输入两个操作数:"); scanf("%d%d", &a, &b); ret = pf(a, b); printf("%d\n", ret); } int main() { int input = 0; do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 0: printf("退出计算器\n"); break; case 1: Calc(Add); break; case 2: Calc(Sub); break; case 3: Calc(Mul); break; case 4: Calc(Div); break; default: printf("输入有误,请重新输入:\n"); break; } } while (input); return 0; }
将函数的地址传过去,参数用函数指针来接受,然后通过函数指针调用这个函数。
6.函数指针数组
是一个数组,用来存放函数地址的数组
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[4])(int, int) = { Add,Sub,Mul,Div }; //pf先和[]结合说明这是一个数组,数组有4个元素,每个元素的类型是 //int(*)(int,int)函数指针 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 a = 0; int b = 0; int ret = 0; int(*parr[5])(int,int) = { 0,Add,Sub,Mul,Div }; do { menu(); printf("请选择:"); scanf("%d", &input); if (input == 0) { printf("退出计算器\n"); } else if (input >= 1 && input < 5) { printf("请输入两个操作数:"); scanf("%d%d", &a, &b); ret = parr[input](a, b); printf("%d\n", ret); } else { printf("输入错误,请重新输入\n"); } } while (input); return 0; }
当使用函数指针数组的时候又简化了代码,并且在后续修改代码的时候提供了极大的方便
7.指向函数指针数组的指针
是一个指针,用来存放函数指针数组的地址:
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(*pfarr[4]) = {Add,Sub,Mul,Div }; //ppfArr是一个指向函数指针数组的指针 //ppfArr先和*结合,说明ppfArr是一个指针,[4]说明指针 //指向的数组有4个元素,每个元素的类型是int(*)(int,int) int(*(*ppfArr)[4])(int, int) = &pfarr; return 0; }
8.回调函数
上面我们在用函数指针实现计算器的时候,写了一个Calc的函数,其实Calc就是一个回调函数,那如何理解回调函数呢?
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
#include<stdio.h> void bubble_sort(int arr[], int sz) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; int flag = 1;//如果待排序的数组已经有序,则跳出循环 for (j = 0; j < sz - 1 - i; j++) { if (arr[j] < arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; flag = 0; } } if (flag == 1) { break; } } } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
排序成降序:
当我们要对一个复杂的对象进行排序的时候,显然冒泡排序不能适用,那如何要对一个复杂对象进行排序呢?
下面来介绍一个库函数qsort()
#include<stdlib.h>//包含头文件 int main() { void qsort(void* base, //待排序的起始位置 size_t num, //待排序的个数 size_t width, //待排序元素的大小,单位是字节 int( * cmp)(const void* e1, const void* e2)); //是一个函数指针,该函数指针指向的函数有两个参数,e1,e2,函数返回类型是int. return 0; } //int cmp(const void* e1, const void* e2) //Return Value Description //< 0 e1 less than e2 //0 e1 equivalent to e2 //> 0 e1 greater than e2
使用库函数qsort排序整形数组:
#include<stdlib.h> #include<stdlib.h> int cmp_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } int main() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0}; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
默认是升序排列:
使用库函数排序复杂对象:
按照一个人的年龄进行排序:
#include<stdio.h> #include<stdlib.h> struct stu { int age; char name[20]; }; int by_cmp_age(const void* e1, const void* e2) { return ((struct stu*)e1)->age - ((struct stu*)e2)->age; } int main() { struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), by_cmp_age); int i = 0; for (i = 0; i < sz; i++) { printf("%s %d ", s[i].name, s[i].age); printf("\n"); } return 0; }
按照一个人的姓名进行排序
#include<stdio.h> #include<stdlib.h> #include<string.h> struct stu { int age; char name[20]; }; int by_cmp_name(const void* e1, const void* e2) { return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name); } int main() { struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), by_cmp_name); int i = 0; for (i = 0; i < sz; i++) { printf("%s %d ", s[i].name, s[i].age); printf("\n"); } return 0; }
模拟实现库函数qsort:
实现库函数qsort的一个参数是函数指针,而函数指针指向的函数参数是void*类型的指针。
为什么会使用void*类型的指针呢?
下面介绍一下void*的指针的作用
模拟实现qsort
#include<stdio.h> #include<string.h> struct stu { int age; char name[20]; }; int by_cmp_name(const void* e1, const void* e2) { return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name); } void Swap(char* buf1, char* buf2, int width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_qsort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2)) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; int flag = 1; for (j = 0; j < sz - 1 - i; j++) { //将传入的数据类型强转化为char*指针类型,然后进行一个字节一个字节的比较 if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); flag = 0; } } if (flag == 1) { break; } } } int main() { struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} }; int sz = sizeof(s) / sizeof(s[0]); bubble_qsort(s, sz, sizeof(s[0]), by_cmp_name); int i = 0; for (i = 0; i < sz; i++) { printf("%s %d ", s[i].name, s[i].age); printf("\n"); } return 0; }
9.指针和数组笔试题练习
一维整形数组:
一维字符数组:数组中不包含'\0'
szieof
strlen:
数组中包含'\0'
sizeof:
strlen:
字符指针:
sizeof:
strlen:
二维数组:
总结:sizeof(数组名),数组名表示整个数组的大小,计算整个数组的大小
&数组名,数组名表示整个数组的地址,除此之外,所有的数组名都表示的是数组首元素的地址。
10.指针笔试题练习
试题一:
试题二:
试题三:
试题四:
试题五:
![]()
试题六:
试题七:
试题八:
11.总结:
以上是关于对C语言指针知识点的一个详细解剖,包含所有关于指针的知识点和指针相关的笔试题,希望能够在学习的路上互相帮助,共同进步,欢迎点赞留言评论!!!
边栏推荐
- Pointer advanced --- pointer array, array pointer
- The ECU of 21 Audi q5l 45tfsi brushes is upgraded to master special adjustment, and the horsepower is safely and stably increased to 305 horsepower
- [2022 Guangdong saim] Lagrange interpolation (multivariate function extreme value divide and conquer NTT)
- [secretly kill little partner pytorch20 days -day01- example of structured data modeling process]
- ESP series pin description diagram summary
- Research Report on supply and demand and development prospects of China's high purity aluminum market (2022 Edition)
- 指针进阶---指针数组,数组指针
- 从 TiDB 集群迁移数据至另一 TiDB 集群
- Verrouillage [MySQL]
- Function coritization
猜你喜欢
Configuring OSPF load sharing for Huawei devices
Deep learning: derivation of shallow neural networks and deep neural networks
Ruffian Heng embedded bimonthly, issue 49
Cisp-pte practice explanation
C language custom type: struct
ESP系列引脚說明圖匯總
Process of obtaining the electronic version of academic qualifications of xuexin.com
2022.02.13 - NC002. sort
Roguelike游戏成破解重灾区,如何破局?
2. File operation - write
随机推荐
被破解毁掉的国产游戏之光
从表中名称映射关系修改视频名称
IOT -- interpreting the four tier architecture of the Internet of things
[2022 Guangdong saim] Lagrange interpolation (multivariate function extreme value divide and conquer NTT)
JS inheritance method
Bottom up - physical layer
Remote storage access authorization
egg. JS directory structure
LDAP應用篇(4)Jenkins接入
个人电脑好用必备软件(使用过)
从 CSV 文件迁移数据到 TiDB
【ROS】usb_cam相机标定
Roguelike游戏成破解重灾区,如何破局?
Upgrade tidb operator
查看局域网中电脑设备
生成器参数传入参数
VMware virtualization cluster
JVM performance tuning and practical basic theory - Part 1
按位逻辑运算符
vulnhub hackme: 1