博客
关于我
【C/C++基础进阶系列】C/C++ 对象模型 -- 类基础知识总结(三)
阅读量:351 次
发布时间:2019-03-04

本文共 12605 字,大约阅读时间需要 42 分钟。

【C/C++基础进阶系列】C/C++ 对象模型 -- 类基础知识总结(三)

【1】new/delete

#include 
#include
using namespace std;class A{public: A() { cout << "A" << endl; } int m_i;};/** * 知识点 * 1. new 一个对象实际上是在堆上分配内存 * new 出的对象必须用 delete 释放内存空间,否则会造成内存泄漏 */int main(){ { /** * new 出的 int 对象没有名即无名对象 * new 可以返回一个指向该对象的指针 * 从而通过该指针操作这个无名int对象 * * 内置类型对象在未赋初值时其初值是未定义的 */ int *pointi = new int; // pointi指向一个int对象 /** * new 出的是 string 类类型的对象 * 此时会调用 string 类的构造函数进行初始化 */ string *mystr = new string; delete pointi; // 请务必将指针置为空指针否则会产生悬空指针 pointi = nullptr; delete mystr; mystr = nullptr; } /** * 演示了 new 一个对象的同时对对象进行初始化的操作 */ { int *pointi = new int(100); // 跟踪调试,指针指向的值变成了100 string *mystr2 = new string(5, 'a'); // 生成5个a的字符串,调用的是符合给进去的参数的string构造函数来构造出合适的字符串内容 vector
*pointv = new vector
{1, 2, 3, 4, 5}; // 一个容器对象,里面有5个元素,分别是1,2,3,4,5 delete pointi; pointi = nullptr; delete mystr2; mystr2 = nullptr; delete pointv; pointv = nullptr; } /** * 值初始化 */ { string *mystr2 = new string(); // “值初始化”,感觉和string *mystr = new string;效果一样,总之最终字符串内容为空("") int *pointi3 = new int(); // 值被初始化为0,这个()加与不加确实不一样,只有加了()值才会被初始化为0,否则为未初始化的值 delete mystr2; mystr2 = nullptr; delete pointi3; pointi3 = nullptr; } /** * 在 new 自定义类型对象时,以下代码效果一致 * 即针对自定义类型,值初始化没有效果 */ { A *pa1 = new A; A *pa2 = new A(); delete pa1; pa1 = nullptr; delete pa2; pa2 = nullptr; } /** * auto 与 new 一起使用 * 注意auto推断的结果,至于为何如此暂不深究(我也不懂) */ { string *mystr2 = new string(5, 'a'); // 这里时获取 C 字符串 const char *p = mystr2->c_str(); auto mystr3 = new auto(mystr2); // 注意这种写法,mystr3会被推断成string ** 类型 //string** mystr3 = new (string *)(mystr2); delete mystr2; mystr2 = nullptr; delete mystr3; mystr3 = nullptr; } /** * const 对象的动态内存分配 */ { const int *pointci = new const int(200); // new后面这个const可以不写,似乎都差不多;当然const对象不能修改其值 //*pointci = 300; // 不合法 cout << "断点放到这里方便观察" << endl; delete pointci; // new 的内存不能忘记释放 pointci = nullptr; } /** * delete 只能释放 new 出来的内存,并且只能释放一次 * 当 p 为空指针时,此时虽然可以释放多次但没有意义 */ { char *p = nullptr; delete p; delete p; } { int i; int *p = &i; //delete p; // 不是new出来的不能delete,否则编译不报错,但执行时会出现异常 } { int *p = new int(); int *p2 = p; delete p2; // 没问题 // delete p; // 异常,因为p和p2指向同一块内存, // 该内存已经通过delete p2释放了, // 所以两个指针指向同一块内存释放了p就不能再释放p2,释放了p2就不能再释放p p2 = nullptr; p = nullptr; } { const int *pci = new const int(300); delete pci; //可以delete const对象 pci = nullptr; } /** * const 对象可以 delete 但不能修改其值 */ { int *pci = new int(300); delete pci; // 可以delete const对象 *pci = 900; // 异常,内存已经被释放了 } return 0;}

【2】new/delete VS malloc/free

#include 
#include
using namespace std;class A{public: A() { cout << "A()构造函数被调用" << endl; } ~A() { cout << "~A()析构函数被调用" << endl; }};/** * new/delete 的高级话题 * new 作用,分配内存并调用A的构造函数 *//** * new 的调用关系 * A *pa = new A(); // 操作符 * operator new(); // 函数 * malloc(); // C 风格函数分配内存 * A::A(); // 有构造函数便调用构造函数 *//** * delete 的调用关系 * delete pa; // 操作符 * A::~A(); // 若存在析构函数则调用析构函数 * operator delete(); // 函数 * free(); // C 风格函数释放内存 *//** * new 和 malloc 的区别 * 1. new 是关键字/操作符,malloc 是函数; * 2. new 一个对象不但会分配内存还会调用对象的构造函数 * 3. new 可以将对象的成员变量的值初始化为0 * * delete 和 free 的区别 * 1. delete 是关键字/操作符,free 是函数; * 2. delete 一个对象不但会释放内存还会调用对象的析构函数 */int main(){ /** * new/delete 与 malloc/free 的区别 * 使用new生成一个类对象时系统会调用该类的构造函数 * 使用delete删除一个类对象时系统会调用该类的析构函数 */ { A *pa = new A(); // 类A的构造函数被调用 delete pa; // 类A的析构函数被调用 pa = nullptr; } /** * new 运算符功能 : 1. 分配内存;2. 调用构造函数初始化该内存 * 分配内存是通过operator new() 函数进行的 * delete 运算符功能 : 1. 调用析构函数;2. 释放内存 */ { int *pi = new int; // 分配出去4字节 delete pi; // 回收内存的时候,编译器怎样知道要回收4字节? // 这就是new内部有记录机制,它分配出去多少,会找个地方记录下来,回收的时候就按这个字节数回收 } { // 直接使用 operator new 分配内存空间 void *myorgpoint = operator new(100); // 分配100字节内存,一般没人这样做 } /** * 动态申请和释放一个数组 * 1. 此处 delete p; 存在弊端 * C++ 针对具有析构函数类型的数组会多分配4个字节, * 此时调用 delete p; 可能无法准确获取待释放的内存空间 * 2. 因此,delete p; 可行的条件为对象类型是内置类型或没有自定义析构函数 */ { int *p = new int[2]; // 动态分配一个整型数组,有2个元素,如果不释放,会产生8个字节泄漏,2个int=8字节 //delete p; // 没有使用[]释放内存,似乎也可以直接释放p这个int数组,并没有内存泄漏 delete[] p; // 这种释放方法是规范的,没有问题的 } /** * 1. A *pA = new A[2](); 为什么会多出 4 个字节? * C++ 在分配数组空间时会多分配4个字节用于保存数组的大小 * 2. delete[] pA; 调用了2次析构函数? * C++ 在分配数组空间时会多分配4个字节用于保存数组的大小 * delete 便可以知道数组中元素的个数从而知道该调用多少次析构函数 * 3. 注意事项 * new/delete 与 new[]/delete[] 必须成对使用 */ { int ilen = sizeof(A); // 1字节,为什么不是0字节?类A里明明没有任何成员,为什么不是0字节? // 因为一个类对象,肯定有地址,一个内存地址,至少能保存1个字节,所以这里是1字节不会是0字节 // A* pA = new A(); // 不delete,发现泄漏了1个字节内存 A *pA = new A[2](); // 给对象数组分配内存但不delete,发现泄漏了6个字节内存,这里为什么是6字节,而不是2字节? // 多出的4字节是用来干嘛的,为什么int* p = new int[2];不多泄漏4字节呢? // 这就是int内置类型和A类类型的差别; // delete pA; // 系统报异常,为什么系统报异常呢 delete[] pA; // 这种释放方法是规范的,没有问题的,调用了2次析构函数(因为分配内存时调用了两次构造函数) cout << "测试" << endl; } return 0;}

【3】new 对象 VS new 对象()

#include 
#include
#include
#include
#include
#include
#include
using namespace std;class A{};/** * new/delete 的高级话题 */int main(){ /** * new 对象时加括号与不加括号的区别 * 1. 若 A 是一个空类则两者没有区别 */ { A *pa = new A(); A *pa2 = new A; delete pa; delete pa2; } return 0;}
#include 
#include
#include
#include
#include
#include
#include
using namespace std;class A{public: int m_i;};/** * new/delete 的高级话题 */int main(){ /** * new 对象时加括号与不加括号的区别 * 2. 若类 A 仅有一个公共成员变量 m_i */ { A *pa = new A(); // m_i 为 0,会将与变量相关的内存置为 0 A *pa2 = new A; // m_i 为随机值 delete pa; delete pa2; } return 0;}
#include 
#include
#include
#include
#include
#include
#include
using namespace std;class A{public: int m_i; A() // 构造函数 { }public: ~A() { }};int main(){ /** * new 对象时加括号与不加括号的区别 * 3. 若类 A 中存在 public 的构造函数 * 则两者都会调用类 A 的构造函数 */ { A *pa = new A(); // m_i 为随机值 A *pa2 = new A; // m_i 为随机值 delete pa; delete pa2; } return 0;}
#include 
#include
#include
#include
#include
#include
#include
using namespace std;/** * new/delete 的高级话题 */int main(){ { int *p1 = new int; // 初始化为随机值 int *p2 = new int(); // 初始化为0 int *p3 = new int(100); // 初始化为100 cout << "断点调试" << endl; delete p1; delete p2; delete p3; } { operator new(12); } return 0;}

【4】new/delete 调用过程探讨

#include 
#include
#include
#include
#include
#include
#include
using namespace std;/** * 编译器若要有效的管理内存的分配和回收,在分配一块内存之外需要额外多分配许多空间保存更多的信息 * 这些信息包括如 1. 分配了多少字节;2. Debug 模式下的调试信息;3. 提高效率进行的边界调整的字节填充;4. 尾信息等 * ppoint 实质指向 malloc 分配出的内存空间中间的某个位置 */int main(){ { char *ppoint = new char[10]; memset(ppoint, 0, 10); delete[] ppoint; } { char *ppoint = new char[55]; memset(ppoint, 0, 10); delete[] ppoint; } return 0;}
#include 
#include
#include
#include
#include
#include
#include
using namespace std;class A{public: // 应该为静态函数,但不写static似乎也行,估计是编译器内部有处理, // 因为new一个对象时还没对象,静态成员函数跟着类走和对象无关 static void *operator new(size_t size); static void operator delete(void *phead); static void *operator new[](size_t size); static void operator delete[](void *phead);public: A() { cout << "类A的构造函数执行了" << endl; } ~A() { cout << "类A的析构函数执行了" << endl; }};void *A::operator new(size_t size){ cout << "A::operator new被调用了" << endl; A *ppoint = (A *)malloc(size); return ppoint;}void A::operator delete(void *phead){ cout << "A::operator delete被调用了" << endl; free(phead);}void *A::operator new[](size_t size){ cout << "A::operator new[]被调用了" << endl; A *ppoint = (A *)malloc(size); return ppoint;}void A::operator delete[](void *phead){ cout << "A::operator delete[]被调用了" << endl; free(phead);}/** * 重载类中的 operator new 和 operator delete 操作符 *//** * new 的调用关系 * A *pa = new A(); // 操作符 * operator new(); // 函数 * malloc(); // C 风格函数分配内存 * A::A(); // 有构造函数便调用构造函数 *//** * delete 的调用关系 * delete pa; // 操作符 * A::~A(); // 若存在析构函数则调用析构函数 * operator delete(); // 函数 * free(); // C 风格函数释放内存 *//** * 编译器角度的理解 * void * temp = operator new(sizeof(A)); * A *pa = static_cast
(temp); * pa->A::A(); * * pa->A::~A(); * operator delete(pa); */int main(){ /** * 调用用户重载的 new/delete 运算符 */ { A *pa = new A(); delete pa; } /** * 调用全局的 new/delete 关键字 * :: 作用域运算符 */ { A *pa2 = ::new A(); ::delete pa2; } return 0;}
#include 
#include
#include
#include
#include
#include
#include
using namespace std;void *operator new(size_t size) // 重载全局operator new{ return malloc(size);}void *operator new[](size_t size) // 重载全局operator new[]{ return malloc(size);}void operator delete(void *phead) // 重载全局operator delete{ free(phead);}void operator delete[](void *phead) // 重载全局operator delete[]{ free(phead);}class A{public: A() //构造函数 { cout << "A::A()" << endl; } ~A() //析构函数 { cout << "A::~A()" << endl; } void *operator new(size_t size) { A *ppoint = (A *)malloc(size); return ppoint; } void operator delete(void *phead) { free(phead); } void *operator new[](size_t size) { A *ppoint = (A *)malloc(size); return ppoint; } void operator delete[](void *phead) { free(phead); }};/** * 演示重载全局 new/delete 运算符 * 演示重载特定类的 new/delete 运算符 */int main(){ { int *pint = new int(12); // 调用重载的operator new delete pint; // 调用重载的operator delete char *parr = new char[10]; // 调用重载的operator new[] delete[] parr; // 调用重载的operator delete[] A *p = new A(); // 调用重载的operator new,之后也执行了类A的构造函数 delete p; // 执行了类A的析构函数,之后也调用了重载的operator delete A *pa = new A[3](); // 调用一次重载的operator new[],之后执行了三次类A的构造函数 delete[] pa; // 执行了三次类A的析构函数,之后也调用了重载的operator delete[] } return 0;}

【5】定位 new

#include 
#include
#include
#include
#include
#include
#include
using namespace std;class PLA{public: int m_a; PLA() : m_a(0) // 构造函数 { cout << "PLA::PLA()构造函数执行" << endl; } PLA(int tempvalue) : m_a(tempvalue) // 构造函数 { cout << "PLA::PLA(int tempvalue)构造函数执行" << endl; } ~PLA() //析构函数 { cout << "PLA::~PLA()析构函数执行" << endl; }};int main(){ /** * 定位new * 定位new与new处于同一个层次 * 定位new的功能,在已分配的原始空间中初始化一个对象 * 已分配,表明定位new不在分配内存 * 初始化对象,即调用对象的构造函数 * 定位new格式 * new(地址)类类型(参数) */ /** * 定位new的调用关系 * 必须已经分配完毕内存空间 * PLA *pmyAobj1 = new (mymemPoint) PLA(); // 定位new操作符 * operator new(); // 函数并没有调用malloc * PLA::PLA(); // 调用构造函数 */ { void *mymemPoint = (void *)new char[sizeof(PLA)]; // 内存必须事先分配出来,为了内存分配通用性,这里返回void *类型 // 开始用这个返回的void*指针 PLA *pmyAobj1 = new (mymemPoint) PLA(); // 定位new,调用无参构造函数,这里并不额外分配内存 void *mymemPoint2 = (void *)new char[sizeof(PLA)]; PLA *pmyAobj2 = new (mymemPoint2) PLA(12); // 定位new,调用带一个参数的构造函数,这里并不额外分配内存 // 释放 pmyAobj1->~PLA(); // 根据需要,有析构函数就可以调用析构函数 pmyAobj2->~PLA(); delete[](void *) pmyAobj1; // 分配时用char[],释放时用delete[],本行等价于delete[](void*)mymemPoint; delete[](void *) pmyAobj2; // 本行等价于delete[](void*)mymemPoint2; } return 0;}
#include 
#include
#include
#include
#include
#include
#include
using namespace std;class PLA{public: int m_a; PLA() : m_a(0) // 构造函数 { cout << "PLA::PLA()构造函数执行" << endl; } PLA(int tempvalue) : m_a(tempvalue) // 构造函数 { cout << "PLA::PLA(int tempvalue)构造函数执行" << endl; } ~PLA() // 析构函数 { cout << "PLA::~PLA()析构函数执行" << endl; } // 定位new操作符的重载,注意参数是比传统new多一个size参数 void *operator new(size_t size, void *phead) { // 这里增加一些自己的额外代码,用于统计之类的,但不要分配内存 return phead; // 收到内存开始地址也只返回内存开始地址即可 }};int main(){ /** * 定位new * 定位new与new处于同一个层次 * 定位new的功能,在已分配的原始空间中初始化一个对象 * 已分配,表明定位new不在分配内存 * 初始化对象,即调用对象的构造函数 * 定位new格式 * new(地址)类类型(参数) */ /** * 定位new的调用关系 * 必须已经分配完毕内存空间 * PLA *pmyAobj1 = new (mymemPoint) PLA(); // 定位new操作符 * operator new(); // 函数并没有调用malloc * PLA::PLA(); // 调用构造函数 */ { void *mymemPoint = (void *)new char[sizeof(PLA)]; // 内存必须事先分配出来,为了内存分配通用性,这里返回void *类型 // 开始用这个返回的void*指针 PLA *pmyAobj1 = new (mymemPoint) PLA(); // 定位new,调用无参构造函数,这里并不额外分配内存 void *mymemPoint2 = (void *)new char[sizeof(PLA)]; PLA *pmyAobj2 = new (mymemPoint2) PLA(12); // 定位new,调用带一个参数的构造函数,这里并不额外分配内存 // 释放 pmyAobj1->~PLA(); // 根据需要,有析构函数就可以调用析构函数 pmyAobj2->~PLA(); delete[](void *) pmyAobj1; // 分配时用char[],释放时用delete[],本行等价于delete[](void*)mymemPoint; delete[](void *) pmyAobj2; // 本行等价于delete[](void*)mymemPoint2; } return 0;}

参考致谢

本博客为博主学习笔记,同时参考了网上众博主的博文以及相关专业书籍,在此表示感谢,本文若存在不足之处,请批评指正。

【1】C++ 新经典

【2】C++11/14 高级编程 Boost 程序库探秘

转载地址:http://gomr.baihongyu.com/

你可能感兴趣的文章
Java Socket网络编程-总结
查看>>
Linux通过yum仓库安装gcc详细教程
查看>>
加油站(贪心)
查看>>
最长的连续元素序列长度(哈希表)
查看>>
访问docker中的nginx容器部署
查看>>
LNMP环境搭建
查看>>
Airtest自动化测试 Docs airtest.core.android package
查看>>
SVN Unable to connect to a repository at URL 的解决方案
查看>>
PostgreSQL 分区表探索(pg_pathman)数据库优化
查看>>
设计一个验证系统
查看>>
ubuntu 安装 vncserver
查看>>
centos7防火墙导致nginx无法访问
查看>>
python绘制一份完美的中国地图
查看>>
Python 超级简单精准计算地点日出日落时间
查看>>
准确率94%!Python 机器学习识别微博或推特机器人
查看>>
Python 元组Tuple 相对于数组List的优势
查看>>
Android OTA升级
查看>>
Android基本知识
查看>>
在Java中,return null 是否安全, 为什么?
查看>>
命令模式【Command Pattern】
查看>>