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 ...
随机推荐
- Ext简单demo示例
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...
- 解决html5 canvas 绘制字体、图片与图形模糊问题
html5 canvas 绘制字体.图片与图形模糊问题 发生情况 多出现在高dpi设备,这意味着每平方英寸有更多的像素,如手机,平板电脑.当然很多高端台式电脑也有高分辨率高dpi的显示器. canva ...
- Proxy和Reflect
原因 最近在写 unar.js(一个技术超越react angular vue)的mvvm库.通过研究proxy的可行性.故作以下研究 Proxy代理一个函数 var fn=function(){ c ...
- 列表生成式、生成器&迭代器
一.列表生成式 先有列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表里的每个值加1,怎么实现? 方法一: a = [0, 1, 2, 3, 4, 5, 6, 7, 8, ...
- IntelliJ Idea常用的快捷键
以下是IntelliJ Idea2017版的,基本上不会有什么变化 IntelliJ Idea这个编译器现在非常流行,他强大的快捷键非常好用,相比于eclipse,IntelliJ Idea界面也比e ...
- Eclipse CDT开发环境搭建及问题记录(Windows)
这两天在整Eclipse,在此记录过程中遇到的一些问题. 1.安装JDK,配置系统路径: 2.下载Eclipse 直接访问Eclipse官网(https://www.eclipse.org/downl ...
- 笔记:Maven 插件及配置详解
插件使用配置 用户能够自己选择将某个插件目标绑定到生命周期的某个阶段上,例如创建项目的源码JAR包,内置的插件绑定关系并没有涉及这个任务,mavent-source-plugin 可以帮助我们完成该任 ...
- thinkphp5多图上传 js部分
在项目中常会用到多图上上传,那就需要多图上传后需要预览,并且能删掉传错(不想传)的图,然而 测试了半天 并不知道jq怎么写,parent()parents()用了半天无果,罢了,还是用原生js来写.这 ...
- linux --> fork()详解
fork()详解 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个 ...
- web语义化之SEO和ARIA
在快速理解web语义化的时候,只知道web语义化有利于SEO和便于屏幕阅读器阅读,但并不知道它是如何有利于SEO和便于阅读器阅读的,带着这个疑问,进行了一番探索总结. SEO 什么是SEO? SEO( ...