C语言_指针(2)

1.指针与数组的关系

1.1 数组名

先看代码:

#include <stdio.h>
int main()
{
 	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("arr = %p\n", arr);
 	return 0;
 }

运行结果是这样的:
在这里插入图片描述
我们可以看到,数组名和数组首元素地址打印出的结果一模一样。其实,数组名就是数组首元素的地址。但有两个例外

  • sizeof(数组名)sizeof中单独放数组名,表示的是整个数组,计算的是整个数组的大小,单位是字节。
  • &数组名:取出的是整个数组的地址。(整个数组的地址和数组首元素的地址是有区别的)。

除此之外,任何地方的数组名只表示首元素地址。

我猜,这个时候,有些朋友会运行下面的代码,想找出arr&arr的差异:

#include <stdio.h>
int main()
{
 	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 	printf("&arr[0] = %p\n", &arr[0]);
 	printf("arr = %p\n", arr);
 	printf("&arr = %p\n", &arr);
 	return 0;
}

运行出来三个一模一样的地址,这时,屏幕面前的朋友会质疑区分arr&arr的必要性。

其实他们的差异可以体现在指针的运算上,如arrarr+1相差4个字节,而&arr&arr+1相差40个字节。他们本身的大小不同,运算的跨度自然也相异。

有兴趣的朋友可以运行一下下方的代码:

#include <stdio.h>
int main()
{
 	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 	printf("&arr[0] = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0]+1);
 	printf("arr = %p\n", arr);
 	printf("arr+1 = %p\n", arr+1);
 	printf("&arr = %p\n", &arr);
 	printf("&arr+1 = %p\n", &arr+1);
 	return 0;
}

1.2 使用指针访问数组

代码如下:

#include<stdio.h>

int main(){
	int arr[10] = {0};
	int i = 0;
	int sz = sizeof(arr)/sizeof(arr[0]);
	int *p = arr;
	for(i=0; i<sz; i++){
		scanf("%d",p+i);//可以写arr+i
	}
	for(i=0; i<sz ; i++){
		printf("%d",*(p+i));//可以写*(arr+i)
	}
	return 0;
}

这段代码中说明了指针访问数组的方式。
我们知道数组元素的访问是用arr[i]的,那arr已经赋值给p,用p[i]可以达到同样效果吗?

答案是可以,有以下代码:

#include <stdio.h>

int main()
{
 	int arr[10] = {0};
 	int i = 0;
 	int sz = sizeof(arr)/sizeof(arr[0]);
 	int* p = arr;
 	for(i=0; i<sz; i++)
 	{
 		scanf("%d", p+i);
 	}
	for(i=0; i<sz; i++)
 	{
 		printf("%d ", p[i]);
 	}
 	return 0;
}

运行结果与第一段代码别无二致,所以我们可以知道本质上p[i]等价于*(p+i),同理arr[i]等价于*(arr+i)

数组元素的访问在编译器处理时,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。

1.3 一维数组传参的本质

先看代码:

#include<stdio.h>

void test(int arr[]){
	int sz2 = sizeof(arr)/sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}

int main(){
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int sz1 = sizeof(arr)/sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

运行结果如下:

在这里插入图片描述
我们可以看到,在函数内部是没有正确获得数组的元素个数的。

我们知道,数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。

所以函数形参理论上应该用指针变量来接收首元素的地址。正是因为函数的参数部分的本质是指针变量,所以当首元素地址传入函数时,数组名变成了一个彻彻底底的地址,sizeof(arr)返回的是一个地址的大小。

注:
一维数组传参,函数形参的部分既可以写成数组的形式,也可以写成指针的形式。如下函数代码段:

void test(int* arr){//指针形式的形参
	printf("%d",sizeof(arr));//计算的是一个指针变量的大小
}

1.4 指针数组

指针数组,听到这个名字,有些朋友就会郁闷了,指针数组是指针还是数组?

我们可以类比一下,整形数组,即存放整形的数组,既然这样,那指针数组不就是存放指针的数组咯。

1.4.1 指针数组的定义

我们在定义整形数组和字符数组时,分别用了intchar,所以数组的内容是什么类型就用什么类型来定义。

如定义一个字符指针数组整形指针数组

char* arr[5] = {NULL};
int* arr1[6] = {NULL};

1.4.2 指针数组模拟二维数组

代码实现如下:

#include<stdio.h>

int main(){
	int arr1[] = {1,2,3,4,5};
	int arr2[] = {2,3,4,5,6};
	int arr3[] = {3,4,5,6,7};
	
	int* p[3] = {arr1,arr2,arr3};
	int i = 0;
	int j = 0;
	for(i=0;i<3;i++){
		for(j=0;j<5;j++){
			printf("%d\t",p[i][j]);//等同于 *(*(p+1)+j)
		}

	}
	return 0;
}

上方代码模拟出了二维数组的效果,但与二维数组有所差异,它的行与行之间不是连续的

1.5 数组指针变量

1.5.1 对数组指针变量形式的理解

数组指针变量,顾名思义,是用来存放数组地址的。形式如下:

int (*p)[10]

解释:p先与*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整形的数组。所以p是一个指针,指向一个数组,叫数组指针。

数组指针的初始化方式如下:

int arr[10] = {0};
int(*p) = &arr;


[ ]的优先级高于 * 号,所以必须加上()保证p先和 * 结合。

1.5.2 二维数组传参的本质

二维数组传参代码如下:

#include<stdio.h>

void test(int a[3][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",a[i][j]);
		}
		printf("\n");
	}
}
int main(){
	int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
	test(arr, 3, 5);
	return 0;
}

二维数组可以看做每个元素为一维数组的数组。那么二维数组的首元素就是第一行,是一个一维数组。如下图所示:

在这里插入图片描述
二维数组传参传的也是首元素的地址,也就是第一行这个一维数组的地址,拿上方代码举例子,二维数组中元素是int [5];所以首元素地址的类型就是数组指针类型 (int(*)[5]) 。当然,函数形参也可以写成指针形式的。如下方函数代码段:

#include<stdio.h>

void test(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+1)+j));
		}
		printf("\n");
	}
}

2. 指针与函数的关系

2.1 函数指针变量

函数指针变量是用来存放函数地址的,函数地址可以通过函数名和&函数名的方式获得

函数指针类型解析如下:
在这里插入图片描述

2.1.1 函数指针变量的创建

函数指针变量的创建有两种情况,代码如下:

无参数时

void test(){
	printf("hello world!!");
}

void (*pf1)() = test;
void (*pf2)() = &test;

有参数时

int Add(int x, int y){
	return x+y;
}

void (*pf3)(int, int) = Add;//x和y写上或省略都可以
void (*pf4)(int x,int y) = &Add;

2.1.2 函数指针变量的使用

先看代码:

#include<stdio.h>

int Add(int x, int y){
	return x+y;
}

int main(){
	int (*pf3)(int, int ) = Add;
	printf("%d\n", (*pf3)(2, 3));
 	printf("%d\n", pf3(3, 5));
	return 0;
}

输出结果:
在这里插入图片描述
可以通过函数指针调用指针指向的函数。

分享俩个有趣的代码:
代码一:

(*(void (*)())0)();

代码二:

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

这两段代码都出自《C陷阱与缺陷》

2.2 函数指针数组

函数指针数组的定义形式如下:

int (*parr[2])();

parr先和 [ ] 结合,说明 parr 是数组,数组的内容是 int (*)( ) 类型的函数指针。

函数指针数组的用途:转移表

例如,计算器的一般实现:

#include<stdio.h>

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 x, y;
 	int input = 1;
 	int ret = 0;
 	do
 	{
 		printf("*************************\n");
 		printf(" 1:add 			   2:sub \n");
		printf(" 3:mul 			   4:div \n");
 		printf(" 0:exit                  \n");
 		printf("*************************\n");
 		printf("请选择:");
 		scanf("%d", &input);
 		switch (input)
 		{
 		case 1:
 			printf("输⼊操作数:");
 			scanf("%d %d", &x, &y);
 			ret = add(x, y);
 			printf("ret = %d\n", ret);
 			break;
 		case 2:
 			printf("输⼊操作数:");
 			scanf("%d %d", &x, &y);
 			ret = sub(x, y);
 			printf("ret = %d\n", ret);
 			break;
 		case 3:
 			printf("输⼊操作数:");
 			scanf("%d %d", &x, &y);
 			ret = mul(x, y);
 			printf("ret = %d\n", ret);
 			break;
 		case 4:
 			printf("输⼊操作数:");
 			scanf("%d %d", &x, &y);
 			ret = div(x, y);
 			printf("ret = %d\n", ret);
 			break;
 		case 0:
 			printf("退出程序\n");
 			break;
 		default:
 			printf("选择错误\n");
 			break;
 		}
	}while(input);

	return 0;

使用函数指针数组的实现:

#include<stdio.h>

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 x, y;
	int input = 1 ;
	int ret = 0;
	int (*p[5])(int x.int y) = { 0, add, sub, mul, div };//转移表
	do
	{
		printf("*************************\n");
 		printf(" 1:add 			   2:sub \n");
		printf(" 3:mul 			   4:div \n");
 		printf(" 0:exit                  \n");
 		printf("*************************\n");
 		printf("请选择:");
 		scanf("%d", &input);
 		if(input<=4 && input >= 1)
 		{		
 			printf( "输⼊操作数:" );
 			scanf( "%d %d", &x, &y);
 			ret = (*p[input])(x, y);
 			printf( "ret = %d\n", ret);
		}
		else if(imput = 0){
			printf("退出计算器\n")}
		else{
			printf("输入有误\n");
		}

	}while(input);
	
	return 0;
}

3. 字符指针变量

字符指针char* 有两种使用方式。
一般使用:

int main(){
	char ch = 'a';
	char* pc = &ch;
	*pc = 'a';
	return 0;
}

还有一种使用方式如下:

int main(){
	const char* pstr = "hello";
	return 0;
}

代码 const char* pstr = “hello”;的本质是把字符串hello首字符的地址放到了pstr中。

《剑指offer》中有一到和字符串相关的题,我们来一起看一下:

#include <stdio.h>
int main()
{
 	char str1[] = "hello bit.";
 	char str2[] = "hello bit.";
 	const char *str3 = "hello bit.";
 	const char *str4 = "hello bit.";
 	if(str1 ==str2)
 		printf("str1 and str2 are same\n");
 	else
 		printf("str1 and str2 are not same\n");
 
 	if(str3 ==str4)
 		printf("str3 and str4 are same\n");
 	else
 		printf("str3 and str4 are not same\n");
 
 	return 0;
}

运行结果:
在这里插入图片描述

这段代码中str3str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但要是用相同的常量字符串取初始化两个不同的字符数组时,就会开辟出不同的内存块。所以str1str2不同。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值