当前位置: 首页 > news >正文

C++ 对象的初始化和清理:构造函数和析构函数

目录

构造函数和析构函数

构造函数

析构函数

构造函数的分类及调用

括号法 

显示法

隐式转换法

拷贝构造函数的调用时机

使用一个已经创建完毕的对象来初始化一个新对象 

值传递的方式给函数参数传值

以值方式返回局部对象

构造函数调用规则

初始化列表

类对象作为其他类成员 各类的构造和析构执行顺序


  

构造函数和析构函数

构造函数

主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

构造函数语法:类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数

主要作用在于对象**销毁前**系统自动调用,执行一些清理工作。

析构函数语法:~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号  ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构 

class Person {public:/* 构造函数 */Person() {cout << "我是构造函数" << endl; // 我是构造函数}/* 细狗函数 */~Person() {cout << "我是细狗函数" << endl; // 我是细狗函数(system("pause")在的情况,不会执行,因为代码还没走完,不会去销毁类)}
};int main() {class Person zhanghai;system("pause");return 0;
}

构造函数的分类及调用

两种分类方式:

  1. 按参数分为: 有参构造和无参构造(默认构造)
  2. 按类型分为: 普通构造和拷贝构造

三种调用方式:

  1. 括号法
  2. 显示法
  3. 隐式转换法

括号法 

class Person {
public:int age;Person() {cout << "Person的无参构造函数调用" << endl;}Person(int a) {age = a;cout << "Person的有参构造函数调用" << endl;}Person(const Person &p) { //将传入的人身上的所有属性,拷贝到我身上age = p.age;cout << "Person的拷贝构造函数调用" << endl;}~Person() {cout << "Person的细狗函数" << endl;}
};class Person p1;   // 默认 无参构造函数的调用 (调用默认的无参构造时,不要加小括号)
class Person p2(1); // 有参构造函数的调用
class Person p3(p2); // 拷贝构造函数的调用
cout << p2.age << endl; // 10
cout << p3.age << endl; // 10

显示法

class Person {
public:int age;Person() {cout << "Person的无参构造函数调用" << endl;}Person(int a) {age = a;cout << "Person的有参构造函数调用" << endl;}Person(const Person &p) { //将传入的人身上的所有属性,拷贝到我身上age = p.age;cout << "Person的拷贝构造函数调用" << endl;}~Person() {cout << "Person的细狗函数" << endl;}
};Person p1;
Person p2 = Person(10); // 有参构造
Person p3 = Person(p2); // 拷贝构造Person(10); // 匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象
cout << "我的上一句输出是 Person的细狗函数" << endl;
Person(p3); // 不要利用拷贝构造函数初始化对象,编辑器会认为 Person(p3) == Person p3

隐式转换法


class Person {
public:int age;Person() {cout << "Person的无参构造函数调用" << endl;}Person(int a) {age = a;cout << "Person的有参构造函数调用" << endl;}Person(const Person &p) { //将传入的人身上的所有属性,拷贝到我身上age = p.age;cout << "Person的拷贝构造函数调用" << endl;}~Person() {cout << "Person的细狗函数" << endl;}
};Person p4 = 10; // 有参构造 相当于 Person p4 = Person(10)
Person p5 = p4; // 拷贝构造 相当于 Person p5 = Person(p4)

拷贝构造函数的调用时机

C++中拷贝构造函数调用时机通常有三种情况

  1. 使用一个已经创建完毕的对象来初始化一个新对象
  2. 值传递的方式给函数参数传值
  3. 以值方式返回局部对象

使用一个已经创建完毕的对象来初始化一个新对象 

class Person {
public:Person() {cout << "无参构造函数!" << endl;mAge = 0;}Person(int age) {cout << "有参构造函数!" << endl;mAge = age;}Person(const Person& p) {cout << "拷贝构造函数!" << endl;mAge = p.mAge;}//析构函数在释放内存之前调用~Person() {cout << "析构函数!" << endl;}
public:int mAge;
};void test01() {Person man(100); //p对象已经创建完毕Person newman(man); //调用拷贝构造函数Person newman2 = man; //拷贝构造//Person newman3;//newman3 = man; //不是调用拷贝构造函数,赋值操作
}

值传递的方式给函数参数传值

class Person {
public:Person() {cout << "无参构造函数!" << endl;mAge = 0;}Person(int age) {cout << "有参构造函数!" << endl;mAge = age;}Person(const Person& p) {cout << "拷贝构造函数!" << endl;mAge = p.mAge;}//析构函数在释放内存之前调用~Person() {cout << "析构函数!" << endl;}
public:int mAge;
};void doWork(Person p1) { //相当于Person p1 = p;}
void test02() {Person p; //无参构造函数doWork(p); 
}

以值方式返回局部对象

class Person {
public:Person() {cout << "无参构造函数!" << endl;mAge = 0;}Person(int age) {cout << "有参构造函数!" << endl;mAge = age;}Person(const Person& p) {cout << "拷贝构造函数!" << endl;mAge = p.mAge;}//析构函数在释放内存之前调用~Person() {cout << "析构函数!" << endl;}
public:int mAge;
};Person doWork2()
{Person p1;cout << (int *)&p1 << endl;return p1; // 返回的是p1的副本,跟p1毫无干系,只是相同而已
}void test03()
{Person p = doWork2(); // Person p = p1cout << (int *)&p << endl;
}int main() {/*依次输出:无参构造函数ff44aaff拷贝构造函数析构函数ff55aacc析构函数*/test03();return 0;
}

构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
#include <iostream>
using namespace std;class Person {
public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数//Person(const Person& p) {//	age = p.age;//	cout << "拷贝构造函数!" << endl;//}//析构函数~Person() {cout << "析构函数!" << endl;}
public:int age;
};void test01()
{Person p1(18);//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作Person p2(p1);cout << "p2的年龄为: " << p2.age << endl;
}void test02()
{//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造Person p1; //此时如果用户自己没有提供默认构造,会出错Person p2(10); //用户提供的有参Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供//如果用户提供拷贝构造,编译器不会提供其他构造函数Person p4; //此时如果用户自己没有提供默认构造,会出错Person p5(10); //此时如果用户自己没有提供有参,会出错Person p6(p5); //用户自己提供拷贝构造
}int main() {/*注释掉 拷贝构造函数 的情况有参构造函数!p2的年龄为: 18析构函数!析构函数!*/test01();return 0;
}

初始化列表

C++用来初始化属性 ——`构造函数():属性1(值1),属性2(值2)... {}

class Person {
public:传统方式初始化//Person(int a, int b, int c) {//	m_A = a;//	m_B = b;//	m_C = c;//}//初始化列表方式初始化,注意冒号的位置Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}void PrintPerson() {cout << "mA:" << m_A << endl; // 1cout << "mB:" << m_B << endl; // 2cout << "mC:" << m_C << endl; // 3}
private:int m_A;int m_B;int m_C;
};int main() {Person p(1, 2, 3);p.PrintPerson();return 0;
}

类对象作为其他类成员 各类的构造和析构执行顺序

构造的顺序是 :

先调用对象成员的构造,再调用本类构造

析构顺序与构造相反

#include <iostream>
#include <string>
using namespace std;class Phone
{
public:Phone(string name){m_PhoneName = name;cout << "Phone构造" << endl;}~Phone(){cout << "Phone析构" << endl;}string m_PhoneName;};class Person
{
public:// 初始化列表可以告诉编译器调用哪一个构造函数 Phone m_Phone = pName => Phone m_Phone = Phone(pName) 有参构造Person(string name, string pName) :m_Name(name), m_Phone(pName){cout << "Person构造" << endl;}~Person(){cout << "Person析构" << endl;}void playGame(){cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;}string m_Name;Phone m_Phone;};
void test01()
{// 当类中成员是其他类对象时,我们称该成员为 对象成员// 构造的顺序是 :先调用对象成员的构造,再调用本类构造// 析构顺序与构造相反Person p("张三", "苹果X");p.playGame();}int main() {test01();return 0;
}

http://www.lryc.cn/news/256218.html

相关文章:

  • Tmux中使用Docker报错 - 解决方案
  • 如何在WordPress中批量替换图片路径?
  • el-pagination 纯前端分页
  • 基于springboot的校园二手市场
  • 【开源】基于Vue和SpringBoot的在线课程教学系统
  • Mysql分布式集群部署---MySQL集群Cluster将数据分成多个片段,每个片段存储在不同的服务器上
  • 身份认证技术
  • Centos7、Mysql8.0 load_file函数返回为空的终极解决方法--暨selinux的深入理解
  • 基于Spring Boot的水产养殖管理系统
  • LCR 090. 打家劫舍 II(leetcode)动态规划
  • 【小沐学Python】Python实现语音识别(Whisper)
  • Nginx负载均衡实战
  • Redis skiplist源码解析(支持范围查询)
  • MVSNeRF:多视图立体视觉的快速推广辐射场重建(2021年)
  • 华为OD机试真题-CPU算力分配-2023年OD统一考试(C卷)
  • 校验数据是否重叠(各种操作符>,<,>=,<=,or,and)
  • 大一C语言作业 12.8
  • ELasticsearch:什么是语义搜索?
  • ooTD I 女儿是自己的,尽情打扮尽情可爱
  • 第62天:django学习(十一)
  • Rust测试字符串的移动,Move
  • vue+electron问题汇总
  • Linux中的网络时间服务器
  • fastadmin打印页面
  • Java 将word转为PDF的三种方式和处理在服务器上下载后乱码的格式
  • C\C++ 获取最值
  • 机器学习之无监督学习:九大聚类算法
  • Linux高级管理-搭建网站服务
  • Windows 系统,TortoiseSVN 无法修改 Log 信息解决方法
  • 编译 Android gradle-4.6-all.zip 报错问题记录