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 ...
随机推荐
- php基础知识(三)---常用函数--2017-04-16
常用函数如下:(红色为重点) 1.取字符串的长度 echo strlen("hello"); 2.echo strcmp("字符串1","字符串2&q ...
- Linux目录结构和基础知识
目录结构: /bin:存放系统常用的命令程序 /boot:系统启动或引导所需要的一些文件 /dev:可用的设备文件 /etc:系统配置相关的东西 /home:所有用户的主目录 /lib,lib64:存 ...
- 二叉树遍历等基本操作(Java实现)
前中后序遍历递归实现+层序遍历: 树的结点类代码: public class TreeNode<Value extends Comparable<? super Value>> ...
- 【数据库】mysql数据库索引
文章归属:http://feiyan.info/16.html,我想自己去写了,但是发现此君总结的非常详细.直接搬过来了 关于MySQL索引的好处,如果正确合理设计并且使用索引的MySQL是一辆兰博基 ...
- 源码实现 --> itoa函数实现
itoa函数实现 itoa()函数的功能是将一个整数转换为一个字符串 例如12345,转换之后的字符串为"12345",-123转换之后为"-123",欢迎大家 ...
- Algorithm --> 并查集
并查集 主要解决图的连通性问题,比如: 1.随意给你两个点,让你判断它们是否连通: 2.问你整幅图一共有几个连通分支: 初始化: void init(int size) { ; i < size ...
- DotNetBar控件,superTabControl多文档界面(MDI)的风格、多Tab的创建方法
最近在做一个条码系统的项目,第一次用dotnetbar控件,,查找资料时看到伍老师一篇文章(链接),但是没有关于加载tab窗口的方法,自己摸索做了一个此类风格出来,并写了一个通用方法,供大家参考. 风 ...
- centos7环境下mysql5.7的安装与配置
最近无事闲来折腾虚拟机,以前都是折腾云服务器,现在自己捣捣.看到mysql的教程蛮好的,准备做个笔记.原文来自mysql5.7的安装与配置(centos7环境) 第一步:下载mysql [root@M ...
- echarts词云引用
最近项目中需要使用echarts的词云图,因为几经波折才引用成功,所以想记下来跟大家分享,(我的随笔不会写那么多让人需要动脑子去理解的东西,就是记录一下步骤,因为经验甚少,底层原理懂得不多,所以就先记 ...
- 201621123062《java程序设计》第11周作业总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 思维导图: 2. 书面作业 本次PTA作业题集多线程 2.1. 源代码阅读:多线程程序BounceThread 2 ...