指针进阶——字符指针、数组指针与指针数组、数组参数和指针参数

字符指针

顾名思义,字符指针是指向char类型的指针变量。由指针基础中我们得知,不同类型的指针变量前进的距离以及解引用的权限存在不同。字符指针+1,指针前进一个字节,借助这个特性,当我们需要逐个字节访问数据的时候,可以使用字符指针变量,简单的使用方法如下所示。

int main(){
	const char* pstr = "hello world";
	printf("%s\n", pstr);
	return 0;
}

在这里插入图片描述

其中,字符串"hello world",是将其首地址存放在了pstr指针变量中,而不是将整个字符串存放在指针变量中。因此,观察如下代码。

int main(){
	char str1[] = "hello world.";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";
	if (str1 == str2)
		printf("0\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("0\n");
	return 0;
}

显而易见,由于数组的创建会开辟出不同的内存块,即使使用相同的字符串进行初始化,因此str1和str2不同。而不同的字符指针指向同一个字符串时,由于常量字符串存储到单独的一个内存区域,因此他们实际会指向同一块内存,所以str3和str4相同。
运行结果如下所示:
在这里插入图片描述


数组指针

与数组指针相对的是指针数组,其本质是数组。

    int a[10];
    // 整型数组。存放的数据类型是整型
    int* a[10];
    // 一级整型指针数组。存放的数据类型是int*
    int** a[10];
    // 二级整型指针数组。存放的数据类型是int**

数组指针,本质则是指针,用于存放数组的地址。
因为 [] 的优先级要高于 *号, 所以使用括号让*先于变量结合。
一种较为少用的使用方法如下:

    int a[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &a;
    // 对比
    int* pa = a;

从中不难发现一个不同,&数组名的数据类型是int (*)[10],而数组名的数据类型是int*,区别在于,数组名代表的是数组首元素地址,&数组名代表的是整个数组地址,通过如下代码可发现他们的不同之处。

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

运行结果如下图所示:
在这里插入图片描述

所以数组指针,是一个指向数组地址的指针。int (*p)[10] = &a中,数组指针p指向的是一整个数组a。
数组指针常用情况是在二维数组的使用中,例如打印二维数组。

void print2(int a[3][5], int c, int r) {
	for(int i = 0; i < c; i++) {
		for (int j = 0; j < r; j++) {
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}
void print22(int (*p)[5], int c, int r) {
	for (int i = 0; i < c; i++) {
		for (int j = 0; j < r; j++) {
			printf("%d ", *(*(p + i) + j));
			//p+i 指向 第i行
			//*(p+i)相当于拿到第i行,又相当于第i行首元素地址
		}
		printf("\n");
	}
	printf("\n");
}
int main() {
	int a[3][5] = { {1,2,3,4,5,},{6,7,8,9,10},{11,12,13,14,15 } };
	print2(a, 3, 5);
	print22(a, 3, 5);
	return 0;
}

print2() 使用数组下标对二维数组进行打印,print22() 使用 int (*p)[5] 接收二维数组a的首元素地址,也就是第一行元素的地址,通过对 (p + i) 解引用拿到第i行的元素,接着对 *(p + i) + j) 进行解引用,拿到第i行第j列的元素。
通过上面讲解,可轻松识别如下代码:

    int a[5];
    // a是一个数组,存放5个元素,每个元素的类型是int
    int *p1[10];
    // p1是一个数组,存放10个元素,每个元素的类型是int*
    int (*p2)[10];
    // p2是一个指向数组的指针,指向的数组有10个元素,每个元素的类型是int
    int (*p3[10])[5];
    // p3是一个数组,存放10个元素,每个元素的类型是int(*)[5]

数组参数和指针参数

把握首要原则:类型匹配原则
一维数组传参
如果我们要创建一个test()函数,分别传递给如下两个一维数组a1和a2作为参数,形参部分该如何写呢?

	int a1[10] = { 0 };
	int* a2[10] = { 0 };
	test(a1);
	test2(a2);

第一种方法是形参写成数组形式:

	void test1(int arr[]){}		// 接收a1,形参部分数组大小省略
	void test12(int arr[10]){}	// 接收a1,不过不建议使用
	void test2(int* arr[]){}	// 接收a2

形参部分数组大小可省略的一个原因在于,C语言在传递数组时,不会传递整个数组,而是传递数组的首元素地址,在一维数组中便是传递第一个元素。
第二种方法是形参写成指针形式:

	void test1(int *arr){}		// 接收a1
	void test2(int **arr){}		// 接收a2

二维数组传参
当传参对象变为二维数组时,形参同样有两种写法。

	int a3[3][4] = {0};
	test3(a3);

第一种方法是形参写成数组形式:

	void test3(int arr[3][4]){}	// 接收a3,形参部分数组大小完整
	void test32(int arr[][4]){}	// 接收a3,形参部分数组省略行数
	void test33(int arr[][]){}	// err,该方法省略列数,报错

与一维数组传参不同之处在于,形参部分的数组行可省略,列不可省略
第二种方法是形参写成指针形式:

	void test3(int (*arr)[4]){}	// 接收a3
	void test3(int *arr){}		// err
	void test3(int* arr[5]){}	// err
	void test3(int **arr){}		// err

为什么只有形参类型为 int (*arr)[4]) 才能接受成功呢?
本质是因为二维数组传参时,数组名表示的是首元素地址,也就是第一行元素的地址,该元素的数据类型是 int (*)[4] ,也就是传递了一个一维数组,因此形参的类型必须要跟实参传递的类型相匹配。


一级指针传参
与数组传参相比,指针传参相对简单,但是判断标准仍是类型是否相互匹配。

	void test(int *p){}	// 接收地址

如给上述形参类型为一级指针的test()函数传参,实参可写为如下几种常见形式:

int main(){
	int a = 0;
	int* p = &a;
	int arr[10] = {0};
	test(&a);	// 变量的地址
	test(p);	// 指针变量p中存放变量a地址
	test(arr);	// 数组名表示数组首元素的地址
	return 0;
}

二级指针传参
与一级指针类似,常见情况如下:

	void test(int **pp){}	// 二级指针接收一级指针的地址
int main(){
	int a = 0;
	int* p = &a;
	int** pp = &p;
	int* arr[10] = {0};
	test(&p);	// 一级指针的地址
	test(pp);	// 二级指针变量pp中存放一级指针变量p地址
	test(arr);	// 首元素的地址是一级指针的地址(首元素的类型是int*)
	return 0;
}

总结

  • 字符指针是指向char类型的指针变量,因为其前进距离和解引用权限的特殊性,通常在逐个字节访问数据的时候进行使用。
  • 指针数组是存放指针的数组,数组指针是指向数组的指针,前者本质是数组,后者本质是指针。数值指针通常用于二维数组。
  • 数组和指针传参,尽管有些复杂,但是最重要的地方在于,都要把握的形参和实参类型相互匹配的原则。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值