-
函数
- 定义包括
- 返回类型
- 函数名字
0
个或多个形参列表,列表位于一对圆括号中,形参之间以逗号分隔开- 函数体,位于语句块中
- 不允许嵌套定义
-
函数通过调用运算符
()
来执行函数 -
函数的执行过程
- (隐式的)定义并且初始化它的形参
-
编译器能以任意可行的顺序对实参求值
-
没有形参的列表
void f1(){}; // 和C语言兼容 void f2(void){};
-
函数的返回类型不能是数组或者函数类型。但是可以是指向数组或者函数的指针。
-
自动对象
- 普通局部变量,控制流经过变量定义语句时创建该对象。到达定义所在块末尾时销毁它。只存在于块执行期间的对象称为自动对象
- 形参时自动对象,函数开始为形参申请存储空间,因为形参定义在函数体作用域之内,一旦函数终止,形参被销毁
- 局部静态对象,有必要令局部变量的声明周期贯穿函数调用以及之后的时间。可以把局部变量定义为
static
类型。控制流第一次经过对象定义语句时初始化,直到程序终止才被销毁。内置类型的局部静态变量初始化为0
-
引用类型的参数可以用来充当多值返回函数
-
const
形参和实参。形参的初始化过程基本上就等于const
变量被初始化,符合一样的规则 -
尽量用
const
引用的形参,因为const
引用形参的可接受范围比普通引用形参的可接受参数的范围大,因为底层const
的限制 -
不能以值传递的方式传递数组,但是并不妨碍出现形参位数组的形式
- 定义接收数组的函数
void print(const int*); void print(const int[]); // 这里的维度表示我们期望数组含有多少元素,实际上不一定 void print(const int[10]);
- 接收数组的函数如何知道数组的长度
c
风格的字符串就是使用\0
来表示。其他的int
数组就没有什么明显有效的特殊不合法数字。- 所以标准库规范是传递首尾指针。
- 另外可以传入表示数组大小的参数
c++
允许使用数组的引用作为形参。因为变量允许。
void print(int (&arr)[10]); int j[2]={0,1}; // 错误,实参不是含有10个整数的数组 print(j);
- 编译器处理数组参数的时候一定会忽略第一维数组的大小,所以没有必要写上去
- 可变形参,省略符形参是为了便于
c++
程序访问某些特殊的c
代码而设置的,这些代码使用了varargs
的c
标准库功能。省略符形参只能出现在形参列表的最后一个位置。形式只能为以下两种
// 第一种,对于这种指定部分形参的部分会进行正常的类型检查。省略符形参对应的实参无须类型检查。此处的逗号是可选的 void foo(param_list,...); void foo(...);
-
调用约定
cdel
标准调用约定,同时也是默认约定
如果函数A
调用函数B
,那么称函数A
为调用者(caller
),函数B称为被调用者(callee
)。caller
把向callee
传递的参数存放在栈中,并且压栈顺序按参数列表中从右向左的顺序;callee
不负责清理栈,而是由caller
清理stdcall
cdecl
fastcall
thiscal
naked call
-
c
语言可变参- 引入相应的库
#include <stdarg.h>
- 栈对齐
函数参数是有对齐的,每个参数不管sizeof
是多少,均以系统栈对齐参数做对齐。公式如下
#define SYSTEM_ALIGN #define CAL_ALIGN = SYSTEM_ALIGN - 1 #define CAL_SIZE(type) (sizeof(type)+CAL_ALIGN)&(~CAL_ALIGN)
-
函数可以返回引用值(函数左侧需要加上
&
引用符号)。返回引用值的函数得到左值,其他类型得到右值。函数返回的左值可以赋值,常量左值无法赋值。不能返回局部变量的引用和指针 -
返回数组的引用和数组的指针
- 使用类型别名来返回数组指针
using intArr=int[10]; intArr* func(){};
- 不使用类型别名来返回数组的指针的函数
// 如果没有此括号,则语义变成了返回指针的数组,这样就不合法并且会报错了 int (*func(int i))[10];
-
重载
main
函数不能被- 顶层
const
不能用来重载 - 底层
const
可以用来重载。并且具有const
的可以传入const
或者非const
的对象,编译器会优先选择非常量版本的函数 - 重载确定
overload resolution
,编译器首先将调用的实参与重载集合中的每一个函数形参进行比较,然后根据比较的结果决定到底调用哪个函数- 编译器找到一个与实参最佳匹配
best match
的函数 - 找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误
- 有多个函数可以用于匹配,但是每一个都不是明显的最佳选择此时也将发生错误,称为二义调用
ambiguous call
- 编译器找到一个与实参最佳匹配
- 重载和作用域,只有再相同的作用域中才能重载函数名。另外
c++
允许在局部作用域中声明函数。还有个值得注意的是,如果在局部作用域中声明了标志符,那么不管是变量还是函数,编译器都只会找到这一个而不会再去看外层的作用域的符号了。 c++
的名字查找发生在类型检查之前- 函数匹配过程
- 第一步时确定候选函数
candidate function
。具有于被调用的函数同名,且声明在调用点可见 - 考察实参,从候选函数中选出能被这组实参调用的函数,选出的函数称为可行函数
viable function
。具有形参数量和调用提供的实参数量相复合,实参的类型于对应的形参类型相同或者能转换成形参的类型 - 从可行函数中选择最佳匹配。有且只有一个函数满足下列条件则匹配成功
- 该函数的每个实参的匹配都不劣于其他可行函数需要的匹配
- 至少有一个实参的匹配优于其他可行函数提供的匹配
- 如果上述检查之后没有一个函数脱颖而出,则调用错误,将报告二义性调用。
- 第一步时确定候选函数
- 使用强制类型转换可以避免二义性调用,但是这样说明设计的形参不合理
- 实参形参转换的精确性等级划分
- 精确匹配
- 实参的类型和形参的类型相同
- 实参从数组类型或者函数类型转换成指针类型
- 向实参添加顶层
const
或者从实参中删除顶层const
- 通过
const
转换实现的匹配(底层const
转换,比如char*
转变成const char*
,如果重载函数的区别在于它们引用类型的形参是否引用了const
,或者指针类型的形参是否指向const
,则当调用发生时编译器通过实参是否为常量来决定哪个函数) - 通过类型提升实现的匹配(整型提升)
- 通过算术类型转换或者指针转换实现的匹配
- 通过类类型转换实现的匹配
- 类型提升和算术类型转换的匹配
void ff(int); void ff(short); ff('a');//char提升为int,调用ff(int) void aa(long); void aa(float); aa(3.14);// double可以转换成上述两种,二义调用
-
默认实参,一旦一个形参被赋予了默认值,则它后面的所有形参都必须有默认值。原因是为了避免歧义,
c++
中的默认形参调用方法是直接省略逗号和实参值。而在vb
中可以用,,
来表示中间的是一个默认实参。// 具有默认实参 void test(int a=1,int b=2); // 具有默认实参的错误形式,这样会编译错误。 void test(int a=1,int b=2,int c); // 此时无法区分是绑定到哪个形参 test(1,2);
c++
函数支持多次声明,但是声明只能修改原来函数的默认实参。后续声明只能为那些没有默认值的形参添加默认实参,而不能为一个形参赋予多次默认实参
string scree(sz,sz,char = ' ' ); // 错误,重复声明 string scree(sz,sz,char = '*' ); // 正确,添加一个默认实参,原来的char类型不变 string scree(sz,sz = 80,char);
#include <iostream> using namespace std; int b(int c,int d=1,int e=2); // 下面的重复定义默认值会报错 int b(int c, int d, int e=3); int b(int c,int d,int e); int b(int c, int d, int e) { cout << d <<"\n"<< e << endl; return 2; } int main() { // 输出2 3 b(1, 2, 3); // 输出1 2 b(1); _fgetchar(); }
- 默认实参初始值可以是任意一个表达式,只要改表达式的类型能转化成形参所需的类型。。并且用作默认实参的名字在声明所在的作用域内解析。另外默认实参求值是发生在函数调用的时候,也就是意味着默认实参的值并不是一个
constexpr
sz wd=80; char def=' '; sz ht(); string scree(sz = ht(),sz = wd,char=def); void f2(){ def = '*';//改变了默认实参的值 sz wd=100;//隐藏了外层定义的wd,但是没有改变默认实参的值 window=scree(); }
-
内联函数和
constexpr
函数,使用inline
关键字。只是像编译器发出一个请求,编译器可以选择忽略这个请求。递归函数和比较长的函数一般不可能内被内联constexpr
函数值的是能用于常量表达式的函数,定义constexpr
函数的方法与其他函数类似。函数的返回类型以及形参类型必须都必须的是字面值类型,而且函数体中必须有且只有一条return
语句。constexpr
函数被隐式的指定为inline
函数。constexpr
函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行,比如空语句,类型别名以及using
声明constexpr
函数也可以返回非常量值,编译器会将确实返回常量值的地方变成常量值进行替换,而非常量值的地方则不会变成常量进行替换。而constexpr
函数如果作用在常量表达式上下文中,编译器会检查函数的结果是否为常量,如果不是,则编译器发出错误信息。inline
函数和constexpr
函数可以在程序中多次进行定义,不过定义必须完全一致,所以一般放在头文件中
-
函数指针
- 函数的类型由返回类型和形参类型共同决定,和函数名无关
- 申明函数类型只需要用指针替换函数名即可
// 函数a bool a(const string b); // 函数指针a bool (*a)(const string b);
- 函数指针的赋值必须精确匹配
- 函数名可以转换成函数指针
- 函数类型和函数指针并不同
// test是函数类型 typedef bool test(const string &); // test是函数指针类型 typedef bool (*test) (const string &);
decltype
函数类型得到的结果是函数类型而不是函数指针
typedef bool test(const string &); decltype(test) *testFunc; // testFunc是函数指针
- 直接声明返回函数指针的函数,从里向外看,
f1
有列表,f1
是个函数,f1
前面有*
所以f1
返回指针。再看括号外面,返回的指针也有形参列表,所以返回值是指针函数,该函数返回int
类型
int (*f1(int))(int *,int);
- 使用尾置类型简化上述返回函数指针的函数
auto f1(int)->int (*)(int *,int);
-
const
修饰返回结果的函数,避免返回结果被赋值,并且接受其的变量也必须为const
变量const int test();
-
const
修饰成员函数,即常量成员函数,表示函数不会修改对象的内容,并且const
成员函数不能调用非const
成员函数。
c++函数
最新推荐文章于 2024-07-13 19:27:27 发布