类与对象(中)
一、类的6个默认成员函数
如果一个类中什么都没有,那么称这个类为空类。类在什么都不写的时候,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
class Eg
{};
二、构造函数
2.1概念
构造函数是特殊的成员函数,名字与类名相同,创建类类型对象时由编译器调用,保证每个数据成员都有一个合适的初始值,并在对象的生命周期里只初始化一次。
class Date
{
public:
void Init(int _year,int _month,int _day)
{
_year=year;
_month=month;
_day=day;
}
void Print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date A1;
A1 Init(2023,10,23)
A1 Print()
Date A2;
A1 Init(2023,10,24)
A1 Print()
return 0;
}
每次给Init设置数据时未免显得太麻烦,如果可以在初始创建对象的时候就把数据设置进去,那就比较方便,这时候就用到默认构造函数了
class Date
{
public:
Date(int _year,int _month,int _day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date A1 (2023,10,23);
A1 Print();
Date A2(2023,10,24);
A2 Print();
return 0;
}
这样的话每次使用时就直接可以初始化值了
注意:构造函数的作用不是开空间创建对象,而是初始化对象。
2.2特性
- 函数名与类名相同
- 无返回值(不需要像成员函数一样写void,本质就是不需要返回)
- 对象实例化时编译器自动调用对应的构造函数
- 构造函数支持重载
- 如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,若用户显示定义了,则编译器就不再生成
class Date
{
public:
Date()//无参构造函数,对应特性1和2
{}
Date(int year,int month,int day)//带参构造函数
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//调用无参构造函数,对应特性3和4
Date d2(2023,10,24);//调用带参构造函数
return 0;
}
class Date
{
public:
/*Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}*/
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//自动调用编译器调用的默认构造函数
//将Date注释去掉,编译器不自动生成构造函数,但这条语句没有合适的构造函数可用
return 0;
}
6.默认构造函数有三种:
(1)类中没有显式定义构造函数,编译器自动生成的无参构造函数
(2)显式定义的无参构造函数
(3)全缺省参数的构造函数
总结:不传参就调用的构造函数都可以叫做默认构造函数,不能同时存在,否则调用时有歧义
7.C++把类型分成内置类型(基本类型)和自定义类型:
内置类型就是语言提供的数据类型,例如:int、char、指针…
自定义类型就是自己定义的类型,如;class、struct…
默认构造函数处理自定义类型,内置类型不确定编译器是否会处理(当作不处理看待)
8.不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d1对象调用了编译器生成的默认构造函数,但是d1对象_year、_month、_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用?
解答:不是的,C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类
型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,编译器生成默认的构造函数会对自定类型成员调用的它的默认成员函数进行初始化赋值,所以我们会感觉大多情况下,编译器的默认构造没用;所以,C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
9.总结:一般情况下自己写构造函数,当成员变量都是自定义类型或者内置类型声明给了缺省值可以考虑编译器自己生成默认构造函数。
三、析构函数
3.1概念
概念:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
class Eg
{
public:
Eg()
{
_a = a;
}
~Eg()//析构函数
{}
private:
int _a = 1;
};
int main()
{
Eg d1;
}
3.2特性
1.析构函数名是类名前加字符~
2. 无参数无返回值类型
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
注意:析构函数不能重载
4.对象生命周期结束时,C++编译器自动调用析构函数
5.默认析构函数行为与默认构造函数类似,处理自定义类型会自动调用默认析构函数,内置类型不确定编译器是否会处理(当作不处理看待)
6.先构造的后析构,后构造的先析构
函数的调用会用到栈帧,要满足栈帧”先进后出“的原则,先构造的进栈的在栈底,后构造的进栈在栈顶,析构时先析构栈顶的元素,再析构栈底的元素
四、拷贝构造函数
4.1概念
概念:只有单个的形参,该形参是对本类类型对象的引用(一般用const的修饰),在用已经存在的类类型对象创建新对象时由编译器自动调用。
4.2特性
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;
}
3.若未显式定义编译器会生成默认拷贝构造函数。(默认拷贝构造函数会按照函数对象按照内存存储按字节序来完成拷贝,这种拷贝叫做浅拷贝也叫值拷贝)
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
//拷贝构造函数
Time(const Time& t)//const可以保证左值
{
_hour = t._hour;
_minute = t._minute;
_second = t.second;
cout<<"
}
private:
int _hour;
int _minute;
int _second;
};
ciass Date
{
public:
private:
//内置类型
int _year;
int _month;
int _day;
//自定义类型
Time _a;
};
int main()
{
Date d1;//创建一个对象
Date d2(d1);//用一个已经存在的d1拷贝构造d2,此处会调用Date的拷贝构造函数(没有显式定义,编译器默认提供的拷贝构造函数)
return 0;
}
注意:在编译器生成的默认拷贝构造函数中
(1)内置类型是按照字节的方式进行值拷贝
(2)自定义类型是调用其拷贝构造函数完成拷贝
4.类中没有涉及到资源申请时,是否写拷贝构造函数都可以,如果涉及到资源申请时,拷贝构造函数是一定要写的,不然为浅拷贝
5.拷贝构造函数使用场景;
(1)使用已经存在的对象创建新对象
(2)函数参数为类类型对象
(3)函数返回值为类类型对象
为了使程序效率提高,一般对象传参时,尽量使用引用传参,返回根据实际场景,能用引用尽量使用引用。
五、赋值运算符重载
5.1运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字:关键字operator加上需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
//比较自定义类型的大小
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;
}
//错误写法
//参数个数要与运算符需要的参数个数相匹配,所以只能有俩个形参
//运算符重载函数的第一个形参默认是this指针,第二个形参是运算符的右操作数
/*bool operator>(const Date d1,const Date d2)
{
if(d1._year > d2._year)
return true;
else if(d1._year == d2._year && d1._month > d2._month)
return true;
else if(d1._year == d2._year && d1._month == d2.month && d1._day > d2._day)
return true;
else
return false;
}*/
//bool operator>(const Date* this,const Date& d2)
//这里系统默认左操作数为this指针
bool operator>(const Date d2)
{
if(_year > d2._year)
return true;
else if(_year == d2._year && _month > d2._month)
return true;
else if(_year == d2._year && _month == d2.month && _day > d2._day)
return true;
else
return false;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023,10,29);
Date d2(2023,10,30);
cout << (d1 > d2) << endl;//d1.operator(d2)->d1.operator(&d1,d2)
return 0;
}
注意:
(1)不能通过连接其他符号来创建新的操作符:比如operator@
(2)重载操作符必须有一个类类型参数
(3)用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
(4)作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针
(5).* (点星运算符):: (域作用限定符) sizeof ?:(条件运算符) .(点运算符) 注意以上5个运算符不能重载。
5.2赋值运算符重载
1.赋值运算符重载格式:
(1)参数类型:(const Date& d)const Date&,传递引用可以提高传参效率,有返回值是为了支持连续赋值
(2)返回值类型:Date&(出了作用域this指针指向对象依然存在),返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值,可以d3=d2=d1
(3)检测是否给自己赋值
(4)返回*this:要符合连续赋值的含义(this指针返回的是左操作数,赋值完毕后就返回this指向的左操作数就好)
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
Date (const Date& t)
{
_year = t.year;
_month = t.month;
_day = t.day;
}
Date& operator=(const Date& d)
{
if(this != &d)//判断是否是自己给自己赋值(无效赋值)d1 = d1
{
_year = d.year;
_month = d.month;
_day = d.day;
return *this;
}
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//构造函数
Date d1(2023,11,2);
//拷贝构造函数:用一个已经存在的对象去拷贝构造另一个对象
Date d2(d1);
//赋值运算符重载:俩个已经存在的对象进行拷贝
d3 = d1;
return 0;
}
2.特性
- 当赋值运算符函数没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。(没有operator=编译器会自动生成operator=)
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
六、const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
1.const成员函数不可以调用非const成员函数
2非const对象可以调用const成员函数
3.const的成员函数不可以调用其他的非const成员函数
4.非const的成员函数可以调用其他的const成员函数
七、取地址及const取地址操作符重载
这俩个默认成员函数一般不需要定义,编译器会默认生成。
class Date
{
public:
Date* operator&()// 取地址操作符重载
{
return this;
}
const Date* operator&()const// const取地址操作符重载
{
return this;
}
private:
int _year;
int _month;
int _day;
};