======函数====== C++ Primer 笔记 第六章\\ ---- ====函数基础==== 函数由四部分组成:函数的返回类型,函数名,函数接收的参数列表 (//parameter list//),还有函数体。我们通过**调用运算符**(一对括号)来对函数进行调用,调用返回的类型即是函数的返回类型。\\ 函数调用分为三步: - 初始化。函数从调用函数的位置获取对应数量的 argument,然后用 argument 对 parameter进行初始化 (**隐式转换**)。 - 执行函数。 - 当遭遇 return 关键字时,函数调用结束,函数返回值(如果有的化),并将程序控制权交回调用函数的主体。 ===Parameters & Arguments=== Argument 用于对函数 Parameter 的初始化。函数的 parameter 和 arguments 有如下的匹配要求: * 初始化类型的要求:**相同类型** & 可以通过类型转换得到相同的类型 * 初始化数量的要求:用于初始化的 arugments 与函数 parameter **数量相同** 函数按顺序对应初始化的 argument 和 paramater,但并不保证先初始化哪个 argument。 ==Parameter list == Parameter List 有如下的要求: * 可以为空,但不能省略(必须有括号)。可以使用 ''void'' 关键字作为填充: void f1(){ /* ... */ }; void f2(void){ /* ... */ } // explicit void parameter list * 每个 parameter 必须**单独声明**: int f3(int v1, v2) { /* ... */ } // error int f4(int v1, int v2) { /* ... */ } // ok * parameter 不能重名,必须有名字。 ==函数返回类型== * 不返回任何类型的时候,函数的返回类型是 ''void''。 * 返回值**不能是函数或者数组**,但**可以是**指向函数或数组的**指针**。 ===局部变量=== 两个前置概念: * 变量由两部分组成:名字(//Name//)和**作用域**(//Scope//)。**名字拥有作用域**。 * Object 有生命周期(//Lifetime//) 两个后续概念: * 名字的作用域范围(//The scope of a name//):从名字可见开始到对应 scope 结束。 * 对象的生命周期 (//The lifetime of an object//):对象存在的的时间(在程序执行的过程中) 由于函数的创建会定义一个新的 scope,因此我们将所有**定义于函数内部的变量**统称为**局部变量**(本地变量,//Local variables//)。局部变量的生命周期取决于函数的 scope。 ==自动对象== 自动对象(//Automatic Object//)对应局部变量,指在 block 内创建的对象。其生命周期到 block 结束。当 block 结束后,任何创建于 block 中的对象都将变为未定义。\\ \\ 自动对象的初始化由 arguments 或者 block 内的 Initializer 负责初始化,比如 parameter 就是一种自动对象,由 argument 初始化。 再次提醒: build-in type 在函数中的默认初始化是 Undefined Beahvior. ==局部静态对象== **//什么是局部静态对象(Local static obejct)?//** * 创建于函数中的对象,初始化于**函数第一次执行**的时候。 * 生命周期从创建开始,直到函数不再执行为止。 **//局部静态对象应用的场景是?// \\ \\ ** 某些函数可能会被反复调用,但这些函数可能需要一个全局的、存在于所有函数调用期间的对象(通常用于记录、累积等等),比如下面的计数器,总量就是通过局部静态对象保存的: size_t count_calls() { static size_t ctr = 0; // value will persist across calls return ++ctr; } int main() { for (size_t i = 0; i != 10; ++i) cout << count_calls() << endl; return 0; } **//局部静态变量的默认初始化?// \\ \\ ** build-in type 的默认初始化为 0. ===函数的声明=== 函数也是对象,所以函数在使用之前也必须声明。函数的声明: * 不需要写 parameter (但是强烈推荐写上,方便理解) * 不需要写函数的 Body 部分。 * 可以存在多次函数声明 写法: type function_name (/* parameter list here is not necessary but recommonded */); ==Function Declarations Go in Header Files== //**怎么样使用头文件来帮助函数的声明?**// \\ \\
\\ \\ //**为什么要这么做?**//\\ \\ 统一接口:只需要在特定的 Header 中声明函数,需要使用的时候包含该 Header 即可。修改同理,只需要修改函数的实现文件和 Header 中的声明。 实现函数的原文件中也应该包含声明了这些函数的 header。编译器可以通过这种方式来验证函数的声明是否一致。 ===分离式编译=== 对应上述的要求,C++ 使用分离式编译(//Separate Compliation//)来处理。在分离式编译中,每个文件可以进行单独编译,最后通过链接组合到一起。 ==编译链接多个源文件== 假设 : * ''fact()'' 函数定义于 ''fact.cc'' 文件中 * ''fact()'' 函数声明于 ''Chapter6.h'' 中 * ''fact()'' 函数需要在 ''factMain.cc'' 文件中使用 $ g++ factMain.cc fact.cc #generates factMain.exe or a.out $ g++ factMain.cc fact.cc -o main #generates main.exe or main ==单独编译并链接的情况== 如果修改了某个源文件,重新编译修改的文件,再手动链接即可。使用 ''-c'' 标签会产生对应的 object 文件,windows 以 ''.obj'' 为后缀,Unix 以 ''.o'' 为后缀: $ g++ -c factmain.cc $ g++ -c main.cc # "c" flag means generate object file. $ g++ factmain.o main.o -o main # link object files and generate a executable file. 以上的 ''g++'' 是编译器的名字,替换成当前使用的编译器名字即可。 ====参数传递==== Parameter 的类型决定了参数传递(//Argument passing//)的方式: * 如果是引用,则将 Parameter 视作 Argument 的 alias;函数处理的对象是 Argument * 如果是传值,则 Parameter 是 Argument 的拷贝,函数处理的对象是 Parameter 这两种方式分别称为**引用传递**(//Passed by reference//)和**值传递**(//Passed by value//)。 ===值传递=== 值传递中,Parameter 通过拷贝的形式得到 Argument 的值,两者处于不同的内存空间,修改其中一个的值不会影响另外一个。 ==指针的传递属于值传递== **指针的传递属于值传递**。传递后会得到两个不同的、但指向同一个位置的指针。因此,改变指针指向的内容会影响 Argument 指向的内容: \\ \\
\\ \\ void reset (int *ip) { *ip = 0; //changes tghe value of the object which ip points } 至于 Argument (指针本身)并不会收到影响。 在 C++ 中, 推荐使用**引用**访问函数**外部**的数据。 ===引用传递=== **//引用传递的实际效果是什么?//**\\ \\ 实际上允许函数修改 Argument **本身**,比如下例: void reset(int &i) { i = 0; // change the value to which i refers } **//为什么要使用引用传递?//** \\ \\ 引用传递的优势与作用在于: * 在函数内对 argument 进行修改只能通过引用实现。 * 相比引用传递,值传递需要拷贝,额外占用空间和时间。比较典型的例子是 string。使用引用传递会大大的提高效率: bool is_shorter(const string &s1, const string &s2) { return s1.size() < s2.size(); } * 有些类型根本无法被拷贝(某些类,IO) * 引用传递可以传递额外的信息: 通常情况下函数只能返回一个值,而使用引用传递能够有效的返回多个值:比如下例,我们希望在某个 string 中找到某个字母,并返回其第一个匹配的位置,以及出现在该 string 中的次数: 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; ++occurs; } } return ret; } 上述函数返回的是第一个匹配字母所在的下标信息;而传递进去用于计数的变量 ''occurs'' 则直接被函数修改了。这是函数使用引用传递修改函数外对象的另一种使用方法。\\ \\ Ref: [[http://courses.washington.edu/css342/zander/css332/passby.html|Function pass by value vs. pass by reference]] ===const in passing=== **//带有 const 的值传递是怎么被处理的?//**\\ \\ 首先需要明确的有两点: * 值传递是一个拷贝的过程 * 应用到**值**上的 ''const'',是 top-level const,是确保值不被改变的 const。 由于在对其他变量进行初始化 / 赋值的时候,带有 top-const 属性的变量会被自动忽略掉;**因此通过值传递初始化 low-const parameter 的时候,无论是 const 还是 non-const 的 argument,均可初始化成功。** \\ \\ **//有没有什么 top-const 带来的副作用?//**\\ \\ **函数重载的时候需要特别注意 top-level const 会被自动忽略的情况:** void fcn(const int i); void fcn(int i); //error, redefines 上例中,在初始化过程中,''const int i'' 的 top-const 属性已经被忽略掉了,因此编译器判定上述两个函数是同一个。 ==poiner / reference parameter and const== 明确两点以下两点后: * 这里讨论的 是带有 Low-const 属性的 parameter,特指 reference to const & pointer to const * 对 low-const 对象的初始化需要 initializer 也具有 low-const 属性,或是能通过类型转换得到 low-const 属性 可以得出结论: * low-const 的 parameter 可以接收 low-const / non-const 的 argument * non-const 的 parameter **不能接收** low-const 的 argument 一些例子: int i = 0; const int ci = i; //ok, top-level ignored string::size_type ctr = 0; void reset(int *ip) {....} void reset(int &i) {....} reset(&i); //ok, plain pointer reset(&ci); //error, a pointer to const can't initialize int* reset(i); //ok, call the version of int& reset(ci); //error, a reference to const can't initialize int& reset(42); //error, a literal can't initialize int& reset(ctr); //error, reference binding requires matached type. ==Use Reference to const When Possible== **//什么时候使用 reference to const 作为 parameter?//** \\ \\ 通常情况下,使用 reference to const 作为 parameter 表示了**不会进行**对 parameter 修改的意愿。这种意愿是面向一切数据的:无论传递的是 non-const 或是 low-const 的数据。\\ \\ **//使用 plain reference 取代 reference to const 会有什么后果?//** * 首先,plain reference 会使调用者误解传递的参数是需要修改的。 * 其次,plain reference 无法接收指定的数据,比如带 low-const 的参数,或者 Literal。这个问题在函数本身被其他函数调用的时候显的更为严重。 比如书中的例子,将接收 ''const string&'' 类型参数的 ''find_char'' 函数改为接收 plain reference 的参数: string::size_type find_char(string &s, char c, string::size_type &occurs); 则引发的问题有: * 自身的问题,无法接收 literial string find_char("Hello World", 'o', ctr); //error, plain reference can't be initialized by a literal * 被其他函数调用的问题,无法接收来自上游函数的 reference to const 参数: bool is_sentence(const string &s) { // if there's a single period at the end of s, then s is a sentence string::size_type ctr = 0; return find_char(s, '.', ctr) == s.size() - 1 && ctr ==1; //error, s is a const string&, find_char can't take s as an argument } **//解决的方法?//** * 改变函数中参数的 const * 如果无法改变函数,则定义原有数据的副本,将该副本交由函数处理。 ===Array Parameters=== Array 具有两个特性: - 不能直接复制 - 访问 Array 一般通过指向数组第一个元素的指针来访问 因此,函数想获得一个**数组**类型的参数,是**不能通过值传递**的方式来得到的,但可以通过**传递指针**来得到。\\ \\ 定义数组的 parameter 有三种**等价**的方式: void print(int arr[]); void print(int arr[10]); void print(int *arr); 上面所有的方法最终都将转化为 ''const int*'' 类型的指针。值得注意的是第二个例子,尽管 parameter 中注明了数组的大小,但该转换过程中只会用到**数组名**。**函数是没有办法通过单个数组参数来知道数组的大小的**。 ==传递数组长度的几种解决方法== 解决上述问题有三种方式: * 对于**数组自带结束标志符**的情况,可以通过对数组元素的判断来获取数组的长度。比如 C-string,可以通过判断当前元素是否为 ''/0'': void print(const char *cp) { if (cp) //cp is not a nullptr while(*cp) //as long as cp is not nullpter cout << *cp++; } * 第二种方法是**使用** ''begin'' 和 ''end'' **两个函数**,使用类似 STL 迭代器的原理传递信息: //define void print(const int *beg, const int *end) { // print every element starting at beg up to but not including end while (beg != end) cout << *beg++ << endl; // print the current element // and advance the pointer } //call int j[2] = {0, 1}; print(begin(j), end(j)); * 第三种方式是**显式的传入数组的长度**,定义第二个 parameter 为数组的大小: //define void print(const int ia[], size_t size){ for (size_t i = 0; i != size; ++i) { cout << ia[i] << endl; } } //call int j[2] = {1,2}; print(j, end(j) -begin(j); 上述三种方法均加上了 const 修饰,表示了不希望通过指针修改数组的意愿。跟引用一样,如果我们不想对数组进行修改,那么就加上 const。 ==使用引用传递数组== 当然数组的引用也可以作为 parameter: void print(int (&arr)[10]) { for (auto elem : arr) cout << elem << endl; } 数组的引用写法必须带上括号,比如 int (arr&)[10]。不带括号会被诠释为包含 10 个 int& 的数组。因为引用不是对象,不能作为元素,因此不带括号的写法是非法的。 与指针不同,由于**数组引用的维度** ''10'' 是定义数组引用的一部分,因此是**可以拿来使用**的。但同时,维度的确定导致了 **argument 的维度必须与其一致**。比如上述的 print 函数,要求维度为 ''10'' 的数组引用: int i = 0, j[2] = {0, 1}; int k[10] = {0,1,2,3,4,5,6,7,8,9}; print(&i); // error: argument is not an array of ten ints print(j); // error: argument is not an array of ten ints print(k); // ok: argument is an array of ten ints ==多维数组的传递== 结论:Parameter 是多维数组的情况必须**指定子数组的长度。**\\ \\ 多维数组同样是使用指针进行传递。与一维数组不同的是,多维数组的指针指向的元素也是数组,因此需要指定该数组的长度。写法如下: int (*martix)[10]; //pointer to an array with 10 int elements int (*martix)[][10]; // equivlent to the first defination ==CLI Options in main== ''main'' 函数实际上也有两个参数(其中一个是数组),用于传递一些可选的命令,比如: prog -d -o ofile data0; #prog is the name of the program 实际上的 ''main'' 函数是写成下面这样的: int main(int argc, char const *argv[]) { /* code */ return 0; } 这两个参数: * ''argc'' 决定了传递 string 的数量 * ''*argv[]'' 决定了传递的内容,本意是传递 c-string 的数组,这里相当于传递指向 string 数组的指针。 实际的工作过程中,取决于命令行是否有额外的可选命令,argv[] 的第一个元素指向 //nullptr// 或者**程序名**。按上面的例子来说: argv[0] = "prog"; // or argv[0] might point to an empty string argv[1] = "-d"; argv[2] = "-o"; argv[3] = "ofile"; argv[4] = "data0"; argv[5] = 0; ''argv'' 保证最后一个元素为 ''0'',对应的 ''argc'' 值为 ''5''。 ===个数变化的参数=== **//应用场景是什么?//**\\ \\ 需要用单一函数处理多种情况。每种情况都有数量不同的 arugment 需要传递。\\ \\ **//解决方案是什么?//** * argument 是**同类型**的,使用 ''initializer_list'' 参数。 * argument 是不同类型的,使用 ''variadic template''(本小节不 cover)。 **//initializer_list 是什么?//**\\ \\ ''initializer_list'' 是一个类似于数组的 STL 容器。与数组一样,initializer_list 可以指定类型: #include initializer_list ls; initializer_list li; 使用 initializer_list 之前需要包含头文件 ''''。除此之外,initializer_list 要求所有的元素都是 const,初始化以后不许再更改。\\ \\ **//initializer_list 支持的操作? //** \\ \\ {{ :undefined:intialize_list.jpg?600 |}} \\ \\ **//如何创建 initializer_list parameter? //**\\ \\ initializer_list 单独作为 parameter: void error_msg(initializer_list il) { for (auto beg = il.begin(); beg != il.end(); ++beg) cout << *beg << " " ; cout << endl; } 和别的 parameter 一起使用: void error_msg(ErrCode e, initializer_list il) { cout << e.msg() << ": "; for (const auto &elem : il) cout << elem << " " ; cout << endl; } **//如何调用 initializer_list ?//**\\ \\ 单独调用 initializer_list: if (expected != actual) error_msg({"functionX", expected, actual}); else error_msg({"functionX", "okay"}); 与其他元素一起调用: if (expected != actual) error_msg(ErrCode(42), {"functionX", expected, actual}); else error_msg(ErrCode(0), {"functionX", "okay"}); ==Ellipsis Parameters== >//Ellipsis parameters are in C++ to allow programs to interface to C code that uses a C library facility named varargs. Generally an ellipsis parameter should not be used for other purposes. Your C compiler documentation will describe how to use varargs. // ====返回类型 & Return 语句==== Return 语句的作用: * 终结当前函数的执行 * 跳转到函数被调用的地方 Return 语句的两种形式: return; return expression; ===没有 return 值的函数=== **//应用场景是什么?//** \\ \\ 只适用于返回类型是 ''void'' 的函数。\\ \\ **//意义是什么?//** * 如果函数中 ''return'' 语句的存在(或是 return 语句没有生效),那么意味着函数会完整的执行到最后一行。 * 如果处于函数中部,则代表**中断当前函数的执行**(类似于 break): void swap(int &v1, int &v2) { //if the values are the same, no need to swap, just return if (v1 == v2) return; //otherwise doing the swap int temp = v2; v2 = v1; v1 = temp; //no explicit return necessary } void 类型的函数也可以使用 return + expression 的写法,前提是返回值必须是另一个返回 void 的函数。返回其他类型的表达式都会导致编译错误。 ===带 return 值的函数=== **//应用场景是什么?//** \\ \\ ''return expression'' 这种形式应用于:除 void 类型函数以外的一切函数。任何带 return 类型的函数必须返回**一个值**。\\ \\ **//返回值的要求是什么?//** * **返回值**类型必须与**函数**定义的返回类型**一致** * 或者可以通过**隐式转换**变得与函数返回类型一致 **//C++ 确保正确的返回值吗?//** \\ \\ C++ 不能保证返回值结果的正确性,但可以检测**返回值类型的正确性**;换句话说,C++ 会尽可能让函数从正常的 return 语句处结束,比如下面的例子: bool str_subrange(const string &str1, const string &str2) { // same sizes: return normal equality test if (str1.size() == str2.size()) return str1 == str2; // ok: == returns bool // find the size of the smaller string; conditional operator, see § 4.7 (p. 151) auto size = (str1.size() < str2.size()) ? str1.size() : str2.size(); for (decltype(size) i = 0; i != size; ++i) { if (str1[i] != str2[i]) return; // error #1: no return value; compiler should detect this error } // error #2: control might flow off the end of the function without a return // the compiler might not detect this error } 第一种情况:无返回值的 return 语句,C++ 能确保其无法通过编译。\\ \\ 第二种情况:实际上还是在强调有返回类型的函数必须返回对应的值。对于循环来说,如果循环中有 return,且该 return 可以正确返回函数需要的返回值,那么就没有问题。但同时,我们需要考虑到**循环中不执行 return 语句**的情况;因此在循环结束后提供 return 语句作为默认返回是必要的,尤其在这种错误很可能不会被编译器查出来的情况下。\\ \\ Ref: [[https://stackoverflow.com/questions/64471157/using-a-return-statement-outside-of-a-loop-and-one-inside-of-it-in-a-function|Using a return statement outside of a loop and one inside of it in a function]] 在含有 return 语句的循环体结束后,也需要提供 return 语句。否则该行为是 run-time undefined,编译器也很可能查不出该错误。 ===值是怎么被返回的=== 函数的返回值与 parameter 的初始化采用的是一样的策略,分为**值返回**和**引用返回**: * 如果是返回普通的值,函数会利用返回值对一个临时对象(调用时产生)进行初始化,然后将该临时对象作为函数调用的结果。这个初始化的过程也是**复制的过程**。 * 如果是返回引用,那么返回值则是引用绑定的对象,没有复制操作的参与。 ==不要返回局部变量的引用或者指针== **//为什么不能?//** \\ \\ 当函数调用结束的时候,用于存储函数的内存将会被**释放**。这意味着任何指向**局部变量**的指针、引用,在**函数调用结束后**都将**无效化**: // disaster: this function returns a reference to a local object const string &manip() { string ret; // transform ret in some way if (!ret.empty()) return ret; // WRONG: returning a reference to a local object! else return "Empty"; // WRONG: "Empty" is a local temporary string } 需要注意的是 string literal 的例子。这种情况下需要分情况: * string literal 不能以引用的形式返回,但可以复制的形式返回。 * string literal 可以以 const char * 类型的指针返回。 解释一下第二点,书上有一段是这么说的: >The string literal is converted to a local temporary string object. * 首先要明确的是,"Empty" 是 literal string。C++ 中的 literal string 是 statically allocate 的,因此其生存周期并不受函数的生命周期影响。 * 其次,C++ 中的 Literal string 是 ''const char*'' 类型的。因此第二种情况实际上是以 ''const char*'' 的形式返回了一个全局对象,并没有什么问题。 * 问题出在 ''const string &manip()''。如果强制按 STL string 类型返回,那么 "Empty" 的类型会从 ''const char*'' 隐式转换为 ''std::string''。std::string 定义的变量在函数中属于局部变量,因此函数结束时,std::string "Empty" 就不存在了。因此返回该 string 的引用就是 Undefined 的行为。 Refs: * [[https://bytes.com/topic/c/answers/456870-string-literals-local-otherwise-temporary|Are string literals local or otherwise temporary?]] * [[https://stackoverflow.com/questions/2481535/how-to-return-a-string-literal-from-a-function|How to return a string literal from a function?]] ==返回 class 类型和 call operator== call operator 的两个特性允许我们在返回**类指针**的时候同时进行调用: * call operator 满足左结合律 * call operator 的优先级与成员访问运算符 ''.'' / 解应用+成员访问运算符 ''->'' 相同。 因此,如果返回的是指向 class 的指针,就可以直接访问类成员: auto sz = shorterString(s1, s2).size(); ==引用返回值是左值== 函数以 plain reference 返回的情况下会得到一个**左值**: char &get_val(string &str, string::size_type ix) { return str[ix]; // get_val assumes the given index is valid } int main() { string s("a value"); cout << s << endl; // prints a value get_val(s, 0) = 'A'; // changes s[0] to A cout << s << endl; // prints A value return 0; } ==List初始化返回值== C++11 标准中我们可以通过 list 的形式来返回多个返回值。该返回属于值返回,使用 List 内容对临时对象进行初始化后返回临时对象。如果列表为空,则进行**默认值初始化**(//Value initialized//)。\\ \\ 以之前的多个 parameter 初始化为例,使用 return List 返回的方式可以替代使用 initializer_list 的方式: vector process() { // . . . // expected and actual are strings if (expected.empty()) return {}; // return an empty vector else if (expected == actual) return {"functionX", "okay"}; // return list-initialized vector else return {"functionX", expected, actual}; } list 返回值需要相同的类型: * 如果返回值build-in 类型,list 是不允许 narrowing conversion 的 * 如果返回值是类类型,那么取决于类如何进行初始化 ==main函数的返回值== **//main 函数的返回值是什么样的?//** \\ \\ 与其他的函数不同,''main()'' 函数并不需要返回指定的返回值。如果程序运行到最后也没有 return,那么编译器会隐式的为 ''main()'' 函数加上 ''return 0''。\\ \\ **//main 函数的返回值有什么意义?//** \\ \\ 通用的是,''0''代表程序**成功执行**;其他的值多半表示执行的失败;具体情况要视机器类型而定。\\ \\ **//如何使返回值摆脱机器依赖?//** \\ \\ C++ 提供了定义于 ''cstdlib'' 头文件中的两个 preprocessor 变量来代替数字作为成功和失败的返回值: int main() { if (some_failure) return EXIT_FAILURE; // defined in cstdlib else return EXIT_SUCCESS; // defined in cstdlib } ==Recursion== 函数调用其自身的方法称之为**递归**(//Recursion//)。递归需要保证某次调用不参与调用自身,否则会无限递归。 ===返回数组指针=== 数组不能被复制,因此函数返回数组的方式是使用指针。 ==使用 type alias 简化定义== 两种简化数组定义的方式: typedef int arrT[10]; using arrT = int[10]; arrT* func(int i); // arrT* means return a pointer to which points 10 int elements ==声明返回值为数组指针的函数== 通常情况下将函数与 parameter 视作一个变量名整体。之后的声明规则与数组声明是一样的: type (* func(parameter list)) [dimension] 与数组指针 / 引用声明一致,函数外的 parenthesis 是必须的。 ==Trailing return type== C++ 11 中提供了一种新的 trailing return type 来简化函数的声明。结构如下: auto function(parameter list) -> array type; 一个例子,函数接收一个 int,返回一个指向包含有10个 int 元素数组的指针: auto func(int i) -> int(*) [10]; ==使用 decltype == 另外一种声明返回数组指针函数的方式是使用 decltype。\\ \\ **//应用条件?//**\\ \\ 在**已知存在数组对象**的情况下,才能使用 decytype 去推断。\\ 除此之外,decltype 返回的是数组类型本身,因此必须在前面**加上** ''*'' 强调函数的返回值是指向数组的指针。 int odd[] = {1,3,5,7,9,11,13,15,17,19}; //arr is a function that returns a pointer to which points 10 int elements //the return value should be an address(&odd) decltype(odd) *arr(int i) { return &odd; } ====重载函数==== **//什么是重载的函数(Overloaded Function)?//**\\ \\ 重载函数指**函数名相同**,但 parameter list 不同的函数,且这些函数都应处于同一个 scope。\\ \\ **//重载函数的主要意义?//**\\ \\ 为不同类型 & 数量的 Parameter 提供**类似的操作**。 main 函数无法被重载。 ===定义重载函数=== 重载函数的要求 parameter 的**类型不同**或者**数量不同**: //different types Record lookup(const Account&); //Acocount is a long type Record lookup(const Name&); // Name is a std::string type //different parameter quantities void print(const char *cp); void print(const int *beg, const int *end); 函数**只有返回类型的不同不算做重载**: Record lookup(const Account&); bool lookup(const Account&); // error: only the return type is different Parameter **只有名字不同不算**重载: //below are the same function Record lookup(const Account &acct); //parameter is named acct Record lookup(const Account&); // parameter names are ignored **Type alias 不算**重载: typedef Phone Telno; Record lookup(const Phone&); Record lookup(const Telno&); // Telno and Phone are the same type ==重载和 const== **top-const** 的 parameter 与 **non-const** 的 parameter **是**同一种参数:\\ \\ 由于值传递初始化会忽略 top-const,编译器无法区分 parameter 是否是 top-const。因此,带有 top-const 和 non-const parameter 的函数是同一种函数: record lookup(phone); record lookup(const phone); //redeclares record lookup(phone) record lookup(phone*); record lookup(phone* const); //record lookup(phone*) **low-const** 的 parameter 与 **non-const** 的 parameter **不是**同一种参数:\\ \\ 编译器可以分辨 refer / point to const 与 refer / point to non-const 的区别,因此可以重载: record lookup(Account&); record lookup(const Account&); //new function that takes a reference to const 如果 argument 是 non-const, 编译器会倾向于**选择 non-const 版本的函数重载版本**。 ==const_cast和重载== 假设如下场景: * 我有一个可以接收 low-const parameter,并返回 low-const parameter 的函数。 * 我想基于这个函数实现一个 接收 non-const parameter 并返回 non-const parameter 的函数。 尽管带有 low-const parameter 的函数可以接收 non-const parameter,但经过隐性转换,最终参与函数运算的还是 low-const parameter。为了得到 non-const 的返回值,需要使用 const_cast 去掉 low-const 属性: //original function: const string& func(const string& s1, const string& s2) { statments.... return const string& s3; } //The author's implementation string& func(string& s1, string& s2) { //this function return a reference to const auto &r = func(const_cast (s1), const_cast (s2)); //convert reference to const to plain reference return const_cast (r); } 注:个人认为这个实现方法的意义真的不是很大。如果想要传递 non-const 得到 non-const,用普通的 string& 实现一遍就可以。如果要确保不可修改,用 low-const 的版本就可以: //low-const in, low-const out const string &shorterString(const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; } //non-const in, non-const out string &shorterString( string &s1, string &s2) { return s1.size() <= s2.size() ? s1 : s2; } 只要正确区分 low-const 和 non-const 的 argument,编译器是一定能完成重载的: string s1 {"Hello"}; string s2 {"world"}; const string& sr1 = s1; const string& sr2 = s2; cout << (shorterString(s1, s2) = "!") << endl; //complie passed. shorterString(s1, s2) returns a plain ref cout << (shorterString(sr1, sr2) = "!") << endl; //complie failed. shorterString(sr1, sr2) returns a ref to const 书上的例子是在谈 low-level const 的参数会被视做重载的函数。对于普通版本的 shorterString(),如果不对其调用同名函数 shorterString 进行 const_cast,那么该调用将调用普通版本的 sharterString,也就是其自身,从而造成无限递归。 看完第七章后,该方法的实际意义是: * 将功能函数独立出来复用,避免代码重复 * 使用专门的接口处理常量参数与变量参数,任何修改不会涉及功能函数 ===调用重载函数=== 编译器在调用重载函数的时候回遵循**函数匹配**(//Function Matching or Overload Resolution//)的策略。在匹配的过程中通常会有三种情况出现: * Best match:编译器找到了最合适的重载函数。 * No match:编译器找不到匹配结果,编译器报错。 * ambiguous call: 编译器找到了不止一个合适的重载函数。 匹配的机制请参考[[cs:programming:cpp:cpp_primer:6_function#function matching|函数匹配]]。 ===重载和scope=== **//重载与 scope 有什么关系?//**\\ \\ 重载不影响 scope,因为重载必须在同一个 scope 才会生效。\\ \\ **//受 scope 影响的是什么?//**\\ \\ Name。需要强调的是,这里的 Name **不分类型和种类**;一个重要的例子就是,变量的 Name 屏蔽外部的同名函数。\\ \\ **//scope 是如何影响 Name 的?//**\\ \\ scope 内声明的 name,一定会屏蔽外部的 name. 即便是不一样的类型。比如下面的例子,函数内部的 bool 变量 read,屏蔽了外部的 read() 函数: string read(); void foo() { bool read; string s = read(); // error, read is a bool type } **//为什么会有这样的影响?//**\\ \\ 这与编译器寻找 name 的声明方式有关: - 当调用函数的时候,编译器会首先寻找本地的(对应 scope 内)的声明。 - 如果找到了对应的名字,编译器就会**忽略其他 scope 中所有同名的声明**。 所以导致的结果就是,只要在本地找到了对应的名字声明,即便这个函数不能用,编译器也不会去寻找 scope 外的替代品了: void print(const string &); //print() at outter scope void print(double); //print() at outter scope, overloaded void foo { void print(int); //print() at inner scope, hides all print() outside the scope print("hello"); //error. inner print() only accepts int } C++ 的 name lookup 会优先于 type checking。 ====特殊用法==== ===Default argument=== **//在哪使用 default argument?//** \\ \\ default argument 可以应用于一些常用的、很少需要修改的变量,比如固定大小的画纸的长宽等等。\\ \\ **//怎么写?//** using sz = string::size_type; stromg screen(sz ht = 24, sz wd = 80, char bacgrnd = ' '); ==调用带 default argument 的函数== 调用带有 default argument 的函数时,default argument **从右到左填充**未填写的 argument: \\ \\ {{ :cs:programming:cpp:cpp_primer:def_argu_filling.svg?600 |}} \\ 如果需要覆盖 default argument,那么需要从左到右依次填入 argument,否则会报错: string screen(int h = 24, int w = 80, char background = '*'); string window; window = screen(); //ok, all default argument; window = screen(66, 256); //argument list is { 66, 256, '*'} window = screen(, , '?'); //error, can omit only trailing argument 所以在一般设计中为了遵循这个规则,一般**把常改的 argument 放到最左边,最不常改的放到最右边**。 ==default argument 的声明== **//default argument 的声明的限制是?//**\\ \\ 函数中,带 default argument 的 parameter 只能在同一个 scope 中**声明一次**。 string screen(sz, sz, char = ' '); string screen(sz, sz, char = '*'); // error: redeclaration string screen(sz = 24, sz = 80, char); // ok: adds default **//为什么存在只能声明一次的限制?//**\\ \\ default argument 实际上对对应的 parameter 进行了初始化。任何进行了初始化的声明,都是定义。同一个函数只能定义一次。因此,default argument 只能填充一次对应的 argument。 ==Default Argument Initializers== **//Default Argument 的要求是?//** * Local variable 不能作为 default argument * default argument 类型需要与 parameter 一致 **//为什么不能使用局部变量作为 default argument?//**\\ \\ 被 default argument 使用的名字在使用其的函数的 scope 中可见;而其值在函数**被第一次调用的**时候确定。因此,我们无法使用该函数内部的同名局部变量对 default argument 进行修改: // the declarations of wd, def, and ht must appear outside a function sz wd = 80; char def = ' '; sz ht(); string screen(sz = ht(), sz = wd, char = def); string window = screen(); // calls screen(ht(), 80, ' '); void f2() { def = '*'; // changes the value of a default argument sz wd = 100; // hides the outer definition of wd but does not change the default window = screen(); // calls screen(ht(), 80, '*') } ===Inline / constexpr 函数=== ==Inline(内联)函数== **//为什么要使用 inline 函数?//**\\ \\ 普通函数在调用的过程中会有额外的开销,包括但不限于: * 寄存器的保存与恢复 * argument 的复制 * 程序转向新的位置 如果被调用的函数是**短小的**(通常就一行),但被**调用的非常频繁**的函数,使用 inline 函数可以节约这部分开销。\\ \\ **//inline 函数的具体作用?//**\\ \\ inline 函数会将函数真正核心的内容在编译期展开,省去了在 run-time 进行函数调用的开销,比如下面的函数: const string & shorterString(const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; } 普通的调用是这样的: cout << shorterString(s1, s2) << endl; 但如果被 inline 修饰,那么很可能是这样的: cout << (s1.size() < s2.size() ? s1 : s2) << endl; **//inline 函数的局限性?//**\\ \\ 简单来说,inline 函数会将函数的核心内容在编译期展开,因此会额外增加编译的时间与执行程序的大小。因此,需要尽量避免对较大的函数进行 inline。\\ \\ 不仅如此,对于编译器来说,关键字 ''inline'' 只会被认作是程序员对其提出的一个“建议”,编译器会根据自身的判断来决定是否将一个函数判定为 ''inline'' 函数。 注意:很多编译器不支持 inline 函数的递归。 Ref: [[https://zhuanlan.zhihu.com/p/50812510|被知乎大佬嘲讽后的一个月,我重新研究了一下内联函数]] ==constexpr 函数== **//constexpr 函数的要求是什么?//** * 返回值必须是 Literal type * parameter 必须是 Literal type * 函数体必须有且只有一个 return 语句 **//constexpr 函数的内容要求是什么?//**\\ \\ 需要确保 constexpr 函数内部的所有 statement 都不会在 run-time 时期有任何行为,比如空语句、type alias 等等。\\ \\ **//constexpr 函数的返回值能确保是 const expression 吗?//**\\ \\ 不能。constexpr 函数不要求返回 constexpr expression。具体来说,如果函数的 arugment 是 non-const,那么返回值也是 non-const: constexpr size_t scale(size_t cnt) {return new_sz() * cnt}; int arr[scale(2)];//ok, 2 is an const expression, so scale(2) returns the same type int i = 2; int a2[scale(i)]; //error, scale(i) returns a non-const constexpr 函数与 constexpr 变量代表的意义不一样。constexpr variable 与 const variable 类似,都隐含了常量的意思;但 constexpr 函数更强调的是在编译期完成计算的意愿(C++11 默认 Constexpr 会为函数添加 const,但14以后就不是了)。 ==inline / constexpr in header== **//inline / constexpr 函数只能定义一次吗?//**\\ \\ 不像一般的函数,inline / constexpr 函数**可以定义多次**。\\ \\ **//为什么可以?//** * C++ 支持分离式编译 * 编译器需要在编译期将 inline / constexpr 函数其展开执行,因此只有函数的声明是不够的。 可以看出,通过 ''extern'' 来共享 inline / constexpr 函数是行不通的;允许每个文件中独立存在 inline / constexpr 函数是有必要的。当然,随之而来的是另外一个要求:**所有 inline / constexpr 函数的定义必须相同。** \\ \\ **//有没有更好的办法共享 inline / constexpr 函数 ?//**\\ \\ 将 inline / constexpr 的函数定义**放到 header 中即可**。通常只将普通函数的声明放到 header 中,是因为有函数重定义的风险;而放置 inline constexpr 函数并没有这样的顾虑。 ===调试辅助=== ==The assert Preprocessor macro== //**arrert 是什么?**//\\ \\ ''assert'' 是一个预处理宏(//Preprocessor marco//)。预处理宏是一个**预处理变量**(//Preprocessor variable//),行为类似于 inline 函数。\\ \\ //**assert 的用法与功能是?**// \\ \\ assert 定义于 ''cassert'' 头文件中。由于 assert 是预处理宏,因此不需要指定命名。用法如下: assert(expression); 其主要功能是用于检查一些“不应该”发生的情况;比如某个词的长度不应该超过为它设置的上限: assert(word.size() > threshold); assert 通过执行目标表达式来得到结果。如果目标表达式返回 ''false'',则 assert 会发送相关信息并**停止整个程序的执行**;反之则不会进行任何操作。\\ \\ //**使用 assert 的限制?**// \\ \\ * 使用 assert 可能存在的最大限制是其名字本身。由于**预处理变量在程序中是唯一**的,因此我们应该**避免任何以 assert 命名**的变量、函数以及其他实体。 * 其次是功能性的限制。assert 只能作为一种**调试程序的辅助手段**,程序应该拥有自身的逻辑 / 错误校验。 ==NDEBUG 预处理变量== **//NDEBUG是什么?//**\\ \\ //NDEBUG// 同样是一个预处理变量。我们可以将 NDEBUG 视作 assert 的总开关:如字面意义,当 NDEBUG 被定义,则 assert 不工作(no debug!)。\\ \\ **//NDEBUG 如何使用?//**\\ \\ * 第一种用法是使用预处理符开启 NDEBUG: #define NDEBUG * 第二种用法是使用编译器命令来开启 NDEBUG: CC -D NDEBUG main.C # use /D with the Microsoft compiler * 第三种用法是与'' #ifndef'' / ''#endif'' 配合使用。使用的逻辑是,如果 ''NDEBUG'' **没有被开启**(定义),那么 ''#ifndef-#endif'' 代码段之间的代码就**会被检查**(我们需要 debug)。配合几个编译器定义的变量可以达到更好的效果: void print(const int ia[], size_t size) { #ifndef NDEBUG cerr <<__func__ << ":array size is " << size < **//还有哪些常用的编译器变量?//** __func__ //function name, string literal __FILE__ //file name, string literal __LINE__ //current line number, int literal __TIME__ //the time the file was complied, string literal __DATE__ //the date the file was complied, string literal 这些变量记录一些调试的环境情况,使调试信息更加完整。比如下面的例子: if (word.size() < threshold) cerr << "Error: " << _ _FILE_ _ << " : in function " << _ _func_ _ << " at line " << _ _LINE_ _ << endl << " Compiled on " << _ _DATE_ _ << " at " << _ _TIME_ _ << endl << " Word read was \"" << word << "\": Length too short" << endl; 可能的输出结果: Error: wdebug.cc : in function main at line 27 Compiled on Jul 11 2012 at 20:50:03 Word read was "foo": Length too short ====Function Matching==== ===重载函数匹配的机制=== **//1.寻找 Candidate Functions//** \\ \\ 只要**名字与被调用函数相同**的,都是 Candidate Function。\\ \\ **//2.寻找 Viable Functions//** \\ \\ Viable function 需要满足: * parameter 的**数量**与被调用函数匹配 * parameter 的**类型**与被调用函数匹配 parameter 的数量匹配过程中,带有 deafault argument 的 parameter 比较灵活,比如带 2 个 parameter 的函数,其中有一个有 default argument,那么调用者无论是带两个 parameter 还是 一个 parameter,都符合函数数量匹配的要求。 **//3.寻找 Best Match//** \\ \\ 最佳匹配基于**匹配等级来衡量**,同时也分两种情况处理:\\ \\ **只需要匹配单个 parameter 的情况:**\\ \\ 这种情况下,优先**选择匹配等级高**的作为最佳匹配对象。\\ \\ **存在匹配多个 parameter 的情况:** \\ \\ 编译器会依次检查 parameter 来决定最佳匹配。如果有函数同时满足下列条件: * 候选者自身,对(被调用函数的)**所有的**、**对应的** argument 的匹配等级,都**不低于**其他的竞争者 * 候选者自身,**至少**和**一个** argument 的匹配等级,**高于**他的竞争者 那么这个函数则为最佳匹配。如果这样的函数**存在不止一个**,那么该调用则是**二义性**的(//ambiguous call//),将导致编译错误。 \\ \\ {{ :cs:programming:cpp:cpp_primer:function_match.svg?600 |}} \\ \\ 一个二义性例子的典型: void f(); void f(int); void f(int, int); void f(double, double = 3.14); f(2.56, 42) 整个过程如下: - 第一轮检查函数名,所有函数都是 candidate function - 第二轮检查 parameter 的数量与对应类型,void f(int, int) / void f(double, double = 3.14) 版本入选为 viable function - 第三轮 best match,按位检查 parameter - 第一位 argument 是 double, 导致 void f(int, int) 版本有一个 parameter 的匹配等级低于另外的竞争者 - 第二位 argument 是 int, 导致 void f(double, double = 3.14) 版本有一个 parameter 的匹配等级低于另外的竞争者 到此为止,两个版本都有匹配等级更高的 parameter 存在,因此导致了二义性。 ===Argument Type Conversions=== ==匹配等级的划分== Argument Type Matching Rank(匹配等级) 按以下的规则进行划分:\\ \\ Rank level 1:**最高等级的匹配**,如果匹配 argument 与 parameter 得到以下结果: * 两者完全相等。 * 两者之间有隐性转换,转换是**数组/函数转化为指针**的转换。 * 两者之间有隐性转换,转换是 Top_level const 的添加或者删除。 Rank level 2:argument 和 parameter 进行了**non-const 到 low-const 的转换**。\\ Rank level 3:argument 和 parameter 之间发生了**整型的类型提升**。\\ Rank level 4:argument 和 parameter 之间发生了**算术类型的转换(除开整型类型的提升),或者指针到别的类型转换**。 \\ Rank level 5:argument 和 parameter 进行了**类的类型转换**。\\ ==匹配需要类型提升的情况== 如果调用过程中存在参数的类型提升,那么调用中比 int 的小的 argument,都将调用 parameter 类型为 int 的函数: void ff(int); void ff(short); ff('a'); // call ff(int) 如果想调用指定大小 parameter 的函数,比如调用上例的 ff(short),argument 必须是 short 类型才可以。也就是说,必须存在完全匹配的调用,才可以无视类型提升的转换: vod ff(int); void ff(short); short si = 10; ff(si); //call ff(short) ==匹配需要算术类型转换的情况== 与类型提升不同,算术类型的互换具有**同等优先级**: void manip(long); void manip(float); manip(3.14); // error: ambiguous call 上例中,double 可以转换为 float 和 long, 因此导致了二义性。 ==匹配带有 low-const 的 argument== C++ 会根据 argument 的 constness 来选取最佳匹配。low_level const parameter 的最佳匹配是 const to reference / pointer; 普通的 argument 则会优先匹配 plain argument。\\ \\ 需要注意的是,存在 plain argument 到 low_level const 的转换; parameter 为 low_level const 的函数可以使用 plain argument,但该匹配等级为 2, 不是最佳匹配。 Record lookup(Account&); // function that takes a reference to Account Record lookup(const Account&); // new function that takes a reference to const account const Account a; Account b; lookup(a); // calls lookup(const Account&) lookup(b); // calls lookup(Account&) ====Pointer to functions==== ===函数指针的定义=== 指向函数的指针(Pointer to functions)的类型由函数的**返回值类型**和 **parameter 类型**组成: bool lentgh_comp(const string &, const string &); //function bool (*pf) (const string &, const string&); //pointer points to a function that takes two string & and return a bool 括号不可丢弃,否则定义的是返回指针的函数: bool *pf (const string &, const string&); //function named pf that returns a pointer to bool ===函数指针的一般使用=== ==函数名会转化为指针== 当函数名被作为值使用的时候,函数会被转化为指向它的指针(与数组类似): pf = length_comp; //length_comp is converted to a pointer that points itsself pf = &length_comp;// equivalent operation, but not necessary 从例子中可以看到**不需要取地址**也可以获得指向函数的指针。\\ \\ ==函数的指针可以用于调用函数== 调用函数可以直接使用指针名调用,调用过程中,解应用可以省略: bool b1 = pf("hello", "world"); //using pointer to call the function, no dereference needed bool b2 = (*pf)("hello", "world"); //equivalent call, derefenence is not necessary bool b3 = length_comp("hello", "world"); //equivalent call ==函数指针之间没有类型转换== 如下例子,''sumLength'' 指向的函数类型不能通过其他类型的指针转换得到: string::size_type sumLength(const string&, const string&); bool cstringCompare(const char*, const char*); pf = 0; // ok: pf points to no function pf = sumLength; // error: return type differs pf = cstringCompare; // error: parameter types differ pf = lengthCompare; // ok: function and pointer types match exactly 可以看出,只有在**返回类型**和 **parameter 的数量和类型** 都**完全匹配**的情况下,才可以进行函数指针的赋值。当然使用 ''0'' 或者 ''nullptr'' 初始化函数指针是个例外: pf = 0; // ok pf = nullptr; // ok ==指向重载函数的指针== 由于不同类型函数指针之间不存在类型转换,因此函数指针与重载函数的对应关系是**一一对应**的: void ff(int*); void ff(unsigned int); void (*pf1) (unsgined int) = ff;// ok void (*pf2) (int) = ff; // error, parameter type not match double (*pf3) (int*) = ff; // error, return type not match ===函数指针作为 parameter 使用=== //**如何定义?**// \\ \\ 函数指针作为 parameter 有两种写的方式,区别在于带不带**解引用操作符**。但无论带不带,两种写法都**等价**: //the 3rd parameter is a function type(converted to pointer) void useBigger(const string& s1, const string& s2, bool pf(const string&, const string&)); //explicitly define a pointer to function as a parameter void useBigger(const string& s1, const string& s2, bool (*pf) (const string&, const string&)); //**如何使用?**// \\ \\ 直接使用函数名即可。函数名将会被自动转化为函数指针: // automatically converts the function lengthCompare to a pointer to function useBigger(s1, s2, lengthCompare); ==使用 typedef 简化函数指针 parameter== **使用 typedef 简化函数指针的类型时,与一般的写法有些差别:** //common typedef oldtype alias_type; //in functions return_type (pointer_to_func_name*) (parameter_list); //declaration of a pointer to function typedef alias_type(*pointer_name) (parameter list); //typedef for pointer to functions 由于存在函数名到函数指针的自动转换,因此两种类型的函数指针表示都可以使用 typedef,比如: // Func and Func2 have function type typedef bool Func(const string&, const string&); // FuncP have pointer to function type typedef bool(*FuncP)(const string&, const string&); **除此之外,typedef 也可以配合 decltype 使用:** * ''decltype'' 的形式不用变形,以正常的形式做定义 * ''decltype'' 也可以应用于之前的两种函数指针表现类型上。但有一点需要注意:由于 decltype 会直接返回函数的类型,如果需要显式的定义**函数指针类型**,那么需要加上 ''*'' 标识符: // Func and Func2 have function type typedef bool Func(const string&, const string&); typedef decltype(lengthCompare) Func2; // equivalent type // FuncP and FuncP2 have pointer to function type typedef bool(*FuncP)(const string&, const string&); // equivalent type // '*' indicates FuncP2 is a pointer typedef decltype(lengthCompare) *FuncP2; typedef 定义过的名字可以直接使用,与之前使用函数名作为 parameter 时的情况等价: //equivalent declarations 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(const string&, const string&, Func); void useBigger(const string&, const string&, Funcp2); ===函数指针的返回=== **函数必须以指针的形式返回:**\\ \\ 与函数作为参数不同,编译器在返回函数类型的时候,不会自动将函数类型视作指向它的指针类型。因此,**解引用符是无法省略的**。 ==直接声明返回类型为函数指针的函数== type (*function_name(function paramter_list))(parameter_list of a pointer to function); 以书上的例子来讲一下这个写法。首先是一个函数: int A(int*, int); 如果我们需要将这个函数 A 作为另外一个 B 的返回值,那么必须写成指针的形式: int (*PA) (int*, int); // pointer points to A, named PA PA ,也就是函数 ''A'' 的返回类型即为: int (*) (int*, int); 现在假设我们需要写一个函数 ''B'' 的声明。如果希望 ''B'' 接收一个 int 参数,并返回 ''A'' 类型的函数,那么函数 ''B'' 的声明应该写成: int (*B(int))(int*, int); {{ :cs:programming:cpp:cpp_primer:f_ret_ptof_1_2.svg?600 |}} ==使用 Type alias 简化上述声明== 针对函数 ''A'',有几种方式可以简化该种返回类型的写法:\\ \\ **使用 using:** using F = int (int*, int); //F is a function type using PF = int(*) (int*, int); // PF is a pointer type 注意下面的第二个错误,再次强调函数的返回值不会被编译器自动转换为指针,因此让 函数 f1 返回一个函数类型是非法的: PF f1(int);// ok, f1 is a pointer type F f1(int);// error, f1 is a function type F *f1(int);// ok, f1 is a pointer type **使用 auto 与 trail type 的组合:** auto f1(int) -> int(*) (int*, int);// trailing is the return type of A. https://cdecl.org/ 神器网站,可以帮助理解复杂的声明。 ===auto 与 decltype 的其他应用=== 书中可能存在没有完成的部分,auto 的用法并未在书中提出。后面的理解为猜测,根据 stack overflow 上的解答来的。 假设我们拥有两个函数,除了名字不同之外,返回类型与 parameter 的类型都相同;这种情况下,我们可以新建一个函数,根据该函数接收到的 argument 来返回指向其中某个具体函数的指针: string::size_type sum_length(const string&, const& string); string::size_type large_length(const string&, const& string); //using a string parameter to decide which pointer to function should be return decltype(sum_length) *getFcn(const string&); 这段代码的实际意义在于,我们可以通过指定的 ''string'' 变量来返回对应的函数。假设我们有 ''getFcn()'' 定义如下: decltype(sum_length) *getFcn(const string& s) { if(s.size() > 100) return sum_length; else return large_length; } 通过调用 ''getFcn()'' 就可以根据 string 长度来调用不同的函数,最终得到结果: string str1, str2; // getFcn(str1) returns the pointer to the function we wish to call // (str1, str2) is the parameter list to the chosen function // len will be the final result auto len = getFcn(str1)(str1, str2); ref:[[https://stackoverflow.com/questions/36503577/using-auto-or-decltype-for-function-pointer-types-in-c-primer|Using auto or decltype for Function Pointer Types in C++ primer]]