3.数组容器
1. 数组
数组是一种类似于标准库类型 vector 的数据结构,数组也是存放类型相同的对象的容器。这些对象本身没有名字,需要通过其所在位置访问。与 vector 不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。对某些特殊的应用来说使用数组程序的运行性能会较好,但是相应的也损失了一些灵活性。
1.1 定义和初始化内置数组
数组是一种复合类型,语法结构如下:
unsigned cnt = 42; // 不是常量表达式
constexpr unsigned sz = 42; // 常量表达式
int arr[10]; // 10个元素,数组的元素被默认初始化
int *parr[sz]; // 数组维度必须为常量
注意: 定义数组的时候必须指定数组的类型,不允许使用 auto 关键字由初始值的列表推断类型。另外和 vector 一样,数组的元素应为某个数据类型,因此不存在引用的数组。
1.显式初始化数组元素
如果在声明时没有指明维度,编译器会根据初始值的数量计算并推断出来;如果指明了维度,则用提供的初始值初始化靠前的元素,剩下的元素被初始化为默认值。初始值的数量不应该超出维度的大小,否则会发生错误。
const unsigned sz = 3;
int ial[sz] = {0,1,2}; // 含有3个元素的数组
int a2[] = {0,1,2};
int a3[5] = {0,1,2}; // 等价于a3[5] = {0,1,2,0,0};
2.字符数组的特殊性
字符数组有一种额外的初始化方式,使用字符串字面值对数组初始化。注意字符串的结尾处还有一个空字符。
const char a4[7] = "Daniel";
3.不允许拷贝和赋值
int a[] = {0,1,2};
int a2[] = a; // 错误
a2 = a; // 错误
4.理解复杂的数组声明
和 vector 一样,数组能存放大多数类型的对象。定义存放指针的数组比较简单和直接,但是定义数组的指针或数组的引用就比较复杂。
int * ptrs[10]; // ptrs是含有10个整型指针的数组
int & refs[10] = /*?*/; // 错误,不存在引用的数组
int (*P)[10] = &arr; // P指向一个含有10个整数的数组
int (&Ref)[10] = arr; // Ref引用一个含有10个整数的数组
1.2 访问数组元素
与标准库类型 vector 和 string 一样,数组的元素也能使用范围for语句或下标运算符来访问。数组的索引从 0 开始。
int arr[] = {1, 2, 3, 4, 5};// 使用下标运算符访问数组的特定元素for (int i = 0; i < 5; ++i) {cout << arr[i] << " "; // 输出数组的每个元素}
与 vector 和 string 一样,数组的下标是否在合理范围内由程序员自己检查。
1.3 指针和数组
在 C++ 语言中,指针和数组有非常紧密的联系。数组有一个特性,在很多用到数组名字的地方编译器会自动地将其替换成为一个指向数组首元素的指针。
string num[] = {"one","two"};
string *p = &num[0]; // p指向num的第一个元素
string *p2 = num; // 等价于p2 = &num[0];
注意: 指向数组元素的指针拥有更多功能,指向数组元素的指针可以执行大多数迭代器运算。这些运算包括解引用、比较、递增、与整数相加等。
int ia[] = { 0,1,2,3,4,8 };
int last = *(ia + 4); // 指向数组的第五个元素
int last2 = *ia + 5; // 表示数组第一个元素的值加上5
1.标准库函数begin和end
C++11 新标准在数组中引用了两个名为 begin 和 end 的函数,两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数,正确的使用形式是将数组作为它们的参数,返回的是对应的指针类型。如下所示:
int ia[] = {1,2,3,4,5};
int *beg = begin(ia);
int *last = end(ia);
2.下标和指针
int ia[] = {0,1,2,3,4,6};
int *p = &ia[2]; // 指针p指向数组ia的第三个元素
int j = p[1]; // p[1]指的是数组中的第四个元素
int k = p[-2]; // p[-2]指的是数组中的第一个元素
1.4 C风格字符串
1.C标准库函数
函数 | 含义 |
---|---|
strlen(p ) | 返回 p 的长度,空字符不计算在内 |
strcmp(p1, p2) | 比较 p1 和 p2 的相等性,相等返回 0,p1 > p2 返回正值,p1 < p2 返回负值 |
strcat(p1, p2) | 将 p2 附加到 p1 之后,返回 p1 |
strcpy(p1, p2) | 将 p2 拷贝给 p1,返回 p1 |
2.strcmp函数
要想比较两个 C 风格字符串需要调用strcmp函数,此时比较的就不再是指针了。如果两个字符串相等,strcmp 返回 0,如果前面的字符串较大返回正值,后面的字符串大返回负值。
char cha1[100] = "hello ";
const char cha2[] = "world";
if (strcmp(cha1, cha2) == 0) // 字符串比较cout << "cha1 == cha2";
else if (strcmp(cha1, cha2) < 0)cout << "cha1 < cha2" << endl;
elsecout << "cha1 > cha2";
cout << endl;
3.strcat函数和strcpy函数
连接或拷贝 C 风格字符串也与标准库 string 对象的同类操作差别很大,正确的方法是使用strcat函数和strcpy函数。
char cha1[100] = "hello ";
const char cha2[] = "world";
strcat_s(cha1, cha2); // 连接
cout << cha1 << endl;
strcpy_s(cha1, cha2); // 拷贝
cout << cha1 << endl;
1.5 与旧代码的接口
很多 C++ 程序在标准库出现之前就已经写成了,因此它们没用到 string 和 vector 类型,但现代 C++ 程序不得不与那些充满了数组和 C 风格字符串代码衔接,为了使这工作简单易行,C++ 专门提供了一组功能。
1.混用string对象和C风格字符串
std::string str = "Hello, ";
const char* cStr = "world!";
std::string result = str + cStr; // 使用加法运算符连接 string 和 C 风格字符串
std::cout << "Result: " << result << std::endl;
2.c_str函数
string 提供了一个名为c_str的成员函数,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组。指针的类型是const char*。
string myString = "Hello, world!";
// 使用 c_str() 返回一个指向以空字符结尾的 C 风格字符串的指针
const char* cStyleString = myString.c_str();
cout << "Content of C-style string: " << cStyleString << endl;
3.使用数组初始化vector对象
不允许使用 vector 对象初始化数组,但可以使用数组来初始化 vector 对象。
int arr[] = {1, 2, 3, 4, 5}; // 声明并初始化数组
// 使用数组初始化 vector 对象
// arr首元素,arr + sizeof(arr) / sizeof(arr[0])尾元素的下一位置
vector<int> vec(arr, arr + sizeof(arr) / sizeof(arr[0]));
// 打印 vector 中的元素
for (int num : vec) {cout << num << " ";
}
cout << endl;
2. 多维数组
严格来说,C++ 语言中没有多维数组,通常所说的多维数组其实是数组的数组。
2.1 多维数组的初始化与引用
1.多维数组的初始化
允许使用花括号括起来的一组值初始化多维数组,这点和普通数组一样。
int ia[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11},
};
类似于一维数组,在初始化多维数组时并非所有元素的值都必须包含在初始化列表中,如果只想初始化每一行的第一个元素,如下:
int ia[3][4] = {{0},{4},{8}}; // 初始化每行第一个元素
int ix[3][4] = {0,1,2,3}; // 显示初始化第一行,其它元素执行值初始化
2.多维数组的下标引用
可以使用下标运算符来访问多维数组的元素,此时数组的每个维度对应一个下标运算符,如果表达式含有的下标运算符数量和数组的维度一样多,该表达式的结果将是给定类型的元素。如果表达式含有的下标运算符数量比数组维度小,则表达式的结果将是给定索引处的一个内层数组。
int arr[3][3] = {{1, 2, 3},{4, 5, 6},{7, 8, 9}};
cout << "Element at index [1][2]: " << arr[1][2] << endl; // 输出元素值
for (auto& row : arr) {for (auto value : row) {cout << value << " ";}cout << endl;
}
2.2 多维数组与指针
1.指针和多维数组
当程序使用多维数组的名字时也会自动将其转换成指向数组首元素的指针。因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针。随着C++新标准的提出,通过使用 auto 和 decltype 就能避免在数组前面加上一个指针类型了。
#include <iostream>
void printArray(int (*arr)[3], int rows) {for (int i = 0; i < rows; ++i) {for (int j = 0; j < 3; ++j) {std::cout << arr[i][j] << " ";}std::cout << std::endl;}
}
int main() {int arr[2][3] = {{1, 2, 3},{4, 5, 6}};
// 传递多维数组给函数,arr会自动转换为指向第一个子数组的指针
printArray(arr, 2);
return 0;
}
// 参数 int (*arr)[3] 表示一个指向包含3个整数的数组的指针。
// 通过传递 arr 到 printArray 函数,arr 会自动转换为指向第一个子数组的指针,允许函数对整个多维数组进行遍历。
2.类型别名简化多维数组指针
读、写和理解一个指向多维数组的指针是一个让人烦不胜烦的工作,使用类型别名能让这项工作变得简单一点,如下所示:
// 类型别名简化多维数组指针
int ia[4][4] = {{1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7},
};
using array = int[4];
for (array* p = ia; p != ia + 4; p++){for (int* q = *p; q != *p + 4; ++q){cout << *q <<" ";}cout << endl;
}