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

简介: 【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不能修改数据成员的宗旨不符.
目录
相关文章
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
560 12
|
存储 监控 算法
基于 C++ 哈希表算法的局域网如何监控电脑技术解析
当代数字化办公与生活环境中,局域网的广泛应用极大地提升了信息交互的效率与便捷性。然而,出于网络安全管理、资源合理分配以及合规性要求等多方面的考量,对局域网内计算机进行有效监控成为一项至关重要的任务。实现局域网内计算机监控,涉及多种数据结构与算法的运用。本文聚焦于 C++ 编程语言中的哈希表算法,深入探讨其在局域网计算机监控场景中的应用,并通过详尽的代码示例进行阐释。
297 4
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
存储 算法 安全
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
安全 编译器 C++
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
458 5

热门文章

最新文章

推荐镜像

更多
  • DNS