函数定义与声明
- 与变量类似,函数只能定义一次,但是可以声明多次
-
函数声明无函数体,用一个分号替代即可
需要函数返回类型、函数名和形参类型
[返回类型] [函数名]([形参列表]);
-
函数定义就是在函数声明基础上加上函数体
[返回类型] [函数名]([形参列表]) { [函数体] }
函数参数传递
每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化
形参初始化的机理和变量初始化一样。理解这句话,就理解了C++传参的本质
值传递
实参的值会拷贝给形参,形参和实参是两个相互独立的对象
- 基本类型形参在函数调用时,实参都是值传递
-
指针形参
执行指针的拷贝时,拷贝的是指针的值。拷贝后,两个指针是不同的指针。但是通过指针间接访问的对象是同一个
//函数接收一个指针 void reset(int *ip) { //改编指针ip所指向对象的值 *ip = 0; //改变ip的局部拷贝,实参实际并未修改 ip = 0; }
引用传递
当形参是引用类型时,对应的实参被引用传递
和其他引用一样,引用形参也是它绑定的对象的别名;也就是说,引用形参是它对应的实参的别名
那么改变形参的值也就是在改变实参的值
-
函数接收引用
//函数接收引用 void reset(int &i) { //改变了i所引用对象的值 i = 0; }
在函数体内改变了形参引用的值,也就是改变了实参引用的值。
-
拷贝大的类型对象比较低效,甚至有些不支持拷贝操作,这时就只能使用引用传参
-
函数传出参数,除了使用指针,也可以使用引用
在C语言中,当有传出参数时,除了可以使用返回值,也可以定义传递指针,在函数内对指针指向的对象赋值
在C++中,也可以通过引用实现相同的功能,在函数体内改变形参引用的值即可
const
形参和实参
-
基本类型和
const
基本类型只有顶层
const
,顶层const
作用于对象本身和变量初始化一样,即常量或者非常量都可以用来初始化顶层
const
形参,实参初始化形参时会忽略顶层const
void fcn(const int i) { ... } const int x = 10; int y = 20; //都是正确的 fcn(x); fcn(y);
所以函数重载上,区别只有形参是
const
或者非const
。这样是错误的,会编译报错
-
指针和引用和
const
指针和引用有顶层
const
和底层const
之分-
顶层
const
和基本类型规则相同,重载编译器都会报错 -
底层
const
-
底层
const
是可以重载的,具体调用时的匹配逻辑见函数匹配区别在于
const
形参,函数内是不能修改参数的 -
使用常量或者非常量实参调用底层
const
形参函数都可以 -
常量实参不能用来调用非
const
形参函数
所以尽量使用常量引用形参
-
-
//引用类型形参
void reset(int &i) {
}
int i = 0;
const int &x = i;
int &y = i;
//错误,const实参不能初始化非const引用实参
reset(x);
//正确,都是const引用
reset(y);
//底层const形参
void reset(const int &i) {
}
int i = 0;
const int &x = i;
int &y = i;
//正确,可以使用非const引用实参初始化const引用形参
reset(x);
//正确,都是const引用
reset(y);
数组形参
-
数组形参
数组两个特殊性质:
-
不允许拷贝数组 所以不能使用值传递方式使用数组
-
使用数组时通常会将其转换成指针 所以传数组实参时,实际传递的是指向数组首元素的指针
//这两种形式是等价的 void print(char[] c){} void print(char * c){}
-
-
因为数组是以指针传递给函数的,那么就需要额外的参数知道数组的长度
-
使用标记指定数组的长度
适用于数组包含一个明确的结束标记且不会和普通数组混淆,比如C语言的字符串,最后存在一个空字符。但对于类似int数组就不适合了
void print(const char* p) { if (p) { while(*cp) { //操作数组元素 } } }
-
使用标准库规范
传入需要操作的数组的首指针和尾后指针
void print(const int *begin, const int *end) { ... }
这样可以使用标准库提供的迭代器,begin()和end()获取到首指针和尾后指针
-
显示传递数组的大小
添加一个形参,接收数组的大小,比较常用的做法
void print(const int *p, const int size) { ... }
-
-
数组形参和
const
- 前面关于引用和指针和
const
的规则同样适用于const
数组
-
C++还能定义数组的引用
形式:
<const> type (¶m) [size];
两边括号不可少,因为不存在引用数组;
数组的大小是数组的一部分,所以size不可少,这样就限制了函数的通用性
- 前面关于引用和指针和
-
多维数组
C++实际没有真正的多维数组,只是数组的数组, 所以多维数组的形参写成:数组类型形参,加上数组的长度
//p指向数组的首元素,一共有rowSize个元素, //该数组的每个元素都是由10个整数构成的数组 void f (const int (*p)[10], int rowSize) {} 等价于 //编译器也会忽略掉第一个维度 void f(const int p, int rowSize) {}
可变形参
-
使用
initializer_list
标准库类型编写可以处理不同数量实参的函数。但是实参的类型可要一致
initializer_list
也是一种模版类型,定义时,必须带上元素类型initializer_list<string> ls;
initializer_list
对象中的元素永远是常量值,无法改变元素的值
initializer_list
也可以使用迭代器访问元素
//输出错误信息 void error_msg(initializer_list<string> li) { //begin()指向首元素,end()指向尾后元素 for (auto begin = li.begin(); begin != li.end(); ++begin) { cout<<*begin<<" "; } cout<<endl; } //调用,元素必须是常量 error_msg({"funX","error","xxx"}); error_msg({"funX","ok"});
-
使用省略符类型,一般用于与C函数交互的接口
形参中使用
...
省略符,在函数内部使用varargs
访问可变参数//省略符只能出现在形参列表的最后位置 void foo(params,...); void foo(...);
省略符形参应该仅仅用于C和C++通用的类型。特别应该注意的是,大多数类类型的对象在传递给省略符形参时都无法正确拷贝