第六章

6.6

形参、局部变量和局部静态变量

  • 形参和定义在函数体内部的变量统称为局部变量,它们仅在函数的作用域内可见
  • 函数体内的局部变量可以分为普通局部变量和静态局部变量
  • 形参:在函数开始时便为其申请内存空间,调用时用实参进行初始化。形参是只存在于块执行期间的对象
  • 普通局部变量在所载块结束后就失效了
  • 静态局部变量对应的对象称为局部静态对象,它的生命周期从定义语句开始一直到程序结束

6.8

在头文件内进行函数声明:和变量类似,在头文件声明然后在源文件定义。预处理器变量的名字全部大写,可以避免与程序中别的实体发生名字冲突。

#ifndef CHAPTER_H_INCLUDED
#define CHAPTER_H_INCLUDEDint fact(int);
double myABS(double);#endif

以下引用自:#pragma指令与#ifndef指令区别

在C/C++中,在使用预编译指令#include的时候,为了防止重复引用造成二义性,通常有两种方式——

  • 第一种是#ifndef指令防止代码块重复引用,比如说
#ifndef _CODE_BLOCK
#define _CODE_BLOCK   // code  #endif// _CODE_BLOCK` ```
  • 第二种就是#pragma once指令,在想要保护的文件开头写入
    #pragma once
  • #ifndef方式是C/C++语言的标准支持,也是比较常用的方式,#ifndef的方式依赖于自定义的宏名(例中的_CODE_BLOCK)不能冲突,它不光可以保证同一份文件不会被包含两次,也能够保证不同文件完全相同的内容不会被包含两次。
    但,同样的,如果自定义的宏名不小心“重名”了,两份不同的文件使用同一个宏名进行#ifndef,那么会导致编译器找不到声明的情况(被编译器判定为重定义而屏蔽了)
    此外,由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,#ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式(Visual Studio 2017新建头文件会自带#pragma once指令)
  • #pragma once一般由编译器提供保证:同一个文件不会被包含多次。这里所说的”同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。无法对一个头文件中的一段代码作#pragma once声明,而只能针对文件。
    此方式不会出现宏名碰撞引发的奇怪问题,大型项目的编译速度也因此提供了一些。缺点是如果某个头文件有多份拷贝,此方法不能保证它们不被重复包含。在C/C++中,#pragma once是一个非标准但是被广泛支持的方式。
  • 二者的区别:
    #pragma once方式产生于#ifndef之后。#ifndef方式受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式有些编译器不支持(较老编译器不支持,如GCC 3.4版本之前不支持#pragmaonce),兼容性不够好。#ifndef可以针对一个文件中的部分代码,而#pragma once只能针对整个文件

6.9(分离式编译)

  • 分离式编译允许我们把程序分割到几个文件中,每个文件独立编译
  • 举例:(C++Primer第五版 第6.1.3节
    fact函数定义在fact.cc中;fact函数声明位于Chapter.h中;fact函数在factMain.cc文件中的main函数中被调用。
    这一过程通常会产生一个后缀名为.obj(Windows)或者.o(UNIX)的文件,含义是该文件包含object code。
    编译器会把对象文件链接在一起形成可执行文件。
  • 分离式编译时具体代码
    fact.cc
#include "Chapter6.h"
using namespace std;
//定义函数fact
int fact(int val)
{if (val < 0)return -1;int ret = 1;for (inti = 1; i != val + 1; ++i)ret *= i;return ret;
}

factMain.cc

#include "Chapter6.h"
#include <iostream>
using namespace std;
//调用函数fact
int main()
{int num;cout << "请输入一个数" << endl;cin >> num;cout << num << "的阶乘是:" << fact(num) << endl;return 0;
}

6.12(传值和传引用)

使用引用可以直接操作所引用的对象,而且可以避免拷贝。
void f(T): 形参采用传值的方式,实参被拷贝给形参
void f(T&):形参采用传引用的方式,实参是形参的别名

6.14(引用传递相对于值传递的优势)

6.23

三个版本print函数:依次调用,输出int i = 0;int j[2] = {0, 1};的 i 和 j

  • 不控制指针的边界(参数是常量整型指针)
void print1(const int *p)
{cout << *p << endl;
}
  • 调用者指定数组的维度(两个参数,分别是常量整型指针和数组的容量)
void print2(const int *p, const int sz)
{int i = 0;while(i != sz){cout << *p++ << endl;++i;}
}
  • 使用C++11新规定的begin和end函数限定数组边界(两个参数,分别是数组的首尾边界)
void print3(const int *b, const int *e)
{for (auto q = b; q != e; ++q)//记得用后置运算符{cout << *q << endl;}
}
  • 调用如下
 print1(&i1);print1(j);print2(&i, 1);print2(j, sizeof(j)/sizeof(*j));print3(begin(j), end(j));

6.24(不指定形参数组维度)

void print(const int ia[10]) { //循环输出数组的值}

形参ia的维度10此时只是我们期望的,但是实际上不一定,而且真实维度如果不是10我们依然可以正常调用print函数
修改后应该是:void print(const int ia[], const int sz) { //循环输出数组的值}

6.25(main处理命令行选项 6.2.5节)

带参数的main与控制台命令行输入

6.27(含有可变形参的函数 6.2.6节)



一个可以计算列表中所有元素的和的函数:

#include <iostream>
using namespace std;int iCount(initializer_list<int> il)
{int count = 0;for (auto i : il){count += i;}return count;
}int main()
{//列表初始化initializer_list<T>cout << "1,1,2,2,3的和是:" << iCount({ 1,1,2,2,3 }) << endl;return 0;
}

6.31(引用何时有效)

引用所引的是函数开始之前就已经存在的对象,则返回该引用是有效的
引用的是函数的局部变量则随着函数的结束,局部变量失效,此时返回的引用无效
不希望改变返回的对象时,返回对常量的引用

6.32

int &get(int *array, int index) {return array[index];}


注意return处不要加&,否则会变成返回 array[ index ] 的地址

6.36(返回数组的引用的函数)

string (&func( )) [10];
&func( )表示函数的返回结果是一个引用
(&func( )) [10]表示引用的对象是一个维度为10的数组
string (&func( )) [10]表示数组的元素是string对象

6.37

直接编写返回数组引用的函数比较繁琐,使用类型别名,尾置返回类型和decltype关键字都可以简化

  • 使用类型别名
 typedef string arr[10]//using arr = string[10];arr& func( );
  • 使用尾置返回类型
 auto func( ) -> string(&) [10];
  • 使用delctype关键字
 string str[10];decltype(str) &func( );

返回数组指针

  • 使用类型别名
    arrT是含有10个整数的数组的别名,func返回一个指向含有10个整数的数组的指针
 typedef int arrT[10];//using arrt = int[10];arrT* func(int i);
  • 声明一个返回数组指针的函数(不使用类型别名)
    定义返回数组指针的函数,数组的维度必须在形参列表的后面

    例子:int (*func( int i )) [10] ;的逐层理解
    func(int i) 调用func函数需要一个int类型的实参
    (*func( int i )) 可以对函数的调用结果解引用,所以必须加括号,否则返回指针的数组
    (*func( int i )) [10] 解引用func的调用得到一个大小为10的数组
    int (*func( int i )) [10] 表示数组中的元素是int类型
  • 使用尾置返回类型
    尾置返回类型跟在形参列表的后面并且以一个 -> 符号开头,函数真正返回类型在形参列表之后,原本防止返回类型的地方放置auto
    注意解引用运算符加了括号
 //func节后一个int类型的实参,返回指向含有10个整数的数组的指针auto func(int i) -> int (*) [10]
  • 使用decltype
    在已知函数返回的指针指向那个数组的情况下可以用decltype关键字声明返回类型
 int odd[10] = {1, 3, 5, 7, 9};decltype(odd) *arrPtr(int i);

arrPtr使用关键字decltype表示它的返回类型是一个指针,且指针指向的对象和odd类型一致。decltype并不负责把数组类型转换为对应的指针,decltype的结果是一个数组,必须在函数声明的时候加一个*符号表示arrPtr返回指针。

6.43(内联函数和constexpr函数)

内联函数和constexpr函数通常定义在头文件内,以保证多个定义完全一致,同时内联函数的定义应该对编译器来说是可见的,编译器才能在调用点内联展开函数的的代码

  • 内联函数,在函数的返回类型前面加 inline ,但是该请求编译器可以忽略
  • constexpr函数,能用于常量表达式的函数,constexpr函数额外规定:函数的返回类型和所有形参的类型都是字面值类型;函数体有且只有一条return语句。
 constexpr int new_sz() { return 42;};constexpr int foo = new_sz();
  • constexpr函数不一定返回常量表达式,即允许constexpr函数的返回值不是常量
    const xize_t scale( size_t cnt) { return new_sz() * cnt; }
    scale(2)返回常量表达式,int i = 2, scale(i)返回的不是常量表达式

6.47(调试器 6.5.3节)

assert 预处理宏

assret(espr),表达式为假,assert输出信息并终止程序

NDEBUG

如果定义了NDEBUG,则assert什么也不做
使用 #define 语句定义NDEBUG,可以关闭调试状态

  • 可以使用NDEBUG编写自己的条件调试代码,NDEBUG未定义就会执行 #ifndef
    #endif之间的代码,NDEBUG已定义这些代码会被忽略:
    此处如果打开调试器,就会执行中间的内容,关闭调试器则不会
 #ifndef NDEBUG//……………………#endif

6.49(候选函数和可行函数 6.6节)

  • 函数匹配第一步:选出调用对应的重载函数集,集合内的就是候选函数
  • 函数匹配第二步:候选函数中选出可以被这组实参调用的函数,即可行函数(形参和实参数量相等,实参和形参类型相同或者可以转化)

6.52(实参类型转换 6.6.1)

实参类型到形参类型转换的等级排序:

6.54(函数指针 6.7节)

  • 函数的类型由返回类型和形参类型共同决定,例如指向函数类型bool (const string &, const string &)的函数指针:bool (*pf)(const string &, const string &)
    pf 前面由*表示 pf 是一个指针,右侧形参列表表示 pf 指向的是函数,左侧的是函数的返回类型
    如果没有括号变成bool *pf(const string &, const string &),pf 就是一个返回bool指针的函数
  • 如果把函数名作为一个值使用,它就会自动地转换成指针
 bool lengthCompare(const string &, const string &)pf = lengthCompare;//取地址符是可选的//pf = &lengthCompare
  • 直接用指针调用函数,不需要提前解引用,以下三个调用等价
 bool b1 = pf("sadad", "dsada");bool b1 = (*pf)("sadad", "dsada");bool b1 = lengthCompare("sadad", "dsada");
  • 可以为函数指针赋值nullptr或0,表示不指向任何一个函数:pf = nullptr
  • 重载函数指针举例
 void ff(int*);void ff(unsigned int);void (*pf)(unsigned int) = ff//pf指向ff(unsigned int)
  • 函数指针可以作为形参使用,形参虽然看起来是函数类型但是实际上被当成指针使用
 void f(bool pf(const string &, const string &));//void f(bool (*pf)(const string &, const string &))f(lengthCompare);//直接把函数作为实参,其会自动转换成指针

简化函数指针(类型别名 或者 decltype)

重点注意
①函数类型,但是在作为形参的化会自动转换为指针
②decltype返回的函数类型不会自动转化成指针类型,需要在结果上加上*
③和形参不同,函数返回类型不会自动转换成指针

  • 此处的func1只是函数类型,但是在作为形参的化会自动转换为指针
 //func是函数类型typedef decltype(lengthCompare) func1;//等价typedef bool func1(const string &, const string &));
  • decltype返回的函数类型不会自动转化成指针类型,需要在结果上加上*
 typedef decltype(lengthCompare) *func2;//等价typedef bool (*func2)(const string &, const string &));
  • 重新声明f:把void f(bool pf(const string &, const string &));改为如下
 void f(fun1);//等价void f(fun2);

返回指向函数的指针

  • 声明返回函数指针的函数最简单的办法是使用类型别名
 using F = int(int *, int);using PF = int(*)(int *, int);
  • 和形参不同,返回类型不会自动转换成指针,我们必须显示第将返回类型指定为指针
 PF f1(int);F  *f1(int);
  • 直接声明,不用类型别名
 int (*f1(int)) (int *, int);
  • 尾置返回类型
 auto f1(int) -> int(*)(int *, int);
  • decltype:牢记decltype返回函数类型而不是指针类型
 bool lengthCompare(const string &, const string &);deltype(lengthCompare) *getFcn(const string &);vector<deltype(lengthCompare) *> vL;

一个元素是指向bool (const string &, const string &)的指针的vector

C++Primer第五版 第六章 课后习题答案相关推荐

  1. 线性代数第五版吉尔伯特课后答_线性代数同济第五版第六章课后习题答案!

    搜集 | 整理 |  测试 | @小愉 免责声明:以下资源或软件均来自互联网,仅供学习和交流使用,如有侵权请联系删除,请勿用于商业和非法途径等,如有法律纠纷与本人无关! 本文未经允许,不得转载! 适用 ...

  2. 线性代数第五版吉尔伯特课后答_线性代数同济第五版第四章课后习题答案!

    搜集 | 整理 |  测试 | @小愉 免责声明:以下资源或软件均来自互联网,仅供学习和交流使用,如有侵权请联系删除,请勿用于商业和非法途径等,如有法律纠纷与本人无关! 本文未经允许,不得转载! 适用 ...

  3. 计算机网络原理(谢希仁第八版)第六章课后习题答案

    第六章 1.互联网的域名结构是怎样的?它与目前的电话网的号码结构有何异同之处? 答:(1)域名的结构由标号序列组成,各标号之间用点隔开:- 三级域名. 二级域名. 顶级域名,各标号分别代表不同级别的域 ...

  4. 数据库系统概论(第五版) 王珊 第二章课后习题答案

    1 .试述关系模型的三个组成部分. 答:关系模型由关系数据结构.关系操作集合和关系完整性约束三部分组成. 2 .试述关系数据语言的特点和分类. 答:关系数据语言可以分为三类: 关系代数语言. 关系演算 ...

  5. java第二版课后题答案_Java语言程序设计第2版第16章 课后习题答案

    <Java语言程序设计第2版第16章 课后习题答案>由会员分享,可在线阅读,更多相关<Java语言程序设计第2版第16章 课后习题答案(62页珍藏版)>请在人人文库网上搜索. ...

  6. 《Python语言程序设计》王恺 机械工业出版社 第六章课后习题答案

    第六章 字符串 6.5 课后习题 (1)Python 中,创建字符串时,可以使用单引号.双引号和三引号 (2)Python 中,使用字符串的 split 方法可以按照指定的分隔符对字符串进行切割,返回 ...

  7. C++ Primer(第五版)第七章 类 部分答案

    第七章 类 练习7.2 练习7.3 练习7.4 练习7.6 练习7.7 练习7.9 练习7.14.7.15.7.22 练习7.23.7.24.7.26 练习7.27 练习7.2 曾在 2.6.2 节的 ...

  8. 数据库系统概论(第五版) 王珊 第六章课后习题答案

    1 .理解并给出下列术语的定义: 函数依赖.部分函数依赖.完全函数依赖.传递依赖.候选码.主码.外码.全码(All 一key ).1 NF .ZNF .3NF .BcNF .多值依赖.4NF . 定义 ...

  9. 数据库概论(第五版)第六章课后习题答案(现更)

    2. 语义:一个系有若干专业,每个专业每年只招一个班,每个班有若干学生.一个系的学生住在同一宿舍区.每个学生可参加若干学会,每个学会有若干学生.学生参加某学会有一个入会年份. 模式1:学生(U, F) ...

  10. C程序设计(谭浩强版)第六章课后习题

    1.用筛选法求100之内的素数 筛选法:古希腊人将1-N个的数字写在一个板子上,然后逐一判断该数符不符合要求,不符合要求得数字就会划掉,知道将数字全部比对结束,比对完后因为板子上划掉得标记太多禄,像筛 ...

最新文章

  1. PHP流程控制语句例题,第四堂、php流程控制语句
  2. 根据开源数据库选择合适的工具
  3. 用Linux构建高效FTP服务器(2)
  4. 人工智能的炒作_解密人工智能:是炒作还是我们期望太高
  5. day32—CSS多列布局学习
  6. this的用法this.name=name 这个什么意思
  7. ElasticSearch6.x 7.x Elasticdump 在线安装、离线安装
  8. 百度坐标转换API使用
  9. cakephp 1.3 Views
  10. js designMode contentEditable 编辑在线网页
  11. java中的socket模型_Socket通信模型
  12. uv转化率多少正常_宣城UV光解设备价格多少-低价供应
  13. Python decorator
  14. OKapi BM25 算法介绍
  15. 如何使用Xpose绕过APP自定义证书验证去抓Https包
  16. 海康威视前端实习生面试
  17. 南京地图njmaps使用,以公众版为例
  18. 二分查找法--有序表
  19. discuz插入幻灯片_如何将符号插入Google文档和幻灯片
  20. 迅雷起死回生背后的男人,竟然是雷军

热门文章

  1. VS2013附加包含目录,添加相对路径
  2. ​​欧洲能源危机日益严重,这个冬天到底会有多 “冷” ?
  3. 干货 | PCB多层板为什么都是偶数层?奇数层不行吗?
  4. WireShark基本使用(5)第 5 章 文件输入/输出及打印
  5. windows 进程通信(使用DDE)
  6. 成长之路——发现问题、提出问题和解决问题
  7. 堆糖:爱豆图片分享社区
  8. char类型和字符串
  9. 微软云服务器怎么注销,如何关闭你的 Microsoft 帐户——注销微软账号
  10. 北京内推 | 微软亚洲研究院自然语言计算组招聘NLP研究实习生