[C++学习笔记] 函数

函数调用

()是调用运算符

函数调用完成两项工作:

  1. 用实参初始化对应形参 隐式地定义并初始化它的形参
  2. 将控制权转移给被调函数 此时 主调函数被中断 被调函数开始执行

传参

int get()int get(void) 都可以表示无参数 后者为c风格

诸如形参等只存在于块执行期间的对象称之为自动对象

局部静态对象在第一次执行其定义语句时进行初始化 直至程序终止才被销毁

C++中尽量使用引用传递来访问函数外部的对象 而不要用指针
事实上指针作为形参仍旧是值传递而非引用传递

传引用时 无需传地址 直接传对象就可 将自动转换成引用
类似于引用定义时也是无需取地址

void re(int &a){
    ......
} 
...
int j=0; 
re(j);

使用引用传递可以避免拷贝耗时

在无需修改值的时候 最好声明为常量引用形参
否则可能会导致错误 因为非常量可以转常量 而常量不可转非常量

可以通过引用形参的方式 使函数一次返回多个值
int(const string &s, int & num) 可以通过num额外返回一个值

带参数的main函数 int main(int argc, char *argv[])
argc表示参数个数 argv是参数字符串 其中argv[0]表示程序名 从argv[1]开始才是向命令行传递的参数

可变参数

函数若要传递未知个数但类型一致的参数 可以使用initializer_list<>
但调用时需将所有参数用大括号{} 括起来

void error_m(int a, initializer_list<string> er) 
error_m( 1, { "a", "b", "c" } ); //调用时 

省略符仅用于访问某些特殊的c代码设置的, 使用了varargs的c标准库功能 通常不用于其他
C++允许定义形参个数和类型不确定的函数,不确定的形参可以使用省略号

使用可变参数函数时注意:

  • 省略号必须在参数列表的末尾。
  • 运行时,才能确认参数的具体个数与类型。
  • 只能顺序访问可变参数,无法后退访问
  • 无法提供任何安全性保证

如果使用省略号,传递可变数量的参数时需要使用va_argva_startva_endva_list等宏,定义在中(c中定义在<stdarg.h>)。

va_start(args,paramN) 令对可变函数参数的访问可行。

  • 第一个参数为va_list类型的变量
  • 第二个参数为"..."前最后一个参数
  • 将args初始化为指向可变参数列表第一个参数

va_arg(args,type) 访问下一个可变函数参数。

  • 第一个参数为va_list类型的变量
  • 第二个参数是返回值的类型
  • 调用va_arg获取当前的参数,并自动更新指向下一个可变参数

va_end(args) 结束可变参数函数的遍历。

  • 释放va_arg变量

代码示例如下:

#include<iostream>
#include<cstdarg>
using namespace std;
 
//求和函数
int add(int firstParam, ...)
{
	va_list arg_ptr;
	int sum = 0;
	int nArgValue;
	sum += firstParam;
	va_start(arg_ptr, firstParam);
	do
	{
		nArgValue = va_arg(arg_ptr, int);
		sum += nArgValue;
	} while (nArgValue != 0);
	va_end(arg_ptr);
	return sum;
}
 
int main()
{
	cout << add(1, 2, 3, 0) << endl;       //运行结果:6
	system("pause");
}

返回值

void函数会隐式地执行return; 语句 也可以手动使用return; 提前退出

函数返回值是通过生成的临时变量储存值 然后将该临时变量拷贝至返回点

函数返回引用时和使用形参引用是一样的 返回的仅仅是对象的别名 不管是调用还是返回结果都不会拷贝对象

函数返回值是个非常量引用时返回的是个左值 可以向其他左值一样使用

Char &get_fisrst(string &str){ 
    return str[0];
} 
get_fisrst(s) = 'a';

main函数不能调用自己 不能递归调用

声明一个返回数组指针的函数

int (*func(int i))[10]{
    ......
}

定义了一个返回10个整数数组指针的函数

另一种方法是使用尾置返回类型 任何函数都可以使用
但对于返回复杂类型的函数最有效 比如数组指针或数组引用
方法:使用时用auto代替类型并在形参之后用->跟着真正的类型

auto func(int i) -> int(*)[10] {
    ......
}

这与上述定义是等价的 但更容易使用

最后一种方法是 在已知返回指向哪个变量时 可以使用decltype来声明

int odd[ ] = {1,2,3}; 
Decltype(odd) *func(int i){ 
    return &odd;
} 

函数重载

如果同一作用域内的几个函数名字相同但形参列表不同 就称之为重载函数

注意 形参若仅有常量非常量的区别被视为相同形参
因常量在拷贝时会失去顶层const
intconst int, int *int* const传参时是一样的

但形参是某种类型的指针或引用时 可以区别指向常量和非常量对象的形参
int &const int&, int *const int *是不一样的

返回类型不同而形参一致同样也不被允许重载

main函数不可重载

函数匹配是与函数重载对应的过过程, 也叫重载确定, 通过形参确定调用的重载函数

当调用重载函数时 没有完全匹配的最佳选择 却有多个可以通过对实参类型转换而满足形参的重载函数 则会产生二义性调用的错误

函数匹配:

  1. 第一步是确定本次调用的重载函数集 , 这个集合中的函数称之为候选函数(满足同名且声明在调用点可见) ;
  2. 第二步是根据提供的实参选出可行函数(形参的个数和对应类型都与提供的实参相符或能转换得到);
  3. 第三步是从可行函数中寻找最佳匹配 实参类型与形参越接近 匹配越好 其原则是: 最佳匹配的每个实参匹配都不劣于其他可行函数的匹配 且 至少有一个实参匹配优于其他
f(int,int){ }
f(double,double){ } 
f(42,3.14);
// 第一个实参的最佳匹配是第一个函数 第二个实参的最佳匹配是第二个函数 
// 因此会产生二义性调用

在局部作用域中声明一个重载函数后 将会隐藏所有外部的重载函数

当一个形参具有默认值,其后的所有形参都必须有 也就是默认形参必须在非默认形参之后

声明函数定义默认形参后 不能重新定义函数来更改原有的默认形参
但可以将原来的非默认形参定义为新的默认形参

用作默认实参的名字在函数声明的作用域内解析 但不受内部局部变量的影响

int a = 10;
int b = 2;
int func(int = a, int = b);
Void f2()
{ 
    int a = 1; //虽然通过定义局部变量隐藏了外部值 但并不影响函数调用的默认实参
    b = 3; //直接修改外部默认实参的值 有效 可以产生效果
    func(); 
} 

可以通过内联函数避免函数调用的开销
通过在返回类型前添加inline关键字即可 其效果就是将该函数在调用点处直接展开(替换)
但是一般只用于优化规模小 流程直接 频繁调用的函数 递归函数和较大的函数就不适合使用内联了

constexpr 函数被隐式地指定为内联函数
constexpr 函数中有且仅有一条return语句(不能有其他任何执行操作的语句) 并且返回值和所有的形参类型一定是字面值
但通过传入非常量类型的实参 可以返回非常量的表达式( 因为内联展开的原因 )

__func__是编译器定义的一个局部静态变量 用于存放当前函数的名字
__FILE__存放文件名的字符串字面值
__LINE__存放当前行号的整型字面值
__TIME__存放文件编译时刻的字符串字面值 (不是消耗的时间 而是编译时的那个时刻)
__DATE__存放文件编译日期的字符串字面值
使用这些常量可以在错误消息中提供更多信息

在使用函数名作为一个值时 自动转换成指针 取地址可有可无

int (*pf)(int); 
int f(int a);
Pf = f; pf =&f; //这两者是等价的
Pf(2); //可以直接通过函数指针调用函数 无需解引用

同样 也可以直接将函数名作为形参使用 等价于指向函数的指针

int f(int); 
void f2(int , int pf(int)); 
void f2(int, int (*pf)(int));
f2(3, f);

也可以定义返回类型为函数指针 如

int(*f1)(int ,int) (int); 

由内而外 自右而左的看 首先看到有形参列表 所以是函数
接着看到* 所以是函数指针
接着看到外侧的形参列表 说明是个返回函数指针的函数
最后看到int 说明返回的是个返回整型的函数指针的函数

为了方便起见 我们可以使用定义别名和尾置返回类型的方式来定义这种复杂函数

//别名方式
using PF = int(*)(int,int);
PF f1(int);

//尾置返回方式
Auto f1(int) -> int (*)(int);

使用decltype来获得函数指针时 必须加上一个*来指出其指针的类型 decltype(func)* f2= func;

函数原型就是函数的声明

分离式编译指把一个程序分割成多个独立的源文件的能力

IO类型属于不能被拷贝的类型 在作为函数参数时只能通过引用传递 而非值传递
并且 读取和写入的操作都会改变流的内容 因此只能使用非常量引用