深度解析C指针的进阶

学习目的

了解字符指针

了解数组指针

了解指针数组

了解数组传参和指针传参

了解函数指针

目录

指针

字符指针

字符指针定义

常量与字符指针

常量的储存区域

指针数组

指针数组定义

模拟二维数组

数组指针

数组指针的定义

数组指针的格式

数组指针的类型

&数组名与数组名

数组指针的使用

数组传参和指针传参

一维数组的传参

二维数组的传参

一级指针的传参

二级指针的传参

函数指针

函数指针定义

函数指针的类型

函数名与函数指针

函数指针数组

指向函数指针数组的指针

回调函数

指针

指针实际意义就是地址

口语中的指针是指针变量,用来存放地址,地址唯一表示一块空间。

指针的大小是固定的4/8个字符,32位平台/64位平台

指针是有类型,类型决定了步长,决定解引用时操作的权限

指针的运算

指针加减整数

指针减指针

指针的关系运算

字符指针

字符指针定义

指向字符的指针

int main()
{
	char ch = 'W';
	char *pc = &ch;
	printf("%c\n", *pc);
	*pc = 'c';
	printf("%c\n", *pc);
	system("pause");
	return 0;
}

常量与字符指针

int main()
{
	char *pc = "hello world!";
	printf("%s\n", pc);
	system("pause");
	return 0;
}

字符指针也可以指向常量,但有几个注意事项

1、使用时直接使用指针,不能解引用

2、指向常量的指针,不能解引用修改其内容

3、使用时最好加上const,防止解引用修改

注意1
int main()
{
	char *pc = "hello world!";
	printf("%s\n", *pc);
	system("pause");
	return 0;
}

 %s格式对应的是字符串%s对应类型为char * , 即字符串。

注意2
int main()
{
	char pc = "hello world!";
	*pc = 'W';
	/*printf("%s\n", *pc);*/
	system("pause");
	return 0;
}

注意3
int main()
{
	const char *pc = "hello world!";
	printf("%s\n", pc);
	system("pause");
	return 0;
}

常量的储存区域

int main()
{
	const char *str1 = "HELLO WORLD!";
	const char *str2 = "HELLO WORLD!";
	char str3[] = "HELLO WORLD!";
	char str4[] = "HELLO WORLD!";
	if (str1 == str2)
	{
		printf("str1等于str2\n");
	}
	else
	{
		printf("str1不等于str2\n");
	}

	if (str3 == str4)
	{
		printf("str3等于str4\n");
	}
	else
	{
		printf("str3不等于str4\n");
	}

	system("pause");
	return 0;
}

 

 上面这道题本质是对字符指针的比较,实际比较的内容是地址

字符串字面量会存储到一个单独的区域,只读区域的常量存放区,只能读取不能改变,为了节省空间,同样的内容在常量存放区只存在一个,所以str1与str2,指向的是同一块内存区域,它们的值都是"HELLO WORLD!"的首元素‘H’的地址。

而str3和str4是两个不同的内存空间,虽然存放同样的字符串,但是空间并不一样,所以str3与str4的地址是不同的

指针数组

指针数组定义

指针数组就是存放指针的数组

int arr[10]整型数组
char ch[10]字符数组
int *arr2[10]一级指针数组
int **arr3[10]二级指针数组

 指针数组的类型是int * [10]

模拟二维数组

int main()
{
	int arr1[5] = { 1, 2, 3, 4, 5 };
	int arr2[5] = { 6, 7, 8, 9, 10 };
	int arr3[5] = { 11, 12, 13, 14, 15 };
	int *arr[3] = { arr1, arr2, arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", *(*(arr + i) + j));//arr是首元素地址,这里的arr是arr1的地址,arr1是arr1[0]的地址
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

 

数组指针

数组指针的定义

指向数组的指针

数组指针的格式

int (*arr)[10]

这里arr先与*结合,说明是指针,然后指向的是一个大小为10的整型数组

数组指针的类型

type (*) [const_n]

&数组名与数组名

数组名是首元素的地址,二维数组的首元素是第一行

例外:&数组名与sizeof(数组名)时,数组名指的是整个数组

int main()
{
	int arr[10];
	printf("arr   =%p\n", arr);
	printf("&arr  =%p\n", &arr);
	printf("arr+1 =%p\n", arr+1);
	printf("&arr+1=%p\n", &arr+1);
	system("pause");
	return 0;
}

arr与&arr的地址相同是数组首元素的地址,但是arr+1时,地址只是向后前进了4个字节,因为是整型,可以理解为arr向前走了一步,这里arr可以理解为指向整型变量的指针,&arr+1向后前进了28个字节(28是十六进制,换算成十进制就是40个字节),所以&arr是整个数组的地址。

int main()
{
	int arr[10];
	printf("%d\n", sizeof arr);
	system("pause");
	return 0;
}

 

这里的结果是40,与int类型4个字节的结果相差十倍,所以sizeof(arr)时arr指的也是数组。

再看一组有意思的代码

int main()
{
	int arr[10];
	int *p = arr;
	int(*pa)[10] = arr;//int(*pa)[10] = &arr;
	printf("*p= %d\n", sizeof(*p));
	printf("*pa=%d\n", sizeof(*pa));
	printf("p=  %d\n", sizeof(p));
	printf("pa  %d\n", sizeof(pa));
	system("pause");
	return 0;
}

 

 为什么*p=4,*pa=40而p=4,pa=4呢

p与pa是指针,所以p的内容是arr的地址,pa的内容也是arr的地址,所以它们的内容是一样的,

但是类型不同,p的类型是指向整型变量的指针,所以p只是指向首元素,但是pa是指向10个元素的整型数组的指针,所以pa指向的是整个数组

那为什么要解引用才可以?

因为p和pa保存的是数组首元素的地址,先解引用才能找到相关的元素进行计算,直接计算只是计算了首元素指针所占空间的长度,结果为4(32位)/8(64位)

再看下一段代码

void print(int(*pa)[10])
{
	for (int i = 0; i < 10; i++)
	{
		printf("%p ", *(pa + i));
	}
}

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	print(&arr);
	system("pause");
	return 0;
}

 结果是打印了10个地址,每个地址之间相差40个字节,这是因为pa的内容是arr传过来的地址,指向一整个数组,*(pa+i)只是解了pa的内容,得到里面的值是arr的地址

所以,相同数组指针打印数组的值,还需要再解引用一次

void print(int(*pa)[10])
{
	for (int i = 0; i < 10; i++)
	{
		printf("%p ", *((*pa) + i));
	}
}

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	print(&arr);
	system("pause");
	return 0;
}

这里 (*pa)首先是对pa解引用,找到arr的地址,然后再对(arr+i)解引用,找到数组的值,但是这样使用,非常不方便。不如直接使用int *pa,那么数组指针的正确使用场景是什么呢?

数组指针的使用

void print(int(*pa)[5])
{
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", *(*(pa + i) + j));
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }, { 11, 12, 13, 14, 15 } };
	print(&arr);
	system("pause");
	return 0;
}

上面这段代码中,*(pa+i)相当于整个二维数组的某一行, 取得地址后,*(pa+i)+j,相当于二维数组中某一个元素的地址,最后解引用得到了二维数组的值。

指向数组指针的数组

int (*pa[10])[5]

是指一个容纳数组指针的数组 ,其中int (*) [5] 是数组指针类型,pa[10]是一个数组

数组传参和指针传参

一维数组的传参

数组传参的本质是传递数组的首地址是指针,形参就是接受首地址,指针形参只能接收变量,所以int *arr,int **arr2可以接受

//一维数组的传参

void test1(int arr[])
{
	printf("arr[]传址\n");
}

void test2(int arr[10])
{
	printf("arr[10]传址\n");
}

void test3(int *arr)
{
	printf("int *arr传址\n");
}

void test4(int *arr2[])
{
	printf("int *arr[]传址\n");
}

void test5(int *arr2[10])
{
	printf("int *arr[10]传址\n");
}

void test6(int **arr2)
{
	printf("int **arr传址\n");
}

int main()
{
	int arr[10];
	int *arr2[10];
	test1(arr);
	test2(arr);
	test3(arr);
	test4(arr2);
	test5(arr2);
	test6(arr2);
	system("pause");
	return 0;
}

二维数组的传参

//二维数组传参

void test1(int arr[][5])
{
	printf("int arr[][5]传参\n");
}

void test2(int arr[3][5])
{
	printf("int arr[3][5]传参\n");
}

void test3(int (*arr)[5])
{
	printf("int (*arr)[5]传参\n");
}

int main()
{
	int arr[3][5];
	test1(arr);
	test2(arr);
	test3(arr);
	system("pause");
	return 0;
}

一级指针的传参

指针传递的本质就是地址,所以任何以地址形式的参数,都能被指针形参即接收

void test1(int *p)
{
	printf("&a\n");
}

void test2(int *p)
{
	printf("p\n");
}

void test3(int *p)
{
	printf("*pp\n");
}

void test4(int *p)
{
	printf("arr\n");
}

void test5(int *p)
{
	printf("&arr2[0][0]\n");
}

int main()
{
	int a = 10;
	int *p = &a;
	int **pp = &p;
	int arr[10];
	int arr2[3][5];
	test1(&a);
	test2(p);
	test3(*pp);
	test4(arr);
	test5(&arr2[0][0]);
	system("pause");
	return 0;
}

二级指针的传参

void test1(int **p)
{
	printf("&p\n");
}

void test2(int **p)
{
	printf("pp\n");
}

void test3(int **p)
{
	printf("arr\n");
}

void test4(int **p)
{
	printf("*ppp\n");
}

int main()
{
	int a = 10;
	int *p = &a;
	int **pp = &p;
	int *arr[10];
	int ***ppp = &pp;
	test1(&p);
	test2(pp);
	test3(arr);
	test4(*ppp);
	system("pause");
	return 0;
}

函数指针

函数指针定义

函数指针就是指向函数的指针

函数指针的类型

type(*) (形参,形参,......... )

函数名与函数指针

函数名就是函数的地址,也就是函数的指针

void test()
{
	;
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	system("pause");
	return 0;
}

 再看一段有趣的代码

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 10, b = 20;
	int(*c)(int, int) = add;
	printf("*c=%p\n", *c);
	printf("c=%p\n", c);
	printf("add=%p\n", add);
	printf("add=%p\n", &add);

	system("pause");
	return 0;
}

 

 因为arr的本质就是函数的地址,所以对指针c进行解引用时得到的也会是函数arr的地址,函数取地址时形成了一个有趣的现象,函数名与&函数名,指向函数名的指针与解引用指针,最后都会的到函数的地址

有趣的问题1.函数是在什么时候开辟空间的

函数指针的使用

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 10, b = 20;
	int(*c)(int, int) = add;
	int ret = (*c)(a, b);
	//printf("*c=%p\n", *c);
	//printf("c=%p\n", c);
	//printf("add=%p\n", add);
	//printf("add=%p\n", &add);
	printf("%d\n", ret);
	system("pause");
	return 0;
}

 int(*c)(int, int)定义了一个返回类型为int,需要两个int参数的指针,指针的地址为add函数的地址,(*c)(a, b)是通过(*c)指针调用函数,同时将a和b作为实参进行传递,(*c)的本质上就是函数地址(a, b)是两个变量,所以(*c)(a, b)与add(a,b)的本质是一样的。

有趣的函数指针代码

代码1:
(*(void(*)())0)();

 上面这段代码void(*)()是返回类型为空,无需参数的函数指针类型,(void(*)())0是将void(*)()的值作为一个类型,将0强制转换成(void(*)())类型,然后用被转换后的0作为指针

代码2
void(*signal(int, void(*)(int)))(int)

  void(*)(int)是返回值为空,参数为int的函数指针类型,signal(int, void(*)(int))是名为signal具有一个整型参数与一个空类型参数的函数,void(*signal(int, void(*)(int)))(int)这里是返回类型为void,参数为int类型,signal函数的值为指针的声明

上面的写法太麻烦可以使用typedef进行简化

void(*signal(int, void(*)(int)))(int)

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

 注意:pfun_t作为void(*)(int)的别名,定义时必须放在(*)里面

函数指针数组

函数指针数组关键词在数组,通俗的说就是保存函数指针的数组

函数指针数组的类型

type (*p[])(形参,形参.......)

函数指针数组的使用(转移表)

当一个程序需要定义大量相同返回类型,相同类型及相同个数的参数时,可以使用转移表

下面的函数通过使用转移表,极大的减少了代码量

void menu()
{
	printf("**************\n");
	printf("*1.加法2.减法*\n");
	printf("*3.乘法4.除法*\n");
	printf("**************\n");
}

int ADD(int a, int b)
{
	return a + b;
}

int SUB(int a, int b)
{
	return a - b;
}

int MUL(int a, int b)
{
	return a * b;
}

int DIV(int a, int b)
{
	return a / b;
}


int main()
{
	int a, b;
	int(*pa[5])(int, int) = { 0, ADD, SUB, MUL, DIV };
	int input;
	do
	{
		menu();
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			printf("计算结果=%d\n", (*pa[input])(a, b));
		}
		else if (0 == input)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("重新输入\n");
		}
	} while (input);
	system("pause");
	return 0;
}

指向函数指针数组的指针

指向函数指针数组的指针,就是该指针指向函数指针的数组

指向函数指针数组的指针的类型

void (*(*pa)[5])()

 首先pa与*结合说明pa是一个指针,(*pa)与[5]结合,说明(*pa)是指向元素为5的数组,void(*(*pa)[5])()说明pa是指向返回类型为空,参数为空,有5个元素的函数指针数组的指针

指向函数指针数组的指针的使用

int ADD(int a, int b)
{
	return a + b;
}

int SUB(int a, int b)
{
	return a - b;
}

int MUL(int a, int b)
{
	return a * b;
}

int DIV(int a, int b)
{
	return a / b;
}

int main()
{
	int a = 10, b = 20;
	int(*p[5])(int,int) = { 0, ADD, SUB, MUL, DIV };//函数指针数组
	int(*(*pp)[5])(int,int) = &p;
	printf("%d\n", (*(*pp)[1])(a, b));
	system("pause");
	return 0;
}

回调函数

回调函数就是一个通过函数指针调用的函数。

如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这个时回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时,由另外一方调用的,用于对该事件或条件进行相应

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值