C++类和对象(默认成员函数、构造函数、析构函数、拷贝构造函数、运算符重载)

目录

一.默认成员函数

1.1 什么是默认成员函数

二.构造函数

 2.1 构造函数的作用

2.2 构造函数的特点

三.析构函数

3.1析构函数的概念

3.2 析构函数的特性

3.3 析构函数的使用场景

四.拷贝构造

4.1 拷贝构造函数的定义

4.2 拷贝构造函数的特性

4.3 浅拷贝和深拷贝

五.运算符的重载

5.1 运算符重载的意义

5.2 运算符重载及形式

5.3 运算符重载的注意事项

5.4 运算符重载的案例


一.默认成员函数

1.1 什么是默认成员函数

    如果一个一个类中什么都没有,这样的类被称为空类。空类在代码中看似什么都没有,但其实编译器会在其中生成6个默认的成员函数。默认函数就是我们什么都不写,编译器也会自动生成的函数。默认函数的功能如下图所示,它们能帮助我们完成清理和初始化、拷贝和复制、取地址重载,三个最主要的功能。

二.构造函数

 2.1 构造函数的作用

构造函数是一个特殊的成员函数,函数名与类名相同,创建类类型的对象时自动调用该函数,对对象中的成员进行初始化

2.2 构造函数的特点

(1)函数名与类名相同

(2)没有返回值(不需要写void)

(3)对象是实例化时编译器自动调用对应的构造函数

(4)构造函数可以重载

(5)实例化时会自动调用

(6)默认生成的构造函数不会处理内置类型的参数,但是对于自定义类型会调用其默认构造函数。如下代码所示,我们在构造一个类以后我们如果没有写构造函数的话,系统就会默认生成一个,并且分析其中的形参类型,因为这里的形参都是int类(内置类型)所以编译器并不会处理这里的参数。

#include<iostream>
using namespace std;
class Date
{
public:

   /* Date(int _year , int _month ,int _day) //默认生成的默认构造函数
    {

    }*/

    void print()
    {
       cout << this->_year << "/" << this->_month << "/" << this->_day;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;   //调用自动生成的默认构造函数进行初始化
    d1.print();
    return 0;
}

        程序运行的结果是一堆随机值,因为Date构造函数中的参数是内置类型,而且成员变量并没有设置缺省参数,所以编译器并没有对内置类型的参数做初始化的操作。

所以在这里我们想完成初始化的操作有两种方式,其一是自己编写一个构造函数,其二是给成员变量设置缺省参数具体,操作如下所示:

其一:自己编写一个构造函数,并且在形参中设置缺省参数。

#include<iostream>
using namespace std;
class Date
{
public:
    Date (int year=2024, int month = 4, int day = 16)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void print()
    {
       cout << this->_year << "/" << this->_month << "/" << this->_day;
    }
private:
    int _year ;
    int _month;
    int _day;
};

int main()
{
    Date d1(2024,4,15);   //实例化对象d1调用内部的成员函数进行初始化
    d1.print();
    return 0;
}

其二:在成员变量中设置缺省值,再利用编译器自动生成的构造函数进行初始化。

           这种写法和上一种是一样的。

#include<iostream>
using namespace std;
class Date
{
public:
   /* Date()
    {

    }*/

    void print()
    {
       cout << this->_year << "/" << this->_month << "/" << this->_day;
    }
private:
    int _year =2024;
    int _month = 4;
    int _day 16;        //这里只是声明,可以理解为缺省参数,并不是不是构造变量,并不开辟空间
};

int main()
{
    Date d1;   //利用自动生成的构造函数进行初始化
    d1.print();
    return 0;
}

 (3)对于自定义类型会调用其默认构造函数的样例:在这里我先写了一个栈的类,然后写了一个myQueue的类,其中包含两个stack栈的小类,在myQueue中我不需要写构造函数来初始化我的类,因为myQueue会调用stack类来初始化类中的成员。

#include<iostream>
using namespace std;
class Stack
{
    public:
        Stack(size_t n = 4 )
        {
            a = (int*)malloc(sizeof(int) * n);
            if (a == nullptr)
            {
                perror("malloc失败!");
                exit(-1);
            }
            capacity = n;
            top = 0;
            cout << "success!" << endl;
        }
        void insert()
        {
            for (int i = 0; i < this->capacity; i++)
                this->a[i] = i + 1;
        }
        void print()
        {
            for (int i = 0; i < this->capacity; i++)
                cout << this->a[i] << " ";
        }
    private:
        int* a;
        int top;
        int capacity;
};
class myQueue
{
    Stack _pushst;
    Stack _popst;            //调用stack的构造函数
};
int main()
{
    Stack s1(10);
    s1.insert();
    s1.print();

    return 0;
}

(7)无参的构造函数和全缺省的构造函数都成为默认构造函数,一旦用户显示定义编译器将不再生成 

三.析构函数

3.1析构函数的概念

        与构造函数的功能相反,析构函数不是完成对象本身的销毁,局部对象的销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中的资源清理工作

3.2 析构函数的特性

        析构函数是特殊的成员函数,他有如下几个特征:

     (1)析构函数名是在类名前加上字符 ~

     (2)无参数无返回值类型

     (3)一个类只能有一个析构函数,若没有显示定义。系统会自动生成。析构函数不能重载

     (4)对象生命周期结束时,C++编译器系统自动调用析构函数

     (5)后定义的对象会先被析构

3.3 析构函数的使用场景

        在绝大部分的场景中我们都需要自己写析构函数,但是如果类中只有简单的自定义类型的话,我们可以不写,因为这样的变量都是存在栈中的,但是如果有在堆上开辟的空间的话(比如使用了malloc函数),我们就需要自己写析构函数了。以下是栈和堆的区别:

栈:自动开辟空间,函数调用结束后自动释放内存空间

堆:动态申请的内存空间,需要收开辟和手动释放,相比于栈来说是很自由的。

四.拷贝构造

        对于普通的内置类型的变量来说,我们可以简单的将两个变量的值相互拷贝,但是拷贝对象不能像拷贝内置类型变量那样去拷贝对象,这个时候我们就需要用到拷贝构造了。

4.1 拷贝构造函数的定义

        只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型
对象创建新对象时由编译器自动调用。

4.2 拷贝构造函数的特性

(1)拷贝构造是构造函数的一个重载形式。

(2)拷贝构造函数只有一个且必须是类类型对象的引用,使用传值方式编译器会报错,因为会引发无限递归调用。

以下是发生无线递归调用的情况,下面的拷贝构造函数的形参是一个类类型,主函数需要把d1拷贝到d中,从而有涉及到把一个对象的值拷贝到另一个对象里的问题了,从此往复就是无限递归调用的情况了。

class Date
{
public:
    Date(int year = 2024, int month = 4,int day = 16)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    Date(Date d)        //拷贝构造函数
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }                           //错误!!!!这么写会造成无线递归调用

    void print()
    {
       cout << this->_year << "/" << this->_month << "/" << this->_day;
    }
private:
    int _year;
    int _month;
    int _day ;
};

int main()
{
    Date d1;
    Date d2(d1);
    Date d3 = d1;
}

正确的写法应该应该如下所示,这里拷贝构造函数中的形参类型是类类型对象的引用函这里的是d就是主函数中d1的一个别名了,这样就不会出现无线递归的调用的问题了。

class Date
{
public:
    Date(int year = 2024, int month = 4,int day = 16)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    Date(Date & d)        //拷贝构造函数
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }                           //错误!!!!这么写会造成无线递归调用

    void print()
    {
       cout << this->_year << "/" << this->_month << "/" << this->_day;
    }
private:
    int _year;
    int _month;
    int _day ;
};

int main()
{
    Date d1;
    Date d2(d1);
    Date d3 = d1;
}
4.3 浅拷贝和深拷贝

(1)浅拷贝:在对象中的成员中,对于内置类型成员的拷贝,我们是可以直接将被拷贝对象中的成员变量的值,赋给另一个对象的成员变量,这种称为浅拷贝。上面一段代码就是 一段很典型的浅拷贝案例。

(2)深拷贝:在对象中的成员中,对于自定义类型成员,如果有成员在堆上申请了一段空间,例如类中使用malloc开辟了一段空间,我们不能直接把这段空间的地址赋给别人,我们需要自己也开辟一段相同大小的空间并且将被拷贝对象中malloc开辟空间中的值赋给拷贝对象新开辟的空间里,此为深拷贝。具体案例如下所示。

#include<iostream>
using namespace std;
class Stack
{
    public:
        Stack(Stack& st)
        {                                                //深拷贝
            a = (int*)malloc(sizeof(int) * st.capacity);
            if (a == nullptr)
            {
                perror("malloc失败!");
                exit(-1);
            }
            memccpy(a, st.a, sizeof(int) * st.capacity);
            capacity = st.capacity;
            top = st.capacity;
            cout << "success!" << endl;
        }

        Stack(size_t n = 4 )
        {
            a = (int*)malloc(sizeof(int) * n);
            if (a == nullptr)
            {
                perror("malloc失败!");
                exit(-1);
            }
            capacity = n;
            top = 0;
            cout << "success!" << endl;
        }

    private:
        int* a;
        int top;
        int capacity;
};

int main()
{
    Stack s1(10);
    Stack s2(s1);
    return 0;
}

(3)使用深拷贝的原因       

         如果以上的情况下我们没有使用深拷贝方法的话,就会出现将一块空间析构两次的问题。因为d1 和 d2 的a指针都指向了一块空间,d2的a指针在这里存放的是d1指向空间的地址,在d2析构后,析构d1时因为a指向的那块空间已经释放了,但是这里的析构函数会再释放一遍这里的空间,这在编译器中是不允许出现的情况。

class Stack
{
    public:
        Stack(Stack& st)
        {                                          
            a = st.a;    //d1 和 d2 的a指针都指向了一块空间,没有使用深拷贝
                         //这么写是错误的
            memccpy(a, st.a, sizeof(int) * st.capacity);
            capacity = st.capacity;
            top = st.capacity;
            cout << "success!" << endl;
        }

        Stack(size_t n = 4 )
        {
            a = (int*)malloc(sizeof(int) * n);
            if (a == nullptr)
            {
                perror("malloc失败!");
                exit(-1);
            }
            capacity = n;
            top = 0;
            cout << "success!" << endl;
        }

    private:
        int* a;
        int top;
        int capacity;
};

int main()
{
    Stack d1;
    Stack d2 = d1;
}

总结:拷贝构造对于内置类型的拷贝我们使用浅拷贝,对于自定义类型使用深拷贝。

五.运算符的重载

5.1 运算符重载的意义

        我们在进行内置类型变量的运算时是十分简单的,但是对于自定义类型对象的运算时,我们不能使用运算符默认的规则在进行运算,例如我有日期类型的对象d1和d2,我想要判断d1和d2的日期是否相等我不能直接写 d1==d2

5.2 运算符重载及形式

        C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数说人话就是重新定义运算符的运算规则,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。


函数名字为:关键字    operator   后面接需要重载的运算符符号。
函数原型:返回值类型  operator操作符(参数列表)

5.3 运算符重载的注意事项


(1)不能通过连接其他符号来创建新的操作符:比如operator@
(2) 重载操作符必须有一个类类型参数
(3)用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
(4)作为类成员函数重载时,形参看起来比操作数数目少1,因为成员函数的第一个参数是this。(5).*     ::    sizeof    ?:      .  这五个运算符不能重载

(6)不能改变操作数的个数

5.4 运算符重载的案例

(1)比较两个日期对象是否相等,重载了等于 == 的运算规则

#include<iostream>
using namespace std;
class Date
{
public:
    Date(int year = 2024, int month = 4,int day = 16)
    {
        _year = year;
        _month = month;
        _day = day;
    }

                   
    bool operator ==(const Date & d) 
    {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }

private:
    int _year;
    int _month;
    int _day ;
};


int main()
{
    Date d1;
    Date d2 = d1;
    cout << (d1 == d2) << endl;

    return 0;
}

(2)比较两个日期对象是否形成小于的关系,重载了小于 < 的运算规则

        这里写法上  d1<d2  和  d1.operator<(d2) 是一样的

#include<iostream>
using namespace std;
class Date
{
public:
    Date(int year = 2024, int month = 4,int day = 16)
    {
        _year = year;
        _month = month;
        _day = day;
    }

                   
    bool operator ==(const Date & d) 
    {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }
    
    bool operator <(const Date & d)
    {
        if (_year < d._year)
            return ture;
        else if (_year == d._year_ && _month < d._month)
            return ture;
        else if (_year == d._year_ && _month == d._month && _day <d._day)
            return ture;     
        else 
            return false; 
    }

private:
    int _year;
    int _month;
    int _day ;
};


int main()
{
    Date d1;
    Date d2 = d1;
    cout << (d1 == d2) << endl;

    return 0;
}

(3)比较两个日期对象是否形成小于等于的关系,重载了小于等于 <= 的运算规则

#include<iostream>
using namespace std;
class Date
{
public:
    Date(int year = 2024, int month = 4,int day = 16)
    {
        _year = year;
        _month = month;
        _day = day;
    }

                   
    bool operator ==(const Date & d) 
    {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }
    
    bool operator <(const Date & d)
    {
        if (_year < d._year)
            return ture;
        else if (_year == d._year_ && _month < d._month)
            return ture;
        else if (_year == d._year_ && _month == d._month && _day <d._day)
            return ture;     
        else 
            return false; 
    }

    bool operator ==(const Date & d)
    {
        return *this < d || &this == d;
    }


private:
    int _year;
    int _month;
    int _day ;
};


int main()
{
    Date d1;
    Date d2 = d1;
    cout << (d1 == d2) << endl;

    return 0;
}

(4)将一个日期加上N天,求出加上N天的日期        

        这里先定义了一个Getmonthday的函数其功能是返回某一年的某一月有多少天,再让原日期的_day加上N天,如果加出来的天数大于Getmonthday函数的值的话,月份就加1,如果月份大于13的话年份就加一。

#include<iostream>
using namespace std;
class Date
{
public:
    Date(int year = 2024, int month = 4,int day = 16)
    {
        _year = year;
        _month = month;
        _day = day;
    }

                
    bool operator ==(const Date & d)
    {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }

    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 % 10 != 0) || (year % 400 == 0)))
            return 29;
        return monthArray[month];
    }

    Date& operator +=(int day)  //这有两个参数this和day
    {
        _day += day;
        while (_day>Getmonthday(_year,_month))
        {
            _day -= Getmonthday(_year, _month);
            ++_month;
            if (_month == 13)
            {
                ++_year;
                _month = 1;
            }
        }
        return *this;
    }

    void print()
    {
       cout << this->_year << "/" << this->_month << "/" << this->_day;
    }
private:
    int _year;
    int _month;
    int _day ;
};


int main()
{
    Date d1;
    d1 += 100;
    d1.print();
    return 0;
}

  • 35
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值