函数
函数声明
-
即函数原型(函数签名/signature)指明函数的名字、参数个数、参数顺序、参数类型、异常说明等。
-
格式:[extern] [调用约定] <返回类型><函数名> (<参数表>)[const][异常说明];
-
从右到左进行参数压栈
-
谁来负责清栈或恢复堆栈?
- 调用者负责:可处理变参和确定参数个数的函数;
- 被调用者负责:只能处理确定参数个数的函数;
-
被调用函数的名字如何表示?
- 同一个项目中的函数名字表示,限定名不同即可区分;
- 不同项目中的函数名字表示,需要区分;
调用约定
-
-
返回类型、缺省参数(编译时匹配)值不能作为区分标志。
详细见函数重载
函数重载
多个同名函数,但它们具有不同参数类型、参数顺序、参数个数、const修饰、异常说明数时,可以同时存在,称为函数重载。
- 函数参数的个数、类型、顺序、const修饰、异常等不完全相同
- 返回值类型不作为区分标志
- 缺省参数不作为区分标志值
- 类型参数的const型与非const型不作为区分标志
- 引用和指针类型参数,是否可以改变实参,可作为区分标志
从函数重载的角度理解给定的函数声明:
int func();
int func(int);
int func(int, int);
int func(int, int) const;
int func(int) throw();
int func(int) noexcept;
int func(int) throw(int, MyE, YourE);
int func(int) noexcept(false);
int func(MyClass obj);
这些函数声明展示了函数重载的概念,即在同一个作用域内定义了多个同名函数,但它们具有不同的参数列表、返回类型或其他特征,以便根据函数调用时的参数类型或约束条件来选择匹配的函数。
在给定的声明中,我们可以看到以下情况:
func():没有参数的函数重载。func(int):接受一个整数参数的函数重载。func(int, int):接受两个整数参数的函数重载。func(int, int) const:接受两个整数参数的常量成员函数重载。func(int) throw():接受一个整数参数且不会抛出异常的函数重载(适用于 C++98 标准)。func(int) noexcept:接受一个整数参数且不会抛出异常的函数重载(适用于 C++11 及以后的标准)。func(int) throw(int, MyE, YourE):接受一个整数参数且可能抛出int、MyE和YourE类型异常的函数重载(适用于 C++98 标准)。func(int) noexcept(false):接受一个整数参数且可能抛出异常的函数重载(适用于 C++11 及以后的标准)。func(MyClass obj):接受一个MyClass类型参数的函数重载。
通过函数重载,可以根据函数调用时提供的参数类型、异常规范和其他特征,选择最匹配的函数进行调用。这样可以提供更灵活和多样化的函数行为。
函数重载的实现-名字重整
由编译器在函数名字的基本信息之上,添加必要的参数信息,形成新的函数名,用于区分不同的重载函数。
例:void fun(int)经重整后,生成类似_fun_int的新函数名
缺点: 一个程序中难以确定另一个程序中的经重整的函数名
函数的参数表
缺省参数的解释和整理:
- 缺省参数:缺省参数是指在函数定义时为参数提供默认值,使得在函数调用时可以省略对应的实参。缺省参数的目的是为了提供更灵活的函数调用方式。
- 函数原型中提供缺省参数:通常情况下,应该在函数的声明或原型中提供缺省参数的默认值。函数原型指的是函数的声明,它通常放在头文件中,用于告知函数的存在和接口。
- 函数定义中提供缺省参数:当函数没有原型或声明时,可以在函数的定义中为参数提供默认值。这种情况下,默认参数的定义必须在调用它的代码之前。
- 无名参数:在函数定义中,可以省略参数的名称,只提供类型。这在函数没有使用到对应参数的情况下可以使用,但在函数体内无法直接访问该参数。
以下是一个示例,展示了缺省参数、函数原型和无名参数的用法:
// 函数原型中提供缺省参数的默认值
void foo(int x, int y = 0);
// 函数定义中提供缺省参数的默认值(无函数原型)
void bar(int x, int y = 0) {
// 函数体
}
// 函数定义中使用无名参数
void baz(int, int); // 声明/原型
void baz(int, int) {
// 函数体
}
在上述示例中,foo() 的函数原型中提供了缺省参数 y 的默认值,而 bar() 的函数定义中也提供了相同的缺省参数默认值。baz() 的函数定义中使用了无名参数。
需要注意的是,缺省参数的定义通常应放在函数的声明或原型中,以便在不同的编译单元中能够正确使用该默认值。此外,在使用无名参数时需要注意,无名参数只是在函数定义中省略了参数名称,但仍然需要提供参数类型。
实参与形参的匹配
-
压栈顺序由调用约定确定
-
但计算顺序是不确定的
-
缺省参数的匹配
-
在编译时匹配,而不是运行时;
-
第一个带缺省值参数的右侧必须都有缺省值;
void f(int,char=‘c’,int); //非法void f(int,char=‘c’,int=9);//正确
-
-
实参的类型转换
若不存在完全匹配的函数,尝试类型转换(每个参数只一次)。
void f(int);void f(double);调用:f(2.5f);
-
值传递
cppCopy codeint a = 10; const int b = 20; My mObj; const My cmObj; void f(int, My); 或 void f(const int, const My); f(a, mObj); // 正确,传递非常量整数和非常量 My 对象 f(b, mObj); // 正确,传递常量整数和非常量 My 对象 f(a, cmObj); // 正确,传递非常量整数和常量 My 对象 f(b, cmObj); // 正确,传递常量整数和常量 My 对象 -
指针传递
cppCopy codeint a = 10; My mObj; int* pa = &a; My* pmy = &mObj; const int* cpa = pa; const My* cpmy = pmy; void f(int*, My*); void f(const int*, const My*); f(pa, pmy); // (1) 使用非常量指针作为参数 f(pa, cpmy); // (2) 使用非常量指针和常量指针作为参数 f(cpa, pmy); // (2) 使用常量指针和非常量指针作为参数 f(cpa, cpmy); // (2) 使用常量指针作为参数 -
数组的传递
int a[3]={1,2,3}; //以下几个等价 void f(int[ ]); void f(int a[ ]); void f(int a[3]); void f(int a[5]); void f(int * a); -
引用传递
int a = 1; const int& b = a; const int c = 2; void f(int, int); void f(int&, int&); void f(int, const int&); void f(int&, const int&); void f(const int&, const int&); f(a, a); // 调用 f(int, int) f(a, b); // 调用 f(int, int) f(a, c); // 调用 f(int, const int&) f(a, 3); // 调用 f(int, int) f(a, a); // 调用 f(int&, int&) f(a, b); // 调用 f(int&, int&) f(a, c); // 调用 f(int&, const int&) f(a, 3); // 调用 f(int&, int) f(a, a); // 调用 f(int, const int&) f(a, b); // 调用 f(int, const int&) f(a, c); // 调用 f(int, const int&) f(a, 3); // 调用 f(int, const int&) f(b, 3); // 调用 f(int, const int&) f(a, a); // 调用 f(int&, const int&) f(a, b); // 调用 f(int&, const int&) f(a, c); // 调用 f(int&, const int&) f(a, 3); // 调用 f(int&, const int&) f(b, b); // 调用 f(int&, const int&) f(a, a); // 调用 f(const int&, const int&) f(a, b); // 调用 f(const int&, const int&) f(a, c); // 调用 f(const int&, const int&) f(a, 3); // 调用 f(const int&, const int&) f(b, b); // 调用 f(const int&, const int&) f(3, 3); // 调用 f(const int&, const int&)
返回引用
下面是对返回引用的解释和整理:
T& f();
// 不等价于
const T& f();
// 必须返回一个有效对象的引用
int global = 100; // 全局变量
int& other(int&, const int&);
int& f(int a, int& b, const int& c) {
int lVar = 88; // 局部临时变量
static int sVar = 99; // 局部静态变量
return a; // 返回 a 的引用
return b; // 返回 b 的引用
return c; // 返回 c 的引用
return global; // 返回全局变量 global 的引用
return global + 8; // 返回表达式 global+8 的引用
return lVar; // 返回局部临时变量 lVar 的引用 (不安全)
return sVar; // 返回局部静态变量 sVar 的引用
return sVar + 2; // 返回表达式 sVar+2 的引用
return sVar += 3; // 返回表达式 sVar+=3 的引用
return other(lVar, b); // 返回函数 other(lVar, b) 的引用
return other(sVar + 2, global); // 返回函数 other(sVar+2, global) 的引用
}
在给定的代码中,我们有以下情况:
T& f();:定义了一个函数f,返回一个类型为T的非常量引用。const T& f();:定义了一个函数f,返回一个类型为T的常量引用。- 返回引用:函数
f中的返回语句返回不同的对象的引用,包括函数参数、全局变量、局部变量和其他函数的返回值。
需要注意的是:
- 返回局部临时变量的引用(例如
lVar)是不安全的,因为在函数执行完毕后,该局部变量将不再存在,引用将会变成悬空引用。 - 返回静态局部变量的引用(例如
sVar)是有效的,因为静态局部变量在整个程序生命周期内都存在。 - 返回其他函数的返回值的引用(例如
other(lVar, b)和other(sVar+2, global))取决于函数other的返回值类型和生命周期。