C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ 默认成员函数

C++六大默认成员函数的实现

作者:落水 zh

C++中的六大默认成员函数包括默认构造函数、默认析构函数、默认拷贝构造函数、默认拷贝赋值运算符、移动构造函数和移动赋值运算符,本文就来介绍一下这些函数的使用,感兴趣的可以了解一下

C++中的六大默认成员函数是编译器在特定条件下自动生成的成员函数,用于管理对象的生命周期和资源操作。它们分别是:

默认构造函数

class Person
{
public:
	//Person()
	//{

	//} 不写的话默认自动生成
	void GetAge()
	{
		std::cout << _age << std::endl;
	}
private:
	int _age;
};

int main()
{
	Person p;
	p.GetAge();
}

其特征如下:

函数名与类名相同

无返回值。

对象实例化时编译器自动调用对应的构造函数。

构造函数可以重载

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

编译器生成默认的构造函数会对自定类型成员调用的它的默认成员
函数。C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

默认析构函数

class Person
{
public:
	//Person()
	//{

	//} 不写的话默认自动生成
	void GetAge()
	{
		std::cout << _age << std::endl;
	}

	~Person()
	{

	}
private:
	int _age;
};

int main()
{
	Person p;
	p.GetAge();
}

RAII技术

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中一种管理资源的编程技术。它通过将资源的生命周期与对象的生命周期绑定在一起,利用C++的构造函数和析构函数来自动管理资源,从而避免了手动分配和释放资源可能带来的问题,如内存泄漏、资源未正确释放等。

RAII的核心思想

优点

示例

以下是一个简单的例子,展示了如何使用RAII来管理动态分配的内存:

#include <iostream>

class ResourceHandler {
private:
    int* data;
public:
    // 构造函数:资源获取
    ResourceHandler() {
        data = new int(10); // 分配资源
        std::cout << "Resource acquired." << std::endl;
    }

    // 析构函数:资源释放
    ~ResourceHandler() {
        delete data; // 释放资源
        std::cout << "Resource released." << std::endl;
    }

    void showData() const {
        std::cout << "Data: " << *data << std::endl;
    }
};

void useResource() {
    ResourceHandler handler;
    handler.showData();
    // 不需要手动释放资源,handler离开作用域时会自动调用析构函数
}

int main() {
    useResource();
    return 0;
}

在这个例子中,ResourceHandler类负责管理一个整数类型的动态分配内存。构造函数在对象创建时分配资源,而析构函数在对象销毁时释放这些资源。这样就确保了无论函数useResource如何退出(正常结束或因异常退出),资源都会被正确释放。

应用场景

RAII不仅限于内存管理,还可以应用于其他资源类型,如文件句柄、网络套接字、数据库连接等。标准库中的智能指针(如std::unique_ptrstd::shared_ptr)、锁机制(如std::lock_guardstd::unique_lock)都是RAII原则的实际应用案例。通过使用这些工具,可以有效地减少资源管理错误,提高代码的安全性和可靠性。

默认拷贝构造

class Person
{
public:
	Person()
	{

	}

	Person(const Person& person)
	{
		this->_age = person._age;
	}

	~Person()
	{

	}

	void GetAge()
	{
		std::cout << _age << std::endl;
	}
private:
	int _age;
};

int main()
{
	Person p;
	p.GetAge();
}

深拷贝和浅拷贝

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	//默认生成拷贝构造

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2(s1);
}

这里我没有写实际的拷贝构造函数,这里s2调用的默认的拷贝构造,所以s2_array的地址就是s1中_array的地址,这就叫浅拷贝:

在这里插入图片描述

这样代码就会有问题,因为一个地址会被析构两次:

在这里插入图片描述

正确的方法应该是给s2的array开辟一块新的空间:

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	Stack(const Stack& st)
	{
		_array = new int[10];
		_size = st._size;
		_capacity = st._capacity;
	}

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2(s1);
}

在这里插入图片描述

这样的拷贝我们称为深拷贝,再次运行程序:

在这里插入图片描述

默认拷贝赋值运算符

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	Stack(const Stack& st)
	{
		_array = new int[10];
		_size = st._size;
		_capacity = st._capacity;
	}

	Stack& operator=(const Stack& st)
	{
		if (this != &st)
		{
			_array = new int[10];
			_size = st._size;
			_capacity = st._capacity;
		}

		return *this;

	}

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2;

	s2 = s1;
}

在这里插入图片描述

移动构造函数(C++11起)

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	Stack(const Stack& st)
	{
		_array = new int[10];
		_size = st._size;
		_capacity = st._capacity;
	}

	Stack& operator=(const Stack& st)
	{
		if (this != &st)
		{
			_array = new int[10];
			_size = st._size;
			_capacity = st._capacity;
		}

		return *this;

	}

	void swap(Stack& st)
	{
		std::swap(_array, st._array);
		std::swap(_size, st._size);
		std::swap(_capacity, st._capacity);
	}

	//移动构造函数
	Stack(Stack&& st):_array(nullptr), _size(0), _capacity(0)
	{
		swap(st);
	}

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2(std::move(s1));
}

在这里插入图片描述

默认移动赋值运算符(C++11起)

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	Stack(const Stack& st)
	{
		_array = new int[10];
		_size = st._size;
		_capacity = st._capacity;
	}

	Stack& operator=(const Stack& st)
	{
		if (this != &st)
		{
			_array = new int[10];
			_size = st._size;
			_capacity = st._capacity;
		}

		return *this;

	}

	void swap(Stack& st)
	{
		std::swap(_array, st._array);
		std::swap(_size, st._size);
		std::swap(_capacity, st._capacity);
	}

	//移动构造函数
	Stack(Stack&& st):_array(nullptr), _size(0), _capacity(0)
	{
		swap(st);
	}

	//移动复制构造
	Stack& operator=(Stack&& st)
	{
		swap(st);

		st._array = nullptr;
		st._size = 0;
		st._capacity = 0;

		return *this;
	}

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2;

	s2 = std::move(s1);
}

在C++中,前置++和后置++运算符可以通过成员函数或非成员函数的形式进行重载。两者的主要区别在于参数列表和返回值:

取地址及const取地址操作符重载

在C++中,取地址操作符(&)和常量取地址操作符(const &)通常不需要显式地重载,因为编译器提供了默认的实现,它们分别返回对象或常量对象的内存地址。然而,在某些特定情况下,你可能想要自定义这些操作符的行为。

取地址操作符重载

当你重载取地址操作符时,你通常是为了改变其默认行为,例如返回一个代理对象的地址而不是原始对象的地址。不过这种情况非常少见,大多数时候并不需要这样做。

常量取地址操作符重载

类似地,重载常量版本的取地址操作符也是为了提供特定的行为,但同样,这并不是常见的需求。

示例代码

尽管不常见,这里还是给出如何重载这两种操作符的基本示例:

#include <iostream>

class MyClass {
private:
    int value;
public:
    MyClass(int val) : value(val) {}

    // 重载取地址操作符
    int* operator&() {
        std::cout << "非const取地址操作符被调用" << std::endl;
        return &value;
    }

    // 重载const取地址操作符
    const int* operator&() const {
        std::cout << "const取地址操作符被调用" << std::endl;
        return &value;
    }
};

int main() {
    MyClass obj(10);
    const MyClass constObj(20);

    int* addr = &obj;       // 调用非const版本
    const int* constAddr = &constObj; // 调用const版本

    std::cout << "*addr: " << *addr << std::endl;
    std::cout << "*constAddr: " << *constAddr << std::endl;

    return 0;
}

输出结果

非const取地址操作符被调用
const取地址操作符被调用
*addr: 10
*constAddr: 20

在这个例子中,我们为MyClass类重载了取地址操作符和常量取地址操作符。当通过非常量对象调用operator&()时,会调用非常量版本的操作符,并打印一条消息。而当通过常量对象调用operator&()时,则调用常量版本的操作符,并打印另一条不同的消息。

注意事项

总的来说,重载取地址操作符和常量取地址操作符是一种高级技巧,适用于特定场景下的特殊需求。在大多数日常编程任务中,这种重载是不必要的。

扩展:前置++和后置++重载

重载规则

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class Count
{
public:
	//重载后置++
	Count operator++()
	{
		++_count;
		return *this;
	}

	//后置++
	Count operator++(int)
	{
		Count temp = *this;
		++_count;
		return temp;
	}

	int getCount() const {
		return _count;
	}
private:
	int _count = 0;
};

int main()
{
	Count myCounter;

	std::cout << "Initial count: " << myCounter.getCount() << std::endl;

	++myCounter; // 调用前置++
	std::cout << "After prefix increment: " << myCounter.getCount() << std::endl;

	Count d;
	d = myCounter++; // 调用后置++
	std::cout << "After postfix increment: " << d.getCount() << std::endl;

	return 0;
}

到此这篇关于C++六大默认成员函数的实现的文章就介绍到这了,更多相关C++ 默认成员函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:
阅读全文