形参列表中的形参通常用都好隔开,即使哥哥形参的类型一样,也必须把两个类型都写出来
int f3(int v1, v2) {/* ... */} // 错误 int f4(int v1, int v2) {/* ... */} // 正确
函数的返回值不能是数组,但可以是指向数组或函数的指针
生命周期 (lifetime)
局部变量(local variable)
:形参和函数体内部定义的变量
自动对象(automatic object)
:只存在于快执行期间的对象,形参 是一种自动对象
局部静态对象(local static object)
:在程序的执行路径第一次经过对象定义语句时初始化,知道程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响
// 统计自己被调用的次数 size_t count_calls() { static size_t ctr = 0; // ctr 是局部静态对象,调用结束后, return ++ctr; // ctr 仍然有效 } // 输出从1到10(包括)的数字 int main() { for (size_t i = 0; i != 10; ++i) cout << count_calls() < endl; return 0; }
函数的声明不包括函数体,所以可以省略形参
void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);
函数应该在头文件中声明而在源文件中定义
含有函数声明的头文件应该被包含到定义函数的源文件中
通过使用引用形参,函数可以改变实参的值
使用引用以避免拷贝
// 比较两个 string 对象的长度,由于 string 对象可能会很长,应避免直接拷贝 bool &isShorter(const string &s1, const string &s2) { return s1.size() < s2.size() }
const
形参和实参允许定义若干具有相同名字的函数,前提是不同函数的形参列表有明显区别
void fcn(const int i) {/* .. */} // fcn 能读取 i,但不能向 i 写值 void fcn(int i) {/* ... */} // 错误:重复定义了 fcn(int)
可以使用非常量初始化一个底层 const
对象,但是不能用底层 const
对象来初始化一个非常量
数组的两个性质:
数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外信息
使用标记指定数组长度:适用于有明显结束标记且该标记不会与普通数据混淆的情况
使用标准库规范:传递指向数组首元素和尾后元素的指针
void print(const int *beg, const int *end) { while(beg != end) cout << *beg++ << endl; } int j[2] = {0, 1}; print(begin(j), end(j));
显式传递一个表述数组大小的形参
void print(const int ia[], size_t size) { for (size_t i = 0; i != size; ++i) cout << ia[i] << endl; } int j[] = {0, 1}; print(j, end(j) - begin(j));
数组引用形参
void print (int (&arr)[10]) { for (auto elem : arr) cout << elem << endl; }
&arr
两端的括号必不可少
f(int &arr[10]) // arr 是引用的数组 f(int (&arr)[10]) // arr 是具有10个整数的整型数组的引用
数组的大小是过程数组类型的一部分,限制了 print
函数的可用性
多维数组
void print(int (*matrix)[10], int rowSize) {/* ... */} // int (*matrix)[10]; 指向含有10个整数的数组的指针
main
处理命令函选项// 假设 main 函数位于可指向文件 prog 内, 向程序传递下面的选项 prog -d -o ofile data0 int main(int argc, char *argv[]) { ... } argv[0] = "prog"; // 或者 argv[0] 也可以指向一个空字符串 argv[1] = "-d"; argv[2] = "-o"; argv[3] = "ofile"; argv[4] = "data0"; argv[5] = 0;
argv
是一个数组, 它的元素是指向C风格字符串的指针; 第一个形参 argc
表示数组中字符串的数量argv
中的实参时, 可选的实参从 argv[1]
开始, argv[0]
保存程序的名字, 而非用户输入return
语句引用返回左值
char &get_val(string &str, string::size_type ix) { return str[ix]; } int main() { string s("a value"); cout << s << endl; get_val(s, 0) = 'A'; // 将 s[0] 的值改为 A cout << s << endl; return 0; }
函数可以返回花括号包围的值的列表
递归 (recursive function): 函数调用了自身
// 计算 val 的阶乘 int factorial(int val) { if (val > 1) return factorial(val - 1) * val; return 1; }
main
函数不能调用自己使用类型别名
typedef int arrT[10]; // arrT 是一个类型别名, 表示的类型是含有10个整数的数组 using arrT = int[10]; // 等价于上一条语句 // 声明 func 函数, 该函数返回一个指向含有10个整数的数组的指针 arrT* func(int i);
不使用类型别名
int arr[10]; // arr 是一个含有10个整数的数组 int *p1[10]; // p1 是一个含有10个指针的数组, 指针指向整数 int (*p2)[10]; // p2 是一个指针,指向含有10个整数的数组 // 和上面的声明一样, 定义返回数组指针的函数时, 数组的维度必须跟在函数名字之后, 例如 int (*func(int i))[10];
func(int i)
表示调用 func
函数时需要一个 int
类型的实参(*func(int i))
表示可以对函数调用的结果执行解引用操作(*func(int i))[10]
表示解引用得到的是一个大小是10的数组int (*func(int i))[10]
表示数组中的元素是 int
类型使用尾置返回类型
// func 接受一个 int 类型的参数, 返回指向含有10个整数的数组的指针 auto func(int i) -> int(*) [10]
使用 decltype
int odd[] = {1, 3, 5, 7, 9}; int even[] = {0, 2, 4, 6, 8}; // 返回一个指针, 指向含有5个整数的数组 decltype(odd) *arrPtr(int i) { // 对函数调用的结果解引用得到的是 odd 类型 // 的数组 return (i % 2) ? &odd : &even; // 返回一个指向数组的指针 }
重载函数(overloaded function)
同一作用域内的几个函数名字相同但形参列表不同
不允许两个函数除了返回类型外其他所有的要素都相同
顶层 const
不影响传入函数的对象: 一个拥有顶层 const
的形参无法和另一个没有顶层 const
的形参区分开来
如果函数的形参是某种类型的指针或引用, 则通过区分其指向的对象是否为常量可以实现函数重载, 此时的 const
是底层的
const_cast
和重载
string::size_type reset(const string &s) { auto ret = s.size(); for (decltype(ret) i = 0; i != s.size(); ++i) { if (s[i] == '.') ret = i; } return ret; } // 重载函数 shorterString, 返回值不是 从const string &, 而是 string & const string &shorterString(const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; } string &shorterString(string &s1, string &s2) { auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2)); return const_cast<string&>(r); }
如果在内层作用域中声明名字, 它将隐藏外层作用域中声明的同名实体
typedef string::size_type sz; string screen(sz ht = 24, sz = wid = 80, char backgrnd = ' ');
constexpr
函数内联 (inline) 函数可避免函数调用的开销, 在函数的返回类型前面加上关键字 inline
就可以将函数声明成内联函数
inline const string & shortString(const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; }
constexpr
函数: 能用于常量表达式的函数
return
语句constexpr
函数被隐式地定义为内联函数constexpr
函数的返回值并非一个常量调试帮助
assert
预处理宏
assert (expr); // 当表达式 expr 为假, 输出信息并终止程序的执行 // 当表达式 expr 为真, 什么都不做
NDEBUG
预处理变量
如果定义了NDEBUG
, 则 assert
什么都不做, 默认状态下没有定义 NDEBUG
// 在main.c 文件的一开始写 #define NDEBUG $ CC -D NDEBUG mian.C # use /D with the Microsoft compiler
如果未定义NDEBUG
, 执行 #ifndef
和 #endif
之间的代码; 如果定义了NDEBUG
, 这些代码将被忽略掉
调试中可能用到的名字
名字 | 作用 | 类型 | 定义者 |
---|---|---|---|
__func__ | 存放函数名字 | const char 的一个静态数组 | 编译器 |
__FILE__ | 存放文件名 | 字符串字面值 | 预处理器 |
__LINE__ | 存放当前行号 | 整型字面值 | 预处理器 |
__TIME__ | 存放文件编译时间 | 字符串字面值 | 预处理器 |
__DATA__ | 存放文件编译日期 | 字符串字面值 | 预处理器 |
函数指针指向的函数而非对象, 指向某种特定类型, 函数的类型由它的返回类型和形参类型共同决定, 与函数名无关
// 比较两个 string 对象的长度 bool lengthCompare(const string &, const string &); // pf 指向一个函数, 该函数的参数是两个 const string 的引用, 返回值是 bool 类型 bool (*pf)(const string &, const string &); // 未初始化
*pf
代表 pf
是一个指针
后面的形参列表表示 pf
指向的是函数
左侧的 bool
表示函数的返回值是 bool
类型
*pf
两侧的括号不能省略, 不写括号时
// 声明一个名为 pf 的函数, 其返回值类型为 bool* bool *pf(const string &, const string &);
使用函数指针
把函数名作为一个值使用时, 该函数自动得转换成指针
pf = lengthCompare; // pf 指向名为 lengthCompare 的函数 pf = &lengthCompare; // 等价于上一条语句
可以直接使用指向函数的指针调用该函数, 无须提前解引用指针
// 以下三条语句等价 bool b1 = pf("hello", "goodbye"); bool b2 = (*pf)("hello", "goodbye"); bool b3 = lengthCompare("hello", "goodbye");
typedef
// Func 和 Func2 等价, 为函数类型 typedef bool Func(const string &, const string &); typedef decltype(lengthCompare) Func2; // FuncP 和 FuncP2 等价, 为指向函数的指针 typedef bool (*FuncP)(const string &, const string &); typedef decltype(lengthCompare) *FuncP2;
using
using F = int(int*, int); // F 是函数类型, 不是指针 using PF = int(*)(int *, int); // PF 是指针类型