一、数组的初始化
1、当初始化列表中的值少于数组元素个数时,编译器会把剩余的元素都初始化为0;
2、如果初始化数组时省略方括号中的数字,编译器会根据初始化列表中的项数来确定数组的大小;
3、对于数组中个数的多少,有一个比较不容易出错的方法 - sizeof () 的使用;
e.g. : int nums[] = {31, 28, 31, 30};
int count = sizeof nums / sizeof nums[0]; //计算数组中的元素个数
【sizeof ():给出运算对象的大小,以字节为单位。在上述的语句中,sizeof nums 是整个数组的大小,sizeof nums[0] 是数组中一个元素的大小。】
4、C99 中可以初始化指定的数组元素。
int arr[7] = {1, 2, [4] = 23, 8, [1] = 12}
上述语句的结果为:arr[0] = 1, arr[1] = 12, arr[2] = 0, arr[3] = 0, arr[4] = 23, arr[5] = 8, arr[6] = 0。
5、C 不允许把数组作为一个单元赋给另外一个数组,除初始化以外也不允许使用花括号列表的形式进行赋值。
#define SIZE 5 int main (void) { int oxen[SIZE] = {5, 3, 2, 8}; int yaks[SIZE] ; yaks = oxen; /*不允许*/ yaks[SIZE] = oxen[SIZE]; /*数组下标越界*/ yaks[SIZE] = {5, 3, 2, 8}; /*不起作用*/ }
二、多维数组的理解
1、二维数组的理解
float rain[5][12]; //内含5个数组元素的数组,每个数组元素内含12个float类型的元素
换句话说,rain中每个元素本身都是一个内含12个float 类型值的数组。
2、三维数组的理解
int box[10][20][30];
对于box,其理解方式有:1,box内含10个元素,每个元素是内含20个元素的数组,这20个数组元素中的每个元素是内含30个元素的数组;2,把box想像成有10个二维数组(每个二维数组都是20行30列)堆叠起来。
3、多维数组的理解
与1、2的内容相类似。
三、指针 & 数组
1、指针的值是其指向对象的地址。地址的表示方式依赖于计算机内部的硬件。
2、指针前使用 ‘ * ’ 运算符可以得到该指针所指向对象的值。
3、指针加1,指针的值递增其所指向类型的大小(以字节为单位)。
四、函数 & 数组 & 指针
1、声明数组形参
对于函数原型中的数组形参的声明,下面的几种原型是等价的。
int sum (int *ar, int n); int sum (int *, int n); int sum (int ar[], int n); int sum (int [], int n);
但是,在函数定义的时候不能省略参数名,下面的几种函数定义是等价的。
int sum (int *ar, int n){ //函数主体 } int sum (int ar[], int n){ //函数主体 }
2、‘ * ’运算符与‘ ++ ’ / ' -- '运算符的结合
total += *star++; //先‘递增’后指向 total += (*star)++; //先指向后递增
对于上述语句,‘ * ’运算符与‘ ++ ’运算符的优先级相同,但是结合律是从右到左,所以先计算start++,之后再计算*start,先‘递增’后指向。使用后缀形式(start++,而不是++start)意味着先把指针指向位置上的值加到total上,然后再递增指针;如果使用前缀形式(++start,而不是start++),意味着要先递增指针,之后把指针指向位置上的值加到total上。
五、指针的几种操作
int urn[5] = { 100, 200, 300, 400, 500 } int * ptr1, *ptr2, *ptr3;
1、赋值
ptr1 = urn; //将一个地址赋给指针 ptr2 = &urn[2]; //将一个地址赋给指针
2、解引用
printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p ", ptr1, *ptr1, &ptr1);
解引用 - “ *ptr1”。给出指针指向的地址上存储的值。
3、取址
printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p ", ptr1, *ptr1, &ptr1);
取址 - “ &ptr1”。指针也有自己的地址,‘ & ’运算符给出的是指针本身的地址。
4、指针与整数相加
ptr3 = ptr1 + 4; //指针加法
' ptr1 + 4 ' 等价于 &urn[4] 。
5、递增指针
ptr1++; //指针递增
‘ ptr1++ ’ 等价于ptr1的值加上4(假设系统中 int 数据为4字节),pte1 指向 urn[1] 。
6、指针减去一个整数
printf("ptr3 - 2 = %p ", ptr3 - 2);
整数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积。
7、递减指针
ptr2--; //指针递减
8、指针求差
printf("ptr2 - ptr1 = %td ", ptr2 - ptr1);
通常两个指针指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。如果 ptr2 - ptr1 = 2,则意味着两个指针所指向的元素之间相差着两个 int ,而并不是两个字节。
9、比较
使用关系运算符进行比较,前提是两者都是指向相同类型的对象。
六、 const 修饰符的使用
1、对形参使用 const
int sum (const int ar[], int n); /*函数原型*/ int sum (const int ar[], int n){ //函数体 }
上述的 const 修饰符告知编译器:sum 函数不可以修改 ar 指向的数组中的内容。如果在 sum 函数中使用的类似于 ar[i]++的表达式,编译器会很容易地检测到错误。
这里只用的 const 修饰符并不是要求原数组是常量,而是该函数在处理数组时将其看做是常量,不可更改。
一般而言,如果函数需要修改数组,在声明数组形参时不使用 const 修饰符;如果编写的函数不用修改数组,在声明数组形参时使用 const 修饰符。
2、const 修饰符的其他内容
【 const 数组】
#define SIZE 3 ... const int data[SIZE] = {2, 4, 7}; ... data[2] = 5; //编译错误
【 const 指针】
double index[3] = {1.1, 2.3, 4.5}; const double * pd = index; ... *pd = 3.8; //不允许 pd[2] = 9.0; //不允许 index[2] = 9.0; //允许,index 未被限定
需要注意的是,可以让 pd 指向别处:' pd ++ ;' 是允许的。
【其他】
把 const 数据或非 const 数据的地址初始化为指向 const 的指针或为其赋值是合法的。
double rates [4] = {88.99, 100.12, 4.98, 76.45}; const double locked[3] = {1.2, 3.5, 7.9}; const double * pc = rates; //有效 pc = locked; //有效 pc = &rates[2]; //有效
只能把非 const 数据的地址赋给普通指针。
double rates [4] = {88.99, 100.12, 4.98, 76.45}; const double locked[3] = {1.2, 3.5, 7.9}; double * pc = rates; //有效 pc = locked; //无效 pc = &rates[2]; //有效
C 标准规定,使用非 const 修饰符修改 const 数据,导致的结果是未定义的。【不应该把 const 数组名作为实参传递给相应形参为非 const 数组名的函数。】
可以声明并初始化一个不能指向别处的指针,关键是 const 修饰符的位置。
double rates [4] = {88.99, 100.12, 4.98, 76.45}; double * const pc = rates; //pc 指向数组的开始 pc = &rates[1]; //无效,pc 不允许指向别处 *pc = 99.9; //有效,更改 rates[0] 的值
可以使用 const 修饰符两次,使得指针即不能更改其所指向的地址,也不能更改其指向的地址上的值。
double rates [4] = {88.99, 100.12, 4.98, 76.45}; const double * const pc = rates; //pc 指向数组的开始 pc = &rates[1]; //无效,pc 不允许指向别处 *pc = 99.9; //无效,不允许更改 rates[0] 的值
七、指针与多维数组
1、简单示例
int zippo[4][2]; /* zippo -- 二维数组首元素的地址(每个元素都是内含两个 int 类型元素的一维数组) zippo + 2 -- 二维数组的第3个元素(即一维数组)的地址 *(zippo + 2) -- 二维数组的第3个元素(即一维数组)的首元素(一个 int 类型的值)的地址 *(zippo + 2) + 1 -- 二维数组的第3个元素(即一维数组)的第2个元素(一个 int 类型的值)的地址 *(*(zippo + 2) + 1) -- 二维数组的第3个一维数组的第2个 int 类型的值,也就是 zippo[2][1] */
2、相关的指针数组
int (* pz) [2]; //pz指向一个内含两个 int 类型值的数组 int * pzx[2]; //pax是一个内含两个指针元素的数组,每个元素都指向 int 的指针
3、指针的兼容性
【两个不同类型的指针之间不可以相互赋值】
4、需要处理二维数组的函数中的形参的声明
... #define ROWS 3 #defien COLS 4 ... void sum_rows (int ar[][COLS], int rows); //空的方括号表明 ar 是一个指针 void sum_rows (int (*ar) [COLS], int rows); void sum_cols (int [][COLS], int ); //可以,省略形参名 int sum2d(int (*ar) [COLS], int rows); //另一种语法
八、复合字面量【C99 新增】
字面量:除符号常量之外的常量。
1、示例
【pre】5 -- int 类型字面量;2.3 -- double 类型字面量;'Y' -- char 类型字面量;"elephant" -- 字符串字面量。
【新增 - 数组】对于数组,复合字面量类似于数组初始化列表,前面是用括号括起来的类型名。
double diva [2] = {2, 4}; //普通的数组声明 (int [2]) {2, 4}; //复合字面量
【去掉声明中的数组名,留下的 int [2] 即是复合字面量的类型名。】
2、相关用法
【与有数组名的数组初始化一样,同样可以省略数组大小,编译器会自动计算数组当前的元素个数。】
(int []) {10, 20, 40} //内含3个元素的复合字面量
【复合字面量是匿名的,必须在创建的同时去使用。常见的用法是指针记录地址。】
int *pt1; pt1 = (int [2]) {10, 20}; //复合字面量的类型名也代表着首元素的地址,*pt1 = 10,pt[1] = 20
【可以把复合字面量作为实际参数传递给带有匹配形式参数的函数。】
int sum (const int ar[], int n); ... int toal3; total3 = sum((int []) {4, 4, 4, 5, 5, 5}, 6);//第一个实参:内含6个 int 类型值的数组。把信息传入函数前不必先创建数组,此为复合字面量的典型用法