C++超详细讲解构造函数与析构函数的用法及实现

目录
  • 写在前面
  • 构造函数和析构函数
    • 语法
    • 作用
    • 代码实现
  • 两大分类方式
    • 三种调用方式
      • 括号法
      • 显示法
      • 隐式转换法
  • 正确调用拷贝构造函数
    • 正常调用
    • 值传递的方式给函数参数传值
    • 值传递方式返回局部对象
  • 构造函数的调用规则
  • 总结

写在前面

上一节解决了类与对象封装的问题,这一节就是对象的初始化和清理的构造函数与析构函数的内容了;对象的初始化和清理也是两个非常重要的安全问题:一个对象或者变量没有初始状态,对其使用后果是未知,同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题;c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器提供编译器提供的构造函数和析构函数是空实现。下面开始正文:

构造函数和析构函数

语法

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

1、没有返回值也不写void

2、函数名称与类名相同

3、构造函数可以有参数,因此可以发生重载

4、程序在调用对象时会自动调用,无需手动调用且只会调用一次

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

1、没有返回值也不写void

2、函数名称与类名相同,在名称前加上符号~

3、构造函数不可以有参数,因此不可以发生重载

4、程序在对象销毁前会自动调用析构,无需手动调用且只会调用一次

作用

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

析构函数 主要作用于对象销毁前系统自动调用,执行一些清理工作

代码实现

#include<iostream>
using namespace std;
class Person
{
public:
	Person()
	{
		cout << "Person构造函数的调用" << endl;
	}
	~Person()
	{
		cout << "~Person析构函数的调用" << endl;
	}
};
void test01()
{
	Person p;//栈上的对象运行完毕后,编译器自动释放
}
int main()
{
	test01();
}

test01中创建了Person类p,主函数只是调用了一下创建的Person类p,编译器就自动调用了类的构造函数和析构函数,析构函数是程序运行完毕后,编译器自动清理内存空间的时候调用的。

两大分类方式

按参数分为 有参构造 和 无参构造

按类型分为 普通构造 和 拷贝构造

无参和有参构造很好理解,就是有无参数的区别,这里讲一下拷贝构造函数:

//拷贝构造函数
	Person(const Person &p) //格式: const 类名 引用(&)变量名
	{
		//讲传入的人身上的所有属性,拷贝到我身上
		age = p.age;
		cout << "Person的拷贝构造函数调用" << endl;
	}

Person()的括号中是const Person &p,这是拷贝构造的函数格式,他需要传入相同类的对象,会产生一个具有相同属性的类,比如p1的年龄为20,经过拷贝构造p2的年龄也会是20,但是两个类对象的地址并不相同,这个到后面会具体解释

三种调用方式

class Person
{
public:
	//构造函数
	Person()
	{
		cout << "Person的无参构造函数调用" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << "Person的有参构造函数调用" << endl;
	}
	//拷贝构造函数
	Person(const Person &p) //格式: const 类名 引用(&)变量名
	{
		//讲传入的人身上的所有属性,拷贝到我身上
		age = p.age;
		cout << "Person的拷贝构造函数调用" << endl;
	}
	~Person()
	{
		cout << "~Person的析构函数调用" << endl;
	}
	int age;
};

括号法

    Person p;//默认构造函数调用
	Person p2(10);//有参构造函数
	Person p3(p2);//拷贝构造函数
	cout << "p2 age=" << p2.age << endl;
	cout << "p3 age=" << p3.age << endl;

注意事项:调用默认构造函数的时候,不要加();Person p1() 编译器会认为是函数的声明,不认为在创建对象,等同于 void func()

显示法

    Person p;
	Person p2=Person(10);//有参构造函数
	Person p3=Person(p2);//拷贝构造函数
	Person(100);//匿名对象,特点:当前执行完毕后,系统会立即回收掉匿名对象
	cout << "AAAAA" << endl;

注意事项2:拷贝构造初始化匿名对象等同于去掉括号,导致重定义,不要用拷贝构造初始化匿名对象,如果利用匿名对象的话,会和Peron p2=Person(10),重复,出现重定义错误;也不要用拷贝构造初始化匿名对象。

隐式转换法

    Person p2 = 10;// 有参构造函数
	Person p3 = p2;// 拷贝构造函数

这个方法不推荐使用,调用的很不明显,建议使用前面两个方法调用构造函数。

正确调用拷贝构造函数

class Person
{
public:
	Person()
	{
		cout << "Person的无参构造函数调用" << endl;
	}
	Person(int a)
	{
		m_age = a;
		cout << "Person的有参构造函数调用" << endl;
	}
	Person(const Person& p)
	{
		m_age = p.m_age;
		cout << "Person的拷贝构造函数调用" << endl;
	}
	~Person()
	{
		cout << "Person 的析构函数调用" << endl;
	}
	int m_age;
};

正常调用

void test01()
{
	Person p1(20);
	Person p2(p1);
	cout << "p2的年龄为:" << p2.m_age << endl;
}

主函数中直接调用test01,这时候会显示 p2的年龄为20,并且打印:拷贝构造函数的调用。所以说,使用一个已经创建完毕的对象来初始化一个新对象的时候会调用拷贝构造函数

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

void doWork(Person p)
{  }
void test02()
{
	Person p;
	doWork(p);
}

大家可以猜一下,在主函数调用,会运行出什么结果,答案是:无参构造函数调用和拷贝构造函数调用,最后是两个析构函数调用;浅析一下过程,调用test02时创建了对象P,所以自动调用无参构造函数,当运行到doWork(p)时,调用拷贝构造函数,随后拷贝构造函数被清理,调用析构函数,程序结束前,p被清理,再次调用析构函数,程序结束。

值传递方式返回局部对象

Person doWork2()
{
	Person p1;
	cout << (int)&p1<<"  1  " << endl;
	return p1;//返回就拷贝构造函数,随后释放掉,调用析构
}
void test03()
{
	Person p = doWork2();//重新创建局部对象,并不是上面返回的对象p1
	cout << (int)&p<<"  2  " << endl;
}

这里doWork2返回值时Person类型,也就是说return p1后会拷贝构造其属性给test03调用的p,但是p1和p2并不是同一个对象,我们可以输出他们的地址来验证。

这里的调用顺序是:Person P1 的无参构造,随后输出p1地址,然后返回值的时候先调用拷贝构造函数,把值赋给p,随后清理p1调用析构;然后回到test03中,输出p的地址,程序结束前调用析构,程序结束。

构造函数的调用规则

编译器提供:

1、创建一个类,c++编译器会给每个类都至少添加三个函数

  1. 默认构造(空实现)
  2. 析构函数(空实现)
  3. 值拷贝构造(值拷贝)

2、如果我们写了有参构造,编译器不再提供默认构造,但是提供值拷贝构造

如果我们写了拷贝构造函数,编译器不再提供其他普通构造函数

void test01()
{
	Person p1;
	p1.m_age = 19;
	Person p2(p1);//即使没写拷贝构造仍然能得到结果p2.m_age =19
	cout << "p2的年龄为:" << p2.m_age << endl;
}

也就是说,就算我们不写无参和拷贝构造,调用test03也会得到值拷贝后的p2年龄,这是编译器默认提供的三个函数。但是如果写了有参构造,Person p1这行代码就会报错,提示找不到默认构造函数;同样的如果自己写了拷贝构造,Person p1也会显示同样的错误。

总结

这篇博文讲了一部分对象的初始化和清理的内容,着重讲了构造函数的调用方法、规则,以及拷贝构造函数的概念,调用方法和细节。下一篇直接准备深浅拷贝的内容和初始化列表,静态成员等的问题,彻底结束对象的初始化和清理内容,期待下篇与你们见面!

到此这篇关于C++超详细讲解构造函数与析构函数的用法及实现的文章就介绍到这了,更多相关C++构造函数与析构函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 正确理解C++的构造函数和析构函数

    目录 一.构造函数 二.C++类的内存模型 2.1.只定义成员函数 2.2.往空类中添加静态成员变量 2.3.再加入非静态成员变量 三.this指针 四.析构函数 一.构造函数 首先,由于类只是一个模板,因此我们在定义类时无法对成员变量初始化,比如下面代码就是错误的: class circle{ public: int m_L = 20; // Error:不允许使用数据成员初始值设定项 }; 因此,初始化只能发生在类创建对象的过程中,但是由于访问权限的原因,无法在类外访问某些成员变量,因此下面

  • C++中构造函数与析构函数的详解及其作用介绍

    目录 构造函数 默认构造函数 有参构造函数 析构函数 析构函数例子 析构函数执行时机 局部对象 全局对象 构造函数 构造函数 (constructor) 是一种特殊的成员函数. 它会在每次创建类的新对象时执行. 构造函数的名称与类的名称是完全相同的, 并且不会返回任何类型. 构造函数可用于为某些成员变量设置初始值. 格式: Class::Class(); // 构造函数 默认构造函数 如果用户自己没有定义构造函数, C++ 系统会自动生成一个默认构造函数. 这个构造函数体是空的, 没有参数, 不

  • C++构造函数和析构函数的使用与讲解

    构造函数(constructor) 1.构造函数是种特殊的类成员函数,遵循如下规则: a.函数名与类名必须相同. b.没有返回值 例如: class Obj { ... public: Obj() { ... } }; 2.构造函数可以带参数,也可以重载 class Obj { ... public: Obj() { ... } Obj(int x, int y) { ... } }; 3.构造函数和普通成员函数不一样,一般不显示调用.在创建一个对象时,构造函数自动调用(编译器来完成). 析构函

  • C++中构造函数与析构函数的调用顺序详解

    前言 在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序.在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用. 简单来说,其构造函数的顺序就一句话: 基类构造函数 -> 成员的构造函数 -> 构造函数体内语句 看下面一个代码示例: #include <iostream> using namespace std; class A { publ

  • C++编程析构函数拷贝构造函数使用示例详解

    目录 构造函数 析构函数 拷贝构造之深拷贝和浅拷贝 深浅拷贝区别 首先定义一个类进行操作. class MM { public: protected: int year; string name; } 构造函数在类中默认有一个无参的构造函数 默认的构造函数为 类名(){}:这个构造函数 如果直接写了构造函数那么这个构造函数将会没有 构造函数 class MM { public: //MM() {};//无参构造函数 MM(int year, string name) :year(year), n

  • C++类成员构造函数和析构函数顺序示例详细讲解

    对象并不是突然建立起来的,创建对象必须时必须同时创建父类以及包含于其中的对象.C++遵循如下的创建顺序: (1)如果某个类具体基类,执行基类的默认构造函数. (2)类的非静态数据成员,按照声明的顺序创建. (3)执行该类的构造函数. 即构造类时,会先构造其父类,然后创建类成员,最后调用本身的构造函数. 下面看一个例子吧 复制代码 代码如下: class c{public:    c(){ printf("c\n"); }protected:private:}; class b {pub

  • C++语法详解之封装、构造函数、析构函数

    大家先了解下什么是构造函数,什么是析构函数,作用是什么? 构造函数(方法)是对象创建完成后第一个被对象自动调用的方法.它存在于每个声明的类中,是一个特殊的成员方法.作用是执行一些初始化的任务.Php中使用__construct()声明构造方法,并且只能声明一个. 析构函数(方法)作用和构造方法正好相反,是对象被销毁之前最后一个被对象自动调用的方法.是PHP5中新添加的内容作用是用于实现在销毁一个对象之前执行一些特定的操作,诸如关闭文件和释放内存等. 下面在通过具体例子看下C++语法详解之封装.构

  • c++基础语法:构造函数与析构函数

    说实话c++还是以前在学校的时候用过的,从毕业到现在一直用c嵌入式编程,现在重新搬出C++语法 ,如果理解上有错误的地方,还请路过的朋友多指正--- 构造函数用来构造一个对象,主要完成一些初始化工作,如果类中不提供构造函数,编译器会默认的提供一个默认构造函数(参数为空的构造函数就是默认构造函数) :析构函数是隐式调用的,delete对象时候会自动调用完成对象的清理工作. 现在主要看看继承中的构造函数和析构函数的调用: 复制代码 代码如下: class  A {} :class  B : publ

  • 深入解析C++中的构造函数和析构函数

    构造函数:在类实例化对象时自动执行,对类中的数据进行初始化.构造函数可以从载,可以有多个,但是只能有一个缺省构造函数. 析构函数:在撤销对象占用的内存之前,进行一些操作的函数.析构函数不能被重载,只能有一个. 调用构造函数和析构函数的顺序:先构造的后析构,后构造的先折构.它相当于一个栈,先进后出. 复制代码 代码如下: #include<iostream>#include<string>using namespace std;class Student{ public:  Stud

  • C++超详细讲解构造函数与析构函数的用法及实现

    目录 写在前面 构造函数和析构函数 语法 作用 代码实现 两大分类方式 三种调用方式 括号法 显示法 隐式转换法 正确调用拷贝构造函数 正常调用 值传递的方式给函数参数传值 值传递方式返回局部对象 构造函数的调用规则 总结 写在前面 上一节解决了类与对象封装的问题,这一节就是对象的初始化和清理的构造函数与析构函数的内容了:对象的初始化和清理也是两个非常重要的安全问题:一个对象或者变量没有初始状态,对其使用后果是未知,同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题:c++利用了构

  • C++超详细讲解构造函数

    目录 类的6个默认成员函数 构造函数 特性 编译器生成的默认构造函数 成员变量的命名风格 类的6个默认成员函数 如果我们写了一个类,这个类我们只写了成员变量没有定义成员函数,那么这个类中就没有函数了吗?并不是的,在我们定义类时即使我们没有写任何成员函数,编译器会自动生成下面6个默认成员函数. class S { public: int _a; }; 这里就来详细介绍一下构造函数. 构造函数 使用C语言,我们用结构体创建一个变量时,变量的内容都是随机值,要想要能正确的操作变量中存储的数据,我们还需

  • Java超详细讲解接口的实现与用法

    目录 1.接口的定义 2.接口的实现 3.接口的引用 4.接口的继承 5.利用接口实现多重继承 1.接口的定义 接口是一种特殊的抽象类,是Java提供的一个重要的功能,与抽象类不同的是: 接口的所有数据成员都是静态的且必须初始化. 接口中的所有方法必须都是抽象方法,不能有一般的方法. [public] interface 接口名称 [extends  父接口名列表]{    [public] [static] [final]数据类型 成员变量名 = 常量;    ...    [public][

  • Java超详细讲解抽象类的原理与用法

    目录 1. 抽象类是什么 2 抽象类的语法 3 抽象类都有什么特性 4 抽象类是干什么的 1. 抽象类是什么 ️给大家上一篇小作文,看完这个,你就理解了什么叫做抽象类 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的, 如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. ️思考一下抽象类和类在功能上什么区别 因为抽象类是没有足够的信息去描绘一个具体的对象的,所以抽象类也就不能实例化对象 除此之外,抽象类的其它功能都是存在的,成员

  • C++超详细讲解拷贝构造函数

    目录 构造函数 特征 编译器生成的拷贝构造 拷贝构造的初始化列表 显式定义拷贝构造的误区 结论 构造函数 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用 拷贝构造函数是构造函数的一个重载,因此显式的定义了拷贝构造,那么编译器也不再默认生成构造函数. 特征 拷贝构造也是一个特殊的成员函数 特征如下: 拷贝构造是构造函数的一个重载: 拷贝构造的参数只有一个并且类型必须是该类的引用,而不是使用传值调用,否则会无限递归: 若没有显

  • C++超详细讲解析构函数

    目录 特性 析构函数处理自定义类型 编译器生成的默认析构函数 特性 析构函数是特殊的成员函数 特征如下: 析构函数名是~类名: 无参数无返回值: 一个类有且只有一个析构函数: 对象声明周期结束,编译器自动调用析构函数: class Stack { public: Stack(int capacity = 4) : _size(0), _capacity(capacity), _p(new int[_capacity]) { cout << "Stack(int capacity =

  • C++超详细讲解模拟实现vector

    目录 1. 模拟实现vector 2. vector常用接口 2.1 reserve 2.2 resize 2.3 push_back 2.4 pop_back() 2.5 insert 2.6 erase 2.7 构造函数的匹配问题 3. 更深层次的深浅拷贝问题 1. 模拟实现vector 我们模拟实现是为了加深对这个容器的理解,不是为了造更好的轮子. 快速搭一个vector的架子 // vector.h #pragma once #include <assert.h> // 模拟实现 --

  • java反射超详细讲解

    目录 Java反射超详解✌ 1.反射基础 1.1Class类 1.2类加载 2.反射的使用 2.1Class对象的获取 2.2Constructor类及其用法 2.4Method类及其用法 Java反射超详解✌ 1.反射基础 Java反射机制是在程序的运行过程中,对于任何一个类,都能够知道它的所有属性和方法:对于任意一个对象,都能够知道它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制. Java反射机制主要提供以下这几个功能: 在运行时判断任意一个对象所属

  • 超详细讲解Java线程池

    目录 池化技术 池化思想介绍 池化技术的应用 如何设计一个线程池 Java线程池解析 ThreadPoolExecutor使用介绍 内置线程池使用 ThreadPoolExecutor解析 整体设计 线程池生命周期 任务管理解析 woker对象 Java线程池实践建议 不建议使用Exectuors 线程池大小设置 线程池监控 带着问题阅读 1.什么是池化,池化能带来什么好处 2.如何设计一个资源池 3.Java的线程池如何使用,Java提供了哪些内置线程池 4.线程池使用有哪些注意事项 池化技术

  • 超详细讲解Java异常

    目录 一.Java异常架构与异常关键字 Java异常简介 Java异常架构 1.Throwable 2.Error(错误) 3.Exception(异常) 4.受检异常与非受检异常 Java异常关键字 二.Java异常处理 声明异常 抛出异常 捕获异常 如何选择异常类型 常见异常处理方式 1.直接抛出异常 2.封装异常再抛出 3.捕获异常 4.自定义异常 5.try-catch-finally 6.try-with-resource 三.Java异常常见面试题 1.Error 和 Excepti

随机推荐