【C语言进阶】--- 指针详解 3.0

接下来进入指针的进阶部分,准备好大脑

补充:(重点)

数组名是数组首元素地址
数组首元素地址和数组地址,值相同,但本质不同,
区别在于二者的类型不相同
比如数组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.指向一维数组的数组指针

  1. int * p[10]; 指针数组,数组里存放的是指针
    p与[ ]优先级更高,所以p[10]先是个数组,存放的是int* 类型的数据
  2. 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)

八、回调函数

通过函数指针调用的函数就是回调函数
如果你把一个函数的地址作为参数传递给给另一个函数,当这个地址被用来调用其所指向的函数时,我们就说这是回调函数
这里我们引入两个知识:

  1. 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++; --------------- 不行④
  1. 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;
}
  • 43
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值