一、 构造函数是干什么的

  1.  
    class Counter
  2.  
    {
  3.  
    public:
  4.  
             // 类Counter的构造函数
  5.  
             // 特点:以类名作为函数名,无返回类型
  6.  
             Counter()
  7.  
             {
  8.  
                    m_value = 0;
  9.  
             }
  10.  
    private:
  11.  
              // 数据成员
  12.  
             int m_value;
  13.  
    }

该类对象被创建时,编译系统对象分配内存空间,并自动调用该构造函数->由构造函数完成成员的初始化工作
eg:    Counter c1;
       编译系统为对象c1的每个数据成员(m_value)分配内存空间,并调用构造函数Counter( )自动地初始化对象c1的m_value值设置为0
故:构造函数的作用:初始化对象的数据成员。
二、 构造函数的种类

  1.  
    class Complex 
  2.  
    {         
  3.  
    private :
  4.  
            double    m_real;
  5.  
            double    m_imag;
  6.  
    public:
  7.  
    Complex() //一般构造函数
  8.  
            {
  9.  
                 m_real = 0.0;
  10.  
                 m_imag = 0.0;
  11.  
            }   
  12.  
     
  13.  
    Complex(double real, double imag) //一般构造函数
  14.  
            {
  15.  
                 m_real = real;
  16.  
                 m_imag = imag;         
  17.  
             }  
  18.  
     
  19.  
    Complex(const Complex & c) //拷贝构造函数
  20.  
            {
  21.  
                    // 将对象c中的数据成员值复制过来
  22.  
                    m_real = c.m_real;
  23.  
                    m_imag    = c.m_imag;
  24.  
            }        
  25.  
    Complex &operator=( const Complex &rhs ) //赋值构造函数
  26.  
            {
  27.  
                    // 首先检测等号右边的是否就是左边的对象本身,若是本对象本身,则直接返回
  28.  
                    if ( this == &rhs ) 
  29.  
                    {
  30.  
                            return *this;
  31.  
                    }                
  32.  
                    // 复制等号右边的成员到左边的对象中
  33.  
                    this->m_real = rhs.m_real;
  34.  
                    this->m_imag = rhs.m_imag;                
  35.  
                   // 把等号左边的对象再次传出            
  36.  
                    return *this;
  37.  
            }
  38.  
    };

//    无参数构造函数
        // 如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做
        // 只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来
       
        //    一般构造函数(也称重载构造函数)
        // 一般构造函数可以有各种参数形式,一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理)
        // 例如:你还可以写一个 Complex( int num)的构造函数出来
        // 创建对象时根据传入的参数不同调用不同的构造函数
       
        //    复制构造函数(也称为拷贝构造函数)
        //    复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中
        //    若没有显示的写复制构造函数,则系统会默认创建一个复制构造函数,但当类中有指针成员时,由系统默认创建该复制构造函数会存在风险,具体原因请查询 有关 “浅拷贝” 、“深拷贝”的文章论述
           
        // 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象,
     //需要注意的一点是,这个其实就是一般的构造函数,但是对于出现这种单参数的构造函数,C++会默认将参数对应的类型转换为该类类型,有时候这种隐私的转换是我们所不想要的,所以需要使用explicit来限制这种转换。

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

下面使用上面定义的类对象来说明各个构造函数的用法:
int main()
{
        // 调用了无参构造函数,数据成员初值被赋为0.0
        Complex c1,c2;

        // 调用一般构造函数,数据成员初值被赋为指定值
        Complex c3(1.0,2.5);
        // 也可以使用下面的形式
        Complex c3 = Complex(1.0,2.5);
        
        //    把c3的数据成员的值赋值给c1
        //    由于c1已经事先被创建,故此处不会调用任何构造函数
        //    只会调用 = 号运算符重载函数
        c1 = c3;        
             
       // 调用拷贝构造函数( 有下面两种调用方式) 
        Complex c5(c2);
        Complex c4 = c2;  // 注意和 = 运算符重载区分,这里等号左边的对象不是事先已经创建,故需要调用拷贝构造函数,参数为c2
//这一点特别重要,这儿是初始化,不是赋值。其实这儿就涉及了C++中的两种初始化的方式:复制初始化和赋值初始化。其中c5采用的是复制初始化,而c4采用的是赋值初始化,这两种方式都是要调用拷贝构造函数的。
}

三、深拷贝与浅拷贝

如果没有自定义复制构造函数,则系统会创建默认的复制构造函数,但系统创建的默认复制构造函数只会执行“浅拷贝”,即将被拷贝对象的数据成员的值一一赋值给新创建的对象,若该类的数据成员中有指针成员,则会使得新的对象的指针所指向的地址与被拷贝对象的指针所指向的地址相同,delete该指针 时则会导致两次重复delete而出错。下面是示例:

  1.  
    #include <iostream.h>
  2.  
    #include <string.h>
  3.  
    class Person 
  4.  
    {
  5.  
    public :
  6.  
             // 构造函数
  7.  
            Person(char * pN)
  8.  
            {
  9.  
                  m_pName = new char[strlen(pN) + 1];
  10.  
                  //在堆中开辟一个内存块存放pN所指的字符串
  11.  
                  if(m_pName != NULL) 
  12.  
                  {
  13.  
                     //如果m_pName不是空指针,则把形参指针pN所指的字符串复制给它
  14.  
                       strcpy(m_pName ,pN);
  15.  
                  }
  16.  
            }        
  17.  
            
  18.  
            // 系统创建的默认复制构造函数,只做位模式拷贝
  19.  
            Person(Person & p)    
  20.  
            { 
  21.  
                      //使两个字符串指针指向同一地址位置         
  22.  
                     m_pName = p.m_pName;         
  23.  
            }
  24.  
             ~Person( )
  25.  
            {
  26.  
                    delete m_pName;
  27.  
            }
  28.  
      private :
  29.  
     
  30.  
            char * m_pName;
  31.  
    };
  32.  
     
  33.  
    void main( )
  34.  
  35.  
            Person man("lujun");
  36.  
            Person woman(man); 
  37.  
            
  38.  
            // 结果导致   man 和    woman 的指针都指向了同一个地址
  39.  
            
  40.  
            // 函数结束析构时
  41.  
            // 同一个地址被delete两次
  42.  
    }

// 下面自己设计复制构造函数,实现“深拷贝”,即不让指针指向同一地址,而是重新申请一块内存给新的对象的指针数据成员
Person(Person & chs);
{
         // 用运算符new为新对象的指针数据成员分配空间
         m_pName=new char[strlen(p.m_pName)+ 1];

         if(m_pName)         
         {
                 // 复制内容
                strcpy(m_pName ,chs.m_pName);
         }
      
        // 则新创建的对象的m_pName与原对象chs的m_pName不再指向同一地址了
}

四、构造函数显示调用和隐式调用

C/C++中的显示调用和隐式调用:

(1)显示调用

显示调用是指在程序中能找到相应的调用代码,或者说是手动调用的

(2)隐式调用

隐式调用是指程序中找不到相应的调用代码,或者说是编译器自动调用的

类的构造函数与析构函数一般就是隐式调用的。

如下代码:

  1.  
    A()
  2.  
    {
  3.  
    //一般构造函数
  4.  
    }
  5.  
     
  6.  
    A& operator =(const A& a)
  7.  
    {
  8.  
    // 赋值构造函数
  9.  
    }
  10.  
     
  11.  
    A(const A& a)
  12.  
    {
  13.  
    //拷贝构造函数
  14.  
    }
  15.  
     
  16.  
    ~A()
  17.  
    {
  18.  
    }

下面给出一些构造示例:

  1.  
    void f()
  2.  
    {
  3.  
    // A()构造函数被调用
  4.  
    A a;
  5.  
    // A(const A& a)构造函数被调用
  6.  
    A b(a);
  7.  
    // A(const A& a)构造函数被调用
  8.  
    A c = a;
  9.  
    // A& operator = (const A& a)赋值操作符重载函数被调用
  10.  
    b = c;
  11.  
    }
  12.  
     
  13.  
    // 离开f()函数之前,a,b,c的析构函数被调用,做一些清理工作

注意上面 "A c = a" ,这句代码实际调用的是拷贝构造函数,而非赋值函数。属于构造函数的隐式调用,可以使用explicit修饰构造函数的定义,使得其不能被隐式调用,如下:

    1.  
      explicit A(int n)
    2.  
      {
    3.  
      m_data = NULL;
    4.  
      if (n>0)
    5.  
      m_data = new int[n];
    6.  
      }

C++类构造函数、拷贝构造函数、复制构造函数、复制构造函数、构造函数显示调用和隐式调用的更多相关文章

  1. C++转换构造函数和隐式转换函数 ~ 转载

    原文地址: C++转换构造函数和隐式转换函数 用转换构造函数可以将一个指定类型的数据转换为类的对象.但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成doubl ...

  2. C++ 隐式类类型转换

    <C++ Primer>中提到: “可以用 单个形参来调用 的构造函数定义了从 形参类型 到 该类类型 的一个隐式转换.” 这里应该注意的是, “可以用单个形参进行调用” 并不是指构造函数 ...

  3. C++ 隐式类类型转换和转换操作符

    隐式类类型转换 C++语言定义了内置类型之间的几个自动转换.也可以定义如何将其他类型的对象隐式转换为我们的类类型,或将我们的类类型的对象隐式转换为其他类型.为了定义到类类型的隐式转换,需要定义合适的构 ...

  4. C++隐式类类型转化

    隐式类类型转换:可以用 单个形参来调用 的构造函数定义了从 形参类型 到 该类类型 的一个隐式转换 class Person { public: Person(): mName()name, mAge ...

  5. [C++]复制构造函数、赋值操作符与隐式类类型转换

    问题:现有类A定义如下: class A{public:        A(int a)                            //构造函数        {              ...

  6. C++构造函数 & 拷贝构造函数 & 派生类的构造函数 & 虚继承的构造函数

    构造函数 ,是一种特殊的方法 .主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 .特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数 ...

  7. C++编写字符串类CNString,该类有默认构造函数、类的拷贝函数、类的析构函数及运算符重载

    编码实现字符串类CNString,该类有默认构造函数.类的拷贝函数.类的析构函数及运算符重载,需实现以下“=”运算符.“+”运算.“[]”运算符.“<”运算符及“>”运算符及“==”运算符 ...

  8. C++类中拷贝构造函数详解

    a. C++标准中提到"The default constructor, copy constructor and copy assignment operator, and destruc ...

  9. C++ //构造函数调用规则 //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) //析构函数(空实现) //拷贝函数(值拷贝) //2.如果我们写了有参构造函数 编译器就不会提供默认构造函数 但是会提供拷贝构造函数 //3.如果我们写了拷贝函数 编译器就不再提供 默认 有参 构造函数

    //构造函数调用规则 #include <iostream> using namespace std; //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) ...

随机推荐

  1. SpringBoot | 2.1 SpringBoot自动装配原理

    @ 目录 前言 1. 引入配置文件与配置绑定 @ImportResource @ConfigurationProperties 1.1 @ConfigurationProperties + @Enab ...

  2. PYTHON matplotlib入门

    '''作为线性图的替代,可以通过向 plot() 函数添加格式字符串来显示离散值. 可以使用以下格式化字符. 字符 描述 '-' 实线样式 '--' 短横线样式 '-.' 点划线样式 ':' 虚线样式 ...

  3. Vue全局弹窗:一次注册,全局可弹

    Vue全局弹窗 今天来搞一个全局弹窗,不用每个文件都引入,只在main.js里作为全局原型引入就好了 先新建弹窗组件 toast.vue <template></template&g ...

  4. xmind8 Mac序列号

    1.首先去官网下载xmind8的安装包:XMind for Mac 也可以去我的百度网盘下载:    链接:https://pan.baidu.com/s/1eY52YsSaPmr-YFhB62Cli ...

  5. SLAM的数学基础(3):几种常见的概率分布的实现及验证。

    分布,在计算机学科里一般是指概率分布,是概率论的基本概念之一.分布反映的是随机或某个系统中的某个变量,它的取值的范围和规律. 常见的分布有:二项分布.泊松分布.正态分布.指数分布等,下面对它们进行一一 ...

  6. JavaScript学习笔记:你必须要懂的原生JS(一)

    1.原始类型有哪几种?null是对象吗?原始数据类型和复杂数据类型存储有什么区别? 原始类型有6种,分别是undefined,null,bool,string,number,symbol(ES6新增) ...

  7. html自定义加载动画

    整体代码 HTML 实现自定义加载动画,话不多说如下代码所示: <!DOCTYPE html> <html lang="en"> <head> ...

  8. 在Rancher中修改K8S服务参数的万金油法则

    作者简介 王海龙,Rancher中国社区技术经理,负责Rancher中国技术社区的维护和运营.拥有7年的云计算领域经验,经历了OpenStack到Kubernetes的技术变革,无论底层操作系统Lin ...

  9. 二本,拿腾讯,阿里 offer 了

    我的春招 Hello,首先自我介绍一下,我是一所普普通通的二本院校的大三学生,坐标江苏. 今年三月份拿到了腾讯实习的offer,人生中第一次面试是腾讯,部门是 TEG 的云架构,并且顺利签约,说实话内 ...

  10. SSM框架,在Html界面利用ajax,json,jQuery实现省市区下拉框联动

    1.先生成省市区表格 2.建立实体类 3.在html画出下拉框 <select id="province"> <option value="" ...