C++构造函数详解及显式调用构造函数

2023-10-26

     c++类的构造函数详解                        

一、 构造函数是干什么的

class Counter
{
public:
         // 类Counter的构造函数
         // 特点:以类名作为函数名,无返回类型
         Counter()
         {
                m_value = 0;
         }
private:
          // 数据成员
         int m_value;
}
       该类对象被创建时,编译系统对象分配内存空间, 并自动调用该构造函数->由构造函数完成成员的初始化工作
eg:    Counter c1;
       编译系统为对象c1的每个数据成员(m_value)分配内存空间,并调用构造函数Counter( )自动地初始化对象c1的m_value值设置为0
故:
        构造函数的作用:初始化对象的数据成员。
二、 构造函数的种类
class Complex 
{         
private :
        double    m_real;
        double    m_imag;
public:
         //    无参数构造函数
        // 如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做
        // 只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来
        Complex(void)
        {
             m_real = 0.0;
             m_imag = 0.0;
        }   
         //    一般构造函数(也称重载构造函数)
        // 一般构造函数可以有各种参数形式,一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理)
        // 例如:你还可以写一个 Complex( int num)的构造函数出来
        // 创建对象时根据传入的参数不同调用不同的构造函数
        Complex(double real, double imag)
        {
             m_real = real;
             m_imag = imag;         
         }  
        //    复制构造函数(也称为拷贝构造函数)
        //    复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中
        //    若没有显示的写复制构造函数,则系统会默认创建一个复制构造函数,但当类中有指针成员时,由系统默认创建该复制构造函数会存在风险,具体原因请查询 有关 “浅拷贝” 、“深拷贝”的文章论述
        Complex(const Complex & c)
        {
                // 将对象c中的数据成员值复制过来
                m_real = c.m_real;
                m_imag    = c.m_imag;
        }            
         // 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象
      //需要注意的一点是,这个其实就是一般的构造函数,但是对于出现这种单参数的构造函数,C++会默认将参数对应的类型转换为该类类型,有时候这种隐私的转换是我们所不想要的,所以需要使用explicit来限制这种转换。
       // 例如:下面将根据一个double类型的对象创建了一个Complex对象
       Complex(double r)
        {
                m_real = r;
                m_imag = 0.0;
        }
         // 等号运算符重载也叫赋值构造函数)
        // 注意,这个类似复制构造函数,将=右边的本类对象的值复制给等号左边的对象,它不属于构造函数,等号左右两边的对象必须已经被创建
        // 若没有显示的写=运算符重载,则系统也会创建一个默认的=运算符重载,只做一些基本的拷贝工作
        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;
        }
};

下面使用上面定义的类对象来说明各个构造函数的用法:
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;        
        //    调用类型转换构造函数
        //    系统首先调用类型转换构造函数,将5.2创建为一个本类的临时对象,然后调用等号运算符重载,将该临时对象赋值给c1
        c2 = 5.2;        
       // 调用拷贝构造函数( 有下面两种调用方式) 
        Complex c5(c2);
        Complex c4 = c2;  // 注意和 = 运算符重载区分,这里等号左边的对象不是事先已经创建,故需要调用拷贝构造函数,参数为c2
//这一点特别重要,这儿是初始化,不是赋值。其实这儿就涉及了C++中的两种初始化的方式:复制初始化和赋值初始化。其中c5采用的是复制初始化,而c4采用的是赋值初始化,这两种方式都是要调用拷贝构造函数的。
}

三、思考与测验

1. 仔细观察复制构造函数
        Complex(const Complex & c)
        {
                // 将对象c中的数据成员值复制过来
                m_real = c.m_real;
                m_img = c.m_img;
        }        
为什么函数中可以直接访问对象c的私有成员?
答:(网上)因为拷贝构造函数是放在本身这个类里的,而类中的函数可以访问这个类的对象的所有成员,当然包括私有成员了。
2. 挑战题,了解引用与传值的区别
  Complex test1(const Complex& c)
  {
          return c;
  }
   Complex test2(const Complex c)
  {
         return c;
   }
    Complex test3()
   {
          static Complex c(1.0,5.0);
          return c;
   }
   Complex& test4()
  {
         static Complex c(1.0,5.0);
         return c;
  }  
  void main()
  {
        Complex a,b;    
        // 下面函数执行过程中各会调用几次构造函数,调用的是什么构造函数?    
       test1(a);
       test2(a);     
       b = test3();
       b = test4();     
       test2(1.2);
       // 下面这条语句会出错吗?
       test1(1.2);     //test1( Complex(1.2 )) 呢?
  }
  答:
为了便于看构造函数的调用效果,我将类重新改一下,添加一些输出信息,代码如下:

#include <iostream>
using  namespace std;
class Complex
{        
     private :
     double    m_real;
     double    m_imag;
     int id;
     static  int counter;
     public:
     //     无参数构造函数
    Complex( void)
    {
        m_real =  0.0;
        m_imag =  0.0;
        id=(++counter);
        cout<< " Complex(void):id= "<<id<<endl;
    }
     //     一般构造函数(也称重载构造函数)
    Complex( double real,  double imag)
    {
        m_real = real;
        m_imag = imag;        
        id=(++counter);
        cout<< " Complex(double,double):id= "<<id<<endl;
    }
     //     复制构造函数(也称为拷贝构造函数)
    Complex( const Complex & c)
    {
         //  将对象c中的数据成员值复制过来
        m_real = c.m_real;
        m_imag = c.m_imag;
        id=(++counter);
        cout<< " Complex(const Complex&):id= "<<id<< "  from id= "<<c.id<<endl;
    }            
     //  类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象
    Complex( double r)
    {
        m_real = r;
        m_imag =  0.0;
        id=(++counter);
        cout<< " Complex(double):id= "<<id<<endl;
    }
    ~Complex()
    {
        cout<< " ~Complex():id= "<<id<<endl;
    }
     //  等号运算符重载
    Complex & operator=(  const Complex &rhs )
    {
         if (  this == &rhs ) {
             return * this;
        }
         this->m_real = rhs.m_real;
         this->m_imag = rhs.m_imag;
        cout<< " operator=(const Complex&):id= "<<id<< "  from id= "<<rhs.id<<endl;
         return * this;
    }
};
int Complex::counter= 0;
Complex test1( const Complex& c)
{
     return c;
}
Complex test2( const Complex c)
{
     return c;
}
Complex test3()
{
     static Complex c( 1.0, 5.0);
     return c;
}
Complex& test4()
{
     static Complex c( 1.0, 5.0);
     return c;
}
int main()
{
    Complex a,b;

     //  下面函数执行过程中各会调用几次构造函数,调用的是什么构造函数?
    Complex c=test1(a);
    Complex d=test2(a);

    b = test3();
    b = test4();

    Complex e=test2( 1.2);
    Complex f=test1( 1.2);
    Complex g=test1(Complex( 1.2));

}
  下面是程序运行结果: 第一次运行结果
第2次运行结果
第3次运行结果

 四、附录(浅拷贝与深拷贝)

       上面提到,如果没有自定义复制构造函数,则系统会创建默认的复制构造函数,但系统创建的默认复制构造函数只会执行“浅拷贝”, 即将被拷贝对象的数据成员的 值一一赋值给新创建的对象,若该类的数据成员中有指针成员,则会使得新的对象的指针所指向的地址与被拷贝对象的指针所指向的地址相同,delete该指针 时则会导致两次重复delete而出错。下面是示例:
  【浅拷贝与深拷贝】
  #include <iostream.h>
#include <string.h>
class Person 
{
public :
         // 构造函数
        Person(char * pN)
        {
              cout << "一般构造函数被调用 !\n";
              m_pName = new char[strlen(pN) + 1];
              //在堆中开辟一个内存块存放pN所指的字符串
              if(m_pName != NULL) 
              {
                 //如果m_pName不是空指针,则把形参指针pN所指的字符串复制给它
                   strcpy(m_pName ,pN);
              }
        }        
        
        // 系统创建的默认复制构造函数,只做位模式拷贝
        Person(Person & p)    
        { 
                  //使两个字符串指针指向同一地址位置         
                 m_pName = p.m_pName;         
        }
         ~Person( )
        {
                delete m_pName;
        }
  private :

        char * m_pName;
};

void main( )

        Person man("lujun");
        Person woman(man); 
        
        // 结果导致   man 和    woman 的指针都指向了同一个地址
        
        // 函数结束析构时
        // 同一个地址被delete两次
}
// 下面自己设计复制构造函数,实现“深拷贝”,即不让指针指向同一地址,而是重新申请一块内存给新的对象的指针数据成员
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不再指向同一地址了
}

参考地址:http://ticktick.blog.51cto.com/823160/194307

下面讨论一个重要问题是:构造函数的显式调用

 

大家看看下面这段代码的输出结果是什么?这段代码有问题么?

 

    #include <iostream>  
     class CTest  
    {
     public:
        CTest()  
        {  
            m_a =  1;  
        }  
        CTest( int b)  
        {  
            m_b = b;  
            CTest();  
        }  
        ~CTest()  
        {}  
         void show  
        {  
            std::cout << m_a << std::endl;  
            std::cout << m_b << std::endl;  
        }  
      private:  
         int m_a;  
         int m_b;  
    }; 
    void main()  
    {  
        CTest myTest( 2);  
        myTest.show();  
    }

----------------------------------------------------------- 

【分析】
-----------------------------------------------------------

输出结果中,m_a是一个不确定的值,因为没有被赋初值,m_b 为2

注意下面这段代码
CTest(int b)
{
    m_b = b;
    CTest();
}
在调用CTest()函数时,实际上是创建了一个匿名的临时CTest类对象,CTest()中赋值 m_a = 1 也是对该匿名对象赋值,故我们定义的myTest的m_a其实没有被赋值。说白了,其实构造函数并不像普通函数那样进行一段处理,而是创建了一个对象,并 且对该对象赋初值,所以显式调用构造函数无法实现给私有成员赋值的目的。

 这个例子告诉我们以后代码中千万不要出现使用一个构造函数显式调用另外一个构造函数,这样会出现不确定性。其实一些初始化的代码可以写在一个单独的init函数中,然后每一个构造函数都调用一下该初始化函数就行了。

    在此,顺便再提出另外一个问题以供思考:

    CTest *p = NULL;  
     void func()  
    {     
        p =  new CTest();  
    }

 

 代码右边显示调用CTest(),是否依然会产生一个匿名的临时对象a,然后将该匿名的临时对象a的地址赋给指针p? 如果是这样的话,出了func函数后,临时对象a是否会被析构? 那指针p不成为了野指针了?你能解释这个问题么?

答:我实验的结果是不会产生临时对象a,直接将产生的对象指针赋给了p

参考:http://ticktick.blog.51cto.com/823160/294573

 


FROM: http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html





本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++构造函数详解及显式调用构造函数 的相关文章

  • Redis中常用命令:基本+五种基本类型(string、list、hash、set、zset)+三种特殊类型(geospatial、hyperloglog、bitmap)

    redis的命令有很多 命令不区分大小写 如下是一些常用的命令 可以通过官网 命令来学习使用更多的命令 例如 1 基本命令 选择数据库select 编号 redis默认的数据库有16个 编号从0 15 默认使用0号数据库 在配置文件中可查看
  • Elasticsearch(三)使用 Kibana 操作 ES

    文章目录 下载 Kibana 镜像 启动 Kibana 容器 索引 分片和副本 索引 索引分片 索引副本 创建索引 映射 数据结构 字段的数据类型 创建映射 查看映射 添加文档 删除文档 删除索引 下载 Kibana 镜像 docker p
  • Deep Java Library(DJL)上手指南

    Deep Java Library DJL https djl ai 是一个Java语言编写的深度学习库 平台 支持各种已有的模型直接导入 受益于Java的跨平台和高性能 相对Python DJL可以很容易部署和高效率运行 尤其是推理 首先
  • uc3842改可调电源教程_《学习笔记》--DC/DC电源电路设计实例

    1 设计概述 利用LTC3704实现3 3V 1 5V的转换 最大输出电流1A LTC3704是一款支持正向电源电压转换为负向电源电压的DC DC电源芯片 支持的输入端电源电压范围是2 5V 36V 输出端电源电压可调 2 引脚介绍 VIN
  • 若依前端后端框架 分离切换用户问题解决!学不会得找我!!!

    笔者最近遇到一个问题 就是有主账号一个字段 有多个从账号 基于这个目的用户表登录名是从账号得登录名 而有一个字段为主账号 识别是这个人 若依前后端 看了官网 都是用userName去鉴权 然后生成token和JWT数据 所有userName
  • todolist 案例 JavaScript

    css样式 body margin 0 padding 0 font size 16px background CDCDCD header height 50px background 333 background rgba 47 47 4
  • VUE 巧用$attrs和inheritAttrs提高组件的可扩展性

    VUE 巧用 attrs和inheritAttrs提高组件的可扩展性 前言 在平时创建组件时 一般使用的是利用props传值 然后通过传入的值再赋给标签的方式 来控制组件里的 这种方法在使用时的可扩展性不大 很难通过外部来动态的往组件内部添
  • java学习笔记(第7天:形参和返回值)

    目录 一 形参和返回值都是基本类型 二 类名作为形参和返回值 三 抽象类作为形参和返回值 四 接口作为形参和返回值 一 形参和返回值都是基本类型 这里比较简单 放一个不确定参数数量的例子玩玩 package Demo public clas
  • 关于Unity游戏开发场景切换:Time.timeScale的捣乱

    在制作场景切换功能的时候 我用的是SceneManager LoadScene函数 从主界面场景切换到关卡1场景 从关卡1场景切换到关卡2场景都是没有问题的 但是 当我在点击Pause按钮来到暂停界面 点击Back Menu按钮准备回到主界
  • LR和线性回归的区别与联系

    区别 区别 线性回归 liner regression LR logistics regression 构建方法 最小二乘法 似然函数 解决问题 主要解决回归问题 也可以用来分类 但是鲁棒性差 解决分类问题 输出 输出实数域上连续值 输出值
  • 在#define中使用参数

    在 define中使用参数 在 define中使用参数可以创建外形和作用与函数类似的类函数宏 例如 define SQUARE X X X 在程序中可以这样用 z SQUARE 2 这看上去像函数调用 但它的行为与函数调用完全不同 预处理器
  • 网关Gateway-快速上手

    gateway网关官方文档 https docs spring io spring cloud gateway docs current reference html 网关的概念 网关作为流量的入口 常用的功能包括路由转发 权限校验 限流等
  • 单片机:按键(使用中断)控制数码管的数字加减(c语言实现)

    本实验的目的 使用中断实现通过编号为8和C的按键控制数码管数字的加减 加至15之后再循环到0 减到0之后保持0不变 include
  • Python Django项目URL中包含另外一个urls模块

    在我们的项目中 不可能只有一个app 如果把所有的app的views中的视图都放在urls py中进行映射 肯定会让代码显得非常乱 因此django给我们提供了一个方法 可以在app内部包含自己的url匹配规则 而在项目的urls py中再
  • win11 设置系统环境变量

    由于win11的设置面板大变样 一时之间找不到高级设置进入系统环境变量的设置 面对这个问题可以这样 1 按win键 在搜索中输入 编辑系统环境变量 如图
  • 线程同步与线程安全

    1线程同步 同步 多线程访问临界资源时 必须进行同步控制 多进程或者多线程的执行并不完全是绝对的并行运行 又可能主线程需要等待函数线程的某些条件的发生 多线程的临界资源有全局数据 堆区数据 文件描述符 同步控制方式 1 1信号量 需要用到头

随机推荐

  • git repo工具介绍引入

    一 repo是什么 是什么 Repo是基于git的仓库管理工具 是一个python脚本 干什么 Repo用于同时管理多个git仓库 可以做统一的上传下载等操作 二 repo使用相关语法介绍 2 1 要使用repo首先需要有 manifest
  • iOS 捷径大全

    iOS 捷径大全 一 实用工具 支付助手3 0 新 微博热搜榜 新浪微博 网购历史价格查询3 0 小火箭 新 支付宝红包 新 任天堂红白机小游戏 你不会自己百度么 上朝网易云 早上好 晚安 天气预报 系统自带 捷径2 1 0 报时语音天气
  • JS数组去重之利用set数据结构去重

    在常用的JS去重方法中 都是通过循环遍历来去重 难免麻烦了不少 这边发现ES6中有更加方便的去重方式 记录一下 1 set数据结构 ES6提供了新的数据结构Set 类似于数组 只不过其成员值都是唯一的 没有重复的值 这边就是利用set没有重
  • RF4463F30半双工模组,伪全双工透传方案(STM32平台)(第二章,业务逻辑)

    RF4463F30半双工模组 伪全双工透传方案 STM32平台 第二章 业务逻辑 前言 核心代码编写 宏定义和变量声明 工具函数 功能函数 发送数据函数 时间管理函数 模块工作状态函数 将数据帧提取放入发送区的函数 接收数据函数 解包函数
  • 售前工程师工作内幕揭秘:面试实战技巧

    售前工程师工作内幕揭秘 面试实战技巧 前言 一 售前面试问题 基本就下面这些 二 售前工程师岗位普遍误区 三 售前工程师核心技能 四 面试中 主动出击 才是王道 五 对行业的了解是做好售前的基础 前言 看到网上很多关于售前工程师面试技巧的内
  • react项目搭建--相对较全面

    前言 简单记录一下第一次搭建react项目框架 之前只是在已搭建好的框架内去开发实现项目 没有自己动手搭建过 亲自动手实际操作 还遇到挺多问题的 参考了不少文献 帮助我解决并完成整个项目搭建的文献 都会在文章最后附上链接 感兴趣的可以都看一
  • 网络安全之数据链路层安全协议

    前言 本章将会讲解数据链路层上的安全协议 目录 前言 一 数据链路层安全协议简介 1 数据链路安全性 二 局域网数据链路层协议 1 本地链路局域网 LAN 2 广域网 WAN 3 IEEE802局域网数据链路层协议 1 数据链路层包括逻辑链
  • Python爬虫-17-案例:利用爬虫框架scrapy ,爬取JavaScript动态加载网页,将图片下载至本地

    新建项目 1 在cmd中创建爬虫项目 2 项目结构 由于基础模板设置这里会默认新建一个images py文件 不设置的话这边可以直接写也是可以的 3 设置settings 图片下载地址分析 1 查看萌女最新选项的图片 2 查看请求信息 发现
  • 新建android studio项目错误,提示AppcompatActivity有问题

    直接上图解决办法 希望后来者少走弯路
  • LAMMPS-使用MATLAB计算径向分布函数

    LAMMPS 使用MATLAB计算径向分布函数 在分子动力学模拟中 径向分布函数 Radial Distribution Function 简称RDF 是一种重要的统计工具 用于描述原子或分子之间的空间分布情况 通过计算RDF 我们可以获得
  • 腾讯云TencentOS Server3.1(TK4)系统镜像如何?兼容CentOS

    腾讯云服务器TencentOS Server系统镜像即Tencent Linux 腾讯云研发的Linux操作系统 TencentOS Server免费使用 在CentOS上开发的应用程序可直接在TencentOS Server上运行 Lin
  • 指令和数据都用二进制代码存放在内存中,从时空观角度回答CPU如何区分读出的代码是指令还是数据

    指令用来确定 做什么 和 怎样做 数据是 做 的时候需要原始数 计算机可以从时间和空间两方面来区分指令和数据 在时间上 取指周期从内存中取出的是指令 而执行周期从内存取出或往内存中写入的是数据 在空间上 从内存中取出指令送控制器 而执行周期
  • 父类,子类与继承

    1 父类 子类 对于父类 就像一种包含关系 父类中的所有的公开的都可以给子类 而子类中特有的公开属性 父类没有 例 对于人这个类为学生类的父类 人有的属性 学生也有 但学生有职业为学生这个特有属性 父类可以有很多子类 即人这个类也可以是工人
  • 移动开发者大会次日观感

    作者 朱金灿 来源 http blog csdn net clever101 继续是2011移动开发者大会的观感 第二天是进行的主题论坛 分别有下面一些主题 开放平台与技术 产品与设计 移动游戏 推广与盈利 电子商务 创业投资与商业模式和新
  • AD PCB画图透明度

    0 透明度 默认 40 透明度 较好 70 透明度
  • ORM-c++解析数据库

    C 的 ORM 框架 OOS http www open open com lib view open1377143228741 html
  • JAVA 利用集合完成斗地主的洗牌 发牌 排序 看牌功能

    斗地主 出牌规则大小 小的在前面 大的在后面 规则 黑红梅方 3 K 2 小王 大王 import java util public class DouDiZhuSort public static void main String arg
  • 面试题创作0004,请结合您对Linux的认识,阐释命令行状态下,执行ls / 会获得的根文件系统下的文件夹这一现象。

    请结合您对Linux的认识 阐释命令行状态下 执行ls 会获得的根文件系统下的文件夹这一现象 提示 此题目是想考察面试者是否有闲暇去充分理解console tty ls inode VFS rs232 bash等概念的综合理解 说明白的同学
  • 蓝桥杯每日一题——基础练习·水仙花数

    输入 输入一个三位数 若每一位的立方相加后等于该数 则称这个数为水仙花数 输出 如果该数是水仙花数则输出yes 否则输出no import math 涉及到计算的函数需要导入math库 num int input 请输入一个三位数 b nu
  • C++构造函数详解及显式调用构造函数

    c 类的构造函数详解 一 构造函数是干什么的 class Counter public 类Counter的构造函数 特点 以类名作为函数名 无返回类型 Counter m value 0 private 数据成员 int m value 该