
我们来看编译器自动生成的构造函数的行为是什么:(该编译器是VS2022)
//编译器自动生成的构造函数 #include using namespace std; class Date { public: void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.Print(); return 0; } 运行结果:随机值

#include using namespace std; class Date { public: // 1.⽆参构造函数 Date() { _year = 1; _month = 1; _day = 1; } // 2.带参构造函数 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } //3.全缺省构造函数(与无差构造不能同时存在,会发生调用歧义) // 默认构造只能存在一个 /*Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; }*/ void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; // 调⽤默认构造函数 Date d2(2025, 1, 1); // 调⽤带参的构造函数 // 注意:如果通过⽆参构造函数创建对象时,对象后⾯不⽤跟括号,否则编译器⽆法 // 区分这⾥是函数声明还是实例化对象 // warning C4930: “Date d3(void)”: 未调⽤原型函数(是否是有意⽤变量定义的?) //Date d3();错误的创建对象的用法 d1.Print(); d2.Print(); return 0; } 有一个注意点是:当调用默认构造函数时用Date d1();的创建对象的方法是错误的;()不能加。
一般情况下编译器自己生成的构造函数不能满足我们的需求,我们需要自己写,但是类似下面这中情况我们可以不写。用两个栈实现队列
#include using namespace std; typedef int STDataType; class Stack { public: Stack(int n = 4) { _a = (STDataType*)malloc(sizeof(STDataType) * n); if (nullptr == _a) { perror("malloc申请空间失败"); return; } _capacity = n; _top = 0; } // ... private: STDataType* _a; size_t _capacity; size_t _top; }; // 两个Stack实现队列 class MyQueue { public: //编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化 private: Stack pushst; Stack popst; }; int main() { MyQueue mq; return 0; } 我们初始化mq时调用编译器自动生成的构造函数,因为MyQueue类的属性只包含两个栈,初始化时就会自动调用栈的构造函数,然而栈的构造函数是我们写好的,所以综上,MyQueue的构造就不用我们直接写了。
#include using namespace std; typedef int STDataType; class Stack { public: Stack(int n = 4) { _a = (STDataType*)malloc(sizeof(STDataType) * n); if (nullptr == _a) { perror("malloc申请空间失败"); return; } _capacity = n; _top = 0; } ~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } private: STDataType* _a; size_t _capacity; size_t _top; }; // 两个Stack实现队列 class MyQueue { public: //编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源 // 若显⽰写析构,也会⾃动调⽤Stack的析构 /*~MyQueue() {}*/ private: Stack pushst; Stack popst; }; int main() { Stack st; MyQueue mq; return 0; } 如果MyQueue中还有除了stack还有资源那么自己写MyQueue时只要释放stack以外的资源就好了,stack的析构还是会自己调用的。
对⽐⼀下⽤C++和C实现的Stack解决之前括号匹配问题isValid,我们发现有了构造函数和析构函数确实⽅便了很多,不会再忘记调⽤Init和Destory函数了,也⽅便了不少。
我们看到用d1初始化d2,但是d1传值传参时需要调用拷贝构造,我们本身就是要调用拷贝构造,但是传值传参是又调用拷贝构造就会导致无穷递归调用。
#include using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d) { cout << "Date(const Date& d)" << endl; _year = d._year; _month = d._month; _day = d._day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; void Func1(Date d) { cout << &d << endl; d.Print(); } // Date Func2() Date& Func2() { Date tmp(2024, 7, 5); tmp.Print(); return tmp; } int main() { Date d1(2024, 7, 5); /* C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥传值传参要调⽤拷⻉构造 所以这⾥的d1传值传参给d要调⽤拷⻉构造完成拷⻉,传引⽤传参可以较少这⾥的拷⻉*/ Func1(d1); cout << &d1 << endl; return 0; } 运行结果:

给Func传值时会调用拷贝构造,我们也可以看到原来的d1和调用函数拷贝构造的d的地址是不一样的。
#include using namespace std; class Date { public: //这样写不是拷贝构造第一个参数必须是本身类类型的引用, //这样写的话再进行函数调用时就会很麻烦 Date(Date* d) { _year = d->_year; _month = d->_month; _day = d->_day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2024, 7, 5); //如果咋样写构造函数不是,他不是拷贝构造,调用时还有取地址很鸡肋,在这里我们也可以看到 //引用和指针是相辅相成的。虽然引用的底层是指针但是使用引用的场景不能被指针替代。 Date d2(&d1); d1.Print(); d2.Print(); return 0; } #include using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d) { cout << "Date(const Date& d)" << endl; _year = d._year; _month = d._month; _day = d._day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2024, 7, 5); //拷贝构造也可以这样写 Date d2 = d1; d1.Print(); d2.Print(); return 0; } 拷贝构造调用也可以写成上面的形式,这种形式看着就很舒服了。
#include using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d) { cout << "Date(const Date& d)" << endl; _year = d._year; _month = d._month; _day = d._day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; Date& Func2() { Date tmp(2024, 7, 5); tmp.Print(); return tmp; } int main() { // Func2返回了⼀个局部对象tmp的引⽤作为返回值 //Func2函数结束,tmp对象就销毁了,相当于了⼀个野引⽤ Date ret = Func2(); ret.Print(); return 0; } 运行结果:

调用func2()时会创建一个对象,打印出来,并返回该对象的引用,但是出函数时栈帧就被销毁了,返回的是一个野引用返回后,赋值进行拷贝构造,但是打印ret时是随机值,原因就是返回了野引用。
//编译报错:error C2652: “Date”: ⾮法的复制构造函数: 第⼀个参数不应是“Date” Date(Date d) { cout << "Date(const Date& d)" << endl; _year = d._year; _month = d._month; _day = d._day; } ![]()
如果我们写构造函数时没有传类的引用,那么会报错,会产生无穷递归。
若是Stack类我们自己不写拷贝构造进行浅拷贝,那么对于STDateType* _a就会仅仅进行地址的拷贝导致有两个对象的属性都指向_a中的资源,生命周期结束时就会对该资源进行两次free发生报错。
.*操作符我们可能很陌生,我们看一下:
#include using namespace std; class A { public: void func() { cout << "A::func()" << endl; } }; typedef void(A::* PF)(); int main() { //回调函数 //这里需要&,算是一种规定 PF pf = &A::func; A aa; (aa.*pf)(); return 0; } 在进行回调函数时我们会用到,但一般情况下我们很少使用该运算符。