【C++】9.函数[深蓝学院C++第7章]
一.函数基础
封装了一段代码,可以在一次执行过程中反复调用。
函数头:
(1)函数名称——标识符,用于后序的调用
(2)形式参数——代表函数的输入参数,函数真正被调用时传入的具体参数叫做实际参数:实参
(3)返回类型——函数执行完成后所返回的结果类型
函数体:
(1)为一个语句块block,包含了具体的计算逻辑
函数声明与定义:
(1)函数声明只包含函数头,不包含函数体,通常置于头文件中
(2)函数声明可以出现多次,但函数定义通常只能出现一次
函数调用:
(1)需要提供函数名和实际参数
(2)实际参数拷贝初始化形式参数,
(3)返回值会被拷贝给函数的调用者
(4)栈帧结构,压栈与出栈
拷贝过程的省略:
(1)返回值优化
(2)C++17强制省略拷贝临时对象
函数的外部链接extern C:
(1)C不支持重载,C++支持重载,为了保持函数名不被mangoly,在函数名前添加extren "C",保持函数名在可执行文件中的名称保真,为C语言等其他语言调用友好
二.函数详解
2.1参数
函数可以在函数头的小括号中包含零到多个形参:
(1)包含零个形参时可以使用void标记
(2)对于非模板函数来说,其每个形参都有确定的类型,但形参可以没有名称
(3)形参名称的变化并不会引入函数的不同版本
(4)实参到形参的拷贝求值顺序不定,void fun(int z,int y);//x和y的初始化顺序是不确定的,由编译器决定
(5)C++17强制省略复制临时对象
函数传值、传址、传引用:
(1)传值,拷值进去、里面的修改不会改变外面的变量
(2)传址,传入地址,间接引用,可以改变源数据
(3)传引用,传入引用,可以改变源数据
函数传参过程中的类型退化:
(1)void fun(int* arr); 传入数组int a[3],类型会从int[3]退化为int*。为了防止类型退化可以传入引用
变长参数:
(1)std::initializer_list<T>,使用模板,void fun(std::initializer_list<int> par);
(2)可变长度模板参数,可以传入不同类型的参数
(3)使用省略号表示形式参数,较少使用
函数可以定义缺省实参:
(1)如果某个形参具有缺省实参,那么它右侧的形参都必须具有缺省实参
(2)在一个翻译单元中(如一个源代码文件中),每个形参的缺省实参只能定义一次(暂略该内容)
(3)具有缺省实参的函数调用时,传入的实参会按照从左到右的顺序匹配形参
(4)缺省实参为对象时,实参的缺省值会随着对象值的变化而变化
main函数的两个版本:
(1)无形参的版本,int main()
(2)待两个形参的版本,int main(int argc,char *argv[]),argc表示实参个数,argv是参数指针,有argc+1个元素
2.2函数体
函数体形成域:
(1)其中包含了自动对象
(2)也可包含局部静态对象
函数体执行完成时的返回:
(1)隐式返回,main可以默认返回0
(2)显式返回关键字: return
● return; 语句
● return 表达式 ;
● return 初始化列表 ;
(3)小心返回自动对象的引用或指针,而源数据已经被销毁;
(4)返回值优化( RVO ) —— C++17 对返回临时对象的强制优化,return value organization,暂略
2.3返回类型
返回类型表示了函数计算结果的类型,可以为 void
返回类型的几种书写方式
(1)经典方法:位于函数头的前部
(2)C++11 引入的方式:位于函数头的后部,auto fun(int a,int b) -> int,可以简化函数的定义,auto S::fun(int a,int b)->MyClass,会去S域中查找
(3)C++14 引入的方式:返回类型的自动推导,使用 constexpr if 构造 “ 具有不同返回类型 ” 的函数,类似#ifdef,在编译器分支
返回类型与结构化绑定( C++ 17 ),fun将返回一个带有2个元素的结构体,可以使用auto接收,auto [v1,v2]=fun();//语法糖
[[nodiscard]] 属性( C++ 17 ),禁止不处理返回值
三.函数重载与重载解析
3.1函数重载
函数重载:使用相同的函数名定义多个函数,每个函数具有不同的参数列表:
(1)不能只改变返回值而实现重载
(2)函数重载与name mangling,因重载而生,编译器会作一定的映射
编译器如何选择正确的版本并调用?见CppCon上的资料,Calling Functions:A Tutorial
名称查找:
(1)限定查找( qualified lookup )与非限定查找( unqualified lookup ),使用域操作符进行限定查找,不指定域就是非限定
(2)非限定查找会进行域的逐级查找 —— 名称隐藏( hiding ),就近域的声明会把外围的同名隐藏、覆盖掉
(3)查找通常只会在已声明的名称集合中进行,就近原则
(4)实参依赖查找( Argument Dependent Lookup: ADL ),只对自定义类型生效,在牵连的类型的域中查找
3.2重载解析
重载解析:在名称查找的基础上进一步选择合适的调用函数
暂略
四.函数相关的其他内容
4.1递归函数
递归函数:在函数体中调用其自身的函数,通常用于描述复杂的迭代过程
4.2内联函数
inline说明符
编译器将部分代码展开至调用处
当多处出现相同代码的展开时,如果标注了inline则链接器会选择一个不会报错,如果没有标注inline链接器就会报错
实际上inline说明符是将程序级别的一次定义原则提升至编译单元级别的一次定义原则。
4.3Constexpr和consteval函数
略
4.4函数指针
4.4.1函数类型与函数指针类型
函数也是一种对象,也有类型,int fun(double x)的类型是int(double)
数组类型和函数类型的相同之处:
(1)int a[3]; using K = int[3]; K b;//b是一个int[3]
(2)using K=int(int); K fun;//则fun是一个int(int)函数
数组类型和函数类型的不同之处:
(1)对于数组K b;是定义了一个数组,开辟了内存;对于函数K fun是声明了一个函数
函数指针类型:
(1)using K = int(int);
(2)K* fun;//该指针可以指向一个函数
(3)fun=&匹配的函数;
4.4.2函数指针与重载
因为函数有重载,在声明函数指针时就得显示地指定指针的类型
4.4.3将函数指针作为函数参数
引入函数指针的原因:高阶函数,可用于运行时切换函数调用,比如std:;transform,泛型编程
vector<int> a;
std::transform(a.begin(),a.end(),a.begin(),&add/dec);//add和dec可以是自定义的算法函数
回调函数
4.4.3将函数指针作为函数返回值
略
小心: Most vexing parse(令人苦恼的解析)
4.4.4代替函数指针
代替函数指针更安全的方式:lambda表达式
本文作者:OhOfCourse
本文链接:https://www.cnblogs.com/OhOfCourse/p/17159825.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步