问题:如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?
并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。
class Date
{};
注意:虽然是空类,但是系统会为其分配1个字节来占位。
构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,完成对象的初始化,并且在对象的生命周期内只调用一次。
特征:
1. 函数名与类名相同。
2. 不具有任何类型,无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数也可以重载。
class Date
{
public :
// 1.无参构造函数
Date ()
{}
// 2.带参构造函数
Date (int year, int month , int day )
{
_year = year ;
_month = month ;
_day = day ;
}
private :
int _year ;
int _month ;
int _day ;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2 (2020, 3, 31); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 比如以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
Date d3();//错误做法
}
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成默认的无参构造函数
Date (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1;
// 没有显式定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数,但是都是随机值
}
6. 无参的构造函数
和全缺省的构造函数
都称为默认构造函数,并且默认构造函数只能有一个
。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
// 默认构造函数
class Date
{
public:
Date()//无参的构造函数
{
_year = 1900 ;
_month = 1 ;
_day = 1;
}
Date (int year = 1900, int month = 1, int day = 1) //全缺省的构造函数
{
_year = year;
_month = month;
_day = day;
}
private :
int _year ;
int _month ;
int _day ;
};
// 以下测试函数能通过编译吗?
void Test()
{
Date d1;//错误,这样不能确定到底是调用上面两个中的哪一个构造函数
}
所以说,无参的构造函数和全缺省的构造函数不能同时存在,而且一般建议默认构造函数写成全缺省函数格式。
7.C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。所以说,编译器生成的默认构造函数并不是没有作用的!
class Time//时间类
{
public:
Time()
{
_hour = 0;
_minute = 0;
_second = 0;
cout << "Time()" << endl;//打印这句证明调用过
}
private:
int _hour;
int _minute;
int _second;
};
class Date//日期类
{
private:
// 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time _t; //时间类对象
};
int main()
{
Date d; //调用日期类的默认构造函数,且对自定义类型的时间类再去调用他自己的默认构造函数
return 0;
}
析构函数
析构函数:析构函数是特殊的成员函数。与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
比如归还向系统申请的空间之类的,不然会造成内存泄漏等事故。
特性
1. 命名:析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class SeqList //以链表结构来举例
{
public :
SeqList (int capacity = 10)//构造函数来完成数据的初始化工作
{
_pData = (int*)malloc(capacity * sizeof(int));//在堆上开辟的内存
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList() //析构函数
{
if (_pData)
{
free(_pData ); // 释放堆上的空间
_pData = nullptr; // 将指针置为空 ,避免野指针
_capacity = 0;
_size = 0;
}
}
private :
int* _pData ;
size_t _size;
size_t _capacity;
};
5. 和编译器自动生成的默认构造函数一样,编译器自动生成的析构函数编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
class String //名字
{
public:
String(const char* str = "anna")//构造函数来完成名字初始化为anna
{
_str = (char*)malloc(strlen(str) + 1); //加上'\0'的字节
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str); //释放资源
_str=nullptr;
}
private:
char* _str;
};
class Person //人
{
private:
String _name;//自定义类型成员 程序结束后会自动调用它的析构函数
int _age;//内置类型成员
};
int main()
{
Person p;//定义对象
return 0;
}
拷贝构造函数
有时需要用到多个完全相同的对象,像以前的每个对象都要进行相同的初始化,很不方便,而c++中提供了对象的复制机制即(拷贝构造函数和赋值运算符重载),来解决这样的问题。
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征:
1.拷贝构造函数也是特殊的成员函数
2. 拷贝构造函数是构造函数的一个重载形式。
3.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
。
class Date
{
public:
Date(int year = 2020, int month = 3, int day = 31)//全缺省的构造函数
{
_year = year;
_month = month;
_day = day;
}
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;
}
4. 若未显示定义,系统生成默认的拷贝构造函数。而 默认的拷贝构造函数对象按内存存储的字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
所以一般不涉及动态分配的数据,可以用默认的拷贝构造函数。但是一旦涉及动态分配的数据,必须要自己定义拷贝构造函数(使用深拷贝)。
// 这里如果自己不定义拷贝构造函数,系统调用默认的则程序会崩溃掉
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1); //动态开辟申请空间
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
_str=nullptr;
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);//拷贝构造
}
解释:但是由于成员数据涉及动态开辟,浅拷贝只是把值简单的复制一次再赋值,这样在上面的场景中,拷贝构造完之后必然有两个指针指向同一块内存,问题是,当其中一个对象的被析构后,资源被释放掉了,但是还有一个指针还指向这那块已经被归还给os的内存,无缘无故就变成了个野指针了。故规定成员数据涉及动态开辟的,要拷贝构造,不能使用默认的浅拷贝!
赋值运算符重载
先谈运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数格式:返回值类型 operator操作符(参数列表)
注意:
1 .不能通过连接其他符号来创建新的操作符:比如operator@ operator# 之类的
2 .重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义,变成减法操作!
4.作为类成员的重载函数时,其形参看起来比操作数数目少1,因为成员函数的操作数有一个默认的形参this,限定为第一个形参
5. .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
赋值运算符重载
和拷贝构造函数一样,也是实现对象的复制机制。只不过是对赋值运算符的重载来完成的。
特征:
1.参数类型为引用,也是避免无限调用构造函数
2. 返回*this,所以返回值为引用
4. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个默认的,完成对象按字节序的值(浅)拷贝。
class Date
{
public :
Date(int year = 1900, int month = 1, int day = 1)//全缺省的构造函数
{
_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;
}
}
private:
int _year ;
int _month ;
int _day ;
};
int main()
{
Date d1(2020,3,31);
Date d2;//先初始化
d2 = d1;// 再 d1调用operator=完成拷贝
//注意一点
Date d3(2020,3,31);
Date d4=d3;//这种写法,调用的是拷贝构造函数,而不是赋值运算符重载
//解释:这是用d3来初始化d4,而初始化的操作是拷贝构造函数来完成的
return 0;
}
取地址及const取地址操作符重载
先谈const修饰类的成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date
{
public :
/*void Display ()
{
cout<<"Display ()" <<endl;
}*/
void Display () const
{
cout<<"Display () const" <<endl;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
int main()
{
Date d1 ;
d1.Display ();
/*const Date d2;
d2.Display ();*/
return 0;
}
利用上述代码来验证下面的问题:
- const对象可以调用非const成员函数吗?
不可以,本来const对象里的成员不允许被修改的,要是调用非const函数时里面被修改了,两者冲突,不被允许。 - 非const对象可以调用const成员函数吗?
可以,虽然非const对象里的成员数据允许被修改,调用const成员函数使其不能被修改,权限虽然被缩小了,但不冲突。 - const成员函数内可以调用其它的非const成员函数吗?
同上 - 非const成员函数内可以调用其它的const成员函数吗?
同上
总结一句话:把他们想象成权限问题,非const表示可读可写,const表示只读,权限只能缩小,不能被放大。(可以包容,但不能纵容,哈哈!)。
取地址及const取地址操作符重载
这两个也是默认的成员函数,系统一般会自动生成默认的。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const //确切一点说后面的const是修饰成员函数的*this而不是this,保护调用对象不能被修改
{
return this ;
}
private :
int _year ;
int _month ;
int _day ;
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
实现一个时间类(对本节知识的运用)
#include<iostream>
using namespace std;
class Date
{
public:
int GetYearMonthday(int year, int month)
{
static int monthday[13] = { 0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (year % 400 == 0 || ((year % 4 == 0) && (year % 100 == 0))&& month==2)
{
return 29;
}
return monthday[month];
}
Date(int year = 1900, int month = 1, int day = 1) //全缺省默认构造函数
{
if (year > 0 && (month > 0 && month<13) && (day>0 && day < GetYearMonthday(year, month)))
{
_year = year;
_month = month;
_day = day;
}
else
cout << "输入时间不合法!!请重新输入" << endl;
}
Date(const Date& d) //拷贝构造函数
{
_year = d._year;
_month = d._month;
this->_day = d._day;
}
Date& operator=(const Date& d)//赋值运算符重载
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
/*Date operator+(int days)
{
Date ret = *this;
ret._day += days;
while (ret._day > GetYearMonthday(ret._year, ret._month))
{
ret._day -= GetYearMonthday(ret._year, ret._month);
ret._month++;
if (ret._month == 13)
{
ret._year++;
ret._month = 1;
}
}
return ret;
}*/
Date operator+(int days) // 复用+= 单纯的算结果,自己的值不变 操作一个临时变量
{
Date ret = *this; //因为是初始化 这个其实调用的是拷贝构造函数,而不是赋值运算符重载 后者的使用情况是 先初始化Date ret; 再赋值 ret = *this;
ret += days;
return ret;
}
Date& operator+=(int days) //自己的值也改变了
{
this->_day += days;
while (this->_day > GetYearMonthday(this->_year, this->_month))
{
this->_day -= GetYearMonthday(this->_year, this->_month);
this->_month++;
if (this->_month == 13)
{
this->_year++;
this->_month = 1;
}
}
return *this;
}
Date operator-(int days) //自己值不变
{
Date ret = *this;
ret -= days;
return ret;
}
Date & operator-=(int days)
{
if (days < 0)
{
*this += (-days); //减的是负数,相当于加法 复用+=
return *this;
}
_day -= days;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetYearMonthday(_year, _month);
}
return *this;
}
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; //返回时,由于是临时变量 不能传引用,因为函数结束后,临时变量销毁,引用的本体没了,引用也不能用, 所以返回值 又是一次拷贝构造
}
inline bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year&& _month == d._month && _year > d._year);
}
inline bool operator==(const Date& d)const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//为了提高代码复用性 ,下面所有关系运算符重载都可以 复用上面这两个 即 > 和== 并且都可以写成内联函数
inline bool operator>=(const Date& d)const
{
return (*this > d) || (*this == d);
}
inline bool operator<(const Date& d)const
{
return !(*this >= d);
}
inline bool operator<=(const Date& d)const
{
return !(*this>d);
}
inline bool operator!=(const Date& d)const
{
return !(*this == d);
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};