- 前言
- 动态内存与智能指针
- shared_ptr类
- 直接内存管理
- shared_ptr和new结合使用
- 智能指针和异常
- unique_ptr
- weak_ptr
- 动态数组
- new和数组
- allocator类
- 使用标准库:文本查询程序
- 特别辣的鸡
- 比较优秀的代码
- 总结
前言
全局对象在程序启动时分配,在程序结束时销毁。局部自动对象:当进入其定义所在的程序块是被创建,在离开块时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。
除自动和static对象外,C++还支持动态分配对象。动态分配对象的生存期与他们在哪里创建无关,只有当显式地被释放时,这些对象才会被销毁。
为了更安全的使用动态对象,标准库定义了两个智能指针类型来管理动态分配的对象。
静态内存:保存局部static对象、类static数据成员以及定义在任何函数之外的变量
栈内存 : 保存定义在函数内的非static对象
二者由编译器自动创建或销毁
static对象:在使用之前分配,在程序结束时销毁
栈 对 象 :仅在其定义的程序块运行时才存在
除了静态内存和栈内存,每个程序还拥有一个内存池。称为自由空间(free store)或堆(help),用于存储动态分配(dynamically allocate)的对象(在程序运行时分配的对象,生存期由程序控制,不使用时必须显式地销毁)
正确的管理动态内存非常棘手
动态内存与智能指针
智能指针也是模板,定义在头文件
智能指针有点像Python中的对象,有引用计数器,自动释放内存等
关键字:new delete 智能指针: shared_ptr、unique_ptr、weak_ptr
shared_ptr类shared_ptrp1; //shared_ptr, point to string shared_ptr > p2; //shared_ptr, point to lsit
shared_ptr和unique_ptr都支持的操作
| shared_ptr sp | 空指针 |
| unique_ptr up | 空指针 |
| p | p为空 false,p不为空 true |
| *p | 解引用 |
| p -> mem | |
| p.get() | 返回p中保存的指针。 若智能指针释放了其对象,返回的指针所指的对象也就消失了 |
| swap(p, q) | 交换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() | 返回与p共享对象的智能指针数量;可能很慢,主要用于调试 |
shared_ptr p3 = make_shared(42); //p3指向 42 shared_ptr p4 = make_shared (4, '9'); //p4指向 string("9999") shared_ptr p5 = make_shared (); //p5指向 int 初始化0 //p6指向一个动态分配的空vector auto p6 = make_shared >();
shared_ptr的拷贝和赋值
引用计数(reference count)
auto p = make_shared(42); //p指向的对象只有p一个引用者 auto q(p); //p和q指向相同对象,此对象有两个引用者 auto r = make_shared (42); //r指向的int只有一个引用者 r = q; //给r赋值,令他指向另一个地址 //递增q指向的对象的引用计数器,递减r原来指向的对象的引用计数器 //r原来指向的对象已没有引用者,会自动释放
**shared_ptr 自动销毁所管理的对象······ **
析构函数destructor:与构造函数相反,与对象结束生命周期有关
······shared_ptr 还会自动释放相关联的内存
程序使用动态内存的三种原因:
- 程序不知道自己需要多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据
定义StrBlob类
class StrBlob{
public:
using size_type = std::vector::size_type;
StrBlob();
StrBlob(std::initializer_list il);
size_type size() const {return this->data->size();}
bool empty() const {return this->data->empty();}
//添加和删除元素
void push_back(const std::string &t){ data->push_back(t);}
void pop_back();
//元素访问
std::string& front();
std::string& back();
private:
std::shared_ptr> data;
//如果data[i]不合法,抛出一个异常
void check(size_type i, const std::string &msg) const;
};
StrBlob::StrBlob(): data(make_shared>() ) {}
StrBlob::StrBlob(std::initializer_list il):
data( make_shared>(il) ) {}
void StrBlob::check(size_type i, const string &msg) const{
if (i >= data->size()) throw out_of_range(msg);
}
string &StrBlob::front(){
//如果vector为空,check会抛出一个异常
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back(){
check(0, "front on empty StrBlob");
return data->back();
}
void StrBlob::pop_back(){
check(0, "front on empty StrBlob");
data->pop_back();
}
直接内存管理
new分配内存,delete释放new分配的内存
int *pi = new int; //pi指向一个动态分配的、未初始化的无名对象 //内置类型或组合类型的对象的值将是未定义的,类类型对象将用默认构造函数进行初始化 string *ps = new string; //初始化为空string int *pi = new int(1024); //pi指向的对象的值为1024 string *ps = new string(3, '9'); /}//ptr离开作用域,被销毁 int* x(new int(1024)); //危险!x是一个普通指针不是一个智能指针 process(x); //❌ process(shared_ptr(x)); //合法,但内存会被释放! int j = *x; //未定义:x是一个空悬指针!它指向的内存已经被释放
······不要使用get初始化另一个智能指针,或为智能指针赋值
{
shared_ptr p(new int(42)); //引用计数为1
//.get()返回内置指针,指向p管理的对象
int *q = p.get(); //正确,但使用q是要注意,不要让他管理的指针被释放,不能delete
{
//未定义:两个独立的shared_ptr指向相同的内存
shared_ptr(q);
}//程序块结束,q被销毁,它指向的内存被释放
int foo = *p; //未定义:p指向的内存已经被释放了
}//程序块结束 p 所指向的内存会被第二次delete
智能指针和异常
void end_connection(connection *p) {disconnect (*p)};
void f(T &d ){
T c = connect(&d);
shared_ptrp(&c, end_connection);
}//当f退出时(即使时由于异常而推出),connection会被正确关闭
基本规范:
- 不使用相同的内置指针初始化(或reset)多个智能指针
- 不delete get()返回的指针
- 不使用get() 初始化或reset另一个智能指针
- 如果使用get()返回的指针,当最后一个对应的智能指针销毁后,指针就变为无效了
- 如果使用智能指针管理的资源不是new分配的内存,传递给它一个删除器
unique_ptrp1(new string("abc")); unique_ptr p2(p1.release()); //将所有权从p1转移给p2,release将p1置为空 unique_ptr p3(new string("Tex")); p2.reset(p3.release()); //将所有权从p3转移给p2,reset释放了p2原来指向的内存 p2.release(); //❌ p2不会释放内存,而且我们丢失了指针 auto p = p2.release(); //✔ 但我们必须计的delete p
向unique_ptr传递删除器
void f(destination &d ){
connection c = connect(&d);
unique_ptr p(&c , end_connection);
//使用连接
}//当f推出(即使是异常退出),connection会被正确关闭
const int& anotherfunc(const int &a){
return a;
}
int main(){
decltype(anotherfunc)* p = anotherfunc;
const int&(*pfunc)(const int&)=0;
pfunc = anotherfunc;
cout << (pfunc == x) << endl;
return 0;}
weak_ptr
auto p = make_shared动态数组(42); weak_ptr wp(p); //wp弱共享p;p的引用计数器未改变 if (auto np = wp.lock()){ }
两种一次分配一个对象数组的方法
new和数组int *p = new int[get_size()]; //p为指向数组元素类型的指针,而非指向数组的指针
//未初始化
int *p = new int[10](); //初始化10个0
string *ps1 = new string[10]; //10个空string
string*ps2 =new string[9](9); //9个空string
int *p = new int[10]{1,2,3,4};
string *ps=new string[10]{"a", string(3,'x')};
若,初始化器的数目大于元素数目,则new表达式失败,抛出pad_array_new_length异常,定义在new头文件中
char arr[0]; //❌不能定义长度为0的数组 char *cp = new char[0]; //✔但cp不能解引用
释放动态数组
delete p; //p必须指向一个动态分配的对象或为空,否则是未定义行为 delete [] p; //p必须指向一个动态分配的数组或为空,否则是未定义行为
智能指针和动态数组
//up指向一个包含10个未初始化int的数组 unique_ptrup(new int[10]); up.release(); //自动调用delete[]销毁其指针 for(size_t i=0; i!= 10; ++i) up[i] = i;
shared_ptr不直接支持管理动态数组
不要用动态数组,用vector等容器
//为了使用shared_ptr, 必须提供一个删除器 shared_ptrallocator类sp(new int[10], [](int *p){delete[]p;}) sp.reset(); //使用我们提供的lambda释放数组,它使用delete[] //否则,shared_ptr默认使用delete,就会碰到delete 动态数组指针的未定义行为 //shared_ptr未定义下标运算符,并且不支持指针的算术运算 for (size_t i=0; i!=10; ++i) *(sp.get() + i) = i; //使用get获取一个内置指针
allocator类定义在头文件memory中
allocatoralloc; auto const p = alloc.allocatr(n); auto x = p; alloc.construct(x++); //*x为空字符串 alloc.construct(x++, 10, 'c'); //*x为 cccccccccc alloc.construct(x++, "hi"); //*x为 hi cout << *p << endl; //正确 cout << *x << endl; //严重错误,x指向为构造的内存 while(x != p) alloc.destroy(--x) ; //释放我们真正构造的string,析构 alloc.deallocate(p, n); //释放内存
拷贝和填充未初始化内存的算法
//vector使用标准库:文本查询程序 特别辣的鸡vi auto p = alloc.allocate(vi.size()*2); auto x = uninitialized_copy(vi.begin(), vi.end() ,p); uninitialized_fill_n(x, vi.size(), 42) //alloc总共n = vi.size()*2个元素,前n个拷贝,后n个赋值42
class TextQuery{
using inner_t1 = map>>;
private:
inner_t1 wd2lineMap;
vector text;
ifstream& infile;
void Map_function();
public:
TextQuery(ifstream &infile):
infile(infile), wd2lineMap(), text() {Map_function();};
string query(const string&);
};
void TextQuery::Map_function(){
if(infile){
string line;
int lineNum=-1;
while ( getline(infile, line) ){
++lineNum;
text.push_back(line);
istringstream wds(line);
string wd;
while(wds >> wd){
auto res = wd2lineMap.find(wd);
if (res == wd2lineMap.end()){
auto paired = pair>(1, {lineNum});
wd2lineMap.insert({wd, paired});
}else
++(wd2lineMap[wd].first);
wd2lineMap[wd].second.insert(lineNum);
}
}
}
}
string TextQuery::query(const string&s){
auto iter = wd2lineMap.find(s);
if (iter == wd2lineMap.end()){
return string("not found!n");
}else{
string res;
auto paired = wd2lineMap[s];
cout << "word: "<< s << " occures "<1?" times":" time") << endl;
for (auto c:paired.second)
if (c>=0 && c
TextQuery tq(infile);
while(true){
cout << "enter word to look for, or q to quit:n";
string s;
if (!(cin >> s) || s == "q") break;
// cout << tq.query(s);
print(cout , tq.query(s)) << endl;
}
}
int main(){
ifstream infile("./text.txt");
runQueries(infile);
return 0;}
比较优秀的代码
#include#include #include #include #include
总结
C++中,动态内存通过new表达式分配,通过delete表达式释放。标准库还定义了allocator类来分配动态内存赶块。
尽量不要用手动的方式管理动态,用智能指针
堆:自由空间的同义词
堆:是一棵完全二叉树······



