C++ 构造函数的理解

一、构造函数的定义:

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

特殊在哪些地方呢?

1、构造函数的名称与类的名称是完全相同的。

2、并且不会返回任何类型,也不会返回 void。

一个典型的构造函数如下:

#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();  // 这是构造函数
 
   private:
      double length;
};
 
// 成员函数定义,包括构造函数
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
 
void Line::setLength( double len )
{
    length = len;
}
 
double Line::getLength( void )
{
    return length;
}
// 程序的主函数
int main( )
{
   Line line;
 
   // 设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Object is being created
Length of line : 6

注意:

1、构造函数和其他成员函数一样,可以在类体内定义,也可以在类体外定义,但是必须在类体内声明!上面的例子就是在类体外定义的,推荐在类体外定义。(https://zhidao.baidu.com/question/983557199561656459.html

2、构造函数可以是public/protected/private

 

 

二、构造函数的种类

 

1、默认构造函数

当我们程序中没有显式的定义构造函数时,编译器会提供一个默认的构造函数,这种编译器创建的构造函数又被称为合成的默认构造函数,合成构造函数的初始化规则是这样的:

  • 如果存在类内的初始值,用它来初始化成员。在C++11的新特性中,C++11支持为类内的数据成员提供一个初始值,创建对象时,类内初始值将用于初始化数据成员。如果在构造函数中又显式地初始化了数据成员,则使用显式初始化的值。
  • 否则,默认初始化该成员。默认初始化意味着和C语言一样的初始化方式,当类对象为全局变量时,在系统加载时初始化为0,而作为局部变量时,由于数据在栈上分配,成员变量值不确定。

2、无参数构造函数

class Complex 
{         
private :
    double m_real;
    double m_imag;

public:
    Complex(void)
    {
         m_real = 0.0;
         m_imag = 0.0;
    } 
};

3、一般构造函数(也称重载构造函数)

一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理),创建对象时根据传入的参数不同调用不同的构造函数

class Complex 
{         
private :
    double m_real;
    double m_imag;

public:
    Complex(double real, double imag)
    {
         m_real = real;
         m_imag = imag;         
    }

    Complex(double real)
    {
         m_real = real;       
    }
};

4、复制构造函数(也称为拷贝构造函数)

复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中
若没有显示的写复制构造函数,则系统会默认创建一个复制构造函数,但当类中有指针成员时,由系统默认创建该复制构造函数会存在风险,具体原因请查询有关 “浅拷贝” 、“深拷贝”的文章论述

class Complex 
{         
private :
    double m_real;
    double m_imag;

public:
    Complex(const Complex & c)
    {
        // 将对象c中的数据成员值复制过来
        m_real = c.m_real;
        m_img  = c.m_img;
    }            
};

5、赋值运算符重载

类的构造可以用重载赋值运算符来实现,即"="。
注意,这个类似复制构造函数,将=右边的本类对象的值复制给等号左边的对象,它不属于构造函数,等号左右两边的对象必须已经被创建
若没有显示的写=运算符重载,则系统也会创建一个默认的=运算符重载,只做一些基本的拷贝工作

class Complex 
{         
private :
    double m_real;
    double m_imag;

public:
    Complex &operator=(const Complex &rhs)
    {
        // 首先检测等号右边的是否就是左边的对象本,若是本对象本身,则直接返回
        if ( this == &rhs ) 
        {
            return *this;
        }
            
        // 复制等号右边的成员到左边的对象中
        this->m_real = rhs.m_real;
        this->m_imag = rhs.m_imag;
            
        // 把等号左边的对象再次传出
        // 目的是为了支持连等 eg:    a=b=c 系统首先运行 b=c
        // 然后运行 a= ( b=c的返回值,这里应该是复制c值后的b对象)    
        return *this;
    }
};

6、初始化列表的构造方式

首先,我们先需要分清初始化和赋值的概念,初始化就是在新创建对象的同时给予初值,而赋值是在两个已经存在的对象之间进行操作。在构造方式上,这两种是不同的。

构造函数支持初始化列表,它负责为新创建的对象的一个或者几个数据成员赋初值,初始化列表的语法是这样的:

class Test
{
public:
    Test(int a):x(a)
    {
    }

private:
    int x;
};

上面的语法等同于如下语法:

class Test
{
public:
    Test(int a):x(a)
    {
        x = a;
    }

private:
    int x;
};

假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
  ....
}

初始化的列表的一个优势是时间效率和空间效率比赋值要高,同时在const类型成员的构造时,普通的赋值构造函数是非法的。当我们创建一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其常量属性。

所以我们可以用这种方式为const成员变量写值。

7、隐式转换构造函数

 

class Test
{
public:
    Test(string s,int para = 1)
    {
        str = s;
    }
    void add(Test ob)
    {
        str += ob.str;
    }
    string str;
};

Test ob1("downey");
int main()
{
    ob1.add(string("downey!"));
    cout<<ob1.str<<endl;
}

运行结果:

downeydowney!

如码所示,Test类有一个构造函数,可以接收一个string类的实参(可以由一个实参构造并不代表只能有一个形参),而add()方法接受一个Test类类型参数,在调用add()方法时,我们直接传入一个string类型,触发隐式转换功能,编译器将自动以string作为实参构造一个Test的临时类对象来传入add()方法,程序结束之后将释放临时变量。
需要注意的是,隐式转换只支持一次转换,如果我们将main()函数改成这样:

int main()
{
    ob1.add("downey!");   
    cout<<ob1.str<<endl;
}

编译器需要将"downey"转换成string类型,然后再进行一次转换,这样是不支持的。在编译阶段就会报错:

error: no matching function for call to XXX

同时,如果我们在声明add()函数时习惯性地使用了左值引用:

void add(Test &ob)
{      //使用引用,&
        str += ob.str;
}

这样又是什么结果呢?

答案是,编译出错。这又是为什么?如果你有仔细看上面的隐式转换过程就可以知道,在使用隐式转换时生成了一个临时变量(类型同函数形参),而临时变量是右值,是不能使用左值引用的。报错信息如下:

error: no matching function for call to XXX  //左值引用不匹配,所以这里找不到匹配的方法。

 

 

ref:

https://www.runoob.com/cplusplus/cpp-constructor-destructor.html

https://www.cnblogs.com/downey-blog/p/10470782.html

https://blog.51cto.com/ticktick/194307

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值