1. 构造函数

构造函数是一种特殊的函数(方法),在根据类创建对象时被调用。构造函数是一种随着对象创建而自动被调用的函数,它的主要用途是为对象作初始化。

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。类似于 Python 类中的 __init__() 函数。

1.1 声明和实现构造函数

通过下面示例理解构造函数,Human 类的构造函数的声明类似于下面这样:

class Human
{public:Human(); //构造函数声明
};

这个构造函数可在类声明中实现,也可在类声明外实现。在类声明中实现(定义)构造函数的代码类似于下面这样:

class Human
{public:Human(){// 代码实现}
};

在类声明外定义构造函数的代码类似于下面这样:

class Human
{public:Human(); //构造函数声明
};Human::Human()
{// 代码实现
}

注意: :: 被称为作用域解析运算符。例如, Human::dateOfBirth 指的是在 Human 类中声明的变量 dateOfBirth ,而 ::dateOfBirth 表示全局作用域中的变量 dateOfBirth

1.2 默认构造函数

#include <iostream>
using namespace std;class Line
{public:void setLength(double len);double getLength(void);Line();  // 这是构造函数private:double length;
};// 成员函数定义,包括构造函数
Line::Line(void)
{cout << "Object is being created" << endl;
}void Line::setLength(double len)
{length = len;
}double Line::getLength( void )
{return length;
}
// 程序的主函数
int main( )
{Line line;// 设置长度line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl;return 0;
}

输出结果:

Object is being created
Length of line : 6

1.3 带参数的构造函数

默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值,如下面的例子所示:

#include <iostream>
using namespace std;class Line
{public:void setLength(double len);double getLength(void);Line(double len);  // 这是构造函数private:double length;
};// 成员函数定义,包括构造函数
Line::Line(double len)
{cout << "Object is being created, length = " << len << endl;length = len;
}void Line::setLength( double len )
{length = len;
}double Line::getLength( void )
{return length;
}
// 程序的主函数
int main( )
{Line line(10.0);// 获取默认设置的长度cout << "Length of line : " << line.getLength() <<endl;// 再次设置长度line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl;return 0;
}

输出结果:

Object is being created, length = 10
Length of line : 10
Length of line : 6

1.4 带默认参数的构造函数

就像函数可以有带默认值的参数一样,构造函数也可以。可以对上面的代码进行修改

...
Line::Line(double len=20.0)
{cout << "Object is being created, length = " << len << endl;length = len;
}...
int main( )
{Line line(10.0);Line line2;...
}

输出结果:

Object is being created, length = 10
Object is being created, length = 20
Length of line : 10
Length of line : 6

1.5 使用初始化列表的构造函数

构造函数对初始化成员变化很有用,另一种初始化成员的方式是使用初始化列表。

初始化列表由包含在括号中的参数声明后面的冒号 : 标识,冒号 : 后面列出了各个成员变量及其初始值。

Line::Line(double len): length(len)
{cout << "Object is being created, length = " << len << endl;
}

上面的语法等同于如下语法:

Line::Line(double len)
{length = len;cout << "Object is being created, length = " << len << endl;
}

假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

C::C(double a, double b, double c): X(a), Y(b), Z(c)
{....
}

2. 析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号 ~ 作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

析构函数是一种随着对象消亡而自动被调用的函数,它的主要用途是释放动态申请的资源。它没有返回类型,没有参数,也没有重载。析构函数的函数名也是指定的,是在构造函数名之前加一个 符号。

2.1 声明和定义析构函数

析构函数的声明类似于下面这样:

class Line
{~Line();   // 析构函数声明
};

这个析构函数可在类声明中实现,也可在类声明外实现。在类声明中实现(定义)析构函数的代码类似于下面这样:

class Line
{public:~Line(){// code}
};

在类声明外定义析构函数的代码类似于下面这样:

class Line
{public:~Line();    // 析构函数声明
};// 析构函数实现
Line::~Line()
{// code
};

2.2 析构函数示例

示例代码 1

#include <iostream>
using namespace std;class Line
{public:void setLength(double len);double getLength(void);Line();   // 这是构造函数声明~Line();  // 这是析构函数声明private:double length;
};// 成员函数定义,包括构造函数
Line::Line(void)
{cout << "Object is being created" << endl;
}
Line::~Line(void)
{cout << "Object is being deleted" << endl;
}void Line::setLength(double len)
{length = len;
}double Line::getLength(void)
{return length;
}// 程序的主函数
int main( )
{Line line;// 设置长度line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl;return 0;
}

输出结果:

Object is being created
Length of line : 6
Object is being deleted

3. 拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 一个对象需要通过另外一个对象进行初始化(使用另一个同类型的对象来初始化新创建的对象);
  • 一个对象以值传递的方式传入函数体;
  • 一个对象以值传递的方式从函数返回;

  • 如果在类中没有定义拷贝构造函数,编译器会自行定义一个
  • 如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数

拷贝构造函数的最常见形式如下:

classname (const classname &obj) {// 拷贝构造函数的主体
}

在这里,**obj** 是一个对象引用,该对象是用于初始化另一个对象的。

3.1 一个对象需要通过另外一个对象进行初始化

直接看代码

#include <iostream>
using namespace std;class Student
{public:string getID();Student(string i, string n, int a); // 简单的构造函数Student(const Student &stu);    // 拷贝构造函数~Student(); // 析构函数private:string id;string name;int age;
};Student::Student(string i="", string n="", int a=0)
{cout << "constructor func run" << endl;id = i;name = n;age = a;
}Student::Student(const Student &stu)
{cout << "copy constructor func run" << endl;id = stu.id;name = stu.name;age = stu.age;
}Student::~Student()
{cout << "destructor func run" << endl;
}string Student::getID()
{return id;
}int main()
{// 创建对象Student stu = Student("0001", "Jack", 18);// 使用另一个同类型的对象 stu 来初始化新创建的对象 stStudent st = stu;cout << "st id is " << st.getID() << endl; return 0;
}

输出结果:

constructor func run
copy constructor func run
st id is 0001
destructor func run
destructor func run

3.2 一个对象以值传递的方式传入函数体

直接看代码

#include <iostream>
using namespace std;class Student
{public:string getID();Student(string i, string n, int a); // 简单的构造函数Student(const Student &stu);    // 拷贝构造函数~Student(); // 析构函数private:string id;string name;int age;
};Student::Student(string i="", string n="", int a=0)
{cout << "constructor func run" << endl;id = i;name = n;age = a;
}Student::Student(const Student &stu)
{cout << "copy constructor func run" << endl;id = stu.id;name = stu.name;age = stu.age;
}Student::~Student()
{cout << "destructor func run" << endl;
}string Student::getID()
{return id;
}void display(Student stu)
{cout << "st id is " << stu.getID() << endl;
}int main()
{// 创建对象Student stu = Student("0001", "Jack", 18);// 将对象 stu 以值传递的方式传递给函数 displaydisplay(stu);return 0;
}

输出结果:

constructor func run
copy constructor func run
st id is 0001
destructor func run
destructor func run

3.3 一个对象以值传递的方式从函数返回

如果函数的返冋值是类 A 的对象,则函数返回时,类 A 的拷贝构造函数被调用。

#include <iostream>
using namespace std;class Student
{public:string getID();Student(string i, string n, int a); // 简单的构造函数Student(const Student &stu);    // 拷贝构造函数~Student(); // 析构函数private:string id;string name;int age;
};Student::Student(string i="", string n="", int a=0)
{cout << "constructor func run" << endl;id = i;name = n;age = a;
}Student::Student(const Student &stu)
{cout << "copy constructor func run" << endl;id = stu.id;name = stu.name;age = stu.age;
}Student::~Student()
{cout << "destructor func run" << endl;
}string Student::getID()
{return id;
}Student stu ("0001", "Jack", 18);Student retStudent()
{// 在函数内部创建对象时,由于编译器优化原因,所以不会调用拷贝构造函数// Student stu ("0001", "Jack", 18);// Student stu = Student("0001", "Jack", 18);return stu;
}int main()
{retStudent();return 0;
}

输出结果:

constructor func run
copy constructor func run
destructor func run
destructor func run

需要说明的是,有些编译器出于程序执行效率的考虑,编译的时候进行了优化,函数返回值对象就不用复制构造函数初始化了,这并不符合 C++的标准。

C++拷贝构造函数(复制构造函数)详解

3.4 当类成员中含有指针类型成员且需要对其分配内存时,一定要重定义拷贝构造函数

默认的拷贝构造函数实现的只能是浅拷贝,即直接将原对象的数据成员值依次复制给新对象中对应的数据成员,并没有为新对象另外分配内存资源。这样,如果对象的数据成员是指针,两个指针对象实际上指向的是同一块内存空间。在某些情况下,浅拷贝会带来数据安全方面的隐患。

当类的数据成员中有指针类型时,我们就必须定义一个特定的拷贝构造函数,该拷贝构造函数不仅可以实现原对象和新对象之间数据成员的拷贝,而且可以为新的对象分配单独的内存资源,这就是深拷贝构造函数。

如何防止默认拷贝发生
声明一个私有的拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类的对象,编译器会报告错误,从而可以避免按值传递或返回对象。

总结:
当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。

当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。

深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

示例代码:

#include <iostream>
using namespace std;class Student
{public:Student(string i, string n, int a); // 简单的构造函数Student(const Student &stu);    // 拷贝构造函数~Student(); // 析构函数string getID();private:string *id;string name;int age;
};Student::Student(string i="", string n="", int a=0)
{cout << "constructor func run" << endl;id = new string;*id = i;name = n;age = a;
}Student::Student(const Student &stu)
{cout << "copy constructor func run" << endl;id = new string;*id = *stu.id;  // 拷贝值name = stu.name;age = stu.age;
}Student::~Student()
{cout << "destructor func run" << endl;delete id;
}string Student::getID()
{return *id;
}void display(Student stu)
{cout << "st id is " << stu.getID() << endl;
}int main()
{Student stu ("0001", "Jack", 18);display(stu);return 0;
}

输出结果:

constructor func run
copy constructor func run
st id is 0001
destructor func run
destructor func run

C++ 笔记(17)— 类和对象(构造函数、析构函数、拷贝构造函数)相关推荐

  1. C++学习笔记【类和对象】【构造函数和析构函数】

    主要内容来源:https://www.bilibili.com/video/BV1et411b73Z?spm_id_from=333.999.0.0 一.构造函数 构造函数的分类 1.按参数分:无参构 ...

  2. 类string的构造函数、拷贝构造函数和析构函数

    原文:http://www.cnblogs.com/Laokong-ServiceStation/archive/2011/04/19/2020402.html 类string的构造函数.拷贝构造函数 ...

  3. 网易游戏笔试题:编写string类的构造函数、拷贝构造函数、赋值构造函数和析构函数

    题目: class MyString { public: MyString(const char *str);//构造函数 MyString(const MyString& strClass) ...

  4. Java_面向对象基础(类、对象、方法和构造函数)

    Java的面向对象基础(类.对象.方法和构造函数) 面向对象的基本概念 面向对象的设计思想 什么是类 什么是对象 类的定义 设计面向对象的例子 步骤一:设计动物这个类 步骤二:创建具体的动物 步骤三: ...

  5. Python学习笔记 (类与对象)

    Python学习笔记 (类与对象) 1.类与对象 面向对象编程语言类: 一个模板, (人类)-是一个抽象的, 没有实体的对象: (eg: 张三, 李四) 属性: (表示这类东西的特征, 眼睛, 嘴巴, ...

  6. String 的普通构造函数、拷贝构造函数、析构函数、赋值函数

    转自:http://blog.csdn.net/xiaoxiangzhu660810/article/details/8149398 题目:编写类String的构造函数.析构函数和赋值函数,已知类St ...

  7. 构造函数、拷贝构造函数和析构函数的的调用时刻及调用顺序

    构造函数.拷贝构造函数和析构函数的的调用时刻及调用顺序 对象是由"底层向上"开始构造的,当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的构造函数,依次类推,直至到达 ...

  8. 构造函数、拷贝构造函数、赋值函数和析构函数

    文章目录 一.构造函数 1.认识构造函数 2.初始化列表 二.拷贝构造函数 1.类对象的拷贝 2.浅拷贝和深拷贝 三.赋值函数 四.析构函数 1.认识析构函数 2.销毁,清理? 3.析构函数来阻止该类 ...

  9. C++中对象的赋值拷贝构造函数

    目录 1.对象与对象之间的赋值. 下面给出代码说明赋值语句 对象赋值的限制和特点 2.拷贝构造函数 拷贝构造函数的特点 自定义的拷贝构造函数的代码及运行结果 默认拷贝构造函数 调用拷贝构造函数的3种情 ...

  10. C++ : 构造函数,拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符应用场景

    构造函数,拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符应用场景 #include <iostream> using namespace std;class Construct ...

最新文章

  1. python编程从入门到实践课后题答案-《Python编程:从入门到实践》课后习题及答案—第11章...
  2. Fast RCNN 训练自己数据集 (1编译配置)
  3. 【工具推荐】ELMAH——可插拔错误日志工具(转)
  4. leetcode129. 求根到叶子节点数字之和
  5. tomcat的localhost_access_log日志文件
  6. [转]gcc下程序调用静态库编译命令:主文件必须在静态库前面!
  7. 立下2019年的Flag,鞭策自己,使命宣言
  8. 简单的页面表格导出Excel
  9. 如何利用计算机教室上英语,如何有效利用多媒体进行英语教学
  10. 惠普服务器查询ilo信息,HP服务器在线配置ilo地址
  11. windows 弹shell_10个简洁实用的Windows装机必备软件
  12. A Comprehensive Measurement Study of Domain Generating Malware 原文翻译
  13. 2015年imac一体机安装双系统问题及解决技巧
  14. indiegogo众筹
  15. 传感器原理与检测技术复习笔记第六章-压电式传感器
  16. 计算机控制系统陈振答案,北京理工大学自动化学院导师教师师资介绍简介-董 宁...
  17. GateWay简介及使用
  18. Ingress-nginx
  19. SMAX自动化服务台:有了我,技术人员再也不用手忙脚乱啦!
  20. 2020山东计算机大赛裴鹏飞,裴鹏飞个人事迹加工作照(7页)-原创力文档

热门文章

  1. 数据库中自定义排序规则,Mysql中自定义字段排序规则,Oracle中自定义字段排序规则,decode函数的用法,field函数的用法
  2. 利用dom4j将实体类转换为对应的xml报文
  3. 2022-2028年中国数字化制造产业研究及前瞻分析报告
  4. C++ 笔记(36)— 接收输入字符串的几种方法
  5. c++一些常见的知识点
  6. 用python给自己写一个加密算法
  7. 提高班第五周周记(国庆第二天)
  8. 梯度优化算法Adam
  9. LeetCode中等题之无重复字符的最长字串
  10. MLIR与Code Generation