一、基本概念
1.1数组声明
typename 数组名字[数组大小];
eg float candy [365]; char code [12]; int states[50];
注意:
1.数组下标从0开始,到n-1结束;
2.数组大小必须用整型常量来声明(并且表达式的值必须大于0);
3. C99 和 C11引入了变长数组,数组大小可以用变量来声明,后续介绍。
1.2数组初始化:
① int states[5] = {1,2,3,4,5}; 如果括号内的数字不足5个,编译器会把剩余的元素都初始化为0。为了在写程序时保证数组大小一致,一般采用宏定义一个SIZE,int states[SIZE] = {1,2,3,4,5}.
② 有时需要把数组设置为只读,应该用const声明和初始化函数。 const int states[5] = {1,2,3,4,5}
③指定初始化器,利用该特性,可以初始化指定的数组元素。 例如要指定初始化最后一个元素,int states[5] = {0,0,0,0,5};利用指定初始化器可以int states[5] = { [4]=5 }.如果指定初始化器后边有更多的值,那么后边的值将被用于初始化指定元素后面的元素。如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化。
eg int days [12] = { 31,28,[4] = 31, 30, 31, [1] = 29};
days[ 0 ] = 31; days[ 1 ] = 29; days[ 2 ] = 0; days[ 3 ] = 0; days[ 4 ] = 31; days[ 5 ] = 30;
days[ 6 ] = 31; days[ 7 ] = 0; days[ 8 ] = 0; days[ 9 ] = 0; days[ 10 ] = 0; days[ 11 ] = 0;
注意:
1.如果初始化数组时省略方括号中的数字,编译器会根据初始化列表中的项数来确定数组的大小。
2.如果初始化列表的项数多余数组元素的个数,会报错。
3.必须先初始化,在使用数组,否则结果不是我们所期望的。(自动存储类别对于未初始化的变量,里边都是垃圾值;但对于一些其他存储类别的未初始化的变量和数组,编译器会自动把他们的值设置为0)。
1.3数组元素赋值
C语言中,数组被看作是派生类型,因此数组声明后,需要借助下标给数组元素逐一赋值,不允许把数组作为一个单元赋给另一个数组,除初始化以外也不允许使用花括号列表的形式赋值。
int oxen [5] = {5,6,4,8}; // 正确
int yaks [5];
yaks = oxen; //错误
yaks [5] = {5,6,4,8}; //不起作用
for ( int j = 0; j< 5; j++)
yaks[j] = j; //正确
1.4 数组边界
在使用数组时,要防止数组下标超出边界,因为编译器不会检查数组下标是否使用得当,使用越界的数组下标会导致程序改变其他变量的值。
二、一维数组和指针
2.1指针和数组的关系
① 数组名是数组首元素的地址, 如果 flizny是一个数组, 那么 flizny = &flizny[0].这两者都是常量,可以将他们赋值给指针变量,然后可以修改指针变量的值。
( fizny++;这句就是错的,因为他是常量;错不在与加1或者减1,在于自加后改变了值。
但是 int * p = flizny; p++; 把该地址赋值给一个指针变量,就可以自加)
② 指针的值+1指的是增加一个他所指向类型的大小(以字节为单位)flizny +2 = &flizny[2]
③在指针前使用*运算符可以得到该指针所指的对象。 *(flizny +2) = flizny[2]
④一个较大对象的地址通常是该对象第一个字节的地址。
明白了数组和指针的关系,便可以适时使用数组表示法或指针表示法。
2.2函数、数组和指针
如果要编写一个处理数组的函数,应该如何编写这个函数呢?
① 函数原型中的形参应该为一个指针(数组名是第一个元素的地址)
② 地址参数并没有包含数组元素个数的信息,有两种方法解决: 1.再加一个数组大小的参数(推荐) 2.在函数定义代码中写上固定的数组大小(不推荐)
③ 因为数组名就是一个指针,因此只有在函数声明或者函数定义中,才可以用 int ar[ ] 代替 int * ar(也就是只有在函数定义和声明中有中括号的出现,在调用时直接使用数组名)
④函数原型可以省略参数名,所以下边4中等价,但是函数定义中不能省略参数名,并且ar只是一个指针,而不是数组。
int sum (int * ar,int n) int sum (int ar[] ,int n)
int sum (int * ,int ) int sum (int [] , int )
⑤除了用一个指针和一个表示数组大小的参数来作为形参,也可以使用两个指针(一个指针形参标识数组的开始,一个表示数组的结束)作为形参,C保证在给数组分配空间时,指向数组后边第一个位置的指针仍然是有效的指针,但该位置不能访问,因为存储在该位置的值未知。
int marbles[5] = { 1,2,3,4,5 };
answer = sump(marbles, marbles + 5);
int sump(int* start, int* end)
int total = 0;
while (start < end)
{
total += *start;
start++;
}
2.3指针操作
①赋值 数组名、带地址运算符的变量名、另一个指针进行赋值(不允许把不同类型的指针进行赋值,例如不能把double类型的地址赋给指向int的指针)
②解引用 *运算符给出指针指向地址上存储的值,不要解引用未初始化的指针,会发生严重错误
③取址 指针变量也有自己的地址
④指针与整数相加 整数会和指针所指向的类型的大小相乘,然后把结果与初始地址相加,超出了初始数组的范围,计算结果未定义。
⑤递增指针 指针移动至数组的下一个元素
⑥指针减去一个整数 指针必须是第一个运算对象,如果结果超出了初始数组的范围,计算结果未定义。
⑦递减指针 指针移动至数组的上一个元素
⑧指针求差 要求两个指针指向相同的数组,可以得到两个地址之间的距离(指针-指针/指针-数字)
⑨比较 关系运算符可以比较两个指针的值,但必须指向相同类型的对象
2.4 保护数组中的数据
在函数章节,我们知道参数传递时有两种,第一种传递相同类型的值,在主调函数中的值不会改变;第二种是传递指针,主调函数中的值也会改变,对于形参是数组的情况,我们只能传递指针,那么如何保护数组中的数据使其不改变呢?----------对形式参数使用const
①对形式参数使用const
int sum (const int ar[ ], int n);
这样使用并不是要求原数组是常量,而是该函数在处理数组时将其视为常量。
②const的其他用法
除了const关键字保护数组,还可以创建 const 数组(数组内容不能变), const 指针(指针只得位置不能变),指向const的指针(指针解引用的值不能变)
指向const的指针 :一般用于函数形参中,表示该函数不会使用指针改变数据,const double *p=rates(数组名);对于指向const的指针,可以把 const数据或者非const数据的地址赋值给指向const的指针;
对于一个普通指针,则只能把非const数据的地址赋值给该指针,const数据的地址赋值给普通指针无效
const指针: double * const pc = rates
也可以构建指向const数据的const指针,是哪种情况主要看const位置。