一、构造函数
构造函数是一个特殊的成员函数 ,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。 构造函数的名称虽然叫构造,但是需要注意的是,构造函数的主要任务并不是开空间创建对象,而是初始化对象。
1.1 构造函数的特性
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载
1.1.1 构造函数类型:
无参构造函数
class Date
{
public:
//无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//调用无参构造函数
Date d1;
//如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
//如下函数:声明了d2函数,该函数无参,返回一个日期类型的对象
Date d2();
return 0;
}
带参构造函数
class Date
{
public:
//带参构造函数
Date(int year,int month ,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//调用带参构造函数
Date d1(2022,5,20);
return 0;
}
1.1.2 默认构造函数
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。特点:不传参就可以调用
class S
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
private:
int _a;
};
class Date
{
public:
private:
int _year;//内置类型
int _month;//内置类型
int _day;//内置类型
S a;//自定义类型
};
int main()
{
//没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
Date d1;
return 0;
}
默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理
内置类型/基本类型:int/char/double/指针...
自定义类型:class/struct去定义类型对象
默认构造函数类型 :
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
class Date
{
public:
//无参构造函数
Date()
{
_year = 100;
_month = 100;
_day = 100;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
2.全缺省构造函数
class Date
{
public:
//全缺省构造函数
Date(int year = 100, int month = 100, int day = 100)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
看下面这段代码:class Stack { public: //默认构造函数有三种 // 1.无参的构造函数 // 2.全缺省构造函数 // 3.我们没写,系统默认生成的构造函数 //有参不是全缺省,下面函数不是默认构造函数 Stack(int capacity) { _a = (int*)malloc(sizeof(int) * capacity); _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: void StackPush() {} void StackPop() {} private: Stack _s1; Stack _s2; }; int main() { MyQueue q1; return 0; }
创建q1时,MyQueue里我们没写有显式定义构造函数,则系统自动生成默认构造函数,对自定义类型成员变量 _s1 、_s2 进行初始化。此时会调用Stack里的默认构造函数对_s1 、_s2进行初始化,但是Stack里面有显式定义构造函数,没有默认构造函数,就会出现下面情景, 无法进行初始化。
调用默认构造函数过程(初始化时,会进行层层默认构造函数调用):
MyQueue(有默认构造函数) -> Stack(无默认构造函数)
针对编译器自己生成默认成员函数,对内置类型/基本类型成员变量不初始化的问题,C++11打了补丁,可以直接给缺省值class S { public: private: //直接给的缺省值,编译器自己生成默认构造函数用 int _a =100; }; class Date { public: private: 直接给的缺省值,编译器自己生成默认构造函数用 int _year = 100; int _month = 100; int _day = 100; S aa; }; int main() { Date d1; return 0; }
总结:一般情况一个C++类,都要自己写构造函数。
一般只有少数情况可以让编译器默认生成,即:
1.类里面成员都是自定义类型成员,并且这些成员都提供了默认构造函数
2.如果还有内置类型成员,声明时给了缺省值
二、析构函数
析构函数是特殊的成员函数 ,与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
2.1 析构函数的特性
class Stack
{
public:
Stack(int capacity = 10)
{
_a = (int*)malloc(sizeof(int) * capacity);
_top = 0;
_capacity = capacity;
}
//析构函数
~Stack()
{
free(_a);
_a = NULL;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
//栈里面定义对象,析构顺序和构造顺序是反的(后进先出)
//构造顺序 s1,s2
//析构顺序 s2,s1(后进先销毁)
Stack s1;
Stack s2;
return 0;
}
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
如果构造函数是Init ,那么析构函数就是Destroy
析构函数和构造函数一样,系统为显式定义,系统会自动生成默认的。
三、拷贝构造函数
只有单个形参,该形参是对本类类型对象的引用( 一般常用const修饰 ),在用已存在的类类型对象创建新对象时由编译器自动调用
3.1 拷贝构造函数的特性
class Date
{
public:
Date(int year = 100, int month = 100, int day = 100)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& a)
{
_year = a._year;
_month = a._month;
_day = a._day;
}
//普通构造函数
Date(const Date* b)
{
_year = b->_year;
_month = b->_month;
_day = b->_day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
//调用拷贝构造函数
Date d2(d1);
//调用普通构造函数
Date d3(d1);
return 0;
}
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
3.1.1 浅拷贝
若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储 按字节序 完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
- 内置类型成员会完成浅拷贝,值拷贝
- 自定义类型成员,去调用这个成员的拷贝构造
class Date
{
public:
Date(int year = 100, int month = 100, int day = 100)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
//此时会调用系统默认生成的拷贝构造函数,d1、d2值完全一样
Date d2(d1);
return 0;
}
但是不是所以的情况都能按字节序完成拷贝,即下面情况,按字节序程序会崩溃:
class Stack
{
public:
Stack(int capacity = 10)
{
_a = (int*)malloc(sizeof(int) * capacity);
_top = 0;
_capacity = capacity;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1;
Stack s2(s1);
return 0;
}
这种情况浅拷贝时:
1.数据同时指向一块动态空间,修改数据会相互影响。
2.这块动态空间析构时会释放两次,程序会崩溃。
所以还是需要自己实现拷贝构造函数,需要用深拷贝来完成。
总结: 一般的类,系统生成的拷贝函数就够用了。只有像stack,Sqelist这样的需要自己直接管理资源的类,就需要自己实现深拷贝。
四、赋值运算符重载
4.1 运算符重载
函数结构:返回值类型 operator 操作符 (参数列表)
内置类型,可以直接用各种运算符,但是自定义类型,不能直接用各种运算符。为了自定义类型可以使用各种运算符,所以有了运算符重载的规则
class Date
{
public:
Date(int year = 2022, int month = 5, int day = 20)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//this指向的调用函数的对象是左操作数
//bool operator==(Date* this , const Date& d)
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//this指向的调用函数的对象是左操作数
//bool operator<(Date* this , const Date& d)
bool operator<(const Date& d)
{
if ((_year < d._year) ||
(_year == d._year) && (_month < d._month) ||
(_year == d._year) && (_month == d._month) && (_day < d._day))
{
return true;
}
else
{
return false;
}
}
//this指向的调用函数的对象是左操作数
//bool operator>(Date* this , const Date& d)
bool operator>(const Date& d)
{
if ((_year > d._year) ||
(_year == d._year) && (_month > d._month) ||
(_year == d._year) && (_month == d._month) && (_day > d._day))
{
return true;
}
else
{
return false;
}
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 5, 20);
Date d2(2022, 5, 21);
Date d3(2022, 5, 22);
//编译器会处理成对应重载运算符调用 if (d1.operator==(d2))
if (d1 == d2)
{
cout << "==" << endl;
}
//编译器会处理成对应重载运算符调用 if (d3.operator>(d2))
if (d3 > d2)
{
cout << ">" << endl;
}
//编译器会处理成对应重载运算符调用 if (d1.operator<(d2))
if (d1 < d2)
{
cout << "<" << endl;
}
return 0;
}
需要注意的是:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参。
- .* 、 :: 、 sizeof 、 ?: 、 . 注意以上5个运算符不能重载。
4.2 赋值运算符重载
赋值运算符需要注意的点:
参数类型 返回值 检测是否自己给自己赋值 返回*this 一个类 如果没有显式定义赋值运算符重载,编译器也会生成一个 ,完成对象按字节序的值拷贝。
class Date
{
public:
Date(int year = 2022, int month = 5, int day = 20)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//赋值运算符重载
Date& operator=(const Date& d)
{
//如果不是赋值给自己
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 5, 20);
Date d3(2022, 5, 21);
//拷贝构造, 一个存在的对象去初始化另一个要创建的对象
Date d2(d1);
//赋值重载/复制拷贝, 两个已经存在对象之间赋值
d3 = d2 = d1;//
return 0;
}
五、日期类的实现
Date.h
#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:
//全缺省构造函数
Date(int year = 2022, int month = 5, int day = 20)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//判断是否为闰年
bool IsLeapYear(int year)
{
return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}
//获取某年某月的天数
int GetMonthDay(int year, int month);
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//返回类型:
//返回时存储空间还存在,则可以用引用返回或者传值返回( Date& / Date )
//返回时存储空间使用权被回收,则只能用传值返回( Date )
//日期 += 天数
Date& operator+=(int day);
//日期 + 天数
Date operator+ (int day);
//日期 - 天数
Date operator-(int day);
//日期 -= 天数
Date& operator-=(int day);
//日期 - 日期 返回天数
int operator-(const Date& d);
//前置和后置的区别:
//后置有参数,来区分前后置
//(int)意味着,只接收参数,但不使用
//前置++
Date& operator++()
{
*this += 1;
return *this;
}
//后置++
Date operator++(int)
{
Date ret(*this);
*this += 1;
return ret;
}
//前置--
Date& operator--()
{
*this -= 1;
return *this;
}
//后置--
Date operator--(int)
{
Date ret(*this);
*this -= 1;
return ret;
}
// == 运算符重载
bool operator==(const Date& d);
// > 运算符重载
bool operator>(const Date& d);
// >= 运算符重载
bool operator>=(const Date& d)
{
return *this > d || *this == d;
}
//复用
// < 运算符重载
bool operator<(const Date& d)
{
return !(*this >= d);
}
// <= 运算符重载
bool operator<=(const Date& d)
{
return !(*this > d);
}
// != 运算符重载
bool operator!=(const Date& d)
{
return !(*this == d);
}
private:
int _year;
int _month;
int _day;
};
Date.cpp
#include "Date.h"
int Date::GetMonthDay(int year, int month)
{
assert(year > 0 && month > 0 && month < 13);
const static int Monthday[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && IsLeapYear(year))
{
return 29;
}
else
{
return Monthday[month];
}
}
Date& Date::operator+=(int day)
{
if (day < 0)
//负负得正,(-day)>0
return *this -= -day;
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month >= 13)
{
++_year;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day)
{
Date ret(*this);
ret += day;
return ret;
}
Date Date::operator-(int day)
{
Date ret(*this);
ret -= day;
return ret;
}
Date& Date::operator-=(int day)
{
if (day > 0)
return *this += -day;
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator>(const Date& d)
{
if (_year > d._year ||
(_year == d._year) && (_month > d._month) ||
(_year == d._year) && (_month == d._month) && (_day > d._day))
{
return true;
}
else
{
return false;
}
}
int Date::operator-(const Date& d)
{
//标记正负
int flag = 1;
Date Max = *this;
Date Min = d;
if (*this < d)
{
Max = d;
Min = *this;
flag = -1;
}
//计数
int n = 0;
while (Min != Max)
{
n++;
Min++;
}
return flag * n;
}
六、const成员
6.1 const修饰的成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date
{
public:
//void Print(Date* const this) -> Date*
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void Func(const Date& d)
{
d->Print();//d.Print(&d) -> const Date*
}
int main()
{
Date d1;
Func(d1);
d1.Print();//d1.Print(&d1) -> Date*
return 0;
}
成员函数后加const:
class Date
{
public:
//void Print(const Date* const this) -> const Date*
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void Func(const Date& d)
{
d.Print();//d.Print(&d) -> const Date*
}
int main()
{
Date d1;
Func(d1);
d1.Print();//d1.Print(&d1) -> Date*
return 0;
}
原则:成员函数内只要不改成成员变量,成员函数建议都加const
6.2 取地址及const取地址操作符重载
class Date
{
public:
Date* operator&()
{
//如果不想给别人懂地址,可以
//return nullptr
return this;
}
const Date* operator&()const
{
//如果不想给别人懂地址,可以
//return nullptr
return this;
}
private:
int _year;
int _month;
int _day;
};
这两个运算符一般不需要重载,不用重新定义,编译器默认会生成。 除非想让别人获取到指定的内容!