(一)构造函数
要点
-
定义:构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。
-
声明语法:
派生类::派生类名(参数表):基类名1(基类1初始化参数列表),...,基类名n(基类n初始化参数列表),成员对象名1(成员对象1初始化参数表),...,成员对象名m(成员对象m初始化参数表),基本类型成员初始化
{
派生类构造函数的其他初始化操作;
}
即构造函数的初始化参数列表: 构造函数名(参数1,参数2,...):成员1(参数1),成员2(参数2)....{}
-
构造函数名字和类名相同
-
构造函数没有返回值
-
不写构造函数,每一个类中都存在默认的构造函数,默认的构造函数是没有参数
-
default显示使用默认的构造函数
-
delete 删掉默认函数
-
当我们自己写了构造函数,默认的构造函数就不存在
-
-
构造函数不需要自己调用,在构造对象的时候自己调用
-
构造函数决定的了对象的长相
-
无参构造函数可以构造无参对象
-
有参构造函数,对象必须要带有参数
-
-
构造函数允许被重载和缺省
-
构造函数一般情况是公有属性
-
构造函数一般是用来给数据成员初始化
-
构造函数允许调用另个构造函数,必须采用初始化参数列表的写法
-
派生类构造函数执行的一般顺序如下:
-
(1)调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。
-
(2)对派生类新增的成员初始化,初始化顺序按照它们在类中声明的顺序
-
(3)执行派生类的构造函数体中的内容。
-
-
构造函数初始化列表中基类名、对象名之间的顺序无关紧要,它们各自出现的序可以是任意的,无论它们的顺序怎样安排,基类构造函数的调用和各个成员对象的初性物顺序都是确定的。
-
具体代码:
#include <iostream> #include <string.h> using namespace std; class MM { public: //构造函数 //MM() = default; //使用的函数是默认的构造函数 MM(){ cout << "无参构造函数" << endl; } MM(int a){ cout << "具有一个参数的构造函数" << endl; } protected: }; class Girl { public: Girl() = delete; //删除默认的构造函数 protected: }; class Student { public: Student() = default; Student(string name, int age){ //做初始化操作 m_name = name; m_age = age; } void printStudent(){ cout << m_name << "\t" << m_age << endl; } protected: string m_name; int m_age; }; //初始化参数列表 class Test { public: //构造函数特殊写法 Test(int a, int b) :a(a), b(b) { } Test():Test(0,0) {} //无参构造函数调用有参构造函数 //构造委托 void print(){ cout << a << "\t" << b << endl; } protected: int a=0; int b=0; }; struct Data { int a; int b; int c; Data(int a) :a(a){}Data(int a, int b, int c) :a(a), b(b), c(c){ cout << "调用三个参数的构造函数" << endl; } void print(){ cout << a << "\t" << b << "\t" << c << endl; } }; void testData() { Data data = { 1,2,3 }; } //这个过程也是调用构造函数过程,{}中数据个数要和构造函数参数一致 void printData(Data data){ data.print(); } void printData2(Data& data) { data.print(); } int main(){ #if 0 MM mm; MM girl(1); //Girl girl; //普通对象 Student stu("npc", 18); stu.printStudent(); //new一个对象 Student* pstu = new Student("执灯", 29); pstu->printStudent(); //对象数组 Student stuArray[3]; //无参构造函数构造 Test test; test.print(); #endif Data data(1, 2, 3); printData(data); printData2(data); return 0; }
(二) 复制构造函数
拷贝构造函数也叫做复制构造函数。
拷贝构造函数特性
-
不写拷贝构造函数,存在一个默认拷贝构造函数
-
拷贝构造函数名和构造函数一样,算是构造函数特殊形态
-
拷贝构造函数的唯一的一个参数就是对对象引用
-
普通引用
-
const引用
-
右值引用--->移动拷贝
-
-
当我们通过一个对象产生另一个对象时候就会调用拷贝构造函数
-
具体代码:
#include <iostream> #include <string> using namespace std; class MM { public: MM() {} MM(MM& object) { cout << "调用复制构造函数" << endl; } protected: }; class Girl { public: Girl(string name, int age) :name(name), age(age) {} Girl() :Girl("", 0) {} Girl(const Girl& object) { //构造函数就是通过一个对象赋值另一个对象 name = object.name; age = object.age; cout << "调用拷贝构造函数" << endl; } void print() { cout << name << "\t" << age << endl; } protected: string name; int age; }; void printGirl(Girl girl) //Girl girl=实参 { girl.print(); } void printMM(Girl& girl) { girl.print(); } void testGirl() { Girl girl("张三", 18); Girl mm(girl); mm.print(); Girl beauty = mm; beauty.print(); cout << "传入普通变量" << endl; printGirl(girl); cout << "传入引用" << endl; printMM(girl); //匿名对象的拷贝构造函数 //匿名对象是右值,右值引用 Girl test = Girl("匿名", 18); } class Boy { public: Boy(string name, int age) :name(name), age(age) {} Boy(Boy&& object) //右值引用 { name = object.name; age = object.age; cout << "右值引用的拷贝构造" << endl; } Boy(Boy& object) { name = object.name; age = object.age; cout << "普通拷贝构造" << endl; } protected: string name; int age; }; void testBoy() { cout << ".............." << endl; Boy boy("boy", 18); Boy gg = boy; //调用普通的对象 Boy coolman = Boy("sdfd", 28); //右值引用的拷贝构造函数 //没有打印结果,编译器做了优化,看不到 } int main() { MM mm; MM girl = mm; //会调用拷贝构造函数 MM beauty(girl); //会调用拷贝构造函数 //误区 MM npc; npc = girl; //运算符重载,不会调用拷贝构造函数 testGirl(); testBoy(); return 0; }
(三)析构函数
析构函数特性
-
声明语法:函数名等于 ~和类名
-
作用:在该类对象消亡之前进行一些必要的清理工作。
-
析构函数没有参数,所以析构函数不能被重载也不能缺省
-
对象死亡(作用域结束、生命周期结束)的最后一个事情是调用析构函数
-
析构函数都是公有属性的
-
什么时候写析构函数?
-
当类的数据成员new了内存就需要自己手动写析构函数
-
-
不写析构函数,也会存在一个析构函数,但是不具有释放new的内存功能
-
具体代码:
#include <iostream> using namespace std; class MM { public: MM() { p = new int; } //手动调用析构函数 void freeMemory() { delete p; p = nullptr; } ~MM() { cout << "我是析构函数" << endl; delete p; p = nullptr; } protected: int* p; }; int main() { //版本1: { MM mm; MM* p = new MM; delete p; //括号作用范围完,立刻马上调用析构函数 p->freeMemory();相当于delete p; } cout << "......" << endl; //版本2 MM mm; MM* p = new MM; delete p; //立刻马山调用析构函数 cout << "......" << endl; return 0; }
-
运行效果:
(四)构造函数与析构函数的顺序问题
构造和析构顺序问题
-
一般情况构造顺序和析构顺序是相反的
-
new对象,调用delete直接被释放
-
static对象,最后释放
具体代码:
#include <iostream>
#include <string>
using namespace std;
class MM {
public:
MM(string info = "A") :info(info) { cout << info; }
~MM() { cout << info; }
protected:
string info;
};
void testOrder() {
MM mm1("B"); //构造从上往下 :B,
static MM mm2("C"); //构造:C //最后释放 C
MM* p = new MM("D"); //构造:D
delete p; //delete直接调用析构函数:D D生命周期结束 故打印D,
MM arr[3]; //构造:AAA //析构下往上,静态最后一个:AAABC
}
int main() {
testOrder();//BCDDAAAAAABC
return 0;
}