目录
1.类的6个默认成员函数
任何一个类在不写的情况下会自动生成6个成员函数。
class Date{}
2.构造函数
2.1概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象生命周期内只调用一次。
2.2特性
构造函数是特殊的成员函数,需要注意的是,构造函数的名字虽然叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特征:
- 函数名与类名相同。
- 无返回值
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
class Date
{
public:
//构造函数,无返回值,名字与类名相同,实例化时自动调用
//无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//支持函数重载
//带参构造函数
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//一般使用全缺省构造函数
Data(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
}
int main()
{
//Data d1();
//无参数时不能有括号,会被编译器忽略。
//无法区分是函数声明还是对象的定义应该如下:
Data d1;
Data d2(2022, 1, 1);
}
5.如果类中没有显式定义构造函数,C++编译器会自动生成一个无参的默认构造函数,一旦用户定 义编译器不再生成。
6.默认构造函数(不传参就可以调用的构造函数) 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 无参构造函数、全缺省构造函数以及编译器默认生成的构造函数,都认为时默认构造函数。
class Date
{
public:
//使用时不能同时存在,默认构造函数只能存在一个
//无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//全缺省构造函数
Data(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
}
7.编译器生成默认的构造函数会对自定义类型成员调用它的默认成员函数 默认生成构造函数对于内置类型成员变量不处理,对于自定义类型成员变量才会处理。
#include <iostream>
using namespace std;
class Stack
{
public:
Stack()
{
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue
{
public:
//自定义类型
//不能自己进行初始化,只能依赖默认构造函数,因为_st1为private,访问不了。
MyQueue()
{
_st1._a = null.ptr
}
//只有自定义类型成员
//默认生成的构造函数对_st1和_st2进行初始化
private:
//C++11补丁:针对编译器自己生成的默认构造函数不初始化问题
//给缺省值,编译器自己生成的默认构造函数使用
int _size = 0;
Stack _st1;
Stack _st2;
};
int main()
{
//类中的_st1和_st2被MyQueue类的默认构造函数会调用Stack的构造函数对其进行初始化
MyQueue q;
return 0;
}
如果一个类中的成员全是自定义类型,可以用默认生成的构造函数。如果有内置类型的成员,或 者需要显示传参初始化,那么都要自己实现构造函数。
若无默认构造函数给自定义类型调用,自定义类型便无法初始化,如下:
#include <iostream>
using namespace std;
class Stack
{
public:
//显示定义了构造函数,编译器便不会再自动生成默认构造函数
//传参的构造函数不是默认构造函数
Stack(int capacity)
{
_a = (int*)malloc(sizeof(int) * capacity);
_top = 0;
_capacity = capacity;
}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue
{
public:
//Stack类无默认构造函数给MyQueue的默认构造函数调用
private:
Stack _st1;
Stack _st2;
};
int main()
{
//MyQueue无默认构造函数可以调用,无法进行初始化,程序报错
MyQueue q;
return 0;
}
3.析构函数
3.1概念
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象再销毁时会自动调用析构函数,完成对象的一些资源清理工作。
3.2特性
析构函数是特殊的成员函数
特征:
- 析构函数名是在类名前加上字符~。
- 无参数返回值
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
class Date { public: //无资源清理,不需要析构函数 ~Date() { } private: int _year; int _month; int _day; }
class Stack { public: Stack() { _a = nullptr; _top = _capacity = 0; } //析构函数 ~Stack() { free(_a); _a = nullptr; _top = _capacity = 0; } private: int* _a; int _top; int _capacity; }; int main() { //栈里面定义对象 //根据栈帧后进先出的性质,由于st2后定义,程序结束时st2比st1先调用析构函数 Stack st1(1); Stack st2(2); return 0; }
- 对象生命周期结束时,C++编译器系统自动调用析构函数。 自动调用(对于内置类型不做处理,对于自定义类型会去调用它的析构函数,同构造函数)
4.拷贝构造函数
4.1概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
4.2特性
拷贝构造函数时特殊的成员函数。
特征:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归。 (将自定义类型实参传值给函数形参要调用拷贝构造)
class Date { public: //拷贝构造函数 //是构造函数的函数重载 //& 必须使用引用传参,若是单纯的传值传参会引发无穷递归 //const 使用&引用可能会改变d1,需要const保护d1 Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day } private: int _year; int _month; int _day; } int main() { Date d1(2022, 1, 1); Date d2(d1); //拷贝构造函数调用 return 0; }
- 若未显示定义,系统默认生成的拷贝构造函数。默认的拷贝构造函数对象按内存存储字节序完成拷贝,这种拷贝叫做浅拷贝或值拷贝。
浅拷贝的问题(浅拷贝的成员指向一块空间):
1.指向同一块空间,修改数据会互相影响。
2.这块空间析构时会释放两次,程序会崩溃。
class Stack
{
public:
Stack(int capacity)
{
_a = (int*)malloc(sizeof(int) * capacity);
_top = 0;
_capacity = capacity;
}
//拷贝构造函数
Stack(const Stack& st)
{
_a = st._a;
_top = st._top;
_capacity = st._capacity;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1(1);
Stack st2(st1);
return 0;
}
st1._a初始化会开辟一块空间,当st2对st1进行浅拷贝或值拷贝的时候仅仅只是将st1的地址赋值给st2._a,因此st1._a和st2._a指向内存中的同一块空间,当对象调用析构函数时,st1以及st2两个对象的析构函数都会作用于_a上,相当于_a的空间被释放了两次,程序崩溃。(需要用深拷贝实现来解决问题)
结论:一般的类,用默认生成的拷贝构造就够用了。只有像Stack这样的类,自己直接管理资源,需要自己实现深拷贝。
注意:两个已经存在的对象之间是赋值,已存在的对象与新对象之间是拷贝。
5.赋值运算符重载
5.1运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其它符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整形+,不能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
- .* 、:: 、sizeof 、?: 、. 注意:以上5个运算符不能重载。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//运算符重载
//函数名:operator运算符
//参数:运算符的操作数
//返回值:运算符运算后结果
//&引用 防止调用拷贝构造
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
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;
}
//赋值运算符重载
//用引用返回就不用传值返回,不会生成拷贝
Date& operator=(const Date& d)
{
//防止发生像d1 = d1这样自己给自己赋值的无效操作
if (this != &d) //这里的&d为取地址
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 1, 1);
Date d2(2022, 1, 2);
if (d1 == d2) // 编译器会处理成对应重载运算符调用if(d1.opreator(&d1, d2))
{
cout << "d1 = d2" << endl;
}
if (d1 < d2)
{
cout << "d1 < d2" << endl;
}
d2 = d1; // -> d2.operator(&d2, d1)
return 0;
}
6.日期的实现
6.1Date.h
#pragma once
#include <iostream>
#include <assert.h>
using std::cout;
using std::cin;
using std::endl;
class Date
{
friend std::ostream& operator<<(std::ostream& out, const Date& d);
friend std::istream& operator>>(std::istream& in, Date& d);
public:
bool isLeapYear(int year)
{
return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
}
int GetMonthDay(int year, int month);
Date(int year = 1, int month = 1, int day = 1);
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Date operator+(int day) const;
Date& operator+=(int day);
Date operator-(int day) const;
Date& operator-=(int day);
int operator-(const Date& d) const;
Date& operator++() // 前置 ++d1 (d1.opreator++())
{
*this += 1;
return *this;
}
//用参数来区分前置后置
Date operator++(int) // 后置 d1++ (d1.opreator++(0))
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date& operator--()
{
*this -= 1;
return *this;
}
Date operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
int operator-(const Date& d) const;
bool operator==(const Date& d) const;
bool operator<(const Date& d) const;
bool operator>(const Date& d) const
{
return !(*this <= d);
}
bool operator>=(const Date& d) const
{
return !(*this < d);
}
bool operator!=(const Date& d) const
{
return !(*this == d);
}
bool operator<=(const Date& d) const
{
return *this < d || *this == d;
}
private:
int _year;
int _month;
int _day;
};
6.2Date.cpp
#include "Data.h"
int Date::GetMonthDay(int year, int month)
{
assert(year >= 0 && month < 13);
//加static防止频繁调用开空间
static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && isLeapYear(year))
return 29;
else
return monthDayArray[month];
}
Date::Date(int year, int month, int day)
{
if (year >= 1 &&
month <= 12 && month >= 1 &&
day >= 1 && day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期非法" << endl;
}
}
bool Date::operator<(const Date& d) const
{
if ((_year < d._year)
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day))
return true;
else
return false;
}
bool Date::operator==(const Date& d) const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
Date Date::operator+(int day) const
{
Date ret(*this);
ret += day;
return ret;
/*Data ret(*this);
ret._day += day;
while (ret._day > GetMonthDay(ret._year, ret._month))
{
ret._day -= GetMonthDay(ret._year, ret._month);
ret._month++;
if (ret._month == 13)
{
ret._year++;
ret._month = 1;
}
}
return ret;*/
}
Date& Date::operator+=(int day)
{
//*this = *this + day;
//return *this;
if (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) const
{
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;
}
int Date::operator-(const Date& d) const
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
min = *this;
max = d;
flag = -1;
}
int n = 0;
while (min != max)
{
++n;
++min;
}
return n*flag;
}
std::ostream& operator<<(std::ostream& out, const Date& d)
{
out << d._year << "_" << d._month << "-" << d._day << endl;
return out;
}
std::istream& operator>>(std::istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
7.const成员
7.1const修饰类的成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明该成员函数中不能对类的任何成员进行修改。
8.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义,编译器会默认生成。
class date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获得指定的内容。