目录
1.回顾 malloc / free
1.1内存 Memory
• 计算机中一切正在运行的程序都运行在内存中• 程序(代码、数据)都加载在内存中1.2程序内存模型
栈:变量、对象、调用函数等所需要的空间由编译器管理,来一个就生成一个压入栈中,结束一个就弹出一个。堆:由程序员自己编写代码管理: 申请 ,使用, 释放 。( 动态内存分配)编译器不维护。1.3动态内存分配
动态:• 需要时申请• 用完后归还分配:• 告诉操作系统你要多大的空间(字节数)• 申请成功给你一个地址(指针)C语言:malloc( m emory alloc ate ) / free• malloc申请堆区内存: int* ptr = (int*)malloc(sizeof(int) * 3); // 获得指定字节数的堆区内存 *(ptr + 2) = 10; // 使用这块内存 • free释放占用的内存: free(ptr);
如果不释放内存空间 => 内存泄露 => 导致内存不够 => 内存溢出2.面向对象动态内存分配: new / delete
2.1C++的动态内存分配
• 思考:C++相比C语言主要多了什么?面向对象。• 进一步思考:定义一个对象和定义一个普通变量有何区别?• 普通变量:分配足够空间即可存放数据• 对象:除了需要空间,还要 构造 / 析构• 类比:• malloc / free: 租用一个仓库 -> 存放货物 -> 货物卖完 -> 退租走人• new / delete : 租一块地 -> 修建大厦 -> 到期不用了 -> 爆破拆除善后 -> 地归原主2.2new 和 delete 运算符
2.2.1new / delete:
申请所需大小的内存 -> 构造对象 -> 返回指针供使用 -> 析构对象 -> 释放空间#include<iostrem> using namespace std; class A { public: int n; A() { cout << "Ctr1" << endl; } A(int n) : n(n) { cout << "Ctr2" << endl; } void func() { cout << "func called" << endl; } ~A() { cout << "Dtr" << endl; } }; int main() { A* p1 = new A; p1->func(); delete p1; A* p2 = new A(10); p2->func(); delete p2; return 0; }
打印结果 Ctr1 func called Dtr Ctr2 func called Dtr
2.2.2new[] 和 delete[] 运算符
• new[]:申请空间并批量构造对象数组,返回 首元素指针• delete[]:批量析构对象数组并释放空间,只能用于new[]出来的指针class A { public: A() { cout << "A constructed\n"; } void func() { cout << "func called\n"; } ~A() { cout << "A destructed\n"; } }; // ... A* p_a = new A[6]; (p_a + 1)->func(); delete[] p_a;
A constructed A constructed A constructed A constructed A constructed A constructed func called A destructed A destructed A destructed A destructed A destructed A destructed
3.两者异同
malloc / free VS new / delete同• 都用于动态内存分配(申请 - 使用 - 释放)• 都发生在堆区内存上• 申请成功得到的都是指针,通过指针操作得到的内存空间/对象异• malloc / free 是一对函数,new / delete 是一对 运算符• malloc / free 仅申请空间(不关心空间用途),而 new / delete 还会构造/析构对象• malloc / free 得到 void* 指针,而 new / delete 得到指向指定类型的指针• new / delete 底层有使用 malloc / free4.虚析构函数
4.1内存泄漏 VS 内存溢出
• 内存 溢出 :一辆车只能坐5个人,来第6个人没法坐了, 内存空间不够用了• 内存 泄漏 :公司一共有10辆车,本来最多能坐50人小明把其中一辆车 借走了一直不还 ,导致别人都 用不了 这辆车本来能坐50人,现在来46个人就溢出了, 增大了内存溢出可能4.2虚析构函数
• 思考:下面代码段有什么问题?class A { public: char* pA; A() { pA = new char[100]; } virtual void func() { cout << "func in A"<<endl; } ~A() { delete[] pA; } };
class B : public A { public: char* pB; B() { pB = new char[100]; } void func() { cout << "func in B"<<endl; } ~B() { delete[] pB; } };
A* ptr = (A*) new B; ptr->func(); delete ptr;
父类指针指向子类对象,释放指针时只会释放指针类型对象,其实子类对象没有被释放 ptr的类型是A* 因此~B()没被调用 导致内存泄露啦!
• 请务必把父类的析构函数设置为虚函数!上面代码解决方案如下(在父类析构函数前加上virtual)class A { public: char* pA; A() { pA = new char[100]; } virtual void func() { cout << "func in A"<<endl; } virtual~A() { delete[] pA; } };
class B : public A { public: char* pB; B() { pB = new char[100]; } void func() { cout << "func in B"<<endl; } ~B() { delete[] pB; } };
A* ptr = (A*) new B; ptr->func(); delete ptr;
5.例题
⭐ Key 1 :new/delete 是一对运算符,动态创建/销毁单个对象(或基础数据)⭐ Key 2 :new[]/delete[] 动态创建/销毁对象数组(或基础类型数组)⭐ Key 3 :new/delete和malloc/free的区别在于前者会构造/析构对象⭐ Key 4 :内存泄露(借了不还):导致内存溢出(资源不够)的可能性增大⭐ Key 5 :虚析构函数是为了避免子类资源泄露(如内存泄露或文件句柄不释放)1.下面有关构造函数和new运算符关系正确的说法是:DA.new运算符只能用于类,因为基础数据类型没有构造函数B. 构造函数一定调用new运算符C.要创建新类的实例时,需要先使用new运算符,然后调用构造函数进行初始化D. 使用new运算符动态产生类的对象时,new运算符会自动调用构造函数2.关于delete和delete[]运算符的下列描述中,错误的是:CA. 它们不能用于野指针和垂悬指针B. 它们可以用于空指针C. 对一个指针可以使用多次该运算符D. 使用delete[]无需在括号符内写待删数组的维数解析:野指针:不知道指向什么乱七八糟地址垂悬指针:指向的地址已经被回收/清理了空指针:nullptr关键字,表示“没有地址”的含义3.小明做项目时定义了一些类:F←S←G。项目运行时,他发现系统存在内存泄露和文件句柄占用的现象,下面哪个举措有可能解决该问题:A. 将成员函数全部声明为内联函数B. 避免使用重载函数C. 将G的析构函数声明为虚析构函数D. 将F的析构函数使用virtual关键字声明4. 填空:下列代码有可能导致_内存泄露_ ,增大 __内存溢出__的可能性。class Base { private: char* pc; public: Base(int n) : pc(new char[n]) {} ~Base() { delete[] pc; } };
class Derived : public Base { private: int* pi; public: Derived(int n1, int n2) : Base(n1) { pi = new int[n2]; } ~Derived() { delete[] pi;} };
5.请补全下列代码,避免内存泄漏问题class A { private: int n; public: A(int n) : n(n) {} _______________ {} };
class B : public A { private: int* data; public: B(int n) : A(n) { data = new int[n]; } ~B() { _____________________; } };
int main () { A* p = new B(100); // 省略对p的操作 delete p; }
答案 virtual ~A() delete[] data;