尽量使用常量引用
把函数不会改变的形参定义成普通的引用是一种常见的错误,这不仅会给函数调用者带来一种误导,即函数可以修改它实参的值,还会引起以下的严重错误。
int get_size(string& s)
{
return s.size();
}
该函数的形参是普通引用,因此不能接受常量引用,由于C++中,字符串字面值是const char* 类型,试图用常量引用初始化非常量引用的变量s将是非法的。例如以下调用非法。
get_size("abc"); // 非法
main处理命令行选项
当使用argv中的实参时,一定要记得可选实参从argv[1]开始;argv[0]保存程序的名字,而非用户输入。
// 调用prog -d -o ofile data0
int main(int argc, char* argv)
{
// argc = 5;
// argv[0] == "prog";
// argv[1] == "-d";
// argv[2] == "-o";
// argv[3] == "ofile";
// argv[4] == "data0";
// argv[5] == 0;
}
函数返回值作为左值的情况
当函数返回值类型是引用类型时,调用时得到的是左值,其余情况都是右值。
int& get_A_i(int* A, int index)
{
return A[index];
}
int main()
{
int A[10];
get_A_i(A, 1) = 1; // 等价于A[1] = 1;
}
需要注意的是不要返回局部变量的引用,static修饰的局部变量除外,因为他有特殊的生命周期。
int &get_i()
{
static int i = 243;
return i;
}
int main()
{
get_i() = 1; // 执行完毕后i=1;
}
声明一个返回数组指针的函数
int(*func(int i))[10];
可以按照从里往外的方式理解该声明的含义:
- func(int i)代表声明的是带有一个int形参的函数,函数名叫func。
- (*func(int i))代表返回值是指针类型。
- (*func(int i))[10]代表指针指向的是一个长度为10的数组。
- int (*func(int i))[10]代表数组中元素的类型是int型。
在C++11中我们可以使用尾置返回类型,从而使上述声明更易理解:
auto func(int i) -> int(*)[10];
把返回值类型放在->的指针之后,就可以让声明更易理解。
重载和const形参
由于顶层const在赋值时没有什么要求,举个例子来说就是int类型可以给const int类型赋值,反过来也可以。所以一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来:
int func(int i);
int func(const int i);
int func2(int *p);
int func2(int *const p);
此处func和func2的两个声明是等价的。但对于底层const来说,就可以区分,比如:
int func(int &i1);
int func(const int &i2);
int func2(int *p1);
int func2(const int *p2);
此时i2和p2的const都是底层const,由于底层const之间不能随意转换,比如作为const int&类型的i2不能初始化int&类型的i1,虽然i1可以初始化i2,但编译器还是有方法选择匹配度更高的函数,因此这样的重载形式是合法的。
关于默认参数
局部变量不能作为函数的默认参数,除此之外。只要表达式的类型可以转换成形参所需的类型,该表达式就能作为默认实参。例如:
int wd = 80;
char def = ' ';
int func() { return 0; }
string screen(int a = wd, char b = def, int c = func())
{
return "";
}
int main()
{
screen(); //相当于screen(wd,def,func())
def = '*';
int wd = 70; // 隐藏外层wd
screen(); // 虽然wd被隐藏,但是局部变量wd与函数的默认参数没有任何关系,所以该语句等价于screen(80,'*',func());
}
需要特别注意的是上例中的第二个screen(),虽然定义了一个局部变量wd隐藏外部的wd,但是这个局部变量wd和函数默认参数没有任何关系,所以默认参数wd的值仍然是80。
调试帮助
C++编译器定义了__func__变量它的值是当前调试的函数的名字。除此以外,预处理器还定义了四个对于程序调试很有用的名字:
__FILE__
存放文件名的字符串字面值。__LINE__
存放当前行号的整形字面值。__TIME__
存放文件编译时间的字符串字面值。__DATE__
存放文件编译日期的字符串字面值。
int main()
{
cerr << "Error: " << __FILE__
<< " : in function " << __func__
<< " at line " << __LINE__ << endl;
}
上面代码输出:
Error: c:\users\Prosat\source\repos\solution\solution\main.cpp : in function main at line 217
所以可以看出编译器提示我们代码错误时所使用的就是这几个变量。
函数指针
定义、赋值和使用
比如我有这样一个函数:
bool lengthCompare(const string &s1, const string &s2)
{
return s1.size() > s2.size();
}
要定义对应的函数指针,需要确保返回值类型和形参列表精确匹配(能互相转化也不行),对于重载函数来说,我必须指明要调用的是哪个函数。
// pf指向一个函数,返回值是bool,形参是const string &s1, const string &s2。
bool(*pf)(const string &s1, const string &s2); // 未初始化
// 赋值
pf = lengthCompare; // 合法,会自动将lengthCompare的地址赋值给pf。
pf = &lengthCompare; // 等价于上面的语句
// 调用
bool b1 = pf("hello", "goodbye");
bool b2 = (*pf)("hello", "goodbye"); // (*pf)括号不能少,否则含义不同。
返回指向函数的指针
int(*f3(int i))(int *a, int b);
同样,我们采用从里往外的方式理解该声明的含义:
- f3(int i)代表声明的是带有一个int形参的函数,函数名叫f3。
- (*func(int i))代表返回值是指针类型。
- int (*func(int i))(int *a,int b)代表指针指向的是一个形参为(int *a,int b)返回值为int的函数。
对应的尾指返回类型声明:
auto f3(int i) -> int(*)(int *a, int b);
个人感觉尾置返回类型真的容易理解多了,看上去很明了。