指针深入介绍
众所周知,亚里士多德撑起了古希腊科学、哲学的半壁江山。
而指针就像是C语言中的亚里士多德一样,撑起了C语言的半壁江山。 不,与其说,指针撑起了C语言的半壁江山,不如说是指针创造了C语言的一片天地。
小明:指针不就是存放了一个地址的变量而已嘛?就这?
小明,数学题你都会做了?还是你终于考上大学了?来这凑啥热闹?
指针类型
首先,要登场的是我们的二级指针
二级指针
小明同学在不经意间,已经说出了一个惊天大秘密!!!
指针就是一个变量 只不过是说 指针 这个变量和其他的变量不同,它存放的是某个数据的位置
变量,就意味着指针也是存放在内存中的某个位置,它也有自己的“门牌号” 也就是只要我有钥匙,我就可以光明正大进入指针的小家
em…当然最关键的还是需要 “&” 老大哥来帮忙配个钥匙
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
此时,我们就通过ppa可以进入pa的小家,“光明正大”地实施改造计划。
首先,趁着 pa 出门打酱油,将 a 进行改变
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
**ppa = 100;
return 0;
}
小明:就这???
那来个狠的
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
**ppa = 100;
int b = 20;
*ppa = &b;
return 0;
}
新创建一个 b 变量,进行操作 **ppa = &b; 此时再使用 pa 得到的就不是 a 变量,而是 b 变量
指针pa:我媳妇呢???
通过观察,可以知道 二级指针的结构是 int* *p; 其中 int* 表示 p 的数据类型是 int* 类型的 剩下的 **“ * ”**则表示 p 是一个指针 合起来就是一个 指向一个int*类型的变量的指针
可以通过观察二级指针的结构,推导出三级指针,四级指针…
总结:
- 二级指针存放的是一级指针的地址,而一级指针存放的是数据的地址
- 二级指针可以通过两次解引用操作来访问数据
- 二级指针进行一次解引用操作就可以改变一级指针存放的内容
小明:感觉也就一般般吧
字符指针
小明(不屑):字符指针?
eg:
char ch = 'w';
char* pc = &ch;
printf("%c\n", ch);
printf("%c\n", *pc);
小明(不屑地准备滑走)
eg:
char* str1 = "Hello World";
char* str2 = "Hello World";
char strArr1[] = "Hello World";
char strArr2[] = "Hello World";
问: 1、此时 str1 和 str2 相等吗?
答:相等
2、此时 strArr1 和 strArr2 相等吗?
答:不相等
小明:为什么 str1和 str2 相等,而 strArr1 和 strArr2 不相等呢?
"Hello World"是一个字符串常量,将字符串常量赋值给字符指针的时候是将字符串常量的首元素的地址传给了字符指针,因此 str1 和 str2 指向的都是 字符串常量的首元素的地址
而 strArr1 和 strArr2 是数组,将字符串赋值给字符数组,相当于将字符一个一个复制到了数组中。
小明:那为啥他俩不还是一模一样?为啥电脑告诉我他们不相等?电脑骗人?
注意,计算机是不会骗人的,他们只是严格的执行一条接一条的指令而已。 之所以 strArr1 和 strArr2 不相等是因为他们确实不相等。
咳咳~strArr1 和 strArr2 是两个数组 只要不是同一个数组,那么他们的首元素地址不可能相等
小明(点头):em。。。我明白了
说到数组,有两个孪生兄弟忍不住想来了
指针数组
顾名思义,指针数组就是一个存放指针的数组 网恋需谨慎,能见才靠谱
int main()
{
int* arr[10];
char** arr2[10];
return 0;
}
而 char** arr2[10]; 表示一个有10个数据类型是 char** 的元素的数组
数组指针
顾名思义,数组指针就是一个指向数组的指针 同样,先来看看数组指针的结构
int main()
{
int* p1[10];
int* (p2[10]);
int (*p3)[10];
return 0;
}
小明:这都是数组指针???
我:你猜
小明:
所以 p1 不是数组指针
所以 p2 也不是数组指针
所以这里只有 p3 是 数组指针 因此,不难发现,数组指针和指针数组的区别
名字 | 本质 | 所占空间 |
---|
数组指针 | 指针 | 4byte / 8byte | 指针数组 | 数组 | 4 * n byte / 8 * n byte(n为数组元素个数) |
名字 | 数据类型 |
---|
数组指针 | int (*) [](指向的数组中的元素是int类型) | 指针数组 | int*[](数组中的元素都是int*类型) |
名字 | 作用 |
---|
数组指针 | 存放一个数组的首元素的地址,表示整个数组的地址 | 指针数组 | 存放n个指针,每个指针指向的元素类型相同 |
数组指针更多的是用来当二维数组作为函数实参的形参
#include<stdio.h>
void print(int (*pa)[3], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for(j = 0; j < col; j++)
{
printf("%d ", pa[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[2][3] = {{1, 2}, {3, 4}, {5, 6}};
print(arr, 2, 3);
return 0;
}
此时 arr 表示的是首元素的地址,同时也表示 第一行的地址 ,也是一个一维数组的地址 类比可得: arr[1] 表示第二行的地址; arr[2] 表示第三行的地址;
因此,pa相当于接收了第一行的地址(arr的每行都是 两个int类型 的数据) 而 pa[ i ] [ j ] 和 *( *( pa + i ) + j)是等价的 所以 pa[ i ] [ j ] 可以读取到第 i 行,第 j 列的元素
介绍到这里,是时候来看看指针Plus版本了 问:解释下列代码的含义
int arr1[5];
int *arr2[5];
int (*arr3)[5];
int (*arr4[10])[5];
小明: 1 是数组 2 是数组指针 3 是指针数组 4 …
你这不行啊,小明
当括号将 * 和 字符单独括起来,后面加一个[ n ],前面加一个数据类型 那这就是数组指针
小明:所以第四个是数组?
对,但也不全对
那我们不妨将arr[10]拿掉,看看它到底是何方神圣
小明:天哪?!这不是个数组指针吗?
是的,就是数组指针
由此,4到底是什么已经真相大白了。 它就是 一个有 10 个元素的指针数组 每个指针都是指向的一个有 10 个整形元素的数组
答: 1 是一个有 5 个 int 类型的元素的数组 2 是一个有 5 个 int* 类型的元素的数组 3 是一个指向一个有 5 个 int 类型的元素的数组的指针 4 是一个有 10个 int*[ 5 ] 类型的元素的数组
还有两个小伙伴也是迫不及待了
小明:还有???
函数指针
介绍函数指针之前,先看一段代码
#include<stdio.h>
void test()
{
;
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
}
由此可见, 函数也没有那么神秘 也是在栈中开辟的一块地址 而且函数和数组很相似,名字都是它们的地址
要能够存储地址就必须是一个指针 判断一下,pfun1和pfun2哪个是指针
void (*pfun1)();
void *pfun2();
小明:pfun1 因为有括号,所以 pfun1 先和 * 结合 后面那个括号应该就是表示这是一个函数指针
有点东西啊,小明 不过,还是没完全对
pfun1 和 * 先结合表示这个是一个指针 后面的括号是指向函数的 参数(此时什么都没写,表示传递的参数为空) void 表示 函数的返回类型
pfun2就是一个函数,它的返回值的类型是void*,同时传递的参数为空
又到了提问的环节 解释下列代码的含义:
(*(void (*)())0)();
void (* signal(int, void(*)(int)))(int);
小明:
不慌,且听我慢慢道来
首先,先一层一层推进
小明:还是不明白
再推进一层
显然,这是一个函数指针类型
而这,就是将 0 进行强制类型转换为void(*)()类型 再对 0 进行解引用 末尾的 ( ) 表示函数的参数(此时也是什么都没有传递)
综上,代码1的含义是: 将 0 强制类型转换为void(*)(),再进行解引用,再传递参数 从而实现了调用在 0 地址处的函数( 0 地址一般无法这样操作)
小明:代码2呢?
同样,括号这么多,先一层一层向内部推进
小明:有点眼熟
再推进一层
小明:这表示的是两个类型,一个int, 一个void(*)(int)
没错 这就是函数signal的两个参数类型 字符串已经和括号结合,表示这是一个函数的声明 那么剩下的就是这个函数的返回类型了 所以,这个函数的返回值类型就是 void(*)(int)
综上所述, 代码2表示的含义是 函数signal 的声明 其中,这个 signal 的参数一个是 int, 另一个是 void(*)(int) signal 的返回值的类型也是 void(*)(int)
这两个代码均在《C陷阱和缺陷》中提及
代码2的结构太复杂,可以利用 typedef 来简化
typedef void (*pfun_t)(int);
pfun_t signal (int, pfun_t);
小明:如果,我需要使用多个函数,而他们的参数,返回值都是一样的,我可以用什么来进行简化呢?
接下来就是函数指针数组来大放光彩了
函数指针数组
函数指针数组和一般的指针数组一样 只不过 函数指针数组中的指针指向的都是函数
在见到函数指针数组之前,先看看正常情况下计算器的实现代码
void menu()
{
printf("******************************\n");
printf("0.exit\n");
printf("1.Add\n");
printf("2.Subtract\n");
printf("3.Multiply\n");
printf("4.Divide\n");
printf("******************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Subtract(int x, int y)
{
return x - y;
}
int Multiply(int x, int y)
{
return x * y;
}
int Divide(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
menu();
do
{
scanf("%d", &input);
switch (input)
{
case 0:
break;
case 1:
scanf("%d %d", &x, &y);
printf("%d\n", Add(x, y));
break;
case 2:
scanf("%d %d", &x, &y);
printf("%d\n", Subtract(x ,y));
break;
case 3:
scanf("%d %d", &x, &y);
printf("%d\n", Multiply(x, y));
break;
case 4:
scanf("%d %d", &x, &y);
printf("%d\n", Divide(x, y));
break;
default:
break;
}
} while(input);
return 0;
}
而现在对其进行改造
int main()
{
int input = 0;
int(*arr[5])(int, int) = { 0 , Add, Subtract, Multiply, Divide };
int x = 0;
int y = 0;
do
{
menu();
scanf("%d", &input);
if (input > 0 && input < 5)
{
scanf("%d %d", &x, &y);
printf("%d\n", (*arr[input])(x, y));
}
} while (input);
return 0;
}
其中 int(*arr[5])(int, int) = { 0 , Add, Subtract, Multiply, Divide }; 就是一个函数指针数组
既然有函数指针数组,那么函数指针数组指针自然也是少不了的
函数指针数组指针
将函数指针去掉,就是之前介绍过的数组指针 所以,通过观察数组指针 可以得到 函数指针数组指针 是一个指向一个函数指针数组的指针,里面存放的是数组的地址
函数指针数组指针虽然名字看起来很复杂,但是他的结构也一点不简单
小明:
首先,写出数组中的元素类型
int(*)(int, int)
其次,将指针和数组添加上去
int(*(*parr)[5])(int, int) = arr;
回调函数
简介:回调函数就是一个通过函数指针调用的函数。 也就是说,将A函数的指针,作为参数传递给B函数 当A函数的指针被用来调用它所指向的A函数时,称此为回调函数
qsort就有回调函数的使用 在使用qsort的时候就需要传递一个函数指针,用来自定义如何排序 下面来简单介绍一下 qsort
qsort 的头文件是<stdlib.h> qsort 的参数含义是数组名,元素个数,元素所占字节大小,比较方法的函数
#include<stdlib.h>
int com(void* q1, void* q2)
{
return *(void*)q1 - *(void*)q2;
}
int main()
{
int arr[10] = { 1,3,2,4,6,8,7,10,9,5 };
qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr), com);
return 0;
}
我们自己自定义的函数返回值的类型一定要是int,同时要有两个void*类型的指针作为参数 当 com 的返回值是小于等于 0 的时候,qosrt不会对数组做出改变 而当 com 的返回值是大于 0 的时候,qsort对数组进行排序 比较方法的函数可以根据需要排序的数组中的元素类型来灵活调整
总结
以上,便是C中部分的指针的介绍。 C的指针的奥妙绝不是我这三言两语能够说的清楚的,它在我看来,指针就是C中最牛逼的部分,不接受反驳
注释:
|