1.类的6个默认成员函数
类六个默认函数包括构造、拷贝构造、析构、赋值运算符重载、取地址操作符重载、const修饰的取地址操作符重载,默认生成
2.构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员
都有 一个合适的初始值,并且在对象的生命周期内只调用一次
特性如下
1. 函数名与类名相同。
2. 无返回值。(也没有void返回值)
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定
义编译器将不再生成。
构造出的对象元素为随机值
重点:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员
class A
{
private:
int a;
public:
A(int b=0)
{
......
}
A()
{
......
}
}
上面的代码会编译错误,因为程序无法判断,当设置一个无参数传递的对象调用哪一个构造函数。
小知识点:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char...,自定义类型就是我们使用class/struct/union自己定义的类型,编译器生成默认的构造函数会对自定类型成员t调用的它的默认成员如下
#include <iostream>
using namespace std;
class A
{
int _a;
public:
A(int a=0)
{
a = _a;
cout<<"使用a的构造函数"<<endl;
}
};
class B
{
int _b;
A a;
public:
};
int main()
{
B b;
}
小知识点:作为成员变量命名最好使用_加名字用来提高代码的可读性,也有加m_(member的首字母)的写法
3.析构函数
与构造函数相对,析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作,如内存的释放,指针的归零等。
析构函数是特殊的成员函数。其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。系统会自动生成默认的析构函数。
4. 拷贝构造函数
属于构造函数的一种,参数为类的类型的引用,作用用来拷贝一个对象就像是ctrl c ctrl v的功能一样,例子如下
#include <iostream>
using namespace std;
class A
{
int _a;
public:
A(int a=0)
{
a = _a;
cout<<"使用a的构造函数"<<endl;
}
A(A& a)
{
_a = a._a;
}
};
int main()
{
B b;
}
注意的是系统默认的拷贝函数使用的是浅拷贝,逐个字节进行拷贝,对于一般类型的成员不会出错,但是对于指针成员,拷贝出的新的指针成员,会指向相同的地址,产生一系列的麻烦。需要人为写拷贝函数,进行深拷贝。
知识点:拷贝函数的形参必须是引用传递,否则会引起无穷递归问题,
A(A a1)
{
_a = a1._a;
}//值传递
//需要给 形参赋值,调用拷贝函数
A(A a1)//上一个的形参
{
_a1 = a1._a;
}//值传递给 形参的形参 赋值,调用拷贝函数
A(A a1)
{
_a1 = a1._a;
}//值传递给 形参的形参 的形参赋值,调用拷贝函数
A(A a1)
{
_a1 = a1._a;
}//值传递给 形参的形参 的形参 的形参赋值,调用拷贝函数
A(A a1)
{
_a1 = a1._a;
}//值传递给 形参的形参 的形参 的形参 的形参赋值,调用拷贝函数
5.赋值运算符重载
根据逻辑,有时候需要比较两个对象的大小或其他属性,但是C++不支持自定义类型的对象的比较大小等操作需要重载运算符,
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
但是重载需要注意以下几点
1.重载无法创造新的运算符如@等
2.重载操作数至少有一个类类型或者联合类型
3.无法重载内置类型(int,char等)的操作符
4.共有5个操作符无法重载:.* 、:: 、sizeof 、?: 、. (注意第一个是点*而不是*)
例子如下为对日期的重载:为初始版本十分臃肿,精简的方法为函数的复用:具体看
这里:https://blog.csdn.net/cat_want_fly/article/details/86506354
#include <iostream>
using namespace std;
class Date
{
public:
int GetMonthDay(int year, int month)
{
int monthArray[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;
else
return monthArray[month];
}
Date(int year = 2019, int month = 1, int day = 1)
{
if (year > 0
&&month > 0
&& month < 13
&&day > 0
&&day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
// 四个成员函数
else
{
cout << "日期非法" << endl;
_year = 0;
_month = 0;
_day = 0 ;
}
}
print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
Date(Date &date)
{
_year = date._year;
_month= date._month;
_day = date._day;
}
Date& operator+=(int day)
{ _day += day;
while(_day>GetMonthDay( _year, _month))
{
if(_month==12)
{
_month = 0;
_year++;
}
if(_month!=0)
{
_day -= GetMonthDay(_year, _month);
_month++;
}
if(_month==0)
{
_day -= GetMonthDay(_year, 12);
_month++;
}
}
return *this;
}
;
Date &operator-=(int day)
{
_day -= day;
while(_day<1&&_year>0)
{
_month--;
if(_month==0)
{
_month = 12;
_year--;
}
_day+=GetMonthDay( _year, _month);
}
return *this;
};
// 前置
Date operator++()
{
(*this)+=1;
}
;
Date operator--()
{
(*this)-=1;
};
// 后置返回 改变数值之前的对象一个拷贝,
Date operator++(int)
{
Date copy((*this));
(*this)+=1;
return copy;
}
;
Date operator--(int)
{
Date copy((*this));
(*this)-=1;
return copy;
}
;
// d1-d2
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
//cout<<"调用析构函数"<<endl;
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
重载完毕后要注意符号优先度如
cout<<d1==d2<<endl;//错误,d1先与<<运算,d2与<<运算,最后进行判断相等操作
cout<< (d1==d2) <<endl;//正确
小知识点:
为什么前置++比后置++重载时少了一个int型的形参?
答:因为前置和后置的运算符一样,所以即使对其进行运算符重载,编译器也无法区分两者,所以这是对同名函数的重载,同名函数的重载需要不同的形参列表,所以int型的形参就是为了区分两个操作符函数,int 本身的值无意义。
6.取地址及const取地址操作符重载
这两个默认函数一般不需要重载,编译器会默认生成
如果重载,一般时为了封装,防止访问到对象的地址,
Date* operator&()
{
return nullptr;
}
如果设置一个Date类型的对象,并用&访问它的地址会返回00000000.
7const成员
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this
指针,表明在该成员函数中不能对类的任何成员进行修改。以上面的代码为例
相当于将隐含的this指针形参由Date *转化为了 const Date *
1.const对象可以调用非const成员函数吗?
答:不可以,非const成员函数形参为非const类型,传参时编译器试图将const类型的指针转化成非const类型,无法实现操作。
2. 非const对象可以调用const成员函数吗?
答:可以,允许非const变量转化为const变量,this指针的类型可以转化。
3. const成员函数内可以调用其它的非const成员函数吗?
答:不可以,调用非const成员函数,就可能对const变量进行修改,这是不允许的,
c++规定 const成员函数内不能调用其它的非const成员函数
4. 非const成员函数内可以调用其它的const成员函数吗?
答:可以,理由和第二题相同
(注意唯一的例外是析构函数,C++标准中说
A destructor can be invoked for a const, volatile or const volatile object.
析构函数可以为const、volatile或const volatile对象调用析构函数。
因为析构函数和资源的回收有关,如果无法访问const函数就会造成内存泄漏)
const也可以用来重载函数
class A{
private:
int i;
public:
void f()
{
cout<<"f()"<<endl;
}
void f() const
{
cout<<"f() const"<<endl;
}
}
const类型的对象调用const修饰的函数,非const类型的对象调用非const修饰函数,
(注意const写在形参括号的右边,如果写在了函数名的左边,编译器会认为函数返回类型为const,如果是对void修饰,函数本身依然没有返回值)