【C++】《C++ Primer 》第六章

第六章 函数

一、函数基础

  • 函数定义:包括返回类型、函数名字和0个或者多个形参(parameter)组成的列表和函数体。
  • 调用运算符:调用运算符的形式是一对圆括号 (),作用于一个表达式,该表达式是函数或者指向函数的指针。
  • 圆括号内是用逗号隔开的实参(argument)列表
  • 函数调用过程
    • 1.主调函数(calling function)的执行被中断。
    • 2.被调函数(called function)开始执行。
  • 形参和实参:形参和实参的个数和类型必须匹配上。
  • 返回类型: void表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针

1. 局部对象

  • 两个非常重要的概念:
    • 名字:名字的作用于是程序文本的一部分,名字在其中可见。
    • 生命周期:对象的生命周期是程序执行过程中该对象存在的一段时间。
  • 局部变量(local variable):形参和函数体内部定义的变量统称为局部变量。它对函数而言是局部的,对函数外部而言是隐藏的。它的生命周期依赖于定义的方式。
  • 在所有函数体之外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,直到程序结束才会被销毁
  • 自动对象:只存在于执行期间的对象。当块的执行结束后,它的值就变成未定义的了。
  • 局部静态对象: static类型的局部变量,在程序的执行路径第一次经过对象定义语句时被创建直到程序终止才被销毁。它的生命周期贯穿函数调用前后。

2. 函数声明

  • 函数声明:函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。函数声明主要用于描述函数的接口,也称函数原型
  • 在头文件中进行声明:建议变量和函数在头文件中声明,在源文件中定义。

3. 分离式编译

  • 分离式编译: CC a.cc b.cc直接编译生成可执行文件;CC -c a.cc b.cc编译生成对象代码a.o b.o; CC a.o b.o编译生成可执行文件。

二、参数传递

  • 形参初始化的机理和变量初始化一样。
  • 引用传递(passed by reference):又称传引用调用(called by reference),指形参是引用类型,引用形参是它对应的实参的别名。
  • 值传递(passed by value):又称传值调用(called by value),指实参的值是通过拷贝传递给形参。

1. 传值参数

  • 当初始化一个非引用类型的变量时,初始值被拷贝给变量。函数对形参做的所有操作都不会影响实参。
  • 指针形参:当执行指针拷贝操作时,拷贝的是指针的值。拷贝过后,虽然两个指针是不同的指针,但是由于指针的特性,通过指针可以修改它所指向对象的值。
  • 在C中:常常使用指针类型的形参访问函数外部的对象。在C++中建议使用引用类型的形参代替指针。

2. 传引用参数

  • 通过使用引用形参,允许函数改变一个或多个实参的值。
  • 引用形参直接关联到绑定的对象,而非对象的副本。所以经常用引用形参来避免不必要的复制。
  • 使用引用形参可以用于返回额外的信息。比如得到多个返回值。
  • 如果无需改变引用形参的值,最好将其声明为常量引用

3. const形参和实参

  • 形参的顶层const被忽略。void func(const int i); 调用时既可以传入const int也可以传入int。
    由于这个特性,所以void func(const int i); 和 void func(int i); 不是重载
  • 可以使用非常量初始化一个底层const对象,但是反过来不行。
  • 尽量使用常量引用。

4. 数组形参

  • 两个特殊性质:
    • 不允许拷贝数组。所以无法以值传递的方式使用数组参数。
    • 使用数组时通常会将其转换成指针。所以在为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
  • 以数组作为新参的函数,也要注意数组的实际长度,不能越界。
  • 管理数组实参的第一种方法:要求数组本身包含一个结束标记。
void print(const char *cp) {
    if(cp){
        while(*cp) cout << *cp++;
    }
}

  • 管理数组实参的第二种方法:使用标注库规范,传递指向数组首元素和尾元素的指针。推荐使用
void print(const int *beg, const int *en) {    
    while(beg != en) cout << *beg++;
}

int j[2] = {0, 1, 2423, 4};
print(begin(j), end(j));
  • 管理数组实参的第三种方法:专门定义一个表示数组大小的形参。
// const in ia[] <=> const in *ia
void print(const in ia[], size_t size) {
    for (size_t i = 0; i < size; ++i) {
        cout << ia[i];
    }
}
  • 传递多维数组
void print(int (*matrix)[10], int rowSize) {
    ;
}

// 或者
void print(int matrix[] [10], int rowSize) {
    ;
}

5. main处理命令行选项

  • int main(int argc, char **argv) {...}; 第二个参数argv是一个数组,它的元素是指向C风格字符串的指针。第一个参数argc表示数组中字符串的数量。
  • 当使用argv中的实参时,一定要记得可选的参数从argv[1]开始,argv[0]保持程序的名字,而非用户的输入。

6. 含有可变形参的函数

  • C++11提供了两种主要的方法来解决处理不同数量实参的函数
    • 如果所有的实参类型相同,可以传递一个initializer_list的标准库类型。
    • 如果实参的类型不同,可以编写一种特殊的函数,也就是所谓的可变参数模板。
  • initializer_list形参:initializer_list是一种标准库类型,用于表示某种特定的值的数组,它定义在同名的头文件中。它的操作如下表:
操作 含义
initializer_list lst; 默认初始化;T类型元素的空列表。
initializer_list lst{a,b,c}; lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const。
lst2(lst) 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。
lst2 = lst 原始列表和副本共享元素
lst.size() 列表中的元素数量
lst.begin() 返回指向lst中首元素的指针
lst.end() 返回指向lst中微元素下一位置的指针
void err_msg(ErrCode e, initializer_list<string> il){
    cout << e.msg() << endl;
    for(cosnt auto$elem: il)
        cout << elem << " ";
    cout << endl;
}

if (expected != actual)
    err_msg(ErrCode(42), {"funcX", expected, aactual};
else
    err_msg(ErrCode(0), {"funcX", "Okay"};

三、返回类型和return语句

1. 无返回值的函数

  • 没有返回值的 return语句只能用在返回类型是 void的函数中,返回 void的函数不要求非得有 return语句。它会默认的加上return 0;

2. 有返回值的函数

  • return语句的返回值的类型必须和函数的返回类型相同,或者能够隐式地转换成函数的返回类型。
  • 值的返回:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
  • 不要返回局部对象的引用或者指针,因为函数结束,局部对象将被释放。
  • 引用返回左值:函数的返回类型决定调用是否是左值。调用一个返回引用的函数得到左值;其他返回类型的函数得到右值。
  • C++11:列表初始化返回值:函数可以返回花括号包围的值的列表。
  • 主函数main的返回值:如果结尾没有return,编译器将隐式地插入一条返回0的return语句。返回0代表执行成功。

3. 返回数组指针

  • type(*function(parameter_list))[dimension]
  • 使用类型别名:typedef int arrT[10]; 或者 using arrT = int[10;],然后 arrT* func() {...};
  • 使用decltype:decltype(odd) *arrPtr(int i) {...};
  • C++11:尾置返回类型:在形参列表后面以一个->开始:auto func(int i) -> int(*)[10]

四、函数重载

  • 重载:如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载(overload)函数。
  • main函数不能重载
  • 重载和const形参
    • 一个有顶层const的形参和没有它的函数无法区分。 Record lookup(Phone* const)和 Record lookup(Phone*)无法区分。
    • 相反,是否有某个底层const形参可以区分(形参是某种类型的指针或者引用)。 Record lookup(Account)和 Record lookup(const Account)可以区分。
  • const_cast和重载
// 比较两个string对象的长度,返回较短的那个引用
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);
}
  • 重载和作用域:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。

五、特殊用途语言特性

1. 默认实参

  • string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
  • 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。

2. 内联(inline)函数

  • 普通函数的一个潜在缺点:调用函数一般比求等价表达式的值要慢一些。因为调用前要先保存寄存器,并在返回时恢复;还可能需要拷贝实参;程序转向一个新的位置继续执行等;
  • inline函数可以避免函数调用的开销,可以让编译器在编译时内联地展开该函数。但是注意,内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求。
inline const string & shorterString(const string &s1, const string &s2) {
    return s1.size() <= s2.size() ? s1 : s2;    
}

// 使用
cout << shorterString(S1, S2) << endl;
// 编译过程中直接展开为类似下面的形式
cout << (s1.size() <= s2.size() ? s1 : s2) << endl;
  • inline函数应该在头文件中定义。且只适合短小的函数。

3. constexpr函数

  • constexpr函数:是指能用于常量表达式的函数,但是它不一定返回常量表达式。定义时的几个约定1.函数的返回类型及所有形参的类型都得是字面值类型。2.函数体中必须有且只有一条return语句
  • constexpr int new_sz() {return 42;}
  • constexpr函数应该在头文件中定义。

4. 调试帮助

  • assert预处理宏(preprocessor macro):assert(expr);
  • NEDBUG预处理变量:CC -D NDEBUG main.c可以定义这个变量NDEBUG。
void print(){
    #ifndef NDEBUG
        cerr << __func__ << "..." << endl;
    #endif
}

六、函数匹配

  • 重载函数匹配的三个步骤:1.候选函数;2.可行函数;3.寻找最佳匹配。
  • 候选函数:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。
  • 可行函数:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数(viable function)。
  • 寻找最佳匹配:基本思想:实参类型和形参类型越接近,它们匹配地越好。

七、函数指针

  • 函数指针指向的是函数而非对象,它指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
// 比较两个string对象的长度
bool lengthCompare(const string& a, const string& b);

// 该函数的类型
bool (const string&, const string&);

// 变成函数指针,只需要将指针替换函数名即可
bool (*pr) (const string&, const string&);
  • 使用函数指针:当把函数名作为一个值使用时,该函数自动转换成指针。
pf = lengthCompare;     // pf指向名为lengthCompare的函数
pf = &lengthCompare;    // 等价的赋值语句:取地址符是可选的
  • 函数指针形参
    • 形参中使用函数定义或者函数指针定义效果一样。
    • 使用类型别名或者decltype。
  • 返回指向函数的指针:1.类型别名;2.尾置返回类型。
原文地址:https://www.cnblogs.com/parzulpan/p/13395661.html