函数

# 函数

自动对象

只存在于块执行期间的对象称为自动对象。

指针形参

指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝前后的指针是两个不同的指针,如

1
2
3
4
5
void reset(int *ip)
{
*ip = 0; //改变指针ip所指对象的值
ip = 0; //只改变了ip的局部拷贝,实参并未改变
}

C++中尽量用引用类型代替指针。

使用引用避免拷贝

大的容器和类拷贝一次很亏,或者有的容器根本不支持拷贝操作。所以应该将这种形参定义成引用形式。如果不需要改变对象的值,就应该把它定义成常量引用。如

1
2
3
4
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}

注:尽量使用常量引用。普通引用除了会给人一种变量可修改的感觉之外,还会限制函数传入参数的类型,例如 string &s 不允许字面值作为实参来初始化它,而 const string &s 则允许。

使用引用形参返回额外信息

如果想让一个函数返回不止一个值,除了使用 pair 或其他数据类型之外,还可以给函数传入一个额外的引用形参,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string::size_type find_(const string &s1, char c, string::size_type &occurs) //函数功能是判断s1中是否存在c,并返回c所在的索引和c出现的次数。其中&occurs就作为引用形参来返回“出现次数”这个额外信息。也就是说在调用这个函数时要传入一个变量来存储这个occurs
{
auto ret = s1.size();
occurs = 0;
for (decltype(ret) i = 0; i != s1.size(); ++i) //也可以使用范围for语句实现
{
if(s1[i] == c)
{
if(ret == s1.size())
ret = i;
++occurs;
}
}
return ret;
}

调用时直接 auto index = find_(s, ‘o’, ctr); 就可以在更新ctr的值的同时返回index。

const 中的形参和实参

当使用实参初始化形参时,会忽略掉顶层 const 。所以如下两个函数不算重载,他们是一样的

1
2
void fcn(const int i) {}
void fcn(int i) {} //这两个在编译器看来是一个函数,因为顶层const被忽略

所以这样是合法的

1
2
3
4
5
6
7
8
9
10
void fun(int i)
{
cout << ++i << endl;
}

int main()
{
const int i = 4; //表面上是const,但是还是能被fun函数改变值,因为用实参初始化形参的时候会忽略掉顶层const。
fun(i);
}

数组形参

由于数组不能拷贝的特性,所以不能使用传值的方法来传入数组参数,但是可以把形参写成类似数组的形式:

1
2
3
void print(const int*);
void print(const int[]);
void print(const int[10]); //这个10表示期望数组中的元素个数,实际不一定。但是如果是引用的话就必须得是10个元素。

这三个函数是等价的,形参都是 const int* 当传入数组名字时就自动变成了指向第一个元素的指针了。

也可以将数组引用作为参数,如

1
2
3
4
5
void print(int (&arr)[10]) //这样虽然限制了数组的维度,但是也限制了函数只能处理10个元素的数组,多一个少一个都不行
{
for (auto elem : arr)
cout << elem << endl;
}

main: 处理命令行选项

1
int main(int argc, char *argv[]) {} //char *argv[]也可以定义成char **argv。两个等价

argv: 是一个数组,里面是 C-style 字符串指针,第一个元素 argv[0] 存的是程序的名字,它不是由用户输入的。从 argv[1] 开始才是用户输入的参数。最后一个指针之后的元素值保证为0

argc: 表示数组中字符串的数量

例如

1
2
3
4
5
6
7
8
命令prog -d -o ofile data0中
argc = 5
argv[0] = "prog";
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;

含有可变形参的函数

如果函数的实参数量未知但是类型相同,可以使用 initializer_list 类型的形参。该标准库定义在 <initializer_list> 中,其提供的操作如下

1
2
3
4
5
6
initializer_list<T> lst; //默认初始化T类型元素的空列表
initializer_list<T> lst{a, b, c}; //lst的元素数量和初始值一样多;lst的元素是对应初始值的副本,列表中的元素是const
lst2(lst); 或 lst2 = lst; //拷贝或赋值一个initializer_list对象。但是不拷贝列表中元素;拷贝后原始列表和副本共享元素。
lst.size(); //列表中元素数量
lst.begin(); //返回指向lst首位元素的指针
lst.end(); //返回指向lst中尾元素下一位置的指针

注:initializer_list 对象中的元素永远是常量值,其中元素的值不能改变!

用法如下:

1
2
3
4
5
6
7
8
9
10
11
void error_msg(initializer_list<string> il)
{
for (auto beg = il.begin(); beg != il.end(); ++beg) //begin()成员提供一个指向列表首元素的指针,end()成员提供一个指向列表尾后元素的指针。
{
cout << *beg << " ";
}
cout << endl;
}

//调用的时候要把序列放在一对花括号内
error_msg({"functionX", expected, actual}) //expected和actual都是string类型变量

引用返回左值

调用一个返回引用的函数得到左值,其他返回类型得到右值。用法如

1
2
3
4
5
6
7
8
9
10
11
12
char &get_val(string &str, string::size_type ix)//返回的是非临时变量的引用,是合法的
{
return str[ix];
}
int main()
{
string s("value");
cout << s << endl;
get_val(s, 0) = 'A'; //返回引用的函数得到左值,相当于把s[0]的值改为A
cout << s << endl;
return 0;
}

列表初始化返回值

C++11标准规定函数可以返回花括号包围的值的列表,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<string> process()
{
if(expected.empty())
{
return {};
}
else if(expected == actual)
{
return {"functionX", "okay"};
}
else
{
return {"functionX", expected, actual};
}
}

如果函数返回值是内置类型,那么花括号里最多一个值。

main返回值

里面定义了两个预处理变量,分别表示成功和失败,如

1
2
3
4
5
6
7
8
9
10
11
int main()
{
if (some_failure)
{
return EXIT_FAILURE;
}
else
{
return EXIT_SUCCESS;
}
}

main函数不能调用自己,所以没有递归

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

声明这种函数一定要带上数组维度。格式如下

Type (*function(parameter_list)) [dimension]

Type代表元素类型,dimension表示数组大小。例如

1
2
3
4
5
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类型

C++11中简化了这种声明方式,可以采用尾置返回类型。它跟在参数列表后面并以 -> 开头,紧接着是返回值类型。最后在前面放一个auto,例如

1
auto func(int i) -> int(*)[10];

可以轻易看出,func接受一个int实参,返回值是一个指向10个int元素数组的指针。

如果知道函数返回的指针将指向哪个数组,也可以使用decltype关键字。由于decltype不负责把数组类型转化成指针,所以decltype的结果是数组,还需要在函数声明中加一个*才表示返回函数指针。如下

1
2
3
4
5
6
7
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};

decltype(odd) *arrPtr(int i)//星号一定要有!decltype不负责转成指针
{
return (i % 2) ? &odd : &even;//返回的要么是指向odd数组的指针,要么是指向even数组的指针
}

函数重载

函数重载指的是函数名字相同但是形参列表不同main函数不能重载

不允许两个函数除了返回类型外其他所有类型都相同!

const_cast 和重载

1
2
3
4
5
6
7
8
9
10
11
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
//这里提供了一个实参不是const的版本
string &shorterString(string &s1, string &s2)
{
//先将两个非const强制转为const,再调用const版本函数,返回一个const引用。
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
return const_cast<string&>(r); //再次通过强制转换为非const引用。由于这个引用绑定的本身就是非const实参(本例中为s1和s2中较小的那个),所以这样用是安全的。
}

默认实参

一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。

局部变量不能作为默认实参。

constexpr函数

使用constexpr函数必须遵守以下约定:函数的返回值类型以及所有形参的类型都得是字面值类型(算数类型、引用和指针类型);函数体中必须有且只有一条return语句。constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作(如空语句、类型别名、using声明等)即可。如

1
2
constexpr int new_sz() { return 42; }
constexpr int foo = new_sz();

constexpr函数被隐式的指定为内联函数。也允许constexpr函数的返回值不是常量,也就是说constexpr函数不一定返回常量表达式。

通常将内联函数和constexpr函数定义在头文件中。

函数指针

函数指针指向某种函数类型,函数类型由它的返回类型和形参类型共同决定。如

1
2
3
4
5
6
7
8
9
10
bool lengthCompare(const string&, const string&);
其中该函数的类型为 bool(const string&, const string&)
所以声明函数指针可以是 bool (*pf)(const string&, const string&);
//使用函数指针
pf = lengthCompare; //当把函数名作为一个值使用时,该函数自动转换成指针
pf = &lengthCompare; //&是可选的
//也可以直接使用函数指针来调用函数,无需解引用。这三个是一样的
bool b1 = pf("hello", "goodbye"); //尤其注意这个!
bool b2 = (*pf)("hello", "goodbye");
bool b1 = lengthCompare("hello", "goodbye");

在重载函数中使用函数指针,指针的类型必须和重载函数中的某一个精确匹配

函数指针当做形参

函数本身不能当形参,但是函数指针可以。如

1
2
3
4
void useBigger(const string &s1, const string &s2, bool pf(const string &, cosnt string &));//第三个参数会自动转换成函数指针。
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, cosnt string &));//显式使用函数指针。两者一样
//调用
useBigger(s1, s2, lengthCompare);

返回指向函数的指针

方法有多种,如

1
2
3
4
5
6
7
using F = int(int*, int); //F是函数类型的类型别名
using PF = int(*)(int*, int); //PF是函数指针类型的别名

PF f1(int); //f1返回值是一个函数指针
F *f1(int); //显式指定返回类型是指向函数的指针
int (*f1(int))(int*, int); //最原始、最直接的方式声明f1是一个指向函数的指针。由内向外理解即可
auto f1(int) -> int (*)(int*, int); //使用尾置返回类型的方式。