C — 指针与数组

一、概述

数组是由数据结构相同的一系列元素所组成。需要使用数组时,通过声明数组告诉编译器数组中内含多少元素和这些元素的类型。

数组的声明: 在变量后面添加一个方括号,如 people[100]
数组的访问: 如果要访问数组中的元素,可以使用数组的下标来访问,如访问数组的第2个元素 perple[1]


二、数组的特性

2.1 数组的初始化

数组的初始化方式有如下4种:

int main() {
	//初始化方式1:指定数组大小,同时初始化数组内每一个元素。
	int powers1[5] = {1, 2, 3, 4, 5};
	
	//初始化方式2:不指定数组大小,编译器会根据元素的个数来初始化。
	int powers2[] = {1, 2, 3, 4, 5};
	
	//初始化方式3,被const修饰的数组内元素不能被修改
	const int powers3[] = {1, 2, 3, 4, 5}; 
	
	//初始化方式4(指定初始化器)
	int powers4[5] = {1};  //除了下标为0的元素被赋值为1,其他4个元素都被初始化为0
	int powers5[] = {[1] = 2};  //除了下标为1的元素被赋值为2,其他4个元素都被初始化为0
}

2.2 数组的赋值

给数组内元素赋值,可以通过数组下标来实现。

#define SIZE 5

int main() {
	int count;
	int arr[SIZE]; 
	for (count = 0; count < SIZE; count++) {
		arr[count] = count * 2; //通过下标给数组赋值
	}
}

2.3 数组边界

访问数组时,要防止数组下标越界的问题。即数组下标不能小于0,也不能大于数组的大小。因为编译器通常无法检测出这种错误。

假设有个数组的大小为 size,则数组下标的区间在:0<= index < size

2.4 指定数组的大小

int main() {
	//方式1:声明数组时,指定数组大小。
	int powers[5]; //数组还没有初始化,所以访问数组时访问到的可能是垃圾数据。
	//方式2:声明数组时,指定数组大小
	int powers[sizeof(int) + 1]; 
	//方式3:变长数组。C99之前不允许
	int powers[n]; 
}

2.5 二维数组

如下展示了二维数组的声明、初始化、赋值、取值等操作。

int main() {
	// 二维数据的定义:我们可以使用行/列的概念来定义二维数组。第一个[]代表行数,第二个[]代表列数。
	int arrs[3][4];
	
	// const 修饰的二维数组,不可修改二维数组内的元素值。
	const int arrs[3][4];
	
	// 二维数组的行支持动态指定大小,列数在声明时必须指定,这一点可以从内存分配的特点上来理解。
	const int arrs[n][4];	

	// 二维数组的定义及初始化,带有const时,数组内元素不可变
	const int arrs[3][4] = {
		{1, 2, 3, 4},
		{2, 4, 6, 8},
		{3, 6, 9, 12}
	};
	
	// 将二维数组第2行第3个元素设置为99
	arrs[1][2] = 99;
	// 访问二维数组第2行第3个元素
	int value = arrs[1][2];
	printf("%d", value); //结果:6
}

三、指针和数组

除了使用数组变量来访问数组外,我们还可以通过指针来访问数组内的元素。指针提供了一种以符号形式使用地址的方法。

3.1 一维数组与指针

数组与地址是如何关联的呢?

数组名是数组首元素的地址(即 arr == &arr[0],两者都是常量)。

通过指针如何访问指定位置元素的地址,如访问 arr 中第2个元素的地址?

通过数组获取元素地址:&arr[1]
通过指针获取元素地址:pi + 1 或者 pi++ (pi 是指向arr的指针,pi++指向当前pi内存地址所在元素的下个元素,同时会导致pi指向的内存地址也发生改变)

通过指针如何访问指定位置的元素,如访问 arr 中第2个元素的值?

通过数组获取元素值:arr[1]
通过指针获取元素值:*(pi + 1) 间接运算符(*)的优先级高于加号(+)。

指针与数组的不同点:

arr 是数组首元素的地址,是一个常量,不能通过 arr++ 来获取下一个元素的内存地址。
指针是一个变量,可以使用 pi++ 来指向下一个元素所在的内存地址。

示例:

int main(void) {
	// 声明并初始化一个长度为5的数组。
	int arr[] = {1, 2, 3, 4, 5};
	int isEqual = arr == &arr[0]; // 数组名是数组首元素的地址,因此这个等式成立。
	printf("地址相同返回1:%d\n", isEqual); 
	
	int * pi; // 声明一个int类型的指针
	pi = arr; // 将arr数组指向指针pi,然后就可以通过指针pi来访问数组了。
	//由于指针pi指向数组的首元素,因此直接通过 *pi取值,取到的是数组中第一个元素。
	printf("*pi = %d\n", *pi); 
	printf("************\n");
	for (int i=0; i<5; i++) {
		printf("arr[%d] = %d\n", i, *(pi + i));
	}
}

输出结果如下:

地址相同返回1:1
pi = 1
*
**********
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5

3.2 二维数组与指针

前面我们讲过了二维数组的声明及初始化,也讲过了一维数组与指针之间的关系,下面我们来深入了解一下二维数组与指针之间的关系。

int main() {
	const int arrs[3][4] = {
		{1, 2, 3, 4},
		{2, 4, 6, 8},
		{3, 6, 9, 12}
	};
    int (*pi)[4]; //声明指向二维数组的指针
    pi = arrs;
	
	// 二维数组名arrs指向的地址等于二维数组第一个元素arrs[0]所在的地址。
	printf("arrs地址 = %p\n", arrs);
	printf("arrs[0]地址 = %p\n", &arrs[0]);
	printf("===========\n");
	
	// 元素地址移动指定单位大小
	printf("&arrs 地址 = %p\n", &arrs);  //&arrs取到的是整个数组起始地址
    printf("(&arrs+1) 地址 = %p\n", &arrs + 1); //(&arrs + 1) 移动了整个二维数组字节大小的内存地址
	// 获取二维数组里的第2行元素的地址,即 &arrs[1]
    printf("arrs[1] 地址 = %p\n", &arrs[1]);
    printf("(arrs + 1) 地址 = %p\n", arrs + 1); //数组名+1表示行数加1
    printf("(pi + 1) 地址 = %p\n", arrs + 1);
    printf("===========\n");
    
	// 访问第m行元素里的第n个元素,如:arrs[1][1].
	printf("arrs[1][1]地址 = %p\n", &arrs[1][1]); //数组访问方式获取元素地址
	printf("(*(arrs + 1) + 1) = %p\n", (*(arrs + 1) + 1)); //使用数组名获取对应元素的地址
	printf("(*(pi + 1) + 1) = %p\n", (*(pi + 1) + 1)); //使用指针获取对应位置的地址
	printf("===========\n");
	
	// 访问指定位置的元素值,如:arrs[1][1].
	printf("arrs[1][1]的值 = %d\n", arrs[1][1]); //数组访问方式获取元素值
    printf("*(*(arrs + 1) + 1) = %d\n", *(*(arrs + 1) + 1)); //使用数组名获取对应元素的地址
    printf("*(*(pi + 1) + 1) = %d\n", *(*(pi + 1) + 1)); //使用指针获取对应位置的地址
    printf("===========\n");
      
}

输出结果如下:

结论1:
arrs 地址 = 0x7ffeefbff4a0
arrs[0] 地址 = 0x7ffeefbff4a0
===========
结论2:
&arrs 地址 = 0x7ffeefbff4a0
(&arrs+1) 地址 = 0x7ffeefbff4d0 // 对比0x7ffeefbff4a0 地址值,移动了 12 * 4 个字节(12个int)。
arrs[1] 地址 = 0x7ffeefbff4b0
(arrs + 1) 地址 = 0x7ffeefbff4b0
(pi + 1) 地址 = 0x7ffeefbff4b0
===========
结论3:
arrs[1][1]地址 = 0x7ffeefbff4b4
((arrs + 1) + 1) = 0x7ffeefbff4b4
(
(pi + 1) + 1) = 0x7ffeefbff4b4
===========
结论4:
arrs[1][1]的值 = 4
*(*(arrs + 1) + 1) = 4
*(*(pi + 1) + 1) = 4
===========

小结:

  1. 二维数组名指向的地址arrs与二维数组第一个元素的地址arrs[0]相同,与对二维数组名取地址值&arrs后获得的值相等 (即 arrs == arrs[0] == &arrs)。
  2. &arrs + 1 得到的地址值基于 &arrs 地址移动整个二维数组的内存大小。
  3. arrs + 1 得到的地址值基于 arrs[0] 地址移动二维数组1行的内存大小 (即 4 * 4 个字节)。
  4. *(arrs + 1) 即为获取二维数组第二行元素,获取到的是一个数组 {2, 4, 6, 8},所以可以把 *(arrs + 1) 看做是指向数组 {2, 4, 6, 8} 的首元素地址,因此 (*(arrs + 1) + 1) 即为取二维数组第二行第2个元素的地址,*(*(arrs + 1) + 1) 即为取二维数组第二行第2个元素的值。
  5. (*(pi + 1) + 1)(*(arrs + 1) + 1) 类似,*(*(arrs + 1) + 1)*(*(pi + 1) + 1) 类似。

四、函数与数组

在实际项目中,也常常将数组作为参数传递到函数中。而上面我们讲到,可以使用指针来操作数组,因此函数的入参也就存在两种表示方法。

数组:使用数组作为函数参数:

//函数原型:使用数组
int sum(const int arrNew[], int n);

int main(void) {
	int arr[] = {1, 2, 3, 4, 5};
	int total = sum(arr, 5);
	printf("求1到5的总和:%d\n", total);
}

int sum(const int arrNew[], int n) {
	int total = 0;
	for (int i=0; i<n; i++) {
		total += arrNew[i];
	}
	return total;
}

指针:使用指针作为函数参数:

//函数原型:使用指针
int sum(const int * arrNew, int n);

int main(void) {
	int arr[] = {1, 2, 3, 4, 5};
	int total = sum(arr, 5);
	printf("求1到5的总和:%d\n", total);
}

// 细节:添加const修饰指针,表示不可以用指针来修改数组内的元素。
int sum(const int * arrNew, int n) {
	int total = 0;
	for (int i=0; i<n; i++) {
		total += *(arrNew + i);
		//total += *(arrNew++); //
	}
	return total;
}

小结:

参数类型优点缺点
数组函数内对数组进行操作不会影响到外面的数组会拷贝一份数组到方法栈中,增加内存的消耗
指针拷贝的是指向数组arr的指针地址,内存消耗小函数内对参数 arrNew 进行操作时,会影响到外面的数组
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值