当前位置:网站首页>深度剖析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语言指针知识点的一个详细解剖,包含所有关于指针的知识点和指针相关的笔试题,希望能够在学习的路上互相帮助,共同进步,欢迎点赞留言评论!!!
边栏推荐
- Summary of phased use of sonic one-stop open source distributed cluster cloud real machine test platform
- 从 SQL 文件迁移数据到 TiDB
- Hcip day 16
- Summary of MySQL index failure scenarios
- 灰度升级 TiDB Operator
- How to use information mechanism to realize process mutual exclusion, process synchronization and precursor relationship
- Upgrade tidb with tiup
- Precise query of tree tree
- On the day of resignation, jd.com deleted the database and ran away, and the programmer was sentenced
- Online yaml to CSV tool
猜你喜欢
【MySQL】锁
Pyqt5 development tips - obtain Manhattan distance between coordinates
On the day of resignation, jd.com deleted the database and ran away, and the programmer was sentenced
指针进阶---指针数组,数组指针
面向个性化需求的在线云数据库混合调优系统 | SIGMOD 2022入选论文解读
【MySQL】数据库的存储过程与存储函数通关教程(完整版)
延迟初始化和密封类
sublime text没关闭其他运行就使用CTRL+b运行另外的程序问题
Use Alibaba icon in uniapp
[MySQL] database stored procedure and storage function clearance tutorial (full version)
随机推荐
Online yaml to CSV tool
游戏解包的危害及资源加密的重要性
Restore backup data on S3 compatible storage with br
Ruffian Heng embedded bimonthly, issue 49
备份与恢复 CR 介绍
【ROS】usb_cam相机标定
Colorlog结合logging打印有颜色的日志
Introduction to number theory (greatest common divisor, prime sieve, inverse element)
Use dumping to back up tidb cluster data to S3 compatible storage
egg. JS directory structure
China polyether amine Market Forecast and investment strategy report (2022 Edition)
VMware 虚拟化集群
String to leading 0
移位运算符
Hungry for 4 years + Ali for 2 years: some conclusions and Thoughts on the road of research and development
JS native implementation shuttle box
Colorlog combined with logging to print colored logs
Huawei cloud OBS file upload and download tool class
LDAP Application Section (4) Jenkins Access
JVM 快速入门