指针进阶详解(上)

前言

指针是检验学者对C语言掌握好与坏的重要标准之一,在指针初阶中,我们以及详细介绍了以下内容:

  1. 指针是一个变量,用来存放地址,地址唯一标识一块空间。
  2. 指针的大小固定为4/8个字节(32位平台/64位平台)
  3. 指针是有类型,指针的类型决定了指针±整数的步长,指针解引用时候的权限。
  4. 指针的运算。

接下来,我们将继续探讨指针的高级主题。

1. 字符指针

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

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

还有一种使用方法:

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

  • 代码const char* pstr = "hello world"特别容易让初学者以为把字符串 hello world放到字符指针pstr里,但本质上是把字符串 hello world的首字符的地址放到了pstr中。

1.1 相关面试题

题目:

int main()
{
    
    
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char *str3 = "hello world";
	const char *str4 = "hello world";

	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 atr4 are not same\n");
	return 0;
}

运行结果:
在这里插入图片描述
代码解读:

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

2. 指针数组

指针初阶中我们已经介绍了指针数组,指针数组是一个存放指针的数组。
在这里我们在简单复习一下,下面数组是什么意思?

int* p1[10];  //整型指针的数组
char *arr2[4];//一维字符指针的数组
char** arr3[5]; //二级字符指针的数组

3. 数组指针

3.1 数组指针的定义

首先说明数组指针是指针。
我们已经熟悉:
整型指针:int * pint; 能够指向整型数据的指针。
浮点数指针:float * pf; 能够指向浮点型数据的指针。
那么数组指针应该是:能过指向数组的指针。

下面代码那个是数组指针?

int* p1[10];
int(*p2)[10];
  • 要明白上面两个类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.(字符串优先级博客链接)

解读:

int* p1[10];
//解释:从p1开始,由于[]的优先级高于*,p1先后[]结合,所以p1是一个数组。
//		然后在与*结合,说明数组中的元素是指针类型;在和int结合说明指针所指向的内容的类型是指针类型
//		所以p1是由一个返回整型数据的指针所组成的数组(p1为指针数组)

int(*p2)[10];
//解释:由于()的优先级高于[]。所以从p2开始,先和*结合说明p2是一个指针。
//		在和[]结合,说明指针所指向的内容是一个数组;在和int结合,说明数组中的每一个元素是整型类型
//		所以p2是指向一个由整型数据组成的数组的指针。(p2是一个数组指针)

3.2 &数组名VS数组名

对于下面的数组:

int arr[10];

arr和&arr分别是什啥?
我们知道arr是数组名,数组名表示首元素地址。那&arr数组名到底是什么呢?
我们先来看下面几段代码:

代码一:

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

运行结果:
在这里插入图片描述
在上面这段代码中,数组名和&数组名打印的地址是一样的。
难道两个真是一样的吗?
我们再来看下面这段代码:

代码一:

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 表示的是数组的地址,而不是数组首元素的地址。
本例中 &arr 的类型是:int (*)[10],是一种数组指针类型。
数组的指针+1,跳过整个数组的大小,所以 &arr 相对于 arr 的差值为40。

3.3 数组指针的使用

介绍完数组指针,那数组指针如何使用呢?
既然数组指针指向的是数组,那数组指针中存放的应该就是数组的地址。

一个数组指针的使用:

//void print_arr(int arr[3][5], int row, int col)
void print_arr(int (*arr)[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");
	}
}

int main()
{
    
    
	int arr[3][5] = {
    
     {
    
    1,2,3,4,5},{
    
    6,7,8,9,10},{
    
    11,12,13} };
	print_arr(arr, 3, 5);
	//数组名arr,表示首元素地址
	//但二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以使用一维数组来接受
	return 0;
}

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

3.4 数组和数组指针类型实例解读

学习了数组和数组指针,接下来我们来看看下面这些代码的意思:

int arr[5];
//解读:arr先和[]结合,说明arr是一个数组;在和int结合,说明数组中的元素是int类型。
//	   所以arr是由整型数据组成的数组
int *parr1[10];
//解读:parr1先和[]结合,说明pass1是一个数组;在和*结合,说明数组的元素是指针类型;最后和int结合,说明指针指向的内容的类型是整型。
//	   所以pass1是有一个返回整型数据的指针所组成的数组。
int (*parr2)[10];
//解读:parr2先和*结合,说明parr2是一个指针;在和[]结合,说明指针所指向的内容是一个数组;最后和int结合,说明数组中的元素是整型类型。
//	   所以parr2是一个指向由整型数据组成的数组的指针。
int (*parr3[10])[5];
//解读:parrr3先和先和[]结合,说明parr3是一个数组;在和*结合,说明数组的元素是指针类型;在和[]结合,说明指针指向的是一个数组;最后和int结合说明,数组中的元素类型是整型。
//	   所以parr3是一个由指向整型数组的指针组成的数组。

其他各种复杂数据类型和上面一样,根据操作符优先级一步步分析即可得到答案!!

4. 数组参数和指针参数

在写代码时,难免要把数组或指针传个函数,那么函数的参数该如何设计呢?

4.1 一维数组传参

void test(int arr[])//ok 形参写成一维数组形式,可以不用指定大小
{
    
    }
void test(int arr[10])//ok
{
    
    }
void test(int *arr)//ok
{
    
    }
void test2(int *arr2[10])//ok
{
    
    }
void test2(int** arr)//ok arr2的地址时,传的是首元素地址。而首元素的类型为int *也是一个地址。
{
    
    }                  //所以arr2传的是一个二级指针

int main()
{
    
    
	int arr[10] = {
    
     0 };
	int* arr2[10] = {
    
     0 };

	test(arr);
	test2(arr2);
	return 0;
}
  • 一维数组传参,形参部分可以是数组,也可以是指针。
  • 传一维数组地址其实本质上是传的是指针。虽然语法上可以写成数组形式,但这仅是C语言为方便初学者理解而设计的,俗称挂羊头卖狗肉,并不会真正带内存中开辟一块空间来存放数组,本质上还是一个指针。所以参数写成数组形式时,[ ]中不仅可以不说明大小,即使给1000也没关系。编译器在编译参数时,会把它识别为一个指针,而非数组。

4.2 二位数组传参

//参数为二维数组时,形参部分,行可以省略,列不行
void test(int arr[3][5])//ok
{
    
    }
void test(int arr[][])//error 
{
    
    }
void test(int arr[][5])//ok
{
    
    }

//二维数组传参,参数为指针时,指针为数组指针,类型为int (*)[5]
void test(int *arr)//error  
{
    
    }
void test(int *arr[5])//error
{
    
    }
void test(int (*arr)[5])//ok
{
    
    }

int main()
{
    
    
	int arr[3][5] = {
    
     0 };
	test(arr);
	return 0;
}
  • 二维数组传参,形参部分可以是数组,也可以是指针。
  • 二维数组传参时,函数参数设计只能省略第一个[ ]中的数字。因为对于一个二维数组来说,数据在内存中时连续存放的,所以可以不知道有多少行,但必须知道每=一行有多少个元素。

4.3 一级指针传参

void print(int* p, int sz)
{
    
    
	for (int i = 0; i < sz; i++)
	{
    
    
		printf("%d ", *(p + i));
	}
}

int main()
{
    
    
	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(p, sz);//一级指针p,传给函数
	return 0;
}
  • 一级指针传参时,参数只能是指针形式。

4.4 二级指针传参

void tesy(char **p)
{
    
    }

int main()
{
    
    
	char n = 10;
	char* p = &n;
	char** pp = &p;

	test(pp);
	test(&p);
	return 0;
}
  • 二级指针传参时,参数只能是指针形式。

5.结尾

本篇文章到此就结束了。创作不易,如果对你有帮助记得点赞加收藏哦!感谢您的支持!!

猜你喜欢

转载自blog.csdn.net/Zhenyu_Coder/article/details/131693941