一、 构造函数是干什么的

  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. C语言:最大公约数和最小公倍数

    #include <stdio.h> int main() { int a,b,c,m,t; printf("请输入两个数:\n"); scanf("%d%d ...

  2. C语言:预处理 编译过程分解 证明图

  3. flex布局制作自适应网页

    网页布局是css的一个重点应用.传统的布局都是依赖display.position.float属性来实现的,但是特殊布局就不易实现,如垂直居中. 01 flex布局是什么?‍ Flex 是 Flexi ...

  4. SpringBoot自动装配-自定义Start

    SpringBoot自动装配 在没有使用SpringBoot之前,使用ssm时配置redis需要在XML中配置端口号,地址,账号密码,连接池等等,而使用了SpringBoot后只需要在applicat ...

  5. ssh保持长连接的方式

    方法有以下三种:1.修改server端的etc/ssh/sshd_configClientAliveInterval 60 #server每隔60秒发送一次请求给client,然后client响应,从 ...

  6. 网络损伤仪WANsim--不同的部署方式

    网络损伤仪WANsim的业务口在逻辑上是不存在IP地址与MAC地址的,所以,WANsim可以串接在测试拓扑中的任意位置,只需要确保有流量通过WANsim即可. 不同的拓扑结构会对测试的结果造成影响.在 ...

  7. 最小覆盖问题-POJ3041-P1129

    POJ3041 这道题正解对于像我这种蒟蒻来说比较难以想到. 我们发现每次覆盖的只是一条线上的所有点.那么我们可以把它想象成一个二分图,两个集合分别是横轴和纵轴. 想一想,这实际上是不是就是x轴轴和纵 ...

  8. C语言学习之基本数据类型【一】

    近期学习鸿蒙硬件物联网开发,用到的开发语言是C: 一.基础语法:第一个案例: 命令 gcc hello.c #include <stdio.h> //stdio.h 是一个头文件 , #i ...

  9. element UI表格行高、padding等设置报错问题

    element UI里面表格的行高需要自己调整高度和设置padding,直接写style是不行的,里面有 : 1.row-style (行的 style) 2.header-row-styl   (表 ...

  10. 大数据学习(13)—— HBase入门

    从这一篇起,开始介绍HBase相关知识.还是一样,大数据的学习,获取官网知识很重要.官网看这里Apache HBase HBase简介 Apache HBase is the Hadoop datab ...