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

本文详细介绍了构造函数和析构函数的概念、特点及应用场景,包括无参构造函数、带参构造函数、拷贝构造函数和析构函数的定义与使用方法。此外,还解释了成员变量初始化的方法及赋值运算符重载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

构造函数

定义

成员变量为私有的,要对他们进行初始化,必须用一个共有成员函数来进行。同时这个函数应该有且仅在定义对象时自动执行一次,这时调用的函数称为构造函数(constructor)。

特征:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象构造(对象实例化)时系统自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 构造函数可以在类中定义,也可以在类外面定义。
  6. 如果类定义中没有给出构造函数,则C++编译器自动生成一个缺省的构造函数,但只要定义了一个构造函数,系统就不会自动生成缺省的构造函数。
  7. 无参的构造函数全缺省的构造函数都认为是缺省的构造函数,并且缺省的构造函数只能有一个。

    代码举例

class Date
{
public:
    Date()//无参构造函数
    {
        _year = 2017;//构造函数函数体内赋值
        _month = 8;
        _day = 8;
    }

    Date(int year, int month, int day)//带参构造函数
        :_year(year)   //使用初始化列表赋值
        , _month(month)
        , _day(day)
    {}

    //**缺省的构造函数只能有一个。**
    //Date(int year = 1900, int month = 1, int day = 1)//全缺省的构造函数  
    //{
    //  _year = year;
    //  _month = month;
    //  _day = day;
    //}
private:
    int _year;//成员变量
    int _month;
    int _day;
};

补充:构造函数,说来就是给成员变量进行初始化。而初始化却有两种方法:
初始化列表、构造函数函数体内赋值。
初始化列表可以参考另一篇博客:详解初始化列表

成员变量的初始化顺序

成员是按照他们在类中声明的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的。例如:

class Date
{
public:
    Date(int year, int month, int day)//带参构造函数
        :_month(month)//第二个初始化
        , _year(year) //第一个初始化
        , _day(day)   //第三个初始化
    {}

private:
    int _year; //第一个定义
    int _month;//第二个定义
    int _day;  //第三个定义
};

拷贝构造函数

定义

创建对象时使用同类对象来进行初始化,这时所用的构造函数成为拷贝构造函数(Copy Constructor),拷贝构造函数是特殊的构造函数。

特征:

  1. 拷贝构造函数其实就是一个构造函数的重载。
  2. 拷贝构造函数的参数必须使用引用,使用传值方式会引发无穷递归调用。(为什么?我们保留疑问,后面解答。)
  3. 若未显示定义,系统会自动生成默认的拷贝构造函数。缺省的拷贝构造函数会,依次拷贝类成员进行初始化。

代码举例

class Date
{
public:
    Date()//无参构造函数
    {
        _year = 2017;
        _month = 8;
        _day = 8;
    }

    Date(const Date& d) //拷贝构造函数
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
private:
    int _year;//成员变量
    int _month;
    int _day;
};

void DateTest()
{
    Date d1;    //调用无参的构造函数
    Date d2(d1);//调用拷贝构造函数
    Date d3 = d1;//调用拷贝构造函数
}

思考:

  1. 拷贝构造函数的参数必须使用引用,使用传值方式会引发无穷递归调用。为什么?
    答:如果采用传值得形式,把形参拷贝到实参会调用拷贝构造函数。那么就会形成无休止的递归。因此C++的标准不允许拷贝构造函数传值参数,而必须是传引用或者常量引用。在Visual Studio和GCC中,都将编译出错。
  2. 为什么下面的对象可以直接访问类的使用成员??
Date(const Date& d) //拷贝构造函数
{
    _year = d._year;
    _month = d._month;
    _day = d._day;
}

答:
在类的成员函数中可以直接访问同类对象的私有/保护成员。
C++的访问限定符是以类为单位的,也就是说在这个单位内的成员可以相互访问。

什么时候调用拷贝构造函数?

1.当用一个类的对象初始化该类的另一个对象。

void DateTest()
{
    Date d1;    //调用无参的构造函数
    Date d2(d1);//调用拷贝构造函数
    Date d3 = d1;//调用拷贝构造函数
}

2.如果函数的形参是类的对象,调用函数时,进行形参和实参结合时。

void fun(Date d)
{}

void Test()
{
    Date d1;
    fun(d1);//函数的形参为类的对象时,当调用函数时,拷贝构造函数被调用.
}

3.如果函数的返回值是类的对象,函数执行完成返回调用者时。

Date fun()
{
    Date d;
    return d;//函数的返回值是类的对象,返回函数值时,调用拷贝构造函数
}

析构函数

定义

当一个对象的生命周期结束时,C++编译系统会自动调用一个成员函数,这个特殊的成员函数即为析构函数(destructor)

特征

  1. 析构函数在类名加上字符~
  2. 析构函数无参数无返回值
  3. 一个类有且只有一个析构函数。若未显示定义,系统自动生成缺省的析构函数。
  4. 对象生命周期结束时,C++编译系统会自动调用析构函数。
  5. 注意析构函数体内并不是删除对象,而是做一些清理工作。

代码举例

在函数退出前打一个断点,可以看出对象生命周期结束时,会调用析构函数。

class Date
{
public:
    Date()//无参构造函数
    {
        cout << "Date()" <<endl;
    }

    ~Date()//析构函数
    {
        cout << "~Date()" << endl;
    }
private:
    int _year;//成员变量
    int _month;
    int _day;
};
int main()
{
    Date d1;
    return 0;
}

这里写图片描述

析构函数完成清理工作:

class Array
{
public:
    Array(int size)
    {
        _ptr = (int *)malloc(size*sizeof (int));
    }
    // 这里的析构函数需要完成清理工作(释放内存)。
    ~Array()
    {
        if (_ptr)
        {
            free(_ptr);
            _ptr = 0;
        }
    }
private:
    int* _ptr;
};

什么时候调用析构函数

1.对象生命周期结束,被销毁时(一般类成员的指针变量与引用都i不自动调用析构函数);
2.delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3.对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。

变量的声明顺序与析构顺序

成员变量的声明顺序与其析构顺序相反,即,先声明的变量后析构,后声明的变量先析构。

class A
{
public:

    A()
    {
        cout << "A()" << endl;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }
};

class B
{
public:

    B()
    {
        cout << "B()" << endl;
    }

    ~B()
    {
        cout << "~B()" << endl;
    }
};
int main()
{
    A a;
    B b;
    return 0;
}

这里写图片描述


赋值运算符重载函数

定义

赋值运算符的重载是对一个已存在的对象进行拷贝赋值。
C++的重载运算符,由关键字operator和运算符号共同组成,一般而言C++里只要运算符不含”.”都可以重载。

代码举例

class Date
{
public:
    Date()//无参构造函数
    {
        _year = 2017;
        _month = 8;
        _day = 8;
    }

    Date(const Date& d) //拷贝构造函数
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    Date& operator = (const Date& d)// 赋值操作符的重载
    {
        if (this != &d)
        {
            this->_year = d._year;
            this->_month = d._month;
            this->_day = d._day;
        }
        return *this;
    }
private:
    int _year;//成员变量
    int _month;
    int _day;
};

分析

1、返回值类型

返回类型一般声明为类型的引用,并在函数结尾时返回实例自身的引用(即*this)。这里主要有两个原因:

  • 返回引用可以减少一次拷贝构造和析构函数导致不必要的开销,因为返回值类型不是引用,会创建一个匿名对象,这个匿名对象时个右值,获取return的值。
  • 可以实现连续赋值。

2、参数
参数声明为const且是一个引用。

  • const 是因为赋值运算,不希望修改原来类的状态,同时可以接受const与非const的参数
  • 引用则避免了拷贝构造函数

3、判断是否是传入实例与当前实例是同一个,保证自赋值的安全如果相同,直接返回可以减少不必要的操作,同时防止指向的同一资源一起被销毁。
4、赋值前,释放自身的内存。


取地址操作符重载

Date* operator&()
{
    return *this;
}

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

const Date* operator&() const
{
    return *this;
}

函数后边的const表明在函数体中不能改变对象的成员,函数的返回值是指向常对象的指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值