有基础,进阶用,个人查漏补缺
-
const声明可以把数据设置为只读,程序只能从数据中检索值,不能写入新值
-
数组必须初始化赋予初值,否则初值就是原来相应内存的现有值。这与储存类型有关,将在12章详细展开。对于其他储存类型,如果声明时未初始化,编译器会自动把他们的值设置为0
-
当初始化列表中的值少于数组元素个数时。编译器会把剩余值初始化为0。
当初始化列表中的值多于数组元素个数时。编译器会报错。
也就是说,如果不初始化数组,数组元素储存的都是垃圾值;如果初始化了部分数组,剩余元素会被初始化为0
-
C99中,可以只初始化某个指定数组元素,其他的会被自动初始化为0;传统的C语言中,必须初始化最后一个元素之前的所有元素,才能初始化它
-
如果再次初始化指定元素,最后的初始化会取代之前的初始化
-
如果声明时未指定数组元素大小,编译器会把数组的大小设置为足够装得下初始化的值,如int a[]={1,[3]=23},则数组a的元素共有4个
-
数组元素赋值:要使用循环给数组元素依次赋值
-
不允许把数组作为一个单元赋给另一个数组
-
除了初始化之外不允许使用花括号列表的形式赋值
int a[4]; int b[4] = {1, 3, 4, 5}; a = b; //不允许 a[4] = b[4] //下标越界 a[4] = {1, 3, 4, 5}; //不起作用
-
-
数组越界,编译器不会报错(出于C信赖程序员原则),但是一旦数组越界,会导致程序改变其他变量的值,甚至异常中止
-
声明数组时只能在方括号中使用整型常量表达式
int n = 5, m = 8; float a1[5]; float a2[5*2+1]; float a3[sizeof(int)+1]; float a4[-4];//不可以,数组大小必须大于0 float a5[0];//不可以,数组大小必须大于0 float a6[2.5];//不可以,数组大小必须为整数 float a7[(int)2.5];//可以,已被强制转化为整数 float a8[n];//C99前不允许
-
多维数组的初始化也是基于一维数组
-
数组名是数组首元素的地址,即数组a == &a[0]
-
在C语言中,指针+1指的是增加一个存储单元;对数组而言,意味着下一个元素的地址,而不是下一个字节的地址。所以必须声明指针的对象类型。
short dates[4]; short * pti; pti = dates;//把数组地址赋给指针 dates+2 == &dates[2];//相同的地址 *(dates+2) == dates[2];//相同的值 *(dates+2) //第3个元素的值 *dates+2 //第1个元素的值+2
-
关于函数形参,只有在函数原型或者函数定义头中,才可以用int ar[]代替int * ar。
多维数组可以int sum(int ar[][COL]),或者int sum(int [][COL]),或者int sum(int (*ar)[][COL]),不允许int ar[][]。
注意,在函数形参int ar[ROW][COL]中,ROW会被忽略。
延伸到n维数组中,最左边方括号中的值会被忽略,因为第一对方括号只用于表示这是一个指针。
/*声明函数*/ int sum2d_1(int rows, int cols, int ar[rows][cols]);//ar是一个变长数组 int sum2d_2(int ar[rows][cols], int rows, int cols);//无效顺序,rows和cols要放在前面 int sum2d_3(int, int, int ar[*][*]);//可以省略形参名字,但是必须用*代替省略的维度
-
数组和指针的大小:(还是不懂为什么)(数组指针、指针数组分不清)
#include <stdio.h> #define SIZE 4 int sun(int ar[], int n); int main(void) { int marbles[SIZE] = {1, 3, 2, 4}; long answer; answer = sum(marles, SIZE); printf("The total number of marbles is %ld.\n", answer); printf("The size of marbles is %zd bytes.\n", sizeof marbles); //输出sizeof marbles=4*4=16(marbles内含4个int,每个int占4字节) return 0; } int sum(int ar[], int n)//此处的形参里的数组名字其实只是一个指针,指向数组 { int i; int total = 0; for(i=0; i<n; i++) { totla += ar[i]; } printf("The size of ar is %zd bytes.\n", sizeof ar); //输出sizeof ar=8 //因为ar不是数组本身,而是**一个指向marbles数组首元素的指针**,而该系统(x64)用8字节储存地址 return total; }
-
利用结合律
totla += *start; start++; //相当于 total += *start++;
一元运算符和++优先级相同,但结合律是从右到左计算,所以start++先计算,在start,即指针start先递增后指向。使用后缀形式,意味着先把指针指向位置上的值加到total上,再递增指针。
- 如果是*++start,则先递增指针,再使用地址上的值
- 如果是(*start)++,则先使用start指向的值,再递增该数值,而不是递增指针
-
地址应该和指针类型兼容,如不能把double的地址赋给指向int的指针
-
指针操作:
#include <stdio.h> int main(void) { int urn[5] = {100, 200, 300, 400, 500}; int *ptr1, *ptr2, *ptr3; ptr1 = urn;//把首地址赋给ptr1 ptr2 = &urn[2];//把第3个元素的地址赋给ptr2,&为取地址符 /*所以*/ //ptr1为初值地址,*ptr1为初值100 //&ptr1为指针ptr1的指针,本段后有详细解释 ptr3 = ptr1 + 4;//等价于&urn[4],即第5个元素的地址 //指针加一个整数,指针必须是第一个运算对象,整数是第二个 ptr1++;//相当于ptr1的值加4,因为我们的系统int为4字节,ptr1指向urn[1] //但是,&ptr1的值依旧不变,毕竟变量不会因为数值发生变化就移动位置 ptr1--;//恢复为初始值 ptr2-ptr1;//得2,求差的两个指针分别指向同一个数组的不同元素,求出两个元素之间的距离 //ptr2-ptr1 == 2,意思是这两个指针所指向的两个元素相隔两个int,而不是2字节 }
-
指针的指针:这里参考指针的指针(简单易懂)
-
普通情况下的指针,内存分配如下
int a = 12; int *b = &a;
-
指针的指针
c = &b; //即 int a = 12; int *b = &a; int **c = &b;
即双重间接访问
-
-
千万不要解引用未初始化的指针
int *pt;//未初始化的指针 *pt = 5;//严重错误
把5储存在pt指向的位置,但是未初始化的pt的地址值是一个随机值,所以5不知道将储存在何处。可能会导致擦写数据或代码,或者程序崩溃。
所以在使用指针之前,必须先用已分配的地址初始化它。
-
如果编写的函数不用修改数组,那么在声明数组形参时建议使用const
-
const:
-
#define可以创建类似功能的符号常量,但是const的用法更加灵活,可以创建const数组、const指针和指向const的指针
-
指向const的指针通常用于函数形参中,表明该函数不会使用指针改变数据
-
指针赋值和const的一些注意事项,看无效的那一条就行了
int a[3] = {1, 2, 3}; const int locked[3] = {1, 1, 1}; const int * pc = a;//初始化有效 //把const数据,或者非const数据的地址初始化为指向const的指针是合法的 //赋值给指向const的指针是合法的 pc = locked; //有效 pc = &a; //有效 /*只能把非const数据的地址赋值给**普通**指针*/ int * pnc = a; //初始化有效 **pnc = locked; //无效,否则就可以通过指针修改const int locked[3]的值了** pnc = &a[2]; //有效 #define LIMIT 20 const int LIM = 50; static int data1[LIMIT]; //有效 static int data2[LIM]; //无效 const int LIM2 = 2 * LIMIT; //有效 const int LIM3 = 2 * LIM; //无效
-
const的其他用法:
int a[3] = {1, 2, 3}; //用法1:声明并初始化一个不能指向别处的指针 int * const pc1 = a; //const的位置是关键,pc1指向数组的开始 pc1 = &a[2]; //不允许,**指针在初始化时就确定了只能指向数组的开始** *pc1 = 11; //允许,更改a[0]的值 //用法2:可以使用const两次,该指针不能更改指向位置,也不能更改指向位置上的值 const int * const pc2 = a; pc2 = &a[2]; //不允许 *pc2 = 11; //不允许
-
-
指针和多维数组
有多维数组 int a[4][2](首选数组表示法)
-
a是该数组首元素的地址,即a == &a[0]
a[0]本身是一个内含2个元素的数组,所以a[0]是该数组首元素的地址,即a[0] == &a[0][0]
所以*a == a[0] == &a[0][0]
所以**a == *a[0] == a[0][0]
所以a是地址的地址
a[0]+1,其值+4(一个int数组的元素大小为4字节);a+1,其值+8(a为内含有2个元素的int数组的首元素地址,大小为8字节)
-
a[2][1] == *( *(a+2) + 1):
a+2 ——二维数组的第3个元素(即第3个一维数组)的地址
*(a+2) ——二维数组的第3个元素(即第3个一维数组)的首元素(int数据)的值
*(a+2) + 1 ——二维数组的第3个元素(即第3个一维数组)的的第2个元素的地址
((a+2) + 1) ——二维数组的第3个元素(即第3个一维数组)的的第2个元素(int数据)的值,即数据第3行第2列的值
-
指向二维数组的指针的声明:
int (* pz)[2];//先(),再[],所以是pz指向一个内含两个int数据的数组 int * px[2];//先[],再*,所以px是一个内含两个int数据的数组,有两个指针指向int的值
-
-
两个类型的指针不之间能强制类型转换
-
过后再看10.7.2 指针的兼容性
-
复合字面量:
-
具有代表数组和结构内容的作用
-
复合字面量的类型名
int a[2] = {10, 20}; //普通数组声明 (int [2]){10, 20}; //复合字面量,类型名为int [2] //复合字面量会自动计算元素个数 (int []){10, 20}; //多维数组 (int [2][4]){{1,3,4}, {3,4,5}};
-
用法:
-
使用指针记录地址
int (* pt1)[3]; pt1 = (int [2][4]){{1,3,4}, {3,4,5}}; //注意不能先创建再使用
-
作为实参进行传递,不用专门创建数组
int sum(const int ar[], int n); ... int total; total = sum((int []){1,3,4,5}, 4);
-
-