目录
- 类的6个默认成员函数
- 构造函数
- 自己写的构造函数
- 默认生成的构造函数
- 析构函数
- 概念
- 特征
- 拷贝构造函数
- 特征
- 运算符重载
- == 、 >、 <=
- +=
- +
- 赋值重载
- Date类的完善
- 构造函数的完善
- +=、-=的完善
- 用+复用+=
- -、-=以及-和-=的相互复用
- 前置++、--
- 后置++、--
- 流插入、流提取
- 取地址重载
- const成员
类的6个默认成员函数
默认成员函数:不写编译器也会默认生成一份
构造函数
自己写的构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。
其特征如下:
- 函数名与类名相同。
- 无返回值,不需要写void。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
- . 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。 - 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会
生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默
认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类
型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看
下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员
函数。 - 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。
#include <iostream>
using namespace std;
class Date
{
public:
Date()
{
_day = 1;
_month = 1;
_year = 1;
}
Date(int day, int month, int year)
{
_day = day;
_month = month;
_year = year;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
int main()
{
Date d1;
Date d2(2023, 1, 24);
d1.Print();
d2.Print();
return 0;
}
对于没有参数的初始化对象时,不能写成下面那样,因为无法与函数声明区分
Date d1()
也可以用缺省函数,下面两个函数构成函数重载,但是无参调用的时候会产生歧义
//Date()
// {
// _day = 1;
// _month = 1;
// _year = 1;
// }
Date(int day = 1, int month = 1, int year = 1)
{
_day = day;
_month = month;
_year = year;
}
默认生成的构造函数
默认构造函数:编译器默认生成的、无参的构造函数、全缺省的构造函数(可以不传参的都叫默认构造),这三个函数不能同时存在,因为会存在调用歧义。
如果不写构造函数,有没有构造函数
默认生成的,但此时是随机值
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
对于栈的初始函数来说,初始化的也是随机值
class Stack
{
public:
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1;
return 0;
}
但对于像MyQueue的构造函数就初始化了
规则
内置类型:int/double/…指针,eg:Date* p是内置类型
自定义类型: class struct…
默认生成的构造函数,对于内置类型成员不做处理(看编译器,建议当成不处理),自定义类型会取调用它的默认构造(调用无参的默认构造,如果自定义类型没有默认构造 - 初始化列表,类和对象下讲)
对于这个缺陷C++11提供如下解决方法,下面这个写法还是声明,给的缺省值
析构函数
概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特征
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载 - 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- 编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
日期类不需要写析构,栈需要写析构
class Date
{
public:
Date(int day = 1, int month = 1, int year = 1)
{
_day = day;
_month = month;
_year = year;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _day = 1;
int _month = 1;
int _year = 1;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
拷贝构造函数
浅拷贝时,st和st1对象会导致对同一块空间的重复释放
解决方法:自定义类型对象拷贝时,调用一个函数,这个函数就叫拷贝构造 - 深拷贝。
(1)传参的时候
(2)初始化构造的时候Date d2(d1)
特征
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用。.0 - 默认生成的拷贝构造函数:对内置类型会完成值拷贝,自定义对象回去调用它的拷贝对象。
像下面所示,st1与st中的_a是指向同一块空间,当这两个对象被释放时,会对_a所指的这段空间释放两次,从而造成错误,拷贝构造主要是解决这个问题的– 深拷贝。
class Stack
{
public:
Stack()
{
//...
}
Stack(const Stack& stt)
{
_a = (int*)malloc(sizeof(int) * stt._capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, stt._a, sizeof(int) * stt._top);
_top = stt._top;
_capacity = stt._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
}
private:
int* _a;
int _top;
int _capacity;
};
1、被拷贝的对象前面加const,防止意外的改变
运算符重载
双操作数的运算符,第一个参数是左操作数,第二个参数是右操作数
对于+、-、*、/、>、<等等,内置类型可以直接使用,自定义类型无法使用
解决方法:(1)写一个函数 (2)使用运算符重载
运算符重载:operator+运算符 ,使用方法:直接使用运算符
函数重载:允许参数不同的同名函数存在
运算符重载:自定义类型可以直接使用运算符
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.*
::
sizeof
?:
. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
== 、 >、 <=
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
public:
int _year;
int _month;
int _day;
};
bool operator==(Date x, Date y)
{
return x._year == y._year && x._month == y._month && x._day == y._day;
}
bool operator>(Date x, Date y)
{
if (x._year > y._year)
return true;
else if (x._year == y._year && x._month > y._month)
return true;
else if (x._year == y._year && x._month == y._month && x._day > y._day)
return true;
return false;
}
bool operator<=(Date x, Date y)
{
return ~(x > y);
}
int main()
{
Date d1(2001, 3, 29);
Date d2(2024, 3, 1);
cout << (d1 > d2) << endl;
cout << (d1 == d2) << endl;
cout << (d1 <= d2) << endl;
return 0;
}
报错的原因:因为流提取运算符的优先级大于>,因此加个括号就没事了
**此时程序的缺陷 :
1、运算符重载函数的参数那,调用了拷贝构造 --> 用&
2、为了在函数里访问类的成员变量,把成员变量设置 成了公有 --> 在类里面设置一些访问成员的函数;将运算符重载函数放到类里面
**
缺陷1修改
bool operator==(const Date& x, const Date& y)
{
return x._year == y._year && x._month == y._month && x._day == y._day;
}
bool operator>(const Date& x, const Date& y)
{
if (x._year > y._year)
return true;
else if (x._year == y._year && x._month > y._month)
return true;
else if (x._year == y._year && x._month == y._month && x._day > y._day)
return true;
return false;
}
bool operator<=(const Date& x, const Date& y)
{
return ~(x > y);
}
缺陷2修改
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& y)
{
return _year == y._year && _month == y._month && _day == y._day;
}
bool operator>(const Date& y)
{
if (_year > y._year)
return true;
else if (_year == y._year && _month > y._month)
return true;
else if (_year == y._year && _month == y._month && _day > y._day)
return true;
return false;
}
bool operator<=(const Date& y)
{
return ~(*this > y);
}
private:
int _year;
int _month;
int _day;
};
也可以d1.operator>(d2)这样显示的调用
这个类还可以些哪些运算符重载,这个取决于哪些运算符对于这个类是有意义的
eg:日期-日期、日期+=天数、日期+天数
+=
int GetMonthDay(int year, int month)
{
assert(year >= 1 && month >= 1 && day >= 1);
int arr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0))
return 29;
return arr[month];
}
Date& operator+=(int day)
{
_day += day;
while (_day > GetMonthDay())
{
_day -= GetMonthDay();
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
细节
根据内置类型的定义,+=是有返回值的,因此自定义类型也应该有返回值
+
int GetMonthDay(int year, int month)
{
assert(year >= 1 && month >= 1 && day >= 1);
int arr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0))
return 29;
return arr[month];
}
Date operator+(int day)
{
Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
tmp._month++;
if (tmp._month > 12)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
赋值重载
operator= 我们不写,编译器回生成默认的operator=。跟拷贝构造的行为类似,内置类型值拷贝,自定义类型调用他的赋值
Date、MyQueue可以不用写,默认生成的operator=就可以用
赋值重载:(重载运算符)两个已经存在的对象拷贝
拷贝构造:一个已经存在的对象去拷贝初始化另一个对象
缺省参数不能同时出现在声明与定义里面,只能在声明中定义
Date& operator=(const Date& y);
Date& Date::operator=(const Date& y)
{
if (this != &y)
{
_year = y._year;
_month = y._month;
_day = y._day;
}
return *this;
}
Date类的完善
构造函数的完善
Date::Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
if (_year < 1 || _month > 13 || _month < 1 || day < 1 || day > GetMonthDay(_year, _month))
{
print();
cout << "日期非法" << endl;
}
}
+=、-=的完善
如果+=、-=一个负的天数那么就会报错
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= (-day);
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day < 1)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
用+复用+=
复用1
Date& Date::operator+=(int day)
{
*this = *this + day;
return *this;
}
Date Date::operator+(int day)
{
Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
tmp._month++;
if (tmp._month > 12)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
复用2
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
复用2要比复用1效率更高
-、-=以及-和-=的相互复用
Date& operator-=(int day);
Date operator-(int day);
Date& Date::operator-=(int day)
{
_day -= day;
while (_day < 1)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
前置++、–
Date& operator++();
Date& operator--();
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date& Date::operator--()
{
*this -= 1;
return *this;
}
注意:前置++、–的效率会比后置++、–的效率高
后置++、–
难点:如何声明后置++、–
函数名相同、参数不同,所以必须和前置++、–构成函数重载,加一个参数,int i(祖师爷规定的,i可写可不写,形参没有用所以一般不写)
后置效率多,多拷贝构造了两次
Date& operator++(int);
Date& operator--(int);
Date& Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date& Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
流插入、流提取
cin是istream的类
cout是ostream的类
io流可以识别不同的类型,原因:函数重载参数识别
写法一:
void operator<<(ostream& out);
void Date::operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << endl;
return;
}
这样写的问题:
对于成员函数,默认左操作数为第一个参数,这样就造成了流插入的可读性不好
解决方法:
实现到全局
方法二
将<<、>>流运算符实现到全局,这样才能让流对象做第一个参数,符合可读性
实现到全局,对于私有的成员变量就没法访问了=>将函数写成友元函数
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
void print();
int GetMonthDay(int year, int month);
friend void operator<<(ostream& out, const Date& y);
private:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out, const Date& y);
void operator<<(ostream& out, const Date& y)
{
out << y._year << "年" << y._month << "月" << y._day << endl;
return;
}
**问题:**没法联系的插入,返回值出现了错误,返回值应该是ostream类型的对象
friend ostream& operator<<(ostream& out, const Date& y);
ostream& operator<<(ostream& out, const Date& y)
{
out << y._year << "年" << y._month << "月" << y._day << endl;
return out;
}
friend istream& operator>>(istream& in, Date& y);
istream& operator>>(istream& in, Date& y)
{
in >> y._year >> y._month >> y._day;
return in;
}
分析:
流本质是为了解决,自定义类型的输入和输出问题
printf scanf无法解决自定义类型的输入输出问题
取地址重载
取地址重载不写的话,编译器也会自动生成。一般自动生成的就够用了,因为取地址重载也写不出什么花来。
如果不想让使用者取地址的话,可以自己写取地址重载,返回空指针。
Date* operator&();
const Date* operator&() const;
Date* Date::operator&()
{
return this;
}
const Date* Date::operator&() const
{
return this;
}
const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
对于一个const对象是没法调用非const的成员函数,因为权限放大问题
解决方法:给this这个对象加上const
const对象和非const对象都可以调用const成员函数
const成员函数声明定义都得写
void print() const;
void Date::print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
const成员函数定义原则
1、能定义成const的成员函数都应该定义成const,这样const对象(权限平移)和非const对象(权限缩小)都可以调用
2、要修改成员变量的成员函数,不能定义成const
bool operator==(const Date& y) const;
bool operator>(const Date& y) const;
bool operator>=(const Date& y) const;
bool operator<(const Date& y) const;
bool operator<=(const Date& y) const;
bool operator!=(const Date& y) const;
Date& operator+=(int day);
Date operator+(int day) const;
Date& operator=(const Date& y);
Date& operator-=(int day);
Date operator-(int day) const;
int operator-(const Date& y) const;
Date& operator++();
Date& operator--();
Date& operator++(int);
Date& operator--(int);