C++类对象背后调用的方法

我们都知道类的设计是C++的基本思想,面向对象设计的重点就是如何将一个实体抽象成一个类,并定义它的成员方法来实现它的各种行为,定义它的成员变量来描述它的各种属性,这些都是C++最基础的东西,不再赘述,这篇博客主要是来说明C++的一个类应该具有的最基础的成员方法,也是最通用的方法。

一、代码例

开始分析之前先给出一个类,然后按照类中涉及的成员方法来进行分析,当然如果没有对这个进行了解的话可以跳过代码直接看下面的部分,然后再对照着看:

class String
{
    
    
public:
	//构造函数
	String(const char *ptr = nullptr)
	{
    
    
		if (ptr != nullptr)
		{
    
    
			mpstr = new char[strlen(ptr) + 1];
			strcpy(mpstr, ptr);
		}
		else
		{
    
    
			mpstr = new char[1];
			*mpstr = '\0';
		}
	}
	//左值引用的拷贝构造
	String(const String &str)
	{
    
    
		mpstr = new char[strlen(str.mpstr) + 1];
		strcpy(mpstr, str.mpstr);
	}
	//右值引用的拷贝构造
	String(String &&str)
	{
    
    
		mpstr = str.mpstr;
		str.mpstr = nullptr;
	}
	//赋值重载
	String& operator=(const String &str)
	{
    
    
		if (this == &str)//防止自赋值
		{
    
    
			return *this;
		}
		delete[]mpstr;
		mpstr = new char[strlen(str.mpstr) + 1];
		strcpy(mpstr, str.mpstr);
		return *this;
	}
	//右值引用的赋值重载
	String& operator=(String &&str)
	{
    
    
		delete[]mpstr;
		mpstr = str.mpstr;
		str.mpstr = nullptr;
		return *this;
	}
	//析构函数
	~String()
	{
    
    
		delete[]mpstr;
		mpstr = nullptr;
	}
private:
	char *mpstr;
};

二、构造函数与析构函数

1.构造函数负责一个类对象的初始化,在用类定义一个对象时自动调用,并按构造函数的内容对对象进行构造,它具有以下特点:
(1)系统中默认存在构造函数,当一个类中没有定义构造函数时,系统会生成一个空的默认构造函数;
(2)构造函数没有返回值,而且函数名称只能是对应的类名,可以带参,当然也可以重载。
在上面的代码中,类的私有成员变量只有一个指针,所以在构造函数中直接对这个字符串指针进行初始化即可:

	String(const char *ptr = nullptr)//因为传入的是常量字符串,所以使用常指针指向
	{
    
    
		if (ptr != nullptr)//若传入了字符串,则开辟空间存入数据,并将指针指向此空间
		{
    
    
			mpstr = new char[strlen(ptr) + 1];
			strcpy(mpstr, ptr);
		}
		else//反之则开辟一个空间存放“\0”,防止后续的方法都要进行判空
		{
    
    
			mpstr = new char[1];
			*mpstr = '\0';
		}
	}

2.析构函数则负责释放一个对象,当栈上的对象出作用域时它会自动调用析构函数来释放对象所占的空间,它的特点:
(1)可以手动调用,因为一个类的成员方法的调用是依赖于对象生成的this指针的,而对象未析构之前他的this指针依旧存在,所以可以调用,但不建议,因为程序结束的时候可能会导致对象的重复析构;
(2)析构函数也没有返回值,函数名称为“~类名”,不能带参,所以不能重载。

	~String()
	{
    
    
		delete[]mpstr;//释放外部资源
		mpstr = nullptr;//指针置为空
	}

三、拷贝构造函数

我们经常会使用一个对象去构造一个新对象,例如以下这种情况:

	String str4 = str3;//将str3的内容拷贝一份给新对象str4
	String str5(str3);//直接作为参数传入构造str5

在这种时候,如果一个类不占用外部资源,那么使用咱们的第一个构造函数是没有问题的,但现在这个类占用了外部资源,那么对于str4,则会直接将str3的成员变量的内容拷贝一份给str4,乍一看没有什么问题,但当程序结束,str3和str4的析构函数会按照mpstr中的地址去释放资源,此时的重复释放就会让程序直接崩溃:
在这里插入图片描述
这就是浅拷贝导致的问题,所以我们需要再定义新的构造函数来适应这种使用对象来构造对象的情况:
1.左值引用的拷贝构造函数:

	String(const String &str)
	{
    
    
		mpstr = new char[strlen(str.mpstr) + 1];//开辟新的空间
		strcpy(mpstr, str.mpstr);//复制其内容
	}

在拷贝构造函数中我们使用原对象的内容拷贝了一份新的内容给新的对象,这样就解决了对象资源的重复问题,在函数的参数设计上使用了左值引用。
在这里插入图片描述

2.右值引用的拷贝构造函数:
在代码的实际执行过程中,经常会有临时对象的生成,我们可能会使用这种临时对象来构造一个新的对象,但这个临时对象即将释放,貌似重新申请一份内存再存入数据不太划得来,所以就有了右值引用的拷贝构造函数:

	String(String &&str)
	{
    
    
		mpstr = str.mpstr;//直接过继资源
		str.mpstr = nullptr;//将即将释放的对象的指针置为空,而释放空指针是不会出错的
	}

我们直接将临时对象的资源过继给新对象,这样省时省力,切断了临时对象和资源的链接:
在这里插入图片描述

四、赋值重载函数

在有些时候我们会使用以下这种方式来给对象赋值:

	str1 = str2;

此时就涉及到了运算符的重载,因为在类中是没有办法直接使用“=”来给对象赋值的,所以我们还需要给类中增加一个新的成员方法来支持“=”的重载。
1.左值引用的赋值重载函数:

扫描二维码关注公众号,回复: 13293682 查看本文章
	String& operator=(const String &str)//使用对象来给对象赋值
	{
    
    
		if (this == &str)//防止自赋值
		{
    
    
			return *this;
		}
		delete[]mpstr;//释放对象的当前资源
		mpstr = new char[strlen(str.mpstr) + 1];
		strcpy(mpstr, str.mpstr);//重新申请资源并拷贝内容
		return *this;//将此对象返回
	}

它本身是一个运算符重载的函数,所以函数名为“operator”,参数为一个常引用,而返回值设置成引用是为了支持连续赋值,如果设置成void则不能支持下面这种形式的赋值:

	str1 = str2 = str3;

若str2返回值为空,则str1不能被正常赋值。

不过赋值重载函数中也会涉及到临时对象,所以按照右值引用的拷贝构造,再给赋值重载添加一个右值引用的形式:

	String& operator=(String &&str)
	{
    
    
		delete[]mpstr;
		mpstr = str.mpstr;
		str.mpstr = nullptr;
		return *this;
	}

也是直接将资源过继给要被赋值的对象,省时省力。

五、何时调用以上方法

基本的方法都已经列出,不过何时会调用他们呢?我们在对应的函数中加上输出函数名,并将上面的主函数改为如下形式,然后执行,就可以看到各个函数的调用时机:

int main()
{
    
    
	//构造函数
	String str1;
	String str2("hello");
	String str3 = "world";
	cout << "----------------------------" << endl;

	//拷贝构造函数
	String str4 = str3;
	String str5(str3);
	cout << "----------------------------" << endl;

	//赋值重载函数
	str1 = str2;

	return 0;
}

输出结果:
在这里插入图片描述
可以看到str1、str2、str3调用了默认的构造函数,而在str4和str5中则使用了左值引用的拷贝构造,最后使用了左值引用的赋值重载,然后析构所有对象;不过并不是所有使用“=”的都会调用赋值重载,例如str4,它虽然含有“=”,但是它是在构造阶段,所以还是使用了拷贝构造函数,而str1已经存在,所以在最后才会调用赋值重载,此点需要注意。而右值引用在这段代码中没有体现出来,不代表存在的没有意义,只是在从过程中未生成临时对象,它也是很重要的。

猜你喜欢

转载自blog.csdn.net/qq_45132647/article/details/105883163
今日推荐