C语言过程化记录-5

本文重点讲指针的进阶前半部分,主要介绍了指针数组和数组指针。

首先复习一些指针的基础知识点:

  1. 指针是变量,用来存放地址。是变量就一定有存储大小,指针的大小是4字节(32位平台)或8字节(64位平台)。
  2. 指针分为了int*,char*等类型。这些类型决定了指针+-某一整数时的步长(比如让int* p进行p+1就是+1个int的步长(4字节)),以及指针解引用操作时的权限。

接下来开始正式的指针进阶学习。

1. 字符指针

顾名思义,指向字符的指针,用char*来表示。例如char* p1 = "A",那么指针p1指向的就是字符常量A的地址。

在进阶学习中,我们要知道的是当有字符指针char* p2 = "Apple",此时的字符指针指向的是首字符“A”的地址,而不是字符串“Apple”放到p2里了

掌握以上知识点后,我们来分析下面这道例题:

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

题目要求判断最终打印结果是什么。首先看代码开头定义的四个变量,

  • str1和str2都是字符数组,在各自开辟的地址里存放了字符串“hello bit. ”,而后面if 语句的“str1 == str2 ”就是对于两个数组存放的首地址是否一样进行判断;两个数组开辟的存储位置不一样,明显首字母位置str1不等于str2的。
  • str3和str4都是字符指针(前面加const是为了保证指针指向的内容后面不会改变,对于const*和*const的补充复习详见文末),它们指向了同一串字符串常量“hello bit.”。C/C++会将字符串常量存储到单独的内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str3 == str4

2. 指针数组和数组指针

指针数组是用来存放指针的数组,比如int* a[10], char** b[20];

数组指针是指向一个数组的指针,比如int (*p)[10];

接下来我们再明确一下数组指针的表达方式。int* p[10] 和int (*p)[10],前者是指针数组,后者却是数组指针。仔细观察后者写法,由于" [ ] "的优先级比“ * ”高,所以必须将*p先用括号括起来变成指针,再和[10]结合,变成指向有10个int元素的数组的指针,指针类型为int(*)[10]——指向有十个元素的整型数组的指针(否则*会先和int结合,变成整形指针数组)。 

(1)数组名a不被看成首元素地址的情况

虽然老生常谈,不过后期这个知识点也很重要。情况一共两种:

  1. 使用sizeof(一个数组名)的时候,数组名表示的是整个数组,sizeof测量的也是整个数组的内存长度。
  2. &数组名,这时候取得的地址是整个数组的地址,而不是首元素地址。虽然两个地址是一样的,但是意义完全不同,比如:对于int arr[10],&arr[1]+1,跨过的是1个int的长度;&arr+1,跨过的是10个int的长度)

(2)数组指针的使用

数组指针指向一个一维数组时,可以写成代码如下:

	char a[5] = "hello";
	char(*p)[5] = &a;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%c", *(*p+i));
		//*p先拿到了数组的地址,*p+i实现遍历每个数组元素的地址,*(*p+i)解引用
		//和前面所说的*(p+1)--步长为1个数组的大小,不一样
	}
	return 0;

 但其实在一维数组的使用中,我们不常使用数组指针。就像上面这种情况,使用一个一维指针*p进行遍历会更加方便易懂。数组指针更多的是应用在二维及以上数组之中,接收从其他函数传来的数组首地址。如下代码:

void print(char(*p)[5], int row, int col)
//这里的[5]不能省略
//用数组指针p来接受二维数组,(*p)[5]将会接收二维数组的第一行一维数组的地址
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%c", *(*(p + i) + j));
			//*(p+i)得到第i行形成的一维数组的地址,*(p+i)+j 得到第i行第j列元素的地址
            //*p+i是得到数组的首地址再加i,每次i++会跨过一个元素的步长
            //*(p+i)是先加i再解引用得到对应的数组地址,每次i++会跨过一个数组的步长
		}
		printf("\n");
	}
}
int main()
{
	char arr[3][5] = { "hello","abcde","thank" };
	print(arr, 3, 5);
	return 0;
}

由抽象过渡到具体实践,数组指针也可以应用在“给名字排序”等等的实验之中。例如下面这个:

//要求:给20个英文名字排序
void my_cmp(char(*np)[10])
{
	int i = 0;
	int j = 0;
	char tmp[20] = { 0 };
	for (i = 0; i < 20; i++)//冒泡法实现姓名排序
	{
		for (j = 0; j < (19 - i); j++)
		{
			if (strcmp(*(np + j), *(np + j + 1)) == 1)
			{
				strcpy(tmp, *(np + j));
				strcpy(*(np + j), *(np + j + 1));
				strcpy(*(np + j + 1), tmp);
			}
		}
	}
}
int main()
{
	int i = 0;
	char name[20][10];
	char(*np)[10] = &name;
    //数组指针指向name第一行数组的地址
	printf("请输入20个英文名字:\n");
	for (i = 0; i < 20; i++)
	{
		scanf("%s",name[i]);
        //这里不能写成scanf("%s",*np++)
        //因为np作为指针,它指向的是存储在内存某地址中的数值
        //所以可以把*np看作常量,常量是不能进行*np++或者(*np)++的
	}
	my_cmp(np);//括号里换成name也可以
	printf("//排序后的结果为//\n");
	for (i = 0; i < 20; i++)
	{
		printf("%s	",name[i]);
	}
	return 0;
}

(2)指针数组的使用

指针数组,使用在一维数组情况上的情况如下: 

    int a = 11;
	int b = 22;
	int c = 33;
	int* pa[3] = { &a,&b,&c };
	//要将指针数组里每个元素都分配上地址
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d  ", *pa[i]);//*(pa[i])
	}
	return 0;

和数组指针类似,指针数组在一维数组的使用上总会显得比较繁琐,它在模拟二维数组等应用上面才展现出了真正效果:

	char* p[3] = { "really","okay","I'm fine" };
	int i = 0, j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; *(p[i] + j )!= '\0'; j++)
		{
			printf("%c", *(p[i] + j));
			//p[i]是第i个字符数组的首元素地址,
			//p[i]+j就是第i个数组里第j个元素的地址
		}
		printf("\n");
	}
	//该指针数组实现了将二维数组降维,将其分成3个一维数组表示出来
    //从这个实验还可以看出,指针数组相比较二维数组还可以节省内存空间
	return 0;

总结(源自百度百科):指针数组的元素个数和二维数组的行数相同;数组指针的大小和二维数组整体大小相同,数组指针的元素个数和二维数组的列数相同。

3. 数组参数,指针参数

(1)数组部分

传参这个部分在没有B站听网课之前困扰了我很久...所以基础知识就不能一知半解地放掉。首先是对于一维数组传参的几种情况进行分析

void test(int arr[])//1.1
{}
void test(int arr[10])//1.2
{}
//一维数组传参时,函数形参可以省略数组个数,即arr[]或者arr[10]皆可
//因为形参本身就不会创建一个10个元素的数组,形参接收到的只会是首元素地址
void test(int* arr)//1.3
{}
//使用一个一级指针正好就能去接收实参传过来的首元素地址

void test2(int* arr[])//2.1
{}
//同理,写成int* arr[10]亦可
void test(int** arr)//2.2
{}
//实参是一级指针,传过去首地址后要用二级指针接收地址

int main()
{
	int arr[10] = { 0 };//1
	int* arr2[20] = { 0 };//2
	test(arr);
	test2(arr2);
	return 0;
}

以此类推,二维数组传参的思路和一维数组的情况差不多,不同之处在于二维数组的形参不能省略列数。比如实参为int a[2][3],那么接收的形参就不能将列数省略:int a[ ][3]。而当我们理解了指针数组和数组指针后,就可以进一步研究用指针参数接受二维数组的情况: 

void test(int *arr)
{}
//上式形参无效
//test(arr),实参传过去的是首元素地址,即第一行一维数组的数组地址
//int*只能接受整形地址,不匹配
void test(int* arr[5])
{}
//上式形参无效
//int* arr[5]是一个指针数组,其中每个元素要接受的也都是整型地址
//而实参传过去的首数组地址既不满足有5个元素地址,也不是整型。不匹配
void test(int (*arr)[5])
{}
//上式正确
//int (*arr)[5]表示数组指针,接受的是有5个元素的数组类型
void test(int **arr)
{}
//上式形参无效
//传送过去的数组地址,不能用二级整形指针接收

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

(2)指针部分

如果参数是一级指针*p,那么它可以接收的实参类型有一个变量的地址(eg. int a;  传参时--&a),或者一个一级指针(eg. int *p=&a;  传参时--p)。

同理,如果参数是二级指针**p,那么它可以接收的实参类型有一个一级指针的地址,或者一个二级指针。(如果是一个一级指针数组,传数组首地址过去也是可以的)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值