当前位置: 首页 > news >正文

C++ 参数传递

一、参数传递:入与出

1.对于入参,拷贝开销低的类型按值传递,其他类型则以const引用来传递

2.对于转发参数,要用T&&来传递,并且只std::forward该参数

template <typename T, typename ... T1>
T create(T1&& ... t1)
{return T(std::forward<T1>(t1)...);
}

形参包的打包和解包

当省略号在参数类型T1的左边时,参数包被打包;当省略号在右边时,参数包被解包。返回语句T(std::forward<T1>(t1)...)中的这种解包实质上意味着表达式std::forward<T1>(t1)被不断重复,直到形参包的所有参数都被消耗掉,并且会在每个子表达式之间加一个逗号。

转发与变参模板的结合是C++中典型的创建模式

template<typename T, typename ... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

 3.对于入-出参数,使用非const的引用来传递

4.对于出的输出值,优先使用返回值而非输出参数

用返回值就好,但别用const,因为它不但没有附加价值,而且会干扰移动语义。也许你认为值的复制操作开销巨大,这既对也不对。原因在于编译器会应用RVO(return value optimization,返回值优化)或NRVO(named return value optimization,具名返回值优化)。RVO意味着编译器可以消除不必要的复制操作。到C++17,原本只是可能会做的优化成了一种保证。

MyType func()
{return MyType{};  //C++17中不会拷贝
}MyType myType = func(); //C++17中不会拷贝

这几行中可能会发生两次不必要的拷贝操作:第一次在返回调用中,第二次在函数调用中,C++17中则不会有拷贝操作发生。如果返回值有个名字,我们就称这种优化为NRVO。

MyType func()
{MyType myValue;return myValue;    //允许拷贝一次
}MyType myType = func();  //在C++17中不会拷贝

按照C++17,编译器仍然可以在返回语句中拷贝值myValue,但在函数调用的地方则不会发生拷贝。

5.要返回多个出值,优先考虑返回结构体或者多元组

std::set<int>::iterator iter;
bool inserted = false;std::tie(iter, inserted) = mySet.insert(0);
auto [iter2, inserted2] = mySet.insert(1);

二、所有权语义

  • func(value):函数func自己有一份value的拷贝并且就是其所有者。func会自动释放该资源
  • func(pointer*):func借用了资源,所以无权删除该资源。func在每次使用前都必须检查该指针是否为空指针
  • func(reference&):func借用了资源。与指针不同,引用的值总是合法的
  • func(std::unique_ptr):func是资源的新所有者。func的调用方显式地把资源的所有权传给了被调用方。func会自动释放该资源
  • func(std::shared_ptr):func是资源的额外所有者。func会延长资源的生存期。在func结束时,它也会结束对资源的所有权。如果func是资源的最后一个所有者,那么它的结束会导致资源的释放。

三、值返回语义

1.返回T*(仅仅)用于表示位置

Node* find(Node* t, const std::string& s)
{if (!t || t->name == s)  return t;if ((auto p = find(t->left, s)))  return p;if ((auto p = find(t->righr, s))) return p;return nullptr;
}

2.但不需要发生拷贝,也不需要表达“没有返回对象”时,应返回T&

A& operator=(const A& rhs) {...};
A operator(const A& rhs) {...};A a1, a2, a3;
a1 = a2 = a3;

返回拷贝(A)的拷贝赋值运算符会触发两个额外的A类型临时对象的创建。

返回局部对象的引用(指针)是未定义行为。

3.不要返回T&&以及不要返回std::move(本地变量)

4.main()的返回类型是int

四、其他函数

lambda表达式

1.当函数不适用时(需要捕获局部变量,或编写一个局部函数),请使用Lambda表达式

  • 如果可调用实体必须捕获局部变量或者它是在局部作用域内声明的,你就必须使用Lambda函数
  • 如果可调用实体需要支持重载,那么应使用普通函数

2.在局部使用(包括要传递给算法)的lambda表达式中,优先通过引用来捕获

3.在非局部使用(包括要被返回、存储在堆上或要传给其他线程)的lambda表达式中,避免通过引用来捕获。

lambda表达式应该只对有效数据进行操作。当lambda通过拷贝捕获数据时,根据定义,数据总是有效的。当lambda通过引用捕获数据时,数据的生存期必须超过lambda的生存期。

4.在有选择的情况下,优先采用默认参数而非重载

如果你需要用不同数量的参数来调用一个函数,尽可能优先采用默认参数而不是重载。这样你就遵循了DRY原则。

void print(const string& s, format f = {});

若要用重载实现同样的功能,则需要两个函数:

void print(const std::string& s);  //使用默认格式
void print(const std::string& s, format f);

5.不要使用va_arg参数

#include <cstdarg>int sum(int num, ...)
{int sum = 0;va_list argPointer;                 //保存下列宏的必要信息va_start(argPointer, num);          //启用对变参数函数的参数for (int i = 0; i < num; ++i){sum += va_arg(argPointer, int); //访问下一个变参数函数的参数}va_end(argPointer);                 //结束对变参数函数参数的访问return sum;
}int main()
{std::cout << "sum(1, 5)    " << sum(1, 5) << "\n";std::cout << "sum(3, 1, 2, 3)    " << sum(3, 1, 2, 3) << "\n";// 参数的num的数量是错的std::cout << "sum(3, 1, 2, 3, 4)    " << sum(3, 1, 2, 3, 4) << "\n";// double而不是int, 值错误std::cout << "sum(3, 1, 2, 3.5)    " << sum(3, 1, 2, 3.5) << "\n";
}

C++17的折叠表达式解决

template<typename ...Args>
auto sum(Args ...args)
{return (... + args);
}int main()
{std::cout << "sum(5)    " << sum1(5) << "\n";std::cout << "sum(1, 2, 3)    " << sum1(1, 2, 3) << "\n";std::cout << "sum(1, 2, 3, 4)    " << sum1(1, 2, 3, 4) << "\n";std::cout << "sum(1, 2, 3.5)    " << sum1(1, 2, 3.5) << "\n";
}

它需要至少一个参数,并使用C++11的变参模板。变参模板可以接受任意数量的参数。这些任意数量的参数由所谓的参数包持有,用省略号(...)表示。此外,在C++17中,可以用二元运算符直接对参数包进行归约。这一针对变参模板的增强被称为折叠表达式。在sum函数的例子中,应用了二元的+运算符(...+args)。

总结:

  • 一个函数应该执行一个操作,要简短,并有一个精心选择的名字。
  • 要把可以在编译期运行的函数实现为constexpr
  • 如果可能的话,将你的函数实现为纯函数
  • 当你的函数需要接受任意数量的参数时,要使用变参模板而不是va_arg参数

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Linux系统的服务——以Centos7为例
  • 迅睿CMS 后端配置项没有正常加载,上传插件不能正常使用
  • Python酷库之旅-第三方库Pandas(008)
  • node_sqlite3.node is not a valid win32 application
  • 工地/矿区/电力/工厂/环卫视频智能安全监控反光衣AI检测算法的原理及场景应用
  • RPC与REST
  • Perl 语言入门学习指南:探索高效脚本编程的奥秘
  • 昇思25天学习打卡营第18天|ShuffleNet图像分类
  • A Threat Actors 出售 18 万名 Shopify 用户信息
  • 量化机器人:金融市场的智能助手
  • DAY1: 实习前期准备
  • 在conda虚拟环境中安装llama-parse依赖
  • linux内核源码学习所需基础
  • HarmonyOS ArkUi 官网踩坑:单独隐藏导航条无效
  • 【计算机体系结构】缓存的false sharing
  • [笔记] php常见简单功能及函数
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • Babel配置的不完全指南
  • CentOS7简单部署NFS
  • E-HPC支持多队列管理和自动伸缩
  • Java 内存分配及垃圾回收机制初探
  • Javascript 原型链
  • javascript 总结(常用工具类的封装)
  • Java多线程(4):使用线程池执行定时任务
  • js
  • js学习笔记
  • python学习笔记-类对象的信息
  • Spark学习笔记之相关记录
  • yii2中session跨域名的问题
  • 仿天猫超市收藏抛物线动画工具库
  • 力扣(LeetCode)56
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 我建了一个叫Hello World的项目
  • 源码安装memcached和php memcache扩展
  • AI算硅基生命吗,为什么?
  • Nginx实现动静分离
  • 阿里云ACE认证之理解CDN技术
  • ​数据链路层——流量控制可靠传输机制 ​
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • #mysql 8.0 踩坑日记
  • (AngularJS)Angular 控制器之间通信初探
  • (c语言)strcpy函数用法
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)ssm码农论坛 毕业设计 231126
  • (一)utf8mb4_general_ci 和 utf8mb4_unicode_ci 适用排序和比较规则场景
  • (转)setTimeout 和 setInterval 的区别
  • (转)大道至简,职场上做人做事做管理
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .NET 的程序集加载上下文
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .net安装_还在用第三方安装.NET?Win10自带.NET3.5安装