《C++ primer》第六章
一、函数基础
函数的定义包括:返回类型、函数名、形参列表、函数体
/*编写函数*/ int fact(int val) { int ret = 1; while (val > 1) ret *= val--; return ret; } /*调用函数*/ int main(void) { int j = fact(5); cout << "5! is: " << j << endl; return 0; } /*主调函数与被调函数: *Ⅰ、函数调用完成两项工作:用实参初始化形参;将控制权转交给被调函数 *Ⅱ、执行函数第一步是(隐式)定义并用实参值初始化形参 *Ⅲ、被调函数遇到return时做两项工作:返回return语句中的值(有的话);将控制权转交给主调函数*/ /*形参与实参: *Ⅰ、有几个形参就必须有几个实参 *Ⅱ、实参的类型必须可以转化成形参的类型,否则引发错误 *Ⅲ、函数可以有多个形参,每个形参必须有类型,形参之间用逗号隔开*/ fact("hello"); //错误,const char*无法转化为int fact(); //错误,实参个数与形参个数不一致 fact(1, 2, 3); //错误,实参个数与形参个数不一致 fact(3.14); //正确,double可以转化成int
1.1、局部对象
/*作用域与生命周期 *作用域:对象的可见范围 *生命周期:对象存在的一段时间 *当内层对象与外层对象重名时,在内层时,内层对象隐藏(覆盖)外层对象*/ /*自动变量 *在块中定义的变量,在块结束时就会被销毁,只在块内可见 *所以,先后几次执行同一个块,每一次都要重新定义变量*/ /*局部静态变量 *生命周期贯穿函数调用及之后的时间 *在函数调用结束之后,并不会被销毁,下次再进行此函数的调用时,该值依然有效 *仅在块内可见,快外不可见*/
1.2、函数声明与分离式编译
/*函数只能定义一次,但可以多次声明 *函数声明不包含函数体,以分号结束,无需形参名字,但是写上容易理解 *函数声明应该放在头文件中*/ /*编译链接多个源文件 *假设fact声明位于头文件chapter1.h,定义位于fact.cc,调用位于factMain.cc中 *当某个源文件发生改变,只需要编译该源文件,然后进行链接即可*/ g++ -c fact.cc -o fact.o g++ -c factMain.cc -o factMain.o g++ fact.o factMain.o -o factMain
二、参数传递
如果形参是引用类型,则它会绑定到对应的实参上,否则将实参的值拷贝后赋给形参
2.1、传值形参
/*如果实参是值传递,函数对形参做的所有操作都不会影响实参*/ /*形参是指针时,可以通过该指针修改实参所指向的对象,但是无法修改实参本身 *例如:指针p指向变量a,被调函数中,完全可以使用p来修改a的值,返回后,a的值会发生改变 *但是,在被调函数中,修改指针指向,只能修改p的副本的指向,所以返回后,p依然指向a * *在C++中,建议使用引用类型的形参代替指针*/
2.2、传引用参数
/*对引用型形参而言,操作该引用,就是操作器所引用的对象 *使用引用可以避免拷贝,减少开销 *使用引用可以返回额外的信息(形参也可以记录一些信息)*/ //返回s中c第一次出现的位置的索引并统计c出现的次数 string::size_type find_char(const string &s, char c, string::size_type &cnt) { auto ret = s.size(); cnt = 0; for (decltype(ret) i = 0; i != s.size(); ++i) { if (s[i] == c) { if (ret == s.size()) ret = i; //记录第一次出现的位置 ++cnt; //统计出现次数 } } return ret; }
2.3、const形参和实参
/*当实参给形参传值时,忽略形参的顶层const。即,当形参为const时,传入const和非const都可以 *但是,当形参是常量时,不可以使用const对其进行初始化*/ int i = 0; const int ci = i; string::size_type ctr = 0; double dval = 0.0; void reset(int &i) { i = 0; } reset(ci); //错误,不能用const初始化非常量 reset(i); //正确 reset(ctr); //错误,类型不匹配,ctr是无符号类型 reset(dval); //错误,类型不匹配,dval是double类型 void reset(int *i) { *i = 0; } reset(&ci); //错误,不能用const int*初始化int* reset(&i); //正确 reset(&ctr); //错误,类型不匹配 reset(&dval); //错误,类型不匹配 string::size_type find_char(const string &s, char c, string::size_type &cnt); //声明 auto loc1 = find_char("hello world", 'o', ctr); //正确,但是如果函数第一个形参不是const就是错误的 string str("hello world"); auto loc2 = find_char(str, 'o', ctr); //正确,无论第一个形参是不是const均正确
2.4、数组形参
/*数组的两个性质: *Ⅰ、不允许拷贝数组 *Ⅱ、使用数组时(通常)会将其转换成指针 *我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针*/ //下列三种形式是等价的,形参均为const int* void print(const int*); void print(const int[]); void print(const int[10]); int i = 0, j[1] = {0, 1}; print(&i); //正确,&i的类型是int* print(j); //正确,j转化成int*指向j[0] /*如何保证数组不越界? *Ⅰ、使用标记指定数组长度,例如,C语言风格字符串以'\0'为结束标志 *Ⅱ、使用标准库规范,begin()和end() *Ⅲ、显式传递一个表示数组大小的值*/ //Ⅰ void print(const char *cp) { if (cp) while (*cp) cout << *cp++; } //Ⅱ void print(const int *beg, const int *end) { while (beg != end) cout << *beg++ << endl; } //Ⅲ void print(const int ia[], size_t size) { for (size_t i = 0; i != size; ++i) cout << ia[i] << endl; } /*数组形参和const *若不需要对数组元素进行写操作,数组的形参应该是指向const的指针*/ /*数组引用形参 *此种用法限制了数组的大小必须为固定值*/ 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); //正确,实参是含有10个整数的数组 /*多维数组 *当多维数组传递给函数时,真正传递的是指向数组首元素的指针,而此首元素也是要给数组 *因此,指针就是一个指向数组的指针,数组第二维(以及后面所有维度)的大小不能忽略*/ void print(int (*matrix)[10], int rowsize) {……} //此时说明,matrix是列是10,行是rowsize的二维数组数组 void print(int (*arr)[3][4], int dim1) {……} //此时说明,arr是三位数组,声明为int arr[dim1][3][4]
2.5、main:处理命令行选项
/*有时,我们需要给main传递实参,一种常见的情况是用户通过设置一组选项来确定函数所要执行的操作 *int main(int argc, char argv[]) {……}。其中argc表示数组argv中字符串的数量,argv表示字符数组】 *使用argv中的实参时,可选的实参从argv[1]开始,argv[0]保存程序的名字*/ /*使用C++编写一个main函数,令其接受两个实参,把实参的内容连接成一个string对象并输出出来*/ #include <iostream> #include <string> using namespace std; int main(int argc, char* argv[]) { // 检查是否提供了足够的参数 if (argc < 3) { cerr << "Usage: " << argv[0] << " <arg1> <arg2>" << endl; return 1; // 返回非零值表示错误 } // 将两个参数连接成一个 string 对象 string result = string(argv[1]) + string(argv[2]); // 输出结果 cout << "Concatenated string: " << result << endl; return 0; // 返回 0 表示成功 } /*g++ 6.2.5.cc -o 6.2.5 *./6.2.5 hello world *输出:"Concatenated string: helloworld*/
2.6、含有可变形参的函数
/*当我们无法提前预知函数的形式参数有多少个并且形参类型均一致时,可以使用initializer_list *其定义在头文件initializer_list中 *initializer_list对象的元素永远是常量值 *向initializer_list形参中传递一个值的序列,必须把序列放在一对花括号中 *提供的操作: *Ⅰ、initializer_list<T> lst; //默认初始化,T类型元素的空列表 *Ⅱ、initializer_list<T> lst{a, b, c, …}; //lst元素数量和初始值一样多,列表中的元素是const *Ⅲ、lst2(lst)或lst2 = lst; //拷贝或赋值一个initialier_list对象不会拷贝列表元素,原始列表和副本共享元素 *Ⅳ、lst.size(); //返回列表中的元素的数量 *Ⅴ、lst.begin(); //返回指向lst中首元素的指针 *Ⅵ、lst.end(); //返回指向lst尾元素的下一个位置的指针*/ /*示例如下:*/ #include <iostream> #include <initializer_list> #include <vector> using namespace std; // 函数接受一个 initializer_list<int> 作为参数 void printNumbers(initializer_list<int> numbers) { cout << "Numbers: "; for (auto& num : numbers) { // 遍历 initializer_list cout << num << " "; } cout << endl; } // 函数接受一个 initializer_list<string> 作为参数 void printStrings(initializer_list<string> strings) { cout << "Strings: "; //for (auto& str : strings) { // 遍历 initializer_list // cout << str << " "; //} for (auto beg = strings.begin(); beg != strings.end(); ) cout << *beg++ << " "; cout << endl; } int main() { // 调用函数,传递初始化列表 printNumbers({1, 2, 3, 4, 5}); // 输出: Numbers: 1 2 3 4 5 printStrings({"Hello", "World", "from", "C++"}); // 输出: Strings: Hello World from C++ return 0; }
三、返回类型和return语句
return的两种形式:“return ;”和“return expression;”
3.1、无返回值函数
返回void的函数不是非要有return,因为此类函数在最后一句后面会隐式地执行return
如果需要中途返回,可以使用return直接返回
void swap(int &val1, int &val2) { if (val1 == val2) return ; //若相等,此处直接返回 int tmp = val2; val2 = val1; val1 = tmp; //若不等,此处隐式执行return }
3.2、有返回值函数
此类return后必须有返回值,并且return的返回值必须与函数的返回类型相同,或者可以隐式转化为函数返回类型
在含有return语句的循环后面应该也有一条return语句
/*值是如何返回的? *Ⅰ、当返回局部变量时,返回值将返回一个未命名的对象,需要拷贝 *Ⅱ、当返回引用时,不需要有临时变量存储返回值,不需要拷贝, *当然,如果用返回值做初始值,依然是需要拷贝的*/ string make_plural(size_t chtr, const string &word, const string &ending) { return (ctr > 1) ? word + ending, word; } /*返回局部变量的引用/指针,是错误的,函数结束后,局部变量会被销毁*/ /*返回类类型的函数的调用运算符 *调用运算符和点、箭头运算符的优先级一致,并且满足左结合率 * *下列语句中,由于调用运算符"()"和"."优先级一致,所以先返回shortString(s1, s2), *得到string类型对象,然后使用点运算符调用size成员函数*/ auto sz = shortString(s1, s2).size(); /*当函数的返回值类型是引用时,调用该函数可以得到左值;其它类型的函数,调用函数得的右值*/ char &get_val(string &str, string::size_type ix) { return str[ix]; } string s("a string"); get_val(s, 0) = "A"; //将s[0]的值改为A /*列表初始化返回值 *C++11标准允许函数返回花括号包围的值的列表 *如果函数返回的是内置类型,则话口号最多包含一个值,并且该值占用空间不应该大于目标类型的空间 *如果返回的是类类型,由类本身定义初始值如何使用*/ /*递归: *一个函数直接/间接调用自身 *在递归函数中,一定有某条路径不包含递归调用,否则将无休止地递归下去,直到栈空间耗尽*/
3.3、返回数组指针
数组不能拷贝,函数不能返回数组,不过,函数可以返回数组的指针或引用
可以使用类型别名的方法,简化返回数组的指针或引用
/*当返回数组指针时,有以下四种表达方法*/ //Ⅰ int (*func(int i))[10]; //Ⅱ using arrT = int[10]; arrT* func(int i) //Ⅲ,使用尾置返回类型 auto func(int i) -> int(*)[10]; //Ⅳ int odd[] = {1, 3, 5,6, 9}; int even[] = {0, 2, 4, 6, 8}; decltype(odd) *arrPtr(int i) { return (i % 2) ? &odd : &even; }
四、函数重载
- 函数名相同,返回值可以相同也可以不同,形参列表一定不同(数量、顺序、类型)
- 一个拥有顶层const的形参无法与另一个没有顶层const的形参区分开
- 一个拥有底层const的形参可以与另一个没有底层const的形参区分开
- const_cast与重载
const string &shortSTring(const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; } string &shortString(string &s1, string &s2) { auto &r = shortSting(const_cast<const string&>(s1), const_cast<const string&>(s2)); return const_cast<string&>(r); }
提示:
- 如果不需要改变形参的话,建议声明为const。因为,引用常量可以使用const对象和非const对象初始化,而引用不可以用const对象进行初始化。并且,当其它函数将形参定义为常量引用,而当前函数将形参定义为引用,并且当前函数会在其它函数中被调用,那么可能传值是会出现错误
string:size_type find_char(string &s, char c, string::size_type &cnt); //声明 bool is_sentence(const string &s) { string::size_type ctr = 0; //错误,因为s是const,而fine_char的第一个形参是非const return find_char(s, '.', ctr) == s.size() - 1 && ctr == 1; }
- 默认实参:调用有默认实参的函数时,可以包含(也可以省略)该实参。函数调用时,实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参(只能省略尾部实参)
- 内联函数(规模小、简单)可以避免函数调用的开销,直接在调用点“展开”。在函数返回值前面加上inline,此说明只是向编译器发出一个请求,编译器可以忽略此请求
- constexpr函数可以在编译时求值,并且一般返回常量表达式(看下面的代码),被指定为隐式内联
#include <iostream> using namespace std; // constexpr 函数 constexpr int add(int a, int b) { return a + b; } int main() { constexpr int a = 10; // 常量表达式 constexpr int b = 20; // 常量表达式 constexpr int result1 = add(a, b); // 编译时求值,result1 是常量表达式 int x = 30; // 非常量表达式 int y = 40; // 非常量表达式 int result2 = add(x, y); // 运行时求值,result2 不是常量表达式 cout << "result1: " << result1 << endl; cout << "result2: " << result2 << endl; return 0; }