第六章 函数

一、函数基础

  • 函数定义:包括返回类型、函数名字和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. Unity发布WebGL改变鼠标样式

    记录:前段时间遇到一个需求,就是打包出来要在某种情况下鼠标的样子要改变成想要的样式. 详细代码如下 public Texture2D[] hand;//小指图标 /// <summary> ...

  2. stringbuilder和stringbuffer速度比较

    同样的代码,只改了类型,分别为stringbuilder和stringbuffer,只比较一下,执行引擎为hive. 当数据量为100000条,string builder耗时280秒,stringb ...

  3. spark中map和mapPartitions算子的区别

    区别: 1.map是对rdd中每一个元素进行操作 2.mapPartitions是对rdd中每个partition的迭代器进行操作 mapPartitions优点: 1.若是普通map,比如一个par ...

  4. Spring中毒太深,离开Spring我居然连最基本的接口都不会写了

    前言 随着 Spring 的崛起以及其功能的完善,现在可能绝大部分项目的开发都是使用 Spring(全家桶) 来进行开发,Spring也确实和其名字一样,是开发者的春天,Spring 解放了程序员的双 ...

  5. 20201128-2 【自动化办公】读写csv文件

    Exercise 1 import csv # 设置员工发展基金确认表路径 source_path = './员工发展基金确认表.csv' # 设置存放拆分结果文件的文件夹路径 result_path ...

  6. collectd+infludb+grafana实现tomcat JVM监控

    前提条件:已安装好java环境,tomcat,influxdb和collectd.本文暂不提供以上内容的安装步骤 系统环境:centos7 原理:开启tomcat的jmx端口,使用collectd的c ...

  7. 如何修改openstack虚拟机密码

    1.虚拟机创建时设置密码 计算节点安装以下软件包 yum install libguestfs python-libguestfs libguestfs-tools-c 配置计算节点nova配置文件/ ...

  8. Python 中日期函数

    导入日期库 datetime import datetime # 或者from datetime import datetime ,date 字符串转datetime

  9. C# 生成6位短信验证码

    1 private string VerifyCode() 2 { 3 Random random = new Random(); 4 return random.Next(100000, 99999 ...

  10. vue 动态注册路由 require.context

    需求场景: 在日常的功能练习和调试过程中,需要一个demo项目进行功能测试,由于频繁.vue页面的同时,又要再router.js文件里面注册路由,感觉有点无聊和枯燥.基于此出发点,考虑能否自动读取文件 ...