《C++ Primer》 第十二章 动态内存

news2025/1/6 20:28:36

《C++ Primer》 第十二章 动态内存

动态内存与智能指针

shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象,weak_ptr指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。

shared_ptr类:默认初始化的智能指针中保存着一个空指针。

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

在这里插入图片描述

在这里插入图片描述

make_shared函数:在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。定义在memory中。

//指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int> (42);
//p4指向一个值为"999999999"的string
shared_ptr<string> p4 = make_shared<string> (10,'9');
//p5指向一个值初始化的int,即值为0
shared_ptr<int> p5 = make_shared<int>();
//通常使用auto定义一个对象来保存make_shared的结果
//p6指向一个动态分配的空vector<string>
auto p6 = make_shared<vector<string>>();

shared_ptr的拷贝和构造:

auto p = make_shared<int>(42);//p指向的对象只有一个引用者
auto q(p);//p和q指向相同对象,此对象有两个引用者

在这里插入图片描述

定义StrBlob类:

class StrBlob{
public:
	typedef std::vector<std::string>::size_type size_type;
	StrBlob();
	StrBlob(std::initializer_list<std::string> i1);
	size_type size() const { return data->size(); }
	bool empty() const { return 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;
};

元素访问成员函数:

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");
}

string& StrBlob::back()
{
    check(0, "back on empty StrBlob");
    return data->back();
}

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

直接管理内存:运算符new分配内存,delete释放new分配的内存。

使用new动态分配和初始化对象:new返回一个指向该对象的指针

int *pi = new int; //pi指向一个动态分配、并返回该对象的指针

string *ps = new string;//初始化为空string
int *pi = new int;//pi指向一个未初始化的int

int *pi = new int(1024);//pi指向的对象的值是1024
string *ps = new string(10,'9'); //*ps为“999999999”

//vector有10个元素,值依次从0~9
vector<int> *pv = new vector<int> {0,1,2,3,4,5,6,7,8,9};

string *ps1 = new string;//默认初始化为空string
string *ps = new string();//值初始化为空string
int *pi1 = new int; //默认初始化;*pi1的值未定义
int *pi2 = new int();//值初始化为0;*pi2为0

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

动态分配的const对象:用new分配const对象是合法的:

//分配并初始化一个const int
const int *pci = new const int(1024);
//分配并默认初始化一个const的空string
const string *pcs = new const string;

内存耗尽:

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

释放动态内存:销毁给定的指针指向的对象;释放对应的内存。

delete p;//p必须指向一个动态分配的对象或是一个空指针

指针值和delete:传递给delete的指针必须指向动态分配的内存,或者是一个空指针。

int i, *pi1 = &i, &pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;
delete i;//错误:i不是一个指针
delete pi1;//未定义:pi1指向一个局部变量
delete pd;//正确
delete pd2;//未定义:pd2指向的内存已经被释放掉了
delete pi2;//正确:释放一个空指针总是没有错误的

const int *pci = new const int(1024);
delete pci;//正确:释放一个const对象

动态对象的生存期直到被释放时为止:

//factory返回一个指针,指向一个动态分配的对象
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;//现在记得释放内存,我们已经不需要它了
}

Foo* use_factory(T arg)
{
    Foo *p = factory(arg);
    //使用p
    return p;//调用者必须释放内存
}

提供优先的保护:动态内存的一个基本问题是可能有多个指针指向相同的内存。

int *p (new int(42));//p指向动态内存
auto q = p;         //p和q指向相同的内存
delete p;			//p和q均变为无效
p = nullptr;        //指出p不再绑定到任何对象

shared_ptr和new结合使用:

shared_ptr<double> p1;//shared_ptr可以指向一个double
shared_ptr<int> p2(new int(42));//p2指向一个值为42的int

//不能将一个内置指针隐式转换为一个智能指针
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显式绑定到一个想要返回的指针上
shared_ptr<int> clone(int p){
	//正确:显示地用int*创建shared_ptr<int>
	return shared_ptr<int>(new int(p));
}

在这里插入图片描述

在这里插入图片描述

不要混合使用普通指针和智能指针;也不要使用get初始化另一个智能指针或为智能指针赋值。

shared_ptr<int> p(new int(42));//引用计数为1
int *q = p.get();//正确,但使用q时要注意,不要让它管理的指针被释放
{//新程序块
//未定义:两个独立的shared_prt指向相同的内存
	shared_ptr<int>(q);
}//程序块结束,q被销毁,它指向的内存本释放
int foo = *p; //未定义:p指向的内存已经被释放了

其它shared_ptr操作:

p = new int(1024); //错误:不能将一个指针赋予shared_ptr
p.reset(new int(1024)); //正确:p指向一个新对象

if(!p.unique())
    p.reset(new string(*p));//我们不是唯一用户;分配新的拷贝
*p += newVal;

智能指针和异常:

void f()
{
	shared_ptr<int> sp(new int(42));//分配一个新对象
	//这段代码抛出一个异常,且在f中未被捕获
}//在函数结束时shared_ptr自动释放内存

void f()
{
	int *p = 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时,可以传递一个可选的指向删除器函数的参数
void f(destination &d)
{
	//获得一个连接;记住使用玩后要关闭它
	connection c = connect(&d);
    shared_ptr<connection> p(&c, end_connection);
	//使用连接
	//如果我们在f退出之前忘记调用disconnect,就无法关闭c了
}

在这里插入图片描述

unique_ptr:与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对侠女。当unique_ptr被销毁时,它所指向的对象也被销毁。

unique_ptr<double> p1;//可以指向一个douvbble的unique_ptr
unique_ptr<int> p2(new int(42));//p2指向一个值为42的int
//由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1);//错误:unique_ptr不支持拷贝
unique_ptr<string> p3;
p3 = p1;//错误:unique_ptr不支持赋值

在这里插入图片描述

使用unique_ptr参数和返回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;
}

weak_ptr:是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。

在这里插入图片描述

创建一个weak_ptr时,要用一个shared_ptr来初始化它

auto p = make_shared<int>(42);
weak_ptr<int> wp(p);//wp弱共享p;p的引用计数未改变

if(shared_ptr<int> np = wp.lock()){//如果np不为空则条件成立
    //在if中,np与p共享对象
}

核查指针类:

//对于访问一个不存在元素的尝试,StrBlobPtr抛出一个尝试
class StrBlobPtr{
public:
	StrBlobPtr(): curr(0){ }
	StrBlobPtr(StrBlob &a, size_t sz = 0):
				wptr(a.data), curr(sz){ }
	std::string& deref() const;
	StrBlobPtr& incr();//前缀递增
private:
	//若检查成功,check返回一个指向vector的shared_ptr
	std::shared_ptr<std::vector<std::string>>
		check(std::size_t, const std::string&) const;
	//保存一个weak_ptr,意味着底层vector可能被销毁
	std::weak_ptr<std::vector<std::string>> wptr;
	std::size_t curr;//在数组中的当前位置
}

//StrBlobPtr的check成员与StrBlob中的同名成员不同,它还要检查指针指向的vector是否还存在
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::size_t i, const std::string &msg) const
{
    auto ret = wptr.lock();//vector还存在吗?
    if(!ret)
        throw std::runtime_error("unbound StrBlobPtr");
   if(i>=ret->size())
       	throw std::out_of_range(msg);
   return ret;//否则,返回指向vector的shared_ptr
}

指针操作:

//deref成员调用check,检查使用vector是否安全以及curr是否在合法范围内
std::string& StrBlobPtr::deref() const
{
	auto p = check(curr, "dereference past end");
	return (*p)[curr]; //(*p)是对象所指向的vector
}

//incr成员也调用check
//前缀地递增:返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr()
{
    //如果curr已经指向容器的尾后位置,就不能递增它
    check(curr, "increment past end of StrBlobPtr");
    ++cur;//推进当前位置
    return *this;
}

//对于StrBlob中的友元声明来说,此前置声明是必要的
class StrBlobPtr;
class StrBlob{
    friend class StrBlobPtr;
    //返回指向首元素和尾后元素的StrBlobPtr
    StrBlobPtr begin() { return StrBlobPtr(*this); }
    StrBlobPtr end(){
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
}

动态数组

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)
	/*处理数组*/;
	
char arr[0];//错误:不能定义长度为0的数组
char *cp = new char[0];//正确:但cp不能解引用

释放动态数组:

delete p;//p必须指向一个动态分配的对象或为空
delete [] pa;//pa必须指向一个动态分配的数组或为空

typedef int arrT[42];//arrT是42个int的数组的类型别名
int *p = new arrT;//分配一个42个int的数组;p指向第一个元素
delete []p;//方括号是必须的,因为我们当初分配的是一个数组

智能指针与动态数组:标准库提供了一个可以管理new分配的数组的unique_ptr版本。为了用一个unique_ptr管理动态数组,我们必须在对象类型后面跟一对空方括号。

//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,必须提供一个删除器
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
sp.reset();//使用我们提供的lambda释放数组,它使用delete[]

//shared_ptr未定义下标运算符,并且不支持指针的算术运算
for(size_t i=0; i!=10; ++i)
	*(sp.get() + i) = i;//使用get获取一个内置指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GUiCOZIp-1677828130213)(C:\Users\21147\AppData\Roaming\Typora\typora-user-images\1675233306285.png)]

allocator类:定义在头文件memory中,将内存分配和对象构造分离开来。当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置:

allocator<string> alloc;//可以分配string的allocator对象
auto const p = alloc.allocate(n);//分配n个未初始化的string

在这里插入图片描述

allocator分配未构造的内存:

auto q = p;//q指向最后构造的元素之后的位置
alloc.construct(q++);//*q为空字符串
alloc.construct(q++, 10, 'c');//*q为cccccccccc
alloc.construct(q++, "hi"); //*q为hi

cout<<*p<<endl;//正确:使用string的输出运算符
cout<<*q<<endl;//灾难:q指向未构造的内存!

拷贝和填充未初始化内存的算法:

在这里插入图片描述

//分配比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);

概念总结

为了更安全地使用动态对象,标准库定义了两个智能指针俩管理动态分配的对象。当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。

静态内存:保存局部static对象、类static数据成员以及定义在任何函数之外的变量。

栈内存:保存定义在函数内的非static对象。分配在静态或栈内存的对象由编译器自动创建和销毁。

对于栈对象,仅在其定义的程序运行时才存在;static对象在使用之前分配,在程序结束时销毁。

在这里插入图片描述

shared_ptr为什么没有release成员?

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/385419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker容器化部署.net core API

1.为API集成Docker环境。&#xff08;VS自带&#xff0c;傻瓜式操作&#xff09; 1.1 点击项目&#xff0c;右键&#xff0c;添加&#xff0c;选择Docker支持 1.2 找到项目根目录中的Dockerfile文件&#xff0c;这是VS刚刚帮我们自动生成的。进入和做如图标红地方修改。 把文…

map和set 的封装

文章目录引入key-value模型map和set底层setset的几个重要接口mapmap几个重要的接口map和set的封装引入 对于map和set的引入&#xff0c;我们用一道在程序中常见的问题解决&#xff1a; 给定一个数组int arr[]{1,2,1,3,1,4,1,5,5,2,3,4,5};&#xff0c;给出以下问题的解决方案&…

【基础算法】双指针---最长连续不重复子序列

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

html基础(列表(ul、ol、dl)、表格table、表单(input、button、label)、div和span、空格nbsp)

1无序列表<ul>和有序列表<ol>1.1无序列表<ul><!-- 无序列表 --><ul><li>吃饭</li><li>睡觉</li><li>打豆豆</li></ul>1.2有序列表<ol><!-- 有序列表 --><ol><li>吃饭</li…

电机控制中的2个重要变量(速度和转矩)

电机控制的其它相关内容,大家可以参看专栏的系列文章,链接如下: 运动控制系统(伺服3环)_运动控制三环控制周期_RXXW_Dor的博客-CSDN博客1 、这篇作为运动控制系列的第一篇吧,后续慢慢更新关于PLC的运动控制https://blog.csdn.net/m0_46143730/article/details/124075713…

阿里10年测开经验分享-我的软件测试之路也并不是一帆风顺

简单的先说一下&#xff0c;坐标西安&#xff0c;16届本科毕业&#xff0c;目前在跳槽&#xff0c;一共有面试了有5家公司&#xff08;因为不想请假&#xff0c;因此只是每个晚上去其他公司面试&#xff0c;所以面试的公司比较少&#xff09; 其中成功的有5家&#xff0c;另外2…

你不会工作1年了连枚举都还不知道吧?

文章目录一、枚举(Enum)1.1 枚举概述1.2 定义枚举类型1.2.1 静态常量案例1.2.2 枚举案例1.2.3 枚举与switch1.3 枚举的用法1.3.1 枚举类的成员1.3.2 枚举类的构造方法1&#xff09;枚举的无参构造方法2&#xff09;枚举的有参构造方法1.3.3 枚举中的抽象方法1.4 Enum 类1.4.1 E…

Azure OpenAI 官方指南02|ChatGPT 的架构设计与应用实例

ChatGPT 作为即将在微软全球 Azure 公有云平台正式发布的服务&#xff0c;已经迅速成为了众多用户关心的服务之一。而由 OpenAI 发布的 ChatGPT 产品&#xff0c;仅仅上线两个月&#xff0c;就成为互联网历史上最快突破一亿月活的应用。本期从技术角度深度解析 ChatGPT 的架构设…

大数据平台小结

搭建大数据平台启动流程1、启动Nginx服务&#xff08;在bdp-web-mysql服务中&#xff09;cd /usr/local/nginx/# 启动Nginx ./sbin/nginx# 查看端口是否存在 netstat -tunlp|grep 200012、启动zookeeper&#xff08;在bdp-executor-realtime123&#xff09;cd /app/bdp/apache-…

二.项目使用vue-router,引入ant-design-vue的UI框架,引入less

根据前文《使用Vue脚手架工具搭建vue项目》搭建好脚手架后使用 1.vue-router 2.引入UI框架ant design vue 3.引入less 1.vue-router vue-router分为两种模式(默认为hash模式)&#xff1a; hash history hash&#xff1a; 特征&#xff1a; 1.hash会在浏览器路径里带#号&#…

高质量数字化转型创新发展大会暨中国信通院“铸基计划”年度会议成功召开

2023年3月3日&#xff0c;由中国信通院主办的高质量数字化转型创新发展大会暨中国信通院“铸基计划”年度会议在北京成功召开。本次大会深度展示了中国信通院在数字化领域的工作成果&#xff0c;并全面展望了2023年行业的数字化发展趋势。同时&#xff0c;大会发布了中国信通院…

C语言入门知识——(7)VS2022的C语言基础调试

1、什么是bug 这个故事很多人都知道 1947年9月9日&#xff1a;第一个“Bug”被发现的时候&#xff1a;“1949年9月9日&#xff0c;我们晚上调试机器的时候&#xff0c;开着的窗户没有纱窗&#xff0c;机器闪烁的亮光几乎吸引来了世界上所有的虫子。果然机器故障了&#xff0c;…

Qt使用OpenGL进行多线程离屏渲染

基于Qt Widgets的Qt程序&#xff0c;控件的刷新默认状况下都是在UI线程中依次进行的&#xff0c;换言之&#xff0c;各个控件的QWidget::paintEvent方法会在UI线程中串行地被调用。若是某个控件的paintEvent很是耗时&#xff08;等待数据时间CPU处理时间GPU渲染时间&#xff09…

BI不是报表,千万不要混淆

商业智能BI作为商业世界的新宠儿&#xff0c;在市场上实现了高速增长并获得了各领域企业的口碑赞誉。 很多企业把商业智能BI做成了纯报表&#xff0c;二维表格的数据展现形式&#xff0c;也有一些简单的图表可视化。但是这些简单的商业智能BI可视化报表基本上只服务到了一线的…

【JAVA程序设计】【C00110】基于SSM(非maven)的车辆维修管理系统

基于SSM&#xff08;非maven&#xff09;的车辆维修管理系统项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架非maven开发的车辆维修管理系统共分为三个角色&#xff1a;管理员、用户 管理员角色包含以下功能&#xff1a; 查看用户、添加用户、查看车辆信息、故…

FPGA纯verilog实现图像视频旋转 串口指令控制旋转角度 提供工程源码和技术支持

目录1、前言2、理论基础3、设计思路和框架图像输入和采集图像旋转处理图像缓存图像输出4、vivado工程详解5、上板调试验证6、福利&#xff1a;工程代码的获取1、前言 图像旋转是一种常用的图像处理技术&#xff0c;其基本原理就是指图像以某一点为中心旋转一定的角度&#xff…

函数的极限

目录 函数极限的定义&#xff1a; 数列的极限和函数极限 定理1&#xff1a; 自变量趋向有限制时&#xff0c;函数的极限 左右极限&#xff1a; 定理&#xff1a; 需要分左右极限求极限的三种问题&#xff1a; 例题&#xff1a; 例2&#xff1a; 极限性质&#xff1a; 保号…

WebRTC标准与框架解读(1)

1、如果让我来设计webrtc框架我在分析源码的时候&#xff0c;都喜欢做这样一件事情&#xff1a;如果让我来设计它&#xff0c;我会怎么做&#xff1f;大家可以紧跟我的思路&#xff0c;分析一下WebRTC为什么如此设计。为了对整个框架有有一个全面的了解&#xff0c;我们首先要做…

外包测试3年,离职后成功入职华为,拿到offer的那天我泪目了....

一提及外包测试&#xff0c;大部分人的第一印象就是&#xff1a;工作强度大&#xff0c;技术含量低&#xff0c;没有归属感&#xff01;外包工作三年总体感受就是这份工作缺乏归属感&#xff0c;心里总有一种落差&#xff0c;进步空间不大&#xff0c;接触不到核心技术&#xf…

IO详解(文件,流对象,一些练习)

目录 文件 文件概念 文件的路径 路径有俩种表示风格 文件类型 如何区分文本文件还是二进制文件? java对文件的操作 File类中的一些方法 流对象 流对象的简单概念 java标准库的流对象 1.字节流,(操作二进制数据的) 2.字符流 (操作文本数据的) 流对象最核心的四个…