1. 类的默认成员函数
2. 构造函数
我们来看编译器自动生成的构造函数的行为是什么:(该编译器是VS2022)
//编译器自动生成的构造函数
#include<iostream>
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<iostream>
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<iostream>
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的构造就不用我们直接写了。
3. 析构函数
#include<iostream>
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的析构还是会自己调用的。
4. 拷⻉构造函数
我们看到用d1初始化d2,但是d1传值传参时需要调用拷贝构造,我们本身就是要调用拷贝构造,但是传值传参是又调用拷贝构造就会导致无穷递归调用。
#include<iostream>
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<iostream>
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<iostream>
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<iostream>
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发生报错。
5. 赋值运算符重载
5.1 运算符重载
.*操作符我们可能很陌生,我们看一下:
#include<iostream>
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;
}
在进行回调函数时我们会用到,但一般情况下我们很少使用该运算符。