数组
- 数组部分初始化,剩余元素都会被初始化为0
int days[12]={31,28,[4]=31,30,31,[1]=29};
输出为{31,29,0,0,31,30,31,0,0,0,0,0}
int stuff[]={1,[6]=4,9,10};
输出为{1,0,0,0,0,0,4,9,10}; - C不允许数组作为一个单元赋给另一个数组
int oxen[4]={5,3,2,8};
int yaks[4];
yaks=oxen;//不允许
yaks[4]=oxen[4];//数组下标越界
yaks[4]={5,3,2,8};//不起作用
- 编译器不会检查数组下标是否使用得当
声明数组之前,使用符号常量表示数组大小,确保整个程序中的数组大小一致。
- 变长数组
多维数组
- 在计算机内部,二维数组是按顺序储存的
- 初始化时,某列表中的数值数超过了数组每行的元素,则会出错,但不会影响其他行的初始化
初始化时,可省略内部的花括号,只保留最外面的花括号。只要保证初始化的数值个数正确,初始化的效果与上面相同。如果初始化数值不够,则按照先后顺序逐行初始化,用完所有值后,后面的值会统一初始化为0。
指针和数组
指针提供一种以符号形式使用地址的方法。
- 数组名是数组首元素的地址。假设num是一个数组,则有
num=&num[0]
在C中,指针加1指的是增加一个存储单元。对数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址 - 在指针前面加*运算符可以得到该指针所指向对象的值
数组和指针的关系十分密切。
例如:dates + 2 == &dates[2]
,*(dates+2)=dates[2]
。
*dates+2指的是dates的第一个元素加2
*(dates+2)指的是dates的第三个元素
函数、数组和指针
假设编写一个处理数组的函数,该函数应该返回数组中所有元素之和,待处理的是名为marbles的int类型数组。如何调用函数?
可能的函数调用为:total=sum(marbles);
则该函数的原型是什么?
- 数组名是该数组的首元素的地址,所以实际参数marbles是一个存储int类型的地址,应把它赋值给指针形式的参数,即该形参应该是一个指向int类型的指针
对应的函数原型为int sum(int * ar);
sum()函数从参数中获得了该数组首元素的地址,知道要在该位置找出一个整数。
注意:该参数并未包含数组元素个数的信息。
一共有两种解决方法。
1.在函数代码中写上固定的数组大小
2.把数组大小作为第二个参数传入 - int *ar形式和int ar[]都表示ar是一个指向int的指针。
//由于函数原型可以省略参数名,所以下列四种原型都是正确的
int sum(int *ar,int n);
int sum(int *,int);
int sum(int ar[],int n);
int sum(int [],int);
#include <stdio.h>
#define SIZE 10
int sum(int[], int);
int main(void) {
int marbles[SIZE] = { 20,10,5,39,4,16,19,26,31,20 };
long answer;
answer = sum(marbles, SIZE);
printf("The total number of marbles is %ld.\n", answer);
printf("The size of marbles is %zd bytes.\n",
sizeof marbles);
return 0;
}
int sum(int ar[], int n) {
int i;
int total = 0;
for (i = 0; i < n; i++)
total += ar[i];
printf("The size of ar is %zd bytes.\n", sizeof ar);
return total;
}
输出为
The size of ar is 8 bytes.
The total number of marbles is 190.
The size of marbles is 40 bytes.
由于传入函数中的是指向数组首元素的指针,并不是数组本身,字符长度为8。
数组是int类型,一个元素占四个字节,共10个元素,40个字节。
使用指针形参
- 函数处理数组需要知道何时开始、合适结束。函数使用一个指针来指向数组的首地址,用整数表明待处理形参的元素个数。同时也可以再使用一个指针来表示数组的末地址
声明函数:int sump(int *,int *);
调用函数:answer = sump(marbles,marbles+SIZE);
C在给数组分配空间时,指向数组后一位的位置的指针仍然是有效的指针,但不可以访问。
-
*start++
,(*start)++
,*(start++)
,ar[i]
,*(ar+i)
*start++
和*(start++)
等效,指针+1,(*start)++
是数组start的首元素+1,ar[i]
和*(ar+i)
等效
指针操作
- 赋值:可以把地址赋给指针。例如:用数组名、待地址运算符&的变量名、另一个指针进行赋值。
地址应该和指针类型兼容,不能把double类型的地址赋给指向int类型的指针 - 解应用:*运算符给出指针指向地址上存储的值
不要解应用未初始化的指针,例如:int *pt;*pt = 5;
,pt未被初始化,所以不知道5被存储到何处 - 取址:指针变量也有自己的地址和值。对指针而言,&运算符给出指针本身的地址
减法有两种,一种是指针减去整数得到另外一个指针,一种是指针减去指针,得到一个整数。
保护数组中的数据
- 对参数使用const。例如:
int sum(const int ar[],int n);
确保在函数使用数组时,将其视为常量,不可更改
const int days[12]={31,28,31,30,31,30,31,31,30,31,30,31};
如果程序稍后尝试改变数组元素的值,编译器将生成一个编译器错误消息。
double rates[5]={88.99,100.12,59.45,183.11,340.5};
const double * pd=rates;
这句话表明不能使用pd来改变它所指向的值
*pd=28.99;
,pd[2]=222.22
,不允许
rates[0]=99.99;
,允许,因为rates未被const限定
pd++;
,允许
只能把非const数据的地址传输给普通指针,否则通过指针就能改变const数据
不要把const数组作为实参传入函数中 - const可以声明并初始化一个不能指向别处的指针
double * const pc = rates;
可以更改指针指向参数的值,但指针只能指向retes[0]
多维数组
int zippo[4][2];
zippo是该数组首元素的地址,zippo的首元素是一个内含两个int值的数组。
在数值上zippo==zippo[0]==zippo[0][0]
zippo是一个占用两个int大小对象的地址,zippo[0]是一个占用一个int大小对象的地址。
故zippo+1与zippo[0]+1不相等。
zippo[0][0]==**zippo==*zippo[0]
zippo[2][1]==*(*(zippo+2)+1)
指向多维数组的指针
- 声明:
int (* pz)[2];
表示pz指向一个内含两个int类型值的数组
int * pz[2];
表示一个指针数组,每个元素都指向int
指针的兼容性
函数和多维数组
*junk是一个3行4列的数组,则声明函数形参:void somefuction(int (* pt)[4]);
同时可以这样声明:void somefunction(int pt[][4]);
注意,第一个方括号是空的,这表明pt是一个指针。
在声明或定义函数时,只能省略最左边括号中的数值,因为最左边的括号只用于表明这是一个指针,其他括号中的数值则用于描述指针所指向数据对象的类型。
int sum(int ar[][12][10][30],int n)
与int sum(int (*ar)[12][10][30])
等效。
变长数组
使用变量表示数组的维度。
- 变长数组必须是自动存储类别
- 不能在函数声明中初始化它们
- 变长数组不改变大小,创建后的变长数组大小保持不变。‘变’指的是:在创建数组中,可以使用变量指定数组的维度。
- 函数声明:
int sum(int rows,int cols,int ar[rows][cols]);
因为ar的声明要使用rows和cols,所以在形参列表中必须在声明ar之前先声明这两个形参。 - 声明时可以省略形参名,但必须用星号来代替省略的维度
int sum(int,int,int ar[*][*]);
新声明的sum()可以处理任意大小的int二维数组
复合字面量
(int [2]){10,20}
- int [2]即是复合字面量的类型名
复合字面量也可以省略大小,编译器会自动计算数组当前的元素个数。 - 不能先创建再使用,必须在创建的同时使用它。
int * pt1;
pt1 = (int [2]){10,20};
- 可以作为实际参数传递给带有匹配形参的函数
复合字面量好处:把信息传入前不必先创建数组
int (* pt2)[4];
pt2 = (int [2][4]){{1,2,3,-9},{4,5,6,-8}};
复合字面量是提供只临时需要的值的一种手段