类和对象------中
类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显示实现,编译器会生成的成员函数称为默认成员函数。class Date{ };
构造函数
概念
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class Date {
public:
void Init(int year = 2023, int month = 1, int day = 1);
void Print();
private:
int _year;
int _month;
int _day;
};
void Date::Init(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Date::Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
int main() {
Date d1;
d1.Init(2023, 4, 29);
d1.Print();
Date d2;
d2.Init();
d2.Print();
return 0;
}
对于Date类,可以通过Init()公有方法给对象设置日期,这样创建对象有些麻烦,我们可以使用构造函数,在创建对象时,就将信息设置进去。
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。
特性
构造函数是特殊的成员函数,虽然名称叫构造函数,但是构造函数承担的主要任务不是开空间创建对象,而是初始化对象。
特征:
1.函数名与类名相同
2.无返回值
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载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; } void Print(); private: int _year; int _month; int _day; }; void Date::Print() { cout << _year << "-" << _month << "-" << _day << endl; } int main() { Date d1;//调用无参构造函数 //Date d1();//错误调用无参构造函数, //原因:如果通过无参构造函数创建对象时, //对象名后面跟上括号,编译器就会认为这是一个函数声明 //正确的用法是对象名后面不跟括号,编译器就会正确的按照我们的想法编译 d1.Print(); Date d2(2023, 4, 29);//调用带参构造函数 d2.Print(); return 0; }
5.如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成
class Date { public: //如果用户显示定义了构造函数,编译器将不在自动生成默认构造函数 /*Date(int year, int month, int day) { _year = year; _month = month; _day = day; }*/ void Print(); private: int _year; int _month; int _day; }; void Date::Print() { cout << _year << "-" << _month << "-" << _day << endl; } int main() { //将Date类中构造函数屏蔽后代码可以正常运行,因为编译器自动生成了一个默认无参的构造函数 //将Date类中的构造函数放开,代码编译失败,因为用户一旦定义,编译器将不在自动生成,没有无参构造函数 Date d; return 0; }
6.编译器自动生成的无参默认构造函数,不会对内置类型的成员做处理(内置成员中保存的是随机值),会对自动类型的成员调用它自己对应的构造函数
class Time { public: Time() { cout << "Time()" << endl; _hour = 0; _minute = 0; _second = 0; } int _hour; int _minute; int _second; }; class Date { public: void Print() { cout << _year << "-" << _month << "-" << _day << "-" << _t._hour << "-" << _t._minute << "-" << _t._second << endl; } private: //基本类型(内置类型) int _year; int _month; int _day; //自定义类型 Time _t; }; int main() { Date d; d.Print(); return 0; }
注意:C++11中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值class Time { public: Time() { cout << "Time()" << endl; _hour = 0; _minute = 0; _second = 0; } int _hour; int _minute; int _second; }; class Date { public: void Print() { cout << _year << "-" << _month << "-" << _day << "-" << _t._hour << "-" << _t._minute << "-" << _t._second << endl; } private: //基本类型(内置类型) int _year = 2023; int _month = 4; int _day = 29; //自定义类型 Time _t; }; int main() { Date d; d.Print(); return 0; }
7.无参构造函数和全缺省构造函数都叫默认构造函数,并且默认构造函数只能有一个
无参构造函数、全缺省构造函数和编译器默认生成的构造函数都是默认构造函数。class Date { public: //默认构造函数一个类只能有一个,因为我们在调用默认构造函数时,如果有多个编译器就不知道我们要调用哪一个 // //无参构造函数 Date() {} //全缺省构造函数 Date(int year = 2023, int month = 4, int day = 1) {} private: int _year; int _month; int _day; }; int main() { Date d; return 0; }
析构函数
概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性
1.析构函数名是在类名前加上字符~
2.无参数、无返回值
3.一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4.对象声明周期结束时,C++编译系统自动调用对应的析构函数typedef int STDataType; class Stack { public: Stack(int capacity = 4) { _a = (STDataType*)malloc(sizeof(STDataType) * capacity); if (_a == nullptr) { perror("malloc"); return; } _capacity = capacity; _top = 0; } //析构函数 ~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = 0; _capacity = 0; } private: STDataType* _a; int _top; int _capacity; }; int main() { Stack st; return 0; }
5.编译器自动生成的析构函数,对内置类型的成员不做处理,对自动的成员调用它对应的析构函数。
6.如果类中没有申请资源时,析构函数可以不写,直接使用编译器默认析构函数,比如Data类。有资源申请,一定要写,否则会造成资源泄漏,比如Stack类。
拷贝构造函数
概念
拷贝构造函数:只有单个形参,改形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征
1.拷贝构造函数是构造函数的一个重载形式,无返回值。
**2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,**因为会引发无穷递归调用。一般拷贝构造函数的形参需要加const修饰。class Date { public: //构造函数 Date(int year = 2023, int month = 4, int day = 30) { _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; } private: int _year; int _month; int _day; }; void func(Date d) {} int main() { Date d; func(d); return 0; }
补充小知识:C++规定自定义类型在传值调用函数时,要先调用自定义类型它的拷贝构造函数去完成参数的拷贝工作(形参是实参的一份临时拷贝),而内置类型直接拷贝。class Date { public: //构造函数 Date(int year = 2023, int month = 4, int day = 30) { _year = year; _month = month; _day = day; } //拷贝构造函数----错误的写法 //Date(const Date d); const Date是自定义类型,在传参时会调用它的拷贝构造,而这个正是它的拷贝构造,会造成无穷递归 //拷贝构造函数----正确的写法 Date(const Date& d) { cout << "Date(const Date& d)" << endl; _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Date d1; //以下这两种写法都可以,都是调用拷贝构造函数创建新对象,用一个已存在的对象创建新的对象 //Date d2(d1); Date d2 = d1; return 0; }
**3.若未显示定义,编译器会生成默认的拷贝构造函数。**默认的拷贝构造函数,对象按内存存储的字节序完成拷贝,这种拷贝叫浅拷贝或者值拷贝,在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成的拷贝。class Time { public: //Time类的构造函数 Time() { _hour = 1; _minute = 1; _second = 1; } //Time类的拷贝构造函数 Time(const Time& t) { cout << "Time(const Time& t)" << endl; _hour = t._hour; _minute = t._minute; _second = t._second; } private: int _hour; int _minute; int _second; }; class Date { private: //内置类型 int _year; int _month; int _day; //自定义类型 Time _t; }; int main() { Date d1; //用已存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数 //但Date类并没有显示定义拷贝构造函数,编译器会默认生成一个拷贝构造函数 Date d2(d1); return 0; }
4.虽然编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,但是类中一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,如果未设计到资源申请,拷贝构造函数写与不写都可以。//下面代码运行会崩溃 typedef int StackDataType; class Stack { public: Stack(int capacity = 4) { _a = (StackDataType*)malloc(sizeof(StackDataType) * capacity); if (nullptr == _a) { perror("malloc"); return; } _top = 0; _capacity = capacity; } ~Stack() { free(_a); _a = nullptr; _top = 0; _capacity = 0; } private: StackDataType* _a; int _top; int _capacity; }; int main() { Stack s1; //调用编译器默认生成的拷贝构造函数 Stack s2(s1); return 0; }
5.拷贝构造函数调用的场景:
- 使用已存在的对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回值根据具体情况,能用引用就用引用。
赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如内置类型+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1个,因为成员函数的第一参数为隐藏的this
.* :: sizeof ?: .
注意以上5个运算符不能重载
//全局的operator
class Date {
public:
Date(int year = 2023, int month = 4, int day = 1) {
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
bool operator==(const Date& d1, const Date& d2) {
return (d1._day == d2._day &&
d1._year == d2._year &&
d1._month == d2._month);
}
int main() {
Date d1;
Date d2;
cout << (d1 == d2) << endl;
return 0;
}
//成员函数operator==
class Date {
public:
Date(int year = 2023, int month = 4, int day = 1) {
_year = year;
_month = month;
_day = day;
}
//bool operator==(Date* const this, const Date& d)
//这里的做操作数是this,指向调用函数的对象
bool operator==(const Date& d) {
return (_day == d._day &&
_year == d._year &&
_month == d._month);
}
//private:
int _year;
int _month;
int _day;
};
int main() {
Date d1;
Date d2;
cout << (d1 == d2) << endl;
return 0;
}
赋值运算符重载
1.赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值的目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this:要复合连续赋值的含义
class Date {
public:
Date(int year = 2023, int month = 4, int day = 1) {
_year = year;
_month = month;
_day = day;
}
//Date& operator=(Date* const this, const Date& d)
//这里的做操作数是this,指向调用函数的对象
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(2000, 1, 1);
Date d2;
cout << d1._year << "-" << d1._month << "-" << d1._day << endl;
cout << d2._year << "-" << d2._month << "-" << d2._day << endl;
d2 = d1;
cout << d1._year << "-" << d1._month << "-" << d1._day << endl;
cout << d2._year << "-" << d2._month << "-" << d2._day << endl;
return 0;
}
2.赋值运算符只能重载成类的成员函数,不能重载成全局函数
原因:赋值运算符如果不显示定义,编译器会生成一个默认的,此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成从默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
class Date {
public:
Date(int year = 2023, int month = 4, int day = 1) {
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
//Date& operator=(Date* const this, const Date& d)
//这里的做操作数是this,指向调用函数的对象
Date& operator=(Date& d1, const Date& d2) {
if (&d1 != &d2) {
d1._year = d2._year;
d1._month = d2._month;
d1._day = d2._day;
}
return d1;
}
//编译失败
//error C2801: “operator =”必须是非静态成员
int main() {
Date d1(2000, 1, 1);
Date d2;
cout << d1._year << "-" << d1._month << "-" << d1._day << endl;
cout << d2._year << "-" << d2._month << "-" << d2._day << endl;
d2 = d1;
cout << d1._year << "-" << d1._month << "-" << d1._day << endl;
cout << d2._year << "-" << d2._month << "-" << d2._day << endl;
return 0;
}
**3.用户没有显示实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节赋值。**内置类型成员变量都是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载函数完成赋值。
注意:如果类中未设计资源申请,赋值运算符重载是否实现都可以;一旦涉及到资源申请则必须要实现。
前置++和后置++重载
class Date {
public:
Date(int year = 2023, int month = 4, int day = 1) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
int GetMonthDay() {
static int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = days[_month];
if (_month == 2 && ((_year % 4 == 0 && _year % 100 != 0) || _year % 400 == 0)) {
day++;
}
return day;
}
//前置++,返回+1后的结果
Date& operator++() {
++_day;
while (_day > GetMonthDay()) {
_day -= GetMonthDay();
++_month;
if (_month == 13) {
_month = 1;
++_year;
}
}
return *this;
}
//C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
//后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
//而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int) {
Date tmp = *this;
++(*this);
return tmp;
}
//private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2023, 4, 1);
Date d2(2023, 4, 1);
Date tmp;
tmp = d1++; //tmp:2023-4-1
tmp.Print();
d1.Print();
tmp = ++d2; //tmp:2023-4-2
tmp.Print();
d2.Print();
return 0;
}
日期类的实现
class Date {
public:
Date(int year = 2023, int month = 4, int day = 1);
Date(const Date& d);
void Print();
int GetMonthDay();
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
Date& operator=(const Date& d);
Date& operator+=(int day);
Date operator+(int day);
Date& operator-=(int day);
Date operator-(int day);
int operator-(const Date& d);
bool operator>(const Date& d);
bool operator==(const Date& d);
bool operator>=(const Date& d);
bool operator<(const Date& d);
bool operator<=(const Date& d);
bool operator!=(const Date& d);
~Date();
private:
int _year;
int _month;
int _day;
};
Date::Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
int GetYearDay(int _year) {
int day = 365;
if (((_year % 4 == 0 && _year % 100 != 0) || _year % 400 == 0)) {
day++;
}
return day;
}
int Date::GetMonthDay() {
static int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = days[_month];
if (_month == 2 && ((_year % 4 == 0 && _year % 100 != 0) || _year % 400 == 0)) {
day++;
}
return day;
}
void Date::Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
Date& Date::operator++() {
++_day;
while (_day > GetMonthDay()) {
_day -= GetMonthDay();
++_month;
if (_month == 13) {
_month = 1;
++_year;
}
}
return *this;
}
Date Date::operator++(int) {
Date tmp = *this;
++(*this);
return tmp;
}
Date& Date::operator--() {
--_day;
if (_day == 0) {
--_month;
if (_month == 0) {
_month = 12;
--_year;
}
_day = (*this).GetMonthDay();
}
return *this;
}
Date Date::operator--(int) {
Date tmp = *this;
--(*this);
return tmp;
}
Date::Date(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& Date::operator=(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
Date::~Date() {
_year = 0;
_month = 0;
_day = 0;
}
Date& Date::operator+=(int day) {
if (day < 0) {
(*this) -= -day;
}else {
_day += day;
while ((_day) > (*this).GetMonthDay()) {
_day -= (*this).GetMonthDay();
++_month;
if (_month == 13) {
_month = 1;
++_year;
}
}
}
return (*this);
}
Date Date::operator+(int day) {
Date tmp = *this;
tmp += day;
return tmp;
}
Date& Date::operator-=(int day) {
if (day < 0) {
(*this) += -day;
}else {
_day -= day;
while ((_day) <= 0) {
--_month;
if (_month == 0) {
_month = 12;
--_year;
}
_day += (*this).GetMonthDay();
}
}
return (*this);
}
Date Date::operator-(int day) {
Date tmp = *this;
tmp -= day;
return tmp;
}
int Date::operator-(const Date& d) {
if ((*this) == d) {
return 0;
}
int tmp = 0;
int flag = 1;
Date d1;
Date d2;
if ((*this) > d) {
d1 = d;
d2 = (*this);
}
else {
flag = -1;
d1 = (*this);
d2 = d;
}
for (int i = d1._year + 1; i < d2._year; ++i) {
tmp += GetYearDay(i);
}
if (d1._year != d2._year) {
tmp += d1.GetMonthDay() - d1._day;
++(d1._month);
for (; (d1._month) <= 12; ++(d1._month)) {
tmp += d1.GetMonthDay();
}
tmp += d2._day;
--(d2._month);
for (; (d2._month) >= 1; --(d2._month)) {
tmp += d2.GetMonthDay();
}
}else {
if (d1._month == d2._month) {
tmp += (d2._day - d1._day);
}else {
tmp += d1.GetMonthDay() - d1._day;
++(d1._month);
tmp += d2.GetMonthDay();
--(d2._month);
for (; (d1._month) <= (d2._month); ++(d1._month)) {
tmp += d1.GetMonthDay();
}
}
}
return (tmp * flag);
}
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) {
return true;
}else if (_year == d._year && _month > d._month) {
return true;
}else if (_year == d._year && _month == d._month && _day > d._day) {
return true;
}
return false;
}
bool Date::operator>=(const Date& d) {
return (((*this) > d) || ((*this) == d));
}
bool Date::operator<(const Date& d) {
return (!((*this) >= d));
}
bool Date::operator<=(const Date& d) {
return (!((*this) > d));
}
bool Date::operator!=(const Date& d) {
return (!((*this) == d));
}
int main() {
Date d(2023, 4, 30);
d++;
d.Print();
d--;
d.Print();
d += 100;
d.Print();
d -= 100;
d.Print();
(d + 100).Print();
d.Print();
(d - 100).Print();
d.Print();
Date tmp(2023, 4, 30);
cout << (tmp == d) << endl;
cout << (tmp >= d) << endl;
cout << (tmp != d) << endl;
tmp++;
cout << (tmp > d) << endl;
cout << (tmp >= d) << endl;
cout << (tmp != d) << endl;
tmp -= 10;
cout << (tmp <= d) << endl;
cout << (tmp < d) << endl;
cout << (tmp != d) << endl;
cout << ((tmp + 1000) - tmp) << endl;
return 0;
}
const成员函数
将const修饰的成员函数称之为const成员函数,const修饰成员函数,实际修饰的是隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date {
public:
Date(int year = 2023, int month = 4, int day = 1) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << "Print()" << endl;
cout << _year << "-" << _month << "-" << _day << endl;
}
void Print() const {
cout << "Print() const" << endl;
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void Test() {
Date d1;
d1.Print();
const Date d2;
d2.Print();
}
int main() {
Test();
return 0;
}
结论:
1.const对象不可以调用非const成员函数
2.非const对象可以调用const成员函数
3.const成员函数不可以调用其他非const成员函数
4.非const成员函数不可以其他调用const成员函数
取地址及const取地址操作符重载
class Date {
public:
//取地址重载
Date* operator&() {
return this;
}
//const取地址重载
const Date* operator&() const {
return this;
}
private:
int _year;
int _month;
int _day;
};
这两个运算符一遍不需要重载,使用编译器自动生成的默认取地址重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定信息
结语
到这里这篇博客已经结束啦。
这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀