1、析构函数函数
1)构造函数
构造函数是一个特殊的成员函数;
其作用是给类里面的数据成员初始化;
在对象构造的时候,系统自动的调用。
class A
{
public:
A(int i = 4,int j = 8):m_i(i),m_j(j)
{
cout<<"A"<<" "<<m_i<<" "<<m_j<<endl;
}
private:
int m_i;
int m_j;
};
void main()
{
//局部对象,其作用域在其所在的{}内,出了其作用域,其所占的内存空间就将被释放,
//如果其是在栈上开辟的空间,则不需要程序员自己释放,系统会自动回收
A a; //a所占8个字节,为m_i和m_j的大小。调用构造函数A
cout<<sizeof(a)<<endl;
}
2)构造函数和析构函数
函数 — 什么时候调用 — 作用 — 未自己定义时,系统提供
构造函数 — 对象构造时自动调用 — 构造对象 — 默认的构造函数(无参,无返回值)
两者作用相反
析构函数 — 对象退出时自动调用— 释放对象 — 自动调用(程序员可以调用,一般情况下不去调用)
3)析构函数
编译器会提供一个默认的析构函数,函数名为 ~类名,析构函数无参(不能重载)
一个类有且仅有一个析构函数
(构造函数为什么可以带参数呢?因为构造函数,我们走构造对象的时候,初始值可以不一样,即传参不一样。但析构函数不带参数,因为其作用就是释放,不管带不带参的构造函数,其类里面的数据成员都是一样的。)
它的目的是释放
~ 表示为逆,
什么时候调用析构函数?
在改构造的对象其作用域即将消失时,即{}即将结束时。
在同一个作用域中,先构造的后析构,栈
class A
{
public:
A(int i = 4,int j = 8):m_i(i),m_j(j)
{
cout<<"A"<<" "<<m_i<<" "<<m_j<<endl;
}
~A()
{
cout<<"~A"<<" "<<m_i<<" "<<m_j<<endl; //这里打印是为了查看析构函数的调用,便于观察
}
private:
int m_i;
int m_j;
};
void main()
{
//局部对象,其作用域在其所在的{}内,出了其作用域,其所占的内存空间就将被释放,
//如果其是在栈上开辟的空间,则不需要程序员自己释放,系统会自动回收
A a; //a所占8个字节,为m_i和m_j的大小。调用构造函数,所以会输出A 4 8
//cout<<sizeof(a)<<endl;
A b(3,6);//先构造的,后析构
A c(2,7);
cout<<"main"<<endl;//用于查看析构函数的调用时间
}
4)什么情况下,必须需要写析构函数?? --new, delete
当有指针作为数据成员时,就需要程序员写构造函数,将构造函数中new的空间释放
new , delete
malloc,free,realloc
new , delete和malloc,free…的区别
1、new 和 delete 是运算符,malloc 和 free 是库函数;
2、new 和 delete 可以调用构造和析构;
-----malloc 和 free 只能分配空间和释放空间;
new完之后可以随时delete;
为什么会出现new
class A
{
public:
A(int i = 0):m_i(i){cout<<"A"<<endl;};
void print()
{
cout<<m_i<<endl;
}
~A(){cout<<"A"<<endl;};
private:
int m_i;
};
#if 0
void main()
{
#if 0
A *p1 = NULL; //这句话只是声明了一个A类型的指针变量,指向NULL,占四个字节
p1 = (A*)malloc(sizeof(A)); //这句话没有构造一个对象(开辟空间+赋合法值),只是开辟了一个A类类型的空间,让p指针指向它,并没有构造对象出来
p1->print();//为随机值,只是开辟空间,并没有构造一个对象出来
free(p1);//只是释放空间
p1 = NULL;
#endif
//A *p = new A;
//A *p = new A(6);
//A *p = new A[5];//new 了五个A类型的对象
//delete p;//error,delete必须是数组
//char *p = new char;//开辟了一个字节的空间
char *p = new char[20];//new了一个20个连续的内存空间,让p指针指向
strcpy(p,"abcdefg");
cout<<p<<endl;
delete[]p; //new 的是一个数组,就要delete一个数组
//delete p;//可以成功释放,对于基本数据类型,可以直接p,以为其为基本数据类型,且连续,但不建议使用
}
#endif
#if 1
void main()
{
A *p1 = new A[5]; //new了5个A类型的对象
//delete p;//error 出错
//delete p[];//error
delete []p1; //delete 必须是数组
char *p2 = new char[20];
delete p2; //可行
}
#endif
不能delete p;类类型数组
正确使用方式:
问题:什么时候程序员需要自己写析构函数?
既然每个类都会提供一个默认的析构函数,而且析构函数只能有一个,那么为什么程序员需要自己写?什么时候程序员需要自己写析构函数?
答:
当有指针作为数据成员时,就需要程序员写析构函数,将构造函数中new的空间释放
为什么这里的m_name(name),可以完成赋值,并成功运行??
class Student
{
public:
Student(int num = 1,char *name = "lol",char sex='f',int age =0);
void Print();
private:
int m_num; //编号
char *m_name; //姓名
char m_sex; //性别
int m_age; //年龄
};
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
//注意这里的m_name(name),可以完成赋值,并成功运行
}
void Student::Print()
{
cout<<m_num<<" "<<m_name<<" "<<m_sex<<" "<<m_age<<endl;
}
void main()
{
Student s1;
Student s(1001,"pangpang",'m',20);
s.Print();
cout<<sizeof(s)<<endl; //内存对齐,s的大小为16个字节
}
s1对象的内存布局:
其中指针name指向的是“pangpang”–字符串常量,存放在常量区存储区,即name指向"pangpang"的首地址,
m_name(name) 就让m_name指向"pangpang"的首地址,并没有将字符串存储其中,
运行完 Student s(1001,“pangpang”,‘m’,20); 后:
但出现一个问题,不能修改m_name的值
//所以一般不这么写,不能对m_name的内容进行修改,如果不进行修改可以这么写
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
/*
m_name(name)将m_name指针指向的那个字符串常量的首地址,并没有开辟空间,不能修改
*/
//strcpy(m_name,"aaaaaaa");//error,m_name没有所指向的空间,不能实现,m_name(name)这句话只是让m_name这个指针指向了所传过来的字符串的首地址
//*m_name = 'k';//error,
}
正确代码,可实现修改,使指针正确的合法的存储(不是指向)正确合法的内容–需要给指针开辟合法的内存单元
class Student
{
public:
Student(int num = 1,char *name = "lol",char sex='f',int age =0);
void Print();
private:
int m_num; //编号
char *m_name; //姓名
char m_sex; //性别
int m_age; //年龄
};
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
//需要给m_name指针开辟内存大小,存储name所指向的字符串
//正确的合法的去存储合法的内容,需要给当前的指针开辟合法的内存单元
m_name = new char[strlen(name) + 1];//+1为'\0'
strcpy(m_name,name);
m_name[0] ='a';
}
#if 0
//所以一般不这么写,不能对m_name的内容进行修改,如果不进行修改可以这么写
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
/*
m_name(name)将m_name指针指向的那个字符串常量的首地址,并没有开辟空间,不能修改
*/
//strcpy(m_name,"aaaaaaa");//error,m_name没有所指向的空间,不能实现,m_name(name)这句话只是让m_name这个指针指向了所传过来的字符串的首地址
//*m_name = 'k';//error,
}
#endif
void Student::Print()
{
cout<<m_num<<" "<<m_name<<" "<<m_sex<<" "<<m_age<<endl;
}
void main()
{
Student s1;
Student s(1001,"pangpang",'m',20);
s.Print();
//cout<<sizeof(s)<<endl; //内存对齐,s的大小为16个字节
}
核心代码:
m_name = new char[strlen(name) + 1];//+1为'\0'
strcpy(m_name,name);
这两句代码就开辟了一块 “strlen(name) + 1” 大小的内存单元,并且将name所指向的字符串的内容拷贝到这段内存单元里,使m_name指向这一块内存单元,
这样m_name不仅指向所需指向的内容,还可以修改。
需要在对象作用域消失之前调用析构函数,才能成功释放
析构函数(一个类有且只有一个析构函数,并且没参数):
~Student()
{
if(m_name != NULL)
{
delete []m_name;
m_name = NULL;
}
//cout<<"~Student"<<endl;
}
使用示例:
#if 1
class Student
{
public:
Student(int num = 1,char *name = "lol",char sex='f',int age =0);
void Print();
~Student()
{
if(m_name != NULL)
{
delete []m_name;
m_name = NULL;
}
cout<<"~Student"<<endl;
}
private:
int m_num; //编号
char *m_name; //姓名
char m_sex; //性别
int m_age; //年龄
};
#if 1
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
//需要给m_name指针开辟内存大小,存储name所指向的字符串
//正确的合法的去存储合法的内容,需要给当前的指针开辟合法的内存单元
m_name = new char[strlen(name) + 1];//+1为'\0'
strcpy(m_name,name);
m_name[0] ='a';
}
#endif
#if 0
//所以一般不这么写,不能对m_name的内容进行修改,如果不进行修改可以这么写
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
/*
m_name(name)将m_name指针指向的那个字符串常量的首地址,并没有开辟空间,不能修改
*/
//strcpy(m_name,"aaaaaaa");//error,m_name没有所指向的空间,不能实现,m_name(name)这句话只是让m_name这个指针指向了所传过来的字符串的首地址
//*m_name = 'k';//error,
}
#endif
void Student::Print()
{
cout<<m_num<<" "<<m_name<<" "<<m_sex<<" "<<m_age<<endl;
}
void main()
{
Student s1;
Student s(1001,"pangpang",'m',20);
s.Print();
cout<<sizeof(s)<<endl; //内存对齐,s的大小为16个字节
}
运行结果: