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字节,这是它们的地址变化:

数组的指针递增图解

指针可以有哪些操作呢?

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]);
    }
}
微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?