概述
类的默认成员函数
- 类的6个默认成员函数
- 构造函数
- 析构函数
- 拷贝构造
- 运算符重载
- const 修饰类的成员变量
- 取地址及const取地址操作符重载
- 默认函数实现日期类
之前我们看到,一个空类他其实也是有大小的,但是我们所定义的空类就真的什么也没有,只是一个空类吗?
类的6个默认成员函数
总的来说,任何一个类在我们什么都不写的情况下,都会自动生成6个默认的成员函数。
- 构造函数:完成初始化工作
- 析构函数:完成清理函数
- 拷贝构造:使用同一个类的对象初始化创建对象
- 赋值重载:主要是把一个对象赋值给另一个对象
- 取地址重载:有两个函数,主要是对普通对象和const对象取地址
但是这些默认生成的成员函数也不一定是靠谱的。
构造函数
class Data
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
这样的类,我们每次在创建一个新的对象的时候都需要完成初始化的操作,而在C++中对这一个过程做了优化,允许在创建类的时候对类中的成员变量进行初始化,看上去很神奇,其实他的本质还是调用了函数,即构造函数。
构造函数是一个特殊的成员函数,名字与类名相同,在创建类的对象时编译器会自动调用,保证每一个数据成员都有一个合适的初值,在对象的生命周期内只调用一次。
特性:
- 函数名与类名相同
- 没有返回值
- 对象实例化时编译器自动处理对应的构造函数
- 构造函数也支持重载
class Data
{
public:
//无参构造函数
Data()
{}
//带参构造函数
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void fun1()
{
Data a;//调用无参构造函数
Data b(2020, 3, 18);//调用带参构造函数
Data c();//错误的调用
}
构造函数本质还是一个函数,可以支持函数重载的,如果想要通过无参构造函数来创建对象的时候,对象名的后面不用跟上括号(第三种写法),否则就不是函数调用,而是函数声明了。
- 如果类中没有显示我们定义的构造函数,那么C++编译器会自动生成一个无参的默认构造函数。而如果我们定义了一个构造函数,那么编译器将不再生成,在使用的时候直接调用用户自定义的构造函数。
- 默认构造函数可以是无参的构造函数,也可以是全缺省的构造函数,并且默认构造函数只能有一个。
class Data
{
public:
//无参构造函数
Data()
{
_year = 2020;
_month = 3;
_day = 19;
}
//全缺省构造函数
Data(int year = 2020, int month = 3, int day = 19)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
我们知道,全缺省的函数也可以不用传参,要是我们在定义全缺省构造函数的同时,又调用一个无参构造函数,这样编译器可以通过吗?答案是显而易见的,编译器会报"指定了多个默认构造函数",以及"对重载函数的调用不明确"的错误。
同样的都是无参数可以通过的,但是放在一个类中编译器不知道应该执行哪一个函数,就会报错,所以说两种默认构造函数在同一个类中只能存在一种。
7. 如果我们在一个类中没有定义构造函数,编译器自动生成的默认构造函数会完成什么作用?
事实证明他没有完成我们想要的初始化操作,成员变量的结果依旧是一个随机值,那么这时候自动生成的默认构造函数开起来好像没有什么用。。。
而C++的类型有自定义类型和内置类型(基本类型)。内置类型就是在语法上已经存在的类型,就像int,char,double等等我们可以直接使用的类型;另一种就是我们自己定义的类型,就像struct,union,class等等。
我们发现编译器生成的默认构造函数可以对自定义类型的成员 _time进行访问,并调用time类的默认构造函数。
析构函数
析构函数的功能与构造函数相反,析构函数不是完成对象的销毁,因为局部对象的销毁工作是由编译器完成的。而类对象在销毁时会自动调用析构函数,以便完成类的一些资源清理工作。他是特殊的成员函数。
特性:
- 析构函数的函数名是在类名的前面加~字符
- 析构函数没有参数也没有返回值
- 一个类有且只有一个析构函数。同构造函数一样,如果未定义,编译器会自动生成一个默认的析构函数。
- 在对象的生命周期结束的时候,C++编译系统会自动调用析构函数。
class arr
{
public:
arr(int capacity = 10)
{
_capacity = capacity;
_size = 0;
_a = (int*)malloc(sizeof(int)* _capacity);
}
~arr()
{
if (_a)
{
free(_a); // 释放堆上的空间
_a = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}
private:
int* _a;
int _size;
int _capacity;
};
- 同构造函数一样,编译器默认生成的析构函数也会调用自定义类型的成员的析构函数。
拷贝构造
构造函数只有单个形参,这个参数是对他自己类型的对象的一个引用(一般使用const修饰),从而创造出另一个一模一样的对象。在用已存在的类的类型对象创建新对象时由编译器自动调用。
特性:
- 拷贝构造函数是构造函数的一个重载形式,函数名同类名,不同的是他只有类本身类型的一个参数
class Data
{
public:
Data(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
- 拷贝构造函数的参数只有一个并且必须使用引用传参,使用传值的方式会引发无穷递归调用。
- 若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储,并按照字节序完成拷贝,这种拷贝叫做浅拷贝或者值拷贝。
- 既然编译器可以默认生成的拷贝构造函数就可以完成字节序的拷贝,我们还需要自己实现吗?如果成员变量中没有指针类型的变量,我们就可以不用自定义拷贝构造函数,但是如果有指针类型的成员变量,在默认的浅拷贝时会出错,这时就需要深拷贝。
我们可以看到,在没有return 0之前,a和b类的内容都是一模一样的,两个指针变量指向的是同一块内存地址。然后当return 0之后,由于a和b类在栈中的存储的顺序,先处理的是b类,直接调用析构函数,这时发现a类也被析构了,但是问题是这时还没有调用a类的析构函数。
所以说,当b类析构函数执行结束后,再次调用a类的析构函数就会造成空指针的访问,肯定会出错的,这个时候默认的浅拷贝就出现了问题,就需要我们自定义的深拷贝了(后面再说)。
运算符重载
在C++中引入了运算符的重载,这是一个具有特殊函数名的函数,同时也具有返回值,函数名以及参数列表,其返回值类型和参数列表都和普通函数差不多。
意义
- 为了增强程序的可读性,让自定义类型可以像内置类型一样使用运算符。
使用方式
- 返回值类型 operator操作符(参数列表)
注意
- 不能通过连接其他符号来创建新的操作符,就像 operator@,这是错误的。
- 重载操作符必须有一个类的类型或者枚举类型的操作数
- 用于内置类型的操作符,他的含义不能改变,就像 内置的整形+,不能在函数内部使用-等其他操作,也就是不能把函数的本意改变。
- 作为类成员的重载函数时,他的形参可以比操作数的数目少1个,是因为成员函数的操作符有一个默认的形参this指针,可以当做一个形参。
- .*(成员指针访问运算符),::(域运算符),sizeof(长度运算符),?:(条件运算符),. (成员访问运算符)这五个运算符不可以被重载。
class Data
{
public:
//类中
bool operator==(Data& d2)
{
return _day == d2._day &&
_month == d2._month &&
_year == d2._year;
}
//private:
int _year;
int _month;
int _day;
};
//类外
bool operator==(const Data& d1, const Data& d2)
{
return d1._day == d2._day &&
d1._month == d2._month &&
d1._year == d2._year;
}
int main()
{
Data d1;
Data d2;
d1 == d2;
operator==(d1, d2);
return 0;
}
如果我们把运算符重载的函数写在类的外面时,只能访问公有的成员变量,私有的不可以访问,只能通过函数来访问。
而调用运算符重载函数,有两种方式,一种就是像我们调用普通函数一样去使用,第二种就是像我们使用变量那种形式进行简洁的使用。
通过查看,发现两种使用方式在底层调用子程序的时候,都是调用的同一个子程序。所以说,使用运算符重载还是比较简单,方便的。
特点:
- 参数类型
- 有返回值
- 还需要检测是否自己给自己赋值
- 返回*this
- 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝)。
int main()
{
Data d1;
Data d2 = d1;//这里调用的是拷贝构造,不是运算符重载
return 0;
}
拷贝构造是一个已经存在的类给另一个不存在的类拷贝一个数据,运算符重载是给两个都存在的类之间的互相赋值。
const 修饰类的成员变量
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
在使用const 的时候,需要注意,const变量的权限只能缩小,不能放大。以日期类为例
赋值变量类型 | 待赋值变量类型 | 是否可行 |
---|---|---|
const Date*(只读) | const Date*(只读) | 可行 |
Date*(可读可写) | Date*(可读可写) | 可行 |
const Date*(只读) | Date* (可读可写) | 不可行 |
Date*(可读可写) | const Date*(只读) | 可行 |
取地址及const取地址操作符重载
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个默认成员函数可以返回这个类this指针的地址,一般用默认生成的就好了。如果想让用户获取到指定的内容,可以稍加修改自定义弄一个。
默认函数实现日期类
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month)
{
static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[month];//拿到当前月除了2.29的最大日期
//如果出现2月29日
if (month == 2
&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
day += 1;
}
return day;
}
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
// d2(d1)
Date(const Date& d)
{
_day = d._day;
_month = d._month;
_year = d._year;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d)
{
_day = d._day;
_month = d._month;
_year = d._year;
return *this;
}
// 析构函数
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
// 日期+=天数
Date& operator+=(int day)
{
//如果日期是负数
if (day < 0)
{
*this -= -day;
return *this;
}
_day += day;
int maxDay = GetMonthDay(_year, _month);//获取当前月的最大天数
while (maxDay < _day)
{
_day -= maxDay;//减掉当前月的天数
_month++;//月+1
//如果月超了,年+1,月变成1月
if (_month == 13)
{
_month = 1;
_year++;
}
maxDay = GetMonthDay(_year, _month);
}
return *this;
}
// 日期-=天数
Date& operator-=(int day)
{
//如果要减的日期是负数
if (day < 0)
{
*this += -day;
return *this;
}
while (_day <= day)
{
_month--;//当前月份-1
//如果没得减,返回上一年
if (_month == 0)
{
_month = 12;
_year--;
}
int maxDay = GetMonthDay(_year, _month);
_day += maxDay;
}
_day -= day;
return *this;
}
// 日期+天数
Date operator+(int day)
{
Date tmp(*this);
//tmp.operator+=(day);
tmp += day;
return tmp;
}
// 日期-天数
Date operator-(int day)
{
Date tmp(*this);
//tmp.operator-=(day);
tmp -= day;
return tmp;
}
// 前置++ , ++i
Date& operator++()
{
*this += 1;
return *this;
}
// 后置++ ,为了区分前置++,加一个参数 i++
Date operator++(int)//Date operator++(int i) //也可以这样
{
Date tmp(*this);//拷贝++之前的值
*this += 1;
return tmp;
}
// 后置-- i--
Date operator--(int)
{
Date tmp(*this);//拷贝++之前的值
*this -= 1;
return tmp;
}
// 前置-- --i
Date& operator--()
{
*this -= 1;
return *this;
}
// >运算符重载
bool operator>(const Date& d)
{
根据年直接比
//if (_year > d._year)
// return true;
年相等比月
//if (_year == d._year && _month > d._month)
// return true;
年月相等比天
//if (_year == d._year && _month == d._month && _day > d._day)
// return true;
if ((_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day))
return true;
return false;
}
// ==运算符重载
bool operator==(const Date& d)
{
return _day == d._day &&
_month == d._month &&
_year == d._year;
}
// >=运算符重载
inline bool operator >= (const Date& d)
{
//即不小于 小于为假
return !(*this < d);
}
// <运算符重载
bool operator < (const Date& d)
{
//如果等于,和大于,则为假
if (*this == d || *this>d)
return false;
return true;
}
// <=运算符重载
bool operator <= (const Date& d)
{
//即不大于,大于为假
return !(*this > d);
}
// !=运算符重载
bool operator != (const Date& d)
{
//即等于为假
return !(*this == d);
}
bool LeapYear(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
return true;
return false;
}
// 日期-日期 返回天数
int operator-(const Date& d)
{
Date small;
Date big;
if (*this > d)
{
big = *this;
small = d;
}
else
{
big = d;
small = *this;
}
//累加法
int numDay = 0;
while (small!=big)
{
small++;
numDay++;
}
return numDay;
}
void Printf()
{
cout << _year << "-" << _month << "-" << _day;
}
private:
int _year;
int _month;
int _day;
};
代码链接github
最后
以上就是现代高山为你收集整理的类的默认成员函数(C++)类的6个默认成员函数构造函数析构函数拷贝构造运算符重载const 修饰类的成员变量取地址及const取地址操作符重载默认函数实现日期类的全部内容,希望文章能够帮你解决类的默认成员函数(C++)类的6个默认成员函数构造函数析构函数拷贝构造运算符重载const 修饰类的成员变量取地址及const取地址操作符重载默认函数实现日期类所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复