cpp类对象作函数参数

cpp类对象作函数参数

参考:c++ Prime Plus(第六版) 第12章

传址or传值

函数传参有几种:指针、引用、值;前两个都是传入地址,最后一个传入对象的值

由于指针操作数据的灵活性高,且指针指的是一个地址,极容易直接修改这个地址里存储的数据,所以出现了引用(指针的语法糖)

引用的好处:

  1. 可以修改调用调用函数中的数据对象(在使用指针时,我们能加const就会加上,以免误操作)
  2. 传递的是引用,不是整个数据对象(提高层序运行速度)

为什么不能用传值代替传入const指针或者引用?

传值对于函数,它只能操作这个值,不能修改原数据;由此联想,我们传递一个值时可能会让程序更快的运行。但实际上传值并不安全,或者说容易在意想不到的地方挖坑,同时,在传递一个比较大类对象时,它的速度并不快

这里创建了一个test类,让我们在类外函数调用类对象看看会出现什么问题:

#include<string.h>
#include<iostream>

class test {
private:
	char* str;
	int len;
public:
	test(const char* s);
	~test();
	friend std::ostream& operator <<(std::ostream& os, const test& one);
};
test::test(const char* s) {
	len = strlen(s);
	str = new char[len + 1]();// 这里需要有()!否则会有想不到的奇怪问题
	strcpy_s(str,len+1, s);
}
test::~test() {
	std::cout << "goodbye," << str << std::endl;
    // 避免重复调用构造函数
	if (str) {
		delete[] str;
		str = nullptr;
	}
}
std::ostream& operator<<(std::ostream& os, const test& one) {
	os << one.str;
	return os;
}
void callme(test& rsb) {
	std::cout << "wanna test 传址:" << rsb << "\n";
}
void callme2(test sb) {
	std::cout << "wanna test 传值:" << sb << "\n";
}

int main() {
	test t1("I'm t1");
	test t2("俺是t2");
	// test t3 = t2;
	callme(t1);
	callme2(t2);
	std::cout << "end" << std::endl;
	//	system("pause");
	return 0;
}

让我们执行一下,如果你也用的是VS,那么你应该也会跳出这个页面:image-20220830134544948

最主要的原因应该是内存管理出现了问题,野指针、溢出等等,而不是编译器出了问题

虽然编译器在这里下了断点停止,但我们依旧能查看输出:

image-20220830211613819

可以看到在我们传值后,程序输出了一堆乱码(文本取决于内存的残留)

似乎传值并没有什么问题,它可以做到正常输出。但真的可以吗?事实上这个输出并不稳定,会随着编译器的改变收到影响;编译器运行时,无法解析传给callme2的类对象,函数也不能识别其原始字符串

这里可以参考c++ Prime Plus第351页内容

深拷贝和浅拷贝

在向callme2函数中传值时,我们原意是传入t2的str值,但是直接传值在cpp里是浅拷贝

  • 浅拷贝:单纯copy了指针的信息;副本指向了原数据的地址
  • 深拷贝:copy数据的同时还拷贝了数据的结构;创建了一块新的地址存储了副本数据

同时,在如果我们去掉析构函数中这一段内容,我们还会发现对于t2,析构函数调用了两次

if(str){
    delete[] str;
    str =nullptr;
}

在callme2函数浅拷贝t2后,t2的生命周期将等同于callme2的函数周期(浅拷贝时副本指向了原有的t2地址,callme函数中的t2又仅为一个局部变量),函数结束时将调用一次析构函数,而main函数结尾时又将调用一次t2的析构函数,而在callme结束时,t2已经被删掉了,此时就会输出乱码

类对象作参

接着我们上面的代码,添加一个t3对象,调用callme函数看看

// 我们刚才用到的函数
class test{
private:
	char* str;
	int len;
public:
	test(const char* s);
	~test();
	friend std::ostream& operator <<(std::ostream& os, const test& one);
}
void callme(test& rsb);

// 创建的对象
test t1("I'm t1");
test t2("I'm t2");
test t3 = t2; // t3由t2得到
callme(t3);

这里我们将使用t2来初始化t3,同样出现了乱码,说明我们这里仍为浅拷贝,t2和t3分别调用了同一地址的析构函数,在t3先一步消除了t2的结构后,t2的指针此时成为了野指针。这也就是指针悬挂问题。

image-20220830214251850

我为什么说是先删除t3再删除t2:自动存储对象被删的循序与创建顺序相反(栈是先进后出嘛)

复制构造函数

在使用上述语句时,我们都是在用隐式复制构造函数(它是按值进行复制的)

这些问题,都可以通过定义显示复制构造函数解决:它可以copy一个类对象的副本到新的对象中。

test::test(const test& t){
    len = strlen(t.str)+1;
    str = new char [len]();
    strcpy_s(str, len, t.str);
}

可以用以下几种方式创建t3:

test t3(t2);
test t3 = t2;
test t3 = test(t2);
test *t3 = new test(t2);

END

注:笔者才疏学浅,如有错误,请指教!

posted @ 2022-08-30 22:03  比萨在哭  阅读(253)  评论(0编辑  收藏  举报