C++ 各种构造函数
c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初学者有所帮助。
一、 构造函数是干什么的
1 class Counter
2 {
3
4 public:
5 // 类Counter的构造函数
6 // 特点:以类名作为函数名,无返回类型
7 Counter()
8 {
9 m_value = 0;
10 }
11
12 private:
13
14 // 数据成员
15 int m_value;
16 }
该类对象被创建时,编译系统对象分配内存空间,并自动调用该构造函数->由构造函数完成成员的初始化工作
eg: Counter c1;
编译系统为对象c1的每个数据成员(m_value)分配内存空间,并调用构造函数Counter( )自动地初始化对象c1的m_value值设置为0
故:
构造函数的作用:初始化对象的数据成员。
二、 构造函数的种类
1 class Complex
2 {
3
4 private :
5 double m_real;
6 double m_imag;
7
8 public:
9
10 // 无参数构造函数
11 // 如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做
12 // 只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来
13 Complex(void)
14 {
15 m_real = 0.0;
16 m_imag = 0.0;
17 }
18
19 // 一般构造函数(也称重载构造函数)
20 // 一般构造函数可以有各种参数形式,一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理)
21 // 例如:你还可以写一个 Complex( int num)的构造函数出来
22 // 创建对象时根据传入的参数不同调用不同的构造函数
23 Complex(double real, double imag)
24 {
25 m_real = real;
26 m_imag = imag;
27 }
28
29 // 复制构造函数(也称为拷贝构造函数)
30 // 复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中
31 // 若没有显示的写复制构造函数,则系统会默认创建一个复制构造函数,但当类中有指针成员时,由系统默认创建该复制构造函数会存在风险,具体原因请查询 有关 “浅拷贝” 、“深拷贝”的文章论述
32 Complex(const Complex & c)
33 {
34 // 将对象c中的数据成员值复制过来
35 m_real = c.m_real;
36 m_img = c.m_img;
37 }
38
39 // 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象
40 // 例如:下面将根据一个double类型的对象创建了一个Complex对象
41 Complex::Complex(double r)
42 {
43 m_real = r;
44 m_imag = 0.0;
45 }
46
47 // 等号运算符重载
48 // 注意,这个类似复制构造函数,将=右边的本类对象的值复制给等号左边的对象,它不属于构造函数,等号左右两边的对象必须已经被创建
49 // 若没有显示的写=运算符重载,则系统也会创建一个默认的=运算符重载,只做一些基本的拷贝工作
50 Complex &operator=( const Complex &rhs )
51 {
52 // 首先检测等号右边的是否就是左边的对象本,若是本对象本身,则直接返回
53 if ( this == &rhs )
54 {
55 return *this;
56 }
57
58 // 复制等号右边的成员到左边的对象中
59 this->m_real = rhs.m_real;
60 this->m_imag = rhs.m_imag;
61
62 // 把等号左边的对象再次传出
63 // 目的是为了支持连等 eg: a=b=c 系统首先运行 b=c
64 // 然后运行 a= ( b=c的返回值,这里应该是复制c值后的b对象)
65 return *this;
66 }
67
68 };
下面使用上面定义的类对象来说明各个构造函数的用法:
1 void main()
2 {
3 // 调用了无参构造函数,数据成员初值被赋为0.0
4 Complex c1,c2;
5
6 // 调用一般构造函数,数据成员初值被赋为指定值
7 Complex c3(1.0,2.5);
8 // 也可以使用下面的形式
9 Complex c3 = Complex(1.0,2.5);
10
11 // 把c3的数据成员的值赋值给c1
12 // 由于c1已经事先被创建,故此处不会调用任何构造函数
13 // 只会调用 = 号运算符重载函数
14 c1 = c3;
15
16 // 调用类型转换构造函数
17 // 系统首先调用类型转换构造函数,将5.2创建为一个本类的临时对象,然后调用等号运算符重载,将该临时对象赋值给c1
18 c2 = 5.2;
19
20 // 调用拷贝构造函数( 有下面两种调用方式)
21 Complex c5(c2);
22 Complex c4 = c2; // 注意和 = 运算符重载区分,这里等号左边的对象不是事先已经创建,故需要调用拷贝构造函数,参数为c2
23
24
25
26 }
三、思考与测验
1. 仔细观察复制构造函数
Complex(const Complex & c)
{
// 将对象c中的数据成员值复制过来
m_real = c.m_real;
m_img = c.m_img;
}
为什么函数中可以直接访问对象c的私有成员?
2. 挑战题,了解引用与传值的区别
1 Complex test1(const Complex& c)
2 {
3 return c;
4 }
5
6 Complex test2(const Complex c)
7 {
8 return c;
9 }
10
11 Complex test3()
12 {
13 static Complex c(1.0,5.0);
14 return c;
15 }
16
17 Complex& test4()
18 {
19 static Complex c(1.0,5.0);
20 return c;
21 }
22
23 void main()
24 {
25 Complex a,b;
26
27 // 下面函数执行过程中各会调用几次构造函数,调用的是什么构造函数?
28
29 test1(a);
30 test2(a);
31
32 b = test3();
33 b = test4();
34
35 test2(1.2);
36 // 下面这条语句会出错吗?
37 test1(1.2); //test1( Complex(1.2 )) 呢?
38 }
四、附录(浅拷贝与深拷贝)
上面提到,如果没有自定义复制构造函数,则系统会创建默认的复制构造函数,但系统创建的默认复制构造函数只会执行“浅拷贝”,即将被拷贝对象的数据成员的值一一赋值给新创建的对象,若该类的数据成员中有指针成员,则会使得新的对象的指针所指向的地址与被拷贝对象的指针所指向的地址相同,delete该指针时则会导致两次重复delete而出错。下面是示例:
【浅拷贝与深拷贝】
1 #include <iostream.h>
2 #include <string.h>
3 class Person
4 {
5 public :
6
7 // 构造函数
8 Person(char * pN)
9 {
10 cout << "一般构造函数被调用 !\n";
11 m_pName = new char[strlen(pN) + 1];
12 //在堆中开辟一个内存块存放pN所指的字符串
13 if(m_pName != NULL)
14 {
15 //如果m_pName不是空指针,则把形参指针pN所指的字符串复制给它
16 strcpy(m_pName ,pN);
17 }
18 }
19
20 // 系统创建的默认复制构造函数,只做位模式拷贝
21 Person(Person & p)
22 {
23 //使两个字符串指针指向同一地址位置
24 m_pName = p.m_pName;
25 }
26
27 ~Person( )
28 {
29 delete m_pName;
30 }
31
32 private :
33
34 char * m_pName;
35 };
36
37 void main( )
38 {
39 Person man("lujun");
40 Person woman(man);
41
42 // 结果导致 man 和 woman 的指针都指向了同一个地址
43
44 // 函数结束析构时
45 // 同一个地址被delete两次
46 }
47
48
49 // 下面自己设计复制构造函数,实现“深拷贝”,即不让指针指向同一地址,而是重新申请一块内存给新的对象的指针数据成员
50 Person(Person & chs);
51 {
52 // 用运算符new为新对象的指针数据成员分配空间
53 m_pName=new char[strlen(p.m_pName)+ 1];
54
55 if(m_pName)
56 {
57 // 复制内容
58 strcpy(m_pName ,chs.m_pName);
59 }
60
61 // 则新创建的对象的m_pName与原对象chs的m_pName不再指向同一地址了
62 }
C++ 各种构造函数的更多相关文章
- .NET 基础 一步步 一幕幕[面向对象之构造函数、析构函数]
构造函数.析构函数 构造函数: 语法: //无参的构造函数 [访问修饰符] 函数名() :函数名必须与类名相同. //有参的构造函数 [访问修饰符] 函数名(参数列表):函数名必须与类名相同. 作用: ...
- javascript工厂模式和构造函数模式创建对象
一.工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程(本书后面还将讨论其他设计模式及其在JavaScript 中的实现).考虑到在ECMAScript 中无法创 ...
- JS继承之借用构造函数继承和组合继承
根据少一点套路,多一点真诚这个原则,继续学习. 借用构造函数继承 在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术( ...
- PHP与JAVA构造函数的区别
早期的PHP是没有面向对象功能的,但是随着PHP发展,从PHP4开始,也加入了面向对象.PHP的面向对象语法是从JAVA演化而来,很多地方类似,但是又发展出自己的特色.以构造函数来说,PHP4中与类同 ...
- C++ 拷贝构造函数和赋值运算符
本文主要介绍了拷贝构造函数和赋值运算符的区别,以及在什么时候调用拷贝构造函数.什么情况下调用赋值运算符.最后,简单的分析了下深拷贝和浅拷贝的问题. 拷贝构造函数和赋值运算符 在默认情况下(用户没有定义 ...
- golang语言构造函数
1.构造函数定义 构造函数 ,是一种特殊的方法.主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中.特别的一个类可以有多个构造函数 ,可根据其参数个 ...
- 前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型
前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型 前言(题外话): 有人说拖延症是一个绝症,哎呀治不好了.先不说这是一个每个人都多多少少会有的,也不管它究竟对生活有多么大的 ...
- Aop动态生成代理类时支持带参数构造函数
一.背景 在某些情况下,我们需要植入AOP代码的类并没有默认构造函数.那么此时动态生成的代理类也需要相同签名的构造函数,并且内部调用原始类的构造函数.自己折腾了1晚上没搞定,现在搞定了发出来供大家一起 ...
- C#的泛型的类型参数可以有带参数的构造函数的约束方式吗?
Review后看到标题让我十分羞愧自己语文功底太差,估计...请见谅......我还特地把这句写回开头了...... 问题 前天遇到的一个问题,所以在MSDN发了个问,刚也丰富了下问题,关于泛型的. ...
- Android中自定义样式与View的构造函数中的第三个参数defStyle的意义
零.序 一.自定义Style 二.在XML中为属性声明属性值 1. 在layout中定义属性 2. 设置Style 3. 通过Theme指定 三.在运行时获取属性值 1. View的第三个构造函数的第 ...
随机推荐
- 网络损伤仪WANsim的带宽限制功能
带宽限制功能 带宽限制功能是网络损伤仪WANsim的第一项损伤功能.进入WANsim的报文首先会经过报文过滤器的处理,随后,就会进入带宽限制. 点击虚拟链路,就可以进入网络损伤界面,对报文进行带宽限制 ...
- Git的使用(六)
前言 版本管理工具总结: 开发团队项目,对项目的版本进行管理. 使用过的版本管理工具: TFS.SVN与Git. TFS:管理项目,通过visual Studio管理源码,拉取分支,提交代码等.也可以 ...
- java 8内置的四大核心函数式接口
Consumer<T> : 消费性接口 返回值 void accept(T t); public void happy(double money, Consumer<Double& ...
- 深度掌握 Java Stream 流操作,让你的代码高出一个逼格!
概念 Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选.排序.聚合等. Stream 的操作符大体上分为两种:中间操作符和终止操作符 中 ...
- vue中this.$router.push()路由传值和获取的两种常见方法
1.路由传值 this.$router.push() (1) 路由跳转使用router.push()方法,这个方法会向history栈添加一个新纪录,所以,当用户点击浏览器后退按钮时,会回到之前的 ...
- user-agent浏览器标识集合
user_agent_list = [ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET ...
- python中单例模式的创建
# 单例模式(使用装饰器) def singleton(cls): instance = {} def wrapper(*args,**kwargs): if cls not in instance: ...
- 【剑指offer】65. 不用加减乘除做加法
剑指 Offer 65. 不用加减乘除做加法 知识点:数学:位运算 题目描述 写一个函数,求两个整数之和,要求在函数体内不得使用 "+"."-"."* ...
- 一个工业级、跨平台、轻量级的 tcp 网络服务框架:gevent
前言 作为公司的公共产品,经常有这样的需求:就是新建一个本地服务,产品线作为客户端通过 tcp 接入本地服务,来获取想要的业务能力.与印象中动辄处理成千上万连接的 tcp 网络服务不同,这个本地服务是 ...
- HTML重点总结
HTML基础 1. 标题 HTML 标题(Heading)是通过<h1> - <h6> 标签来定义的. <h1>这是一个标题</h1> <h2&g ...