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;
}
在进行回调函数时我们会用到,但一般情况下我们很少使用该运算符。