当前课程知识点:C/C++:从基础语法到优化策略 > Final Exam > Final exam > 5.3 Allocate memory: C++ style
返回《C/C++:从基础语法到优化策略》慕课在线视频课程列表
前面我们讲了采用C语言风格的malloc函数和free函数去申请和释放内存
那么在这一部分我们再来看一看C++的函数里面或者操作里面怎么去申请和释放内存
在C++里面我们不采用malloc去申请
而我们采用的是new关键字去来去申请
new的话跟前面的malloc比有什么优势呢
我认为是他更加的简洁一些
你如果采用malloc其实相对来说他的操作更低级
也就是你需要首先去计算你需要多少个字节
因为malloc的参数是字节数
所以你要去先算好多少个字节 然后再去申请
在new里面相对简单一点 比如说我们可以这样new int
这样的话实际上他就去动态申请一块内存
这个类型的对于int来讲就是四个字节
然后申请四个字节 然后赋给这个指针
这个里面既然你是new int的话
你不需要malloc里面的那样的强制类型转换那这个例子
那么到底申请多少个字节 那么new自动来计算 根据int的大小 自动来计算
而不用去让程序员用sizeof去计算
那么在这个例子里面 我们一旦给ptr_int申请了这个内存之后
我们如果要去对这块内存进行赋值
那么我们可以比如说1赋给指针指向的这个地址的内容
这个指针指向的内容就会变为1
那我们来看一看这个例子
好在这个例子里面我们来看
首先我们定义了一个变量叫nights
nights 是1001他的值
然后我们new int 赋给pt 那么pt我们也赋一个值是1001
这个时候变量nights和pt地址指向的那个内容都是1001
那么我们可以把它们打印出来
nights的值打印出来nights的地址打印出来
pt指向的内存里面存的那个值打印出来
pt这个地址把它打印出来
我们来可以看一看
那么我再来往后看 后面的话我们同样的
我们申请了一个double类型new double 然后赋给pd这个指针
然后我们对这个申请的内存赋值
赋一个一百万零一 自然数值
然后我们再把这个pd的存的值和pd这个地址打印出来
然后我们也可以对pd再取个地址
再取个地址 也就是说pd本身是个地址了
其实它既是一个地址 它也是一个变量
pd你也可以看成一个变量 那么我对这个变量再取一次地址
它的地址跟这个地址是不是一样的呢
那么后面来说
那么最后就是打印出来之后 我们再把pt的sizeof打印出来
sizeof(*pt)打印出来 pd打印出来和这个打印出来
我们最后 我们去运行一下 看一下
好我们来看这个输出结果 nights的值是1001
他的location他的地址也就是这里
int value这个地方是一个指针
我们申请的指针也是1001存的内容一样 但是他们的地址跟前面是不一样的
我们可以看出来他们还差的很远 离的很远
因为地址差异很大
那么double也是一样的 是1乘以十的七次方
然后他的地址是在这里
所以说这三个地址皆不相同
那么我们再来看
存pd的这个变量的地址 也就是对pd取了地址后 他跟前面是不是一样的
是不一样的 因为取地址实际上是pd
这个变量的地址实际上是变成地址的地址
大家要想清楚这个逻辑 它是地址的地址
地址这个变量的地址
在这个时候你把pd看成一个普通的变量 也就是在这里
pd虽然是一个指针 你可以把它看成一个普通的变量 然后&取地址
好那么我们来看sizeof(pt)
我这个电脑是64位的 所以sizeof(pt)这个指针他是8个字节
那么*pt那就是一个整数了
那这个整数就四个字节 那 pd 他是地址八个字节
这个地方他是个double类型 新pd是个double类型 double是占八个字节 就是他的意思
好 我们来看我们前面用malloc函数的时候
我们使用free函数去释放内存
在C++里面 我们如果用new申请的话 我们用delete去释放
去delete去释放 那么一定记得new和delete要成对出现
要成对出现 有一次new 一定记得要有一次delete
一定要成对出现 否则的话还是跟C语言一样你只去malloc不去free的话
和只new不delete实际上是一样的结果
所以一定记得去释放 否则的话
你如果不释放你的内存会被耗光
也说这个内存可能这个ps变量你申请了赋一个int值 下次又把ps变量去赋一个
这时候第一次的申请的内存 那就没有变量指向这块内存了
这内存你就再也无法去回收 去释放了
再强调一点就是 如果ps这个变量new int
然后你再运行是ps = new int
那么第一次申请这块内存 你就再也找不回来了
因为没有变量指向这块内存
所以这样就会造成memory leak内存泄露
所以内存泄露就是有一些内存你不用了 但是你又无法去回收了
然后就导致这个问题
这个里面在debug的时候也是有困难的 为什么呢
如果你有很轻微的内存泄露 非常少
你的程序可能你跑一跑也看不出来
因为它只是内存微涨了一点点
那么有可能在程序运行几天之后慢慢把内存耗光了
所以这个也是一个非常难debug的问题
大家在编程的时候一定要紧绷这个自己的注意力
一旦有new 了你一定要去找一找他哪里去delete
一定要去找到那个delete的地方
然后当然一些注意事项就像这样展示的
我们new一块内存 然后delete它 没问题
如果delete两次是不行的他只能释放一次
还有这个int 这是个变量
这个变量我们取地址付给pi
在这种情况下 pi的这个地址实际上是int这个变量
这个时候他不是动态申请的 我们不需要去delete
这边是不需要的
前面我们讲的这个例子里面 new 只是申请一个元素
一个int或者一个double
如果我们要申请多个元素或者说创建一个动态数组是怎么做
那么可以这样做 数组我们之前学的数组就是一个静态数组
直接int a[10]; 这样去写
那它是一个静态的它是在编译的时候就把它建好了申请好了
那么我们这里说动态 就是在运行的时候把这个数组创建出来
我们需要是用new 当然用malloc是一样的 是没问题的
new int[10]这样他就创建一个数组 有十个int元素
这个地方就比malloc好一点 你不需要去自己去算多少个字节
那么这是申请 释放的时候大家记得如果你这加个方括号
那么你delete的时候一定要加
为什么要加这个方法号呢
你可以认为他是个规范 另外的话
如果是int类型 这个到问题不大 你加不加都问题不大
但是如果是一种对象某个类的类型
那可能你不加方括号和加方括号它的行为是不一样的
我们后面的讲到类的时候再来讲这个问题
所以有些注意事项delete的时候不要delete这个
没有申请的内存不是new申请的就不要去delete它了
然后也不要释放两次
如果你new加了方括号 那么你delete也要加
那么如果你只是new了一个单的实体
那么这个时候你可以不用加方括号
所以最后一条就说你去delete一个空指针是没问题的
因为delete才会发现他是空的 他不会做
然后什么都不做就就结束了
我们来看一看这个动态数组的申请 动态数组的申请的话
前面讲了我们可以这样new double[3]
这样就申请了三个
同样的我们前面提过对指针是可以做加法和减法的操作的
我们可以对p3这个指针加一也可以对p3减一
那么p3加一和减一分别会造成什么样的后果呢
语法是没问题的会怎么样呢
我们可以来看看这个例子我们来看
这个例子首先我们定义了一个指针p3
p3是什么 p3是一个数组 动态申请的数组
有三个元素 每个元素都是double类型的 也是八个字节
那么我们把第一个元素零号编号的元素赋值0.2
第一号赋值0.5 0.8
大家注意 在这我们可以看到动态申请的数组
它操作起来跟静态的实际上是一样的
都是这样去读和写 他加个方括号加个索引就可以取和和写元素
那我们可以打印p3[1] 那打印出来应该是0.5对吧
那就没问题 这个时候我们p3 + 1
加一我们知道 p3是个地址 如果我们对他进行加一的话
实际上他的这个指针会移八个字节
他会往后移八个也就是是这样的话 他取的元素实际上是0.5
加一实际上是p3的值 如果你把它地址打印出来 你会发现它的地址的值被加了8
所以说如果是p3 + 1之后 那么p3这个指针其实就指向了这个元素这个位置
因为初始的p3是指向这 那这时候p3就指向这了 0. 5了
那这个时候p3[0]他就应该返回0.5
p3[1]应该返回0.8 那么p3[2]就越界了
同样的 我们可以把它再减回去 再一减又回到原来的值了
然后我们可以把它释放掉
释放的时候一定要先减回去再去释放它
好我们来看一看这里面是不是如我们刚才所说的
我们加一之后 p3[0]就会变成0.5 p3[1]变成0.8 我们来看一看
我们运行一下p3[1]初始是0.5
那么加一之后 p3[0]就是0.5 那p3[1]就是0.8 那这呢
我再打印一下 比如说我们还可以把这个值给打印一下 p3
这个是加一之后的值 然后减一之后
cout我们的p3
那我们就可以把打印出来 我们还可以在加之前也可以打印出来
我们打印了三次
第一次打印就是他的申请的时候 刚申请的内存就是new返回来的这个值p3
然后加一然后再打印一次 减一再打印一次 我们来看一看
这个地方没打印出来
我没保存不好意思
好现在我们来看 初始的地址是这样的
我们看后面尾数是个40加一之后就变成48
变成48他挪了8个字节
挪了整整的一个浮点数的字节数8个 再减一又变成了40
所以说我们看到加一减一 实际上是挪八个字节
好这页就是刚才我们要说的
加一减一在double的时候 他会一次就挪八个
比如说在这个例子里面 我们存着一万二万三万
那么在申请内存的时候 比如说那就申请了
这个三个double类型的 那就是这样324个字节
从100-124到这
那么第二个321它是一个shot 那每一个是两个字节 那这样
那么在这个时候我们把wages赋给pw stacks赋给ps
那么pw 就指向这个 就是假如说它的地址是100那pw加一就是108
ps指向了这个初始地址124它加二就是126
这是他们加和减的区别
这还有个例子就是指针的加减
时间关系 我们先不讲大家课后可以去练习一下
我们注意的时候 就是说我们的指针跟数组有什么区别
指针和数组在去读写元素的时候都是一样的
加个方号就可以取 但是他们是不同的
如果去改变指针 我们可以改变指针
我们pw + 1这样然后再赋给pw 就是自动量
这样去做是可以的 如果你是数组
wages这个你去加一再赋给他自己的话 这个是不可以这样做
这是禁止这样做的
第二个就是sizeof操作 这个是很容易令人混淆的 在这里
这double wages三个元素
如果用sizeof去判断位置的大小 那么它将返回数组的大小
也就是三乘以八二十四sizeof
但是如果你sizeof(pw) 它返回的因为pw是个指针
指针不是数组 这个时候就你如果sizeof指针
那么他就会返回指针地址所占的字节数
那么sizeof六十四位的系统那就是八
short ps你去sizeof也是八
但是你sizeof(stacks)这个地方是三乘以二 是六
这是他们的不同之处 总之要特别小心
那么最后这部分
这部分我们来看前面我们使用new都是创建的基本数据类型的数组
我们还可以用new去创建动态申请就是一个struct 是一个结构体
比如说new一个结构体的名字 然后赋给这个ps 那么
采用new去这样去创建出来的这个结构体
ps是个指针 你要去操作它里面的元素的时候不再使用点 而使用箭头
我们来看一个简单的例子
在这个例子里面 我们有定义的一个结构题 inflatable
那么这里面会有三个元素 name volume price
然后我们可以定义一个指针ps
这个指针的类型是inflatable指针这样一个类型
然后我用new申请一个结构体
然后创建一个实例 也就是创建一个对象
把这个地址赋给ps
那么这个ps就指向了这个结构体的首地址
那么也就是说这个结构体现在有一个实例了 也有个对象了
那么我们就可以取它里面的元素 name怎么取
那ps->name 取他第二个volume 第二个的话
我们如果不想用箭头想用点
那么我们这要加*
这两种都是可以的都是可以的都是对的
但是我习惯于第一种写法
price你可以直接这样去读和写 读和写都是这样的
这样我们就不去运行它演示了
好那么我们再来讲讲delete
我们来看一个例子吧 用这个例子来讲 delete 怎么去做
我们来看这个string
这个例子来展示如何使用delete
这个例子其实我认为这不是一个很好的例子
但是在有些情况下我们可能需要这样去用
在这里面我们有main函数 我们还定义一个getname函数 getname函数是干嘛的
我们来看一下getname函数里面 首先我定义一个静态数组
然后打印Enter last name 然后把这个数组
然后从标准输入读数据读一个字符串传给这个静态数组
实际上这个函数它本意是想把用户读进去的last name数组返回来
那么返回来你不可能把这个temp返回来
因为你如果吧temp返回的话 temp这个变量只是在这个函数内有效
函数一返回这个变量就消失被释放量
所以那怎么办 他既然要释放
我们知道动态申请的内存 除非你手动释放 他不会被释放了
所以我们就想了个办法 我们定义一个
我们动态申请一个内存 申请多少呢 就是strlen 你输入的长度加一因为我们要加一个零
然后把刚才用户输入的这个静态数组的内容拷贝到pn这个数组里面动态数组里面
然后把pn返回回来 也就是说这个函数里面执行了一次new操作你又操作返回回来
那么我们在这的时候 我们就可以直接定义一个指针name
name就用getname去赋值返回来
然后我们就可以把它这个什么
这个打印出来
我们可以把它打印出来
然后我们可以去打印它的地址等等
然后也可以把它删掉
这个地方大家可以看
这个变量指向一块内存 但是在这被释放了
很好没问题 那我们可以继续的再去申请一块内存
赋给name 然后再去打印 什么都执行两次 这都是没问题的
但是切记不要只delete 如果你只delete一次的话
只去delete一次 那么这个语法上这个程序是没有错误的
但是你第一次申请的内存再也没有办法去释放了
因为name本来存着指向这块内存的地址这时候name的地址被改成第二块内存
那么第一块内存就没有变量可以去找到他了
所以说是不能去释放的
所以当你的程序逻辑比较复杂的时候 特别是内存管理比较复杂
一定要想清楚在哪儿申请 在哪儿释放
所以说在刚才的例子里面 我们的new和delete不在同一个函数里
一般不要这样做 一般最好是配对出现
那这个函数申请就在这个函数里释放
如果你有个函数专门是用来申请的 那么你记得就是去 一定要记住要去释放它
好 这个是我们刚才讲的 这些都是
我们现在刚才讲的都是用C++里面的申请
那么也是C++里面的内存管理
大家需要多做练习 把你的想法去猜测 去了解
然后多练习验证 多写代码 去验证你的想法 对不对
这个指针 这个使用一定要熟练掌握
否则话 你后面高级一点的知识点是永远无法去精通的
-Quiz 1
-Quiz 2
-3.3 Relational expressions (> < ==)
-3.5 Branching statements (if else)
-3.8 break and continue statements
-Quiz 3
-4.4 Structures, Unions and Enumerations
-Quiz 4
-5.3 Allocate memory: C++ style
-Quiz 5
-6.3 Recursion and pointer to functions
-Quiz 6
-Quiz 7
-8.1 C/C++ with ARM development board
-Quiz 8
-9.2 Constructors and destructors
-Quiz 9
-10.1 Operators in cv::Mat in OpenCV
-10.4 Automatic conversions and type casts for classes
-Quiz 10
-11.1 Dynamic memory and classes
-11.2 New and improved String class
-11.3 Using pointers to objects
-Quiz 11
-12.2 Static and dynamic binding
-12.3 Access control: protected
-12.4 Inheritance and dynamic memory allocation
-Quiz 12
-13.1 Constructor and assignment
-13.2 Classes with object members
-Quiz 13
-14.1 CNN for image classification
-Quiz 14
-15.3 RTTI and type cast operators
-Quiz 15
-Final exam



