接下来进入指针的进阶部分,准备好大脑
补充:(重点)
数组名是数组首元素地址
数组首元素地址和数组地址,值相同,但本质不同,
区别在于二者的类型不相同
比如数组int arr[10];
- 数组首元素地址的类型:首先这是一个地址所以要用指针接收,(*),然后是地址指向元素的类型为int,所以这个指针的类型是int*
- 数组地址的类型:首先这是一个地址所以用指针接收,(*),这个地址表示的是整个数组,这个数组的类型为int [10],所以这个指针的类型是int(*)[10]
- 额外:数组的类型怎么判断,先判断数组的大小,[10],再判断数组中存储数据的类型,int,所以数组的类型就是int [10]
- arr是数组首元素地址,&arr是数组地址,那么*(&arr) == arr,给数组地址解引用可以得到数组首元素地址
- arr[i]的意思是访问数组第i个元素的位置,也可以表示为*(arr+i),数组元素地址+i后解引用,而系统在实现arr[i]这个功能本质就是用*(arr+i),理解到这里,有没有感受到为什么说C语言研究的是底层呢?
一、字符指针
char arr[] = "abcd";
const char* p = "abcd";
因为"abcd"是字符串常量,且*p不能修改这个常量
所以,用const修饰char* p,保证*p不能修改指针变量p指向的值
特别地
- 这里的指针变量p接收的是字符数组的首元素地址,也就是字符’a’的地址
- 常量储存在代码区,是不能被修改的
二、.指针数组
字符数组 — 存放字符的数组
整形数组 — 存放整型的数组
指针数组 — 存放指针(地址)的数组
int a = 1; int b = 2; int c = 3;
int pa = &a; int pb = &b; int pc = &c;
int* arr[3] = {pa, pb, pc};
这里的数组arr存放的是指针变量
int* arr[3]可以这么理解:
- arr[ 3 ] 代表这是一个大小为3的数组,int* 代表这个数组里存的数据类型为int*
- 那么int* arr[ 3 ]的意思就是 叫做arr的数组里存放了3个类型为int*的指针变量
特别地:这个数组类型为:int* [ 3 ]
三、数组指针
1.指向一维数组的数组指针
- int * p[10]; 指针数组,数组里存放的是指针
p与[ ]优先级更高,所以p[10]先是个数组,存放的是int* 类型的数据 - int (*p)[10]; 数组指针,指向数组的指针
*p表示p是个指针变量,int [10]是数组类型,p是指向数组类型为int [10]的指针,所以指针变量p的类型为int (*) [10]- 这里举个例子方便你理解:
- int * p; 有*这个符号时,表示这个p是个指针变量,这个指针变量存放数据的类型为int, 所以这个指针变量p的类型是int*
int arr[10];
类型 | ||
---|---|---|
arr | 首元素地址 | int* |
&arr[0] | 首元素地址 | int* |
&arr | 整个数组的地址 | int (*) [10] |
数组指针:指针变量指向的是整个数组的地址,而不是首元素地址
即使这两个地址在数值上是一样的,但指针对这两个地址进行+/-的操作时,结果是不同的,因为本质上指向数组地址和指向首元素地址的指针类型是有区别的,
- 指向数组地址的指针类型为int (*) [10]
- 指向首元素地址的指针类型为int*
- 当对指向数组地址的指针进行+1,
- 这时指针指向数组末尾的位置,指针移动的步长是整个数组的大小
- 当对指向首元素地址的指针进行+1,这时指针指向的是数组中第二个元素的位置,是按照数组中元素所占大小决定步长,
- 若数组元素为char型,指针+1的意思就是指针移动1个字节
- 若为int型,指针移动4个字节
综上,这个表达式才是正确的 int (*p) [10] = &arr;
而不是 int (*p) [10] = arr;
p是个变量
—> *p表示p是个指针变量
—> (*p)[10]表示p指向的是个数组
—> int (*p)[10]表示的是指针变量p指向的数组,这个数组存放的是int型的数据
—> p的类型为int(*)[10]
2.指向二维数组的数组指针
首先我们要清楚
int arr[10];
- arr是数组名,数组名是首元素地址
- 二维数组的每一行可以理解为二维数组的一个元素
- 每一行又是一个一维数组
- 所以,二维数组其实就是存放一维数组的数组
- 二维数组的数组名,也是数组名,数组名就是首元素的地址
将二维数组中的每一行看成一个元素,等同于,一行就是一个一维数组,二维数组的数组名表示的是首元素的地址,那就是第一个元素的地址,那第一个元素不就是第一行吗,也就是第一个一维数组,因此二维数组的首元素地址,就是第一个一维数组的地址(这个一维数组的地址代表的是整个一维数组的地址,不是一位数组的首元素地址,因此后序如果指针+1时,改变的步长是整个一维数组的大小)
arr
-------- 首元素地址
-------- 第一行地址
-------- 一维数组的地址(整个一维数组的地址,不是一维数组首元素的地址)
四、数组指针传参
实际应用,这里用二维数组传参举例子
常规的二维数组传参,用二维数组接收
void func1(int arr[3][5], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
//利用数组指针接收地址,这里接收的实际上是二维数组arr的首元素地址,也就是第一个一维数组的数组地址
//所以需要用一维数组类型的指针接收就行
//*p表示p是个指针,指针指向的是个一维数组数据,这个一维数组的类型为int [5]
void func2(int(*p)[5], int row, int col)
{
//这里指针指向的是第一个元素的地址,这个元素实际是个一维数组,所以指针指向的是第一个一维数组的数组地址
for (int i = 0; i < row; i++)
{
//要访问一个数组的元素,就要遍历每个位置
for (int j = 0; j < col; j++)
{
//这里的指针p是一维数组的数组地址,
//p+i是决定p指向第几个一维数组,
//*(p+i)表示将一维数组地址转化为一维数组的首元素地址,有了首元素的值,才可以用指针访问一维数组中的元素
//(*(p+i)+j)表示指针指向该一维数组中下标为j的元素
//*((*(p + i)) + j)表示访问该元素的内容
printf("%d ", *((*(p + i)) + j));
//(*(p + i))相当于一维数组名,那么也可以这样访问数组中的元素(*(p + i))[j]
//arr是数组名,arr[n]用来访问数组中的元素,arr[n]的底层实现是:*(arr+n)
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,
2,3,4,5,6,
3,4,5,6,7 };
//func1(arr, 3, 5);
func2(arr, 3, 5);
return 0;
}
五、函数指针
函数指针:指向函数地址的指针
- 函数名是函数的地址
- &函数名也是函数的地址
int Add(int x, int y)
{ return x+y; }
int(*p)(int,int) = &Add;
int ---------------> 函数返回类型
(int,int) ---------> 函数参数
&Add --------------> 函数地址
指针变量p的类型 ----> int(*)(int,int)
调用函数:
int ret = Add(3,4);
int ret1 = p(4,5);
int ret2 = (*p)(5,6);
用指针p调用函数时,*可写可不写
六、函数指针数组
存放函数指针的数组
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(* pArr[4])(int,int) = {Add,Sub,Mul,Div};
首先pArr是个数组,数组里存放的数据类型为int(*)(int,int)
这个数组的类型为:int(*)[4](int,int)
下面这张图根据我可以尝试去运行,然后去解释每个地址
这是完整代码
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(* parr[4])(int,int) = {add,sub,mul,div};
//首先parr是个数组,数组里存放的数据类型为int(*)(int,int)
printf("%x\n", parr[0]);
printf("%x\n", add);
printf("%d\n", sizeof(int(*)(int, int)));
printf("%x\n", &parr[0]);
printf("%x\n", (&parr)[0]);
printf("%x\n", (&parr)[1]);
printf("%x\n", (&parr) + 1);
printf("%x\n", &(parr[3]) + 1);
return 0;
}
七、指向函数指针数组的指针
int(*p)(int,int) ------------------ 函数指针
int(*pArr[ ])(int,int) ------------ 函数指针数组
int(*(*p)[ ])(int,int) = &pArr ------------- 函数指针数组的地址
p就是指向函数指针数组的指针
首先要有个指针变量区接收这个函数指针数组,*p,然后再看这个指针指向数据的类型,函数指针数组的类型是,int(*)[ ](int,int)
八、回调函数
通过函数指针调用的函数就是回调函数
如果你把一个函数的地址作为参数传递给给另一个函数,当这个地址被用来调用其所指向的函数时,我们就说这是回调函数
这里我们引入两个知识:
- void
- ①void* 的指针,无具体类型的指针
- ②void* 类型的指针可以接收任何类型的地址
- ③这种类型的指针是不能直接用来解引用操作的
- ④也不能直接进行指针运算的
int a = 10;
float f = 3.14f;
int* pa = &a; ------- 可以
char* pc = &a; ------ 不行 &a的类型是int*
void* pr = &a; ------ 可以②
pr = &f; ------------ 可以②
*pr; ---------------- 不行③
pr++; --------------- 不行④
- qosort库函数
void qsort( void* base, //指向了需要排序的数组的第一个元素
size_t num, //排序的元素个数
size_t size, //一个元素的大小,单位是字节
int (*cmp)(const void*, const void*) //函数指针类型,这个指针指向的函数,能比较base指向数组中的元素
)
struct stu
{
char name[20];
int age;
};
int cmp(const void* p1, const void* p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
qsort库函数默认为排序结果为升序
int main()
{
struct stu arr[] = {{"猪八戒",30},{"孙悟空",20},{"沙僧",50}};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]),cmp);
for (int i = 0; i < sz; i++)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
return 0;
}
//利用冒泡排序实现qsort函数的功能
struct stu
{
char name[20];
int age;
};
int cmp(const void* p1, const void* p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
void Swap(char* p1, char* p2, int size)
{
for (int i = 0; i < size; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
void bubble_qsort(void* base, int num, int size, int(*cmp)(const void* p1, const void* p2))
{
for (int i = 0; i < num - 1; i++)
{
for (int j = 0; j < num - 1 - i; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 1)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
int main()
{
struct stu arr[] = { {"猪八戒",30},{"孙悟空",20},{"沙僧",50} };
int num = sizeof(arr) / sizeof(arr[0]);
int size = sizeof(struct stu);
bubble_qsort(arr, num, size, cmp);
for (int i = 0; i < 3; i++)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
return 0;
}