char* ps = "hello world"; //1.
char arr[] = "hello world"; //2.
char* ps本质上是把这串字符的首字符的地址放在ps中,因此,它与字符串在数组的存储是有差异的。指针和数组并不是相等的。
在内存中表示:
下面通过一段代码来进一步加深认识:
#include<stdio.h>
int main()
{
char str1[] = "hello";
char str2[] = "hello";
char* str3 = "hello";//"hello"为常量字符串,最好在char 前面加上const,防止str指向的内容被修改
char* str4 = "hello";
if (str1 == str2)
printf("str1==str2\n");
else
printf("str1!=str2\n");
if (str3 == str4)
printf("str3==str4\n");
else
printf("str3!=str4\n");
return 0;
}
两个数组开辟了两个不同的空间,因此,这两个数组各自代表了自己首元素的地址,其地址不同;而指针则指向同一个地址。
总结:
指针和数组并不相等。
- 声明数组时会同时分配一些内存空间,以便容纳数组元素;
- 声明指针时,只会分配用于容纳指针本身的空间。
这也是为什么数组名传参时对其进行直接操控不会改变实际参数的原因。
指针数组
指针数组,顾名思义即存放指针的数组
int main()
{
int* arr1[10];// - 指针数组
char* arr2[4];// - 指针数组
char** arr3[5];// - 指针数组
return 0;
}
模拟二维数组
int main()
{
int a[] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = { 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));
//printf("%d ", arr[i][j]);//模拟二维数组
}
printf("\n");
}
return 0;
}
数组指针
数组指针,顾名思义即存放数组的指针
int(*parr)[10] = &arr;// - 数组指针
含义:parr指向的数组含10个int型元素
如果打算在指针上执行指针运算,应该避免:
int (*p)[ ] = &arr;
当某个整数与这种类型的指针执行指针运算时,他的值将根据空数组的长度进行调整,可能会产生意想不到的结果。
数组名与&数组名的区别
数组名表示数组首元素地址;
&数组名表示整个数组的地址;
int main()
{
int arr[10] = { 0 };
int* p1 = arr;//整型指针
int(*p2)[10] = &arr;//数组指针
printf("%p\n", p1);
printf("%p\n", p1 + 1);//跳过一个整型
printf("%p\n", p2);
printf("%p\n", p2 + 1);//跳过一个数组
return 0;
}
易混辨析
数组参数 指针参数
一维数组传参
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);//arr2为存放整型指针的数组
return 0;
}
下面那些能够接收参数呢?
void test(int arr[]) {}
void test(int arr[10]) {}
void test(int *arr) {}
void test2(int *arr[20]) {}
void test2(int **arr) {}
都能。
二维数组传参
int main()
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
下面那些能够接收参数呢?
void test(int arr[3][5]) {}
void test(int arr[][]) {} //error
void test(int arr[][5]) {}
void test(int* arr) {} //error 传过来第一行的地址 - 即数组的地址 - 不能用一级指针接收
void test(int* arr[5]) {}//error
void test(int (*arr)[5]) {}
void test(int **arr) {}//error
一级指针传参
函数参数部分为一级指针时能够接收参数形式:
void test(char*)
{
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
char ch = 'w';
test(&ch);
char* p1 = &ch;
test(p1);
return 0;
}
二级指针传参
函数参数部分为一级指针时能够接收参数形式:
void test(int** ptr){}
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
test(ppa);//
test(&pa);//
int* arr[10] = { 0 };
test(arr);//传存放一级指针的数组也是可以的
printf("%d\n", a);
return 0;
}
函数指针
int f(int x,int y)
{}
int main()
{
int (*pf)(int,int) = &f;//初始化
int ret = (*pf)(3, 5);//函数调用
//等价于int ret = pf(3,5); - 间接访问操作非必须,编译器需要的只是一个函数指针
//等价于int ret = f(3, 5);
//这就是我们平时常用的使用函数名的形式调用函数
printf("%p\n", &f);
//printf("%p\n", f);
return 0;
}
int (*pf)(int,int) = &f;//初始化
int ret = (*pf)(3, 5);//函数调用
表达式中的&操作符不是必需的。 因为函数名会被编译器转化为函数指针。我们使用&
操作符只是为了更容易说明关系。也可以推出:f
和pf
等价
通过函数指针进一步了解函数声明:
比如:
void (signal(int, void()(int)))(int);
//可以理解为
void()(int) signal(int,void()(int)
在这里插入代码片
而在编译器上使用时我们可以使用typedef
(对类型重定义)
typedef void(*pfun_t)(int)
函数指针数组
int (*pf1)(int, int) = Add;//函数指针
int (*pf2)(int, int) = Sub;
int (*pfArr[2])(int, int);//函数指针数组
int (*pfArr[2])(int, int) = { Add,Sub };//函数指针数组初始化
函数指针数组自然只能存放相同类型的指针。
既然函数指针是一个数组,它就有地址。那么我们如何取出函数指针数组的地址呢?
int main()
{
int(*p)(int, int);//函数指针
int(*p2[4])(int, int);//函数指针的数组
int(* (*p3)[4] )(int, int) = &p2;//取出函数指针数组的地址
return 0;
}
📝:去掉数组名和元素个数,剩下的就是数组元素类型
函数指针两个用途:
- 转换表
- 作为参数传递给另一个函数 - 回调函数
回调函数
回调函数是一个通过函数指针调用的函数。
通过qsort函数及其延伸理解回调函数
首先介绍qsort函数:
qsort(arr, sz, sizeof(arr[0]), cmp_int);
接下来模仿qsort函数实现冒泡排序(任何类型):
struct Stu
{
char name[20];
int age;
};
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;//强制类型转化为整型指针 - 升序
//return *(int*)e2 - *(int*)e1;//强制类型转化为整型指针 - 降序
}
int sort_age(const void* e1,const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int sort_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)//width - 宽度,比较每一个字节
{
char t = *buf1;
*buf1 = *buf2;
*buf2 = t;
buf1++;
buf2++;
}
}
void bubble_sort(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;
for (j = 0; j < sz - 1; j++)
{
if (cmp((char*)base+j*width,(char*)base+(j+1)*width ) > 0)//比较两个元素地址
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
void test()
{
struct Stu s[] = { {"zhang",30},{"li",34},{"wan",20} };
bubble_sort(s, 3, sizeof(s[0]), sort_age);//年龄
//bubble_sort(s, 3, sizeof(s[0]), sort_name);//名字
}
int main()
{
test();
}
进一步理解回调函数:
通过编写用于比较两个值的函数,然后将一个指向该函数的指针作为参数传递给查找函数。通过该查找函数调用这个函数来执行 值的比较。这样,我们只需要编写不同类型的比较函数,就能比较不同类型的值。事实上,查找函数与类型无关,因为它本身并不执行实际的比较。
❓ 为什么强制转换为char*类型?
char*加1时,才会跳过1个字节,才能转换成要交换的元素的类型
❓ 为什么用void*?
因为我们需要查找函数能作用于任何类型的值,所以将参数类型声明为void*,表示“一个指向未知类型的指针”