What & How & Why

这是本文档旧的修订版!


数组、Vector 与字符串

第 3 章笔记


数组

Intro

  • 数组是一种类型,由多个相同对象组成的序列
定义数组
  • 数组的类型与包含的对象类型不同:数组的类型由对象的类型和自身的长度组成

int a; //int
int b[10]; // int[10]

  • 数组的长度必须是常量表达式
    • 长度需要大于 0(负数是不行的,)
    • 长度需要能转换为 size_t(converted expression,比如浮点就不行)

int x;
std::cin >> x;
//不合法
//类型由编译器决定,但长度要在运行期才能得到
//编译器无法推断出b[x] 的类型
int b[x];
//合法
constexpr short y = 3;
int c[y];

  • C++ 标准不支持运行期数组长度;但在编译器实现中, gcc 和 clang 都可以通过去掉 –pedantic-errors tag 来支持 variable length array。但这样做是有风险的,因为该功能是 complier denpend。除此之外,某些必须在编译器完成的功能也不能使用该类类型做为参数,比如 decltype(b)
数组的初始化
  • 默认初始化:元素都会按照默认的方式来创建(根据C++ 的方式)
  • 聚合初始化
    • 编译器可以自行推断数组长度
    • 给出的元素不足,剩余元素会进行默认初始化

int b[3]; //default int
int c[3] = {1, 2, 3}; // int[3], aggregate init
int cn[] = {1,2, 3}; //int[3]
int d[3] = {1,2} // partially aggregate init, 1,2,0

  • 不能使用 auto:auto 会将 {} 视作 initialization_list
  • 不能复制数组:
    • 初始化不能通过赋值构造
    • 拷贝赋值的结果是拷贝的指向数组首元素的指针
  • 数组作为右值会退化为指针。
  • 使用指针访问数组性能更好
字符串数组的特殊性

//char[6] 简化定义写法,会自动添加 ''\0'' 表示结束
char str[] = "hello";
//char[5] 完整定义写法,但不会为末尾添加 ''\0''
char str[] = {‘h’, 'e', 'l', 'l', '0'};

数组的复杂声明

  • 指针为元素的数组

//a[3] 是有3个元素为指针的数组
int* a[3];

  • 指向数组的指针

//注意括号
//a 是指针,指向 int[3]
int (*a) [3];

  • 数组的引用

//a 是引用,指向 int[3]
int b[3];
int (&a) [3] = b;

引用不支持聚合初始化。C++ 中不支持元素为引用的数组。从概念上来说,引用不是对象。数组要求元素使对象,因此没有元素为引用的数组。

数组中的元素访问

  • 数组中的第一个元素下标为 0
  • 通过下标操作符访问:a[0]
数组访问的细节
  • C++ 中,左值的含义被修改为了 locator value,也就是带地址信息的值(而不是按等号的左右来决定)。
    • 数组是左值,但不能放在等号左边
    • 在等号右部使用(作为右值)时,数组的类型会隐式转换为指向数组头元素的指针。也就是说,指针也可以使用 [] 操作符
    • 当使用 x[y],且 x, y 都是 Built-in 类型时,x[y] 被解析为 *(x+y),也就是转换为指针,再解引用。
    • 需要注意下标越界

int a[3] = {1,2,3};
auto b = a;
//int(&)
decltype(b)
//int*
a;
//int*
b;
//int, *(b+0)
b[0]

可以使用 decltype 来判断表达式是否是左值。

数组和指针

数组到指针的隐式转换

  • 使用数组的时候通常数组会被转化为指针
    • 会丢失一部分信息(比如长度,编译器无法检查,这也是下标越界的原因)

// decay, b 是 int* 类型
int a[3];
auto b = a;
//不会 decay 的范例
//decltype 返回的是数组的类型
// int[3]
decltype(a);
// int size * 3
sizeof(a);

  • 可以通过声明避免隐式转换

// b 是 Int(&)[3] 类型
// 此时长度信息没有丢失
auto& b = a;

  • 不要使用 extern 指针声明数组

//声明数组
//不能使用 extern 指针
extern int array[4];
//illegal
//runtime error

extern int* array;
为什么不能这么做?

很多情况下,长度信息需要反复的修改。如果在声明的时候带上长度信息,那么每次更改 array 的长度都必须同时去声明中改变 array 的长度。因此很多情况下会使用指针替代数组。但 extern 不能直接使用指针代替数组。来看看下面的例子:

//source.cpp
int array[4] = {1,2,3,4};
void fun()
{
    std::cout << array << std::endl;
}
//main.cpp
extern int* array;
void fun();

int main()
{
    fun();
    std::cout << array << std::endl;
}

// output
// 两个 array 的输出不一样
0x7ff718ee3000
0x200000001
问题出在哪里?

  • 编译阶段没有问题,array 会被翻译为 *(array + step)
  • 链接过程中:无论是 main.cpp 和 source.cpp 中的 array 是没有类型的,只有名称。这是因为类型只在编译期有效,如果加了更多的类型信息可能会导致不同翻译单元之间的链接问题。
  • 运行过程中:
    • array 打印出来是一个 16 进制的数。
    • 如果编译期对 array 进行打印,那么 array 是一个地址

</WRAP>