目录
6个默认成员函数
默认成员函数,若我们不显示定义,编译器会自动产生默认成员函数。
构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。
特性
特性:
1.名字与类名相同
2.无返回值(可以带参数)
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
/* 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。*/
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
Date d3();//声明d3函数,不是调用构造函数!
}
如果我们不写构造函数,会发生什么呢?
不写构造函数,编译器默认生成构造函数。
编译器默认生成的构造函数对于内置类型,不进行处理,初始化。
对于自定义类型,会调用它的默认构造函数,如果自定义类型没有默认构造函数,就报错!
默认构造函数有三种:1.编译器默认生成 2.显示定义全缺省构造函数 3.显示定义无参构造函数。
注:默认构造函数只能存在一个!!!
写构造函数:
class A
{public:
A()//默认构造函数
{
_a = 0;
cout << "A()" << endl;
}
private:
int _a;
};
class Date
{
public:
// 1.无参构造函数
Date()
{
_year = 0;
_month = 0;
_day = 0;
cout << "Date()" << endl;
}
// 2.带参全缺省构造函数
/*Date(int year=10, int month=10, int day=10)
{
_year = year;
_month = month;
_day = day;
}*/
//3.不显示定义,编译器默认生成
//这3种默认构造函数只能存在一个!!!(否则编译器不知道调用哪个)
private:
//内置类型
//如果不写构造函数,编译器生成的默认构造函数不对内置类型处理
int _year;
int _month;
int _day;
//自定义类型
A _aa;//调用自己的默认构造函数
};
void testdate()
{
Date d1;
}
int main()
{
testdate();
return 0;
}
不写构造函数
class A
{public:
A()//默认构造函数
{
_a = 0;
cout << "A()" << endl;
}
private:
int _a;
};
class Date
{
public:
// 1.无参构造函数
/*Date()
{
_year = 0;
_month = 0;
_day = 0;
cout << "Date()" << endl;
}*/
// 2.带参全缺省构造函数
/*Date(int year=10, int month=10, int day=10)
{
_year = year;
_month = month;
_day = day;
}*/
//3.不显示定义,编译器默认生成
//这3种默认构造函数只能存在一个!!!(否则编译器不知道调用哪个)
private:
//内置类型
//如果不写构造函数,编译器生成的默认构造函数不对内置类型处理
int _year;
int _month;
int _day;
//自定义类型
A _aa;//调用自己的默认构造函数
};
void testdate()
{
Date d1;
}
通过调试窗口和输出,验证了上面的说法。
析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
析构函数的调用规则与构造函数类似。
不写析构函数,编译器默认生成。
对于内置类型,不进行处理,
对于自定义类型,会调用它的析构函数。
如果自定义类型也没有自己的析构函数,它的默认析构函数不会对其完成清理。
所以凡是涉及到动态内存的,一定要写析构函数!否则内存泄漏。
编译器默认生成的析构函数对于自定义类型会完成处理吗?
答案是否定的,它不会完成对资源的释放。
class stack
{
public:
stack(int capacity=4)
{
if (_capacity == 0)
{
_a =(int*)malloc(sizeof(int) * capacity);
_capacity = capacity;
_top = 0;
}
}
~stack()
{
if (_a)
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
cout << "~stack()" << endl;
}
}
private:
int* _a;
int _top;
int _capacity;
};
void teststack()
{
stack s;
//出了test函数后会调用析构函数
}
int main()
{
teststack();
return 0;
}
拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特性
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
注:写了拷贝构造却不写构造函数,编译器不会生成默认构造函数!
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// Date(const Date d) // 错误写法:编译报错,会引发无穷递归
Date(const Date& d) //正确写法
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
如果不写拷贝构造,编译器会生成默认拷贝构造函数。
对于内置类型,会完成按字节拷贝(也就是浅拷贝)
对于自定义类型,会调用它的拷贝构造,若没写,则也完成浅拷贝。
如果进行浅拷贝,对于内置类型,没有问题,但对于涉及到动态空间的自定义类型,会造成大问题!
// 这里会发现下面的程序会崩溃掉?
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
/*注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝。*/
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
Stack s2(s1);//s1和s2指向同一块空间,free两次,所以崩溃
return 0;
通过调试窗口我们发现,s1,s2指向同一块空间,所以当main函数结束时,将对同一块空间调用两次析构函数,造成一块空间的多次释放!所以会崩溃。
所以只进行浅拷贝会出问题,要自己写拷贝构造。实现深拷贝。
赋值重载
运算符重载
c++为了增强代码可读性,引入了运算符重载,运算符重载是具有特殊函数名的函数。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this指针
.* :: sizeof ?: . 注意以上5个运算符不能重载。
1. 赋值运算符重载格式
参数类型:const A & ,传递引用可以提高传参效率
返回值类型:A & ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回 * this :要复合连续赋值的含义2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
运算符重载只能是类的成员函数。3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
Date& Date::operator=(const Date& d)//返回值方便连续=
{
if (*this != d)
{//自己给自己赋值,直接跳过!
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void test()
{
Date d1(2022, 12, 11);
Date d2=d1;//与d2(d1)相同,调用拷贝构造!因为d2是刚创建的新对象
Date d3;
d3 = d2 = d1;//赋值重载,d1,d2,d3均已存在
d1.Print();
d2.Print();
d3.Print();
}
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必 须要实现。
总结:
赋值重载与拷贝构造类似。
如果不写,编译器默认生成
对于内置类型,完成浅拷贝,
对于自定义类型,调用它自己的函数
如果自定义类型也未写,则也进行浅拷贝。
const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
void Print()const;//形参为 const Date const*this
void Print();//形参为Date const*this
思考下面几个问题:
1. const对象可以调用非const成员函数吗?
2. 非const对象可以调用const成员函数吗?
3. const成员函数内可以调用其它的非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数吗?
以Date中Print为例子:
1.const对象不可以调用非const函数,会发生权限放大!
例:const对象传给非const的Print,相当于 const Date*this传给Date const*this
这里发生了权限的放大!所以不能调用。
2. 非const对象可以调用const成员函数。发生权限缩小。
权限小可以接受权限大的!
3.const成员函数内可以调用其它的非const成员函数
4.非const成员函数内可以调用其它的const成员函数
只要函数内部遵守传参规则,const和非const成员函数都可以相互调用!
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
class Date
{
public :
Date* operator&()
{
return this ;
//这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
//要重载,比如想让别人获取到指定的内容!
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
完整日期类实现
.h
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
void Print()const;
// 获取某年某月的天数
int GetMonthDay(int year, int month)const;
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day)const;
// 日期-天数
Date operator-(int day)const;
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
bool operator>(const Date& d)const;
// ==运算符重载
bool operator==(const Date& d)const;
// >=运算符重载
bool operator >= (const Date& d)const;
// <运算符重载
bool operator < (const Date& d)const;
// <=运算符重载
bool operator <= (const Date& d)const;
// !=运算符重载
bool operator != (const Date& d)const;
// 日期-日期 返回天数
int operator-(const Date& d)const;
int Dateweekday()const;
/*void operator<<(ostream& out);*/ //不能在局部定义
private:
int _year;
int _month;
int _day;
};
.cpp
#include"日期类.h"
void Date:: Print()const
{
if (_year<1 ||
_month < 1 ||
_month>12 ||
_day>GetMonthDay(_year, _month)
|| _day < 1)
{
cout << "非法日期";
}
cout << _year << '/' << _month << '/' << _day << endl;
}
Date::Date(int year, int month, int day)//默认构造函数 初始化
//缺省参数 不能同时在声明和定义存在
//建议存放在声明中
{
_year = year;
_month = month;
_day = day;
}
Date::~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
//cout << "拷贝构造" << endl;
}
int Date:: GetMonthDay(int year, int month)const
{
assert(year > 0 && month>0&&month<13);//禁止不合法
static 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& Date::operator=(const Date& d)//返回值方便连续=
{
if (*this != d)
{//自己给自己赋值,直接跳过!
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
bool Date::operator>(const Date& d)const
{
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;
}
else
{
return false;
}
}
bool Date:: operator < (const Date& d)const
{
return !(*this >= d);
}
bool Date:: operator==(const Date& d)const
{
return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator >= (const Date& d)const
{
return *this > d || *this == d;
}
bool Date:: operator <= (const Date& d)const
{
return *this < d || *this == d;
}
bool Date:: operator != (const Date& d)const
{
return !(*this == d);
}
Date&Date::operator+=(int day)
{
if (day < 0)
{
day = (-1) * day;
}
_day += day;
while ( _day>GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
Date Date::operator+(int day)const
{
Date ret = *this;
ret += day;
//ret为临时变量,会被销毁,所以不能引用返回!
return ret;
}
Date Date::operator-(int day)const
{
Date ret = *this;
return ret -= day;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
day = (-1) * day;
}
_day -= day;
while (_day<0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date& Date::operator++()//前置加加
{
_day += 1;
if (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
Date Date:: operator++(int)//后置加加
{
Date ret = *this;
++* this;
return ret;
}
Date Date::operator--(int)
{
Date ret = *this;
--* this;
return ret;
}
Date&Date:: operator--()
{
_day -= 1;
while(_day < 1)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
int Date:: operator-(const Date& d)const//日期-日期
{
int flag = 1;
Date min = d;
Date max = *this;
if (max < min)
{
max = d;
min = *this;
flag = -1;
}int count = 0;
while (max>min)
{
--max;
count++;
}
return flag * count;
}
int Date:: Dateweekday()const
{
static Date tmp(2022, 12, 5);
int t = *this - tmp;
if (t < 0)
{
t = -t;
}
return (t % 7) + 1;
}
ostream& operator<<(ostream& out,const Date&d )
{
out << d._year << '/' << d._month << '/' << d._day << endl;;
return out;
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
本篇文章就梳理到这里。