【C/C++ 构造函数 详解】深入解析C++ 构造函数:C++ 11 中的新特性与实践

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【C/C++ 构造函数 详解】深入解析C++ 构造函数:C++ 11 中的新特性与实践

构造函数介绍

📌类对象被创建的时候,编译系统对象分配内存空间,并自动调用构造函数,用于初始化对象的数据成员.

  • 构造函数不需要在定义时声明类型
  • 构造函数不需要用户进行调用
  • 可以在类内,也可以在类外构造函数;在类外构造函数时,需要在类内进行声明。
  • 构造函数的名字必须`与类名相同,并且不会返回任何类型,也不会返回 void(函数体中不能有 return 语句)。
  • 构造函数通常用于对类内的数据进行初始化

构造函数种类

默认构造函数/无参构造函数

📌如果创建一个类,没有写任何构造函数,则系统会自动生成默认的无参构造函数,且此函数为空。

说明:

1. 只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数

2. 如果类包含有内置类型或者复合类型的成员,则只有当这些成员全都被赋予了类内的初始值时.这类才适合于使用合成的默认构造函数

3. 如果一个构造函数为所有参数都提供了默认实参,则他实际上也定义了默认构造函数.

4. 有的时候编译器不能为某些类合成默认的构造函数.例如,如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数.

初始化构造函数/一般构造函数

一般构造函数写法(初始化成员):

  • 初始化列表方式:以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化值
  • 内部赋值方式:正常函数的赋值。

转换构造函数(有且只有一个参数,参数是基本类型且是其他类型)

📌当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数,用于将其他类型的变量,隐式转换为本类对象。

作用:

  • 定义转换构造函数的目的是实现类型的自动转换。
  • 当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。

调用时间:

  • 发生在初始化语句。
  • 发生在赋值语句。

示例:

//例1: 隐式转换
    class Complex {
    public:
      double real, imag;
      Complex( int i) {//类型转换构造函数
        cout << "IntConstructor called" << endl;
        real = i; imag = 0;
      }
      Complex(double r,double i) {real = r; imag = i; }
    };
    int main ()
    {
      Complex c1(7,8);
      Complex c2 = 12;// 属于情况一:编译器先创建一个Complex对象c2,然后将12传递给转换构造函数对c2变量进行初始化。
      c1 = 9; // 属于情况二:9被自动转换成一个临时Complex对象,然后将该对象赋值给c1。这里的转换属于隐式的转换。
      cout << c1.real << "," << c1.imag << endl;
      return 0;
    }
    //例2:显式转换
    class Complex {
    public:
      double real, imag;
      explicit Complex( int i) {//显式类型转换构造函数
      cout << "IntConstructor called" << endl;
      real = i; imag = 0;
      }
      Complex(double r,double i) {real = r; imag = i; }
    };
    int main () {
      Complex c1(7,8);
      Complex c2 = Complex(12);
      c1 = 9; // error,9不能被自动转换成一个临时Complex对象,不支持隐式的转换。所匹配的转换构造函数不支持隐式转换。
      c1 = Complex(9) //正确,这里,complex(9)先创建一个临时的Complex对象,然后调用转换构造函数将其初始化,即:显示的将9转换为一个Complex对象。
      cout << c1.real << "," << c1.imag << endl;
      return 0;
    }
    //显式类型转换构造函数,不会隐式的调用类型转换构造函数,必须显式的调用

转换构造函数能够将其它类型转换为当前类类型(例如将 double 类型转换为 Complex

类型),但是不能反过来将当前类类型转换为其它类型(例如将 Complex 类型转换为 double 类型)。

C++提供了类型转换函数(Type conversion function)来解决这个问题.

类型转换函数/类型转换运算符重载函数/类型转换运算符函数

类型转换函数的作用就是将当前类类型转换为其它类型,它只能以成员函数的形式出现,也就是只能出现在类中。

语法:

operator type(){
        //TODO:
        return data;
    }
  • 重载函数都使用关键字operator,它的意思是”运算符“.
  • 类型转换函数也没有参数,因为要将当前类的对象转换为其它类型.
  • type 可以是内置类型、类类型以及由 typedef 定义的类型别名,任何可作为函数返回类型的类型(void 除外)都能够被支持。一般而言,不允许转换为数组或函数类型,转换为指针类型或引用类型是可以的。
  • 类型转换函数一般不会更改被转换的对象,所以通常被定义为const 成员。
  • 类型转换函数可以被继承,可以是虚函数。
  • 一个类虽然可以有多个类型转换函数(类似于函数重载),但是如果多个类型转换函数要转换的目标类型本身又可以相互转换(类型相近),那么有时候就会产生二义性。

拷贝构造函数(复制构造函数)

📌复制构造函数参数为类对象本身的引用,根据一个已存在的对象复制出一个新的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中。

语法:

classname (const classname &obj) {   
    // 构造函数的主体
    }
  • 拷贝构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类型的引用,当对象之间复制时会自动调用拷贝构造函数.
  1. 当定义一个新对象并用同一类型的对象都它进行初始化时,将显示使用拷贝构造函数,
  2. 当该类型的对象传递给函数返回该类型的对象时,将隐式调用拷贝构造函数
  • 当类中有数据成员是指针,或者有成员表示在构造函数中分配的其他资源,由系统默认创建的复制构造函数会存在“浅拷贝”的风险,因此必须显示定义拷贝构造函数.
  • 复制构造函数的参数可以是 const 引用,也可以是非 const 引用。
    一般使用前者,这样既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。
    一个类中写两个复制构造函数,一个的参数是 const 引用,另一个的参数是非 const 引用,也是可以的。


深拷贝和浅拷贝:

浅拷贝指的是在对对象复制时,只对对象中的数据成员进行简单的赋值,若存在动态成员,就是增加一个指针,指向原来已经存在的内存。
这样就造成两个指针指向了堆里的同一个空间。当这两个对象生命周期结束时,析构函数会被调用两次,同一个空间被两次free,造成野指针。

深拷贝就是对于对象中的动态成员,不是简单的赋值,而是重新分配空间。


  • 拷贝构造函数被调用的情况
    类的对象需要拷贝时,拷贝构造函数将会被调用

以下情况都会调用拷贝构造函数:

  • 一个对象以值传递的方式传入函数体(作为函数的形参传递)
    如果函数 F 的参数是类 A 的对象,那么当 F 被调用时,类 A 的复制构造函数将被调用。换句话说,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。
    示例:
#include<iostream>
            using namespace std;
            class A{
            public:
                A(){};
                A(A & a){
                    cout<<"Copy constructor called"<<endl;
                }
            };
            void Func(A a){ }
            int main(){
                A a;
                Func(a);
                return 0;
            }
  • 一个对象以值传递的方式从函数体返回(作为函数的实参返回)
    如果函数的返冋值是类 A 的对象,则函数返冋时,类 A 的复制构造函数被调用。换言之,作为函数返回值的对象是用复制构造函数初始化 的,而调用复制构造函数时的实参,就是 return
    语句所返回的对象。

    示例:
#include<iostream>
           using namespace std;
           class A {
           public:
               int v;
               A(int n) { v = n; };
               A(const A & a) {
                   v = a.v;
                   cout << "Copy constructor called" << endl;
               }
           };
           A Func() {
               A a(4);
               return a;
           }
           int main() {
               cout << Func().v << endl;
               return 0;
           }
  • 一个对象需要通过另一个对象进行初始化(对象之间拷贝)
    当用一个对象去初始化同类的另一个对象时,会引发复制构造函数被调用。例如,下面的两条语句都会引发复制构造函数的调用,用以初始化 c2。
    示例:
Complex c2(c1);
            Complex c2 = c1;

  • 移动构造函数

📌移动构造函数是参数类型为*右值引用的拷贝构造函数。移动构造函数的本质是对资源的掠夺以及避免深拷贝.

移动构造的优点:节省了开辟内存与赋值的时间。

对于基本类型,其移动构造等价于复制构造。

语法:

class_name(class_name && )

示例:

Data(Data&& Other) {
        this->IntData = Other.IntData;
        Other.IntData = nullptr;
        this->IntDataLength = Other.IntDataLength;
        Other.IntDataLength = 0;
    }
  • 什么时候需要移动构造函数?
    当你的类或者结构体中有这样的指向堆上资源的指针,而且想要对临时变量所持有的资源直接掠夺过来的时候。

  • 委托构造函数(允许构造函数通过初始化列表调用同一个类的其他构造函数)

📌C++11新增了委托构造函数(delegating constructor),使得一个委托构造函数能够使用它所属的类的其他构造函数进行自己初始化的过程。
当一个委托构造函数委托给另一个构造函数时,首先,受委托的构造函数的初始值列表和函数体依次执行,然后才执行委托构造函数的函数体。

目的是简化构造函数的书写,提高代码的可维护性,避免代码冗余膨胀。

注意事项:

  • 不要形成委托环
  • 如果在委托构造函数中使用try,可以捕获目标构造函数中抛出的异常。
  • 类名后面的参数列表必须与类中另外一个构造函数匹配

相关示例

  • 无参构造函数和一般构造函数
#include <iostream>
  using namespace std;
  
  class Coordinate
  {
  public:
    // 无参构造函数
    // 如果创建一个类你没有写任何构造函数,则系统自动生成默认的构造函数,函数为空,什么都不干
    // 如果自己显示定义了一个构造函数,则不会调用系统的构造函数
    Coordinate()
    {
      c_x = 0;
      c_y = 0;
    }     
  
    // 一般构造函数
    Coordinate(double x, double y):c_x(x), c_y(y){}   //列表初始化
    // 一般构造函数可以有多个,创建对象时根据传入的参数不同调用不同的构造函数
  
    Coordinate(const Coordinate& c)
    {
      // 复制对象c中的数据成员
      c_x = c.c_x;
      c_y = c.c_y;
    }
  
    // 等号运算符重载
    Coordinate& operator= (const Coordinate& rhs)
    {
      // 首先检测等号右边的是否就是等号左边的对象本身,如果是,直接返回即可
      if(this == &rhs)
        return* this;
      // 复制等号右边的成员到左边的对象中
      this->c_x = rhs.c_x;
      this->c_y = rhs.c_y;
      return* this;
    }
  
    double get_x()
    {
      return c_x;
    }
  
    double get_y()
    {
      return c_y;
    }
  
  private:
    double c_x;
    double c_y;
  };
  
  int main()
  {
    // 调用无参构造函数,c1 = 0,c2 = 0
    Coordinate c1, c2;
    // 调用一般构造函数,调用显示定义构造函数
    Coordinate c3(1.0, 2.0);
    c1 = c3;    //将c3的值赋值给c1,调用"="重载
    Coordinate c5(c2);
    Coordinate c4 = c2;    // 调用浅拷贝函数,参数为c2
    cout<<"c1 = "<<"("<<c1.get_x()<<", "<<c1.get_y()<<")"<<endl
      <<"c2 = "<<"("<<c2.get_x()<<", "<<c2.get_y()<<")"<<endl
      <<"c3 = "<<"("<<c3.get_x()<<", "<<c3.get_y()<<")"<<endl
      <<"c4 = "<<"("<<c4.get_x()<<", "<<c4.get_y()<<")"<<endl
      <<"c5 = "<<"("<<c5.get_x()<<", "<<c5.get_y()<<")"<<endl;
    return 0;
  }

  • 拷贝构造函数
//拷贝构造函数
#include <iostream>
  using namespace std;
  
  class Test
  {
  public:
    // 构造函数
    Test(int a):t_a(a){
    cout<<"creat: "<<t_a<<endl;
    }
  
    // 拷贝构造函数
    Test(const Test& T)
    {
      t_a = T.t_a;
      cout<<"copy"<<endl;
    }
  
    // 析构函数
    ~Test()
    {
      cout<<"delete: "<<t_a<<endl;
    }
  
    // 显示函数
    void show()
    {
      cout<<t_a<<endl; 
    }
  
  private:
    int t_a;
  };
  
  // 全局函数,传入的是对象
  void fun(Test C)
  {
    cout<<"test"<<endl;
  }
  
  int main()
  {
    Test t(1);
    // 函数中传入对象
    fun(t);
    return 0;
  }

  • 移动构造函数
#include <iostream>
using namespace std;
class IntNum
{
private:
    int *xptr;
public:
    IntNum(int x);
    // IntNum(const IntNum &n);
    IntNum(IntNum &&n);
    ~IntNum();
    int getInt() { return *xptr; }
};
IntNum::IntNum(int x = 0) : xptr(new int(x))
{
    cout << "Calling constructor..." << endl;
}
IntNum::IntNum(IntNum &&n) : xptr(n.xptr)//两个& 表示右值的引用
{
    n.xptr = nullptr;
    cout << "Calling Move Assignment Operator..." << endl;
}
IntNum::~IntNum()
{
    delete xptr;
    cout << "Destructing..." << endl;
}
IntNum getNum()
{
    IntNum a;
    return a;
}
int main(int argc, char *argv[])
{
    cout << getNum().getInt() << endl;
    return 0;
}

  • 委托构造函数
class X {
void CommonInit();
Y y_;
Z z_;
public:
X();
X( int );
X( W );
};
X::X() : y_(42), z_(3.14) { CommonInit(); }
X::X( int i ) : y_(i), z_(3.14) { CommonInit(); }
X::X( W e ) : y_(53), z_( e ) { CommonInit(); }

其他

  • 构造函数不能为虚函数,虚函数的作用在于通过子类的指针或引用来调用父类的那个成员函数。
    而构造函数是在创建对象时自己主动调用的,不可能通过子类的指针或者引用去调用.。
  • 构造函数不能用const修饰,因为构造函数本身是初始化对象的过程,与const不能修改数据成员的宗旨不符.
目录
相关文章
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
141 77
|
1月前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
58 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
1月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
42 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
1月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
68 19
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
47 13
|
1月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
46 12
|
1月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
42 10
|
1月前
|
算法 C++
【C++数据结构——图】最小生成树(头歌实践教学平台习题) 【合集】
【数据结构——图】最小生成树(头歌实践教学平台习题)目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:【合集】任务描述 本关任务:编写一个程序求图的最小生成树。相关知识 为了完成本关任务,你需要掌握:1.建立邻接矩阵,2.Prim算法。建立邻接矩阵 上述带权无向图对应的二维数组,根据它建立邻接矩阵,如图1建立下列邻接矩阵。注意:INF表示无穷大,表示整数:32767 intA[MAXV][MAXV];Prim算法 普里姆(Prim)算法是一种构造性算法,从候选边中挑
42 10
|
1月前
|
存储 算法 C++
【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】
本任务要求编写程序实现图的邻接矩阵和邻接表的存储。需掌握带权有向图、图的邻接矩阵及邻接表的概念。邻接矩阵用于表示顶点间的连接关系,邻接表则通过链表结构存储图信息。测试输入为图的顶点数、边数及邻接矩阵,预期输出为Prim算法求解结果。通关代码提供了完整的C++实现,包括输入、构建和打印邻接矩阵与邻接表的功能。
46 10
|
1月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
37 7

热门文章

最新文章

推荐镜像

更多