目录
前言
指针进阶
字符指针
指向常量字符串的指针
指针数组
指针数组打印数组内容
数组指针
对数组指针的理解
&数组名和数组名
数组指针的使用
数组参数、指针参数
一维数组传参
二维数组传参
一级指针传参
二级指针传参
函数指针
阅读两段有趣的代码
代码1
代码2
函数指针数组
函数指针数组的用途:转移表(通过下标调用函数)
指向函数指针数组的指针
回调函数
qsort函数来排序整型数组
qsort函数排序结构体
模拟qsort函数
前言
指针进阶
我们在初级指针章节已经接触过了指针,我们知道了指针的概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。
在这里我们将继续探索指针的奥秘,掌握高级指针初识以及运用计算,本篇内容将详细介绍各种指针,内容较多,干货满满,相信大家会收获很多!!话不多说,进入正题:
字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* ,
一般使用:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还有另外一种使用方式:
int main()
{
char* pstr = "hello bit.";//常量字符串
char arr[]="hello bit";
printf("%s",arr);//数组名表示字符串首地址,即“h”的地址,打印为hello bit;
printf("%sn", pstr);//pstr存放首字符“h”的地址。打印为hello bit;
return 0;
}
代码 char* pstr = "hello bit."; 大家特别容易以为是把字符串 hello bit 放到字符指针 pstr 里 了,但是本质上是把字符串 hello bit. 首字符的地址放到了pstr中。就像这样:
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。
了解了这个之后我们来看看一道题:
指向常量字符串的指针
#include
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
char* str3 = "hello bit.";//常量字符串,不能改,那么两个字符肯定存在一个地址中,不过是用不同的指针指向首字符
char* str4 = "hello bit.";
if (str1 == str2)//数组名表示字符串首地址,数组名不同,地址不同
printf("str1 and str2 are samen");
else
printf("str1 and str2 are not samen");
if (str3 == str4)//字符串指针存放的是首字符的首地址,都指向'h'的地址
printf("str3 and str4 are samen");
else
printf("str3 and str4 are not samen");
return 0;
}
解析:
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域, 当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始 化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。
指针数组
在初阶指针中我们也提到了指针数组,指针数组是一个存放指针的数组。
int* arr1[10]; //整形指针的数组 char *arr2[4]; //一级字符指针的数组 char **arr3[5];//二级字符指针的数组
指针数组打印数组内容
#include
int main()
{
int a[5] = { 1,2,3,4,5 };
int b[5] = { 2,3,4,5,6 };
int c[5] = { 3,4,5,6,7};
int* arr[3] = { a,b,c };//数组名表示首地址
int i = 0;
for (i = 0; i < 3; i++)//指的是三个一维数组
{
int j = 0;
for (j = 0; j < 5; j++)//每个一维数组的元素
{
printf("%d ", *(arr[i] + j));//相当于arr[i][j];
}
printf("n");
}
}
用图形来描述就是这样的:
好了,对于指针数组就是这样,比较简单,下面介绍一个比较重要的知识:
数组指针
数组指针是指针,我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够 指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
其基本的结构就是:int (*p)[10];
下面我对于数组指针做出了一个解释羅:
对数组指针的理解
p先和*结合,说明p是一个指针变量。
然后指着指向的是一个大小为10个整型的数组。
所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合
p先和*结合,说明p是一个指针变量。
然后指着指向的是一个大小为10个整型的数组。
所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合
如果我们要用一个指针来存放一个数组的地址,那么这个指针应该是数组指针指向整个数组的地址:
int arr[10]={1,2,3,4,5};
int (*parr)=&arr;//取出的是整个数组的地址,int是元素类型,(*parr)是数组指针
&数组名和数组名
对于下面的数组:
int arr[10];
arr和&arr分别是什么:
我们知道arr是数组名,数组名表示首元素的地址,那么&数组名是什么,我们可以通过代码来一步步深入研究:
#includeint main() { int arr[10] = {0}; printf("%pn", arr); printf("%pn", &arr); return 0; }
虽然两个地址一样,但是意义决然不同,我们还可以看这么一段代码:
#includeint main() { int arr[10] = { 0 }; printf("arr = %pn", arr); printf("&arr= %pn", &arr); printf("arr+1 = %pn", arr+1); printf("&arr+1= %pn", &arr+1); return 0; }
或者通过指针来访问:
#includeint main() { int arr[10] = { 0 }; int* p1 = arr;//首元素地址 int(*p2)[10] = &arr;//整个数组的地址 printf("%pn", p1); printf("%pn", p1+1);//跳过一个整形,四个字节 printf("%pn", p2); printf("%pn", p2+1);//跳过一个整形数组,40个字节 }
通过以上代码和运行结果可以知道:
其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
&arr 表示的是数组的地址,而不是数组首元素的地址。 数组的地址+1,跳过整个数组的大小。
但是我们要记住有两个例外:
1.sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小。
2.&数组名,数组名表示整个数组,取出的是整个数组的地址。
⚠️:这两个例外对于关于指针的解题很重要,所以我们一定要熟记。
数组指针的使用
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
数组指针的使用对于二维数组是比较多的,对于一维数组则比较少,我们先来看看他对于一维数组的使用:
#includeint main() { int arr[10] = {1,2,3,4,5,6,7,8,9,0}; int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p return 0; }
#includeint main() { int arr[10]={1,2,3,4,5,6,7,8,9,10}; int (*pa)[10]=&arr; int i=0; for(i=0;i<10;i++) { printf("%dn",*(*pa)+i); } return 0; }
但是我们一般不会这样写,这样写看着别扭,可读性不高,而是这样用于二维数组:
#includevoid print(int(*p)[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 ", *(*(p + i) + j));//p指向第一个一维数组的地址,p+i依次指向第二个第三个数组 / #include void print_arr(int arr[],int sz)//打印函数 { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("n"); } int cmp_int(const void* e1, const void* e2)//比较函数 { return *(int*)e1 - *(int*)e2;//比较函数,由于不知道要比较的是什么类型,所以可以强制转换为自己需要转换的类型 //qsort规定,当结果大于0时返回大于0的数,小于0时返回小于0的数 //至于函数内部怎么根据这个来排序,怎么把比较元素的地址传给e1,e2的是库函数规定的 } 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--; } } //模拟的qsort函数 void my_qsort(void* base,int sz,int width,int(*cmp_int)(const void* e1,const void* e2)) { int i = 0; for (i = 0; i < sz; i++) { int j = 0; for (j = 0; j < sz - 1 - i; j++) { //base接收首元素地址的指针 // j*width表示用下标或者说元素个数乘以一个元素的字节宽度就等于指针要跳过的大小 //(char*)base + j * width和(char*)base + (j + 1) * width代表要比较的两个元素的地址 //由于需要比较的元素类型多变,所以用char型指针+字节宽度就很通用 if (cmp_int((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); //不知道交换什么类型就可以一个一个字节交换,所以要把字节宽度传参 } } } } int main() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); print_arr(arr, sz); my_qsort(arr,sz,sizeof(arr[0]),cmp_int);//arr代表首元素地址 print_arr(arr, sz); }
好了,本篇内容就到这里,相信大家看完这篇文章,一定对指针有了更全面的了解。在这里我建议大家可以去像我一样去总结各种指针以及他的用法,这样我们一定会更上一层楼,一起加油啊,大家看完了,别忘了一键三连哦



