6.1 函数基础
// 把ret和val的乘积赋给ret,然后将val减1
ret *= val--;
调用函数式要提供能够转换成形参类型的实参。
局部静态对象static
,如果没有显式的初始值,它将自动执行值初始化,内置类型的局部静态变量初始化为0。
一般情况下,函数只能定义一次,但可以声明多次,如果用不到可以只有声明没有定义。
分离式编译
如果修改其中一个源文件,只需重新编译那个改动了的文件。大多数编译器提供了分离式编译每个文件的机制。这一过程会产生.obj(windows)或.o(unix)的目标对象文件,编译器把对象文件链接在一起形成可执行文件。
CC -c factMain.cc # generates factMain.o
CC -c fact.cc # generates fact.o
CC factMain.o fact.o # generates factMain.exe or a.out
CC factMain.o fact.o -o main # generates main or main.exe
6.2 参数传递
- 引用传递
- 值传递
传值参数
指针形参
指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝后,两个指针是不同的指针。可以通过指针修改它所指向的对象的值,而不能改变实参指针本身的值。
传引用参数
通过引用形参可以改变一个或多个实参的值,等价于直接传入对象而不是对象的地址。
使用引用避免拷贝
如果传入的对象占有较大的空间,例如类,结构,容器,较长的字符串等,在值传递拷贝操作时效率很低,或者有的不支持拷贝操作时,都应使用引用传参避免拷贝。如果为了避免低效的拷贝,但又不需要修改实参的值,可以把形参定义成常量引用。
引用形参返回额外信息
引用形参可以帮助我们在函数中返回多个结果。
- 定义新的数据结构
- 传入引用实参
const形参和实参
int fcn(const int i);
int fcn(int i);
两个函数形式上虽不同但没有实质的区别,因为顶层const
被忽略,两个函数传入的参数可以完全一样。
指针或引用形参、const
int i = 42;
const int *cp = &i;
const int &r = i;
const int &r2 = 42;
int *p = cp; // 错误,p的类型和cp的类型不匹配
int &r3 = r; // 错误,r3 的类型和 r 的类型不匹配
int &r4 = 42; // 错误,不能用字面值初始化一个非常量引用
同样的规则应用到参数传递
数组形参
- 不拷贝数组
- 转换成指针
- 确保不越界
void print(const int*);
void print(const int[]);
void print(const int[10]);
三个函数实质上相同,形参都是 const int *
。
int i = 0, j[2] = {0,1};
print(&i); // &i,类型是int*
print(j);
管理指针形参
- 使用标记指定数组长度
字符串存储在数组中,并且最后一个字符后面跟着一个空字符
- 使用标准库规范
传递指向数组首元素的尾后元素(尾元素下一个位置)的指针
void print(const int *beg, const int *end)
{...}
- 显示传递一个形参
传递多维数组
void print(int (*matrix)[10], int rowSize){}
void print(int matrix[][10], int rowSize){}
main函数参数
int main(int argc, char *argv[]){ ... }
int main(int argc, char **argv){ ... }
argc
表示数组中字符串的数量,argv
是一个元素是字符串指针的数组
argv[0]
指向程序名或者空字符串,最后一个元素值为0
可变形参
- initializer_list 使用标准库
- 可变参数模板
- 省略符形参
initializer_list
函数实参数量未知,但是类型相同,使用initializer_list
形参。表示某种特定类型的数组,其定义在同名的头文件中。类似于vector
,但是initializer_list
的元素都是常量。
省略符形参
可用它传递可变数量的实参,一般用于与C函数交互的接口程序。
省略符参数只能出现在形参列表的最后一个位置,他的形式:
void foo(parm_list, ...); // 逗号是可选的
void foo(...);
6.3 返回类型和return语句
不要返回局部对象的引用或指针
调用一个返回非常量引用的函数得到的是左值,其他返回类型得到右值。
列表初始化返回值
vector<string> process()
{
// excepted actual 是 string 对象
return {"function", expected, actual};
}
main
函数返回值
没有return
语句时,编译器自动隐式插入一句返回0
的return
语句。
main
函数返回0
表示执行成功,返回非0
具体含义依机器而定,为了使其含义与机器无关,cstdlib
头文件定义了两个预处理变量来关联成功与失败。
int main()
{
if(some_failure)
return EXIT_FAILURE;
else
return EXIT_SUCCESS;
}
返回数组指针
数组不能拷贝,所以函数不能返回数组,但可以返回数组的指针或者引用。
使用类型别名来定义函数
typedef int arrT[10]; // arrT 含有10个int的数组
using arrT = int[10]; // 等价
arrT* func(int i);
声明一个返回数组指针的函数
int arr[10];
int *p1[10];
int (*p)[10] = &arr;
不用别名来声明函数
int (*func(int i))[10];
声明的含义:
func(int i)
表示调用func函数时需要一个int类型的实参(*func(int i))
意味着我们可以对函数调用的结果执行解引用操作(*func(int i))[10]
表示解引用func的调用将得到一个大小是10的数组int (*func(int i))[10]
表示数组中的元素是int类型
使用尾置返回类型
尾置返回类型跟在形参列表后面并以一个->
符号开头。在函数返回类型的位置用auto
代替。
// 接受一个int类型的实参,返回一个指针,指向含有10个int的数组
auto func(int i) -> int(*)[10];
使用decltype
知道函数返回的指针将指向哪个数组时,可以使用decltype
关键字声明返回类型。
如下例:根据参数i的不同指向两个已知数组中的某一个:
int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? &odd : &even;
}
arrPtr
使用关键字decltype
表示他的返回类型是个指针,并且指针所指向的对象与odd
一致,所以attPtr
返回一个指向含有5个int
的数组的指针。但是decltype
不把数组类型转换成指针,其结果是数组,要在arrPtr
前加一个*
符号。
6.4 函数重载
重载,函数名字相同形参列表不同,只有返回类型不同的不是重载。main
函数不能重载。
重载和const形参
顶层const不影响传入函数的对象,所以一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开。
Record lookup(Phone);
Record lookup(const Phone); // 重复声明 Record lookup(Phone)
Record lookup(Phone *);
Record lookup(Phone* const); // 重复声明 Record lookup(Phone *)
如果形参是某种类型的指针或引用,通过区分指向的是常量对象还是非常量对象可以实现函数重载,此时const是底层的。
Record lookup(Account&);
Record lookup(const Account&); // 新函数
Record lookup(Account *);
Record lookup(const Account*); // 新函数
const
对象只能传给const
形参,而非常量可以转换成const
,所以const
形参也可以接受非常量实参。当传递非常量对象的时候,编译器会优先选择非常量版本的函数。
const_cast和重载
const_cast
在重载函数的情景中最有用。
const string & func1(const string &s1, const string &s2)
{
// return &
}
string & func2(string &s1, string &s2)
{
auto &r = func1(const_cast<const string&>(s1), const_cast<const string&>(s2));
return const_cast<string&>(r);
}
利用const_cast
转换调用const
版本,保证安全。
重载函数的调用
当重载函数参数数量相同且参数类型可以相互转换时,确定所调用的重载函数更加复杂。
重载和作用域
如果在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。在不同的作用域中无法重载函数名。
6.5 特殊用途特性
- 默认实参
- 内联函数
- constexpr
默认实参
一旦某个形参设置了默认值,它后面所有形参都必须有默认值。
内联函数和constexpr函数
内联函数可避免函数调用的开销,
在函数返回类型前面加上关键字inline,声明成内联函数。
6.6 函数匹配
重载函数形参数量相等,类型可以转换时,确定具体函数的调用更加复杂。
- 寻找可行函数
- 寻找最佳匹配
- 多参数匹配产生二义性拒绝请求
6.7 函数指针
函数指针指向的是函数而不是对象。用指针替换函数名。
bool stringcompare(const string &, const string &);
bool (*pf)(const string &, const string &);
函数名使用时会自动作为指针。
// 把函数地址赋给指针
pf = stringcompare;
pf = &stringcompare; // 等价
// 三个等价调用
bool b1 = pf("hello", "goodbye");
bool b2 = (*pf)("hello", "goodbye");
bool b3 = stringcompare("hello", "goodbye");
赋值时要注意参数类型匹配
bool cstrcomp(const char *, const char *);
pf = 0;
pf = cstrcomp; // 错误,类型不匹配
使用重载函数指针要明确定义
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff;
void (*pf2)(int) = ff; // 错误,参数列表不匹配
double (*pf3)(int *) = ff; // 错误,返回类型不一致