C/C++函数返回值与形参实参需要注意的问题

目录

前言

一、函数实参形参绑定同步变化的要素

1. 形参实参不同步变化

2. 形参实参同步变化

 二、函数返回局部变量

1.返回指针类型局部变量 

<1> 指针指向变量内容存放在栈区(以数组为例)

<2> 指针指向变量内容存放在常量区(以常量字符串为例)

<3> 指针指向变量内容存放在堆区(以堆上的数组为例)

2.返回基本类型局部变量

总结


前言

        经过反复斟酌决定还是出一篇关于阐述函数返回值和形参实参合理使用的文章,重点在于指引C/C++语言学习者明白到底什么时候形参的变化可以引起实参的相应变化以及函数返回值是否可能存在不稳定或是存在隐患的问题,希望这篇文章可以给自己和众多学习者点明要害,得以更加清晰的看待函数调用与指针传参或返回指针的奥秘。

(注:本文中绝大多数讲解指针传递的稳定性,后续会写返回值类型为值、指针、引用的文章)


一、函数实参形参绑定同步变化的要素

首先来看以下代码,我们传入指针p然后将指针p指向的值修改为100,然后测试函数内输出:

void test1(int* pr)
{
	*pr = 100;
	cout << "test_p = " << *pr << endl;
}

试着调用该函数:

void use_test1()
{
	int a = 50;
	int *p = &a;
	cout << "p = " << *p << endl;   // 测试调用前指针指向的值
	test1(p);
	cout << "p = " << *p << endl;   // 测试调用后指针指向的值
}

运行结果:

C/C++函数返回值与形参实参需要注意的问题_第1张图片

可以看出调用test1(int* pr)函数成功改变了函数use_test1()函数中实参*p的值,说明这里形参*pr成功和实参*p建立了绑定关系。

好,接着对上面test1()函数略作修改:

void test1(int* pr)
{
	int num = 100;
	pr = #
	cout << "test_p = " << *pr << endl;
}

保持use_test1()函数不变,继续测试结果:

C/C++函数返回值与形参实参需要注意的问题_第2张图片

可以看出,不同于之前的执行结果,在经过函数test1(int* pr)后,实参*p的值并未发生改变,说明形参的变化并未绑定到实参。这是为什么?明明test1()函数传入的是指针,按理来说形参实参变化应该一致才对,接下来我将从细阐述原因:

  • 在第一个函数中,我们通过解引用操作修改了指针所指向的内存的值。这是因为我们传递给函数的是指针的副本,但副本和原始指针指向同一个内存地址。因此,当我们通过解引用修改指针所指向的内存时,原始指针也会受到影响。
  • 在第二个函数中,我们修改了pr指针的值,但这只是修改了函数内部的副本,不会影响到原始指针。当函数退出后,这个副本就被销毁了,不会对原始指针产生任何影响。

看到这里应该是更加迫切想要知道第二个函数为什么没有影响实参的值,那我先对第二个函数作出阐释:

1. 形参实参不同步变化

不妨先看调试窗口:

 在同一次调试运行中,可以看到指针p所指向的地址,接着进入内部函数test1(),如下:

由于存在语句 pr = # 所以pr指向的地址即为局部变量num的地址,那么对形参pr和实参p进行比较:

 发现两者所指向的地址并不相同,因为pr是p指针的副本,本来pr和p指向同一处内存,但是语句 pr = # 相当于改变了pr指针的指向,使得pr与p之间不再有绑定同步变化的关系

 上面这句话怎么证明呢?  接下来看给出的两个对比图,分别对应执行令形参改变指向的语句 pr = # 前后两个过程:

注意看上图执行语句前p和pr所指向的是同一个地址,接下来执行语句 pr = # 

对比发现:pr在执行语句前后指向的不是同一片内存,先后分别和p、&num一致

2. 形参实参同步变化

和上面一样,直接对程序进行调试,监视每一步执行后p和pr的地址是否发生了变化,直到已经执行完语句 *pr = 100; 仍然两者地址和开始一致,并未发生改变,所以对形参pr指向的内存进行改动时,实参pr指针的值会同步发生相应变化。

 直到程序运行即将结束时,两者地址未发生差异,同时在退出函数test1()的同时形参pr的值同步给了实参p,使得值同时都变为100:

 二、函数返回局部变量

1.返回指针类型局部变量 

<1> 指针指向变量内容存放在栈区(以数组为例)

const char* get_string()
{
	constexpr char c1[] = "abcde";    // constexpr用于修饰常变量
	return c1;
}

 以上代码中指针c1指向数组的首元素,返回的是一个指向字符数组的字符指针,下面调用函数查看结果:

void test2()
{
	const char* c = get_string();
	cout << c << endl;
}

运行结果:

 很显然get_string()函数返回值被接收时是一堆乱码,这是为什么?

在get_string()函数内部声明的c1数组以及它的值"abcde”是在栈上保存的,当用return将str的值返回时,将c1的值拷贝传回,当get_string()函数执行结束后,会自动释放栈上的空间,即存放"abcde"的单元可能被重新写入数据,所以获取返回值时得到的是一堆毫无逻辑的值。

<2> 指针指向变量内容存放在常量区(以常量字符串为例)

const char* get_string()
{
	const char* c1 = "abcde";
	return c1;
}

上面<1>中的test2()函数不变,调用此函数,运行结果:

 可以看出结果是正常的,说明指针正常传回了常量字符串的值

为什么这里却可以得到正常的返回值呢?

指针c1是存放在栈上的,但是"abcde”是一个常量字符串,因此存放在常量区,而常量区的变量的生存期与整个程序执行的生命期是一样的,因此在get_string()函数执行完后,c1指向存放“abcde”的单元,并且该单元里的内容在程序没有执行完是不会被修改的,因此可以正确输出结果

#  上面<1><2>看起来略有困难的可以先参考本人关于字符串的链接:http://t.csdn.cn/dD621   #

<3> 指针指向变量内容存放在堆区(以堆上的数组为例)

const char* get_string()
{
	char* c1 = new char[10];  // 在堆区申请空间存放数组
	strcpy(c1, "abcde");  // 对c1赋值
	return c1;
}

另外多提一句:堆区为数组申请内存应该是 new char[10] 而不是 new char(10) 

保持test2()函数不变,调用此函数,运行结果:

说明这种情况下同样可以输出正确的结果,是因为使用new关键字在堆上申请的空间,这部分空间是由程序员自己管理的,如果程序员没有手动释放堆区的空间,那么存储单元里的内容是不会被重写的,因此可以正确输出结果。

2.返回基本类型局部变量

int get_num()
{
	constexpr int num = 20;
	return num;
}

使用下面函数调用get_num()以测试返回值是否正常:

void test3()
{
	cout << get_num() << endl;
}

运行结果:

 测试字符串用值类型返回:

string get_str()
{
	const string s = "xswl";
	return s;
}

运行结果:

这里可能会有疑问,这些基本数据类型被复制后不是都存在于栈区吗,为什么这里可以正常传回局部变量的值?

在get_num()或get_str()函数执行完后,存放num或s值的单元是可能会被重写,但是在函数执行return时,会创建一个int型或string型(基本数据类型)的临时变量,将num或s的值复制拷贝给该临时变量,因此返回后能够得到正确的值,即使存放num或s值的单元被重写数据,但是不会受到影响。


总结

        通过理解函数参数传递方式、形参和实参的关系以及函数返回值的稳定性,我们可以更深入地理解函数调用的内部机制,避免潜在的问题,并编写出更高质量的代码。通过本文的指引和建议,希望读者能够更好地掌握函数参数与返回值的使用,提高代码的可读性、可维护性和性能。如果您对本文如有异议或是不懂的地方,欢迎私信我,祝您度过充实又快乐的一天!

你可能感兴趣的:(个人学习心得(C++),c++,c语言,开发语言)