详解C++类中的六个默认成员函数

转自:https://blog.csdn.net/aixiaodeshushu/article/details/84657955 

文章目录
构造函数
特点
析构函数
特点
拷贝构造函数
特点
赋值运算符重载
特点
const
&及const &重载
在C++98中,如果一个类中什么都没有,简称为空类,系统会自动生成六个默认的成员函数,构造函数、析构函数、拷贝构造函数、赋值运算符重载、&操作符重载、const,在C++11中又加入了控制默认函数(=default , =delete)

首先,大致了解一下六个默认构造函数的作用:


构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次

特点
函数名与类型相同
没有返回值(void都不行)
对象实例化时编译器自动调用对应的构造函数
构造函数可以重载
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
创建类类型对象时,由编译器自动调用,并且在对象的生命周期内只调用一次
有初始化列表(可带可不带)
如果类中没有显式定义构造函数,编译器会自动生成一个无参的构造函数
不能被const修饰
没有this指针,因为构造函数是创建对象的,都没有对象哪来的this指针
对上述特点详解:

1. 没有返回值

构造函数和析构函数都是非常特殊的成员函数,都没有返回值,这和void是不一样的,void的返回值是空,而不是没有。在程序中创建和销毁一个对象非常特殊,就像人的出生和死亡,而且总是由编译器来来调用这些函数以确保他们的正常执行。如果存在返回值,要么编译器直到如何处理这个返回值,要么就必须由程序员来显式的调用构造函数和析构函数,这样一来,函数的安全性就被破坏了。

2.构造函数的重载

    class Date
    {
    public:
        Date()
        {}
    
        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 d2(2018, 11, 30); //调用带参的构造函数
        return 0;
    }

注:上述调用无参的构造函数时,对象后面一定不能带括号,否则就成了函数声明

    //声明了d3函数,该函数无参,返回一个日期类型的对象
    Date d3();

3.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

class Date
{
public:
    Date()
    {}

    Date(int year = 2018, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }

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

int main()
{
    Date d1;
    Date d2(2018, 11, 30);
    
    system("pause");
    return 0;
}

一编译就会报错


4.初始化列表

class Date
{
public:
    Date()
    {}

    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 d2(2018, 11, 30);
    
    system("pause");
    return 0;
}

初始化列表这块这里不做详细解释,以后的博客中会做详细解释

5.不能被const修饰

因为const修饰类的成员函数时,表示该函数不能修改成员变量,而构造函数就是要修改成员变量

析构函数
顾名思义,构造函数是告诉我们一个对象是怎么来的,而析构函数就是告诉我们一个对象是怎么没的

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

特点
析构函数名是在类名前加上~
没有参数,没有返回值、
一个类有且只有一个析构函数
如果显式没有定义构造函数,则编译器自动生成一个构造函数
对象生命周期解释后,编译器自动调用析构函数
析构函数不是销毁对象,而是完成对象资源的清理工作
如果类中涉及资源的管理,析构函数一定要显式给出
后构造的对象先释放
有this指针
对上述特点详解:
1.一个类只有一个析构函数

因为析构函数没有参数,所以不存在函数重载的问题

2.析构函数不是销毁对象,而是完成对象的清理工作

这里的清理工作指的是如果有动态申请来的空间,析构函数就负责把这部分空间的资源清理,但这个对象直到函数生命周期结束,才会被销毁

3.如果类中涉及资源的管理,析构函数一定要显式给出

因为编译器自己生成的析构函数不会帮你去释放堆上的空间

class SeqList
{
public:
    SeqList()
    {
        _array = NULL;
        _capacity = 0;
        _size = 0;
    }

    SeqList(int n, int data)
    {
        _array = (int *)malloc(sizeof(int) * n);
        for (int i = 0; i < n; ++i)
        {
            _array[i] = data;
        }
        _capacity = n;
        _size = n;
    }

    ~SeqList()
    {
        if (_array)
        {
            free(_array);
            _capacity = 0;
            _size = 0;
        }
    }

private:
    int* _array;
    size_t _capacity;
    size_t _size;
};

void test()
{
    SeqList s1;
    SeqList s2(10, 5);
}

int main()
{
    test();
    system("pause");
    return 0;
}

4.后构造的对象先释放

在上述代码中加几句话即可

class SeqList
{
public:
    SeqList()
    {
        cout << this << endl;
        _array = NULL;
        _capacity = 0;
        _size = 0;
    }

    SeqList(int n, int data)
    {
        _array = (int *)malloc(sizeof(int) * n);
        for (int i = 0; i < n; ++i)
        {
            _array[i] = data;
        }
        _capacity = n;
        _size = n;
        cout << this << endl;
    }

    ~SeqList()
    {
        cout << this << endl;
        if (_array)
        {
            free(_array);
            _capacity = 0;
            _size = 0;
        }
    }

private:
    int* _array;
    size_t _capacity;
    size_t _size;
};

void test()
{
    SeqList s1;
    SeqList s2(10, 5);
}

int main()
{
    test();
    system("pause");
    return 0;
}


拷贝构造函数
顾名思义,就是创建一个和原对象一模一样的对象,比如双胞胎。

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

特点
只有一个形参
拷贝构造函数是构造函数的一种重载形式
若显示未定义,系统会默认生成拷贝构造函数(浅拷贝)
涉及资源管理的问题,一定要显示定义拷贝构造函数
拷贝构造函数的参数必须使用引用,如果使用传值会无穷递归调用
拷贝构造函数可以连续赋值
对上述特点详解:

1.若显示未定义,系统会默认生成拷贝构造函数(浅拷贝)

class Date
{
public:
    Date()
    {}

    Date(int year, int month, int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {
        cout << this << endl;
    }

    ~Date()
    {
        cout << this << endl;
    }

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

int main()
{
    Date d1(2108,12,12);
    Date d2(d1);
    
    system("pause");
    return 0;
}


对于我们的日期类,不存在资源管理的问题。所以不会出现问题
但如果是链表尼?存在资源的管理

class SeqList
{
public:
    SeqList(int n, int data)
    {
        _array = (int *)malloc(sizeof(int) * n);
        for (int i = 0; i < n; ++i)
        {
            _array[i] = data;
        }
        _capacity = n;
        _size = n;
    }

    ~SeqList()
    {
        if (_array)
        {
            free(_array);
            _capacity = 0;
            _size = 0;
        }
    }

private:
    int* _array;
    size_t _capacity;
    size_t _size;
};

void test()
{
    SeqList s1(10, 5);
    SeqList s2(s1);
}

为什么会这样??

因为我们编译器自己生成的拷贝构造函数时浅拷贝的方式,也叫值拷贝。
浅拷贝:按内存存储字节序完成拷贝
通俗易懂的说就是,你给我什么东西,我就拷贝什么,你给我地址,我就拷贝地址

所以,如果类中涉及资源的管理,就一定要显示定义拷贝构造函数

2.拷贝构造函数的参数必须使用引用,如果使用传值会无穷递归调用

如果是传值,首先编译器都不会通过

其次,如果传值,就必须要创建一个临时对象。


3.拷贝构造函数可以连续赋值

int a = 10;
int b = 20;
int c = 30;
a = b = c;

对于上述的连续赋值,有没有考虑过是谁赋值谁,然后载赋值谁?

先 c 赋值给 b ,然后 c 再赋值给 a

class Date
{
public:
    Date()
    {}

    Date(int year, int month, int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {
    }

    ~Date()
    {
    }

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

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

int main()
{
    Date d1(2108,12,12);
    Date d2;
    Date d3;
    d3 = d2 = d1;
    
    system("pause");
    return 0;
}

赋值运算符重载
这里直介绍 = 运算符的重载

特点
参数类型最好是引用,可以提高效率
检测是否存在自己给自己加赋值的情况
一个类中如果显式没有给出赋值运算符的重载,编译器会自己生成,同样地,生成的也是按照浅拷贝的方式进行赋值
至少要有一个自定义类型(类类型)的参数
class Date
{
public:
    Date()
    {}

    Date(int year, int month, int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {
    }

    ~Date()
    {
    }

    Date(const Date& d)
    {
        _year = d._day;
        _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;
};

int main()
{
    Date d1(2108,12,12);
    Date d2;
    Date d3;
    d3 = d2 = d1;
    
    system("pause");
    return 0;
}

为什么一定要有一个自定义类型的参数??
因为如果都是内置类型的参数,比如两个int ,= 完全就可以不用重载,就可以很好的进行赋值。自己再重载,岂不是画蛇添足。

const
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明该成员函数中不能对类的任何成员进行修改

class Date
{
public:
    Date(int year, int month, int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}
     //表示类成员变量都不可以被修改
    void Display() const
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

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

int main()
{
    Date d1(2108,12,12);
    d1.Display();
    
    system("pause");
    return 0;
}

如果必须要修改类成员变量,可以加上multanle关键字

class Date
{
public:
    Date(int year, int month, int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}
     
    void Display() const
    {
        _year += 1;
        cout << _year << "-" << _month << "-" << _day << endl;
    }

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

int main()
{
    Date d1(2108,12,12);
    d1.Display();
    
    system("pause");
    return 0;
}


&及const &重载
class Date
{
public:
    Date* operator&()
    {
        return this;
    }
    //因为对象被const修饰不能更改,所以返回值也要被const修饰
    const Date* operator&() const
    {
        return this;
    }
private:
    int _year;
    int _month;
    int _day;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值