======函数======
C++ Primer 笔记 第六章\\
----
====函数基础====
函数由四部分组成:函数的返回类型,函数名,函数接收的参数列表 (//parameter list//),还有函数体。我们通过**调用运算符**(一对括号)来对函数进行调用,调用返回的类型即是函数的返回类型。\\
函数调用分为三步:
- 初始化。函数从调用函数的位置获取对应数量的 argument,然后用 argument 对 parameter进行初始化 (**隐式转换**)。
- 执行函数。
- 当遭遇 return 关键字时,函数调用结束,函数返回值(如果有的化),并将程序控制权交回调用函数的主体。
===Parameters & Arguments===
Argument 用于对函数 Parameter 的初始化。函数的 parameter 和 arguments 有如下的匹配要求:
* 初始化类型的要求:**相同类型** & 可以通过类型转换得到相同的类型
* 初始化数量的要求:用于初始化的 arugments 与函数 parameter **数量相同**
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 初始化。
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;
}
type function_name (/* parameter list here is not necessary but recommonded */);
==Function Declarations Go in Header Files==
$ 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 (指针本身)并不会收到影响。
void reset(int &i) {
i = 0; // change the value to which i refers
}
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===
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==
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
}
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 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''。
===个数变化的参数===
#include
initializer_list ls;
initializer_list li;
使用 initializer_list 之前需要包含头文件 ''
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;
}
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
}
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]]
// 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函数的返回值==
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。\\ \\
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;
}
====重载函数====
//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);
}
//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
string read();
void foo() {
bool read;
string s = read(); // error, read is a bool type
}
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
}
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 的声明==
string screen(sz, sz, char = ' ');
string screen(sz, sz, char = '*'); // error: redeclaration
string screen(sz = 24, sz = 80, char); // ok: adds default
// 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(内联)函数==
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;
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
assert(expression);
其主要功能是用于检查一些“不应该”发生的情况;比如某个词的长度不应该超过为它设置的上限:
assert(word.size() > threshold);
assert 通过执行目标表达式来得到结果。如果目标表达式返回 ''false'',则 assert 会发送相关信息并**停止整个程序的执行**;反之则不会进行任何操作。\\ \\
#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]]