祥云平台网站建设怎么收费,年终总结ppt模板免费下载网站,网站开发基本要求,怎样拓展客户类的6个默认成员函数构造函数概念特性析构函数概念特性拷贝构造函数概念特征拷贝构造函数典型调用场景#xff1a;赋值运算符重载运算符重载赋值运算符重载取地址及const取地址操作符重载类的6个默认成员函数
到底什么是类的6个默认成员函数呢#xff1f;相信大家一定对此怀…类的6个默认成员函数构造函数概念特性析构函数概念特性拷贝构造函数概念特征拷贝构造函数典型调用场景赋值运算符重载运算符重载赋值运算符重载取地址及const取地址操作符重载类的6个默认成员函数
到底什么是类的6个默认成员函数呢相信大家一定对此怀有很多疑问那接下来就让我们一起来探讨它们的奇妙之处吧。
如果一个类中什么成员都没有简称为空类。
class Date {};空类中真的什么都没有吗并不是任何类在什么都不写时编译器会自动生成以下6个默认成员函数。默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数。 默认成员函数听着很玄妙那它们到底是怎样设计的呢又有什么样的规则呢接下来让我们一起来探讨吧
构造函数
概念
对于以下Date类
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 d1;d1.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0; }对于Date类可以通过 Init 公有方法给已创建好的对象设置日期但如果每次创建对象时都调用该方法设置信息未免有点麻烦也有可能忘记从而导致其它的问题。那能否在对象创建时就将信息设置进去呢
构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在一个对象整个生命周期内只调用一次。
特性
构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象。 对象被创建的同时编译器自己会调用构造函数去初始化对象
其特征如下
函数名与类名相同。无返回值。对象实例化时编译器自动调用对应的构造函数。构造函数可以重载。 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(2015, 1, 1); // 调用带参的构造函数// 注意如果通过无参构造函数创建对象时对象后面不用跟括号否则就成了函数声明// 以下代码的函数声明了d3函数该函数无参返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3(); //调用无参的构造函数时这样写编译器分不清}如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。 要注意默认构造函数可以分为三类1.编译器自己生成的构造函数。2.显式定义的构造函数但是没有参数。3.显示定义的构造函数有参数但参数全缺省。 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类中构造函数屏蔽后代码可以通过编译因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开代码编译失败因为一旦显式定义任何构造函数编译器将不再生成// 无参构造函数放开后报错error C2512: “Date”: 没有合适的默认构造函数可用因为Date d1只会调用无参的构造函数不管是显示定义还是编译器自己生成Date d1;return 0;}关于编译器生成的默认成员函数很多童鞋会有疑惑不实现构造函数的情况下编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用d对象调用了编译器生成的默认构造函数但是d对象_year/_month/_day依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用
解答C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型如int/char…注意只要是指针类型就都属于内置类型自定义类型就是我们使用class/struct/union等自己定义的类型看看下面的程序就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认构造函数。
class Time
{
public:Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};int main()
{//首先Date类和Time类里并没有显示定义构造函数。
//下面的Date d;是Date类型实例化出了d,同时编译器又调用了Date类的默认构造函数
//Date类的默认构造函数只会管内置类型但目前我们学的默认构造函数并不会去初始化内置类型
//后面会讲到如何让默认构造函数可以初始化内置类型
//d空间内又定义了Time _t;Time _t不就是对Time类的实例化吗而且Time_t时编译器不就是会自动调用Time类的默认构造函数吗
//一定要把上面这个关系搞清楚不然会越来越理不清Date d;return 0; }注意C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值。 下面Date类中的成员变量在声明时就给了默认值
class Time
{
public:Time(){//输出“Time()”是为了方便观察是否调用了构造函数cout Time() endl;_hour 0;_minute 0;_second 0;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)//下面是声明时给了默认值int _year 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};int main()
{Date d;return 0; }无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。 注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数。
析构函数
概念
通过前面构造函数的学习我们知道一个对象是怎么来的那一个对象又是怎么没呢的 析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。
特性
析构函数是特殊的成员函数其特征如下
析构函数名是在类名前加上字符 ~。无参数无返回值类型。一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构函数不能重载。对象生命周期结束时C编译系统系统自动调用析构函数关于编译器自动生成的析构函数是否会完成一些事情呢下面的程序我们会看到编译器生成的默认析构函数对自定义类型成员调用它的析构函数。
class Time
{
public:~Time(){//便于观察cout ~Time() endl;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};int main()
{Date d;return 0; }// 程序运行结束后输出~Time()
// 在main方法中根本没有直接创建Time类的对象为什么最后会调用Time类的析构函数
// 因为main方法中创建了Date对象d而d中包含4个成员变量其中_year, _month, _day三个是内置类型成员
//销毁时不需要资源清理最后系统直接将其内存回收即可而_t是Time类对象所以在
// d销毁时要将其内部包含的Time类的_t对象销毁所以要调用Time类的析构函数。但是main函数
// 中不能直接调用Time类的析构函数实际要释放的是Date类对象所以编译器会调用Date类的析构函数
//而Date没有显式提供则编译器会给Date类生成一个默认的析构函数目的是在其内部调用Time
// 类的析构函数即当Date对象销毁时要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数而是显式调用编译器为Date类生成的默认析构函数
// 注意创建哪个类的对象则调用该类的构造函数销毁哪个类的对象则调用该类的析构函数如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如Date类有资源申请时一定要写否则会造成资源泄漏。那什么是申请资源呢比如用顺序表实现栈时用malloc函数动态开辟的数组必须要用free去释放不然会有问题。
拷贝构造函数
概念
拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰,为什么加const修饰待会讲)在用已存在的类类型对象创建新对象时由编译器自动调用。如创建了Date类的对象d1,并进行了初始化再创建Date类的d2时可以写成 Date d2(d1); 这样编译器会自动去调用拷贝构造函数效果相当于调用构造函数加拷贝操作。
特征
拷贝构造函数也是特殊的成员函数其特征如下
拷贝构造函数是构造函数的一个重载形式。拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。
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;}关于上面拷贝构造函数的形参为什么有const修饰因为引用是存在权限问题的调用函数时实参传给形参权限可以不变也可以缩小但就是不能被放大。而我直接把形参用const修饰权限是最小的这里就不会出现问题。 若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。 注意涉及到类类型的值传递时编译器会调用编译器自动生成的默认拷贝构造函数对象的内置类型是按照字节方式直接拷贝的而自定义类型再调用自己的拷贝构造函数完成拷贝。 类中如果没有涉及资源申请时拷贝构造函数是否写都可以像日期类这样的类是没必要的一旦涉及到资源申请时则拷贝构造函数是一定要写的否则就是浅拷贝。 拷贝构造函数典型调用场景
1.使用已存在对象创建新对象。 2.函数参数类型为类类型对象。 3.函数返回值类型为类类型对象。
注意为了提高程序效率一般对象传参时尽量使用引用类型返回时根据实际场景能用引用尽量使用引用。
赋值运算符重载
运算符重载
C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。 函数原型返回值类型 operator操作符(参数列表) 注意 不能通过连接其他符号来创建新的操作符比如operator 重载操作符必须有一个类类型参数。 用于内置类型的运算符其含义不能改变例如内置的整型不 能改变其含义。 作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this。 .* :: sizeof ?: . 注意以上5个运算符不能重载。 接下来以运算符为例来
// 现在我们定义一个全局的operator
class Date
{
public:Date(int year 2023, int month 1, int day 1){_year year;_month month;_day day;}
//private:int _year;int _month;int _day;
};
//这里我们定义了一个全局的operator
// 这里会发现运算符重载成全局的就需要类成员变量是公有的
//但这样我们就可以随意操作成员变量了并不好。
//我们可以保持成员变量私有属性但要做出相应改动
// 1.专门定义一个类成员函数来获取私有的类成员变量。
// 2.使用友元函数后面会具体讲到用法是在类里面对这个全局的operator
// 进行声明并且在声明前面加上friend 这样就可以正常的操作私有的成员变量了bool operator(const Date d1, const Date d2) {return d1._year d2._year d1._month d2._month d1._day d2._day; }
void Test ()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);//下面写成d1d2是因为这个优先级内置类型的要低//如果不加小括号就会先执行coutd1这就会出问题//编译器会将d1d2转化成d1.operator(d2),这不就是在函数调用吗//我们自己写成d1.operator(d2)也是可以的只是不方便而已cout(d1 d2)endl; }
//将operator写到类中属性为public
class Date
{
public:Date(int year 2023, int month 1, int day 1){_year year;_month month;_day day;}//此处operator写到类中属性为public// bool operator(Date* this, const Date d2)// 这里需要注意的是左操作数是this指向调用函数的对象bool operator(const Date d2){return _year d2._year; _month d2._month _day d2._day;}
private:int _year;int _month;int _day;
};赋值运算符重载
赋值运算符重载是运算符重载的一种 赋值运算符重载格式 1.参数类型const T传递引用可以提高传参效率 2.返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值。像使用内置类型时写成d1d2d3 3.检测是否自己给自己赋值。 4.返回*this 要复合连续赋值的含义。
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;}return *this;}
private:
int _year ;int _month ;int _day ;
}; 赋值运算符只能重载成类的成员函数不能重载成全局函数。因为赋值运算符如果不显式实现编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的成员函数 用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。注意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。 如果类中未涉及到资源管理赋值运算符是否显示化实现都可以一旦涉及到资源管理则必须要实现。
取地址及const取地址操作符重载
这里我们先讲一下什么是const成员
将const修饰的“成员函数”称之为const成员函数const修饰类成员函数实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。 注意如果类成员函数声明和定义分离的话都要加上const 现在说一下取地址及const取地址操作符重载
class Date
{
public :Date* operator(){return this ;}const Date* operator()const{return this ;}private :int _year ; int _month ; int _day ;
}; 注意这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可只有特殊情况才需要重载比如想让别人获取到指定的内容 如果有老铁认认真真看完了这篇文章我相信一定能有不小的收获让我们一起加油学习吧