19-2 动态分配数组
除了动态分配单个值外,我们还可以动态分配变量数组。与必须在编译时固定数组大小的固定数组不同,动态分配数组允许我们在运行时选择数组长度(这意味着长度不需要是constexpr)。
作者注:
在本系列课程中,我们将动态分配C风格数组——这是最常见的动态分配数组类型。虽然可以动态分配std::array,但在这种情况下通常更推荐使用非动态分配的std::vector。
要动态分配数组,我们使用new和delete的数组形式(常称为new[]和delete[]):
#include <cstddef>
#include <iostream>
int main()
{
std::cout << "Enter a positive integer: ";
std::size_t length{};
std::cin >> length;
int* array{ new int[length]{} }; // use array new. Note that length does not need to be constant!
std::cout << "I just allocated an array of integers of length " << length << '\n';
array[0] = 5; // set element 0 to value 5
delete[] array; // use array delete to deallocate array
// we don't need to set array to nullptr/0 here because it's going out of scope immediately after this anyway
return 0;
}

由于我们分配的是数组,C++ 知道应使用数组版本的 new 而不是标量版本的 new。本质上,即使 [] 没有紧邻 new 关键字,系统也会调用 new[] 运算符。
动态分配数组的长度类型为std::size_t。若使用非constexpr的int类型,需通过static_cast转换为std::size_t,否则编译器会因视为窄化转换而发出警告。
请注意,由于该内存分配位置与固定数组不同,数组规模可相当庞大。运行上述程序时,分配长度达1,000,000(甚至100,000,000)的数组均不会出错。不妨尝试!正因如此,C++中需要大量内存的程序通常采用动态分配机制。
动态删除数组
删除动态分配的数组时,必须使用数组版本的delete操作符,即delete[]。
这会告知CPU需要清理多个变量而非单个变量。新手程序员在处理动态内存分配时最常见的错误之一,就是在删除动态分配的数组时使用delete而非delete[]。若对数组使用标量版本的delete,将导致未定义行为,例如数据损坏、内存泄漏、程序崩溃或其他问题。
关于数组delete[]常被问及的问题是:“数组delete如何知道要删除多少内存?”答案在于数组new[]会记录分配给变量的内存大小,从而使delete[]能精确删除相应区域。遗憾的是,程序员无法直接获取该大小/长度信息。
动态数组与固定数组几乎完全相同
在第17.8节——C风格数组衰变中,你了解到固定数组保存着数组首个元素的内存地址。你还了解到,固定数组可衰变为指向数组首元素的指针。在此衰变形式下,固定数组的长度不可用(因此也无法通过sizeof()获取数组大小),但除此之外差异甚微。
动态数组初始化时即为指向数组首个元素的指针。因此它同样存在长度不可知的局限性。动态数组的功能与衰变后的固定数组完全相同,唯一区别在于程序员需通过delete[]关键字负责释放动态数组的内存。
初始化动态分配的数组
若需将动态分配的数组初始化为0,其语法非常简单:
int* array{ new int[length]{} };
在 C++11 之前,没有简单的方法将动态数组初始化为非零值(初始化列表仅适用于固定数组)。这意味着你必须遍历数组并显式赋值给每个元素。
int* array = new int[5];
array[0] = 9;
array[1] = 7;
array[2] = 5;
array[3] = 3;
array[4] = 1;
超级烦人!
不过从C++11开始,现在可以使用初始化列表来初始化动态数组了!
int fixedArray[5] = { 9, 7, 5, 3, 1 }; // initialize a fixed array before C++11
int* array{ new int[5]{ 9, 7, 5, 3, 1 } }; // initialize a dynamic array since C++11
// To prevent writing the type twice, we can use auto. This is often done for types with long names.
auto* array{ new int[5]{ 9, 7, 5, 3, 1 } };
请注意,此语法在数组长度与初始化列表之间没有operator=运算符。
为保持一致性,固定数组也可使用统一初始化进行初始化:
int fixedArray[]{ 9, 7, 5, 3, 1 }; // initialize a fixed array in C++11
char fixedArray[]{ "Hello, world!" }; // initialize a fixed array in C++11
显式声明数组的大小是可选的。
数组的动态调整大小
动态分配数组允许你在分配时设置数组长度。然而,C++并未提供内置方法来调整已分配数组的大小。可通过动态分配新数组、复制元素并删除旧数组来规避此限制。但此方法易出错,尤其当元素类型为类时(类对象的创建遵循特殊规则)。
因此,我们建议避免自行操作,改用std::vector替代。
测验时间
问题 #1
编写一个程序:
- 询问用户希望输入多少个名字。
- 动态分配一个 std::string 数组。
- 引导用户逐个输入名字。
- 调用 std::sort 对姓名进行排序(参见 18.1 节——使用选择排序对数组排序,以及 17.9 节——指针运算与下标操作)
- 打印排序后的姓名列表。
std::string 支持通过比较运算符 < 和 > 进行字符串比较,无需手动实现字符串比较功能。
输出结果应与以下示例一致:
How many names would you like to enter? 5
Enter name #1: Jason
Enter name #2: Mark
Enter name #3: Alex
Enter name #4: Chris
Enter name #5: John
Here is your sorted list:
Name #1: Alex
Name #2: Chris
Name #3: Jason
Name #4: John
Name #5: Mark
提醒:
您可以使用 std::getline() 读取包含空格的名称(参见第 5.7 课——std::string 简介)。
提醒:
若要使用 std::sort() 处理数组指针,需手动计算开始和结束位置。
std::sort(array, array + arrayLength);

显示答案
#include <algorithm> // std::sort
#include <cstddef>
#include <iostream>
#include <string>
std::size_t getNameCount()
{
std::cout << "How many names would you like to enter? ";
std::size_t length{};
std::cin >> length;
return length;
}
// Asks user to enter all the names
void getNames(std::string* names, std::size_t length)
{
for (std::size_t i{ 0 }; i < length; ++i)
{
std::cout << "Enter name #" << i + 1 << ": ";
std::getline(std::cin >> std::ws, names[i]);
}
}
// Prints the sorted names
void printNames(std::string* names, std::size_t length)
{
std::cout << "\nHere is your sorted list:\n";
for (std::size_t i{ 0 }; i < length; ++i)
std::cout << "Name #" << i + 1 << ": " << names[i] << '\n';
}
int main()
{
std::size_t length{ getNameCount() };
// Allocate an array to hold the names
auto* names{ new std::string[length]{} };
getNames(names, length);
// Sort the array
std::sort(names, names + length);
printNames(names, length);
// don't forget to use array delete
delete[] names;
// we don't need to set names to nullptr/0 here because it's going to go out
// of scope immediately after this anyway.
return 0;
}

浙公网安备 33010602011771号