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不再指向同一地址了
}

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

(0)

相关推荐

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

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

  • 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++中构造函数的参数缺省的详解

    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

  • 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++计算图任意两点间的所有路径

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

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

    C++ 中构造函数的实例详解 c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初学者有所帮助. 1. 构造函数是干什么的 class Counter { public: // 类Counter的构造函数 // 特点:以类名作为函数名,无返回类型 Counter() { m_value = 0; } private: // 数据成员 int m_va

  • Java中File的实例详解

    Java中File的实例详解 File 代表文件或者目录的类 构造函数 File(File parent,String child)---代表了指定父目录下的指定的子文件或者子目录 File(String pathname)---代表了指定路径对应的文件或者目录对象 重要方法 创建 createNewFile()---只能用来创建文件,并且一次只能创建一个文件,要求文件存储的目录必须真实存在 mkdir()---只能用来创建目录,不能用来创建多层目录 mkdirs()---创建多层目录 删除 d

  • javascript 中的继承实例详解

    javascript 中的继承实例详解 阅读目录 原型链继承 借用构造函数 组合继承 寄生组合式继承 后记 继承有两种方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法. 由于函数没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且实现继承主要依靠原型链来实现. 下面介绍几种js的继承: 原型链继承 原型链继承实现的本质是重写原型对象,代之以一个新类型的实例.代码如下: function SuperType() { this.pr

  • Java 重载、重写、构造函数的实例详解

    Java 重载.重写.构造函数的实例详解 方法重写 1.重写只能出现在继承关系之中.当一个类继承它的父类方法时,都有机会重写该父类的方法.一个特例是父类的方法被标识为final.重写的主要优点是能够定义某个子类型特有的行为. class Animal { public void eat(){ System.out.println ("Animal is eating."); } } class Horse extends Animal{ public void eat(){ Syste

  • C++11智能指针中的 unique_ptr实例详解

    在前面一篇文章中,我们了解了 C++11 中引入的智能指针之一 shared_ptr 和 weak_ptr ,今天,我们来介绍一下另一种智能指针 unique_ptr . 往期文章参考: [C++11新特性] C++11 智能指针之shared_ptr [C++11新特性] C++11智能指针之weak_ptr unique_ptr介绍 unique是独特的.唯一的意思,故名思议,unique_ptr可以"独占"地拥有它所指向的对象,它提供一种严格意义上的所有权. 这一点和我们前面介绍

  • Angularjs中数据绑定的实例详解

    Angularjs中数据绑定的实例详解 这是一个最简单的angularjs的例子,关于数据绑定的,大家可以执行一下,看看效果 <html ng-app> <head> <title>angularjs-include</title> <script type="text/javascript" src="js/angular/angular.min.js"></script> </head

  • JSP Spring配置文件中传值的实例详解

    JSP Spring配置文件中传值的实例详解 通过spring提供方法,在配置文件中取传值 调用get方法  targetObject :指定调用的对象       propertyPath:指定调用那个getter方法 例1: public class Test1 { private String name = "nihao"; public String getName() { return name; } } Xml代码 <bean id="t1" cl

  • Linux 在Shell脚本中使用函数实例详解

    Linux 在Shell脚本中使用函数实例详解 Shell的函数 Shell程序也支持函数.函数能完成一特定的功能,可以重复调用这个函数. 函数格式如下: 函数名() { 函数体 } 函数调用方式: 函数名 参数列表 实例:编写一函数add求两个数的和,这两个数用位置参数传入,最后输出结果. root@ubuntu:/home/study# vi test3 #!/bin/bash add(){ a=$1; b=$2; z=`expr $a + $b`; echo "The sum is $z&

  • java 中匿名内部类的实例详解

    java 中匿名内部类的实例详解 原来的面貌: class TT extends Test{ void show() { System.out.println(s+"~~~哈哈"); System.out.println("超级女声"); } TT tt=new TT(); tt.show(); 只是说我们这里采用的是匿名的形式来处理. 重写了Test的show()方法,在重写好了以后,又调用了重写后的show()方法 实现代码: package cn.com; c

  • IOS 开发之swift中手势的实例详解

    IOS 开发之swift中手势的实例详解 手势操作主要包括如下几类 手势 属性 说明 点击 UITapGestureRecognizer numberOfTapsRequired:点击的次数:numberOfTouchesRequired:点击时有手指数量 设置属性 numberOfTapsRequired 可以实现单击,或双击的效果 滑动 UISwipeGestureRecognizer direction:滑动方向 direction 滑动方向分为上Up.下Down.左Left.右Right

随机推荐