C++ Primer记录_第十二章

第十二章 动态内存

  • 目前为止使用的对象都有着严格定义的生存期。使用内存池中静态区或栈区。
    • 全局对象在程序启动时分配,在程序结束时销毁。
    • 对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁。
    • 局部static对象在第一次使用前分配,在程序结束时销毁。
  • 动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。使用内存池中自由空间或堆区。

12.1 动态内存与智能指针

  • new,在动态内存中为对象分配空间并返回一个指向该对象的指针。
  • delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
  • 动态内存的使用很容易出问题:
    • 内存泄露。
    • 引用非法内存的指针。
    • 等。
  • 新的标准库提供了两种智能指针:
    • shared_ptr允许多个指针指向同一个对象。
    • unique_ptr则“独占”所指向的对象。
    • weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。
  • 这三种类型都定义在memory头文件中。

12.1.1 shared_ptr类

  • 类似vector,智能指针也是模板。
shared_ptr<string> p1;    // shared_ptr, 可以指向string
shared_ptr<list<int>> p2; // shared_ptr, 可以指向int的list
  • 默认初始化的智能指针中保存着一个空指针。
//如果p1不为空,检查它是否指向一个空string
if (p1 && p1->empty())
{
    *p1 = "hi"; //如果p1指向一个空string,解引用p1,将一个新值赋予string
}
shared_ptr和unique_ptr都支持的操作解释
shared_ptr sp空智能指针,可以指向类型为T的对象
unique_ptr upstd::string
p将p用作一个条件判断,若p指向一个对象,则为true
*pFString
p->mem等价于(*p).mem
p.get()返回p中保存的指针,要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了。
swap(p, q)交换p和q中的指针。
p.swap(q)同上。
shared_ptr独有的操作解释
make_shared(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化此对象。
shared_ptr p (q)p是shared_ptr q的拷贝,此操作会递增q中的计数器,q中的指针必须能转换为T*
p = qp和q都是shared_ptr,所保存的指针必须能相互转换,此操作会递减p的引用计数,递增q的引用计数,若p的引用计数变为0,则将其管理的原内存释放。
p.unique()若p.use_count()为1,返回true,否则返回false。
p.use_count()返回与p共享对象的智能指针数量,可能很慢,主要用于调试。
  • 最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。
// 指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
// p4指向一个值为"9999999999"的string
shared_ptr<string> p4 = make_shared<string>(10, '9');
// p5指向一个值初始化的int,值为0
shared_ptr<int> p5 = make_shared<int>();
// p6指向一个动态分配的空vector<string>
auto p6 = make_shared<vector<string>>();
  • 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:
auto p = make_shared<int>(42); // p指向的对象只有p一个引用者
auto q(p);                     // p和q指向相同对象,此对象有两个引用者
  • 每个shared_ptr都有一个关联的计数器,被称为引用计数
  • 一旦一个shared_ptr的计数器变为0,就会自动释放自己所管理的对象
r = q; //给r赋值,令它指向另一个地址
       //递增q指向的对象的引用计数
       //递减r原来指向的对象的引用计数
       // r原来指向的对象已没有引用者,会自动释放
  • shared_ptr的析构函数会递减它所指向的对象的引用计数,如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
// factory返回一个shared_ptr,指向一个动态分配的对象
shared_ptr<Foo> factory(T arg)
{
    //恰当地处理arg
    // shared_ptr负责释放内存
    return make_shared<Foo>(arg);
}
  • 由于p是use_factory的局部变量,在use_factory结束时它将被销毁。
void use_factory(T arg)
{
    shared_ptr<Foo> p = factory(arg);
    //使用p
} // p离开了作用域,它指向的内存会被自动释放掉
  • 如果有其他shared_ptr也指向这块内存,它就不会被释放掉。
void use_factory(T arg)
{
    shared_ptr<Foo> p = factory(arg);
    //使用p
    return p; //当我们返回p时,引用计数进行了递增操作。
} // p离开了作用域,它指向的内存会被自动释放掉
  • 程序使用动态内存出于以下三种原因之一:
    1. 程序不知道自己需要使用多少对象。
    2. 程序不知道所需对象的准确类型。
    3. 程序需要在多个对象间共享数据。
  • 当我们拷贝一个vector时,原vector和副本vector中的元素是相互分离的。
vector<string> v1; //空vector
{
    //新作用域
    vector<string> v2 = {"a", "an", "the"};
    v1 = v2; //从v2拷贝元素到v1中
}
// v2被销毁,其中的元素也被销毁
// v1有三个元素,是原来v2中元素的拷贝
  • 假定希望定义一个名为Blob的类,与容器不同,希望Blob对象的不同拷贝之间共享相同的元素。
  • 两个对象共享底层的数据,当某个对象被销毁时,不能单方面地销毁底层数据。
Blob<string> b1; //空Blob
{
    //新作用域
    Blob<string> b2 = {"a", "an", "the"};
    b1 = b2; // b1和b2共享相同的元素
}
// b2被销毁,但b2中的元素不能销毁
// b1指向最初由b2创建的元素
  • 使用动态内存的一个常见原因是允许多个对象共享相同的状态。
  • 贴一个学习时老师徒手撸的简单智能指针类。
  • Main.cpp
#include <iostream>
#include "AutoPoint.h"
#include "PointReferenceManager.h"

using namespace std;

class A //测试用自定义类A
{
public:
	int Age;
	int Num;
	int* pI;

	A()
	{
		Num = 10;
		cout << "普通构造函数" << endl;
		pI = new int;
	}

	A(A& other)
	{
		cout << "拷贝构造函数" << endl;
	}

	A(A&& other)
	{
		cout << "移动构造函数" << endl;
		pI = other.pI;
		other.pI = nullptr;
	}

	~A()
	{
		cout << "析构函数" << endl;
		if (pI)
		{
			delete pI;
		}
	}
};

class B //测试用自定义类B
{
public:
	A a;
	int Num;
	~B()
	{
		cout << "Free B" << endl;
	}
};


template<typename T>
class Pointer
{
public:
	Pointer():BindA(nullptr){}
	~Pointer()
	{
		if (BindA)
		{
			delete BindA;
		}
	}
public:
	T* BindA;
};

A Fun()
{
	A a;
	return a;
}

AutoPoint<A> GetA()
{
	AutoPoint<A> pa(new A);
	pa->Num = 100;
	return pa;
}

int main()
{
	AutoPoint<A> pa = GetA();
	cout << pa->Num << endl;
	system("Pause");
}
  • AutoPoint.h
#pragma once

struct NULLPOINTER
{

};

template<typename T>
class AutoPoint
{
public:

	~AutoPoint();

	AutoPoint():BindPoint(nullptr){}

	explicit AutoPoint(T* OutPoint);

	AutoPoint(AutoPoint<T>& Other);
	AutoPoint(AutoPoint<T>&& Other);

	T* operator->();

	AutoPoint<T>& operator=(NULLPOINTER* Point);
	AutoPoint<T>& operator=(AutoPoint<T>& Other);//拷贝赋值运算符重载
	AutoPoint<T>& operator=(AutoPoint<T>&& Other);
	bool operator==(const AutoPoint<T>& Other);
	bool operator!=(const AutoPoint<T>& Other);



	//删除new关键字
	void* operator new(size_t) = delete;

	//转换规则,从当前类型到布尔的转换规则explicit禁止隐士转换
	explicit operator bool()
	{
		return BindPoint != nullptr;
	}

	void Free();
public:
	T* BindPoint;
};

template<typename T>
AutoPoint<T>::AutoPoint(AutoPoint<T>&& Other)
{
	if (Other.BindPoint)
	{
		BindPoint = Other.BindPoint;
		Other.BindPoint = nullptr;
	}
}

template<typename T>
AutoPoint<T>& AutoPoint<T>::operator=(AutoPoint<T>&& Other)
{
	if (Other.BindPoint)
	{
		Free();
		BindPoint = Other.BindPoint;
		Other.BindPoint = nullptr;
	}
}

template<typename T>
bool AutoPoint<T>::operator!=(const AutoPoint<T>& Other)
{
	return BindPoint != Other.BindPoint;
}

template<typename T>
bool AutoPoint<T>::operator==(const AutoPoint<T>& Other)
{
	return BindPoint == Other.BindPoint;
}

template<typename T>
AutoPoint<T>& AutoPoint<T>::operator=(AutoPoint<T>& Other)
{
	if (!Other.BindPoint)
	{
		return *this;
	}
	//先要释放掉旧的内存引用计数
	Free();
	BindPoint = Other.BindPoint;
	PointReferenceManager::Get()->AddPointReference(BindPoint);
	return *this;
}

template<typename T>
AutoPoint<T>& AutoPoint<T>::operator=(NULLPOINTER* Point)
{
	if (!Point)
	{
		Free();
	}
	return *this;
}

template<typename T>
void AutoPoint<T>::Free()
{
	if (BindPoint)
	{
		int Counter = PointReferenceManager::Get()->RemovePointReference(BindPoint);
		if (Counter<=0)
		{
			delete BindPoint;
		}
		BindPoint = nullptr;
	}
}

template<typename T>
T* AutoPoint<T>::operator->()
{
	return BindPoint;
}

template<typename T>
AutoPoint<T>::~AutoPoint()
{
	Free();
}

template<typename T>
AutoPoint<T>::AutoPoint(T* OutPoint)
{
	BindPoint = OutPoint;
	PointReferenceManager::Get()->AddPointReference(BindPoint);
}

template<typename T>
AutoPoint<T>::AutoPoint(AutoPoint<T>& Other)
{
	if (!Other.BindPoint)
	{
		return;
	}
	BindPoint = Other.BindPoint;
	PointReferenceManager::Get()->AddPointReference(BindPoint);
}
  • PointReferenceManager.h
#include <iostream>
#include <map>

using namespace std;

class PointReferenceManager
{
private:
	PointReferenceManager() {};
public:
	static PointReferenceManager* Get();
	void AddPointReference(void* Point);
	int RemovePointReference(void* Point);
	int GetPointReferenceCounter(void* Point);
private:
	map<void*, int> CounterMap;
};
  • PointReferenceManager.cpp
#include "PointReferenceManager.h"

PointReferenceManager* PointReferenceManager::Get()
{
	static PointReferenceManager* Instance = new PointReferenceManager;
	return Instance;
}

void PointReferenceManager::AddPointReference(void* Point)
{
	//检查之前是否有人存过这个地址
	auto iter = CounterMap.find(Point);
	if (iter==CounterMap.end())
	{
		CounterMap.insert(pair<void*,int>(Point,1));
	}
	else
	{
		CounterMap[Point]++;
	}
}

int PointReferenceManager::RemovePointReference(void* Point)
{
	int Count = 0;
	//检查之前是否有人存过这个地址
	auto iter = CounterMap.find(Point);
	if (iter != CounterMap.end())
	{
		Count = CounterMap[Point];
		Count--;
		if (CounterMap[Point]<=0)
		{
			//删除给定地址的引用计数条目
			CounterMap.erase(iter);
			Count = 0;
		}
		else
		{
			CounterMap[Point] = Count;
		}
	}
	return Count;
}

int PointReferenceManager::GetPointReferenceCounter(void* Point)
{
	int Count = 0;
	auto iter = CounterMap.find(Point);
	if (iter!=CounterMap.end())
	{
		Count=CounterMap[Point];
	}
	return Count;
}

12.1.2 直接管理内存

  • 使用new动态分配和初始化对象。
int *pi = new int; // pi指向一个动态分配的、未初始化的无名对象
  • 类类型对象将用默认构造函数进行初始化。
string *ps = new string; //初始化为空string
int *pi = new int;       // pi指向一个未初始化的int
  • 可以使用直接初始化方式初始化一个动态分配的对象。
int *pi = new int(l024);          // pi指向的对象的值为1024
string *ps = new string(10, '9'); //*ps为"9999999999"
vector<int> *pv = new vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  • 可以对动态分配的对象进行值初始化。
auto pl = new auto(obj);      // p指向一个与obj类型相同的对象
auto p2 = new auto {a, b, c}; //错误:括号中只能有单个初始化器
  • 动态分配的const对象。
//分配并初始化一个const int
const int *pci = new const int(1024);
//分配并默认初始化一个const的空string
const string *pcs = new const string;
  • 内存耗尽,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc。
//如果分配失败,new返回一个空指针
int *pl = new int;           //如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int; //如果分配失败,new返回一个空指针
  • new (nothrow)这种形式的new为定位new
  • 释放动态内存通过delete表达式,来将动态内存归还给系统。
delete p; // p必须指向一个动态分配的对象或是一个空指针
  • 指针值和delete的使用时场景。
int i, *pi1 = &i, *pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;
delete i;   //错误:1不是一个指针
delete pi1; //未定义:pi1指向一个局部变量
delete pd;  //正确
delete pd2; //未定义:pd2指向的内存已经被释放了
delete pi2; //正确:释放一个空指针总是没有错误的
  • 虽然一个const对象的值不能被改变,但它本身是可以被销毁的。
const int *pci = new const int(l024);
delete pci; //正确:释放一个const对象
  • 动态对象的生存期直到被释放时为止。
  • 对于一个由内置指针管理的动态对象,直到被显式释放之前它都是存在的。
Foo *factory(T arg)
{
    //视情况处理arg
    return new Foo(arg); //调用者负责释放此内存
}
void use_factory(T arg)
{
    Foo *p = factory(arg);
    //使用p但不delete它
} // p离开了它的作用域,但它所指向的内存没有被释放!
void use_factory(T arg)
{
    Foo *p = factory(arg);
    //使用p
    delete p;//现在记得释放内存,我们已经不需要它了
} 
void use_factory(T arg)
{
    Foo *p = factory(arg);
    //使用p
    return p; //调用者必须释放内存
} 
  • 动态内存的管理非常容易出错。
    1. 内存泄露,忘记delete内存。
    2. 试图操作空指针
    3. 同一块内存释放两次。在delete之后,指针变成了空悬指针,指向一块曾经保存数据对象但现在已经无效的内存的指针。

12.1.3 shared_ptr和new结合使用

  • 不初始化一个智能指针,它就会被初始化为一个空指针。
  • 可以使用new返回的指针来初始化智能指针。
shared_ptr<double> p1;           // shared_ptr可以指向一个double
shared_ptr<int> p2(new int(42)); // p2指向一个值为42的int
  • 接受指针参数的智能指针构造函数是explicit的。
shared_ptr<int> p1 = new int(1024); // 错误:必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));  // 正确:使用了直接初始化形式
  • 一个返回shared_ptr的函数不能在其返回语句中隐式转换。
shared_ptr<int> clone(int p)
{
    return new int(p); //错误:隐式转换为shared_ptr<int>
}
shared_ptr<int> clone(int p)
{
    //正确:显式地用int*创建shared_ptr<int>
    return shared_ptr<int>(new int(p));
}
定义和改变shared_ptr的其他方法解释
shared_ptr p(q)p管理内置指针q所指向的对象:q必须指向new分配的内存,且能够转换为T*类型
shared_ptr p(u)p从unique_ptr u那里接管了对象的所有权:将u置为空
shared_ptr p(q, d)p接管了内置指针q所指向的对象的所有权,q必须能转换为T*类型,p将使用可调用对象d来代替delete
shared_ptr p(p2, d)p是shared_ptr p2的拷贝,唯一的区别是p将用可调用对象d来代替delete
p.reset()若p是唯一指向其对象的shared_ptr,reset会释放此对象。
p.reset(q)若传递了可选的参数内置指针q,会令p指向q,否则会将p置为空。
p.reset(q, d)若还传递了参数d,将会调用d而不是delete来释放q
  • 不要混合使用普通指针和智能指针。
//在函数被调用时ptr被创建并初始化
void process(shared_ptr<int> ptr)
{
    //使用ptr
} // ptr离开作用域,被销毁

shared_ptr<int> p(new int(42)); //引用计数为1
process(p);                     //拷贝p会递增它的引用计数;在prcess中引用计数值为2
int i = *p;                     //正确:引用计数值为1

int *x(new int(1024));       //危险:x是一个普通指针,不是一个智能指针
process(x);                  //错误:不能将int*转换为一个shared_ptr<int>
process(shared_ptr<int>(x)); //合法的,但内存会被释放!
int j = *x;                  //未定义的:x是一个空悬指针!
  • 不要使用get初始化另一个智能指针或为智能指针赋值。
shared_ptr<int> p(new int(42)); //引用计数为1
int *q = p.get();               //正确:但使用q时要i王意,不要让它管理的指针袚释放
{
    //未定义:两个独立的shared_ptr指向相同的内存
    shared_ptr<int>(q);
}
//程序块结束,q被销毁,它指向的内存被释放
int foo = *p; //未定义:p指向的内存已经被释放了
  • 其他shared_ptr操作,用reset来将一个新的指针赋予一个shared_ptr。
shared_ptr<int> p(new int(42));
p = new int(1024);      //错误:不能将一指针赋予shared_ptr
p.reset(new int(1024)); //正确: p指向一个新对象
if (!p.unique())
{
    p.reset(new string(*p)); //我们不是唯一用户;分配新的拷贝
}
*p += newVal; //现在我们知道自己是唯一的用户,可以改变对象的值

12.1.4 智能指针和异常

  • 如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放。
  • 函数的退出有两种可能,正常处理结束或者发生了异常,无论哪种情况,局部对象都会被销毁。
void f()
{
    shared_ptr<int> sp(new int(42)); //分配一个新对象
    //这段代码抛出一个异常,且在f中未被捕获
} //在函数结束时shared_ptr自动释放内存
  • 当发生异常时,直接管理的内存是不会自动释放的。
void f()
{
    int *ip = new int(42); //动态分配一个新对象
    //这段代码抛出一个异常,且在f中未被捕获
    delete ip; //在退出之前释放内存
}
  • 那些分配了资源,而又没有定义析构函数来释放这些资源的类,可能会遇到与使用动态内存相同的错误。
struct destination;                //表示我们正在连接什么
struct connection;                 //使用连接所需的信息
connection connect(destination *); //打开连接
void disconnect(connection);       //关闭给定的连接
void f(destination &d /* 其他参数*/)
{
    //获得一个连接;记住使用完后要关闭它
    connection c = connect(&d);
    //使用连接
    //如果我们在f退出前忘记调用disconnect, 就元法关闭c了
}
  • 删除器函数能够完成对shared_ptr中保存的指针进行释放的操作。
struct destination;                //表示我们正在连接什么
struct connection;                 //使用连接所需的信息
connection connect(destination *); //打开连接
void disconnect(connection);       //关闭给定的连接
void end_connection(connection *p) { disconnect(*p); }

void f(destination &d /* 其他参数*/)
{
    connection c = connect(&d);
    shared ptr<connection> p(&c, end_connection);
    //使用连接
    //当f退出时(即使是由于异常而退出),connec巨on会被正确关闭
}

12.1.5 unique_ptr

  • 一个unique_ptr拥有它所指向的对象,每个时刻只有一个unique_ptr指向一个给定对象。
unique_ptr<double> p1;           //可以指向一个double的unique_ptr
unique_ptr<int> p2(new int(42)); // p2指向一个值为42的int
  • unique_ptr不支持普通的拷贝或赋值操作。
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); //错误:unique_ptr不支持拷贝
unique_ptr<string> p3;
p3 = p2; //错误:unique_ptr不支持赋值
unique_ptr操作解释
unique_ptr u1空unique_ptr,可以指向类型为T的对象,u1会使用delete来释放它的指针
unique_ptr<T, D> u2u2会使用一个类型为D的可调用对象来释放它的指针
unique_ptr<T, D> u(d)空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr释放u指向的对象,将u置为空
u.release()u放弃对指针的控制权,返回指针,井将u置为空
u.reset()释放u指向的对象
u.reset(q)如果提供了内置指针q,令u指向这个对象
u.reset(nullptr)否则将u置为空
  • 不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另个unique。
//将所有权从p1(指向string Stegosaurus)转移给p2
unique_ptr<string> p2(p1.release()); // release将p1置为空

unique_ptr<string> p3(new string("Trex"));
//将所有权从p3转移给p2
p2.reset(p3.release()); // reset释放了p2原来指向的内存
  • 调用release会切断unique_ptr和它原来管理的对象间的联系。
p2.release();          //错误:p2不会释放内存,而且我们丢失了指针
auto p = p2.release(); //正确,但我们必须记得delete(p)
  • 可以拷贝或赋值一个将要被销毁的unique_ptr。
  • 对于两段代码,编译器都知道要返回的对象将要被销毁,在此情况下,编译器执行一种特殊的拷贝。
unique_ptr<int> clone(int p)
{
    //正确:从int*创建一个unique_ptr<int>
    return unique_ptr<int>(new int(p));
}

unique_ptr<int> clone(int p)
{
    unique_ptr<int> ret(new int(p));
    // ...
    return ret;
}
  • 向unique_ptr传递删除器。
// p指向一个类型为objT的对象,并使用一个类型为delT的对象释放objT对象
//它会调用一个名为fcn的delT类型对象
unique_ptr<objT, delT> p(new objT, fen);
  • 重写连接程序,使用了decltype来指明函数指针类型。
void f(destination &d /*其他需要的参数*/)
{
    connection c = connect(&d); //打开连接
    //当p被销毁时,连接将会关闭
    unique ptr<connection, decltype(end connection) *> p(&c, end connection);
    //使用连接
    //当f退出时(即使是由于异常而退出),connection会被正确关闭
}

12.1.6 weak_ptr

  • weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。
weak_ptr操作解释
weak_ptr w空weak_ptr可以指向类型为T的对象
weak_ptr w(sp)与shared_ptr sp指向相同对象的weak_ptr,T必须能转换为sp指向的类型
w = pp可以是一个shared_ptr或一个weak_ptr,赋值后w与p共享对象
w.reset()将w置为空
w.use_count()与w共享对象的shared_ptr的数量
w.expired()若w.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); // wp弱共享p,p的引用计数未改变
  • 由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock,此函数检查weak_ptr指向的对象是否仍存在。
if (shared_ptr<int> np = wp.lock()) //如果np不为空则条件成立
{
    //在if中,np与p共享对象
}

12.2 动态数组

  • 标准库中包含一名为allocator的类,允许我们将分配和初始化分离,使用allocator通常会提供更好的性能和更灵活的内存管理能力。

12.2.1 new和数组

  • 在下例中,new分配要求数量的对象并返回指向第一个对象的指针。
//调用get_size确定分配多少个int
int *pia= new int[get_size()];//pia指向第一个int
  • 可以用一个表示数组类型的类型别名来分配一个数组。
typedef int arrT[42]; // arrT表示42个int的数组类型
int *p = new arrT;    //分配一个42个int的数组,p指向笫一个int
  • 动态数组并不是数组类型。
  • 默认情况下,new分配的对象,不管是单个分配的还是数组中的,都是默认初始化的。
int *pia = new int[10];          // 10个未初始化的int
int *pia2 = new int[10]();       // 10个值初始化为0的int
string *psa = new string[10];    // 10个空string
string *psa2 = new string[10](); // 10个空string
  • 在新标准中,我们还可以提供一个元素初始化器的花括号列表。
// 10个int分别用列表中对应的初始化器初始化
int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// 10个string,前4个用给定的初始化器初始化,剩余的进行值初始化
string *psa3 = new string[10]{"a", "an", "the", string(3, 'x')};
  • 动态分配一个空数组是合法的。
size_t n = get_size(); // get_size返回需要的元素的数目
int *p = new int[n];   //分配数组保存元素
for (int *q = p; q != p + n; ++q)
/*处理数组*/
  • 当n等于0时,调用new[n]是合法的。
char arr[0];            //错误:不能定义长度为0的数组
char *cp = new char[0]; //正确:但cp不能解引用
  • 释放动态数组。
  • 销毁pa指向的数组中的元素,并释放对应的内存。
  • 按逆序销毁。
delete p;    // p必须指向一个动态分配的对象或为空
delete[] pa; // pa必须指向一个动态分配的数组或为空
  • 使用类型别名定义时释放也一样。
typedef int arrT[42]; // arrT表示42个int的数组类型
int *p = new arrT;    //分配一个42个int的数组,p指向笫一个int
delete[] p;           //方括号是必需的,因为我们当初分配的是一个数组
  • 智能指针和动态数组。
// up指向一个包含10个未初始化int的数组
unique_ptr<int[]> up(new int[10]);
up.release(); //自动用delete[]销毁其指针
  • 可以使用下标运算符来访问数组中的元素。
for (size_t i = 0; i != 10; ++i)
{
    up[i] = i; //为每个元素赋子一个新值
}
指向数组的unique_ptr解释
指向数组的unique_ptr不支持成员访问运算符(点和箭头运算符)。
其他unique_ptr操作不变。
unique _ptr<T[]> uu可以指向一个动态分配的数组,数组元素类型为T
unique _ptr<T[]> u( p)u指向内置指针p所指向的动态分配的数组,p必须能转换为类型T*
u[i]返回u拥有的数组中位置i处的对象
  • 与unique_ptr不同,shared_ptr不直接支持管理动态数组。
  • 如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器。
//为了使用shared_ptr, 必须提供一个删除器
shared_ptr<int> sp(new int[10], [](int *p)
                   { delete[] p; });
sp.reset(); //使用我们提供的lambda释放数组,它使用delete[]
  • shared_ptr不直接支持动态数组管理这一特性会影响我们如何访问数组中的元素。
  • 为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素
// shared_ptr未定义下标运算符,并且不支持指针的算术运算
for (size_t i = 0; i != 10; ++i)
{
    *(sp.get() + i) = i; //使用get获取一个内置指针
}

12.2.2 allocator类

  • 将内存分配和对象构造组合在一起会导致不必要的浪费。
  • 没有默认构造函数的类就不能动态分配数组。
string *const p = new string[n];
//构造n个空string
string s;
string *q = p; // q指向笫一个string
while (cin >> s && q != p + n)
{
    *q++ = s; //赋予*q一个新值
}
const size_t size = q - p; //记住我们读取了多少个string
//使用数组
delete[] p; // p指向一个数组;记得用delete[]来释放
  • 标准库allocator类定义在头文件memory中,帮助我们将内存分配和对象构造分离开来。
  • allocator是一个模板。
allocator<string> alloc;          //可以分配string的allocator对象
auto const p = alloc.allocate(n); //分配n个未初始化的string
标准库allocator类及其算法解释
allocator a定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存
a.allocate(n)分配一段原始的、未构造的内存,保存n个类型为T的对象
a.deallocate(p, n)释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象,p必须是一个先前由allocate返回的指针,且n必须是p创建时所要求的大小,在此之前需调用destroy
a.construct(p, args)p必须是一个类型为T*的指针,指向一块原始内存,arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象
a.destroy( p)p为T*类型的指针,此算法对p指向的对象执行析构函数
  • allocator分配未构造的内存。
auto q = p;                       // q指向最后构造的元素之后的位置
alloc.construct(q++);             // *q为空宇符串
alloc.construct(q++, 10, 'c');    // *q为cccccccccc
alloc.construct(q++, "hi");       // *q为hi!
  • 还未构造对象的清况下就使用原始内存是错误的。
  • 用完对象后,必须对每个构造的元素调用destroy来销毁它们。
while (q != p)
{
    alloc.destroy(--q); //释放我们真正构造的string
}
  • 最后释放内存。
alloc.deallocate(p, n) 
  • 标准库还为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象。
allocator算法解释
这些函数在给定目的位置创建元素,而不是由系统分配内存给它们。
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个对象,b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象
  • 分配一块比vector中元素所占用空间大一倍的动态内存,然后将原vector中的元素拷贝到前一半空间,对后一半空间用一个给定值进行填充。
//分配比vi中元素所占用空间大一倍的动态内存
auto p = alloc.allocate(vi.size() * 2);
//通过拷贝vi中的元素来构造从p开始的元素
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
//将剩余元素初始化为42
uninitialized_fill_n(q, vi.size(), 42);

12.3 使用标准库:文本查询程序

12.3.1 文本查询程序设计

12.3.2 文本查询程序类的定义

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Flame老唐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值