函数
函数基础->参数传递->返回类型和return语句->函数重载->默认实参->内联函数和constexpr函数->调试帮助->函数指针
局部对象:会隐藏外层作用域中同名的其他所有声明,包括同名的函数
局部静态对象:局部变量定义成static类型,那么这个变量在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁。
示例:函数统计自己被调用了多少次
size_t count_calls()
{
static size_t ctr = 0;
return ++ctr;
}
int main()
{
for(size_t i = 0; i != 10; ++i)
cout << count_calls() << endl;
return 0;
}
传值和传引用:如果形参是引用类型,那么形参将绑定到对应的实参上。否则,就将实参的值拷贝后赋值给形参。
传引用的好处:有些类型不支持拷贝操作,只能传引用。而且对于大的类类型对象或者容器对象,可以避免拷贝。而且可以用引用形参返回额外信息。
const形参和实参:
可以使用非常量初始化一个底层const对象,但是反过来不行。
void reset(int &i);
int i = 0;
const int ci = i;
reset(&ci); // 错误
void fcn(const int i);
fcn(i); // 正确
调用
fcn
函数时,用实参初始化形参时会忽略掉顶层
const
,所以定义
fcn
的时候,
void fcn(const int i){} 和 void fcn(int i){}是重复的定义。
数组形参
数组传值:由于不能拷贝数组,所以无法传值方法使用数组参数,可以传递指向数组首元素的指针。
void print(const int*);
void print(const int[]);
void print(const int[10]);
数组传引用:
void print(int (&attr)[10]){}
注意&attr两边的括号不能少,否则就变成了引用的数组。
main:处理命令行选项
int main(int argc, cahr *argv[]){...}
int main(int argc, char **argv){...}
prog -d -o ofile data0
argv[0] = “prog”
argv[1] = “-d”
...
argv[4] = “data0”
argv[5] = 0;
含有可变形参的函数
三种方法处理不同数量实参的函数:
(1)如果所有实参类型相同,传入标准库类型initializer_list(C++11)
(2)如果实参类型不同,编写可变参数模板(C++11)
(3)省略符
示例:定义不确定数量参数的函数
void error_msg(initializer_list<string> il)
{
for(auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << “ “;
cout << endl;
}
//或者void error_msg(ErrCode e, initializer_list<string> il);参数类型不同//或者void foo(parm_list, ...); void foo(...);
使用:
error_msg({“functionX”, expected, actual}); // expected和actual是string对象
返回类型和return语句
不要返回局部对象的引用或指针
const string &manip()
{
string ret;
if(!ret.empty())
return ret; //错误
else
return “Empty”; //错误
}
C++11中,函数可以返回花括号包围的值的列表。
vector<string> process()
{
return {};
//return {“functionX”, “okay”};
}
主函数
main
可以没有
returny
语句直接结束,编译器将隐式地插入一条返回
0
的
return
语句。
返回数组指针
示例:声明func函数
(1)直接写
int (*func(int i))[10];
这样写比较麻烦,可以使用类型别名使语句写起来简单:
示例:
typedef int arrT[10]; //arrT是一个类型别名,表示含有10个整数的数组
using arrT = int[10]; //与上一句等价
arrT* func(int i); //声明函数func,返回指向包含10个整数的数组的指针
(2)使用尾置返回类型
C++11中还有更简单的声明方法,就是用尾置返回类型,也就是把返回类型放在最后面,原先应该放返回类型的地方写auto
auto func(int i)->int(*)[10];
(3)使用decltype
如果已经明确知道返回值是指向哪个数组的,就可以用decltype关键字声明返回类型。
int odd[] = {1, 3, 5, 7, 9};
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? &odd : &even;
}
注意,
decltype
返回的类型是数组,如果要函数返回指向数组的指针,那么需要在函数名
arrPtr
前面加上
*
函数重载
Record lookup(const Account &acct);
Record lookup(Account &acct);
这两条语句是一样的,不能区分。但是如果形参是引用和指针,那const是可以区分的:
Record lookup(const Account&);
Record lookup(Account&);
这两条语句是重载。
const_cast和重载
示例:
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
由于这个函数在传实参的时候,可以传给他非常量string。但是这时候返回的还是常量string。我们希望传入非常量string的时候返回的也是非常量string。这时候我们可以再写一个重载的函数
string &shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
return const_cast<string&>(r);
}
如果传入非常量
string
,那么先强制转换成常量,调用刚刚那个函数,然后把结果去掉
const
调用重载的函数
调用重载函数时有三种可能发生的情况:
(1)找到实参的最佳匹配
(2)找不到任何一个可匹配,编译器产生错误信息
(3)多于一个函数可以匹配,但是每一个都不是最佳匹配,编译器产生错误,这是二义性调用。
默认实参
一旦某个形参被赋予了默认值,那么他后面的所有形参都必须有默认值。
声明的时候,给定的作用域中一个形参只能被赋予一次默认实参,函数声明可以有多条,那么这一条后续的声明只能为之前那些没有默认值的形参添加默认实参。
内联函数和constexpr函数
内联函数
为什么要有内联函数?因为有一些简单的要频繁调用的函数,函数调用的开销大。一次函数调用包括:调用前保存寄存器;返回时恢复;拷贝实参;程序转向新的位置继续执行。内联函数会在调用点上展开,不需要函数调用。
inline const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
constexpr函数
constexpr函数是能用于常量表达式的函数。返回类型和所有形参都是字面值类型,函数体中有且只有一条return语句。constexpr函数是内联函数,编译器在该函数的调用处,把他替换成其结果值。
constexpr函数不一定返回常量表达式,如果传入的实参不是常量表达式,那么返回值也不是常量表达式。
示例:
constexpr int new_sz() {return 42;}
constexpr size_t scale(size_t cnt){return new_sz() * cnt;}
int arr[scale(2)]; //正确
int i = 2;
int a2[scale(i)];//错误,这里需要的是常量表达式,但是传入int型,返回的不是constexpr
assert预处理宏
assert(expr);
如果expr为假(0),assert输出信息并终止程序的执行。如果expr为真(非0),assert什么也不做。
NDEBUG预处理变量
如果定义了NDEBUG,那么assert什么也不做,默认状态下没有NDEBUG,assert将运行。
NDEBUG也可以自己调试
如果未定义NDEBUG,执行#ifndef和#endif之间的代码;如果定义了,忽略这些代码。
示例:
void print(const int ia[], size_t size)
{
#ifndef NDEBUG
cerr << “Error:” << __FILE__ << “ : in function “ << __func__ << “ at line “ << __LINE__ << endl;
#endif
}
NDEBUG
可以用
#define
定义,也可以
$ CC -D NDEBUG main.c
函数指针
示例:
函数bool lengthCompare(const string &, const string &);
声明一个指向这个函数的指针:
bool (*pf)(const string &, const string &); //注意*pf两端括号不能少
使用函数指针:
pf = lengthCompare;
pf = &lengthCompare; //两句都可
bool b1 = pf(“hello”, “goodbye”);
bool b2 = (*pf)(“hello”, “goodbye”);
bool b3 = lengthCompare(“hello”, “goodbye”); // 这三句等价
重载函数的指针
编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中的某一个精确匹配。
函数指针形参
和数组类似,有两种定义函数形参的方法,第一种写成函数的样子,但是实际上却是当成指针来用。另一种就是直接写成函数指针。
void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &));
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &));
使用:
useBigger(s1, s2, lengthCompare);
返回指向函数的指针
(1)直接写
int (*f1(int))(int*, int);
(2)尾置
auto f1(int)->int (*)(int*, int);
(3)使用类型别名
using F = int(int*, int);
using PF = int(*)(int*, int);
PF f1(int); //正确
F f1(int); //错误,F是函数不是函数指针
F *f1(int); //正确
(4)使用
decltype
size_type sumLength(const string&, const string&);
decltype(sumLength) *getFcn(const string &);