C++_Primer12.dynamic_memory
动态内存
dynamic memory
动态分配的对象的生存期与它们在哪里创建无关,只有当显式地被释放时才会销毁。
标准库定义了两个智能指针类型来管理动态分配的对象。当一个对象应该被释放时,指向他的智能指针可以确保自动地释放它。
除了静态内存,和栈内存,每个程序还拥有一个内存池,称为自由空间(free store)或堆(heap),用来存储动态分配的对象。
程序使用动态内存出于以下三种原因之一:
- 程序不知道自己需要使用多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据
动态内存和智能指针
动态内存的管理是由一对运算符完成的:new 和 delete, 分别用来分配和释放内存空间。
忘记释放内存会造成内存泄露;提前释放内存会产生引用非法内存的指针。
为了更容易和更安全地使用动态内存,新标准提供了两种智能指针:
- shared_ptr 允许多个指针指向同一个对象
- unique_ptr 则“独占”指向的对象
标准库还定义了 weak_ptr 的伴随类,是一种弱引用,指向 shared_ptr 所管理的对象。
以上三种类型都定义在 memory 头文件中。
shared_ptr 类
shared_ptr, unique_ptr 都支持的操作:
| 操作 | 说明 |
|---|---|
| shared_ptr unique_ptr |
空智能指针,可以指向类型为T的对象 |
| p | 将p用作一个条件判断,空为 false,否则为 true |
| *p | 解引用 |
| p->mem | 等价于 (*p).mem |
| p.get() | 返回p中保存的指针。若智能指针释放了其对象,返回的指针指向的对象也就消失了 |
| swap(p, q) p.swap(q) |
交换p和q中的指针 |
shared_ptr 独有的操作:
| 操作 | 说明 |
|---|---|
| make_shared |
返回一个 shared_ptr, 指向一个动态分配的类型为T的对象,使用 args 初始化该对象 |
| shared_ptr |
p是 shared_ptr q 的拷贝。此操作会递增 q 中的计数器。 q中的指针必须能转换为 T* |
| p = q | p,q 都是 shared_ptr,所保存的指针必须能相互转换。 此操作会递减p的引用计数,递增q的引用计数。 若p的引用计数变为0,则将其管理的原内存释放 |
| p.unique() | 若 p.use_count() 为1,返回 true,否则返回 false |
| p.use_count() | 返回计数量,可能很慢,主要用于调试 |
unique_ptr 独有的操作:
| 操作 | 说明 |
|---|---|
| unique_ptr unique_ptr<T, D> u2 |
空 unique_ptr,可以指向类型为 T 的对象,u1 会使用 delete 来释放它的指针; u2 会使用一个类型为 D 的可调用对象来释放它 |
| unique_ptr<T, D> u(d) | 空 unique_ptr,指向类型为 T 的对象,用类型为 D 的对象 d 代替 delete |
| u = nullptr u.release() |
释放 u 指向的对象,将 u 置空 |
| u.reset() u.reset(q) u.reset(nullptr) |
释放 u 指向的对象。 如果提供了内置指针 q,令 u 指向这个对象;否则将 u 置空。 |
// 指向一个整型,值为42 的智能指针
shared_ptr<int> p3 = make_shared<int>(42);
// 指向10个'9'字符串的智能指针
shared_ptr<string> p4 = make_shared<string>(10, '9');
// 指向一个值初始化的int,值为0
shared_ptr<int> p5 = make_shared<int>();
// 通常使用 auto 定义对象来保存智能指针
auto p6 = make_shared<vector<string>>();
引用计数递增:
- 拷贝一个 shared_ptr
- 用一个 shared_ptr 初始化另一个 shared_ptr
- 作为参数传递给一个函数
- 作为函数的返回值
引用计数器递减:
- 赋予新值
- shared_ptr 被销毁(比如局部 shared_ptr 离开其作用域)
到底用计数器还是其他数据结构记录有多少共享对象,完全由标准库的具体实现来决定。
shared_ptr 在无用后让然保留的一种可能情况是,把 shared_ptr 存放在一个容器中,然后重排了容器,从而不再需要某些元素,此时应该使用 erase 删除那些不再需要的 shared_ptr 元素。
在多个对象间共享数据
由容器创建的元素在容器被销毁时销毁。当拷贝一个 vector 时,原 vector 和副本 vector 的元素是相互分离的:
vector<string> v1;
{ // 新作用域
vector<string> v2 = {"a", "an", "the"};
v1 = v2;
} // v2 被销毁,其中的元素被销毁
// v1 有三个元素,是原来 v2 中元素的拷贝
当两个对象共享底层数据时,我们不能单方面销毁底层数据:
Blob<string> b1;
{ // 新作用域
Blob<string> b2 = {"a", "an", "the"};
b1 = b2;
} // b2 被销毁,但其中的元素不能被销毁
// b1 指向最初由 b2 创建的元素
使用智能指针管理共享数据:
class StrBlob {
public:
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il);
// 添加和删除元素
void push_back(std::string& s) { data->push_back(s); }
void pop_back();
// 元素访问
std::string& front();
std::string& back();
size_type size() { return data->size(); }
bool empty() { return data->empty(); }
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type i, const std::string& msg) const;
}
StrBlob::StrBlob(): data (make_shared<std::vector<std::string>>()) {}
StrBlob::StrBlob(std::initializer_list<std::string> il):
data (make_shared<std::vector<std::string>>(il)) {}
StrBlob::check(size_type i, const std::string& msg) const {
if (i >= data->size()){
throw out_of_range(msg);
}
}
StrBlob::front(){
check(0, "front on empty StrBlob");
return data->front();
}
StrBlob::back(){
check(0, "back on empty StrBlob");
return data->back();
}
StrBlob::pop_back(){
check(0, "pop_back on empty StrBlob");
return data->pop_back();
}
16章(p610):
template <typename T> class Blob {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
// 构造函数
Blob();
Blob(std::initializer_list<T> il);
// Blob 中元素数目
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// 添加和删除元素
void push_back(const T& t) { data->push_back(t); }
// 移动版本
void push_back(T& t) { data->push_back(std::move(t)); }
void pop_back();
// 元素访问
T& back();
T& operator[](size_type i);
private:
std::shared_ptr<std::vector<T>> data;
void check(size_type i, const std::string& msg) const;
}
直接管理内存
使用 new 和 delete 管理内存。将不能依赖类对象拷贝、赋值和销毁操作的任何默认定义,因此使用智能指针的程序更容易编写和调试。
int* pi = new int; // new 分配的空间返回一个指针,默认初始化
string* ps = new string; // 初始化为空的字符串指针,默认初始化
int* pi1 = new int(1024); // 分配空间的同时赋值,直接初始化
string* ps1 = new string(10, '9'); // 10个'9'组成的字符串,直接初始化
vector<int>* pv = new vector<int>{0,1,2,3}; // 直接初始化
string* ps2 = new string; // 默认初始化为空 string
string* ps3 = new string(); // 值初始化为空 string
int* pi2 = new int; // 默认初始化,*pi2 的值未定义
int* pi3 = new int(); // 值初始化为0
对于定义了自己的构造函数的类类型(比如 string)来说,要求值初始化是没有意义的,不管采用什么形式,对象都会通过默认构造函数来初始化。
但对于内置类型,两种形式的差别就大了;值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值则是未定义的。
auto
如果提供了初始化器(带括号),则可以使用 auto 来从初始化器推断对象类型。
由于编译器用初始化器的类型来推断要分配的类型,所以只有当括号中仅有单一初始化器时才可以使用 auto
auto p1 = new auto(obj); // p 指向一个与 obj 类型相同的对象;并用 obj 初始化对象
auto p2 = new auto{a,b,c}; // 错误:括号中只能有单个初始化器
p1 类型为 int*
const 对象
const int* pci = new const int(1024);
const string* pcs = new const string;
内存耗尽
如果一个程序用光了它所有可用的内存,new 表达式会失败,抛出一个类型为 bad_alloc 的异常。
可以改变使用 new 的方式来阻止他抛出异常:
int* pi = new (nothrow) int; // 如果失败,返回一个空指针
这种形式的 new 为定位 new(placement new),定位 new 表达式允许向 new 传递额外的参数。
nothrow 是标准库定义的对象;bad_alloc 和 nothrow 都定义在头文件 new 中。
delete 释放动态内存
销毁给定的指针指向的对象;释放对应的内存。
delete p;
传递给 delete 的指针必须指向动态分配的内存,或是一个空指针。
释放一块非 new 分配的对象,或将相同的指针释放多次,其行为未定义。
delete 之后指针变成了空悬指针(dangling pointer)。未初始化指针的所有缺点空悬指针都有。
如果在 delete 之后仍然需要保留该指针,可以将 nullptr 赋予指针。
坚持只用智能指针
只有在没有任何智能指针指向时,智能指针才会自动释放
shared_ptr 和 new 结合使用
// 使用 new 返回的指针来初始化只能指针
shared_ptr<int> p(new int(42)); // 正确,直接初始化
shared_ptr<int> p1 = new int(1024); // 错误:必须使用直接初始化形式
内置指针不能隐式转换为智能指针。反之亦然。
| 操作 | 说明 |
|---|---|
| shared_ptr |
q 必须指向 new 分配的内存,且能够转换为 T* |
| shared_ptr |
u 为 unique_ptr指针。p 从 u 接管了对象的所有权,并将 u 置为空 |
| shared_ptr |
d 为代替 delete 的可调用对象 |
| shared_ptr |
p 是 p2 的拷贝,d 代替 delete |
| p.reset() p.reset(q) p.reset(q, d) |
若 p 是唯一指向其对象的 shared_ptr,会释放此对象,p置空。 令 p 指向 q 所指内容。 用 d 代替 delete |
q 为内置指针类型,u为 unique_ptr
默认情况下,智能指针使用 delete 释放资源,所以一般只能用指向动态内存的指针对其初始化。
也可以用自己的操作代替 delete。
可以用内置指针对智能指针初始化,但他们之间不能进行隐式转换:
shared_ptr<int> clone(int i){
return new int(p); // 错误
return shared_ptr<int>(new int(p)); // 正确
}
void process(shared_ptr<int> ptr){
// 使用 ptr
} //ptr 离开作用域,被销毁
// 正确:
shared_ptr<int> p(new int(42));
process(p);
int i = *p;
// 错误:
int* x(new int(1024)); // 普通指针
process(x); // 错误:int* 不能转换为 shared_ptr<int>
process(shared_ptr<int>(x)); // 合法,但内存会被释放;x 会被执行 delete 操作
int j = *x; // 未定义:x 是一个空悬指针;
使用内置指针访问智能指针指向的对象是危险的,因为无法预计对象何时会被销毁。
get 返回内置指针
智能指针类型定义了一个 get 函数,返回内置指针。
是为了需要向不能使用智能指针的代码传递一个内置指针时设计的。
使用 get 返回的指针的代码不能 delete 它。
将另一个智能指针绑定到 get 返回的指针上是错误的,虽然编译器不会报错:
shared_ptr<int> p(new int(42));
int* q = p.get();
{
// 两个独立的 shared_ptr 指向相同的内存
shared_ptr<int>(q);
} // 程序块结束时 q 被销毁,其指向的内存被释放
int foo = *p; // 未定义:p 指向的内存已被释放
get 用来将指针的访问权限传递给代码,只有确定不会 delete 指针的情况下才能使用 get。
永远不要用 get 初始化另一个智能指针,或为另一个智能指针赋值。
reset
p = new int(1024); // 错误:不能将内置指针赋予 shared_ptr
p.reset(new int(1024)); // 正确:p 指向一个心对象
reset 会更新引用计数,可能会释放 p 指向的对象。
reset 常与 unique 一起使用来控制多个 shared_ptr 共享的对象。
在操作底层对象之前应当检查当前对象是否唯一,若不唯一,应当创建一份新的拷贝:
if (!p.unique()){
p.reset(new string(*p));
}
*p += newVal;
智能指针和异常
// 即使因异常推出函数,智能指针也会自动释放
// 而内置指针不会自动释放,可能造成内存泄露
void f1(){
shared_ptr<int> sp(new int(42));
// 这段代码抛出一个异常,且未被捕获
} // 函数结束时 shared_ptr 自动释放内存
void f2(){
int* p(new int(42));
// 这段代码抛出一个异常,且未被捕获
delete p;
}
智能指针规范
- 不使用相同的内置指针初始化(或reset)多个智能指针
- 不 delete get() 返回的指针
- 不使用 get() 初始化或 reset 另一个智能指针
- 如果使用 get() 返回的指针,记住当最后一个对应的智能指针销毁后,该指针就无效了
- 如果智能指针管理的资源不是 new 分配的内存,需要传递给它一个删除器
struct destination;
struct connection;
connection connect(distination*);
void disconnect(connection);
void end_connection(connection* p) { disconnection(*p); }
void f(destination& d){
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// 使用链接
...
// 当 f 退出时(或由于异常而退出),connection 会被正确关闭
}
习题 12.14
使用 shared_ptr 管理 connection 函数
struct destination {
destination(const string& ip, const string& port): m_ip(ip), m_port(port) {}
string m_ip, m_port;
}
struct connection{
connection(const string& ip, const string& port): m_ip(ip), m_port(port) {}
string m_ip, m_port;
}
connection connect(destinction* pdest){
shared_ptr<connection> sp_conn(new connection(pdest->m_ip, pdest->m_port));
// use sp_conn;
cout << "use_count: " << sp_conn.use_count()
<< "\tto:" << pdest->ip << ":" << pdest->port << endl;
return * sp_conn;
}
void disconnect(connection conn){
// close connection
cout << "close connection: " << conn.m_ip << ":" << conn.m_port;
}
void end_connection(connection* p){
disconnect(*p);
}
void f(distination* dest){
connection connect(dest);
shared_ptr<connection> sp_conn(&connect, end_connection);
// lambda 版本
shared_ptr<connection> sp_conn(&connect, [](connection* p){ disconnect(*p) });
}
void test(){
destination dest("192.168.5.7", "8080");
f(&dest);
}
unique_ptr
与 shared_ptr 不同,没有类似 make_shared 的标准库函数返回一个 unique_ptr。
定义 unique_ptr 时,需要将其绑定到一个 new 返回的指针上:
unique_ptr<double> p1;
unique_ptr<int> p2(new int(42));
unique_ptr 不支持普通的拷贝或赋值操作:
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); // 错误:不支持拷贝
unique_ptr<string> p3;
p3 = p2; // 错误:不支持赋值
可以使用 release 和 reset 将指针所有权从一个 unique_ptr 转移给另一个 unique:
unique_ptr<string> p1(new string("Tret"));
unique_ptr<string> p2(p1.release());
unique_ptr<string> p3(new string("Fox"));
p3.reset(p2.release());
release 操作只是将指针置空,并不会释放所指内存:
p2.release(); // 错误,p2 不会释放内存,而且我们丢失了指针
auto p = p2.release(); // 正确,但之后需要手动 delete(p)
传递和返回 unique_ptr 参数
虽然不能拷贝 unique_ptr,但是可以拷贝或赋值一个将要被销毁的 unique_ptr:
unique_ptr<int> clone(int p){
return unique_ptr<int>(new int(p));
}
unique_ptr<int> clone(int p){
unique_ptr<int> ret(new int(p));
// ...
return ret;
}
函数将要执行完时,编译器会执行一种特殊的“拷贝”。
向 unique_ptr 传递删除器
向 unique_ptr 传递删除器的方式:
unique_ptr<objT, delT> p(new objT, fcn);
fcn 是 delT 类型的对象。
例子:
connection c = connect(&dest);
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
weak_ptr
一种不控制所指对象生存周期的智能指针,指向一个 shared_ptr 管理的对象。绑定时不会改变 shared_ptr 的引用计数。
| 操作 | 说明 |
|---|---|
| weak_ptr |
|
| weak_ptr |
|
| w = p | p 可以是一个 shared_ptr 或 weak_ptr。赋值后 w 和 p 共享对象 |
| w.reset() | 置空 |
| w.use_count() | 返回对应的 shared_ptr 计数 |
| w.expired() | 若 use_count() 为0,返回 true,否则返回 false |
| w.lock() | 如果 expired() 为 true,返回一个空 shared_ptr; 否则返回一个指向 w 的对象的 shared_ptr |
// 创建 weak_ptr 时需要一个 shared_ptr 初始化它
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);
由于对象可能不存在,不能用 weak_ptr 直接访问对象,而必须调用 lock:
if (shared_ptr<int> np = wp.lock()){
...
}
习题12.19
定义自己版本的 StrBlobPtr,更新 StrBlob 类,加入恰当的 friend 声明及 begin 和 end 成员。
class StrBlob
{
public:
friend class StrBlobPtr;//声明friend
StrBlobPtr begin();
StrBlobPtr end();
StrBlob();//默认构造函数
StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)){}
StrBlob(string il):data(make_shared<vector<string>> (il)){}
typedef vector<string>::size_type size_type;//定义类型别名,方便使用
//定义函数,返回大小
size_type size() const
{
return data->size();
}
//判断vector<string>是否为空
bool empty()
{
return data->empty();
}
//向vector<string>中加入元素
void pushback(const string &s)
{
data->push_back(s);
}
//访问函数,应首先调用check()
string& front()
{
check(0,"front on empty StrBlob");
return data->front();
}
string& back()
{
check(0,"back on empty StrBlob");
return data->back();
}
void popback()
{
check(0,"pop_back on empty StrBlob");
data->pop_back();
}
private:
shared_ptr<vector<string>> data;//指向vector<string>的智能指针
void check(size_type i,const string &msg) const//若访问元素的大小大于data的size,输出错误信息
{
if (i > data->size())
{
throw out_of_range(msg);//抛出该out_of_range异常,表示不在范围之内
}
}
};
class StrBlobPtr
{
public:
StrBlobPtr():curr(0){}//构造函数,将curr设定为0
StrBlobPtr(StrBlob &a, size_t sz = 0):wptr(a.data),curr(sz){}//构造函数,将StrBlob的智能指针与此类中的weak_ptr绑定
string& deref() const
{
auto p =check(curr,"deference past end");
return (*p)[curr];
}
StrBlobPtr& incr()
{
auto p =check(curr,"deference past end");
++curr;
return * this;
}
private:
shared_ptr<vector<string>> check(size_t i,const string& msg) const//检查函数,返回一个vector<string>的智能指针
{
auto ret = wptr.lock();//检查对象是否还存在
if(!ret)
{
throw runtime_error("未绑定");
}
if (i >= ret->size())
{
throw out_of_range(msg);
}
return ret;
}
weak_ptr<vector<string>> wptr;//定义弱智能指针
size_t curr;//设立游标,表示下标
};
StrBlobPtr StrBlob::begin()
{
return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end()
{
return StrBlobPtr(*this, data->size());
}
动态数组
大多数应用应该使用标准库容器而不是动态分配的数组。
容器有默认的拷贝、赋值和析构操作,并拥有更好的性能。
而动态数组必须定义自己版本的操作。
C++ 语言和标准库提供了两种一次分配一个对象数组的方法:
- 使用 new 分配内存
- allocator 类允许将分配和初始化分离。
分配一个动态数组会得到一个元素类型的指针。由于分配的内存不是一个数组类型,因此不能对动态数组调用 begin 或 end。这些函数使用数组维度来返回首尾元素的指针。同样的原因,也不能用范围 for 语句来处理动态数组。
new 和数组
int* pia = new int[get_size()];
放括号中的大小必须是整型,但不必是常量。
用类型别名分配数组:
typedef int arrT[42];
int* p = new arrT;
初始化
int* pia = new int[10]; // 10个未初始化的 int
int* pia2 = new int[10](); // 值初始化为0
string* psa = new string[10]; // 10个空 string
string* psa2 = new string[10](); // 10个空 string
int* pia3 = new int[10]{0,1,2,3}; // 用列表中的值初始化,剩余元素进行值初始化
如果列表中元素数大于所需数目,new 表达式失败,不会分配任何内存,并抛出异常 bad_array_new_length.(new 头文件中)
虽然可以用空括号对数组中元素进行值初始化,但不能在括号中给出初始化器。这意味着不能用 auto 分配数组。
分配一个空的动态数组是合法的:
char arr[0]; // 错误
char* cp = new char[0]; // 正确,但 cp 不能解引用
释放动态数组
delete p; // 释放动态对象
delete [] pa; // 释放动态数组
动态数组释放时,数组中元素按逆序销毁。
方括号表示此指针指向一个对象数组的第一个元素。如果未写方括号,其行为未定义。
使用智能指针管理动态数组
unique_ptr<int[]> up(new int[10]);
for (size_t i = 0; i != 10; ++i) {
up[i] = i;
}
up.release(); // 自动用 delete[] 销毁动态数组
与 unique_ptr 不同,shared_ptr 不直接支持管理动态数组。因为 shared_ptr 默认使用 delete 作为删除器,所以必须提供自己定义的删除器:
shared_ptr<int> sp(new int[10], [](int* p) { delete[] p; });
for (size_t i = 0; i != 10; ++i) {
* (sp.get() + i) = i; // 用 get 获取内置指针
}
sp.reset();
shared_ptr 未定义下标运算符
习题 12.23
编写一个程序,连接两个字符串字面常量,将结果保存在一个动态分配的char数组中。重写这个程序,连接两个标准库string对象。
#include <iostream>
#include <new>
#include <string>
#include <memory>
using namespace std;
void test1223();
int main(int argc, char** argv) {
test1223();
}
void test1223() {
string a = "Hello";
string b = "World";
size_t len = a.size()+b.size();
// 直接分配动态数组,需要引入 string.h 头文件
char* pc(new char[len]);
strcat(pc, a.c_str());
strcat(pc, b.c_str());
cout << "pc:\t" << pc << endl;
delete[] pc;
// 智能指针版
unique_ptr<char[]> uc(new char[len+1]);
for (size_t i = 0; i != a.size(); ++i) {
uc[i] = a[i];
}
for (size_t i = a.size(); i != len; ++i) {
uc[i] = b[i - a.size()];
}
cout << uc.get() << endl;
uc.release();
}
习题 12.24
编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长输入。测试你的程序,输入一个超出你分配的数组长度的字符串。
#include <iostream>
#include <new>
#include <string>
using namespace std;
int main() {
const size_t len = 8;
char* pc(new char[len]);
char c;
size_t i = 0;
while (cin >> c) {
if (i != len - 1){
pc[i] = c;
++i;
}
}
delete[] pc;
return 0;
}
allocator 类
利用 allocator 可以实现内存分配和对象构造分离。(付出一定开销)
| 操作 | 说明 |
|---|---|
| allocator |
创建一个内存分配器 |
| a.allocate(n) | 分配一段原始的、未构造的内存,保存 n 个类型为 T 的对象 |
| a.deallocate(p, n) | 释放先前 p 分配的内存,n 必须为 p 申请内存时的大小; 调用 deallocate 之前,用户必须对每个在这块内存中创建的对象调用 destroy |
| a.construct(p, args) | 在 p 指向的位置构造一个对象; args 将被传递给 T 的构造函数 |
| a.destroy(p) | 此算法对 p 指向的对象执行析构函数 |
allocator 分配的内存是未构造的,需要使用 construct 构造对象:
allocator<string> alloc; // 可以分配 string 的 allocator 对象
auto const p = alloc.allocate(n); // 分配 n 个未初始化的 string
auto q = p;
alloc.construct(q++);
alloc.construct(q++, 10, 'c');
alloc.construct(q++, "hi");
// 释放每个元素
while (q != p) {
alloc.destroy(--q);
}
// 释放 p;p 不能为空,n 必须与分配时大小一样
alloc.deallocate(p, n);
拷贝和填充未初始化内存的算法
| 操作 | 说明 |
|---|---|
| uninitialized_copy(b,e,b2) | 从迭代器 b 和 e 指定的输入范围中拷贝元素到迭代器 b2 指定的位置。 b2 内存必须足够大能容纳拷贝的元素。 |
| uninitialized_copy_n(b,n,b2) | 从迭代器 b 指定的位置开始,拷贝n个元素到 b2位置 |
| uninitialized_fill(b,e,t) | 在迭代器 b 和 e 指定的原始内存范围中创建对象,对象的值均为 t 的拷贝 |
| uninitialized_fill_n(b,n,t) | 从迭代器 b 的位置创建 n 个对象,值为 t 的拷贝。 |
创建一块动态内存,大小是一个 vector<int> 对象的两倍,并将该对象拷贝到动态内存的前一半中,后一半用给定值填充:
allocator<int> alloc;
auto p = alloc.allocate(vi.size() * 2);
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
uninitialized_fill_n(q, vi.size(), 5);
使用标准库:文本查询程序
读入一个文本文件,在其中查找指定单词,给出单词在文件中出现的次数,及所在行与该行内容的列表。如果单词在一行中出现多次,则只列出一次。行按照升序输出。
思路:
- 程序读取文件时,需要记住单词出现的每一行。因此需要逐行读取输入文件,并将每行分解为独立的单词
- 使用一个 vector
保存整个输入文件的一份拷贝。每一行保存为 vector 中的一个元素,需要打印时,用行号作为下标提取行文本 - 使用一个 istringstream 来将每行分解为单词。
- 使用一个 set 保存每个单词在输入文本中出现的行号。这保证了每行只出现一次且行号按升序保存
- 使用一个 map 将每个单词与其行号 set 关联起来。
#include <iostream>
#include <new>
#include <memory>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <fstream>
#include <sstream>
using namespace std;
int main(int argc, char** argv) {
string filename("text.txt");
string query("the");
shared_ptr<vector<string>> lines(new vector<string>());
ifstream ifs(filename);
shared_ptr<map<string, shared_ptr<set<int>>>> word_map(new map<string, shared_ptr<set<int>>>());
string line;
int idx(0);
while (getline(ifs, line)) {
lines->push_back(line);
istringstream iss(line);
string word;
while (iss >> word) {
if (word_map->find(word) != word_map->end()) {
(*word_map)[word]->insert(idx);
} else {
shared_ptr<set<int>> spset(new set<int>{idx});
word_map->insert(make_pair(word, spset));
}
}
++idx;
}
// test: output all lines
for (auto it(lines->begin()); it != lines->end(); ++it) {
cout << *it << endl;
}
// test: output all word_map
for (auto it(word_map->begin()); it != word_map->end(); ++it) {
cout << it->first << ":\t";
for (auto it_set(it->second->begin()); it_set != it->second->end(); ++it_set) {
cout << *it_set << ",";
}
cout << endl;
}
// output query result
if (word_map->find(query) != word_map->end()) {
auto qset(*(*word_map)[query]);
cout << "found " << qset.size() << " times of \"" << query << "\":" << endl;
for (auto it(qset.begin()); it != qset.end(); ++it) {
int line_idx = *it;
cout << "[line: " << line_idx << "]:\t" << (*lines)[line_idx] << endl;
}
} else {
cout << "found no \"" << query << "\"" << endl;
}
return 0;
}
附录:初始化详解
初始化:
https://www.cnblogs.com/caidi/p/9679673.html
https://www.jianshu.com/p/4c8d2fd4f094
markdown 图表:
http://rstyro.gitee.io/blog/2021/06/28/Markdown流程图语法示例/
https://www.jianshu.com/p/839f2c9c830e/
默认初始化
没有显式指定初始值,并且也没有调用初始化器。
对于内置类型(int,double,bool),如果定义在语句块外(全局变量)则初始化为0;如果是局部变量则值未定义。
对于类类型的变量(string或自定义类),如果该类没有默认构造函数,则会引发错误。如果类内的内置类型成员未在类内被初始化,其值也是未定义的。因此,建议为每个类都定义一个默认构造函数(=default),并给内置类型成员赋默认值。
int a;
Blob b1;
int* p0(new int); // 默认初始化一个动态内存的 int,然后地址直接初始化给 p
int* p = new int; // 默认初始化一个动态内存的 int,然后地址拷贝给 p
Blob* pb1 = new Blob; // 同上
Blob b2(); // 类类型的值初始化调用默认构造器,即默认初始化
Blob* pb2 = new Blob(); // 同上
值初始化
调用了初始化器,但没有提供值;使用默认值初始化。
类类型的值初始化调用默认构造器,即默认初始化。
int a = int(); // 没有 ‘int a();’ 的写法,这样表示声明了一个函数
// 先值初始化一个临时变量,然后再拷贝初始化
vector<string> vs(10);
int* pi = new int(); // 先值初始化,再把指针拷贝给 pi
string* ps = new string();
直接初始化
调用初始化器并提供了初始值(相比拷贝初始化而言;不必创建临时变量)
int a(2);
string s("123");
vector<int> v2(v1);
Blob b2(b1);
int* pi = new int(5);
Blob* pb2 = new Blob(b1);
拷贝初始化
使用了"=";先创建临时变量,然后拷贝给当前变量。
对于内置类型基本上没有影响,而对于类类型则会花费较多时间。
类类型在动态分配内存时,没有拷贝初始化。
int a = 12;
int b = int(10);
string s = string("123");
Blob b2 = Blob(b1);

浙公网安备 33010602011771号