C++ 中构造函数的实例详解

C++ 中构造函数的实例详解

c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初学者有所帮助。

1. 构造函数是干什么的

class Counter
{

public:
  // 类Counter的构造函数
  // 特点:以类名作为函数名,无返回类型
  Counter()
  {
    m_value = 0;
  }

private:
  // 数据成员
 int m_value;
}

该类对象被创建时,编译系统对象分配内存空间,并自动调用该构造函数->由构造函数完成成员的初始化工作

eg: Counter c1;

编译系统为对象c1的每个数据成员(m_value)分配内存空间,并调用构造函数Counter()自动地初始化对象c1的m_value值设置为0

故:构造函数的作用:初始化对象的数据成员。

2. 构造函数的种类

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_img = c.m_img;
  }      

  // 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象
  // 例如:下面将根据一个double类型的对象创建了一个Complex对象
  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;
  }
};

下面使用上面定义的类对象来说明各个构造函数的用法:

void 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    

}

3. 思考与测验

(1) 为什么函数中可以直接访问对象c的私有成员 ?

Complex(const Complex & c)
{
  // 将对象c中的数据成员值复制过来
  m_real = c.m_real;
  m_img = c.m_img;
}

(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 )) 呢?
}

4. 浅拷贝与深拷贝

上面提到,如果没有自定义复制构造函数,则系统会创建默认的复制构造函数,但系统创建的默认复制构造函数只会执行“浅拷贝”,即将被拷贝对象的数据成员的值一一赋值给新创建的对象,若该类的数据成员中有指针成员,则会使得新的对象的指针所指向的地址与被拷贝对象的指针所指向的地址相同,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不再指向同一地址了
}

如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

时间: 2017-10-14

C++中strstr函数的实现方法总结

C++中strstr函数的实现方法总结 函数说明: 包含文件:string.h 函数名: strstr 函数原型:extern char *strstr(char *str1, char *str2); 功能:从字符串str1中查找是否有字符串str2, 如果有,从str1中的str2位置起,返回str1的指针,如果没有,返回null. 返回值:返回该位置的指针,如找不到,返回空指针. 方法一: #include <iostream> #include <assert.h> usi

C++ 中boost::share_ptr智能指针的使用方法

C++ 中boost::share_ptr智能指针的使用方法 最近项目中使用boost库的智能指针,感觉智能指针还是蛮强大的,在此贴出自己学习过程中编写的测试代码,以供其他想了解boost智能指针的朋友参考,有讲得不正确之处欢迎指出讨论.当然,使用boost智能指针首先要编译boost库,具体方法可以网上查询,在此不再赘述. 智能指针能够使C++的开发简单化,主要是它能够自动管理内存的释放,而且能够做更多的事情,即使用智能指针,则可以再代码中new了之后不用delete,智能指针自己会帮助你管理

C++ 中Vector常用基本操作

标准库vector类型是C++中使用较多的一种类模板,vector类型相当于一种动态的容器,在vector中主要有一些基本的操作,下面通过本文给大家介绍,具体内容如下所示: (1)头文件#include<vector>. (2)创建vector对象,vector<int> vec; (3)尾部插入数字:vec.push_back(a); (4)使用下标访问元素,cout<<vec[0]<<endl;记住下标是从0开始的. (5)使用迭代器访问元素. vect

C++中函数指针详解及代码分享

函数指针 函数存放在内存的代码区域内,它们同样有地址.如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,如同数组的名字就是数组的起始地址. 1.函数指针的定义方式:data_types (*func_pointer)( data_types arg1, data_types arg2, ...,data_types argn); c语言函数指针的定义形式:返回类型 (*函数指针名称)(参数类型,参数类型,参数类型,-); c++函数指针的定义形式:返回类型 (类名

C++计算图任意两点间的所有路径

基于连通图,邻接矩阵实现的图,非递归实现. 算法思想: 设置两个标志位,①该顶点是否入栈,②与该顶点相邻的顶点是否已经访问. A 将始点标志位①置1,将其入栈 B 查看栈顶节点V在图中,有没有可以到达.且没有入栈.且没有从这个节点V出发访问过的节点 C 如果有,则将找到的这个节点入栈,这个顶点的标志位①置1,V的对应的此顶点的标志位②置1 D 如果没有,V出栈,并且将与v相邻的全部结点设为未访问,即全部的标志位②置0 E 当栈顶元素为终点时,设置终点没有被访问过,即①置0,打印栈中元素,弹出栈顶

C++中构造函数的参数缺省的详解

C++中构造函数的参数缺省的详解 前言: 构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值.在构造函数中也可以采用这样的方法来实现初始化. #include <iostream> using namespace std; class A { public : A(int aa=0,int bb=00); //在声明构造函数时指定默认参数 int volume( ); int a; int b; }; int main( ) { A

对python3 中方法各种参数和返回值详解

如下所示: # -*- coding:utf-8 -*- # Author: Evan Mi # 函数 def func1(): print('in the func1') return 0 # 过程 def func2(): print('in the func2') """ 多个值用逗号分割后返回,会分装到一个tuple中返回, 接收的时候,如果使用一个变量接收,那么这个接收变量就是一个tuple类型的 如果接收的时候也用逗号分割多个值来接收,那么可以分别对应返回tupl

C++中构造函数与析构函数的调用顺序详解

前言 在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序.在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用. 简单来说,其构造函数的顺序就一句话: 基类构造函数 -> 成员的构造函数 -> 构造函数体内语句 看下面一个代码示例: #include <iostream> using namespace std; class A { publ

ES6中的rest参数与扩展运算符详解

前言 本文主要给大家介绍了关于ES6中rest参数与扩展运算符的相关内容,rest参数和扩展运算符都是ES6新增的特性.rest参数的形式为:...变量名:扩展运算符是三个点(...).下面话不多说了,来一起看看详细的介绍: rest参数 rest参数用于获取函数的多余参数,这样就不需要使用arguments对象了.rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中. function add(...values) { let sum = 0; for (var val of val

jQuery中通过ajax调用webservice传递数组参数的问题实例详解

下面通过实例给大家说明比较直观些,更方便大家了解. 本人的项目中通过jquery.ajax调用webservice. 客户端代码如下: $.ajax({ url: "test/xxx.asmx", type: 'POST', dataType: 'xml', timeout: , data: { name: "zhangsan", tags: ["aa", "bb", "cc"] }, error: fun

python 函数中的内置函数及用法详解

今天来介绍一下Python解释器包含的一系列的内置函数,下面表格按字母顺序列出了内置函数: 下面就一一介绍一下内置函数的用法: 1.abs() 返回一个数值的绝对值,可以是整数或浮点数等. print(abs(-18)) print(abs(0.15)) result: 18 0.15 2.all(iterable) 如果iterable的所有元素不为0.''.False或者iterable为空,all(iterable)返回True,否则返回False. print(all(['a','b',

java使用FFmpeg合成视频和音频并获取视频中的音频等操作(实例代码详解)

FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序. ffmpeg命令参数如下: 通用选项 -L license -h 帮助 -fromats 显示可用的格式,编解码的,协议的... -f fmt 强迫采用格式fmt -I filename 输入文件 -y 覆盖输出文件 -t duration 设置纪录时间 hh:mm:ss[.xxx]格式的记录时间也支持 -ss position 搜索到指定的时间 [-]hh:mm:ss[.xxx]的格式也支持 -title

C#中WPF ListView绑定数据的实例详解

C#中WPF ListView绑定数据的实例详解 WPF中ListView用来显示数据十分方便, 我们可以将它分成几个列,每一个列用来显示一条数据,但是又是在一方之中. 那么怎样实现这样的效果的呢,这就要用绑定了. 我们先来看一看他的xmal代码 <ListView Name="receiveList" Grid.Row="0"> <ListView.View> <GridView> <GridView.Columns>

MongoDB 中Limit与Skip的使用方法详解

MongoDB 中Limit与Skip的使用方法详解 一 MongoDB Limit() 方法 如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数. 语法 limit()方法基本语法如下所示: >db.COLLECTION_NAME.find().limit(NUMBER) 实例 > db.col.find({},{"title":1,_id:0}).li

Python 中 Virtualenv 和 pip 的简单用法详解

本文介绍了Python 中 Virtualenv 和 pip 的简单用法详解,分享给大家,具体如下: 0X00 安装环境 我们在 Python 开发和学习过程中需要用到各种库,然后在各个不同的项目和作品里可能用的版本还不一样,正因为有这种问题的存在才催生了virtualenv的诞生.virtualenv 可以在电脑上创建一个虚拟环境,可以针对每一个项目创建一个虚拟环境,这样就不用担心各个不同的项目用不同版本的库的时候出现的冲突了. 下面的内容只适用于 Linux/OSX,未经 Windows 环