Post

手撕指针第二章 | 数组

手撕指针第二章 | 数组

1. 声明和访问

数组是 C++ 表示内存中对象序列最基本的方式。如果你用到的只是内存中一个固定大小、固定元素类型的序列,那么数组完全满足你的要求。

假设有类型 TT[size] 的含义是「包含 sizeT 类型元素的数组」。元素的索引范围是 0size-1

你可以使用下边运算符和指针访问数组元素。下标引用和间接访问表达式是等价的。

数组中元素的数量(即数组的边界)必须是常量表达式,如果你希望边界可变,最好使用 vector

越界访问数组是一种未定义的行为,而且很有可能会引发严重的程序错误。在 C++ 语言中,运行时边界检查既不常见,也无法保证。

1
2
3
4
5
6
7
8
9
10
11
int main() {
    int a;
    a = 0;
    int arr[] = {1, 2, 3, 4};
    printf("%d\n", *arr); /* 1 */
    printf("%p\n", arr); /* 0x7ff7b82f8110 */
    printf("%d\n", arr[0]); /* 1 */
    printf("%d\n", *(arr + 1)); /* 2 */

    return 0;
}

在上述代码中,我们把变量 a 称为标量,因为他是个单一的值,类型是一个整数。 而 arr 是一个数组,它是一些值的集合,下标用于标识该集合中某个特定的值。 如 arr[0] 表示 arr 中的第一个值,arr[2] 表示 arr 中的第三个值。

arr[0] 的类型是整型,那么 arr 的类型又是什么呢? 在 C 语言中,几乎所有使用数组名的表达式中,数组名的值是一个指针常量,也就是数组第一个元素的地址,它的类型取决于数组元素的类型。

但,请不要得出「数组和指针是相同的」的结论。指针只是一个标量值,数组具有确定数量的元素(不过编译器并不会对下标值进行有效性检查)。 编译器用数组名来记住这些属性,只有当数组名在表达式当中使用时,编译器才会为它产生一个指针常量。

这个值时指针常量,而不是指针变量,常量的值是不能修改的。不难理解,指针常量所指向的是内存中数组的起始位置,如果修改这个指针变量,唯一可行的操作就是把整个数组移动到内存的其他位置。但是,在程序完成链接之后,内存中数组的位置就是固定的。所以,当程序运行时,再想移动数组就为时已晚了。

C++ 的内置数组本质上是语言的一种底层功能,我们常常用数组来实现标准库 vectorarray 等更高层级上的、行为定义更好的数据结构。 数组不能执行赋值操作,一旦需要,数组名就会隐式地转换成指向数组首元素的指针

要避免在接口中使用数组,因为数组名隐式转换是 C 代码和 C 风格的 C++ 代码中很多错误的根源。 如果是在自由存储1,切记一定要在最后一次使用数组之后把对应指针 delete[] 掉。

最简单可靠的办法是用资源句柄(string vector unique_ptr)控制自由存储上的数组的生命周期。

如果你是静态2分配数组或者是在栈上分配数组,一定不要 delete[] 他。

2. 数组和指针

  1. newdelete 直接控制其生命周期的对象。注意,自由存储并不等价于堆。 ↩︎

  2. 在全局作用域或名字空间作用域中声明的对象以及在函数或类中声明的 static 成员,只被创建并初始化一次,并且知道程序结束之前都活着。 ↩︎

This post is licensed under CC BY 4.0 by the author.