参考资料:
- 《C++ Primer》第5版
- 《C++ Primer 习题集》第5版
3.5 数组(P101)
数组类似于 vector
,不同点在于数组的大小固定不变,在某些情况下性能较好,但灵活性较差。
3.5.1 定义和初始化内置数组(P101)
数组是一种复合类型,声明形如 a[d]
,其中 d
是数组的维度。维度必须大于 0 且必须是一个常量表达式。默认情况下,数组的元素被默认初始化。
如果在函数内部定义了内置类型的数组,则默认初始化会令数组含有未定义的值。
定义数组时必须指定数组类型,不允许通过 auto
推断元素类型。不存在引用的数组。
显式初始化数组元素
可以对数组进行列表初始化,此时可以忽略数组的维度:
- 如果声明数组时没有指明维度,编译器会根据初始值的数量计算出维度。
- 如果指明了维度,那么初始值的数量不能超过维度,如果初始值的数量小于维度,则剩下的元素被初始化为默认值。
内置类型的默认值为
0
,类类型执行类的默认初始化。
字符数组的特殊性
可以使用字符串字面值对字符数组进行初始化,此时需要留意字符串字面值结尾的空字符 '\0'
。
不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作初始值;不能用数组为其他数组赋值:
int a[] = {0 ,1, 2};
int a1[] = a; // 错误
a1 = a; // 错误
理解复杂的数组声明
默认情况下,修饰符由内向外、由右向左依次绑定:
int *ptrs[10]; // 含有10个int*的数组// 指向含有10个int元素的数组的指针
int (*Parray)[10] = &arr;
// 这里遇到一个问题,&arr会被编译器识别成int (*)[10]类型,而arr会被编译器识别成int *类型
3.5.2 访问数组元素(P103)
数组的元素可以用范围 for
语句或下标运算符进行访问。使用数组下标时,通常将其定义为 size_t
类型。size_t
是与机器相关的无符号类型,定义在 cstddef
头文件中。
后面会提到,下标运算符可以接受负数,所以这里是“通常”使用
size_t
数组使用的下标运算符是由 C++ 语言直接定义的,可以用于任何数组类型。
检查下标的值
下标大于 0
而小于数组大小。
3.5.3 指针和数组(P105)
在很多用到数组名字的地方,编译器会自动将其替换为一个指向数组首元素的指针。
string nums[] = {"one", "two", "three"};
string *p = nums; // 等价于p = &nums[0]
使用 decltype
关键字时,上述转换不会发生;使用 sizeof
运算符时,数组名代表整个数组;使用 &数组名
时,得到的是指向整个数组的指针。
指针也是迭代器
vector
迭代器支持的运算,指向数组元素的指针全部支持。
标准库函数begin
和end
C++11 新标准引入了 begin
和 end
函数,参数均为数组:
int ia[] = {0, 1, 2, 3, 4};
int *b = begin(ia), *e = end(id);
指针运算
两个指针相减的类型为 ptrdiff_t
,是定义在 cstddef
中的机器相关的有符号类型。
指针的比较同样适用于空指针和所指对象非数组的指针。
解引用和指针运算的交互
int ia[] = {0, 1, 2, 3, 4};
int last = *(ia+4);
下标和指针
对数组执行下标运算实质上是对指向数组元素的指针执行下标运算:
int i = ia[2];int *p = ia;
i = *(p+2);
内置的下标运算可以处理负值;标准库类型的下标运算使用的下标必须为无符号数( ::size_type
),不能处理负值。
3.5.4 C风格字符串(P109)
尽量不要使用 C 风格字符串,因为其容易引发安全漏洞。
字符串字面值是 C 风格字符串的实例。C 风格字符串是一种约定俗成的写法,把字符串存放在字符数组中,并以空字符结尾。
C标准库string函数
上述函数定义在 cstring
头文件中,函数的参数均为指向以空字符结尾的字符数组的指针,但函数本身并不负责验证这一点。所以如果传入一个不以空字符结尾的字符数组的指针,可能会发生错误。
比较字符串
目标字符串的大小由调用者决定
在执行 C 风格字符串的拼接和复制操作时,必须保证目标字符串有足够的空间。
3.5.4 与旧代码的接口(P111)
混用string
对象和C风格字符串
任何出现字符串字面值的地方,都可以用以空字符结尾的字符数组来替代:
- 允许使用以空字符结尾的字符数组为
string
对象初始化或赋值。 - 在
string
的加法运算中允许其中一个运算对象为以空字符结尾的字符数组。
如果想用 string
对象初始化一个字符数组,可以使用 string
类中的 c_str
函数:
string s("hello");
const char *str(s); // 错误
const char *str(s.c_str()); // 正确,但不推荐
c_str
函数的返回值类型是 const char*
,指向一个以空字符结尾的字符数组,该数组所存的内容恰好与 string
对象中的内容一致。需要注意的是,c_str
返回的数组可能会因为其 string
对象的改动而失效,所以最好在执行 c_str
后对字符数组进行拷贝:
char *str = new char[20];
strcpy(str, s.c_str);
使用数组初始化 vector
对象
允许用数组初始化 vector
对象,需要给出数组的首元素地址和尾后地址:
int ia[] = {0, 1, 2, 3, 4};
vector<int> iv(begin(ia), end(ia));
vector<int> iv1(ia, ia+1); // 使用数组中的部分元素初始化vector
现代 C++ 程序应尽量使用 vector
、string
和迭代器,而避免使用数组、C 风格字符串和指针。
3.6 多维数组(P112)
严格来说,C++ 没有多维数组,只有数组的数组:
int ia[3][4]; // ia为维度为3的数组,其中的元素是维度为4的int数组
多维数组的初始化
下面的两种写法是等价的:
int ia[2][2] = {{0, 1},{2, 3}
};
int ia[2][2] = {0, 1, 2, 3};
在列表初始化多维数组时,不必列出所有元素,未列出的元素执行默认值初始化。下面两种写法的含义不同:
int ia[2][2] = {{0}, {1}
};
int ia[2][2] = {0, 1}
多维数组下标的引用
使用范围for
语句处理多维数组
int ia[3][4] = {};
for(const auto &row : ia){for(auto column : row){cout << col << endl;}
}
注意,外层循环的控制变量必须声明成引用类型,否则控制变量将被设置为指针类型。
指针和多维数组
多维数组实际上数组的数组,所以数组名转换得来的指针指向第一个内层数组:
int ia[3][4] = {};
int (*p)[4] = ia;
使用 auto
和 decltype
可以避免复杂的数组指针:
for(auto p=begin(ia);p!=end(ia);p++){for(auto q=begin(*p);q!=end(*p);q++){cout << *q << endl;}
}
类型别名简化多维数组指针
using int_array int[4];
int_array *p = ia;
cltype` 可以避免复杂的数组指针:
for(auto p=begin(ia);p!=end(ia);p++){for(auto q=begin(*p);q!=end(*p);q++){cout << *q << endl;}
}
类型别名简化多维数组指针
using int_array int[4];
int_array *p = ia;