一、构造函数的缘由

  本文我们主要来讲解c++中类的构造函数,其中涉及了深拷贝和浅拷贝的问题,这也是在面试笔试中经常会碰到的问题。如果您是第一次听说构造函数,可能会觉得这个名字有点高大上,而它却和实际中的工程问题有关。在正式的讲解前,我们先来思考一个问题:(注意,本文采用的课件来源于狄泰软件学院,感谢狄泰的唐老师!)

  

 #include <stdio.h>

 class Test
{
private:
int i;
int j;
public:
int getI() { return i; }
int getJ() { return j; }
}; Test gt; // 全局变量放在bss段 int main()
{
// 访问全局变量gt的的成员变量值
printf("gt.i = %d\n", gt.getI());
printf("gt.j = %d\n", gt.getJ()); Test t1;
// 访问局部变量t1的的成员变量值
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ()); Test* pt = new Test;
// 访问堆空间pt的的成员变量值
printf("pt->i = %d\n", pt->getI());
printf("pt->j = %d\n", pt->getJ()); delete pt; // 注意释放堆空间 return ;
}

成员变量的初始值

  通过这个简单的例子,我们发现,同样是声明一个类的对象,因为对象所在的存储空间不同(bss段、堆空间、栈),导致其对象的成员变量的初始值不相同。对于我们类的使用者来说,我们当然不希望出现这种情况。于是,我们希望在定义一个类的对象的同时,初始化其成员变量的值,统一化,不管是在bss段,堆空间、栈上。

  

因此,我们可以提供下面的解决方案:

  

这样的方式虽然可以解决我们之前遇到的问题,但是在实际的使用过程中会觉得不好用。

  

为此,c++的设计者想出了一个办法,即构造函数:

  

注意,构造函数必须满足: 1. 与类的名字相同   2.无返回值

二、有参构造函数和重载构造函数

  

  

注意这里的初始化方式,即“ ()“” 和“ = ”。

  

在创建一个对象数组的时候,我们可以手工调用构造函数来初始化该对象数组。

 #include <stdio.h>

 class Test
{
private:
int m_value;
public:
Test()
{
printf("Test()\n"); m_value = ;
}
Test(int v)
{
printf("Test(int v), v = %d\n", v); m_value = v;
}
int getValue()
{
return m_value;
}
}; int main()
{
Test ta[] = {Test(), Test(), Test()}; // 手工调用构造函数 for(int i=; i<; i++)
{
printf("ta[%d].getValue() = %d\n", i , ta[i].getValue());
} // int i(100); // 这样的方式是初始化i
Test t = Test(); // 初始化方式 printf("t.getValue() = %d\n", t.getValue()); return ;
}

创建对象数组

三、无参构造函数和拷贝构造函数

下面来介绍两个特殊的构造函数:

  

  

注意:没有构造函数是指连拷贝构造函数都没有。

3.2 深拷贝和浅拷贝

  

浅拷贝只是简单的成员变量复制。我们可以看下面的例子:

 #include <stdio.h>

 class Test
{
private:
int i;
int j;
int* p;
public:
int getI()
{
return i;
}
int getJ()
{
return j;
}
int* getP()
{
return p;
}
Test(const Test& t)
{
i = t.i;
j = t.j; /* 这里是浅拷贝,p和t.p所指向的内存空间相同
*/
// p = t.p ; /* 注意这里是深拷贝,与浅拷贝不同的是,重新在堆空间申请内存空间,
* 并将该空间的值和t.p的相同,这样就可以使得对象的逻辑状态相同
*/
p = new int;
*p = *t.p;
}
Test(int v)
{
i = ;
j = ;
p = new int;
*p = v;
}
void free()
{
delete p;
}
}; int main()
{
Test t1();
Test t2(t1); printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP());
printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP()); t1.free(); // 释放两次,如果是浅拷贝会出问题,而深拷贝不会
t2.free(); return ;
}

深拷贝和浅拷贝

  

既然我们已经了解了深拷贝和浅拷贝的区别,那么什么时候我们该使用深拷贝呢?

  (即申请堆空间、打开文件、网络端口等操作)

  


四、初始化列表

  在正式讲解初始化列表前,我们先来思考一个问题:

    

    

实际上,我们会发现这个类并没有给成员变量ci一个初始值,所以会出错。那么我们怎么样为const 成员变量初始化一个值呢?这里就引入了初始化列表的概念。如下:

    

可以看到初始化列表是在构造函数函数名之后加上一个冒号,之间用逗号隔开,m1、m2、m3是代表类的成员变量,括号里面的是对应的初始值。需要注意的是:

  

 #include <stdio.h>

 class Value
{
private:
int mi;
public:
Value(int i)
{
printf("i = %d\n", i);
mi = i;
}
int getI()
{
return mi;
}
}; class Test
{
private:
// 成员变量的初始化顺序和成员变量的声明顺序有关,而与初始化列表中的位置无关
Value m2;
Value m3;
Value m1;
public:
Test() : m1(), m2(), m3()
{
//由于初始化列表先于构造函数执行,所以这一句代码最后执行
printf("Test::Test()\n");
}
}; int main()
{
Test t; return ;
}

成员变量的初始化顺序

  我们再回到之前的那个问题:

  

一个小问题:

  

  (注意,本文采用的课件来源于狄泰软件学院,感谢狄泰的唐老师!)

c++中的类之构造函数的更多相关文章

  1. OOP3(继承中的类作用域/构造函数与拷贝控制/继承与容器)

    当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内.如果一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义 在编译时进行名字查找: 一个对象.引用或指针的 ...

  2. c++中的类(构造函数,析构函数的执行顺序)

    类对象的初始化顺序 新对象的生成经历初始化阶段(初始化列表显式或者隐式的完成<这部分有点像java里面的初始化块>)——> 构造函数体赋值两个阶段 1,类对象初始化的顺序(对于没有父 ...

  3. 关于c++11中static类对象构造函数线程安全的验证

    在c++11中,static静态类对象在执行构造函数进行初始化的过程是线程安全的,有了这个特征,我们可以自己动手轻松的实现单例类,关于如何实现线程安全的单例类,请查看c++:自己动手实现线程安全的c+ ...

  4. TypeScript完全解读(26课时)_8.ES6精讲-ES6中的类(进阶)

    8.TypeScript完全解读-ES6精讲-类(进阶) 在index.ts内引入 Food创建的实例赋值给Vegetabled这个原型对象,这样使用Vegetables创建实例的时候,就能继承到Fo ...

  5. scala中的面向对象定义类,构造函数,继承

    我们知道scala中一切皆为对象,函数也是对象,数字也是对象,它是一个比java还要面向对象的语言. 定义scala的简单类 class Point (val x:Int, val y:Int) 上面 ...

  6. Qt中新建类构造函数的初始化参数列表

    使用Qt-creator自动生成一个窗体应用程序时会自动创建一个新的类,我的程序中名为MyDialog,类的定义为: #ifndef MYDIALOG_H #define MYDIALOG_H #in ...

  7. scala入门教程:scala中的面向对象定义类,构造函数,继承

    我们知道scala中一切皆为对象,函数也是对象,数字也是对象,它是一个比java还要面向对象的语言. 定义scala的简单类 class Point (val x:Int, val y:Int) 上面 ...

  8. C#中派生类调用基类构造函数用法分析

    这里的默认构造函数是指在没有编写构造函数的情况下系统默认的无参构造函数 1.当基类中没有自己编写构造函数时,派生类默认的调用基类的默认构造函数例如: ? 1 2 3 4 5 6 7 8 9 10 11 ...

  9. C++-什么时候需要在类的构造函数中使用初始化列表

    1,如果基类没有default构造函数,则意味着其不能自己初始化.如果其被派生,派生类的构造函数要负责调用基类的构造函数,并传递给它需要的参数.下例中Base 2,如果类成员没有默认构造函数.下例中E ...

随机推荐

  1. 设计模式——简单工厂模式(C++实现)

    #include <iostream> #include <string> using namespace std; class COperator { public: ; p ...

  2. 检查硬件变化的命令kudzu

    当新加一个硬件时,系统并没有出现,可以通过这个命令来检查下:

  3. java 三种工厂模式

    一.简单工厂模式 一个栗子: 我喜欢吃面条,抽象一个面条基类,(接口也可以),这是产品的抽象类. public abstract class INoodles { /** * 描述每种面条啥样的 */ ...

  4. poj-1005-l tanink i need a houseboat

    Description Fred Mapper is considering purchasing some land in Louisiana to build his house on. In t ...

  5. 开源一个定时任务调度器 webscheduler

    在企业应用中定时任务调度的需求是必不可少的,比如定时同步数据,定时结转数据,定时检测异常等等.公司之前是在使用一款采用.net 开发的windows服务形式的定时程序,基本能满足需求,在一段时间的时候 ...

  6. 【JS】 Javascript与BOM的互动 寻路

    JS BOM 之前提到过JS和DOM之间的互动方法.而BOM(Browser Object Module)是浏览器的对象模型,它也可以和JS进行互动.也就是说,JS还可以和浏览器进行互动.因为现代主流 ...

  7. Android学习笔记2——shape

    Android有很多特别的xml文件,如常用的selector.style以及shape,熟练使用这些xml可以是我们的项目变得更个性化. 一.子标签(corners.gradient.padding ...

  8. Oracle安装11.2.0.4.180116补丁及如何检查数据库安装补丁

    最近做了一个安装11.2.0.4.180116补丁的实验,突然想起之前和同事讨论的一个问题:如何检查数据库安装补丁的版本,之前搜到的是去查dba_registry_history,有的说在操作系统中执 ...

  9. 庖丁解牛Linux内核学习笔记(1)--计算机是如何工作的

    存储程序计算机模型 冯诺依曼体系结构 冯诺依曼体系结构是存储程序计算机,什么叫存储程序计算机?从硬件角度说,假设有cpu和内存,两者通过总线连接,在cpu内部有一个寄存器叫ip(instruction ...

  10. 从0开始的LeetCode生活—001-Two Sum

    题目: Given an array of integers, return indices of the two numbers such that they add up to a specifi ...