1. 数组
- 数组声明
int arr1[10];
float arr2[4];
char arr3[50] ;
- 数组初始化
int arr[5] = {1,2,3,4,5};
- 使用const声明数组,数组为只读,不能再修改。
const int arr[5] = {2,4,5,6,7};
- 生命数组后如果未进行初始化,数组元素和未初始化的普通变量一样,存储的是垃圾值;如果只初始化了数组中的部分变量,则其余变量会被初始化为0。
- 如果初始化数组时忽略方括号里的数字(即数组长度),编译器会根据初始化列表的项数来确定数组大小。
- 使用for循环遍历数组时,可以通过sizeof arr / sizeof arr[0] 来获得数组元素的个数。
- 也可以通过数组的下标来初始化数组元素
int arr[5] = {[2] = 10}; //将第三个元素初始化为10;
int arr[5] = {[1] = 3,6,8} //将第2、3、4个元素初始化为3、6、8;
int arr[5] = {4,2,[0] = 3}; //注意,后面的初始化内容会覆盖掉前面的初始化内容
int arr[] = {[10] = 99}; //如果为指定数组长度,编译器会把数组的大小设置为足够装得下初始化的值,本数组长度为11;
- 使用数组时要防止数组下标越界。例如长度为10的数组arr,arr[-2]或arr[10]这样的初始化会把值存在数组的前一个或后一个位置,该位置可能存储着其他变量,这些变量被覆盖掉可能导致程序运行混乱。
int a = 10;
int arr[5];
int b = 2;
//以下两条语句可能导致a和b的值被更改,编译器不会提示,所以程序员需要格外注意。
arr[-1] = 99;
arr[5] = 99;
2. 二维数组
- 初始化二维数组,二维数组可以看做数组的数组
const int arr[3][4] =
{
{2,3,4,5},
{4,5,6,7},
{7,5,3,8}
};
3. 指针和数组
在C中,数组名就是数组首元素的地址。
int arr[5];
arr == arr[0]; //此语句成立
- 在指针前面使用 * 运算符可以得到该指针所指向对象的值
- 指针+1,指针的值递增他所指向类型的大小。例如arr[3]可以看做先找到内存的arr地址,然后向后移动3个元素的大小(取决于储存元素的类型),获得储存在该位置的值。
arr + 2 == &arr[2] //两个相同的地址
*(arr + 2) == arr[2] //两个相同的值
4. 函数、数组和指针
- 定义一个函数时,如果该函数的参数是一个数组,形式参数应该是一个指针类型。
//函数原型可以省略参数名
int sum(int arr[]);
int sum(int []);
int sun(int *);
int sum(int* arr);
//函数定义不能省略参数名
int sum(int arr[]){函数体}
int sum(int* arr){函数体}
- 在函数中,使用参数列表中的数组时,要注意此时的参数并不是数组本身,它是一个指向数组首元素的指针。在使用sizeof时,得到的结果只是其中一个元素的size。
int main(){
int arr[5] = {1,2,3,4,5};
printf("%d",sizeof arr); //打印的结果是20,因为arr数组有5个int值,每个int占4字节,所以整个数组的大小是20字节
sum(arr);
}
int sum(int arr[]){
printf("%d",sizeof arr); //打印的结果是8,因为arr是一个指向arr首元素的指针,系统使用8字节存储地址,所以指针变量的大小是8字节(其他系统可能不是8字节)。
}
- 函数处理数组必须知道何时开始,何时结束。可以使用一个整型参数告诉函数待处理的数组的元素个数,也可以传递两个指针,第一个指针指明数组的开始处,第二个指针指明数组的结束处。
//第一种,指明元素的个数
int sum(int arr[],int size);
int sum(int* arr,int size);
//第二种,指明数组的结束处
int sum(int arr[],int* end);
int sum(int * arr,int* end);
//下面是传入结束指针的两种用法,sum函数将参数数组的元素累加。
int sum(int arr[], int* end) {
int sum = 0;
//用法一
for (size_t i = 0; i < end - arr; i++) {
sum += arr[i];
}
//用法二
while (end>arr){
sum += *arr; //*arr表示当前数组元素的值
arr++; //以上两行代码也可以压缩为 sum += *arr++;
}
- 形式参数的指针表示法和数组表示法对于C语言来说是等价的,两个表达式都没问题。但是对于程序员来说,使用数组表示法让代码更清晰。
5. 指针操作
- 指针赋值:注意两个指针的类型应该一致,不能把一个double类型的地址赋给指向int类型的指针。
int arr[5] = {1,2,3,4,5};
int * ptr;
ptr = &arr[4]; //把一个地址赋给指针变量
- 解引用:使用*符号解引用指针,获得该指针指向的值。
- 取址:使用&运算符获得指针本身的地址。
- 指针与整数相加:整数会与指针指向类型的大小相乘,然后与指针地址相加,因此arr+4与&arr[4]等价。如果结果超出数组范围,计算结果则是未定义的(垃圾值)。除非正好超过数组末尾第一个位置,C保证该指针有效
- 递增指针:递增指向数组的指针可以让该指针指向数组的下一个元素
- 指针减去一个整数:与指针与整数相加相反。&arr[3]-2与arr[1]等价。
- 递减指针:与递增相反,让指针指向前一个元素
-
指针求差:两个指针指向同一个数组的不同元素,通过求差得出两个元素的距离。例如int数组中,pointer1 - pointer2得4,指的是两个指针相隔4个int。
注意,编译器不会检查指针是否仍指向数组元素
创建一个指针时,系统只分配了储存指针本身的内存,没有分配存储数据的内存。因此,在使用指针之前,必须先用已分配内存的地址初始化它。
double * pointer; //未初始化的指针
*pointer = 2.5; //严重的错误
double d = 6;
pointer = &d; //正确的初始化
6. 保护数组中的数据
在函数中,对参数列表的数组进行操作会修改原数组的值,因为函数通过指针直接使用了原始数据。如果函数的意图不是修改原数组的数据内容,在函数原型和函数定义中应该使用const关键字,避免误操作修改数组内容。这样如果遇到修改数组内容的表达式,编译器会报出错误。
int sum(const int arr[]);
int sum(const int arr[]){函数体}
只能把非const数据的地址赋给普通指针。使用非const标识符修改const数据,结果是未定义的。
double rates[5] = {1.1,2.3,442.1,44.2,54.1};
double * const pointer = rates;
pointer = &rates[2] ; //不允许,该指针不能指向别处
*pointer = 45.55; //允许,可以改变rates[0]处的值
const double * const ptr = rates; //既不允许该指针指向别处,也不允许改变地址上的值。
ptr = &rates[4]; //不允许
*ptr = 45.545; //不允许
7. 指针和多维数组
int arr[4][2] = {{5, 6}, {8, 4}, {3, 2}, {9, 7}};
printf("arr[0] = %d",**arr); //取得的是5,等价于*arr[0]
printf("*arr[2] = %d\n",*arr[2]); //取得的是3
要特别注意,与 zippo[2][1]等价的指针表示法是((zippo+2) + 1)。看上去比较复杂,应最好能理解。下面列出了理解该表达式的思路:
int n = 5;
double d = 10.1;
int * ptr1 = &n;
double * ptr2 = &d;
d = n; //可以,隐式类型转换
ptr2 = ptr1; //不允许两个不同类型的指针赋值
//假设有如下声明
int * pt;
int (*pa)[3]; //一个指向包含三个int元素数组的指针
int ar1[2][3];
int ar2[3][2];
int **p2; // 一个指向指针的指针
有如下的语句:
pt = &ar1[0][0]; // 都是指向int的指针
pt = ar1[0]; // 都是指向int的指针
pt = ar1; // 无效
pa = ar1; // 都是指向内含3个int类型元素数组的指针pa = ar2; // 无效
p2 = &pt; // both pointer-to-int *
*p2 = ar2[0]; // 都是指向int的指针
p2 = ar2; // 无效
在函数原型中声明多维数组时,第一个方括号中的值省略(即使填写,编译器也会忽略该值)。后面的方括号的数值不能省略。
int sum(int arr [] [10] [5],int rows ); //正确,等价于下面的语句
int sum(int (*arr)[10][5],int rows);
变长数组:变长数组不能改变大小,这里的变指的是在创建数组时,可以使用变量制定数组的维度。
int sum2d(int rows,int cols,int arr[rows][cols]); //ar是一个变长数组,注意不能改变参数顺序
//或者使用下面的格式
int sum2d(int ,int ,int arr[*][*]);
//函数定义如下,该函数可以处理任意大小的二维int数组
int sum2d(int rows, int cols, int ar[rows][cols]) {
int r;
int c;
int tot = 0;
for (r = 0; r < rows; r++) {
for (c = 0; c < cols; c++) {
tot += ar[r][c];
}
}
return tot;
}
8. 复合字面量
复合字面量是提供临时需要的值的一种手段。一旦离开定义该字面量的块作用域,程序无法保证该字面量是否存在。
//普通创建数组
int arr[2] = {1,2};
//使用复合字面量创建匿名数组
(int [2]){1,2};
(int []){1,2,3}; //也可以省略大小,编译器会自动计算数组元素个数
//因为复合字面量是匿名的,所以不能先创建再使用它,必须在创建的同时使用。
int *ptr;
ptr = (int [ ]){1,2,3};
//也可以使用复合字面量作为实际参数传给函数;
int sum(int * p,int size); //函数原型
sum((int []){2,3,4},3); //这样的好处是使用函数时不用先创建数组
int (*ptr)[4]; //声明一个指针
ptr = (int [2][4]){{12,3,44,5},{3,4,5,6}};