从0开始学c语言-27-字符指针,指针数组和数组指针

发布于:2022-12-10 ⋅ 阅读:(650) ⋅ 点赞:(0)

本人0基础开始学编程,我能学会的,你也一定可以,学会多少写多少。

下载安装请从官网入手,社区版本即可,这里主要使用的软件是VS2019,图标如下。

 上一篇:从0开始学c语言-26-操作符练习、指针练习、调试作业_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:从0开始学c语言-20-指针与地址、指针类型、野指针、指针运算_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-21-指针和数组、二级指针、指针数组_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

1. 字符指针

2. 指针数组和数组指针

区分

* 和 [ ]  的优先结合顺序

&arr和arr

访问范围

数组的标志便是指针的标志

+1步长和二级指针(&arr)

[ ] 和 * 的联系

总结

实际运用:

数组指针的使用

*p的另外一种理解

二维数组

区分运用的练习

总结:


1. 字符指针

我们常见的字符指针是这样用的。

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

实际上还可以这样用

int main()
{
    const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0; }
本质是把字符串 hello bit. 首字符的地址放到了 pstr 中。

不信你就看这个文章学习小发现-02-char和char*和char[ ]*_阿秋的阿秋不是阿秋的博客-CSDN博客

那么我们看看这道题,练习一下你懂了没有。

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; }

输出结果是

"str3 and str4 are same\n"

虽然str1和str2的字符串内容一样,但是两者的地址不同,是两块独立的空间。

str3和str4指向的常量字符串内容相同,str3和str4都只是保存首字符的地址,所以只开发了一块空间来储存这个字符串,让str3和str4指向同一个地址。

所以要清楚字符指针保存的什么。

2. 指针数组和数组指针

区分

实际上,我们已经学过指针数组。

指针数组是一个存放指针的数组。
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

上面这是存放指针的数组,叫指针数组本质是数组。

int *p1[10]; 
int (*p2)[10];

那么你看看这俩谁是数组指针呢?

很明显,第一个是存放int*指针的指针数组。

第二个才是我们要学的数组指针,本质是指针。

* 和 [ ]  的优先结合顺序

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

也就是说,如果没有把*p用括号括起来,那p首先会和 [ ] 来组合为一个数组,就像下面这样。

int *p1[10]; 
int **p[10];

因为没有把*p括起来,所以这两个都是指针数组,第一个存放int*,第二个存放int**.

&arr和arr

int arr[10]={0};

实际上,如果你打印&arr和arr都会是同一个地址,那么他们两个的意思会一样吗?

不一样!

访问范围

我画图示意一下。                    

 arr和&arr都指向首元素的地址,但是一次访问的范围不同

数组的标志便是指针的标志

我们在监视窗口中监视到的arr类型是int[10],似乎arr应该代表整个数组才对。

但实际上我想说的是,监视窗口中arr数组名存放的是地址,前面我们说过,其实指针就是有指向性的地址,那么我们就可以把数组名变为指针来运用,但是指针会指向谁该如何判断呢

我们从类型来下手,可以看到arr的类型是int[10],我们又说

 指针就是有指向性的地址,数组名存放的是地址,那么我们就可以把数组名变为指针来运用

也就是说,数组的标志便是指针的标志

就像int*指针一样,我们是去掉*来判断int*指针指向谁

同样的道理,我们去掉 [10],这便是数组名arr指向的对象类型,是int。 

又因为数组名保存的是首元素的地址,便指向了第一个int类型的元素。

同样的道理,我们来看&arr

可以看到&arr的类型是int[10]*

我们去掉*,便是&arr指向的对象类型,是int[10]

又因为&arr保存的是首元素的地址,于是便和arr一样,指向了同一个地址,但是arr和&arr指向的范围(对象类型)不同。

现在可以更深刻的理解这个图了把?

+1步长和二级指针(&arr)

+1步长

我们又知道,指针类型会决定指针+1的步长

int*指向int,步长就是int。char*指向char,步长就是char。

arr的类型是int[10] ,&arr是int[10]*

所以arr每次会跳过一个int,&arr则会跳过int[10]

我们又发现arr和&arr指向的范围(对象类型)便是+1的步长。

二级指针(&arr)——这部分属于是我自己的思考,慎读

我们又说数组的标志便是指针的标志,那么实际上

arr的类型是int[10] ——>10个( int*) ——>arr就是(1个int* )——>步长是int*

&arr的类型便是int[10]* ——> (10个 int*)*——>&arr就是(10个 int*)—>步长是10个 int*

因为我们通常认为*才是指针的标志呀,便可以像上面这样转换

所以我们在判断步长的时候,去掉类型的最后一个符号

1·如果是*,那么剩下的就是步长

例如:int[10]* - int[10] -10个int*

2·如果是[ ],那么剩下的加上*

例如:int[10] - int*

所以实际上我们的数组应该是这样才对

也就是说arr这个数组,存放了10个指针,每个指针都有自己的独立空间,于是arr数组名的步长就是存放的一个指针的大小

但那样画不利于理解,下面这个好些,这时候arr是指向int的,arr本身是int*的指针,这便是数组。

 而&arr这个二级指针就会是这样子,存放了10个指针,每个指针都有自己的独立空间,但是&arr相当于一个围墙,把他们圈在了一个共同空间内,于是&arr的步长就会是arr整个数组的大小

 (被同一颜色划线的空间代表&arr

为了更好理解一些,我们画成这样,

所以今后你见到数组的时候会不会更加理解呢?

数组实际上就是同一类型指针集合指向了同一类型的元素,因为数组保存了每个元素的地址,而每个地址在运用的时候会有指向性,有指向性的地址就是指针。所以数组名本身就是指针。

就像我们一开始推导的那样

arr的类型是int[10] ——>10个( int*) ——>arr就是(1个int* )——>步长是int*

&arr的类型便是int[10]* ——> (10个 int*)*—>&arr就是(10个 int*)—>步长是10个 int*

更直接一些

arr的类型是int[10] ——>指向10个int——>arr本身是int*指针

&arr的类型便是int[10]* ——> 指向10个int*——>&arr是int[10]*指针

那么为什么数组要用[ ]来访问呢?

[ ] 和 * 的联系

实际上早已经在下面这篇文章里说过[ ]和*的联系,不过还不够深刻,有了上面的理解,我们能够更加理解他俩的关系。

从0开始学c语言-21-指针和数组、二级指针、指针数组_阿秋的阿秋不是阿秋的博客-CSDN博客

int arr[10]={0,1,2,3,4,5,6,7,8,9};

既然我们认为数组名本身就是指针,那么arr[0],arr[1]之类的又代表什么呢?

我先给上结论

arr[0]=*(arr+0)

arr[1]=*(arr+1)

arr[2]=*(arr+2)

也就是说,arr[ i ] =*(arr+i ) 

注:arr[ i ] 和*(arr+i ) 类型是int , arr+i 的类型是int*。

arr[ i ] 和*(arr+i ) 相当于 arr 这个指针向后挪动 i 步长后访问住户。

总结

判断 数组和指针 指向对象类型

1·数组的标志便是指针的标志

指针去掉*来判断指针指向谁

数组去掉[ ]来判断数组名指向谁

实际上数组名是指针:

arr的类型是int[10] ——>指向10个int——>arr本身是int*指针

&arr的类型便是int[10]* ——> 指向10个int*——>&arr是int[10]*指针

本质上数组就是同一类型指针的集合,指向了同一类型的元素,数组名本身就是指针。

判断步长的两种理解方式:

1——判断+1步长的,arr和&arr指向的范围(对象类型)便是+1的步长。

        arr的类型是int[10] ,指向int,步长int(跳过一个int元素)

        &arr是int[10]*,指向int[10],步长int[10](跳过10个int元素)

2——我们在判断步长的时候,去掉类型的最后一个符号

        1·如果是 *,那么剩下的就是步长

                例如:int[10]* - int[10]

        2·如果是[ ],那么剩下的加上*

                例如:int[10] - int*

步长结论:arr数组名的步长就是本身指针类型的大小,&arr的步长就会是arr整个数组的大小。

补充结论:arr[ i ] 和*(arr+i ) 相当于 arr 这个指针向后挪动 i 步长后访问住户。

实际运用:

我们说int[10]类型是指向了10个int,数组名本身是int*指针

那么当看到指针数组的时候,你还能理解吗?

int *p1[10]; 
int **p[10];

int[10]类型是指向了10个int,数组名本身是int*指针

int *p1[10]当中p1的类型是int*[10],那么类比上面

int*[10]就是指向了10个int*类型的元素,数组名本身是int**指针。

剩下的你会把?不说咯。

现在回到数组指针

数组指针的使用

既然数组指针指向的是数组,那数组指针存放的应该是数组的地址。

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是我们一般很少这样写代码
    return 0; }

*p的另外一种理解

前面我们说数组名实际上就是指针,我们观察一下数组指针的书写方式

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

很像数组的书写方式吧?一定记得把*p括起来!

我们就可以把*p理解为数组名,*p的类型就是int[10],而int[10]又代表指向10个int,数组名本质是int*指针。那么就可以把*p=arr

那p又会是什么类型呢?*p的类型是int[10],去掉*p的*后,p的类型就是int[10]*。

&arr的类型也是int[10]*,那么p=&arr

现在我们监视窗口看看是否符合我们的推论。

 *p=arr 符合!

 数组名本质是int*指针。*p=arr。 符合!

 p=&arr。符合!

那么赋值过程就会是这样的代码

int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr; //类型int[10]*
	//p=&arr 数组地址
	int i = 0;
	for (i=0;i<10;i++)
	{
		printf("%d\n", *((*p) + 1));
		//*p=arr 首元素地址
	}
	return 0;
}

二维数组

 int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};

我是这样看待这个数组的,

arr是二维数组名,它的类型是int[3][5],指向3个 int[5]类型的 一维数组,arr数组名本身是首元素的地址,二维数组的首元素是二维数组的第一行,也就是说二维数组名arr本身是int[5]*类型的数组指针

那么我们在传递arr这个二维数组到某一个函数中去,应该用数组指针进行接收。就像下面这样。

void print_arr2(int (*arr)[5])

或者好理解一些,就是这样。

void print_arr1(int arr[3][5]) //[3]可省略

这里给上一些我学习时候的思考过程

int main()
{
int arr[3][4] = { {1,2},3 ,2 };
	//&arr int[3][4]*

//如何用指针存二维数组
int(*p3)[4] = arr;
	//p3 int[4]* 和arr作为指针的时候类型一样
	//把arr首元素的地址存到p3中,也就是第一行
	//     arr=p3 arr表示第一行地址(相当于指针指向第一行的一维数组
	//    *arr=*p3 代表第一行 类型为int[4]
	//  *arr+1=*p3+1 代表第一行的第二个元素 类型int*
	//   arr+1=p3+1 代表下一行 类型int[4]*
	//*(arr+1)=*(p3+1) 代表下一行 类型int[4]
	//函数传参以这个为基准

int(*p4)[3][4] = &arr; //整个数组
	//	   p4=&arr int[3][4]*
	//    *p4=*(&arr) int[3][4] 代表arr整个数组
	//  *p4+1=*(&arr)+1 下一行 int[4]* 此时的加一把arr当做一维数组
	//   p4+1=&arr+1 加了一整个数组大小的地址 int[3][4]*
	//*(p4+1)=*(&arr+1) 加了一整个数组大小的地址 int[3][4]	
	return 0;
}

还有利用二维数组传参如何赋值的代码

二维数组的数组名是首元素的地址
首元素:第一行
*(p+1)表示某一行的地址
*(*(p+i)+j)就是某一行的某一个元素的地址
void test(int(*p)[4], int a, int b)
{
	//p接收第一行地址的数组指针 int[4]*
	//*p代表第一行数组的地址和元素 int[4]
	int i = 0;
	int j = 0;
	//printf("%p ", p+1);
	// //p+1指向下一行的数组指针    int[4]* 
	//printf("%p ", *p+1);
	// // *p+1代表第一行的下一个元素 int* 只包含一个元素
	//printf("%p ", *(p+1));
	// // *(p+1)下一行的地址和元素 int[4]
	//printf("%p ", *(p+1)+1);
	// // *(p+1)+1下一行的第二个元素 int* 只包含一个元素
	//其他则包含一行的元素

	/*for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ",*(*(p+i)+j));
		}
		printf("\n");
	}*/
}
int main()
{
	int arr[3][4] = { {1,2},3 ,2 };
	test(arr, 3, 4);
	//二维数组传过去的是第一行的地址
	//相当于你传过去了int[4]类型的一维数组
	//所以用一维数组指针接收
	return 0;
}

我觉得这里的思考过程实际上和一维数组的思考过程是相通的,便不想过多解释。

区分运用的练习

int (*parr3[10])[5];

先自己思考好每个的类型,和代表的意思。

int arr[5]; —— 指向int

arr是数组名,类型int[5],指向5个int,数组名本质类型是int*,指向int的指针。

int *parr1[10]; —— 指向int*

parr1是数组名,类型int*[5],指向5个int*,数组名本质类型是int* *,指向int*类型的指针。

int (*parr2)[10]; —— *parr2指向int ,parr2指向int[10]

*parr2是数组名,类型int[10],指向5个int,数组名本质类型是int*,指向int的指针。

parr2是指针变量,类型int[10]*,指向int[10],指向数组的指针。

int (*parr3[10])[5]; —— *parr3[10] 指向int,parr3[10]指向int[5],parr3指向int[5]*

*parr3[10] 是数组名,类型int[5],指向5个int,数组名本质类型是int*,指向int的指针。

在前面学习的时候我们知道,[ ]的结合性高于*,所以*parr3[10]相当于*(parr3[10]),所以

去掉*后的parr3[10] 的类型就是int[5]*,是指向数组的指针。

parr3[10] 把[10]去掉,parr3是指针数组,类型int[5]*[10],指向int[5]*类型,本质是int[5]**指针,指向int[5]*的指针。

总结:

1·在 int 和 [ ] 之间的是数组名,剩下的就是数组名的类型,数组名本质是指针。

2·*p中的*代表这是一个指针变量p的类型是在*p原来类型后加*便是p指针变量的类型。

3·[ ]的结合性高于*,所以 *p[10] = *(p[10]),p[10]中的[10]代表这是一个存放10个同类型元素的数组,p的类型就是在p[10]原本的类型后加[10]就可以了。

本文含有隐藏内容,请 开通VIP 后查看