玩透指针系列—— 渐入佳境,指针还能这么用!

Posted Chaser Feng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了玩透指针系列—— 渐入佳境,指针还能这么用!相关的知识,希望对你有一定的参考价值。

玩透指针系列(一)中我们已经知道了指针的基础知识:
1 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。

接下来在本篇博客中将继续探究指针的进阶知识。

1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*
一般用法:

int main()
{
  char ch = 'w';//此时的'w'为常量字符
  char *pc = &ch;
  *pc = 'w';
  return 0;
}

还有一种使用方式:

int main()
{
char* pstr = "hello girl.";//此时"hello bit."为常量字符串
printf("%s\\n",pstr);
return 0;
}

说明:上述的 “hello girl.” 为常量字符串,不能改变
那么“ char* pstr = “hello girl.”; ” 这句代码的意思是把一个字符串放到 pstr 指针变量里了吗?
其实不是,上述代码的本质是把字符串 “hello girl.” 的首字符的地址放到了 pstr 中,如图:

即把首字符 ‘ h ’ 的地址存放在指针变量 pstr 中。
我们要知道,打印一个字符串只需要提供字符串的首元素地址即可,既然字符指针 pstr 中存放的是字符串的首元素地址,那么我们只要提供pstr(字符串首地址)并以字符串的形式打印,便可以打印字符串 "hello girt.”(与打印字符串数组一样,提供数组名(实质上也为首元素的地址)然后以字符串的形式打印出字符串) 。
如:

为了更好的理解字符指针和常量字符串,有如下代码:

#include <stdio.h>
int main()
{
	char str1[] = "hello girl.";
	char str2[] = "hello girl.";
	char *str3 = "hello girl.";
	char *str4 = "hello girl.";
	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;
}


原因:
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
看图理解:

2.指针数组

通过学习,我们知道数组也有分类,例如
int arr1[10] 为整形数组,用于存放整数,其中每个元素为 int 型
char arr2[10] 为字符数组,用于存放字符,其中每个元素为 char 型
除了如这类数组外,还有指针数组,具体例如:

int* arr1 [ 10 ] : //整形指针的数组,即数组中的每个元素为整型指针
char * arr2 [ 4 ] : //一级字符指针的数组,每个元素为 char* 型的指针

char ** arr3 [ 5 ] : //二级字符指针的数组,每个元素为 char** 型的指针

3.数组指针

在讲解数组指针前,首先介绍一下变量类型
在定义一个变量时,除去数组名,就是变量类型
例如:

int a = 10; // 除去变量名a,变量类型为int
char ch = ‘w’; // 除去变量名ch,变量类型为char
int* p = NULL; // 除去变量名p,变量类型为int*

对于数组:

int arr[10] = { 0 }; // 除去变量名 arr,变量类型为 int [10]
int arr[3][4] = { 0 }; // 除去变量名 arr,变量类型为 int [3][4]
int* arr[10] = { 0 }; // 除去变量名 arr,变量类型为 int* [10]

3.1 数组指针的定义

数组指针是指针?还是数组?
答案是:指针
我们已经熟悉:
整形指针: int * pint ; 能够指向整形数据的指针。
浮点型指针: float * pf ; 能够指向浮点型数据的指针。
那数组指针就是:能够指向数组的指针
具体看代码:

#include<stdio.h>
int main()
{
int arr [10] = { 0 };
int ( *p ) [10] = &arr; // &arr— 取出的为数组的地址,此时的p就为数组指针
return 0;
}

解释:
p 先和 * 结合,说明 p 是一个指针变量,然后指针指向的是一个大小为10个的整型数组,所以 p 是一个指针,指向一个数组,叫数组指针。
这里要注意:
[ ] 的优先级要高于 * 号,所以必须加上()才能确保 p 先和 * 结合。
这里指针 p 的类型就是 int ( * ) [10]

3.2 数组名 VS &数组名

对于一个数组的数组名,它什么时候代表数组首元素的地址?什么时候又代表整个数组的地址?这里作以下说明
数组名代表整个数组只有两种情况:
1. & 数组名,如 &arr 即为取出整个数组的地址
2. sizeof(数组名),如 sizeof(arr) ,为计算整个数组的大小

其他情况下均代表数组首元素地址。

那么 “ 数组名 ” 和 “ &数组名 ” 有什么区别呢?

#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\\n", arr);
printf("%p\\n", &arr);
return 0;
}


我们可以看到 arr 和 &arr 的地址的值一样,即数组首元素的地址和整个数组的地址的值是一样的,那么两者的的本质是否真的一样呢?
有如下代码:

#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\\n", arr);
printf("&arr= %p\\n", &arr);
printf("arr+1 = %p\\n", arr+1);
printf("&arr+1= %p\\n", &arr+1);
return 0;
}


根据上面的代码我们发现,其实 &arr 和 arr ,虽然值是一样的,但是意义应该不一样的。
实际上:
&arr 表示的是数组的地址(是以整个数组为视角),而不是数组首元素的地址。数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40 。

3.3 数组指针的使用

我们知道数组指针指向的是数组,那数组指针中存放的应该是数组的地址,那么对数组指针解引用就相当于得到了数组名
数组指针简单的使用案例就是打印二维数组,具体看如下代码:

#include<stdio.h>
void print(int(*p)[5], int row, int col)//巧妙的以数组指针来作为形参
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", *(*(p + 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} };
	print(arr, 3, 5);
	return 0;
}


说明:将二维数组的数组名传过去(实质上是将二维数组的第一个元素,即将三行中的第一行数组整体传过去),所以用一个数组指针接收,该数组指针指向一个数组,数组5个元素,每个元素类型为 int 型。

printf("%d ", ((p + i) + j));其中对于这行代码的解释如下:

虽然上述代码运用了数组指针,但是打印二维数组并不建议这样操作,相对比较麻烦,对于初学者理解起来有一定困难,通常情况下直接传入形参为一个二维数组去操作会方便很多。

4.数组参数、指针参数

4.1 一维数组传参

 #include <stdio.h>
void test(int arr[])//ok?  数组接收,完全OK
{}
void test(int arr[10])//ok?  数组接收,完全OK
{}
void test(int *arr)//ok?  指针接收,没问题
{}
void test2(int *arr[20])//ok?  数组接收,没问题
{}
void test2(int **arr)//ok?  指针接收,没问题
//(传过来的为数组首元素(一个指针的地址)所以形参为二指针接收没问题)
{}
int main()
{
int arr[10] = {0};//整型数组
int *arr2[20] = {0};//整型指针数组
test(arr);
test2(arr2);
return 0;
}

总结:
对于一维数组,当传入函数的实参为数组名(本质上为数组首元素地址)时,函数接收的形参可为数组,也可为指针。

4.2 二维数组传参

void test(int arr[3][5])//ok? 数组接收,完全OK
{}
void test(int arr[][])//ok? 二维数组的形式不知道,不能传这样的形参
{}
void test(int arr[][5])//ok? 知道二维数组的列数,可以传入
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok? No!
//二维数组的数组名为第一行的地址,不能用一级指针接收
{}
void test(int* arr[5])//ok? No! 参数为指针数组,不能接收
{}
void test(int (*arr)[5])//ok? 可以 参数为数组指针
{}
void test(int **arr)//ok? No! 实参为数组名,形参为二级指针,无法接收
{}
int main()
{
int arr[3][5] = {0};
test(arr);//实质上为传入第一行元素的地址
return 0;
}

总结:
对于二维数组,当传入函数的实参为数组名时,形参可以是同样形式的数组名接收,也可用数组指针接收。

4.3 一级指针传参

我们知道,当传入的参数为一级指针时,我们可以用一级指针的形参对其进行接收,那么当函数形参为一级指针的时候,我们可以传入什么样的参数呢?

#include<stdio.h>
void test(int* p)
{}
int main()
{
	int a = 10;
	test(&a); // 可以传入变量的地址
	int* p = &a;
	test(p); // 可以传入一级指针
	int arr[10] = { 0 };
	test(arr); // 可以传入一维数组名
	//...
	return 0;
}

总结:形参为一级指针,那么实参本质上也得为一级指针
所以,实参可为:变量的地址,一级指针,一维数组名

4.4 二级指针传参

#include<stdio.h>
void test(int** p)
{}
int main()
{
	int a = 10;
	int* pa = &a;
	test(&pa);//可以传入一级指针的地址
	int** paa = &pa;
	test(paa);//可以传入二级指针
	int* arr[10];
	test(arr);//可以传入一级指针数组的数组名
	//...
	return 0;
}

总结:形参为二级指针,那么传过去的实参实质上得为一级指针的地址
所以,实参可为:一级指针地址,二级指针,一级指针数组的数组名(第一行的地址)

以上是关于玩透指针系列—— 渐入佳境,指针还能这么用!的主要内容,如果未能解决你的问题,请参考以下文章

QT调试时怎么查看某个指针指向的一片内存区域的信息

为什么delete指针后指针设为null

怎么用指针?

怎么用指针?

怎么用指针?

怎么用指针?