C语言简明教程(十二)(数组和指针完整实例详解)

接上一节:函数和指针
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字节,这是它们的地址变化:
C语言简明教程(十二)(数组和指针完整实例详解)

文章图片
指针可以有哪些操作呢?
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变量指定数组大小,用变量方式指定数组大小的数组叫做变长数组,要注意变长数组不能在声明中初始化,并且只能是自动存储类别。除了常用的一维数组,二维数组也常用到,二维数组的的图像:行列矩阵,以及类似树状图,多维数组依此类推,下面是一个二维数组的树状图像解释:
C语言简明教程(十二)(数组和指针完整实例详解)

文章图片
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];

六、数组和指针综合实例【C语言简明教程(十二)(数组和指针完整实例详解)】使用数组主要有两种方式:数组方式和指针方式,这里的指针方式指的是数组指针,一维数组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]); } }

    推荐阅读