c++ primer 6th 函数
一、函数基础
-
即使两个形参的类型一样,也必须把两个类型都写出来。
-
局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。
#include<iostream> #include<algorithm> #include<cstring> #include<map> #include<bits/stdc++.h> using namespace std; size_t count_calls() { static size_t ctr = 0; return ++ctr; } int main() { for(size_t i = 0;i != 10; ++i) { cout << count_calls() << endl; } return 0; }
如果局部静态变量没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0。
二、参数传递
-
使用引用避免拷贝
如果函数无需改变引用形参的值,最好将其声明为常量引用。
-
const形参和实参
当用实参初始化形参时会忽略掉顶层const。
void fcn(const int i); void fcn(int i);//错误重复定义了fcn(int)
-
数组引用实参
#include<stdio.h> #include<iostream> #include<string.h> #include<iostream> using namespace std; void print(int (&arr)[10]) { for(auto elem : arr) { cout << elem << endl; } } int main() { int a[10] = {1,2,3,4,5}; int i = 0; int j[2] = {0,1}; print(&i);//不是10个整数的数组 print(j);//不是10个整数的数组 print(a); }
&arr两端的括号必不可少 f(int &arr[10])//将arr声明成了引用的数组 f(int (&arr)[10])//arr是具有10个整数的整数数组的引用 指针同理
-
含有可变形参的函数
#include<stdio.h> #include<iostream> #include<string.h> #include<iostream> using namespace std; void error_msg(initializer_list<string> il) { for(auto beg = il.begin();beg != il.end();++beg) { cout << *beg << " "; } cout << endl; } int main() { int a[10] = {1,2,3,4,5}; error_msg({"hello","hello","hello"}); }
initializer_list lst; 默认初始化:T类型元素的空列表 initializer_list lst{a,b,c...}; lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const lst2(lst) 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素 lst.size() 列表中的元素数量 lst.begin() 返回指向lst中首元素的指针 lst.end() 返回指向lst中首元素的指针 lst2 = lst 原始列表和副本共享元素
三、返回类型和return语句
-
值是如何被返回的
string make_plural(size_t ctr,const string &word,const string &ending) { return (ctr > 1)?word + ending : word; }
该函数的返回类型时string,意味着返回值将被拷贝到调用点。因此,该函数将返回word的副本或者一个未命名的临时string对象,该对象的内容是word和ending的和。
const string &shorterString(const string &s1,const string &s2) { return s1.size() <= s2.size() ? s1 : s2; }
其中形参和返回类型都是const string的引用,不管是调用函数还有返回结果都不会真正拷贝string对象
-
引用返回左值
#include<stdio.h> #include<iostream> #include<string.h> #include<iostream> using namespace std; 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'; cout << s << endl; return 0; //shorterString("hi","bye") = "X"; //错误:返回值是个常量 }
-
主函数main的返回值
如果没有return 0;编译器将隐式地插入一条返回0的return语句
-
不要返回局部对象的引用或指针
#include<stdio.h> #include<iostream> #include<string.h> #include<iostream> #include<cstdlib> using namespace std; const string &manip() { return "hello"; } int main() { string a = manip(); cout << a << endl;//无法运行 }
当函数结束时临时对象占用的空间也就随之释放掉了,所以两条return语句都指向了不再可用的内存空间。
-
使用尾置返回类型
//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 }
decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是个数组,要想表示arrPtr返回指针还必须在函数声明时加一个*符号。
四、函数重载
-
判断两个形参类型是否相异
Record lookup(const Account &acct); Record lookup(const Account&); //省略了形参的名字 typedef Phone Telno; Record lookup(const Phone&); Record lookup(cosnt Telno&);//Telno和Phone的类型相同
main函数不能重载
-
重载和const形参
顶层const不影响传入函数的对象。
Record lookup(Phone); Record lookup(const Phone); //重复声明 Record lookup(Phone*) Record lookup(Phone* const);//重复声明
非常量可以转换成const
如果形参时是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:
Record lookup(Account&); Record lookup(const Account&); //新函数,作用与常量引用 Record lookup(Account*) Record lookup(const Account*);//新函数,作用域指向常量的指针
但是当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。
-
const_cast和重载
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 = ' '); string window; window = screen(, , '?');//错误:只能省略尾部的实参 window = screen('?') //调用screen('?',80,' ')
-
默认实参声明
string screen(sz,sz,char = ' '); //我们不能修改一个已经存在的默认值 string screen(sz,sz,char = '*'); //错误:重复声明 string screen(sz = 24,sz = 80,char); //正确
-
默认实参初始值
sz wd = 80; char def = ' '; sz ht(); string screen(sz = ht(),sz = wd,char = def); string window = screen();//调用screen(ht(),80,' ') //调用时: void f2() { def = "*";//改变默认实参的值 sz wd = 100;//隐藏了 window = screen();//调用screen(ht(),80,'*'); }
-
内联函数
内联函数可避免函数调用的开销
内联机制用于优化规模较小、流程直接、频繁调用的函数。
-
constexpr函数
能用于常量表达式的函数,函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句。
#include <stdio.h> #include <iostream> #include <string.h> #include <vector> #include <algorithm> using namespace std; constexpr int add() { return 5; } constexpr size_t scale(size_t cnt) { return add() * cnt; } int main() { cout << scale(2) << endl; }
把内联函数和constexpr函数放在头文件内
-
调试帮助
assert是一种预处理宏,定义在cassert头文件中,所以使用时无需std::,也不需要提供using声明。
assert(expr);
NDEBUG预处理变量
assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NODEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。
五、函数匹配
候选函数:函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数成为候选函数。候选函数具备两个特征:一是与被调用的函数同名,二是其声明在调用点可见。
可行函数:考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,而是每个实参的类型与对应的 形参类型相同,或者能转换成形参的类型。
含有多个形参的函数匹配
f(int,int);
f(double,double);
f(42,3.5);会出现二义性。
六、函数指针
-
使用函数指针
#include <stdio.h> #include <iostream> #include <string.h> #include <vector> #include <algorithm> #include <cassert> using namespace std; bool lengthCompare(const string &s1, const string &s2) { return s1.size() > s2.size(); } bool (*pf)(const string &, const string &); int main() { pf = lengthCompare; cout << pf("hello", "goodbye") << endl; cout << (*pf)("hello", "goodbye") << endl; cout << lengthCompare("hello", "goodbye") << endl; pf = 0; // pf不指向任何函数 string :: size_type sumLength(const string&,const string &); //返回类型不匹配 pf = sumLength; }
-
重载函数的指针
void ff(int*); void ff(unsigned int); void (*pf)(unsigned int) = ff;//pf1指向ff(unsigned) void (*pf2)(int) = ff;//没有和一个ff与该形参列表匹配 double (*pf3)(int *) = ff;//ff和pf3的返回类型不匹配
-
函数指针形参
//第三个形参是函数类型,它会自动地转换成指向函数的指针 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 &)); //自动将函数lengthCompare转换成指向该函数的指针 useBigger(s1,s2,lengthCompare);
//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) *Func2;
void useBigger(const string&, const string& ,Func);//自动转换成指针 void useBigger(const string&,const string&,FuncP2);
-
返回指向函数的指针
using F = int(int*,int);//函数类型不是指针 using PF = int(*)(int*,int);//指针类型
PF f1(int); F f1(int);//F是函数类型,f1不能返回一个函数 F *f1(int);
int (*f1(int))(int *,int); auto f1(int)-> int (*)(int*,int);
-
将auto和decltype用于函数指针类型
decltype(sumLength) *getFcn(const string &);//切记decltype返回函数类型了不返回指针类型