一 类的6个默认成员函数:
如果一个类中什么成员都没有,简称为空类。
例:
#include <iostream>
class Empty
{
// 空类,什么成员都没有
};
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。
默认构造函数:如果用户没有定义任何构造函数,编译器会自动生成一个默认构造函数。
拷贝构造函数:用于创建一个对象是另一个对象的副本。如果用户没有定义,编译器会生成一个默认的拷贝构造函数。
拷贝赋值运算符:用于将一个对象赋值给另一个对象。如果用户没有定义,编译器会生成一个默认的拷贝赋值运算符。
移动构造函数:用于将资源从一个对象移动到另一个对象。如果用户没有定义,编译器会生成一个默认的移动构造函数。
移动赋值运算符:用于将资源从一个对象移动并赋值给另一个对象。如果用户没有定义,编译器会生成一个默认的移动赋值运算符。
析构函数:用于销毁对象并释放资源。如果用户没有定义,编译器会生成一个默认的析构函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
二 构造函数:
2.1:构造函数的概念:
构造函数是一个特殊的成员函数,它的名称与类名相同,没有返回值。在创建类的对象时,构造函数由编译器自动调用,用于初始化对象的数据成员。
2.2:构造函数的特征
函数名与类名相同。
没有返回值。
在对象实例化时由编译器自动调用。
构造函数可以重载,即一个类可以有多个构造函数,只要它们的参数列表不同。
2.3:无参/有参构造函数代码示例
class Date
{
public:
//有参数的构造函数:
//Date(int _year = 1999 , int _month = 2 , int _day = 26)
// 无参构造函数:
Date() //函数名与类名相同。
{
// 使用 this 指针访问成员变量
this->_year = 2024;
this->_month = 7;
this->_day = 6;
}
void Print()
{
std::cout << this->_year << "-" << this->_month << "-" << this->_day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //调用无参数构造函数
d1.Print();
//Date d2(2022, 7, 6); //调用带参构造函数
//d2.Print();
return 0;
}
Date()是无参的构造函数,没有参数。在对象 d1
创建时自动调用。你们有可能会问了,为什么在无参的构造函数里面this指针指向成员变量,那为什么main函数里见不到this指针呢?因为当 Date d1;
创建对象时,编译器会自动传递 d1
的地址给 this
指针所以就不需要显示this指针。
如果类中没有显式定义构造函数,编译器会自动生成一个无参的默认构造函数。一旦用户显式定义了任何构造函数,编译器将不再生成无参的默认构造函数。
2.4:默认构造函数代码示例
class Date
{
public:
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
输出:
上面代码因我未显示定义构造函数所以编译器帮我生成了一个默认的构造函数而且是看不见的,那为什么默认生成的输出的值是随机值呢?
原来C++把类型分为内置类型和自定义类型,内置类型就是语言提供的基本数据类型,如int
、char
等。自定义类型是用户定义的类型,如使用class
、struct
、union
定义的类型。
2.5:内置类型和自定义类型的默认构造函数处理
内置类型:
1.内置类型的成员变量在默认构造函数中不会被自动初始化
2.如果不显式初始化,成员变量的值将是未定义的(即随机值)
自定义类型:
1.自定义类型的成员变量在默认构造函数中会调用其默认构造函数。
这意味着,即使你没有显式定义自定义类型的构造函数,编译器也会自动调用默认构造函数来 初始化这些成员变量。
例子:
class Time
{
public:
Time()
{
// Time类的无参构造函数
std::cout << "Time()" << std::endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date()// 初始化内置类型成员变量
{
this->_year = 2024;
this->_month = 7;
this->_day = 5;
}
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year; // 内置类型
int _month; // 内置类型
int _day; // 内置类型
Time _t; // 自定义类型
};
int main()
{
Date d; // 调用无参构造函数
d.Print();
return 0;
}
输出:
我们来说一下它的执行顺序:首先是执行主函数main当执行到 Date d; 时编译器就会先去调用自定义函数Time_t;然后等它全部初始化完成 再去调用无参数构造并且初始化里面的内置类型。
那我们这是显式定义自定义类型的构造函数并且给成员变量赋值了,所以就不会出现随机值,如果想要显式定义自定义类型的构造函数并且不想要随机值那该怎么办呢?这时候C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
例子:
class Date
{
public:
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year = 2024; // 内置类型
int _month = 2; // 内置类型
int _day = 1; // 内置类型
};
输出:
2.6:默认构造函数
在C++中,默认构造函数是指在创建对象时不需要提供任何参数的构造函数。默认构造函数可以分为两种:
- 默认构造函数:一个类只能有一个真正的默认构造函数(不需要参数)。
- 无参构造函数和全缺省参数构造函数:
- 如果参数不同,它们会重载,编译器不会报错。
- 如果参数相同(即都没有参数),它们就相当于有两个默认构造函数,这时编译器会报错,因为无法区分调用哪个构造函数。
关键点:
无参构造函数:没有参数的构造函数。 全缺省参数构造函数:所有参数都有默认值的构造函数。 重载:当构造函数的参数列表不同,它们可以共存且不会冲突。
例子:
class Date
{
public:
// 无参构造函数
Date()
{
_year = 2024;
_month = 7;
_day = 2;
}
// 全缺省参数构造函数
Date(int year = 2023, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d; // 调用无参构造函数
d.Print();
Date d2(2023, 4, 3); // 调用全缺省参数构造函数
d2.Print();
return 0;
}
输出:
之所以会报错是因为全缺省参数构造函数和无参数构造函数它们都有自己的默认值,当执行到Date d; 时它并不知道到底要调用哪一个所以就会报错,那怎么更改呢?只需要把全缺省参数的默认值给去掉就行了,这样编译器就不会迷糊到底要调用哪一个了
三 析构函数:
3.1 析构函数的概念:
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的? 析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
3.2 析构函数的特征:
1:名称:析构函数的名称是在类名之前加上~
。
2:无参数无返回值:析构函数没有参数,也没有返回值。
3:唯一性:每个类只能有一个析构函数。
4:自动调用:当对象的生命周期结束时,析构函数会被编译器自动调用。
5:不可重载:析构函数不能像其他成员函数一样被重载。
3.3:代码示例:
class Time
{
public:
Time()
{
std::cout << "Time()" << std::endl;
_hour = 0;
_minute = 0;
_second = 0;
}
~Time()
{
std::cout << "~Time()" << std::endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date()
{
std::cout << "Date()" << std::endl;
_year = 2024;
_month = 7;
_day = 2;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
std::cout << "~Date()" << std::endl;
}
void Print()
{
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // 自定义类型成员变量
};
int main()
{
Date d1; // 创建Date对象,调用Date构造函数和Time构造函数
d1.Print();
Date d2(2023, 4, 3);
d2.Print();
return 0;
}
输出:
我们现在来捋一下它的执行过程:
首先从主函数进入之后就会执行无参的构造函数但因无参构造函数里有自定义类型成员变量所以先要调用Time(); 你们是不是调用完它之后直接就跳动析构函数,这是不被允许的 原因是析构函数要等对象的生命周期结束时,析构函数会被编译器自动调用,自定义类型成员变量执行完了那就该执行无参构造函数了,然后就这样循环直到程序结束的时候就会调用d1和d2的析构函数,先调用Date的析构函数,再调用Time的析构函数。
根据上面析构输出的打印那咱们想没想过为什么是先调用Date然后再调用Time呢?跟上面的无参数构造函数的输出截然不同这是什么原因呢?
3.4 构造函数和析构函数的调用顺序:
构造函数调用顺序:
1. 首先调用成员变量的构造函数
2. 然后调用包含这些成员变量的类的构造函数。
析构函数调用:
1. 首先调用包含这些成员变量的类的析构函数。
2. 然后调用成员变量的析构函数。