第六章 函数

一、函数基础

  • 函数定义:包括返回类型、函数名字和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.尾置返回类型。

【C++】《C++ Primer 》第六章的更多相关文章

  1. C++Primer 第六章

    //1.我们通过调用运算符来执行函数.调用运算符的形式是一对圆括号,他作用于一个表达式,该表达式是一个函数或者指向函数的指针.圆括号之内是用逗号分隔的实参列表,用于初始化函数形参.调用表达式的类型就是 ...

  2. 【C++ Primer 第六章】 1. 定义模板

    类模板 题目描述:实现StrBlob的模板版本. /* Blob.h */ #include<iostream> #include<vector> #include<in ...

  3. 《C++Primer》第五版习题答案--第六章【学习笔记】

    <C++Primer>第五版习题答案--第六章[学习笔记] ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2020/1/16 第六章:函数 ...

  4. C Primer Plus 学习笔记 -- 前六章

    记录自己学习C Primer Plus的学习笔记 第一章 C语言高效在于C语言通常是汇编语言才具有的微调控能力设计的一系列内部指令 C不是面向对象编程 编译器把源代码转化成中间代码,链接器把中间代码和 ...

  5. C primer plus 读书笔记第六章和第七章

    这两章的标题是C控制语句:循环以及C控制语句:分支和跳转.之所以一起讲,是因为这两章内容都是讲控制语句. 第六章的第一段示例代码 /* summing.c --对用户输入的整数求和 */ #inclu ...

  6. 精读《C++ primer》学习笔记(第四至六章)

    第四章: 重要知识点: 4.1 基础 函数调用是一种特殊的运算符,它对运算对象的数量没有限制. 重载运算符时可以定义运算对象的类型,返回值类型,但运算对象的个数,运算符的优先级,结合律无法改变. 当一 ...

  7. C++ Primer Plus学习:第六章

    C++入门第六章:分支语句和逻辑运算符 if语句 语法: if (test-condition) statement if else语句 if (test-condition) statement1 ...

  8. 【C++】《C++ Primer 》第十六章

    第十六章 模板与泛型编程 面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况. OOP能处理类型在程序允许之前都未知的情况. 泛型编程在编译时就可以获知类型. 一.定义模板 模板:模板是泛型编 ...

  9. 精通Web Analytics 2.0 (8) 第六章:使用定性数据解答”为什么“的谜团

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第六章:使用定性数据解答"为什么"的谜团 当我走进一家超市,我不希望员工会认出我或重新为我布置商店. 然而, ...

随机推荐

  1. Oh my God, Swagger API文档竟然可以这样写?

    最好的总会在不经意间出现. 作为后端程序员,免不了与前端同事对接API, 一个书写良好的API设计文档可有效提高与前端对接的效率. 为避免联调时来回撕逼,今天我们聊一聊正确使用Swaager的姿势. ...

  2. CSS文本溢出效果&滚动条样式设置

    一.文本溢出 1.overflow: hidden;  超出文本会被剪裁隐藏不可见 scroll;超出文本会被剪裁, 显示滚动条 auto; 如果文本超出会显示滚动条,没超出不会显示, overflo ...

  3. Linux下网卡配置多个IP

    ip addr add 192.168.12.4/24 dev eno16777728但是每次重启会失效 如果希望每次重启会重新绑定IP,可以将:ip addr add 192.168.12.X/24 ...

  4. 面试 23-面试技巧 by smyhvae

    23-面试技巧 by smyhvae #写简历的注意事项 最多可以写"深入了解",但不要写"精通". #遇到不知道的问题,该怎么回答 这块儿我没了解过,准备回去 ...

  5. Winform 去掉 最大化 最小化 关闭按钮(不是关闭按钮变灰)终极解决办法

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  6. 任务调度框架Quartz快速入门!

    目录 Quartz是什么 Quartz中的重要API及概念 超重要API 重要概念 Quartz设计理念:为什么设计Job和Trigger? 最简单的Quartz使用案例 Job实例和JobDetai ...

  7. 如何写出安全的、基本功能完善的Bash脚本

    每个人或多或少总会碰到要使用并且自己完成编写一个最基础的Bash脚本的情况.真实情况是,没有人会说"哇哦,我喜欢写这些脚本".所以这也是为什么很少有人在写的时候专注在这些脚本上. ...

  8. AndroidSDK安装选项说明

    前言:本文的目的在于了解AndroidSDK相关安装选项,正确根据自身需要选择性安装,避免安装过多无用的东西导致硬盘爆满. 1. AndroidSDK安装选项说明,如上图. 2. 实际游戏打包使用到A ...

  9. 配置简单的拦截器java中

    springMVC.xml文件中==== <!-- 拦截器 --> <mvc:interceptors> <mvc:interceptor> <mvc:map ...

  10. JAVA十大经典排序算法最强总结(含JAVA代码实现)

    0.排序算法说明 0.1 排序的定义 对一序列对象根据某个关键字进行排序. 0.2 术语说明 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面: 不稳定:如果a原本在b的前面,而a=b,排 ...