《C和指针》阅读笔记(7)

本文继续总结《C和指针》第8章的内容,本章的内容由3个部分构成:一维数组、多维数组、指针数组。

一维数组

通常定义一个整型变量使用 int var = 0;

如果想要定义一组整型变量,我们可以这样定义:

int a1 = 0;
int a2 = 1;
int a3 = 2;
int a4 = 3;
int a5 = 4;
// 这里才写了5个变量,我已经极不耐烦了,书写太麻烦

如果有一个"容器"可以把这些整型变量都"装"起来,作为一个整体。打个比方,假设一个整型变量就是一瓶矿泉水,5个整型变量就是5瓶矿泉水,在实际生活中,一件水是12瓶,如果没有箱子将12瓶水包装成一件,显然,搬运水的效率会很低。同样的道理,数组就相当于箱子,提高数据"搬运"的效率。

一维数组定义

语法形式:数据类型 数组名[数组长度];

int arr[10];

定义了一个整型数组,数组的长度为10,数组的每个元素是一个整型。

数组名是一个指针,一个指向类型的指针常量,常量的值是不能改变的。

我们一般将int a = 1;这样的变量叫做标量(可以联想数学上的定义),既然有标量,那么就会有向量,数组就相当于向量,存储的是相同数据类型的元素。

一维数组初始化

一个好的习惯时,在定义数组时进行初始化。例如:

int arr[10] = {0}; // 将数组的所有元素初始化为0

因为无论该数组作为全局变量还是局部变量,不依赖编译器进行初始化总是更加安全的。

一维数组访问

下标访问

一般来说,我们可以通过下标方式对数组的元素进行访问和修改。例如

arr[5] = 15; //表示将数组的第5个元素赋值为15
// 需要注意的是, c语言中数组的计数是从0开始的。

指针访问

int arr[10] = {1,2,3};
int *p_arr = &arr[0];

*(p_arr+5) = 15; //也表示将数组的第5个元素赋值为15

既然下标和指针都能访问和修改数组的元素,那么使用那种方式更好呢?这个没有确切的答案。如果从代码的阅读性和可维护性的角度来讲,下标访问的方式会更直观一些;如果从程序的运行效率的角度来讲,指针访问的方式的效率会更高一些。所以在工程中,需要根据实际情况去权衡。

一维数组参数

我们知道,标量可以作为函数的参数,那么数组是否也能用作函数参数?如果可以,要怎样使用呢?

#include <stdio.h>
#include <stdlib.h>

#define ARRAY_LEN(name, type) (sizeof(name)/sizeof(type)) // 获取数组的长度(元素个数)

// 函数原型声明
int func1(int arr[], int len); // 形参使用数组名
int func2(int *p_arr, int len); // 形参使用指针

// 函数定义
int func1(int arr[], int len)
{
    int i = 0;
    for (i = 0; i < len; ++i)
    {
        printf("数组的第%d个元素的值:%d\n", i,arr[i]);
    }

    return 0;
}

int func2(int *p_arr, int len)
{
    int i = 0;
    for (i = 0; i < len; ++i)
    {
        printf("数组的第%d个元素的值:%d\n", i, p_arr[i]);
    }

    return 0;
}

int main(int argc, char** argv)
{
    int i = 0;
    int a[10] = {0};

    // 给数组赋值
    for (i = 0; i < ARRAY_LEN(a, int); ++i) 
    {
        a[i] = i+1; 
    }

    func1(a, ARRAY_LEN(a, int));
    func2(a, ARRAY_LEN(a, int));

    return 0;
}

从这个例子中,我们可以发现

在函数原型声明时,一维数组形式的形参和指针形式的形参是等价的。本质上都是指针。因为数组名是一个指向某种数据类型的指针常量。

在函数调用时,传入的实参都是相同的,可以说明,在函数声明及使用的情形下,数组名和指针是等价的。(PS:仅仅是在这种使用场景下,可以这样表述。因为数组名和指针是两个不同的概念)

另外,我在定义函数时,第二个参数指定了数组的实际长度,如果不指定长度仅传递数组的地址的话,函数内部对数组的长度是未知的。

多维数组

多维数组定义

语法形式: 数据类型 数组名[][][]…

二维数组

int arr_2d[4][5];

三维数组

int arr_3d[3][4][5];

四维数组

int arr_4d[2][3][4][5];

多维数组初始化

#include <stdio.h>
#include <stdlib.h>


#define ARRAY_LEN(name, type) (sizeof(name)/sizeof(type))

int main(int argc, char** argv)
{
    // 定义一个4行5列的二维数组
    // 降维角度
    // 可以将arr_2d看作是 一个一维数组,该数组有4个元素,每个元素是一个含5元素的一维数组
    // 存储
    // 二维数组的所有元素的存储是按照行主序来进行的,也就是说,将该数组的元素"展平"后,存储为1,2,3,4,5,11,12,13,14,15,21,22,23,24,25,31,32,33,34,35
    // 行主序: 从数组下标的最右边的维度先存储;简单来说就是先行后列,从左到右,从上到下。
    // 有些编程语言中,矩阵是列主序存储的,例如matlab.
    int arr_2d[4][5] = 
    { // 第一层括号
        {1, 2, 3, 4, 5}, // 第二层括号
        {11, 12, 13, 14, 15},
        {21, 22, 23, 24, 25},
        {31, 32, 33, 34, 35},
    };
    // 从花括号的层数也可以看出数组的维度,两层花括号,就是二维数组
    // 在线性代数中,我们将其叫做矩阵(matrix)

    
    // 定义一个三维数组
    // 降维角度
    // 可以将arr_3d看作是 一个一维数组,该数组有3个元素,每个元素是一个4行5列的二维数组
    // 也可以将arr_3看作是 一个3行4列二维数组,该数组的每个元素是一个含5个元素的一维数组
    int arr_3d[3][4][5] = 
    { // 第一层括号
        { // 第二层括号
            {0x01, 0x02, 0x03, 0x04, 0x05}, // 第三层括号
            {0x11, 0x12, 0x13, 0x14, 0x15},
            {0x21, 0x22, 0x23, 0x24, 0x25},
            {0x31, 0x32, 0x33, 0x34, 0x35},
        },
        {
            {0x101, 0x102, 0x103, 0x104, 0x105},
            {0x111, 0x112, 0x113, 0x114, 0x115},
            {0x121, 0x122, 0x123, 0x124, 0x125},
            {0x131, 0x132, 0x133, 0x134, 0x135},
        },
        {
            {0x201, 0x202, 0x203, 0x204, 0x205},
            {0x211, 0x212, 0x213, 0x214, 0x215},
            {0x221, 0x222, 0x223, 0x224, 0x225},
            {0x231, 0x232, 0x233, 0x234, 0x235},
        }
    };
    // 三层花括号就是三维数组


    // 定义一个四维数组
    int arr_4d[2][3][4][5] = 
    { // 第一层括号
        { // 第二层括号
            { // 第三层括号
                {0x0001, 0x0002, 0x0003, 0x0004, 0x0005}, // 第四层括号 
                {0x0011, 0x0012, 0x0013, 0x0014, 0x0015}, 
                {0x0021, 0x0022, 0x0023, 0x0024, 0x0025}, 
                {0x0031, 0x0032, 0x0033, 0x0034, 0x0035}, 
            },
            {
                {0x0101, 0x0102, 0x0103, 0x0104, 0x0105}, 
                {0x0111, 0x0112, 0x0113, 0x0114, 0x0115}, 
                {0x0121, 0x0122, 0x0123, 0x0124, 0x0125}, 
                {0x0131, 0x0132, 0x0133, 0x0134, 0x0135}, 
            },
            {
                {0x0201, 0x0202, 0x0203, 0x0204, 0x0205}, 
                {0x0211, 0x0212, 0x0213, 0x0214, 0x0215}, 
                {0x0221, 0x0222, 0x0223, 0x0224, 0x0225}, 
                {0x0231, 0x0232, 0x0233, 0x0234, 0x0235}, 
            },
        },
        {
            {
                {0x1001, 0x1002, 0x1003, 0x1004, 0x1005}, 
                {0x1011, 0x1012, 0x1013, 0x1014, 0x1015}, 
                {0x1021, 0x1022, 0x1023, 0x1024, 0x1025}, 
                {0x1031, 0x1032, 0x1033, 0x1034, 0x1035}, 
            },
            {
                {0x1101, 0x1102, 0x1103, 0x1104, 0x1105}, 
                {0x1111, 0x1112, 0x1113, 0x1114, 0x1115}, 
                {0x1121, 0x1122, 0x1123, 0x1124, 0x1125}, 
                {0x1131, 0x1132, 0x1133, 0x1134, 0x1135}, 
            },
            {
                {0x1201, 0x1202, 0x1203, 0x1204, 0x1205}, 
                {0x1211, 0x1212, 0x1213, 0x1214, 0x1215}, 
                {0x1221, 0x1222, 0x1223, 0x1224, 0x1225}, 
                {0x1231, 0x1232, 0x1233, 0x1234, 0x1235}, 
            },
        },
    };


    printf("2d len:%ld\n", ARRAY_LEN(arr_2d, int));
    printf("3d len:%ld\n", ARRAY_LEN(arr_3d, int));
    printf("4d len:%ld\n", ARRAY_LEN(arr_4d, int));

    return 0;
}

多维数组访问

多维数组元素的访问和二维数组一样,也可以通过下标方式来访问和修改。

以四维数组为例,

int arr_4d[2][3][4][5] = 
    { // 第一层括号
        { // 第二层括号
            { // 第三层括号
                {0x0001, 0x0002, 0x0003, 0x0004, 0x0005}, // 第四层括号 
                {0x0011, 0x0012, 0x0013, 0x0014, 0x0015}, 
                {0x0021, 0x0022, 0x0023, 0x0024, 0x0025}, 
                {0x0031, 0x0032, 0x0033, 0x0034, 0x0035}, 
            },
            {
                {0x0101, 0x0102, 0x0103, 0x0104, 0x0105}, 
                {0x0111, 0x0112, 0x0113, 0x0114, 0x0115}, 
                {0x0121, 0x0122, 0x0123, 0x0124, 0x0125}, 
                {0x0131, 0x0132, 0x0133, 0x0134, 0x0135}, 
            },
            {
                {0x0201, 0x0202, 0x0203, 0x0204, 0x0205}, 
                {0x0211, 0x0212, 0x0213, 0x0214, 0x0215}, 
                {0x0221, 0x0222, 0x0223, 0x0224, 0x0225}, 
                {0x0231, 0x0232, 0x0233, 0x0234, 0x0235}, 
            },
        },
        {
            {
                {0x1001, 0x1002, 0x1003, 0x1004, 0x1005}, 
                {0x1011, 0x1012, 0x1013, 0x1014, 0x1015}, 
                {0x1021, 0x1022, 0x1023, 0x1024, 0x1025}, 
                {0x1031, 0x1032, 0x1033, 0x1034, 0x1035}, 
            },
            {
                {0x1101, 0x1102, 0x1103, 0x1104, 0x1105}, 
                {0x1111, 0x1112, 0x1113, 0x1114, 0x1115}, 
                {0x1121, 0x1122, 0x1123, 0x1124, 0x1125}, 
                {0x1131, 0x1132, 0x1133, 0x1134, 0x1135}, 
            },
            {
                {0x1201, 0x1202, 0x1203, 0x1204, 0x1205}, 
                {0x1211, 0x1212, 0x1213, 0x1214, 0x1215}, 
                {0x1221, 0x1222, 0x1223, 0x1224, 0x1225}, 
                {0x1231, 0x1232, 0x1233, 0x1234, 0x1235}, 
            },
        },
    };

arr_4d数组的存储空间有120个int型的元素,这120个int在内存中是连续分配的,每个占4个字节。只不过我们在定义多维数组时,给它人为的加上一个逻辑,四维([2][3][4][5])。因为c语言中数组是行主序的,

printf("arr_4d[0][0][0][1]=0x%04x\n", arr_4d[0][0][0][1]); // arr_4d[0][0][0][1]=0x0002
printf("arr_4d[1][1][2][3]=0x%04x\n", arr_4d[1][1][2][3]); // arr_4d[1][1][2][3]=0x1124

多维数组的数组名比一维数组就相对复杂一点,但是也没啥可怕的

printf("%p\n", arr_4d);				// int (*)[3][4][5]
printf("%p\n", arr_4d[0]);			// int (*)[4][5]
printf("%p\n", arr_4d[0][0]);		// int (*)[5]
printf("%p\n", arr_4d[0][0][0]);	// int *
// 不同维度的数组名,代表的指针的含义是不同的。
// int * 是 指向int的指针,偏移单位是1个int
// int(*)[5] 是 指向一维数组的指针,该数组的长度为5,指针的偏移单位为5个int
// int(*)[4][5] 是 指向二维数组的指针,该数组的长度为20,指针的偏移单位为20个int
// int (*)[3][4][5] 是 指向三维数组的指针,该数组的长度为60, 指针的偏移单位为60个int

// 这四种形式的数组名的地址都是相同的
// 例如,(注意每次执行,地址的值都不想同,但4个值是相同的)
0x7ffc2f3b8790
0x7ffc2f3b8790
0x7ffc2f3b8790
0x7ffc2f3b8790

printf("%p\n", arr_4d);		// 0x7ffe28d4c1b0
printf("%p\n", arr_4d[1]);	// 0x7ffe28d4c2a0

//2a0-1b0=f0, 也就是十进制的240,因为每个int占4字节,所以刚好就是相差60个元素的地址。
//也就是说,在数组的第4维的角度来看,可以将arr_4d[2][3][4][5]看作是一个一维数组,里面有两个元素,每个元素是一个3*4*5的数组
// arr_4d[0][0][0] 相当于指向一维数组的指针,即int *

再看一个通过指针访问的例子

printf("arr_4d[1][1][2][3]=0x%04x\n", *(*(*(*(arr_4d+1)+1)+2)+3)); // 等价于arr_4d[1][1][2][3]

显然,对于高维数组,通过指针访问数组的元素,可读性很差。

再看一个另外一个指针访问的例子:

int *p = &arr_4d[0][0][0][0];
printf("arr_4d[1][1][2][3]=0x%04x\n", *(p+60+20+10+3)); // 等价于arr_4d[1][1][2][3]
// 这个例子是从数组元素存储的角度,来访问指定位置的元素。
// 因为第4维的偏移单位是60,所以 1*60 = 60
// 第3维的偏移单位是20, 所以 1*20 = 20
// 第2维的偏移单位是5, 所以 2*5 = 10
// 第1维的偏移单位是1, 所以 3*1 = 3

多维数组参数

前面我们看到了类似下面的指针形式:

printf("%p\n", arr_4d);				// int (*)[3][4][5]
printf("%p\n", arr_4d[0]);			// int (*)[4][5]
printf("%p\n", arr_4d[0][0]);		// int (*)[5]
printf("%p\n", arr_4d[0][0][0]);	// int *

这就是所谓的数组指针,即指向数组的指针。

在将一维数组作为形参的函数原型中,即可以将该参数写成数组的形式,也可以将该参数写成指针的形式。

但是对于多维数组,只有在第1维的时候可以既写成数组或写成指针,在高维时,必须以数组的形式来表明数组的长度信息。

假设,一个函数的形参是一个指向包含5个整型的数组的指针,那么函数的原型可以写成如下形式:

void func(int (*p)[5]);
void func(int arr[][5]); // 二者都可,我习惯用第一种形式,个人觉得更顺眼!

指针数组

前面介绍的数组的元素都是int类型,一般数组的元素可以是基本数据类型,但若是要表达更复杂的数据类型,有时我们虽然可以通过可见的结构体,来构造结构体数组表达复杂的数据类型,但这似乎还是不够灵活,特别是对于不可见的数据结构时。使用指针来表示不可见的数据结构,似乎简单可行。

指针数组定义

指针数组就是 数组的元素 都是 指向某种类型的指针。

简单的文件描述符hash就可通过指针数组来实现,数组的索引代表文件描述符fd,数组的元素(指针)代表一个队列,队列中的节点存储相应的数据。

指针数组初始化

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
    int i = 0;
    int *p_arr[10] = {NULL};

    for (i = 0; i < 10; ++i)
    {
        printf("p[%d]=%p\n", i, p_arr[i]);
    }
    return 0;
}

关注我

我的公众号二维码,欢迎关注

QQ讨论群:679603305

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值