C++ 学习笔记之---类和动态内存分配
参考自《C++ Primer Plus 6th Edition》
程序对内存的使用:
链接:http://zhidao.baidu.com/link?url=An7QXTHSZF7zN9rAuY05mvaHHar0xIpgK6Yqp9oAkm2GmZYoTAz9UpN4JuhWJvSLsbu0-lOcO47PzXcNWda6gK
1. 栈区 (stack) - 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。
2. 堆区 (heap) - 在内存开辟另一块存储区域。一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3. 全局区 (静态区) (static) - 编译器编译时即分配内存。全局变量和静态变量的存储是放在一块的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束后由系统释放。
4. 文字常量区 - 常量字符串就是放在这里的。程序结束后由系统释放
5. 程序代码区-存放函数体的二进制代码。
定义静态成员变量:
可以在类声明中定义静态成员变量,使用 static 修饰。不过,虽说是成员变量,但是不属于这个类的任何一个对象。它们是是分开存储的。
因为对所有对象,这个变量的值都是一样的,存储上也只用存一份就好。访问的时候,使用 "className::varName" 即可。绝大多数语言中可以定义静态变量,只是法上稍有不同。Java中的静态变量,既可以通过对象来访问,也可以通过类来访问。C++中就只能通过类名来访问。不过,Java通过对象来访问静态变量,实质上是通过类名来访问的。好吧,这个问题无关痛痒。
其次,C++不允许在类声明中初始化静态成员变量。而且初始化的时候要使用作用域运算符,"className::varName"。一种"内部"的感觉。
在类中定义常量:
1. 编译时确定的常量
存储: 对所有对象而言,这个常量都是一样的。因此和对象分开存储,仅保留一份副本。
实现: 1. 枚举: 如 enum {SIZE = 100 }; 这就定义了一个枚举常量 SIZE = 100。
当然,你可以定义多个,并给定类型名。
2. 静态成员变量: 如 const static int a = 5;
3. 用const限定并初始化, 如声明成员 const int id = 5 (C++ 11 拓展)
2. 运行时确定的常量
存储: 不同对象,可以有不同常量,属于对象的普通成员
实现: 声明用const修饰的成员,然后用构造函数的成员初始化列表.
![](/assets/blank.gif)
![](/assets/blank.gif)
#include <iostream>class Student { public:const int id;Student(int ID) : id(ID) {} };int main() {Student a(10); // a的id常量为10Student b(20); // b的id常量为20std::cout << a.id << " " << b.id << std::endl; }
View Code
成员初始化列表的初始化工作,是在对象创建后,构造函数函数体的代码执行前做的。对于内置类型成员的初始化,不管是放在初始化列表中初始化,还是放在函数体中初始化,效率是一样的。不过,对于对象成员来说,使用初始化列表来初始化,效率更高。暂且不提。要注意的一点是: 成员初始化列表只能用于构造函数。
复制构造函数 与 赋值运算符:
函数原型:
copy constructor: className (const className &)
assignment operator: className& operator=(const className &)
当定义的类,有指针成员,且使用new初始化的时候,需要定义"深拷贝"的复制构造函数和赋值运算
符。(暂不考虑定位new,因为常规new申请的内存位于堆中,需要程序员手动delete。而定位new申请的内存地址是自行指定的,如果定在堆,则情况相同。如果定义在静态内存中,那就没我们的事儿了。交给OS 吧)
基本概念:
深拷贝:
将一个对象拷贝给另一个对象的时候,被赋值的对象存储赋值对象的一个额外副本。若类成员中含有指针成员,且用new初始化的时候,被赋值的成员,会申请一块内存,将赋值对象的指针成员所指的内存的内容复制到这块内存中。两个指针各自指向自己申请的内存。
浅拷贝:
和深拷贝相似,浅拷贝对于非指针成员都是直接赋值。但是当类成员中含指针成员,且用new初始化的时候,被赋值的成员指针并不会额外申请一块内存,而仅仅是将自己指向赋值对象的指针成员所指的那块内存。两个指针指向同一块内存。
当我们没有定义类的复制构造函数和赋值运算符时,编译器会生成默认的版本,它们使用浅拷贝。
回到上面所说的,为什么我们需要定义"深拷贝"的复制构造函数和赋值运算法捏 ? 难道,是因为默认的浅拷贝会导致错误 ?
没错! 我们知道,如果定义的类中含指针成员,如果它将会使用new申请新内存。在析构函数中,我们会用delete释放相应的内存占用。
考虑两种情况:
1. 一个对象使用另外一个已有对象初始化,这样将调用默认复制构造函数(有可能还会调用赋值操作符,视编译器而定)。由于使用浅拷贝,就会存在这两个对象的指针成员指向同一块内存的情况,当这两个对象弃用时,会调用它们的析构函数。这样会出现同一块内存被释放两次的情况,出现未知的错误。
类似地,如果你定义了一个返回对象的函数,也会造成同一块内存释放两次的情况,为啥 ? 因为这还将调用复制构造函数,按值传递意味着创建原始变量的一个副本。caller和这个函数(callee)中的对象的指针指向同一块内存。当函数返回的时候,函数中的这个对象要被kill掉,调用析构函数了,释放掉占用的内存... 放心,这些都不会告诉你的。嗯,当caller中的那个对象析构时,那块内存又被释放了一次... 仍然是不可预知的错误。类似地,创建临时对象的时候,也会调用复制构造函数,这将发生同样的趣事--同样的奇怪的错误。
2. 两个已有对象之间的赋值,这将调用默认赋值运算符函数。后面的情况和1相同,都是浅拷贝闹的--两个对象的指针指向同一块内存,然后被释放两次。
啰嗦一句,“当定义的类中含有指针成员,且使用常规new(或定位new,定位在申请的堆内存中)初始化的时候,需定义深拷贝的复制构造函数和赋值操作符”,不然会被外星人抓走。
其他的的内存分配、回收问题
将涉及定位new的使用。(不考虑内存不够用的情况)
. 如果使用定位new运算符,定位在静态内存中,就不必释放了 (交给OS吧)
. 如果先用常规new运算符,申请了一块堆内存。然后,再使用定位new运算符在这块堆内存中为我们的对象申请内存捏 ?
这种情况下,你却不能delete这些对象。因为,对对象指针执行delete操作,不仅会调用析构函数,而后还会回收成员所占用的内存。你如果delete了这个对象,然后又delete那块堆内存,就会造成某些内存被释放两次的情况 (正是原来存放对象成员的内存)。
但是! 也因为你没有delete这些对象,这些对象是不会调用析构函数的。万一调用析构函数是必须的 (比如: 对象中有一个指针成员,该指针成员指向了一块用常规new申请的另外一块堆内存,不调用析构函数,这一块内存不就无法回收了吗 ? 飘渺孤鸿影~ 寂寞开无主~ 又恨又爱的孤岛内存~ )
但是! 解决方法还是有的,我们可以显式调用析构函数啊 ! 像这样: p->~className(); 这样,对象就会调用它的析构函数,且不会回收成员所占的内存了。
一个简单的例子:
![](/assets/blank.gif)
![](/assets/blank.gif)
#include <iostream> #include <string> #include <new> using namespace std;class Student { private:string name; public:Student(const string& s): name(s) {}~Student() {cout << name << " destroyed\n";} };int main() {double * buffer = new double[512];Student *s1 = new (buffer) Student("Peter");Student *s2 = new (buffer + sizeof(Student)) Student("Tom");/* 下面两条语句将引发错误,后面delete[] buffer,* 导致同一块内存被释放两次*///delete s1;//delete s2;/*显式调用析构函数, 这里按栈的顺序了,其实都行,不走寻常路 o_O */s2->~Student();s1->~Student();delete[] buffer;return 0; }
View Code
转载于:https://www.cnblogs.com/zhangzph/p/4542767.html
C++ 学习笔记之---类和动态内存分配相关推荐
- C语言学习笔记10-指针(动态内存分配malloc/calloc、realloc、释放free,可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)
C语言:指针 1. 指针:保存地址的变量 *p (pointer) ,这种变量的值是内存的地址. 取地址符& 只用于获取变量(有地址的东西)的地址:scanf函数-取地址符 地址的大小 ...
- 【C语言进阶学习笔记】五、动态内存分配(爆肝吐血力作,强烈建议收藏!!!)
前言 现代计算机基本都是基于冯诺伊曼结构体系设计出来的,冯诺伊曼结构体系的核心就是"存储程序",将程序(指令集)和数据以同等地位存储在内存中.但是我们的内存空间并不是无限大的,所以 ...
- 梓益C语言学习笔记之链表&动态内存&文件
梓益C语言学习笔记之链表&动态内存&文件 一.定义: 链表是一种物理存储上非连续,通过指针链接次序,实现的一种线性存储结构. 二.特点: 链表由一系列节点(链表中每一个元素称为节点)组 ...
- 《C++ Primer Plus》读书笔记之十—类和动态内存分配
第12章 类和动态内存分配 1.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存.可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的 ...
- C++ Primer Plus学习(十一)——类和动态内存分配
类和动态内存分配 动态内存和类 静态类成员 特殊成员函数 string类的改进 构造函数中的new 返回对象 指向对象的指针 成员初始化列表(member initializer list) 动态内存 ...
- C++类与动态内存分配
11.10 类与动态内存分配 通常,最好是在程序运行时(而不是编译时)确定诸如使用多少内存等问题.对于在对象中存储姓名来说,通常的C++方法是,在类构造函数中使用new运算符在程序运行时分配所需的内存 ...
- 第12章类和动态内存分配
第12章类和动态内存分配 (1) class student {char name[40];//并不是每一个字符串都是40//如果是一个对象数组,则浪费空间 }; 12.1 (1)静态成员在类声明中声 ...
- 读书笔记||类和动态内存分配
一.动态内存和类 C++在分配内存的时候是让程序是在运行时决定内存分配,而不是在编译时再决定.C++使用new和delete运算符来动态控制内存.但是在类中使用这些运算符将导致许多新的编程问题,在这种 ...
- 第12章-cpp类和动态内存分配
本章内容包括: • 对类成员使用动态内存分配. • 隐式和显式复制构造函数. • 隐式和显式重载赋值运算符. • 在构造函数中使用new所必须完成的工作. • 使用静态类成员. • 将定位new运算符 ...
最新文章
- oracle执行计划连接方式
- fillna函数_听说这些pandas函数,是数据科学家和软件工程师的最爱
- linux 分区 文件,Linux的分区与文件结构
- python默认参数陷阱_python默认参数陷阱
- MyBatis接口代理
- 关于do{}while()的代码讨论
- Atitit mybatis spring整合。读取spring、yml、文件的mysql url 步骤,读取yml,文件,使用ongl定位到url pwd usr 读取mybatis模板配置,
- 线下商店销量预测挑战赛
- 从零开始的FPGA学习4-比较器、全加器
- Could not find artfact com.oracle:ojdbc7:jar:12.1.0.2.0 in nexus-aliyun
- We7务实性电子政务与‘云’
- 暑假旺季到了,如何选择酒店呢
- 2022年全球市场木材干燥窑总体规模、主要生产商、主要地区、产品和应用细分研究报告
- doc创建计算机用户,问什么我电脑一直创建这些文件夹?$RECYCLE.BIN qqpcmgr_docpro System Volume Information...
- 英语翻译的重点词汇词组
- 01-RobotStudio新建系统
- JVM中的monitorenter和monitorexit
- 【Spring MVC】mvc详解
- GPS跟踪载波环matlab代码,GPS接收机载波跟踪环路解决方案
- mysql from 嵌套查询,MySQL嵌套查询实例详解