C++系列-构造函数和析构函数

本文详细介绍了C++中的构造函数(包括无参、有参和拷贝构造)、析构函数的作用、调用方式以及注意事项,重点讨论了拷贝构造函数的调用时机和规则。

构造函数和析构函数

  • 在对象创建时,需要初始化一些内容—使用构造函数
  • 在使用完毕后,需要进行一些清理工作—使用析构函数
  • 构造函数和析构函数由编译器自动调用,不管写不写,编译器都会调用
  • 如果自己不定义,编译器会自动调用自己内部的构造和析构函数,是空实现

析构函数

  • ~类名(){}
  • 没有返回值,也不用写void
  • 函数名称与类名相同,前面加~
  • 析构函数不能有参数
  • 在对象销毁前会自动调用,且只调用一次
code:
#include<iostream>
using namespace std;
class Person
{
public:
	Person()
	{
		cout << "自定义构造函数被调用" << endl;
	}
	~Person()
	{
		cout << "自定义析构函数被调用" << endl;
	}
};
void test()
{
	Person p1;	// 栈上数据,在函数调用后会清理
	cout << "这是个测试项" << endl;
}
void main()
{
	test();
	system("pause");
}
result:
自定义构造函数被调用
这是个测试项
自定义析构函数被调用

构造函数

构造函数的语法

  • 类名(){}
  • 没有返回值,也不用写void
  • 函数名称与类名相同
  • 构造函数可以有参数,可以发生重载
  • 创建对象时会自动调用,而且只调用一次

构造函数的分类

  • 按照参数分
    – 有参和无参构造函数
  • 按照类型分
    – 普通和拷贝构造函数
拷贝构造函数
  • 定义拷贝构造函数,Person(const Person& p),里面可以使用p上的属性给要实例化的对象赋值

构造函数的调用方式

  • 括号法----推荐
    – 当构造函数无参时,实例化对象使用Person p1;
    – 当调用有参构造函数时,实例化对象使用Person p2(10);
    – 当调用拷贝构造函数时,实例化对象使用Person p2 (p1);
  • 显式法
    – 当构造函数无参时,实例化对象时使用Person p1;
    – 当调用有参构造函数时,实例化对象使用 Person p2 = Person(18);
    – 当调用拷贝构造函数时,实例化对象使用 Person p3 = Person(p2);
    – Person(18):本身是匿名对象,本来没名,赋值给左方,当前行执行完,就会释放
    – 不要使用拷贝构造函数初始化匿名对象,如单独的一行Person§; 编译器会认为是person p; 认为是在实例化对象p,重定义错误
  • 隐式法
    – 当调用有参构造函数时,实例化对象使用 Person p2 = 18; 编译器会自动转换为显示法
    – 当调用拷贝构造函数时,实例化对象使用 Person p3 = p2;

注意事项

  • 无参构造函数调用不能加括号,Person p1();编译器会认为是函数的声明,认为Person是返回类型
  • Person(18) 匿名对象,当前行执行完,会立马释放
  • 不要利用拷贝构造函数初始化匿名对象,Person(p3),编译器会认为是Person p3, 发生了重定义
#include<iostream>
using namespace std;
#include"circle.h"

class Person
{
private:
	int m_age = 10;
public:
	Person()
	{
		cout << "无参构造参数被调用" << endl;
	}
	Person(int age)
	{
		m_age = age;
		cout << "有参构造函数被调用," << "年龄是" << m_age << endl;
	}
	Person(const Person& p)		// 拷贝构造函数,传入的超参数是一个const引用,在实现部分,可以把传入的对象身上的任意属性值传给要进行实例化的对象
	{
		m_age = p.m_age;
		cout << "拷贝构造函数被调用, " << "年龄是" << m_age << endl;
	}
	~Person()
	{
		cout << "析构函数被调用," << "年龄是" << m_age << endl;
	}
};
void test1()
{
	// 括号法
	Person p1;			//不能加括号,Person p1();编译器会认为是函数的声明
	Person p2(18);
	Person p3(p2);
}
void test2()
{
	// 显式法
	Person p1;
	Person p2 = Person(18);
	Person p3 = Person(p2);
	Person(26);				// Person(18) 匿名对象,当前行执行完,会立马释放
	//Person(p3);			// 不要利用拷贝构造函数初始化匿名对象,Person(p3),编译器会认为是Person p3, 发生了重定义
}

void test3()
{
	// 隐式法
	Person p1;
	Person p2 = 66;
	Person p3 = p2;
}

void main()
{
	test1();
	cout << endl;
	test2();
	cout << endl;
	test3();
	system("pause");
}

result:
无参构造参数被调用
有参构造函数被调用,年龄是18
拷贝构造函数被调用, 年龄是18
析构函数被调用,年龄是18
析构函数被调用,年龄是18
析构函数被调用,年龄是10

无参构造参数被调用
有参构造函数被调用,年龄是18
拷贝构造函数被调用, 年龄是18
有参构造函数被调用,年龄是26
析构函数被调用,年龄是26
析构函数被调用,年龄是18
析构函数被调用,年龄是18
析构函数被调用,年龄是10

无参构造参数被调用
有参构造函数被调用,年龄是66
拷贝构造函数被调用, 年龄是66
析构函数被调用,年龄是66
析构函数被调用,年龄是66
析构函数被调用,年龄是10
请按任意键继续. . .

拷贝构造函数的调用时机

  • 利用存在的对象初始化新对象
  • 值传递的方式传参
  • 值方式返回局部对象
**#include<iostream>
using namespace std;
#include"circle.h"
class Person
{
public:
	int age = 10;
	Person()
	{
		cout << "无参构造参数被调用" << endl;
	}
	Person(int ref_age)
	{
		age = ref_age;
		cout << "有参构造函数被调用," << "年龄是" << age << endl;
	}
	Person(const Person& ref_p)
	{
		age = ref_p.age;
		cout << "拷贝构造函数被调用, " << "年龄是" << age << endl;
	}
	~Person()
	{
		cout << "析构函数被调用," << "年龄是" << age << endl;
	}
};
void check_ref(Person ref_p)
{
}
Person check_return()
{
	Person p1(33);
	cout << &p1 << endl;
	return p1;
}
void test1()
{
	Person p1(11);
	Person p2(p1);
}
void test2()
{
	Person p1(22);
	check_ref(p1);
}
void test3()
{
	Person p1;
	cout << &p1 << endl;
	p1 = check_return();
}

void main()
{
	test1();
	cout << endl;
	test2();
	cout << endl;
	test3();
	system("pause");
}**

构造函数的调用规则

  • 创建一个对象,C++编译器会给每个类至少添加3个函数
    – 默认构造函数(无参),空函数
    – 拷贝构造函数(值拷贝)
    – 析构函数,空函数
  • 如果自定义了有参构造函数,编译器就不再提供默认构造函数,但是仍然提供拷贝构造函数。
  • 如果自定义了拷贝构造函数,变异就不再提供其它的构造函数。
<think> 我们正在讨论C++中的空构造函数析构函数。根据提供的引用,我们可以总结如下: 引用[1]提到空类会被编译器加入一个char成员,以便实例化的对象占有空间。但这里更关心的是构造函数析构函数。 引用[2][3]详细说明了构造函数析构函数的基本概念: - 构造函数:在创建对象时初始化成员属性,由编译器自动调用。 - 析构函数:在对象销毁前执行清理工作,由编译器自动调用。 - 如果没有显式定义,编译器会生成默认的(空实现的)构造函数析构函数。 引用[4]展示了构造函数析构函数的具体使用示例,其中构造函数初始化了成员变量并分配了内存,析构函数则释放了内存。 引用[5]则说明了基类析构函数如果不是虚函数,则通过基类指针删除派生类对象时,派生类的析构函数不会被调用,这可能导致资源泄漏。 现在,针对用户的问题:空构造函数析构函数的作用。 1. 空构造函数(即函数体为空的构造函数): - 如果没有定义任何构造函数,编译器会生成一个默认的空构造函数(如果没有任何其他构造函数被定义的话)。但是,如果定义了其他构造函数,则编译器不会生成默认构造函数,除非显式地使用`=default`。 -构造函数的作用: - 允许创建对象而不需要任何参数(即无参构造函数)。例如,当创建对象数组时,每个元素都会调用无参构造函数- 即使函数体为空,编译器仍然会执行一些必要的操作,比如调用成员对象基类的构造函数(如果有的话)。所以,即使构造函数体为空,成员变量可能是由它们自己的默认构造函数初始化的(如果成员是类类型的话)。 - 对于内置类型的成员变量,如果没有在构造函数的初始化列表中显式初始化,它们将保持未初始化(即随机值)。因此,空构造函数可能不会初始化内置类型的成员变量,这需要注意。 2. 空析构函数(即函数体为空的析构函数): - 如果没有定义析构函数,编译器会生成一个默认的空析构函数-析构函数的作用: - 在对象销毁时,编译器会调用析构函数。如果析构函数为空,则不会执行额外的清理操作。 - 但是,即使析构函数体为空,编译器也会自动调用成员对象基类的析构函数(按照构造的逆序)。因此,如果成员是类类型,它们的析构函数会被正确调用。 - 如果类中没有需要手动释放的资源(如动态分配的内存、文件句柄等),那么空析构函数就是合适的。 重要注意事项: - 如果类中包含了动态分配的资源(如通过`new`分配的内存),则必须定义非空的析构函数来释放这些资源,否则会导致内存泄漏。 - 在继承体系中,基类的析构函数通常应该声明为虚函数(除非该类不会被继承)。引用[5]说明了如果基类析构函数不是虚函数,那么通过基类指针删除派生类对象时,派生类的析构函数不会被调用,从而可能导致资源泄漏。 总结: -构造函数:主要用于允许默认构造,编译器会调用成员基类的构造函数(如果有),但不会初始化内置类型(除非在初始化列表中指定)。 -析构函数:编译器会调用成员基类的析构函数,适用于没有需要手动释放资源的类。但要注意继承体系中的虚析构函数问题。 相关问题: 1. 什么情况下需要为类定义非空的构造函数? 2. 在C++中,基类的析构函数为什么应该声明为虚函数? 3. 默认构造函数(空构造函数)在什么时候会被编译器自动生成? 4. 如果一个类没有定义析构函数,那么编译器生成的默认析构函数会做什么? </think> ### C++中空构造函数析构函数的作用分析 #### 1. 空构造函数的作用 - **对象默认初始化** 当类未声明任何构造函数时,编译器会自动生成空构造函数(默认构造函数),用于创建对象实例。例如: ```cpp class MyClass { public: MyClass() {} // 显式定义的空构造函数 }; ``` 此时`MyClass obj;`能正常创建对象[^3]。 - **兼容容器操作** STL容器(如`vector`)要求元素类型必须有默认构造函数,空构造函数满足此要求: ```cpp std::vector<MyClass> vec(10); // 需要调用默认构造函数 ``` - **成员默认初始化** 若类包含类类型成员(如`std::string`),空构造函数会触发这些成员的默认构造: ```cpp class Widget { std::string name; // 调用std::string的默认构造函数 public: Widget() {} // 触发name的默认构造 }; ``` > **注意**:内置类型(如`int`)在空构造函数中不会被初始化,值为随机数[^4]。 #### 2. 空析构函数的作用 - **资源自动释放** 即使析构函数为空,编译器也会自动销毁所有成员变量(按声明逆序): ```cpp class Resource { std::unique_ptr<int> ptr; // 智能指针自动释放 public: ~Resource() {} // 空析构函数仍会调用ptr的析构 }; ``` - **支持继承体系** 在继承链中,派生类析构会自动调用基类空析构函数: ```cpp class Base { public: virtual ~Base() {} // 空虚析构支持多态销毁 }; class Derived : public Base { FileHandle file; // 资源句柄 ~Derived() override { file.close(); } // 先执行派生析构 }; // 后自动调用Base::~Base() ``` - **防止资源泄漏(占位)** 显式定义空析构函数可防止编译器生成默认析构函数,为后续添加资源释放逻辑预留位置。 #### 3. 关键特性对比 | 特性 | 空构造函数 | 空析构函数 | |---------------------|------------------------------|--------------------------------| | **编译器自动生成** | 未声明构造函数时生成 | 未声明析构函数时生成 | | **内置类型处理** | 不初始化(随机值) | 无操作 | | **类成员处理** | 调用其默认构造函数 | 调用其析构函数 | | **虚函数意义** | 构造函数不能为虚函数 | 虚析构函数支持多态销毁[^5] | #### 4. 使用场景 - **适用空构造** - POD(Plain Old Data)类型的数据传输对象 - 仅包含智能指针或RAII对象的类 - 需要STL容器兼容的类 - **适用空析构** - 不含手动分配资源的类(内存、文件句柄等) - 基类声明虚析构函数(即使函数体为空) - 作为接口类的默认实现 > **陷阱**:空析构函数在涉及多态时,若基类未声明`virtual`,会导致派生类资源泄漏[^5]: > ```cpp > Base* obj = new Derived(); > delete obj; // 若基类析构非虚,仅调用~Base() > ``` --- ### 相关问题 1. 为什么C++编译器会为没有构造函数的类生成默认构造函数? 2. 在哪些场景下必须手动定义非空的析构函数? 3. 虚析构函数如何解决多态对象销毁时的资源泄漏问题? 4. 空类(无成员变量)的默认构造函数析构函数有什么特殊行为?[^1] 5. 构造函数初始化列表相比在构造函数体内赋值有什么优势? [^1]: 空类会被编译器插入1字节占位符以保证实例可寻址 [^2]: 析构函数不可重载且自动调用 [^3]: 编译器强制要求初始化清理 [^4]: 构造函数初始化成员,析构函数释放资源 [^5]: 虚析构函数确保调用完整析构链
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值