c++ 动态内存


前言

  全局对象在程序启动时分配,在程序结束时销毁。局部自动对象:当进入其定义所在的程序块是被创建,在离开块时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。

  除自动和static对象外,C++还支持动态分配对象。动态分配对象的生存期与他们在哪里创建无关,只有当显式地被释放时,这些对象才会被销毁。

  为了更安全的使用动态对象,标准库定义了两个智能指针类型来管理动态分配的对象。

  静态内存:保存局部static对象、类static数据成员以及定义在任何函数之外的变量
  栈内存 : 保存定义在函数内的非static对象
二者由编译器自动创建或销毁
static对象:在使用之前分配,在程序结束时销毁
栈 对 象 :仅在其定义的程序块运行时才存在

除了静态内存和栈内存,每个程序还拥有一个内存池。称为自由空间(free store)或(help),用于存储动态分配(dynamically allocate)的对象(在程序运行时分配的对象,生存期由程序控制,不使用时必须显式地销毁)
正确的管理动态内存非常棘手
在这里插入图片描述


动态内存与智能指针

智能指针也是模板,定义在头文件<memory>
智能指针有点像Python中的对象,有引用计数器,自动释放内存等

关键字:new delete 智能指针: shared_ptr、unique_ptr、weak_ptr

shared_ptr类

shared_ptr<string> p1;    //shared_ptr, point to string
shared_ptr<list<int>> p2; //shared_ptr, point to lsit<int>

shared_ptr和unique_ptr都支持的操作

shared_ptr<T> sp空指针
unique_ptr<T> up空指针
pp为空 false,p不为空 true
*p解引用
p -> mem
p.get()返回p中保存的指针。
若智能指针释放了其对象,返回的指针所指的对象也就消失了
swap(p, q)交换p和q中的指针
p.swap(q)交换p和q中的指针

shared_ptr独有的操作

make_shared<T> (args)返回一个shared_ptr,指向一个动态分配的类型为T的对象
使用args初始化此对象
shared_ptr<T> 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共享对象的智能指针数量;可能很慢,主要用于调试
shared_ptr<int> p3 = make_shared<int>(42);        //p3指向 42
shared_ptr<string> p4 = make_shared<string>(4, '9'); //p4指向 string("9999")
shared_ptr<int> p5 = make_shared<int>();			 //p5指向 int 初始化0
//p6指向一个动态分配的空vector<string>
auto p6       = make_shared<vector<string>>();

shared_ptr的拷贝和赋值
引用计数(reference count)

auto p = make_shared<int>(42); //p指向的对象只有p一个引用者
auto q(p);                     //p和q指向相同对象,此对象有两个引用者
auto r = make_shared<int>(42); //r指向的int只有一个引用者
r = q;                         //给r赋值,令他指向另一个地址
                               //递增q指向的对象的引用计数器,递减r原来指向的对象的引用计数器
                               //r原来指向的对象已没有引用者,会自动释放

在这里插入图片描述
**shared_ptr 自动销毁所管理的对象······ **
析构函数destructor:与构造函数相反,与对象结束生命周期有关

······shared_ptr 还会自动释放相关联的内存

程序使用动态内存的三种原因:

  1. 程序不知道自己需要多少对象
  2. 程序不知道所需对象的准确类型
  3. 程序需要在多个对象间共享数据
    定义StrBlob类
class StrBlob{
public:
	using size_type = std::vector<std::string>::size_type;
	StrBlob();
	StrBlob(std::initializer_list<std::string> 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<std::vector<std::string>> data;
	//如果data[i]不合法,抛出一个异常
	void check(size_type i, const std::string &msg) const;
};
/*StrBlob构造函数*/
StrBlob::StrBlob(): data(make_shared<vector<string>>() ) {}
StrBlob::StrBlob(std::initializer_list<std::string> il):
	data( make_shared<vector<string>>(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();
}
/*StrBlob的拷贝、赋值和销毁*/

直接内存管理

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'); //*ps 为 “999”
vector<int> *pv = new vector<int>{0,1,2,3,4};
string *psl = new string;  //默认初始化为空string
string *ps = new string(); //值初始化为空string
int *pi1 = new int;        //默认初始化;*pi1的值未定义
int *pi2 = new int();      //值初始化为0;*pi2为0
int a(); //a = 1 不是0??

以下初始化方式不是并列的:
构造初始化:使用圆括号(调用运算符?)
列表初始化:使用花括号
拷贝初始化:使用=,编译器把等号右边的初始值拷贝到新创建的对象中去
直接初始化:不使用等号
值初始化 :只提供对象容纳的元素数量而不用略去初始值。此时库会创建一个值初始化元素初值
默认初始化:使用默认构造函数进行的初始化

auto p1 = new auto {obj};  //✔,p指向与obj类型相同的对象
						   //该对象用obj进行初始化
auto p2 = new auto{a,b,c}; //❌括号中只能有单个初始化器

const int *pci = new const int(1024);
const string *pcs = new const string;

内存耗尽
若new不能分配所要求的内存空间,抛出类型为bad_alloc的异常
bad_alloc和nothrow都定义在头文件new中

int *p1 = new int;			 //如果分配失败,new抛出std::bad_alloc 
int *p2 = new (nothrow) int; //如果分配失败,new返回一个空指针

释放动态内存
delete expression

delete p; //p必须指向一个动态分配的对象或是一个空指针,否则是未定义的行为
		  //销毁对象,释放对应的内存空间
		  //delete只能释放指针指向的动态内存够对象,释放其他的对象或多次连续释放同一指针是未定义的行为

忘记delete内存会导致人们常说的“内存泄漏”问题

shared_ptr和new结合使用

//就目前而言,智能指针最好与指向动态内存对象的指针绑定
shared_ptr<double> p1; //p1为空智能指针
shared_ptr<int> p2(new int(42));  //p2指向一个值为42的int,该构造方法是explicit的
shared_ptr<int> p2 = new int(42); //❌,必须使用直接初始化的形式

shared_ptr<int> clone(int ival){
	return new int(ival); 				   //❌
	return shared_ptr<int>(new int(ival)); //✔
}

在这里插入图片描述
在这里插入图片描述
不要混合使用智能指针和普通指针

void process(shared_ptr<int> ptr){/****/}//ptr离开作用域,被销毁

int* x(new int(1024));       //危险!x是一个普通指针不是一个智能指针
process(x); 			     //❌
process(shared_ptr<int>(x)); //合法,但内存会被释放!
int j = *x;                  //未定义:x是一个空悬指针!它指向的内存已经被释放

······不要使用get初始化另一个智能指针,或为智能指针赋值

{
shared_ptr<int> p(new int(42)); //引用计数为1
//.get()返回内置指针,指向p管理的对象
int *q = p.get();               //正确,但使用q是要注意,不要让他管理的指针被释放,不能delete
{
//未定义:两个独立的shared_ptr指向相同的内存
	shared_ptr<int>(q);
}//程序块结束,q被销毁,它指向的内存被释放
int foo = *p;   //未定义:p指向的内存已经被释放了
}//程序块结束 p 所指向的内存会被第二次delete

智能指针和异常

void end_connection(connection *p) {disconnect (*p)};

void f(T &d /* */){
	T c = connect(&d);
	shared_ptr<connection>p(&c, end_connection);
}//当f退出时(即使时由于异常而推出),connection会被正确关闭

基本规范:

  • 不使用相同的内置指针初始化(或reset)多个智能指针
  • 不delete get()返回的指针
  • 不使用get() 初始化或reset另一个智能指针
  • 如果使用get()返回的指针,当最后一个对应的智能指针销毁后,指针就变为无效了
  • 如果使用智能指针管理的资源不是new分配的内存,传递给它一个删除器

unique_ptr

在这里插入图片描述

unique_ptr<string> p1(new string("abc"));
unique_ptr<string> p2(p1.release()); //将所有权从p1转移给p2,release将p1置为空
unique_ptr<string> 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<connection, decltype(end_connection)*> 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<int>(42);
weak_ptr<int> 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_ptr<int[]> up(new int[10]);
up.release();                      //自动调用delete[]销毁其指针
for(size_t i=0; i!= 10; ++i)
	up[i] = i;

在这里插入图片描述
shared_ptr不直接支持管理动态数组
不要用动态数组,用vector等容器

//为了使用shared_ptr, 必须提供一个删除器
shared_ptr<int> 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类

allocator类定义在头文件memory中
在这里插入图片描述

allocator<string> alloc;
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<int> 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<string, pair<int, set<int>>>;
private:
    inner_t1  wd2lineMap;
    vector<string>  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<int, set<int>>(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 "<<paired.first<< (paired.first>1?" times":" time") << endl;
        for (auto c:paired.second)
            if (c>=0 && c<text.size())
                res = res + "(line " + to_string(c)
                    + ") " + text[c] + "\n";
            else
                throw out_of_range("index out of vector");
        return res;
    }
}
void runQueries(ifstream &infile){
    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 <iostream>
#include <fstream>
#include <sstream>
#include <set>
#include <map>
#include <vector>
#include <memory>
using namespace std;

class QueryResult{
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
    using line_no = std::vector<std::string>::size_type;
    QueryResult(std::string s,
                std::shared_ptr<std::set<line_no>> p,
                std::shared_ptr<std::vector<std::string>> f):
        sought(s), lines(p), file(f) {}
private:
    std::string sought;
    std::shared_ptr<std::set<line_no>> lines;
    std::shared_ptr<std::vector<std::string>> file;
};
class TextQuery{
public:
    using line_no = std::vector<std::string>::size_type;
    TextQuery(std::ifstream&);
    QueryResult query(const std::string&) const;
private:
    std::shared_ptr<std::vector<std::string>> file;
    std::map<std::string, 
            std::shared_ptr<std::set<line_no>>> wm;
};

TextQuery::TextQuery(ifstream &is):file(new vector<string>){
    string text;
    while (getline(is, text)){
        file->push_back(text);
        int n = file->size() - 1;
        istringstream line(text);
        string word;
        while (line >> word){
            auto &lines = wm[word];            //必须用引用
            if (!lines)
                lines.reset(new set<line_no>); //
            lines->insert(n);
        }
    }
}
QueryResult TextQuery::query(const std::string&sought) const{
    static shared_ptr<set<line_no>> nodata(new set<line_no>);
    auto loc = wm.find(sought);
    if (loc == wm.end())
        return QueryResult(sought, nodata, file);
    else
        return QueryResult(sought, loc->second, file);
}
ostream &print(ostream& os, const QueryResult &qr){
    os << qr.sought << " occurs " << qr.lines->size() << " "
       << (qr.lines->size()>1?" times": " time") << endl;
    for (auto num:*qr.lines)
        os << "\t(line " << num+1 << ") "
           << *(qr.file->begin() + num) << endl;
    return os;
}

void runQueries(ifstream& infile){
    TextQuery tq(infile);
    while(true){
        cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(cin>>s) || s == "q") break;
        print(cout, tq.query(s)) << endl;
    }
}
int main(){
    ifstream infile("./text.txt");
    runQueries(infile);
return 0;}

总结

  C++中,动态内存通过new表达式分配,通过delete表达式释放。标准库还定义了allocator类来分配动态内存块。
尽量不要用手动的方式管理动态,用智能指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值