为什么不把要返回元素直接当成函数的返回值返回呢

不把要返回元素直接当成函数的返回值返回的原因:如果把要返回元素直接当成函数的返回值返回,会使函数的返回值变得复杂,不易阅读和理解,并且会影响到使用该函数的程序的代码结构,还会使返回值的结构变得极其复杂,导致使用时难以理解和处理。

一、不把要返回元素直接当成函数的返回值返回的原因

如果把要返回元素直接当成函数的返回值返回,会使函数的返回值变得复杂,不易阅读和理解,并且如果要增加新的返回元素,则需要修改函数的返回值,这样会影响到使用该函数的程序的代码结构,不利于软件的扩展和维护,此外如果返回的元素之间有复杂的数据结构关系,例如嵌套的数据结构,还会使返回值的结构变得极其复杂,导致使用时难以理解和处理。

例如C里没有异常没有type deduction也无所谓构造函数,你的一个函数如果可能成功可能失败,通用做法是返回一个状态码,真正的返回值用传指针方式弄出去。这样基本也就是C能做到的最好效果了。C++里则完全不同,虽然传统方式差不多也是这样的,特别是某style guide这种邪教资料四处泛滥破坏历史发展进程的情况下。这个确实很无奈。C++标准前进的方向是该返回什么就返回什么,guaranteed copy elision、optional、structured binding、tuple都是可以使用的工具。直接返回该返回的对象优势也很多,不需要这个对象default constructible(可能需要std::optional / boost::optional / exception式error reporting),可以auto推导类型方便重构,部分场景下可以改进性能等等。错误报告可以像boost一样使用异常或者传指针/引用的error code来实现,一般error code的类型不会没事重构的。

二、函数返回值公式简介

函数可以有返回值,也可以没有返回值。

  • 没有返回值的函数:功能只是完成一个操作,应将返回值类型定义为void,函数体内可没有return语句。
  • 有返回值的函数:函数的最后会有一个返回值return,可以用来获取该函数执行结果返回给该函数,让外部调用该函数的。

返回值可指定,如果不指定默认返回None。函数的返回值类型是在定义函数时指定的。return 语句中表达式的类型应与定义函数时指定的返回值类型一致。如果不一致,则以函数定义时的返回值类型为准,对 return 语句中表达式的类型自动进行转换,然后再将它返回给主调函数使用。

在调用函数时,如果需要从被调函数返回一个值供主调函数使用,那么返回值类型必须定义成非 void 型。此时被调函数中必须包含 return 语句,而且 return 后面必须要有返回值,否则就是语法错误。一个函数中可以有多个 return 语句,但并不是所有的 return 语句都起作用。执行到哪个 return 语句,就是哪个 return 语句起作用,该 return 语句后的其他语句都不会执行。

三、C++中的返回值

函数可以有返回值,只要 return 就可以给出一个,不过常常不用它,有两个原因:第一,C/C++里返回值是复制出去的,而对于大的对象,复制的代价很高;第二,有些对象是不能复制的,至少编译器不知道怎么复制,比如数组。于是有了下面这样的函数:

bool GetObj(ObjType& obj);
bool Encode(const char* src, char* dest);

用一个参数来代替返回值,而返回值只是指示函数执行是否成功。返回一个大对象是困难的,但这个困难存在于 C 程序中,而不是 C++ 程序中。 C 函数里较少通过返回一个指针来返回对象,因为:

  1. 如果指针指向栈变量,毫无疑问,要么不用这个返回值,要么是一个错误。
  2. 如果指针指向堆变量,需要用这个函数的程序员会好好的看文档且足够细心会调用 free,要么就是内存泄漏。
  3. 如果指针指向 static 变量,那么用这个函数的程序员牢牢记住“下次调用这个函数以后,上次的返回值也会跟着变”。

在C++里直接返回裸指针的话,情况并不会有什么起色,不过 C++ 有智能指针的,通常它们指向堆变量,占用的空间和裸指针一样大。考虑前面第一个函数,写成:

std::auto_ptr<ObjType> GetObj();

返回值如果不要,作为临时变量,会立即被析构,返回的对象被释放;如果需要,就得赋值给另一个智能指针。总之不用程序员记得,编译器会保证这个对象的释放。

考虑第二个函数,稍微有一点麻烦,因为 auto_ptr 是不能用来持有数组的,不过,在C++的世界里,std::string 几乎总是比 char* 好用:

std::auto_ptr<std::string> Encode(const char* src);

最后考虑最麻烦的情况:

bool AssembleObjList(ObjType objList[], size_t length);

这种类型的函数无论是在 C 标准库里,还是在各种操作系统的 API 里,比比皆是,事实上它存在两大缺陷:第一,如果需要的数量超出给出的,要么是一个安全问题(经典的缓冲区溢出,如 strcpy),要么是失败,程序员不得不作出估计——众所周知,程序员的估计能力比他们的薪水低得多;第二,如果执行成功,到底 Assemble 了多少个?于是我们见到了这样的函数:

bool AssembleObjList(ObjType objLIst[], size_t* lengthPtr);

这个函数通常是两步调用的:

size_t length = 0;
AssembleObjList(0, &length);
ObjType* objList = new ObjType[length];
AssembleObjList(objList, &length);
for(size_t i=0; i<length; ++i)// 处理每个元素

这种形式能解决上面列出的两个问题,但这实在是太麻烦。如果用C++的库将会变得又干净,又舒服:

std::auto_ptr<std::vector<ObjType> > AssembleObjList();

延伸阅读

return 工作原理

被调函数运行结束后才会返回主调函数,但是被调函数运行结束后系统为被调函数中的局部变量分配的内存空间就会被释放。也就是说,return 返回的那个值在被调函数运行一结束就被释放掉了,那么它是怎么返回给主调函数的呢?

事实上在执行 return 语句时系统是在内部自动创建了一个临时变量,然后将 return 要返回的那个值赋给这个临时变量。所以当被调函数运行结束后 return 后面的返回值真的就被释放掉了,最后是通过这个临时变量将值返回给主调函数的。而且定义函数时指定的返回值类型实际上指定的就是这个临时变量的类型。

文章标题:为什么不把要返回元素直接当成函数的返回值返回呢,发布者:Z, ZLW,转载请注明出处:https://worktile.com/kb/p/49368

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Z, ZLW的头像Z, ZLW
上一篇 2023年4月15日
下一篇 2023年4月15日

相关推荐

发表回复

登录后才能评论
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部