接上一节:函数和指针
C语言的数组是个重要的内容,本身并不简单,数组和指针结合可以写的特别复杂,例如int array[3]、int [][3]、int *pt[3]和int (*pt)[3]等,数组指针、指针数组这些要轻松使用都不是一个容易的事情,本文尽力以一个更简单的角度完整描述C语言的数组。
一、数组和指针操作
先来给出数组的简单声明和初始化,声明的简单形式为:
// 数组声明格式:类型名 数组名[数组元素个数]
// 声明并初始化一个整型数组,int型,数组名为array,数组中有6个元素
int array[6] = {23, 52, 63, 30, 12, 19};
for (int i = 0; i < 6; ++i) { // 遍历数组
printf("%d ", array[i]);
}
这里首先讨论一下,数组和指针的联系,数组实际上是一块连续的内存空间,每块内存空间一个数组元素,每个数组的元素的类型都相同,既然是内存空间,那肯定就有地址,数组名代表数组第一个元素的地址,所以数组名是一个指针,对该指针进行取值操作就可以访问数组元素了,通过将地址递增进行访问,例如*(array + 1)表示数组第二个元素的值。
为什么将指针递增就可以访问数组元素了?当然+1这个操作是编译器实现的,不同类型的数组+1的结果是不同的,以上例子中,一个数组元素占4个字节,array+1表示将array这个地址+4,但是如果是char类型的数组,这时候地址就+1,所以平时我们将地址+1其实看起来就是下一个元素的地址了。
1字节8位一般是计算机存储数据的最基本单位,计算机按每1字节8位编址一次,如下图,假设这是一个char数组,一个元素占1字节,这是它们的地址变化:
指针可以有哪些操作呢?
1、取址、赋值和取值,例如int *pt = &number,int *ppt = pt,*ppt。
2、加减运算,指针自加减一个整数可以访问到指定地址的值,数组相减可以计算相隔的距离,之前提到的ptrdiff_t可以存储这个距离值。
3、指针比较,例如大于、小于或等于等。
既然数组名是一个指针,那么可以使用指针表示一个数组,如上面的数组int array[6],可以写成int *array,但是不能使用初始化列表初始化,如int *array = {1, 2}是错误的,为什么呢?因为后者是一个指针,当然只能存地址不能存数组了,但这是一般人可能会失误的写法,如果后者想要有数组的效果,那么就要malloc了。
不过这个小节是为了说明指针和数组的关系,指针是可以加减的,以及在数组中指针加减的原理,下面正式介绍数组。
二、数组数据类型
和上一节介绍指针和函数一样,我们先不要去问指针或函数是什么,而是先定义它们是什么数据类型,首先数组也是一种数据类型,数组数据类型和基本类型没什么区别,只是和函数类型和指针类型一样特别,在数组里要特别注意数组指针,以及数组指针和指针数组的区别,下面是数组数据类型的格式和例子:
// 数组类型标准形式:type ()[row][col]
// 数组类型int ()[]
int (arr_01)[10]; // 一维数组类型,变量为arr_01
int (*pt_arr)[3]; // 二维数组指针类型,变量为pt_arr
typedef int (Array)[4]; // 给一维数组类型起别名,别名为Array
typedef int (*Pta)[4]; // 二维数组指针类型别名,别名为Pta
有没有发现数组和上一节讨论的函数类型类似,数组类型主要由元素类型和数组大小组成,数组讲究维数,一维使用一对中括号,多维则使用多对中括号。
认准以上数组类型的书写方式,数组的各种复杂的写法,其实并不是很复杂,因为有了数据类型,其它都和基本数据类型一样的使用方式,只是还是存在一些不同。
三、数组声明
数组的声明有几种不同的情况,主要分为数组变量声明、函数形参声明和数组指针声明,具体声明实例和分析如下:
1、数组变量声明
// 1、数组变量声明:类型 数组名[数组元素]
// 一维数组
int numbers[6];
// 二维数组,5行6列
int values[5][6];
// 二维变长数组
int row = 2;
int col = 5;
int birds[row][col]; // row和col之后不能再更改,即birds数组大小仍然是固定的
数组的声明含义是,例如int
array[10],创建一个拥有10个空间的数组,每个空间的大小为sizeof(int),数组的首地址为array。指定数组大小可以使用宏定义符号常量,[]方括号内使用整型表达式,对于const变量,C90不允许使用,C99/C11运行使用const变量指定数组大小,用变量方式指定数组大小的数组叫做变长数组,要注意变长数组不能在声明中初始化,并且只能是自动存储类别。
除了常用的一维数组,二维数组也常用到,二维数组的的图像:行列矩阵,以及类似树状图,多维数组依此类推,下面是一个二维数组的树状图像解释:
2、函数形参声明
数组的声明特别是在函数参数中变化多样,其实表示数组的方式就只有两种,数组方式和指针方式,如果看不明白照着上面数组数据类型看就对了,要注意数组类型是很多的,不同的元素类型或者数组大小数组类型是不相同的,数组指针是指向一个数组的指针,它和数组名的使用方式一样。
// 1、一维数组作为函数形参声明的两种方式
void run(int array[]); // 数组方式
void run(int []); // 省去变量名
void run(int *array); // 使用指针方式
void run(int *); // 指针方式省去变量名
// 2、二维数组作为函数形参的声明方式
void print(int array[3][4]); // 数组方式
void print(int [3][4]); // 省去变量名
void print(int **array); // 指针方式
void print(int (*pta)[3][4]); // 数组指针
void print(int **); // 省去变量名
void print(int (*)[3][4]); // 省去变量名
// 3、二维变长数组作为函数形参的声明方式
void show(int array[][4]); // 数组方式
void show(int [][4]); // 省略变量名
void show(int (*pta)[4]); // 指针方式
void show(int (*)[4]); // 省略变量名
// 4、包含数组中的数据使用const修饰
const int vars[3] = {1, 2, 3};
void sleep(const int[]);
四、数组初始化
数组的初始化同样有几种方式,但是较好理解和处理,如下:
// 数组初始化的几种方式
// 1、使用初始化列表,显式指定数组大小
int numbers[5] = {1, 2, 3, 4, 5};
// 2、略去数组大小,让编译器自动计算
int values[] = {1, 2, 3, 4, 5};
// 3、指定初始化
int arrays[8] = {1, 2, [2]=5, [6]=7, [1]=3};
// 4、使用字面量初始化,字面量为{1, 2, 3}
int langs[3] = (int [3]){1, 2, 3};
五、访问数组
上面说到表示一个数组数据类型使用: 类型 [元素个数],如int [3];表示一个数组变量使用int a[3],这是数组表示法,相对应的还有指针表示法。访问数组主要是访问数组中的元素,同样也有数组和指针两种表示方式:
int numbers[] = {1, 3, 5, 7, 9};
// 访问数组元素的两种方式
int start = numbers[3]; // 数组方式访问
int end = *(numbers + 2); // 指针方式访问
// 访问数组元素地址的两种方式
int *pta = numbers + 3;
int *ptb = &numbers[2];
六、数组和指针综合实例
使用数组主要有两种方式:数组方式和指针方式,这里的指针方式指的是数组指针,一维数组int arr[]的数组指针为int *,二维数组的数组指针为int (*pt)[],要注意int**这样形式的使用,它是双重指针,将一般数组作为int**传递会出错,但是使用malloc则是没问题,下面是数组和指针的完整使用实例:
// 涉及数组的函数设计:参数中需要带有数组的大小
// 1、下面两个函数的形参使用数组方式声明
void print_array(const int array[], int length); // 打印数组
void sort(int [], int, int order); // 数组排序,order=0 ASC, order=1 DESC
// 2、以下函数的形参使用数组指针的方式
void log_warm(char (*)[5], int count);
void log_info(int row, int col, int (*array)[row][col]);
void lon_error(const int (*)[6], int row, int col);
void print_07_03(void){
int numbers[] = {9, 5, 1, 17, 3, -9, 5, 96};
int length = sizeof(numbers) / sizeof(numbers[0]);
sort(numbers, length, 0);
print_array(numbers, length);
putchar('\n');
sort(numbers, length, 1);
print_array(numbers, length);
putchar('\n');
char strings[][5] = {"grep", "vim", "find", "more", "man"};
log_warm(strings, 5);
int array[][3] = {
{3, 5, -1},
{2, 9, 1},
{7, 3, 6},
{6, 2, 9}
};
}
void print_array(const int array[], int length){
for(int i = 0;i < length;i++){
printf("%d ", array[i]);
}
}
void sort(int array[], int length, int order){
for (int i = 0; i < length; ++i) {
for (int j = i + 1; j < length; ++j) {
if(order && array[i] < array[j]){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
else if(!order && array[i] > array[j]){
int temp = array[j];
array[j] = array[i];
array[i] = temp;
}
else{
}
}
}
}
void log_warm(char (*strings)[5], int count){
for (int i = 0; i < count; ++i) {
printf("%s ", strings[i]);
}
}