一、基础
1. 函数的定义
返回类型、函数名字、0个或多个形参组成的列表以及函数体。
2. 函数的调用
完成两项工作:用实参初始化函数对应的形参;将控制权转移给被调用的函数。
3. 形参与实参
实参是形参的初始值。实参的类型必须与对应的形参类型相匹配,(能隐式转换也可以)
4.形参列表
每个形参必须有一个声明符
void func() {} //隐式定义空形参列表
void func(void) {} //显示定义空形参列表
二、 参数传递
形参的类型决定了形参和实参交互的方式。
如果形参是引用类型,它将绑定到对应的实参上,即引用形参是它对应的实参的别名;
否则,将实参的值拷贝后赋给形参,形参和实参是两个独立的对象。
1. 指针形参
当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。
因为指针可以使我们间接地访问它所指的对象,所以通过指针可以修改它对象的值。
(最好不用指针形参传递需要改变的实参对象,C++中用引用类型的形参更好)
2. 传引用参数
int n = 0,i = 42;
int& r = n;//r绑定了n(即r是n的另一个名字)
r = 42;//现在n的值是42
r = 1;//现在n的值和i相同
i = r;//i的值和n相同
void reset(int& i) //i是传给reset函数的对象的另一个名字
{
i = 0;//改变i所引用对象的值
}
int j = 42;
reset(j);//j采用传引用方式,它的值被改变
cout << "j = " << j << endl;//输出j=0
上述调用过程中,形参 i 仅仅是 j 的别名。
使用引用避免拷贝(拷贝比较低效,并且有些类型并不支持拷贝操作)
如果函数无需改变引用形参的值,最好将其声明为常量引用。
3. 形参、局部变量、局部静态变量的区别
形参和函数体内部的变量统称为局部变量,每当函数执行完就会释放;而局部静态变量存储在静态存储区,生命周期贯穿函数调用及之后的时间。
4. 局部静态变量
某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。
局部静态变量在程序执行路径第一次经过对象定义语句时初始化,并且知道程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。
//统计自己被调用了多少次的函数
size_t count_calls()
{
static size_t ctr = 0; //调用结束后,这个值依然有效
return ++ctr;
}
5. 数组形参(必须保证数组不会越界)
void print(const int*)
void print(const int[])
void print(const int[10]) //这几个是等价的,传入的都是数组第一个元素的指针
void print(int (&arr)[10]) //arr是具有十个整数的整形数组的引用,括号不能少
如果我们传给print函数的是一个数组,那么实参自动地转换成指向数组某元素的指针。
void func(int *matrix[10]) //形参是十个指针的数组
void func(int (*matrix)[10]) //形参是指向有十个整数的数组的指针
void func(int matrix[][10]) //形参是指向含有十个整数的数组的指针
6. 处理可变形参
为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:
a)所有实参的类型相同,传递一个initializer_list的标准库类型(是一个模板类型)
initializer_list<T> lst; 默认初始化:T类型元素的空列表
initializer_list<T> lst{a, b, c,…}; lst的元素和初始值一样多,lst的元素是对应初始值的副本,列表中的元素是const的
lst2(lst); //拷贝或赋值一个initializer_list 对象不会拷贝列表中的元素,而是共享元素
lst2 = lst;
lst.size(); 返回列表中的元素格式
lst.begin(); 返回指向lst中首元素的指针,不是迭代器
lst.end(); //返回指向lst中尾元素的下一个元素的指针
使用实例
int sum(initializer_list<int> li)
{
int total = 0;
for(auto beg = li.begin(); beg!=li.end(); ++beg)
total += *beg;
return total;
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
cout<<sum({a,b})<<endl;
cout<<sum({a,b,c})<<endl; //注意这个格式,其实就是容器的列表初始化
return 0;
}
特点是:initializer_list对象中的元素永远是常量值,无法改变元素的值。
b)实参类型不同,之后再介绍
7.省略符形参
省略符形参应该仅仅适用于C和C++通用的类型,大多数类型的对象在传递给省略符形参时都无法正确拷贝。
//省略符形参只能出现在形参列表的最后一个位置,形式只有以下两种
void func (parm_list, ...);
void func (...)
三、返回类型
1. 不要返回局部对象的引用或指针
2. 调用一个返回引用的函数得到左值
3. 列表初始化返回值
vector<int> process()
{
return {1,2,3,4};
}
4. main函数不能调用它自己
5. 返回数组指针
因为数组不能被拷贝,所以函数不能返回数组。不过可以返回数组的指针或引用。
可以使用简化的类型别名方式。
typedef int arrT[10];
using arrT = int[10]; //两个等价声明,arrT是一个类型别名,表示的类型是含有10个整数的数组
arrT* func(int i);
6. 尾置返回类型
函数的返回值类型放在函数的参数列表后面
形式为在形参列表后面并以一个->符号开始
特点:
a) 任何函数的定义都能够使用尾置返回
b) 对于函数的返回类型比较复杂的更为有效
c) 使代码更加直观和简洁
d) 使用“尾置”返回类型来配合auto、decltype使用提高泛型编程
(具体查资料,例子没有看懂)
四、函数重载
1. main函数不能重载
2. 顶层const不影响传入函数的对象,一个拥有顶层const的形参和另一个没有顶层const的形参无法区分开来
3. 如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的。
4. 在C++中,名字查找发生在类型检查之前
5. const_cast(只能转换指针或引用)
使用const_cast去掉const属性,其实并不是真的改变原类类型(或基本类型)的const属性,它只是又提供了一个接口(指针或引用),使你可以通过这个接口来改变类型的值。
const_cast<type_id> (expression)
//typeid和expression的类型是一样的
作用:
a)常量指针被转化成非常量的指针,并且仍然指向原来的对象;
b)常量引用被转换成非常量的引用,并且仍然指向原来的对象;
c)const_cast一般用于修改底指针。如const char *p形式。
6. 函数的匹配
寻找最佳匹配,即形参类型与实参类型最匹配的那个可行函数。
7.实参类型转换
为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级
a)精确匹配:实参类型和形参类型相同;实参从数组类型或函数类型转换成对应的指针类型;向实参添加顶层const或者从实参中删除顶层const
b)通过const转换实现的匹配
c)通过类型提升实现的匹配
d)通过算数类型转换实现的匹配
e)通过类类型转换实现的匹配(具体见书第219页)
五、特殊用途语言特性
1. 默认实参:在给定作用域内,一个形参只能被赋予一个默认实参。
C++规定,要把没有默认实参的形参放到前面,而默认实参的形参写到靠右的位置,并且一个形参如果有默认实参,那么其右侧必须都有默认实参。即默认实参在尾部。
2. 内联函数
可以避免函数调用的开销,通常内联函数就是在每个调用点“内联”地展开。
一般用于:优化规模较小、流程直接、频繁调用的函数。
很多编译器都不支持内联递归函数。
3. constexpr函数
指能用于常量表达式的函数。
要遵循的约定:函数的返回类型及所有形参的类型都得是字面值类型; 并且函数体中有且只有一个return语句。
constexpr函数不一定返回的是常量表达式。
3. 把内联函数和constexpr函数放在头文件内(通常)
4. 调试帮助
程序可以包含一些用于调试的代码,但是这种代码只在开发程序时使用,当程序编写完成准备发布时,要屏蔽相关调试代码。
使用两种预处理功能:
a)assert
是一种预处理宏(即预处理变量),类似内联函数
assert(expr);
首先对expr求值,如果表达式为假,assert输出信息并终止程序的执行。如果表达式为真,assert什么也不做。
b) NDEBUG预处理变量
assert的行为依赖于一个名为NDEBUG的预处理变量的状态,如果定义了NDEBUG,则assert可以什么也不做。默认状态下是没有定义NDEBUG的。
#ifdef NDEBUG
#undef NDEBUG
#include <assert.h>
#define NDEBUG
#else
#include <assert.h>
#endif // #ifdef NDEBUG
六、函数指针
1. 函数指针指向的是函数而非对象。
bool (*pf)(const string &, const string &)
//pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
bool *pf(const string &, const string &)
//声明一个名为pf的函数,该函数返回bool*
注意加不加括号的区别,如果是函数指针,则必须要加括号。
2. 使用函数指针
当我们把一个函数名作为一个值使用的时候,该函数自动地转换为指针。
pf = lengthCompare //pf指向名为lengthCompare的函数
pf = &lengthCompare //两者是等价的赋值语句:取地址符是可选的
调用函数指针(可以直接使用指向函数的指针调用该函数,无需提前解引用指针)
bool b1 = pf("hello", "goodbye"); //调用length函数
bool b2 = (*pf)("hello", "goodbye"); //等价的调用
bool b2 = lengthCompare("hello", "goodbye"); //另一个等价的调用