八十五、 C++内存动态分配与回收
- C语言中的动态内存分配和回收是用malloc、free来完成的
- C++中也可以用上述两个函数来完成。
- C++中也为用户提供了两个关键字来进行动态内存分配和回收 new 、delete
85.1 分配
- 单个内存分配
格式: 数据类型 *指针名 = new 数据类型- 例如: int *p1 = new int;
//此时表示在堆区中申请一个整型大小的空间,把地址赋给p
- 例如: int *p1 = new int;
- 连续内存分配
格式:数据类型 * 指针名 = new 数据类型[个数]- 例如: int *p2 = new int[5];
//在堆区中连续申请5个整型大小的空间
- 例如: int *p2 = new int[5];
85.2 回收
- 单个内存回收
格式: delete 指针名- 例如: delete p1;
- 连续内存回收
格式:delete [ ]指针名- 例如 : delete [ ]p2;
85.3 new、delete 和malloc 、free之间的区别 (笔试面试题)
- new delete 是关键字,malloc free是函数
- new申请空间是以数据类型为单位,malloc申请空间是字节单位
- new可以申请空间是初始化,malloc申请空间不会
- new申请空间是什么类型指针就是什么类型,malloc返回值是void*,需要强制转换才能使用。
- new 申请对象空间时,会自动调用构造函数,而malloc不会
- delete释放对象空间时,会自动调用析构函数,而free不会
85.4 示例:
#include <iostream>
using namespace std;
int main()
{
//在堆区空间中申请int大小空间
int *p1 = new int;
cout << *p1 << endl;
*p1 = 1314;
cout << *p1 << endl;
//在堆区空间中申请int大小空间并初始化
int *p2 = new int(10);
cout << *p2 << endl;
//在堆区空间中申请char大小空间并初始化
char *p3 = new char('H');
cout << *p3 << endl;
//释放(回收)内存空间
delete p1;
delete p2;
delete p3;
cout << "----------------------" << endl;
//在堆区空间中连续申请5个int大小空间
int *p4 = new int[5];
for(int i=0; i<5; i++)
{
cout << p4[i] << endl;
}
cout << "----------------------" << endl;
//在堆区空间中连续申请5个int大小空间并初始化
int *p5 = new int[5]{100,200,300,400,500};
for(int i=0; i<5; i++)
{
cout << p5[i] << endl;
}
//释放(回收)连续内存
delete []p4;
delete []p5;
//给指针来个指向 避免野指针
p1 = nullptr;
p2 = nullptr;
p3 = nullptr;
p4 = nullptr;
p5 = nullptr;
return 0;
}
八十六、 C++类中特殊的成员函数
- 特殊函数种类: 构造函数、析构函数、拷贝构造函数、拷贝赋值函数、移动赋值、移动拷贝、取地址运算符、常取地址运算符
- 特殊原因:
- 这些函数无需程序员手动定义,系统会默认提供,如果程序员手动定义,那么系统就取消默认提供。
- 这些函数无需程序员手动调用,在特定的条件下,系统自动调用,即使是程序员手动定义的函数。
86.1 构造函数
86.1.1 功能
- 在类实例化对象时,会自动调用构造函数来给类对象申请空间以及初始化。
86.1.2 格式
- 函数名:与类同名
- 返回值:无 也无void
- 参数:可以有参数,可以无参数
- 权限:一般为public
类名(形参列表)
{
函数体内容;
}
86.1.3 调用时机
- 类实例化对象时,会自动调用构造函数。
- 栈区
- 何时实例化对象,何时调用构造函数
形式 :类名 对象(实参);
- 何时实例化对象,何时调用构造函数
- 堆区
- 何时使用new ,何时调用构造函数
示例:
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
int id;
public:
Stu() //无参构造函数 由系统默认提供
{
cout << "Stu::无参构造函数" << endl;
}
Stu(string name, int id)
{
this->name = name;
Stu::id = id;
cout << "Stu::有参构造函数" << endl;
}
void show()
{
cout << "姓名:" << name << " 学号:" << id << endl;
}
};
int main()
{
Stu s1; //自动调用无参构造函数
Stu s2("张三", 1001); //自动调用有参构造函数
s2.show();
return 0;
}
86.1.4 注意
- 系统会提供一个默认无参构造函数。
如果自己写了有参构造函数,会把系统提供的默认无参构造函数给屏蔽掉。
如果想使用无参构造函数,则需要显性定义出无参构造函数,否则报错。 - 构造函数中可以给定默认参数值。
86.1.5 初始化列表
- 构造函数的本身功能是完成给对象申请空间的,初始化工作是由初始化列表完成。
- 初始化列表格式:
由构造函数形参列表后的小括号后面由冒号引出类名 (形参1,形参2,形参n):成员变量1(形参1),成员变量2(形参2),······,成员变量n(形参n)
- 必须使用初始化列表的情况:
- 当类中有常成员变量时,对该变量的初始化必须使用初始化列表来完成。
- 当类中有引用变量时,对该变量的初始化必须使用初始化列表来完成。
- 当类中有其他类的子对象时,对该子对象的初始化必须使用初始化列表来完成。
注意:只有构造函数才有初始化列表,其他普通函数没有初始化列表。
示例:
#include <iostream>
using namespace std;
class Bir
{
private:
int year;
int month;
int day;
public:
Bir(int y,int m, int d):year(y),month(m),day(d)
{
cout << "Bir::有参构造函数" << endl;
}
};
class Stu
{
private:
string name;
int id;
Bir bir;
public:
//无参构造函数
//Stu() {}
//有参构造函数
Stu(string name, int id, int y,int m, int d):name(name),id(id),bir(y,m,d)
{
cout << "Stu::有参构造函数" << endl;
}
void show(){cout << name;}
};
int main()
{
Stu s1("zhangsan", 1001, 2002, 9, 9); //先调用子对象的构造函数,再调用自己的构造函数
return 0;
}
86.2 析构函数
86.2.1 功能
- 当类对象生命周期结束后,自动调用析构函数,来对类对象回收资源(释放空间)。
86.2.2 格式
- 函数名:~类名
- 返回值:无 无void
- 参数:无参数
- 权限:一般是public
~类名()
{
函数体内容;
}
86.2.3 调用时机
- 类对象生命周期结束后,会自动调用析构函数。
- 栈区
- 当类对象所在的函数结束时,自动调用析构函数
- 堆区
- 何时使用delete ,何时调用析构函数
示例 :
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
int id;
public:
//无参构造函数
Stu() {cout << "Stu::无参构造函数" << endl;}
//有参构造函数
Stu(string name, int id):name(name),id(id)
{
cout << "Stu::有参构造函数" << endl;
}
//析构函数
~Stu()
{
cout << "Stu::析构函数" << endl;
cout << this << endl;
}
void show()
{
cout << name << endl;
}
};
int main()
{
Stu s1; //自动调用无参构造
Stu s2("zhangsan", 1001); //自动调用有参构造函数
cout << "&s1 = " << &s1 << " &s2 = " << &s2 << endl;
//先构造的 后析构 后构造的 先析构
return 0;
}
小结:
- 类中都系统默认提供析构函数,如果显性定义了析构函数,则系统取消默认提供。
- 当类中的成员变量有指针并且这个指针成员变量在堆区申请了空间,则此时需要显性定义析构函数,并且在析构函数中手动将指针成员所申请的空间释放,避免内存泄漏。
- 每个类中只有一个析构函数,原因:析构函数没有参数,所以不能重载。
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
int id;
double *score; //有指针成员变量时,并申请了堆区空间
public:
//无参构造函数
Stu() {cout << "Stu::无参构造函数" << endl;}
//有参构造函数
Stu(string name, int id, double b):name(name),id(id),score(new double(b))
{
//score = new double(b);
cout << "Stu::有参构造函数" << endl;
}
//析构函数
~Stu()
{
cout << "Stu::析构函数" << endl;
cout << this << endl;
delete score; //需要把析构函数显性定义出来,在其中把指针成员释放掉
}
void show(){cout << name << endl;}
};
int main()
{
Stu s1; //自动调用无参构造
Stu s2("zhangsan", 1001, 89); //自动调用有参构造函数
cout << "&s1 = " << &s1 << " &s2 = " << &s2 << endl;
Stu *p = new Stu; //自动调用无参构造函数
delete p; //自动调用析构函数
return 0;
}
86.3 拷贝构造函数
86.3.1 功能
- 拷贝构造函数其实就是一种特殊的构造函数,用一个类对象给另一个类对象初始化的。
86.3.2 格式
- 函数名:与类同名
- 返回值:无 无void
- 参数: 同类的其他类对象
- 权限:一般为public
类名(const 类名 &other)
{
函数体内容
}
86.3.3 调用时机
- 用一个类对象给另一个类对象初始化时,自动调用拷贝构造函数
eg:
Stu s(s1);
Stu s = s1; - 当类对象作为函数的形参时, 实参传递给形参时,自动调用拷贝构造函数
- 当函数返回一个类对象时,自动调用拷贝构造函数
86.4 浅拷贝和深拷贝(笔试面试)
- 系统会提供一个默认的拷贝构造函数,如果自己显性定义出了拷贝构造函数,则系统会取消默认提供函数。
- 系统提供的拷贝构造函数,是把一个对象的所有数据成员初始化另一个对象的所有数据成员,叫浅拷贝
小作业:
设计一个Per类,
类中包含私有成员:
姓名、年龄、指针成员身高、体重,
再设计一个Stu类,
类中包含私有成员:
成绩、Per类对象p1,
设计这两个类的 构造函数、 析构函数 和 拷贝构造函数。
我写的:
class.h
#ifndef __CLASS_H__
#define __CLASS_H__
#include <iostream>
using namespace std;
class Pre
{
private:
string name;
int age;
int *high;
int *weight;
public:
//构造函数
Pre();
Pre(string name, int age, int high, int weight);
//析构函数
~Pre();
//拷贝构造函数
Pre(const Pre &other);
//获取姓名
string get_name();
};
class Stu
{
private:
//构造函数
int score;
Pre p;
public:
Stu();
Stu(int score, Pre p);
//析构函数
~Stu();
//拷贝构造函数
Stu(const Stu &other);
};
#endif // __CLASS_H__
pre.cpp
#include "class.h"
//构造函数
Pre::Pre(){
cout << this->name << " 调用了无参构造函数" << endl;
}
Pre::Pre(string name, int age, int high, int weight){
this->name = name;
this->age = age;
this->high = new int(high);
this->weight = new int(weight);
cout << this->name << " 调用了有参构造函数" << endl;
}
//析构函数
Pre::~Pre(){
delete this->high;
delete this->weight;
cout << this->name << " 调用了析构函数" << endl;
}
//拷贝构造函数
Pre::Pre(const Pre &other){
this->name = other.name;
this->age = other.age;
this->high = new int(*(other.high));
this->weight = new int(*(other.weight));
cout << this->name << " 调用了拷贝构造函数" << endl;
}
string Pre:: get_name(){
return this->name;
}
stu.cpp
#include "class.h"
Stu::Stu(){
cout << this->p.get_name() << " 调用了无参构造函数" << endl;
}
Stu::Stu(int score, Pre p){
this->score = score;
this->p = p;
cout << this->p.get_name() << " 调用了有参构造函数" << endl;
}
//析构函数
Stu::~Stu(){
cout << this->p.get_name() << " 调用了析构函数" << endl;
}
//拷贝构造函数
Stu::Stu(const Stu &other){
this->score = other.score;
this->p = other.p;
cout << this->p.get_name() << " 调用了拷贝构造函数" << endl;
}