该笔记包括函数的一些基础知识,根据个人的知识掌握情况有所简略,日后有需要再补充。
#2022/01/08 补充了模板函数
目录
函数基础
一个求阶乘的函数示例
#include <iostream> int fact(int s) { int result = 1; //不能放入循环内 for (; s != 1; ) { result *= s--; } return result; } int main() { int s; std::cin >> s; int output = fact(s); std::cout << output; }
局部静态变量在程序的执行路径第一次经过时初始化,并且直到程序终止才被销毁。
size_t count_calls(){ static size_t ctr = 0; //在函数调用结束后,这个值仍有效 reutrn ++ctr; } int main(){ //将依次记录函数被调用的次数,输出的结果是1到10的数字 for (size_t i = 0; i != 10; ++i) cout << count_calls() << endl; return 0; }
(函数声明和分离式编译暂略)
参数传递
当形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名;也就是说,引用形参是它对应的实参的别名。
当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用。
传值参数
当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响初始值。
例如调用fact(s)
不会改变传入的实参s
的值。
指针形参
指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。
void reset(int *ip) { *ip = 0; //改变指针ip所指对象的值 ip = 0 //只改变了ip的局部拷贝,实参未被改变 } int main{ int i = 42; reset(&i); //改变i的值而非i的地址 cout << "i = " << i << endl; //输出i = 0 }
传引用参数
重写reset
函数
void reset(int &i) // i是传给reset函数的对象的另一个名字 { i = 0 //只改变了i所引对象的值 } int main{ int j = 42; reset(j); //j采用传引用方式,它的值被改变 cout << "j = " << j << endl; //输出j = 0 }
使用引用避免拷贝
拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作,这是应通过引用形参访问。
bool isShorter(const string &s1, const string &s2){ return s1.size() < s2.size(); }
使用引用形参返回额外信息
//返回s中c第一次出现的位置索引 //引用形参occurs负责统计c出现的总次数 string::size_type find_char(const string &s, char c, string::size_type &occurs) { auto ret = s.size(); //第一次出现的位置 occurs = 0; //设置表示出现次数的形参 for (decltype(ret) i = 0; i != s.size(); **i) { if (s[i] == c) { if (ret == s.size()) ret = i; //记录c第一次出现的位置 ++occurs; //将出现的次数加1 } } return ret; //出现次数通过occurs隐式地返回 }
const形参和实参
void fcn (const int i) { /* fcn能够读取i,但是不能向i写值 */ }
调用fcn函数时,既可以传入const int也可以传入int。但反过来不行!
尽量使用常量引用
我们不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。
例如在find_char函数中:
string::size_type find_char(const string &s, char c, string::size_type &occurs); //出现错误 find_char("Hello World", 'o', ctr);
另一个问题在于,假如其他函数把它们的形参定义成常量引用,那么find_char则无法在此类函数中正常使用。
bool is_sentence(const string &s){ //如果在s的末尾有且只有一个句号,则s是一个句子 string::size_type ctr = 0; //此时调用find_char将会出现错误 return find_char(s, '.', ctr) == s.size() - 1 && ctr == 1; }
数组形参
数组的两个特殊性质:
- 不允许拷贝数组。
- 使用数组时通常会将其转换成指针。
因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
// 尽管形式不同,这三个print函数是等价的 // 每个函数都有一个const int*类型的形参 void print(const int*); void print(const int[]); //可以看出来,函数的意图是作用于一个数组 void print(const int[10]); //这里的维度表示我们期望数组含有多少元素,实际不一定 int i = 0, j[2] = {0, 1}; print(&i); //正确:&i的类型是int* print(j); //正确:j转换成int*并指向j[0]
管理数组指针形参的三种方式
- 使用标记指定数组长度。要求数组本身包含一个结束标记。
void print(const char *cp) { if (cp) //若cp不是一个空指针 while (*cp) //只要指针所指的字符不是空字符 cout << *cp++; //输出当前字符并将指针向前移动一个位置 }
这种方法适用于那些有明显标记且标记不会与普通数据混淆的情况。
- 使用标准库规范。
void print (const int *beg, const int *end){ //输出beg到end之间(不含end)的所有元素 while (beg != end) cout << *beg++ << endl; //输出当前元素并将指针向前移动一个位置 } //需要传入两个指针 int j[2] = {0, 1}; print(begin(j), end(j)); //begin和end函数
- 显式传递一个表示数组大小的形参
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; } int i = 0, j[2] = {0, 1}; int k[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; print(&i); //错误:实参不是含有10个整数的数组 print(j); //错误:实参不是含有10个整数的数字 print(k); //正确
传递多维数组
//matrix指向数组的首元素,该数组的元素是由10个整数构成的数组 void print(int (*matrix)[10], int rowSize) {/*...*/}
Note:将多维数组传递给函数时,真正传递的是指向数组首元素的指针。因为多维数组是数组的数组,指针就是一个指向数组的指针。数组第二维(以及后面的所有维度)的大小是数组类型的一部分,不能省略。
返回类型
返回数组指针
声明一个返回数组指针的函数
Type (*function (parameter_list)) [dimension] // 例如 int (*func(int i)) [10];
使用尾置返回类型
//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组 auto func(int i) -> int(*)[10];
使用decltype
int odd[] = {1,3,5,7,9}; int even[] = {0,2,4,6,8}; decltype(odd) *arrPtr(int i) { return (i % 2) ? &odd : &even; //返回一个指向数组的指针 }
函数重载
如果同一个作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
例如:
void print(const char *cp); void print(const int *beg, const int *end); void print(const int ia[], size_t size);
重载和const形参
Record lookup(Phone); Record lookup(const Phone); //重复声明 Record lookup(Phone*); Record lookup(Phone* const); //重复声明 //对于接受引用或指针的函数来说,对象是常量还是非常量对应的形参不同 Record lookup(Account&); //函数作用于Account的引用 Record lookup(const Account&); //新函数,作用于常量引用 Record lookup(Account*); //新函数,作用于指向Account的指针 Record lookup(const Account*); //新函数,作用于指向常量的指针
函数指针
函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定。例如:
// 比较两个string对象的长度 bool lengthCompare(const string &, const string &);
这个函数的类型是bool(const string &, const string &)
。想要声明一个可以指向该函数的指针,只需要用指针替换函数名即可:
// pf指向一个函数,该函数是两个const string的引用,返回值是bool类型 bool (*pf)(const string &, const string &); //未初始化
使用函数指针
当我们把函数名作为一个值使用时,该函数自动地转换成指针。例如:
pf = lengthCompare; //pf指向名为lengthCompare的函数 pf = &lengthCompare; //等价于上一句,&符号是可选的
此外,还能直接使用指向函数的指针调用该函数,无需提前解引用指针:
bool b1 = pf("hello", "goodbye"); //调用lengthCompare函数 bool b2 = (*pf)("hello", "goodbye"); //一个等价的调用 bool b3 = lengthCompare("hello", "goodbye"); //另一个等价的调用
在指向不同函数类型的指针间不存在转换规则。但可以为函数指针赋一个nullptr或者值为0的整型常量表达式,表示该指针没有指向任何一个函数:
string::size_type sumLength(const string &, const string &); bool cstringCompare(const char*, const char*); pf = 0; //正确:pf不指向任何函数 pf = sumLength; //错误,返回类型不匹配 pf = cstringCompare; //错误:形参类型不匹配 pf = lengthCompare; //正确:函数和指针的类型精确匹配
函数指针形参
// 第三个形参是函数类型,它会自动地转换成指向函数的指针 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 &)); //调用 void useBigger(s1, s2, lengthCompare);
返回指向函数的指针
using F = int(int*, int); //F是函数类型,不是指针 using PF = int(*)(int*, int); //PF是指针类型 PF f1(int); //正确:PF是指向函数的指针,f1返回指向函数的指针 F f1(int); //错误:F是函数类型,f1不能返回一个函数 F* f1(int); //正确:显式地指定返回类型是指向函数的指针 // 也可以 int (*f1(int))(int*, int); // 尾置返回类型 auto f1(int) -> int(*)(int*, int);
模板函数
类似于如下函数体基本相同,只是第二参数的类型有所差异的函数可以采用模板函数。
void display_message(const string&, const vector<int>&); void display_message(const string&, const vector<double>&); void display_message(const string&, const vector<string>&); void display_message(const string &msg, const vector<int> &vec){ cout << msg; for (int ix = 0; ix < vec.size(); ++ix) cout << vec[ix] << ' '; } void display_message(const string &msg, const vector<string> &vec){ cout << msg; for (int ix = 0; ix < vec.size(); ++ix) cout << vec[ix] << ' '; }
function template定义示例如下:
template <typename elemType> void display_message(const string &msg, const vector<elemType> &vec){ cout << msg; for (int ix = 0; ix < vec.size(); ++ix){ elemType t = vec[ix] cout << t << ' '; } }
function template使用:
vector<int> ivec; string msg; // ... display_message(msg, ivec)
编译器会将elemType绑定为int类型,然后产生一份对应的函数实例。
function template的重载:
template <typename elemType> void display_message(const string &msg, const vector<elemType> &vec); template <typename elemType> void display_message(const string &msg, const list<elemType> <);