C语言细节 数组

1.若初始化列表中的项数与数组大小不匹配:

1.不完全初始化(即初始化列表中的元素过少)参见 C语言基础.数组..2 部分

2.若初始化列表中的元素过多,会发出警告
int main(void) {
	int days[5]={1,2,3,4,5,6};
    return 0;
}
//警告:
[Warning] excess elements in array initializer
[Warning] (near initialization for 'days')

2.自动确定数组大小:

若定义数组时省略数组大小,编译器会根据初始化列表中的项数来确定数组的大小

//实例:
#include <stdio.h>

int main(void) {
	int days[]={31,28,31,30,31,30,31,31,30,31};
	int index;
	for (index=0;index<sizeof(days)/sizeof(days[0]);index++) {
		printf("%d,%d\n",index+1,days[index]);
	}
    return 0;
}
//结果:
1,31
2,28
3,31
4,30
5,31
6,30
7,31
8,31
9,30
10,31

3.指定初始化器(Designated Initializer):

C99中新增加了1个特性:指定初始化器.利用该特性可以初始化指定的数组元素
如,可以只初始化最后1个元素(见下)
		int arr[6]={0,0,0,0,0,212};//传统语法
		int arr[6]={[5]=212};//指定初始化器

//语法:
<type> <avar>[<n>]={<x1>=<val1>...};
  //没有被初始化的元素会被设为0或空格
  //参数说明:type/avar/n同普通的初始化
    x1,x2...:指定某个位置的元素
    val1,val2...:将指定位置的元素初始化为该值

//实例1:
#include <stdio.h>
#define MONTH 12
int main(void) {
	int days[MONTH]={31,28,[4]=31,30,31,[1]=29};
	int i;
	for (i=0;i<MONTH;i++) {
		printf("%d,%d\n",i+1,days[i]);
	}
    return 0;
}
//结果:
1,31
2,29//[1]=29跳回来又初始化了days[1],覆盖了原来的值28
3,0
4,0
5,31//某个元素xi使用指定初始化器后
6,30//初始化列表在xi之后的元素从xi对应的位置之后继续初始化
7,31
8,0
9,0
10,0
11,0
12,0

//实例2:
#include <stdio.h>
#define MONTH 12
int main(void) {
	int days[MONTH]={31,28,[4]=31,30,31,[1]=29,4};
	int i;
	for (i=0;i<MONTH;i++) {
		printf("%d,%d\n",i+1,days[i]);
	}
    return 0;
}
//结果:
1,31
2,29
3,4//接着[1]=29之后继续初始化
4,0
5,31
6,30
7,31
8,0
9,0
10,0
11,0
12,0

//实例3:
#include <stdio.h>
int main(void) {
	int days[]={31,[4]=31,30};
	int i;
	for (i=0;i<sizeof(days)/sizeof(days[0]);i++) {
		printf("%d,%d\n",i+1,days[i]);
	}
	printf("%d",sizeof(days)/sizeof(days[0]));
    return 0;
}
//结果:
1,31
2,0
3,0
4,0
5,31
6,30
6//数组的大小会被编译器设置为恰好可以容纳所有初始化列表中的元素

4.指定数组的大小:

在C99之前,只能使用整型常量表达式来指定数组大小,:
  //"整型常量表达式"是指由整型常量构成的表达式
  //整型常量包括:整型明示常量,整数(如5),sizeof()的返回值
    //注意:const指不被视为整型常量(这点与C++不同),不过某些编译器可能允许使用const变量,但这种代码不可移植

//实例:
#define SIZE 5
int main(void) {
    int m=5;
    int arr[SIZE];//可以
    int a1[5];//可以
    int a2[5*2+1];//可以
    int a3[sizeof(int)+1];//可以
    //int a4[-4];//错误:数组大小必须大于0
    //int a5[0];//错误:数组大小必须大于0
    //int a6[2.5];//错误:数组大小必须是整数
    int a7[(int)2.5];//可以:因为已被转换为整型常量
    int a8[m];//C99之前不允许,C99运行,C11将其设为可选的特性
}

//变长数组(Variable-length Array;VLA):见 9 部分
即使用变量指定大小的数组,在C99中增加了该特性,不过C11又将其设为可选的特性
引入VLA的主要目的是让C语言成为更好的数值计算语言
  //如其简化了将FORTRAN现有的数值计算例程库转换为C代码的过程
VLA有一些限制,如声明VLA时不能进行初始化

5.指针表示法和数组表示法:

//指针表示法:
*(<avar>+<i>)

//数组表示法:
<avar>[<i>]

//比较:
1.2种表示法是等价的
2.指针表示法(尤其是与自增运算符结合使用时)更接近机器语言,因此如果使用指针表示法,一些
编译器在编译时能生成效率更高的代码;不过,许多程序员认为代码优化应该留给编译器去做,他们
的主要任务是确保代码正确/逻辑清晰
3.编译器会把数组表示法转换成指针表示法

6.保护数组中的数据:

传递数组通常是通过传递其地址来实现的,以提高效率/减少内存消耗,但这样做可能导致一些问题
如果将数组的地址传递给函数,就可以在函数中修改数组中元素的值,这可能导致误修改
有些时候不需要在函数中修改数组中的元素,这时候就需要1种方法来避免误修改

##############################################################################

1.对形式参数使用const(参考 C语言细节.指针.6 部分):
在K&R C的年代,唯一避免误修改的方法是提高警惕.ANSI C则提供了1种预防手段,即对函数的形
式参数使用const关键字,实例如下:
#include <stdio.h>

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

int main(void) {
	int ar[]={1,2,3,2,4,1,5};
	int a=sum(ar,sizeof(ar)/sizeof(ar[0]));
	//第1个参数为const数据或非const数据均可
	printf("%d\n",a);//结果:18
    return 0;
}

##############################################################################

2.使用了const,无论是通过指针表示法还是数组表示法,都无法修改数组中元素的值
不过,即使使用了const,也可以让指针指向别处,:
  int ar[]={1,2,3,2,4,1,5};
  const int *pd=ar;
  printf("%d\n",*pd);//结果:1
  pd++;
  printf("%d\n",*pd);//结果:2
此外,在上述代码中,还可以直接通过ar修改数组中的元素

##############################################################################

3.关于指针赋值与const:
①把const或非const数据的地址赋给const的指针都是合法的
double rates[2]={3.14,1.41};
const double locked[4]={0.08,0.075,0.0725,0.07};
const double *pc=rates;//合法
pc=locked;//合法
②但只能把非const数据的地址赋给非const的指针
  //否则可以通过指针修改const数据
  //把const数据的地址赋给非const的指针的行为是未定义的,结果取决于编译器
double rates[2]={3.14,1.41};
const double locked[4]={0.08,0.075,0.0725,0.07};
double *pnc=rates;//合法
pnc=locked;//警告:[Warning] assignment discards 'const' qualifier from pointer target type

##############################################################################

4.使用非const标识符修改const数据的行为是未定义的,结果取决于编译器
#include <stdio.h>

void f(int a[]) {
	a[1]=10;
}

int main(void) {
	const int a[3]={1,2,3};
	f(a);
	printf("%d\n",a[1]);//结果:10
    return 0;
}
//警告:
[Warning] passing argument 1 of 'f' discards 'const' qualifier from pointer target type
[Note] expected 'int *' but argument is of type 'const int *'

##############################################################################

5.创建1个不能指向别处的指针:
double rates[5]={1.41,3.14,6.33,2.88,9.12};
double * const pc=rates;
//pc=&rates[2];//非法
*pc=92.99;//合法

double rates[5]={1.41,3.14,6.33,2.88,9.12};
double * const pc;
pc=&rates[2];//非法
//报错:即这么定义的指针变量是只读的,只能在初始化时赋值
[Error] assignment of read-only variable 'pc'
[Error] assignment of read-only location '*pc'

double rates[5]={1.41,3.14,6.33,2.88,9.12};
const double * const pc=rates;
//pc=&rates[2];//不合法
//*pc=92.99;//不合法

7.多维数组与指针:

int zippo[4][2]={
	{1,2},
	{3,4},
	{5,6},
	{7,8}
};
//说明:
1.zippo是元素首元素的地址,&zippo[0]/zippo[0]均相同;zippo[0]也是1个数组,是其首行的地址,&zippo[0][0]相同.但它们的含义不同,
zippo是数组指针,zippo[0]则是2维行指针,&zippo[0][0]则是1维整形指针.不过虽然维度不同,但也可以相互赋值(不建议这么做)
2.指针+1,其值的增加量等于指向的数据类型的大小,因此zippo+1和zippo[0]+1不同,因为zippo指向1个包含2个整数的数组({1,2}),而zippo[0]
指向1个整数(1)
3.*(zippo[0])表示zippo[0][0]的值;*(zippo)表示zippo[0]的值,&zippo[0][0];**zippo相当于zippo[0][0]

在这里插入图片描述
8.函数与多维数组(续):

例如,如果传入的参数是1个指向包含4int类型值的数组的指针,2种声明参数的方式:
void f(int (*pt)[4]);//参见 C语言细节.指针.4 部分
void f(int pt[][4]);//第1个方括号是空的,表示pt是1个指针//仅在作为形参时允许
或者也可以使用 C语言基础.指针..3.(2) 部分中使用的声明方式

注意,下面的声明方式错误:
void f(int pt[][]);//,pt[]告诉编译器这是个指针,而编译器还需要知道pt指向的对象的大小

//实例:
void sum_rows(int ar[][COLS],int rows) {
	int r,c,tot;
	for (r=0;r<rows;r++) {
		tot=0;
		for (c=0;c<COLS;c++) {
			tot+=ar[r][c];
		}
		printf("%d\n",tot);
	}
}

int main(void) {
	int junk[ROWS][COLS]={
		{2,4,6,8},
		{3,5,7,9},
		{12,10,8,6}
	};
	sum_rows(junk,ROWS);
    return 0;
}
//结果:
20
24
36

9.变长数组(Variable-Length Array;VLA):

C99新增了变长数组,允许使用变量表示数组的长度,而C11将其作为可选特性

//实例:
int r=3,c=4;
int sales[r][c];

//注意:
1.变长数组必须是自动存储类别,也就是说,不能使用staticextern存储类别说明符
2.定义变长数组时不能进行初始化
3.不能修改变长数组的大小,"变长"指的是使用变量确定其大小
4.变长数组的变量名和普通数组的变量名一样,也是1个指针
5.C99/C11允许在声明变长数组时使用const变量(常变量),故该数组必须是自动存储类别

10.复合字面量(Compound Literal):

字面量是指除符号常量外的常量,5int类型的字面量,但在C99之前,没有数组的字面量
C99新增了复合字面量,是数组的字面量

//语法:
(<type> [<n1>][<n2>]...){<val1>,<val2>...}
  //复合字面量是匿名的,只能在创建时使用
  //复合字面量也是1个指针
  //参数说明:
    type:数组中元素的数据类型
    n1,n2...:数组的大小
    val1,val2...:数组中的元素

//实例:
#include <stdio.h>

void sum(int ar[],int n) {
	int r,tot=0;
	for (r=0;r<n;r++) {
		tot+=ar[r];
	}
	printf("%d\n",tot);//结果:9
}

int main(void) {
	int *pt1=(int [2]){3,6};
	printf("%d,%d,%d\n",pt1,*pt1,pt1[1]);//结果:6487552,3,6
	sum((int [2]){3,6},2);
    return 0;
}

11.包含指针的数组:

<type> * <avar>[<n>];
  //更详细的规则参见 C语言细节.其他数据类型.四 部分
  //参数说明:
    type:<avar>为指向该数据类型的指针
    avar:数组名
    n:数组长度

//实例:
#include <stdio.h>

int main(void) {
	int i1=1,i2=2,i3=3;
	int * a[]={&i1,&i2,&i3};
	printf("%d,%d",a[0],*a[0]);//结果:6487580,1
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值