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 sp
unique_ptr up
空智能指针,可以指向类型为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(args) 返回一个 shared_ptr, 指向一个动态分配的类型为T的对象,使用 args 初始化该对象
shared_ptr p(q) 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 u1
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 p(q) q 必须指向 new 分配的内存,且能够转换为 T*
shared_ptr p(u) u 为 unique_ptr指针。p 从 u 接管了对象的所有权,并将 u 置为空
shared_ptr p(q, d) d 为代替 delete 的可调用对象
shared_ptr p(p2, d) 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 w
weak_ptr w(sp)
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 创建一个内存分配器
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/

graph TD cond01{有等号} --> |N| cond02{有括号} cond01{有等号} --> |Y| op01[拷贝初始化] cond02 --> |N| op02[默认初始化] cond02 --> |Y| cond03{提供了值} cond03 --> |N| op03[值初始化] cond03 --> |Y| op04[直接初始化-含列表初始化]

默认初始化

没有显式指定初始值,并且也没有调用初始化器。

对于内置类型(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);
posted @ 2022-12-22 21:36  keep-minding  阅读(32)  评论(0)    收藏  举报