12.2动态数组
之前学到new和delete是可以创建/删除一个对象的,但是我们有时候需要创建一组对象和删除一组对象。现在我们可以使用容器来这么做,vector,list,不过new和delete也提供了创建/删除一组对象的操作。
创建动态数组
使用new创建动态数组
我们可以使用如下的方式用new创建动态数组。
int* p = new int[10];
using TenArr = int[10];
int* p = new TenArr;
需要注意的是,动态创建的数组返回的是首个元素的指针,而不是数组类型,因此p不能使用范围for和begin( p ),end( p ).
如果在创建动态数组的时候,没有显式的初始化,则使用默认初始化。
我们可以使用()显式的初始化,也可以使用初始化列表进行初始化。但是不可以在()中写初始化列表。
int *p = new int[10];//默认初始化
int *p = new int[10]();//显式的初始化
int *p = new int[10]{1,2,3,4,45,56};//列表初始化
int *p - new int[10]({1});//错误
我们可以创建一个大小为0的动态数组。
int a[0];//error
int *a = new int[0];//正确
使用new创建的大小为0的数组,返回的指针不指向任何对象,相当于一个尾后迭代器。
智能指针和动态数组
C++标准库提供了可以管理new分配的数组的unique_ptr版本。
在创建这个版本的unique_ptr,需要在尖括号内写上[]
unique_ptr<int[]> p(new int[10]);
因为p保存的是一个数组,所以我们不能使用成员访问符来访问其中的元素。即“.”和"->",但是我们可以使用下标运算符来访问。p[1]=xxx;
当删除该智能指针时,p调用delete [] 来回收内存。
管理动态数组的unique_ptr操作。
C++标准库没有提供来管理new的动态数组的shared_ptr版本。
但是我们还是可以使用,不过要自己提供一个代替delete[]的函数,由于C++标准库本身没有管理动态数组类型的shared_ptr,所以我们不能使用下标运算符来访问其中的元素,而是只能使用p+n的方式来访问元素。
删除动态数组
对于使用普通指针来管理的动态数组类型,我们使用delete [] ptr;来删除这个动态数组。
注意,delete []ptr要求ptr必须是动态数组,或者nullptr。
而delete ptr。要求ptr,必须是new出来的一个对象,或者nullptr。
如果对动态数组使用delete ptr。或者对一个对象使用delete []ptr这都是不符合预期的,这样的操作可能会让程序在运行期间发生错误。
智能指针管理的动态数组当引用计数为0时,为维护的指针调用delete []
练习
12.23
在拷贝的时候可以考虑使用strcpy,但是vs2017不推荐使用这个函数,编译会报错
。
记住C风格字符串是以’\0’结尾的
void combine_two_str_v1(const char* str1,const char* str2) {
size_t len = 0;
size_t str1_len = 0;
const char* temp = str1;
while (*temp!='\0') {
++temp;
++len;
}
temp = str2;
str1_len = len;
while (*temp!='\0') {
++temp;
++len;
}
char* char_arr = new char[len + 1];
char* temp_char_arr = char_arr;
temp = str1;
while (*temp!='\0') {
*temp_char_arr = *temp;
++temp;
++temp_char_arr;
}
temp = str2;
while (*temp!='\0') {
*temp_char_arr = *temp;
++temp;
++temp_char_arr;
}
//在结尾处还需要加上\0
*temp_char_arr = '\0';
cout << char_arr << endl;
delete [] char_arr;
}
void combine_str_v2(const string& str1,const string& str2) {
char* char_arr = new char[str1.size() + str2.size() + 1];
char* temp_char_arr = char_arr;
for (auto const &item:str1) {
*temp_char_arr = item;
++temp_char_arr;
}
for (auto const& item:str2) {
*temp_char_arr = item;
++temp_char_arr;
}
*temp_char_arr = '\0';
cout << char_arr << endl;
delete[]char_arr;
}
12.24
使用一个足够大的数组来存入字符串,在VS2017中,输入的字符超过了数组所能存储的大小也没有报错,但是这可能造成了未知的隐患。
char* char_arr = new char[10];
while (cin>>char_arr) {
cout<<char_arr<<endl;
}
delete[]char_arr;
12.25
delete []pa;
12.2.2 allocator类
使用new创建的对象,分配空间和初始化对象是绑定在一起的。然而我们有时会在初始化之后为对象再一次的赋值,这就让第一次的初始化没有任何意义。
另一种情况是,我们创建了一个动态数组,但是使用时并没有使用那么多对象,而是使用了部分对象,这也让没有使用的对象的初始化没有意义。
还有一种情况是,某一些类对象没有默认构造函数,那么我们在new一个类的时候,就必须提供初值。
为了更加的灵活的分配内存空间和初始化对象,C++提供了allocator类型,它是一个泛型类型,其将分配空间和初始化对象分开了。也将销毁对象和回收内存给分开了。
allocator类型定义在memory头文件中。
其操作如下,四个函数分别时,分配空间,回收空间,初始化对象,销毁对象。
缺点是,我们初始化和销毁对象只能一个一个的初始化和销毁。
所以标准库为allocator提供了两个伴随算法,copy和fill,用于批量的初始化对象。
在具体使用allocator时,我们的步骤是
1.开辟空间
2.初始化对象
3.销毁对象
4.回收空间
下面的练习就是一个很好的例子。
练习
可以看到这种把分配内存和初始化分开的方法,非常的繁琐,但是胜在更加灵活
//创建一个对象,用于分配内存
std::allocator<int> a;
//分配内存
size_t total_size = 10;
auto p = a.allocate(total_size);
//为前5个初始化
size_t init_size = 5;
//批量的初始化对象
std::uninitialized_fill_n(p,init_size,10);
//auto q = p;
//使用分配的空间
for (size_t i = 0; i < init_size;++i) {
cout << *(p + i) << endl;
}
//一个一个的销毁对象
for (size_t i = 0; i < init_size;++i) {
a.destroy(p+i);
}
//回收空间
a.deallocate(p,total_size);