参考自《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修饰的成员,然后用构造函数的成员初始化列表.

#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(); 这样,对象就会调用它的析构函数,且不会回收成员所占的内存了。

一个简单的例子:

#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++ 学习笔记之---类和动态内存分配相关推荐

  1. C语言学习笔记10-指针(动态内存分配malloc/calloc、realloc、释放free,可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)

    C语言:指针 1. 指针:保存地址的变量 *p (pointer) ,这种变量的值是内存的地址.   取地址符& 只用于获取变量(有地址的东西)的地址:scanf函数-取地址符   地址的大小 ...

  2. 【C语言进阶学习笔记】五、动态内存分配(爆肝吐血力作,强烈建议收藏!!!)

    前言 现代计算机基本都是基于冯诺伊曼结构体系设计出来的,冯诺伊曼结构体系的核心就是"存储程序",将程序(指令集)和数据以同等地位存储在内存中.但是我们的内存空间并不是无限大的,所以 ...

  3. 梓益C语言学习笔记之链表&动态内存&文件

    梓益C语言学习笔记之链表&动态内存&文件 一.定义: 链表是一种物理存储上非连续,通过指针链接次序,实现的一种线性存储结构. 二.特点: 链表由一系列节点(链表中每一个元素称为节点)组 ...

  4. 《C++ Primer Plus》读书笔记之十—类和动态内存分配

    第12章 类和动态内存分配 1.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存.可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的 ...

  5. C++ Primer Plus学习(十一)——类和动态内存分配

    类和动态内存分配 动态内存和类 静态类成员 特殊成员函数 string类的改进 构造函数中的new 返回对象 指向对象的指针 成员初始化列表(member initializer list) 动态内存 ...

  6. C++类与动态内存分配

    11.10 类与动态内存分配 通常,最好是在程序运行时(而不是编译时)确定诸如使用多少内存等问题.对于在对象中存储姓名来说,通常的C++方法是,在类构造函数中使用new运算符在程序运行时分配所需的内存 ...

  7. 第12章类和动态内存分配

    第12章类和动态内存分配 (1) class student {char name[40];//并不是每一个字符串都是40//如果是一个对象数组,则浪费空间 }; 12.1 (1)静态成员在类声明中声 ...

  8. 读书笔记||类和动态内存分配

    一.动态内存和类 C++在分配内存的时候是让程序是在运行时决定内存分配,而不是在编译时再决定.C++使用new和delete运算符来动态控制内存.但是在类中使用这些运算符将导致许多新的编程问题,在这种 ...

  9. 第12章-cpp类和动态内存分配

    本章内容包括: • 对类成员使用动态内存分配. • 隐式和显式复制构造函数. • 隐式和显式重载赋值运算符. • 在构造函数中使用new所必须完成的工作. • 使用静态类成员. • 将定位new运算符 ...

最新文章

  1. oracle执行计划连接方式
  2. fillna函数_听说这些pandas函数,是数据科学家和软件工程师的最爱
  3. linux 分区 文件,Linux的分区与文件结构
  4. python默认参数陷阱_python默认参数陷阱
  5. MyBatis接口代理
  6. 关于do{}while()的代码讨论
  7. Atitit mybatis spring整合。读取spring、yml、文件的mysql url 步骤,读取yml,文件,使用ongl定位到url pwd usr 读取mybatis模板配置,
  8. 线下商店销量预测挑战赛
  9. 从零开始的FPGA学习4-比较器、全加器
  10. Could not find artfact com.oracle:ojdbc7:jar:12.1.0.2.0 in nexus-aliyun
  11. We7务实性电子政务与‘云’
  12. 暑假旺季到了,如何选择酒店呢
  13. 2022年全球市场木材干燥窑总体规模、主要生产商、主要地区、产品和应用细分研究报告
  14. doc创建计算机用户,问什么我电脑一直创建这些文件夹?$RECYCLE.BIN qqpcmgr_docpro System Volume Information...
  15. 英语翻译的重点词汇词组
  16. 01-RobotStudio新建系统
  17. JVM中的monitorenter和monitorexit
  18. 【Spring MVC】mvc详解
  19. GPS跟踪载波环matlab代码,GPS接收机载波跟踪环路解决方案
  20. mysql from 嵌套查询,MySQL嵌套查询实例详解

热门文章

  1. 坚持每天学英语10分钟,5年弹指一挥间
  2. html5前端开发做什么,Web前端能做什么开发
  3. 数码摄影技巧拍摄的基本概念
  4. 比特安详细解析Fantasm Finance事件:注重细节,才能历久弥新
  5. defy系统自带软件列表
  6. nmap学习2--端口扫描
  7. 【转载】嵌入式Linux学前建议(初学者必读)
  8. Qt自定义控件--pagenavigation(页面导航)
  9. 不定积分——辅助角公式(asinx+bcosx)
  10. 教你如何快速将音频文件AAC转换成MP3格式