一、数组
1.1 数组的定义
通过学习本章,数组就是占用一段连续的内存地址的一个抽象,它存储同类型的元素。
1.2 声明数组
类型 变量名[个数]
类型是为了让计算机知道每一个单元格是多大,数组的使用其实就是指针,访问指定位置的元素,就是指针移动到指定的位置,如声明一个 int arr[5] ,访问 arr[4] ,就是 访问*(arr + 4) ,这里的指针移动了 4 个单位,一个单位就是 int 所占的空间(代码见 2.1 (b) );数组变量名是标识符;个数×类型大小就是这个数组在内存中所占的空间大小。
1.3 初始化数组
在使用数组之前必须进行初始化,否则会产生意想不到的东西;
例如:
int a[5]; //声明并初始化
int i;
for(i = 0; i < 5; i++)
printf("%d ", a[i]);
/*
输出结果为:
4196144 0 4195472 0 974532160 (可能根据系统的不同而不同)
*/
使用 { } 的方式初始化,如果不指定元素,则自动初始化为0;
例:
int b[5] = {}; //声明并初始化
int i;
for(i = 0; i < 5; i++)
printf("%d", b[i]);
/*
输出结果为:
0 0 0 0 0
*/
使用 { } 这种方式初始化如果数组声明的个数与初始化个数不匹配,则默认把其他元素初始化为0;
int c[5] = {5, 6}; //声明并初始化
int i;
for(i = 0; i < 5; i++)
printf("%d", c[i]);
/*
输出结果为:
5 6 0 0 0
*/
使用 { } 可以初始化指定元素的值(C99的新特性)。
例:
int d[5] = {[3] = 3, [4] = 4}; //声明并初始化
int i;
for(i = 0; i < 5; i++)
printf("%d", c[i]);
/*
输出结果为:
0 0 0 3 4
*/
注:这里为了方便直接使用数值常量 5 来作为数组大小,规范的写法应该使用 #define 定义的常量。
1.4 多维数组
如 1.1 的图所示,多维数组其实和一维数组没有本质的区别,声明及初始化多维数组的方式如下:
int arr[5][5] = {{}}; //声明并初始化
int arr2[5][5] = {[2] = {1, 2, 3}};
int i;
int j;
for(i = 0; i < 5; i++)
{
for(j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
for(j = 0; j < 5; j++)
{
printf("%d ", arr[2][j]);
}
/*
输出结果:
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
1, 2, 3, 0, 0
*/
二、数组和指针
2.1 数组和指针的关系
数组和指针的关系十分密切,数组表示法其实就是在变相地使用指针,可以使用指针标识数组的元素和获得元素的值。实际上,C语言标准在描述数组的时候借助了指针。
2.1.1 数组名就是数组元素的首地址,即 arr = &(arr[0])
例:
#include <stdio.h>
#define SIZE 5
int main(void)
{
int arr[SIZE] = {}; //声明并初始化
//判断是否相等
(arr == &arr[0]) ? printf("equal! \n") : printf("not equal! \n");
return 0;
}
/*
输出结果:
equal!
*/
2.1.2 可以使用指针访问数组中的元素
Ⅰ. 访问一维数组中的元素
例:
#include <stdio.h>
#define SIZE 5
int main(void)
{
int arr[SIZE] = {[4] = 5};
int * p = arr;
printf("使用数组访问 arr[4]: %d \n", arr[4]);
printf("使用指针访问 arr[4]: %d \n", *(p + 4));
//注:*(p + 4) <=> p[4] <=> arr[4]
// 这样更能深刻理解数组的名字就是首地址
//使用指针改变数组的元素
*(p + 4) = 4;
printf("使用指针改变 arr[4]: %d \n", *(p + 4));
return 0;
}
/*
输出结果:
使用数组访问 arr[4]: 5
使用指针访问 arr[4]: 5
使用指针改变 arr[4]: 4
*/
Ⅱ. 访问二维数组中的元素
/*
像上述方法一样使用指针,
该方法会产生警告,
array4.c:7:12: warning: initialization from incompatible pointer type [enabled by
default]
int * p = arr;
*/
#include <stdio.h>
#define SIZE 5
int main(void)
{
int arr[SIZE][SIZE] = {{}};
int * p = arr;
arr[2][0] = 10;
printf("使用数组访问 arr[2][0]: %d \n", arr[2][0]);
printf("使用指针访问 arr[2][0]: %d \n", *(p + 10));
*(p + 10) = 4;
printf("使用指针改变 arr[2][0]: %d \n", *(p + 10));
return 0;
}
/*
输出结果:
使用数组访问 arr[2][0]: 10
使用指针访问 arr[2][0]: 10
使用指针改变 arr[2][0]: 4
*/
/*
把指针声明为指向数组的指针,该数组内含 SIZE 个 int 类型值。
这是最规范的写法
*/
#include <stdio.h>
#define SIZE 5
int main(void)
{
int arr[SIZE][SIZE] = {{}};
int (* p) [SIZE] = arr; // p 为指向数组的指针,该数组内含 SIZE 个 int 值
//注:arr <=> &(arr[0])
arr[2][0] = 10;
printf("使用数组访问 arr[2][0]: %d \n", arr[2][0]);
printf("使用指针访问 arr[2][0]: %d \n", *(*(p + 2)));
//注:*(*(p + 2)) <=> p[2][0] <=> arr[2][0]
*(*(p + 2)) = 4;
printf("使用指针改变 arr[2][0]: %d \n", *(*(p + 2)));
return 0;
}
/*
输出结果:
使用数组访问 arr[2][0]: 10
使用指针访问 arr[2][0]: 10
使用指针改变 arr[2][0]: 4
*/
2.2 指针表示数组
2.2.1 指针表示一维数组
数组表示:
数组类型 数组名字 [个数]
指针表示:
数组类型 * 指针名字
例:
#include <stdio.h>
#define SIZE 5
int main(void)
{
int a[SIZE]= {[4] = 10};
char b[SIZE] = {[4] = 'a'};
float c[SIZE] = {};
int * pa = a;
char * pb = b;
float * pc = c;
//输出数组和指针的地址,发现它们的地址相同
printf("arr_a: %p, pointer_a: %p \n", a, pa);
printf("arr_b: %p, pointer_b: %p \n", b, pb);
printf("arr_c: %p, pointer_c: %p \n", c, pc);
return 0;
}
/*
arr_a: 0x7fffc29a6510, pointer_a: 0x7fffc29a6510
arr_b: 0x7fffc29a6500, pointer_b: 0x7fffc29a6500
arr_c: 0x7fffc29a64e0, pointer_c: 0x7fffc29a64e0
*/
2.2.2 指针表示多维数组(以二维数组举例)
数组表示:
类型 变量名 [rows] [cols]
指针表示:
类型 (* 指针名)[cols]
#include <stdio.h>
#define ROW 5
#define COL 5
int main(void)
{
int arr[ROW][COL] = {[2] = {1, 2, 3}};
int (*p) [COL] = arr;
//输出数组和指针的地址,发现它们相同
printf("arr_arr: %p, pointer_arr: %p \n", arr, p);
return 0;
}
/*
输出结果:
arr_arr: 0x7fff28009820, pointer_arr: 0x7fff28009820
*/
三、函数中使用数组参数
处理数组的函数实际上用指针作为参数
3.1 定义数组形参
因为函数使用指针作为参数,所以无法知道数组的长度,为了能够让函数知道数组长度,必须在形参中加一个变量 n 来代表长度。
3.1.1 两种表达方式
Ⅰ. 直接使用指针
Ⅱ. 使用数组的形式(其实就是指针)
3.1.1.1 一维数组
例:
#include <stdio.h>
/*
函数用于把数组的每个元素乘 2 再 加 5,n 表示数组长度
*/
void change_arr(int * p, int n); // 使用指针的形式
void change2_arr(int arr[], int n); //使用数组的形式
int main(void)
{
int arr[] = {1, 2, 3, 4};
int i;
printf("before change, arr: \n");
for(i = 0; i < 4; i++)
printf("%d ", arr[i]);
printf("\n");
//调用函数,改变arr
change_arr(arr, 4);
printf("after change, arr: \n");
for(i = 0; i < 4; i++)
printf("%d ", arr[i]);
printf("\n");
}
//指针表示法
void change_arr(int * p, int n)
{
int i;
for(i = 0; i < n; i++)
*(p + i) = 2 * (*p +i) + 1;
return;
}
//数组表示法
void change2_arr(int arr[], int n)
{
int i;
for(i = 0; i < n; i++)
arr[i] = 2 * arr[i] + 5;
return;
}
/*
运行结果:
before change, arr:
1 2 3 4
after change, arr:
7 9 11 13
*/
3.1.1.2 二维数组
#include <stdio.h>
#define ROW 2 //定义行为 2
#define COL 4 //定义列为 4
/*
函数用于把数组的每个元素乘 2 再 加 5,n 表示数组长度
*/
void change_arr(int (* p)[COL]); // 指针表示法
void change2_arr(int arr[][COL]); // 数组表示法
int main(void)
{
int arr[ROW][COL] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
int i, j;
printf("before change, arr: \n");
for(i = 0; i < ROW; i++)
{
for(j = 0; j < COL; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
//调用函数,改变arr
change_arr(arr);
//change2_arr(arr);
printf("after change, arr: \n");
for(i = 0; i < ROW; i++)
{
for(j = 0; j < COL; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
printf("\n");
}
//指针表示法
void change_arr(int (* p)[COL])
{
int i, j;
for(i = 0; i < ROW; i++)
{
for(j = 0; j < COL; j++)
*(*(p + i) + j) = 2 * (*(*(p + i) + j)) + 5;
}
return;
}
//数组表示法
void change2_arr(int arr[][COL]) {
int i, j;
for(i = 0; i < ROW; i++)
{
for(j = 0; j < COL; j++)
arr[i][j] = 2 * arr[i][j] + 5;
}
return;
}
/*
运行结果:
before change, arr:
1 2 3 4
5 6 7 8
after change, arr:
7 9 11 13
15 17 19 21
*/
四、保护数组中的数据
编写一个处理基本类型的(如,int )的函数时,通常都是直接传递数值,只有程序需要在函数中改变该数值时,才需要传递指针;而对于数组则只能传递指针,因为这样做既节省空间又节省时间。
函数中直接使用指针,很有可能会意外修改数组中的数据,为了避免这样的情况,在形式参数中使用const关键字。
4.1 const 关键字
Ⅰ. 创建符号常量,如 const double PI = 3.14159;
Ⅱ. 创建 const 数组,如 const int days [12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
Ⅲ. 创建指向 const 的指针,如 const int * p = arr; //该指针指向数组的第一个元素,但不能通过指针修改该元 素
Ⅳ. 创建 const 指针,如 int * const p = arr; //指向数组首元素的指针,不能指向别处
4.2 const 数组
const 数组不能被修改
例:
#include <stdio.h>
#define SIZE 5
int main(void)
{
const int arr[SIZE] = {};
arr[2] = 4; //改变const数组
return 0;
}
/*
编译不通过:
const1.c: In function ‘main’:
const1.c:8:2: error: assignment of read-only location ‘arr[2]’
arr[2] = 4;
*/
4.3 指向 const 的指针
指向 const 的指针不能修改它指向的值,但可以修改本身。
例:
#include <stdio.h>
#define SIZE 5
int main(void)
{
int arr[SIZE] = {1, 2, 3, 4, 5};
const int * p = arr; //把 p 指向的 int 类型声明为 const
*p = 2; //改变指向 const 的指针的值
/* 编译不通过:
const2.c:9:2: error: assignment of read-only location ‘*p’
*p = 2;
*/
//可以修改 p 本身
printf("p: %p \n", p);
printf("++p: %p \n", ++p);
/* 运行结果:
p: 0x7ffdea913290
++p: 0x7ffdea913294
*/
return 0;
}
4.4 const 指针
const 指针能修改本身,但可以修改它指向的值
例:
#include <stdio.h>
#define SIZE 5
int main(void)
{
int arr[SIZE] = {1, 2, 3, 4, 5};
int * const p = arr;
//修改指针本身
printf("p: %p \n", p);
printf("++p: %p \n", ++p);
/*编译不通过:
const3.c: In function ‘main’:
const3.c:10:2: error: increment of read-only variable ‘p’
printf("++p: %p \n", ++p);
*/
//修改指针指向的值:
p[2] = 10;
printf("%d \n", arr[2]);
/*运行结果:
10
*/
return 0;
}
4.4 const 注意事项
a. 指向 const 的指针可以用 const 数据或者非 const 数据进行初始化或赋值
例:
#include <stdio.h>
int main(void)
{
const int * p;
const int arr_const[] = {1, 2, 3, 4, 5};
int arr[] = {1, 2, 3, 4, 5};
p = arr_const; //使用const数据初始化
printf("p: %p \n", p);
p = arr; //使用非const数据赋值, 这两行顺序无所谓
printf("p: %p \n", p);
p = &arr_const[2];
printf("p: %p \n", p);
p = &arr[3];
printf("p: %p \n", p);
return 0;
}
/*以上内容都合法
运行结果:
p: 0x7ffd256dd4e0
p: 0x7ffd256dd4c0
p: 0x7ffd256dd4e8
p: 0x7ffd256dd4cc
*/
b. 普通指针只能用非 const 数据的地址初始化或赋值。这两个规则十分合理,否则指针就能改变 const 数组中的数据
#include <stdio.h>
int main(void)
{
int * p; //声明普通指针
const int arr_const[] = {1, 2, 3, 4, 5};
int arr[] = {1, 2, 3, 4, 5};
p = arr; //使用非const数据赋值
printf("p: %p \n", p);
p = &arr[3];
printf("p: %p \n", p);
p = arr_const; //使用const数据初始化
printf("p: %p \n", p);
/*警告:
const5.c:16:4: warning: assignment discards ‘const’ qualifier from pointer target type [enabled by default]
p = arr_const; //使用const数据初始化
*/
p = &arr_const[2];
printf("p: %p \n", p);
/*警告:
const5.c:19:4: warning: assignment discards ‘const’ qualifier from pointer target type [enabled by default]
p = &arr_const[2];
*/
return 0;
}
/*这段代码有警告
运行结果:
p: 0x7ffcb6b48520
p: 0x7ffcb6b4852c
p: 0x7ffcb6b48540
p: 0x7ffcb6b48548
*/
c. 在函数实参中也应该遵守上述两个规则, 即如果形参中没有 const 关键字,则不能传递 const 声明的数组。
例:
#include <stdio.h>
#define SIZE 5
void func(int arr[]); //声明非 const 形 参的函数
int main(void)
{
const int arr[] = {};
func(arr); //调用函数
return 0;
}
void func(int arr[])
{
arr[2] = 5;
return;
}
/* 警告:
const6.c: In function ‘main’:
const6.c:9:2: warning: passing argument 1 of ‘func’ discards ‘const’ qualifier from pointer target type [enabled by default]
func(arr);
^
const6.c:4:6: note: expected ‘int *’ but argument is of type ‘const int *’
void func(int arr[]); //声明非 const 形 参的函数
*/
参考书籍:
C Primer Plus (第六版)中文版