C语言数组的深入解读

总结了一些常见问题,请根据目录按需浏览。

指定初始化器(Designated initializer)

上一段代码,数组的指定初始化器这样用,但是要注意 C99 之后才允许这样哦,观察其运行结果。

// designatedInitializer.c
#include <stdio.h>
int main()
{
	int arr[] = {0, 1, [4]= 5, 6, 7, [0] = 10};
	int i;
	for (i = 0; i < sizeof(arr)/sizeof(arr[0]) ; i ++)
	{
		printf("arr[%2d] = %2d\n",i,arr[i]);
	}
	return 0;
}

在这里插入图片描述

结果分析

  1. 对数组中的部分值初始化赋值,未进行赋值的其余项将初始化为0;
  2. 指定初始化后面的值将被用于初始化指定元素后面的元素;
  3. 再次初始化指定的元素, 那么最后的初始化将会取代之前的初始化;
  4. 未指定数组大小,编译器会把数组的大小设置为足够装得下初始化的值。

错误的数组初始化

这样是不被允许的,

int arr2[7];
arr2 = arr;

这样也不可以,

int arr2[7];
arr2 = {0,1,2,3,4,5,6};

但是这样可以!

int aCount = 20;
int arr[aCount];
arr[0] = 0;
arr[1] = 1;
…

下标越界问题分析

程序如下。

// arr_bounds.c
#include <stdio.h>
int main()
{
	int value1=100;
	int arr[5];
	int value2=200;
	int i;
	for (i = -1; i < 7 ; i ++)
	{
		arr[i] = i;
		printf("arr[%d] = %2d , Address = %d \n",i,arr[i],&arr[i]);
	}
	
	printf("----------------------------------\n");
	printf("arr[%d] = %2d , Address = %d \n",-1,arr[-1],&arr[-1]);	
	printf("----------------------------------\n");
	printf("arr[%d] = %2d , Address = %d \n",5,arr[5],&arr[5]);
	printf("arr[%d] = %2d , Address = %d \n",6,arr[6],&arr[6]);
	printf("----------------------------------\n");
	printf("value1 = %2d , Address = %d \n",value1,&value1);
	printf("value2 = %2d , Address = %d \n",value2,&value2);
	return 0;
}	

在代码中定义了一个大小为5的整型数组,但是给赋值的时候故意上下越界访问下标为-1和5,6的元素,结果如下图。

在这里插入图片描述

结果分析

可见无论是向上或是向下越界都不影响对数组的访问(修改其值),但是!却严重影响了其他人(value)的状态,value1和value2的值都被改变了!这不是我们想要的结果。

由于定义数组时分配的时连续的内存空间,value1和value2两个值分别在连续空间的两侧不远处(可能紧挨着也可能错些位,猜测编译器的缘故)。

因为在程序运行之前,数组的下标值可能尚未确定。为安全起见,编译器必须在运行时添加额外代码检查数组的每个下标值,这会降低程序的运行速度。

C语言本着信任程序员的原则,不检查边界,这样的程序运行速度更快。但并不是所有的程序员都能做到这一点,所以就出现了下标越界的问题。

const关键字-对数组的保护

函数按值传递数组就必须分配足够的空间来储存原数组的副本,再把数据拷贝至新数组中。 如果把地址传递给函数, 直接处理原数组则效率要高。

1.如果不想修改数组中的数据且防止这种意外的发生,可以在函数原型和函数定义中声明形式参数时应使用关键字const 并不是要求原数组是常量, 而是该函数在处理数组时将其视为常量不可更改

int func(const int arr[], int n);

2.把const或非const数据的地址初始化为指向const的指针或为其赋值。

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
const double * pc = rates; // 有效
pc = locked; //有效
pc = &rates[3]; //有效

3.使用非const标识符修改const数据,可行但不应当这样做!

// const_in_arr.c
#include <stdio.h>
/*数组每个值 + 1*/
void add1(double *arr, int _num)
{
	int i;
	for (i=0; i<_num; i++)
	{
		*arr = *arr + 1;
		arr ++;
	}
}
int main()
{
	int i = 0;
	double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
	const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
	double * pnc = rates; // 有效
	//pnc = &rates[3]; // 有效
	pnc = locked; // 仍可用
	pnc[2] = 4.4;
	add1(pnc,4);
	for (i=0; i<4; i++)
		printf("locked[%d] = %lf\n",i,locked[i]);	
	return 0;
}	

发现编译器没有报错,但是给出了一条警告
在这里插入图片描述

结果如下图,0.0725 -> 4.4 -> 5.4,看样子是都修改成功了。很显然我们应该避免这样危险的操作。

在这里插入图片描述


Tips 在C++中const使用会比C严格,同样的代码在C中是WARNING而在C++中编译会ERROR
在这里插入图片描述


4.声明并初始化一个不允许指向别处的指针

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
double * const pc = rates; // pc指向数组的开始
pc = &rates[2]; // 不允许, 因为该指针不能指向别处
*pc = 92.99; // 没问题 -- 更改rates[0]的值

5.创建既不能更改它所指向的地址也不能修改指向地址上的值的指针

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double * const pc = rates;
pc = &rates[2]; //不允许
*pc = 92.99; //不允许

多维数组

代码,

// multiarr.c
#include <stdio.h>
int main()
{
	// int multiarr0[][] = { {1,2},{3,4},{5,6},{7,8} }; 不合法 
	// int multiarr0[4][] = { {1,2},{3,4},{5,6},{7,8} }; 不合法
	// int multiarr0[][2] = { {1,2},{3,4},{5,6},{7,8} }; 合法! 
	int multiarr[4][2] = { {1,2},{3,4},{5,6},{7,8} };
							
	printf("-------------------------------------------------------------------\n");
	// printf("数组名 multiarr 是该数组首元素的地址,一个占用两个int大小对象的地址\n");
	printf("multiarr = %d\n",multiarr);  
	// printf("multiarr[0]是一个占用一个int大小对象的地址\n");
	printf("multiarr[0] = %d\n",multiarr[0]);   	
	// printf("*multiarr代表该数组首元素(multiarr[0]) 的值\n");
	printf("*multiarr = %d\n",*multiarr); 
	// printf("zippo[0]是一个int类型值的地址,该值的地址是&multiarr[0][0],所以*multiarr就是&multiarr[0][0]");
	printf("&multiarr[0][0] = %d\n",&multiarr[0][0]);  	
	printf("-------------------------------------------------------------------\n");
	// printf("*(mulutiarr[0])表示储存在multiarr[0][0]上的值\n");
	printf("*(multiarr[0]) = %d\n",*(multiarr[0])); 
	// printf("**multiarr与*&multiarr[0][0]等价,相当于zippo[0][0]\n");
	printf("multiarr[0][0] = %d\n",multiarr[0][0]);
	// printf("multiarr是地址的地址, 必须解引用两次才能获得原始值\n");
	printf("**multiarr = %d\n",**multiarr); 
	printf("*&multiarr[0][0] = %d\n",*&multiarr[0][0]);	
	return 0;
}

详细阅读上面代码中的注释,体会数组地址,数组内容和指针之间的关系。下图辅助理解,zippo是一个二维数组(图自《c primer plus》)。
在这里插入图片描述

指针数组/数组的指针

指向数组的指针

int (* pz)[2]; // pz指向一个内含两个int类型值的数组

包含两个指针的数组

int * pax[2]; // pax是一个内含两个指针元素的数组, 每个元素都指向int的指针

由于[]优先级高,先与pax结合, 所以pax成为一个内含两个元素的数组。

如下写法等价

pz[m][n] == *(*(pz + m) + n)

.
.
.
.
.
.
桃花仙人种桃树,又摘桃花换酒钱_

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值