c++中的类之构造函数
一、构造函数的缘由
本文我们主要来讲解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++中的类之构造函数的更多相关文章
- OOP3(继承中的类作用域/构造函数与拷贝控制/继承与容器)
当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内.如果一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义 在编译时进行名字查找: 一个对象.引用或指针的 ...
- c++中的类(构造函数,析构函数的执行顺序)
类对象的初始化顺序 新对象的生成经历初始化阶段(初始化列表显式或者隐式的完成<这部分有点像java里面的初始化块>)——> 构造函数体赋值两个阶段 1,类对象初始化的顺序(对于没有父 ...
- 关于c++11中static类对象构造函数线程安全的验证
在c++11中,static静态类对象在执行构造函数进行初始化的过程是线程安全的,有了这个特征,我们可以自己动手轻松的实现单例类,关于如何实现线程安全的单例类,请查看c++:自己动手实现线程安全的c+ ...
- TypeScript完全解读(26课时)_8.ES6精讲-ES6中的类(进阶)
8.TypeScript完全解读-ES6精讲-类(进阶) 在index.ts内引入 Food创建的实例赋值给Vegetabled这个原型对象,这样使用Vegetables创建实例的时候,就能继承到Fo ...
- scala中的面向对象定义类,构造函数,继承
我们知道scala中一切皆为对象,函数也是对象,数字也是对象,它是一个比java还要面向对象的语言. 定义scala的简单类 class Point (val x:Int, val y:Int) 上面 ...
- Qt中新建类构造函数的初始化参数列表
使用Qt-creator自动生成一个窗体应用程序时会自动创建一个新的类,我的程序中名为MyDialog,类的定义为: #ifndef MYDIALOG_H #define MYDIALOG_H #in ...
- scala入门教程:scala中的面向对象定义类,构造函数,继承
我们知道scala中一切皆为对象,函数也是对象,数字也是对象,它是一个比java还要面向对象的语言. 定义scala的简单类 class Point (val x:Int, val y:Int) 上面 ...
- C#中派生类调用基类构造函数用法分析
这里的默认构造函数是指在没有编写构造函数的情况下系统默认的无参构造函数 1.当基类中没有自己编写构造函数时,派生类默认的调用基类的默认构造函数例如: ? 1 2 3 4 5 6 7 8 9 10 11 ...
- C++-什么时候需要在类的构造函数中使用初始化列表
1,如果基类没有default构造函数,则意味着其不能自己初始化.如果其被派生,派生类的构造函数要负责调用基类的构造函数,并传递给它需要的参数.下例中Base 2,如果类成员没有默认构造函数.下例中E ...
随机推荐
- pycharm中的光标变粗的问题
pycharm中的光标变粗后不能自动打成对的小括号和中括号及引号---->解决办法:按下insert键 按下:insert键进行切换
- Linux中安装字体
Linux中安装字体 查看系统中的字体 fc-list 查看系统中的中文字体 fc-list :lang=zh将然后将字体文件拷贝到/usr/share/fonts/中 cp aa.ttl /usr/ ...
- Python基础-week02
本节内容摘要:http://www.cnblogs.com/Jame-mei 0.xmind8破解及安装(想要破解版及资料请下方留言,这里不做具体说明) 1.模块初识 2.pyc是什么? 3.pyth ...
- 20165230 《Java程序设计》第1周学习总结
20165230 2017-2018-2 <Java程序设计>第1周学习总结 教材学习内容总结 本周通过学习了解了java的历史,地位,特点以及java的应用和基本的开发步骤,对Java有 ...
- 使用 win10 的正确姿势
17年9月初,写了第一篇<使用 win10 的正确姿势>,而现在半年多过去,觉得文章得更新一些了,索性直接来个第二版吧. -----2018.3.24 写 一. 重新定义桌面 我的桌面: ...
- Linux 如何使用echo指令向文件写入内容
0.前言 本文总结如何使用echo命令向文件中写入内容,例如使用echo指令覆盖文件内容,使用echo指令向文件追加内容,使用echo指令往文件中追加制表符. echo向文件中输出内容 ...
- Laravel 中缓存驱动的速度比较
缓存是web开发中重要的一部分,我相信很多人和我一样,经常忽略这个问题. 随着工作经验的累积,我已经意识到缓存是多么的重要,这里我通过 Scotch 来解释一下它的重要性. 通过观察发现,Scotch ...
- PHP微信公众平台oauth2.0网页授权登录类的封装demo
一.微信授权使用的是OAuth2.0授权的方式.主要有以下简略步骤: 第一步:用户同意授权,获取code 第二步:通过code换取网页授权access_token 第三步:拉取用户信息(需scope为 ...
- 在Python中使用Redis
在Python中要使用Redis数据库,首先要安装redis 之前的博客中有写到在命令行模式下操作Redis数据库. 要在项目中使用的话可以这么做: 通过初始化 redis.Redis,得到返回的对象 ...
- Beta 第七天
今天遇到的困难: 构造新适配器的时候出现了某些崩溃的问题 ListView监听器有部分的Bug 今天完成的任务: 陈甘霖:完成相机调用和图库功能,完成阿尔法项目遗留下来的位置调用问题,实现百度定位 蔡 ...