析构函数
创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等。
特点:
析构函数是一种特殊的成员函数,没有返回值,是在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~
符号。析构函数不允许有参数 ,不可以发生重载。
构造函数
用途:
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
基本语法:
ClassName(){}
特点:
- 不需要返回值不用写void。
- 构造函数名称 与 类名 相同的 。
- 允许有参数,可以发生函数重载。
代码示例
#include <iostream>
/**
* 构造函数和析构函数
*/
using namespace std;
class Person{
public:
// 构造函数和析构函数必须要写到公共权限下面
Person(){
cout << "Person的构造函数调用" << endl;
}
// 析构函数
~Person(){
cout << "Person的析构函数调用" << endl;
}
};
void test01(){
// 创建对象
Person p;
/*
构造函数由编译器自动调用一次,无须手动调用
如果我们不提供构造函数,编译器也会自动提供构造函数,默认空实现.
*/
// 析构函数 也是编译器自动调用一次
// 如果我们不提供析构函数,编译器会提供空实现析构函数
// 析构函数 在对象被销毁前 自动调用
}
int main() {
cout << "Hello, World!" << endl;
// 调用函数
test01();
return 0;
}
构造函数调用
构造函数的分类
1、按参数可分为:无参构造(默认构造) 、带参构造
2、按类型可分为:拷贝构造、普通构造。
代码示例
#include <iostream>
using namespace std;
// 创建学生类
class Student{
public:
// 定义变量
int age;
Student(){
cout << "Student的默认构造函数调用" << endl;
}
Student(int a){
cout << "Student的有参构造函数调用" << endl;
}
// 拷贝构造,值传递的本质就是调用拷贝构造函数
Student(const Student &stu){
cout << "Student的拷贝构造函数调用" << endl;
age = stu.age;
}
~Student(){
cout << "Student的析构函数调用" << endl;
}
};
// 构造函数的调用
void test01(){
// 默认构造函数的调用
Student stu;
}
/**
* 调用方法
* @return
*/
void test02(){
/**
* 括号法
*/
/*
Student stu(10); // 有参构造
stu.age = 16;
// 调用拷贝构造函数
Student stu1(stu);
cout << "stu1的年龄是:"<< stu1.age << endl;
*/
// 注意:不要利用括号法,调用默认构造函数 Student stu(); 将代码看成函数的声明而不是认为是在创建对象。
/**
* 显示法
*/
/*
Student stu3 = Student(10); // 有参构造调用
Student stu4 = Student(stu3); // 显示法调用拷贝构造函数
*/
// 匿名对象
// Student(10); // 单独写Student(10),被称为匿名对象,特点:当本行执行完毕,立即释放。
// 不要利用拷贝构造函数,初始化匿名对象。
// 隐式转换法 可读性低
Student stu5 = 10; // 编译器隐式将代码转换为 Student stu = Student(10);
// 利用隐式转换法,调用拷贝构造函数
Student stu6 = stu5; // 隐式转为student stu6 = Student(stu5);
}
int main() {
cout << "Hello, World!" << endl;
// 调用函数
// test01();
test02();
return 0;
}
拷贝构造函数的调用时机
方式一:
对象以值传递的方式传给函数参数
方式二:
函数局部对象以值传递的方式从函数返回.
方式三:
用一个对象初始化另一个对象
代码示例
#include <iostream>
/**
* 拷贝构造函数的调用时机
*/
using namespace std;
// 创建学生类
class Student{
public:
// 定义变量
int age;
Student(){
cout << "Student的默认构造函数调用" << endl;
}
Student(int a){
cout << "Student的有参构造函数调用" << endl;
}
// 拷贝构造,值传递的本质就是调用拷贝构造函数
Student(const Student &stu){
cout << "Student的拷贝构造函数调用" << endl;
age = stu.age;
}
~Student(){
cout << "Student的析构函数调用" << endl;
}
};
// 1.用已经创建好的对象初始化新的对象
void test01(){
Student stu;
stu.age = 19;
// 拷贝构造函数调用
Student stu2(stu);
cout << "stu2的年龄是:" << stu2.age << endl;
}
// 2.值传递的方式 给函数参数传值
void doStudy(Student stu){}
void test02(){
// 创建对象
Student stu;
doStudy(stu);
}
// 3、以值的方式返回局部变量
Student doStudy2(){
Student stu;
return stu;
}
void test03(){
Student stu = doStudy2();
}
int main() {
cout << "Hello, World!" << endl;
// 调用函数
// test01();
// test02();
test03();
return 0;
}
构造函数调用规则
默认情况下,c++编译器至少为我们写的类增加3个函数。
1.默认构造函数(无参,函数体为空)。
2.默认析构函数(无参,函数体为空)。
3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝。
注意点:
1、如果定义拷贝构造函数,c++不会再提供任何默认构造函数。
2、如果定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造。
深拷贝和浅拷贝
浅拷贝
定义:同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝。
一般情况下,浅拷贝没有任何副作用,但是**当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题**。
深拷贝
当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间。
代码示例
#include <iostream>
#include <cstring>
/**
* 深拷贝与浅拷贝
*/
using namespace std;
// 创建Person类
class Person{
public:
// 定义成员变量
char * myName;
int myAge;
// 代参构造
Person(char * name, int age){
// 给Person的name,age开辟堆空间
myName = (char *)malloc(strlen(name) + 1);
strcpy(myName, name);
myAge = age;
}
//自己提供拷贝构造函数,实现深拷贝
Person(const Person &p){
myAge = p.myAge;
myName = (char *)malloc(strlen(p.myName) + 1);
// 复制
strcpy(myName, p.myName);
}
// 析构函数
~Person(){ // 释放在堆空间开辟的属性
cout << "Person析构函数被调用" << endl;
// 条件判断
if(myName != NULL){
free(myName);
myName = NULL;
}
}
};
// 声明函数
void test01(){
Person p1("curry", 10);
cout << "p1的姓名:" << p1.myName << "年龄:"<<p1.myAge <<endl;
// 调用拷贝构造函数
Person p2 = p1; // 初始化p2对象
cout << "p2的姓名:" << p2.myName << "年龄:" << p2.myAge << endl;
}
int main() {
cout << "Hello, World!" << endl;
test01();
return 0;
}
explicit关键字
c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用。
explicit注意
- explicit用于修饰构造函数,防止隐式转化。
- 是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。
代码示例
#include <iostream>
/**
* explicit关键字的意义
*/
using namespace std;
class MyString{
public:
explicit MyString(int len){
cout << "MyString有参构造函数(int )调用" << endl;
}
MyString(char * str){
cout << "MyString有参构造函数(char *)调用" << endl;
}
};
// 调用函数
void test(){
MyString str = "kobe is mvp";
MyString str1 = MyString("curry is child");
MyString str2 = MyString(10);
}
int main() {
// 调用函数
test();
return 0;
}
代码运行结果
MyString有参构造函数(char *)调用
MyString有参构造函数(char *)调用
MyString有参构造函数(int )调用