• c++中类的默认成员函数


    c++中类共有六个默认成员函数,每个类都有默认的成员函数,空类也是如此。主要完成三个任务:1,初始化和清理 2,拷贝复制 3,取地址重载。为什么会有默认构造函数的存在呢?必有其存在的道理,在c语言阶段,我们定义一个栈等数据结构,会自己写初始化,清理的函数,如果忘记了清理,会有内存泄漏的麻烦。所以何必一直要自己来调用呢,在类中我们直接让它自己调用即可,方便了不少。本文,主要讲默认成员函数的使用规则和一些注意点。用日期类举例子,所以将其给出来,不是劝退哈,讲完后会发现其简单的很。

    class Date
    {
    	friend ostream& operator <<(ostream& out, const Date& d);
    public:
        Date& operator=(const Date &d);
    	Date(int year = 1, int month = 1, int day = 1);//构造函数
    	void Print()const;
    	int GetMonthDay(int year, int month)const;
    	Date(const Date &d);
    	bool operator>(const Date& d)const;
    	bool operator<(const Date& d)const;
    	bool operator>=(const Date& d)const;
    	bool operator<=(const Date& d)const;
    	bool operator==(const Date& d)const;
    	bool operator!=(const Date& d)const;
    	Date& operator+=(int day);
    	Date operator+(int day)const;
    	Date& operator-=(int day);
    	Date operator-(int day)const;
    	Date& operator++();
    	Date operator++(int);//后置++
    	Date& operator--();
    	Date operator--(int);
    	int operator-(const Date& d)const;
    	void PrintWeekDay() const;
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    一,构造函数

    构造函数,函数名就是类名
    所谓默认构造函数,就是我们不传参数,自动会初始化成员变量;我们不去主动的声明构造函数,就会直接调用系统默认生成的;如果我们自己声明以及定义了一个构造函数那么就有两种情况:
    1.

    Date();
    Date(int year = 1, int month = 1, int day = 1);
    
    • 1
    • 2

    这就是默认构造函数的两种形式:无参的,全缺省的。这是满足默认构造函数的,无需我们传参,就会直接调用其来初始化成员变量。
    2.
    当然,也有需要传参的构造函数,这是依据个人需求来的。但是不会你定义一个类就会自动的初始化,这需要传参来初始化了。

    Date(int year,int month,int day);//不缺省
    Date(int year, int month = 1, int day = 1);//半缺省
    
    • 1
    • 2

    但是,系统默认生成的构造函数和我们自己动手写的默认构造函数有区别嘛?答案是:当然有。
    系统默认生成的构造函数不会对内置类型做处理,只会操作自定义类型。内置类型就是int,double,char等系统给定的类型,自定义类型就比如自己定义的类,系统会调用这个类自身的默认构造函数。

    class Date
    {
    private:
    int _year;
    int _month;
    int _day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    日期类系统自带的构造函数不会做任何的处理。我们如果想初始化内置类型的成员变量就必须得自己写构造函数。

    class A
    {
    private:
    Date _a;
    Date _b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    像这种类,我们是不需要写构造函数的,默认的就够用。因为默认的构造函数会直接调用Date类的默认构造函数来初始化,_a,_b。这俩个自定义类型成员变量。至于Date类有没有默认构造函数,这我们不需要关心,一码事归一码事。
    我们来写一个日期类的构造函数:

    Date::Date(int year=1,int month=1;int day=1)
    {
     _year = year;
    	_month = month;
    	_day = day;
    	if (!(_year >= 0 && (month > 0 && month<13) && (day <= GetMonthDay(year, month) && day>0)))
    	{
    		cout << "日期非法-->";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    二,析构函数

    • 析构函数名为~类名
    • 析构函数的作用和构造函数作用相反,但是其运作原理类似。但是析构函数不支持函数重载,因为析构函数没有函数参数。
    • 析构函数就是用来清理类中资源的,一般情况下,需要清理的无非是释放在堆上开辟空间,如果不作处理会导致内存泄漏。

    那么何时会调用析构函数呢?在类的成员变量的生命周期结束时,会自动调用析构函数。同样有俩种情况:我们不自己写析构函数那么就会用系统默认的析构函数;如果写了就会自己写的析构函数。注意下面俩段代码,我们可以判断一下,哪个类用写析构函数,哪个不用写?

    class A
    {
     private:
     int _a;
    } 
    class B
    {
    private:
     int *b;
    }
    class C
    {
    private:
      A c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    类A没有资源需要处理,所以不用写析构函数;类B需要自己动手写析构函数,如果不写,可能造成内存泄漏,野指针等问题;类C不用自己动手写,用默认的析构函数就行了。
    系统默认的析构函数和构造函数一样,都不会对内置类型做处理,只操作自定义成员变量,会调用其本身的析构函数。
    因为,日期类和类A一样,都没有资源需要处理,所以我写个类B的析构函数。

    ~B()
    {
     free(this->b);
    }
    
    • 1
    • 2
    • 3
    • 4

    这里大家可能会对this指针有问题,可以看此篇博文《this指针详解》

    三,拷贝构造

    • 拷贝构造函数名为类名,但是参数得是类的引用。
    • 拷贝有浅拷贝和深拷贝,系统默认生成的拷贝构造函数就是浅拷贝:按字节序来拷贝值。但有些情况是需要深拷贝的,这就需要我们来自己动手写拷贝构造,就比如类中有指针,我们如果用前拷贝会导致俩个对象的指针指向同一处地址,这是非常危险的行为,首先在析构的时候就会出现问题,一块空间被释放了两次,这会导致空指针的问题;其次,俩个对象同用一块地址,可能在不经意间动了不该动的对象。
    • 系统默认的拷贝构造对于自定义类型,会去调用自定义类型的拷贝构造。

    日期类不需要拷贝构造,系统自带的就足够了。但是也可以小写一波。

    Date::Date(const Date& d)
    {
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    四,运算符重载

    • 为了增强代码可读性,有了运算符重载。比如我有两个对象分别是a,b。我写一个a>b,可以嘛?编译器能看懂嘛?到底该依据哪个成员变量来判断对象的大小呢?所以就有了运算符重载,其本质是函数,只不过名字特殊了一些。
    • 运算符重载的函数名为operator+重载运算符,原型为:返回值 operator 运算符(参数列表)。
      注意点:
    • 运算符重载只对自定义类型,内置类型的运算符含义不变;
    • 运算符重载必须有 的类型参数。
    • .*, ::, sizeof, . ,?: 这五位老哥不能重载。

    写一个赋值重载,就是=的重载。

    Date& Date::operator=(const Date &d)
    {
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    赋值重载,系统其实也有默认的赋值重载,默认的赋值重载干的事情和拷贝构造完全类似。

    1. 完成浅拷贝,会对内置类型完成浅拷贝,所以日期类不用我们自己完成
    2. 对于自定义类型会去调用它的operato = 。
    3. 深拷贝这种事,默认的是做不到的,需要自己动手完成。

    取地址操作符的重载,const修饰的取地址操作符重载

    这个重载自己实现的较少,默认的重载就够了,但是如果想让别人取到特定的地址还是有必要自己实现的。

    class Date
    { 
    public :
     Date* operator&()
     {
     return this ;
     }
     
     const Date* operator&()const
     {
     return this ;
     }
    private :
     int _year ; // 年
     int _month ; // 月
     int _day ; // 日
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这就是日期类取地址运算符的重载。


    总结:类的默认成员函数有构造,析构,拷贝,赋值操作符重载,取地址操作符重载,const修饰的取地址操作符重载。灵活运用,默认的不够用,就自己写,根据需求来完成类的成员函数。

  • 相关阅读:
    Selenium4+Python3系列(八) - Cookie、截图、单选框及复选框处理、富文本框、日历控件操作
    【小嘟陪你刷题01】LeetCode 448 && 238 && 728 && 724 && 349 && 747 && 面试题05.06 && 645
    HarmonyOS ArkUI滚动类组件-Scroll、Scroller
    特征生成(特征创建)
    cyclictest生成结果统计图
    《迷失岛2》游戏开发框架开发日记:场景切换和淡入淡出效果
    Spring简介、IOC容器
    基于JavaSwing开发简单的学生信息管理系统(增删改查SQLServer数据库)+报告
    java计算机毕业设计WEB儿童运动馆业务信息系统MyBatis+系统+LW文档+源码+调试部署
    Azure 机器学习:使用 Azure 机器学习 CLI、SDK 和 REST API 训练模型
  • 原文地址:https://blog.csdn.net/lyzzs222/article/details/123283909