C++程序设计语言(特别版) -- 基本功能(下)

第7章 函数

7.1.1 函数的定义

声明和定义,定义就是具体实现。

extern void swap(int*, int*);   //声明
void swap(int *q, int* p) {...} //定义

在函数的定义里,可以存在不使用的参数。典型的情况是,出现未命名参数的原因是做过代码的简化,或者是计划在将来做功能扩充。

void search(table* t, const char* key, const char*)
{//第三个参数没有使用}
inline描述符给编译器一个提示,要求它试着把所有对在线函数的调用在线化。

继续补概念,引入inline的主要原因是替代C中表达式形式的宏定义。这种宏定义在形式上类似一种函数,当在使用它时,仅仅只做预处理器符号表中的简单替换,不能享受C++编译器严格类型检查的好处,在C++中引入类及类的访问控制,这也不可能使用宏定义来实现。

7.1.2 静态变量

  • 如果一局部变量被声明为static,那么将只有唯一的一个静态分配对象,它被用于在该函数的所有调用中表示这个变量。

7.2 参数传递

  • 修改引用参数的函数将会使程序更难阅读,因此最好避免写这样的函数。__(之前提过,用指针的形式告诉别人这个参数需要修改)__通过引用方式传递大的对象,比通过值传递的效率更高一些。在这种情况下,可以将有关的参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。
  • 文字量,常量和需要转换的参数都可以传递给const&参数,但不能传递给非const的引用参数。对非const引用参数不允许做类型转换。(个人理解:const代表不修改,当函数以const引用为参数时,出现类型转换就会产生临时变量,但是对临时变量的引用不会影响输出。当函数以非const引用为参数的时候,出现类型转换就会产生临时变量,对临时变量的修改没有意义,因为临时变量会被撤销)

7.2.1 数组参数

  • 如果将数组作为函数的参数,传递的就是数组的首元素的指针。数组与其他类型不同,数组不会按值的方式传递。
  • 再提一次,vector等类型可以用于代替内部的、低级的数组和指针一类东西。

7.3 返回值

  • 绝不能返回指向局部变量的指针,因为被指位置中内容的改变情况是无法预料的。

7.4 重载函数

1.准确匹配:无须转换或只须做平凡转换(例如,数组名到指针,函数名到指针,T到const T等)的匹配。 2.利用提升的匹配;既包括整数提升(bool到int,char到int,short到int以及他们的无符号版本)以及float到double的提升。 3.利用标准转换(例如,int到double,double到int)的匹配。 4.利用用户定义转换匹配。 5.利用在函数声明中省略号...的匹配。 如果在某个最高的层次同时发现两个匹配,这个调用将作为存在歧义而被拒绝。

7.4.2 重载与作用域

  • 在不同的非名字空间作用域里声明的函数不算重载。
void f(int);

void g() {
    void f(double);
    f(1); //调用f(double)
}
  • 如果希望重载能跨越类作用域或名字空间作用域,那么可以利用声明或使用指令。

7.7 指向函数的指针

  • 对于一个函数只能做两件事:调用它,取得它的地址。通过取一个函数的地址而得到的指针,可以在后面用于调用这个函数。可以不写从指针得到函数的间接运算符*,取得地址的&也可以不用写。

7.8 宏

关于宏的第一规则是:绝不应该去使用它,除非你不得不这样做。宏名字不能重载,而且宏预处理器不能递归调用。

书本举了一个危险的宏定义例子:

#define SQUARE(a) a*a
...
int y = SQUARE(xx+2); // y=xx+2*xx+2,而不是(xx+2)的平方
...
通过##宏运算符可以拼接起两个串,构造一个新串。

指令#undef X,保证不再有称为X的有定义的宏。

7.9 忠告

  1. 质疑那些非const的引用参数;如果你想要一个函数去修改其参数,请使用指针或者返回值;
  2. 当你需要尽可能减少参数复制时,应该使用const引用参数。
  3. 广泛而一致地使用const。
  4. 避免宏。
  5. 避免不确定数目的参数。
  6. 不要返回局部变量的指针或引用。
  7. 当一些函数对不用的类型执行概念上相同的工作时,请使用重载。
  8. 在各种整数上重载时,通过提供函数去消除常见的歧义性。
  9. 在考虑使用指向函数的指针时,请考虑虚函数或模板是不是更好的选择。
  10. 如果你必须使用宏,请使用带有许多大写字母的丑陋的名字。

第8章 名字空间和异常

8.2 名字空间

  • 作为将实现与界面分离的结果,现在在每个函数只有一个声明和一个定义。
  • 常规的局部作用域、全局作用域和类也都是名字空间。

8.2.2 使用声明

  • 当某个名字在它自己的名字空间之外频繁使用时,这种重复的东西可以通过一个使用生命而清除掉,只需要在某个地方说明,在这个作用域里所用的函数是某个名字域的函数。__(using)__使用声明将引进局部的同义词。
  • 我们也可以把有关的声明放在需要用到的声明空间的定义里。

8.2.3 使用指令

  • using namespace XXX;
  • 全局性的使用指令是一种完全转变的工具,在其他地方最好避免使用。在一个名字空间里的使用指令是一种名字空间的组合工具。在一个函数里,可以安全将使用指令作为一种方便的记法。

8.2.5.1 无名名字空间

  • namespace等价于namespace \($\),在本编译单元类无名空间内所有东西都可以直接使用,在其他编译单元里将无法说出这个无名名字空间的成员名字。

8.2.7 名字空间别名

  • namespace 短名字 = 长名字;

8.2.8.2 组合和选择

  • 假设有三个名字空间A,B,C;在C中using A和B就是组合。但是当显式 调用C::A成员时会出现错误,所以需要在C中再次using A::A成员来避免潜在冲突。

8.2.9.2 名字空间和重载

  • 重载可以跨名字空间工作。对于我们能以最小的代价将现存的库修改为使用名字空间的东西而言,这特征必不可少。

8.3.1 异常抛出和捕捉

  • throws抛出,try...catch...捕捉。

8.4 忠告

  1. 用名字空间表示逻辑结构。
  2. 将每个非局部的名字放在某个名字空间里,main除外。
  3. 名字空间的设计应该让你很方便地使用它,而又不会意外地访问了其他的无关名字空间。
  4. 避免对名字空间使用很短的名字。
  5. 如果需要,通过名字空间别名去缓和长名字空间名的影响。
  6. 避免给你的名字空间添加太大的记法负担。
  7. 在定义名字空间成员时使用namespace::member的形式。
  8. 只在转换时,或者局部作用域里,才用using namespace。
  9. 利用异常去松弛“错误”处理代码和正常处理代码之间的联系。
  10. 采用用户自定义类型作为异常,不用内部类型。
  11. 当局部控制结构足以应付问题时,不要用异常。

第9章 源文件和程序

9.2 链接

  • 在所有的编译单位中,对所有函数、类、模板、变量、名字空间、枚举和枚举符的名字的使用必须保持一致,除非它们被显式地描述为局部的东西。
  • 如果一个名字可以在与其定义所在的编译单位不同的地方使用,就说它是具有外部链接的。如果某名字只能定义在其定义所在的编译单位内部使用,它就被称为是具有内部链接的。
  • 外部链接和在线的组合是禁止的。
  • 按照默认约定,const和typedef都具有内部链接。
  • 局部于一个编译单元的全局变量是造成混乱的一个常见根源,最好是避免之。为了保证一致性,你一般应该把全局的const和typedef放在头文件。

9.2.1 头文件

作为一种经验法则,头文件里可以包括:

不应该包括:

9.2.3 单一定义规则

  • 关键字“export”的意思就是“在其他的编译单元可以访问”。

9.2.4 与非C++代码的链接

  • extern "C" 指令描述的只是一种链接约定,它并影响调用函数的定义。特别地,声明为extern "C"的函数仍然要遵守C++的类型检查和参数转换规则。

9.4.1 非局部变量的初始化

  • 原则上说,在所有函数之外定义的变量应该在main()的调用之前完成初始化。
  • 对于不同编译单元里的全局变量,其初始化的顺序则没有任何保证。因此,对于不同编译单元里的全局变量,在它们的初始化之间建立任何顺序依赖都是不明智的。

9.5 忠告

  1. 利用头文件去表示界面和强调逻辑结构。
  2. 用#include将头文件包含到实现有关功能的源文件里。
  3. 不要在不同的编译单位里定义具有同样名字,意义类似但又不同的全局实体。
  4. 避免在头文件里定义非inline函数。
  5. 只在全局作用域或名字空间里使用#include。
  6. 只用#include包含完整的定义。
  7. 使用包含保护符。#ifndef
  8. 用#include将C头文件包含到名字空间里,以避免全局名字。
  9. 将头文件做成自给自足。
  10. 区分用户界面和实现界面。
  11. 区分一般用户界面和专家用户界面。
  12. 在有意向用于非C++程序组成部分的代码中,应避免需要运行时初始化的非局部对象。

question?

1.什么是编译单位?

  • 当一个c或cpp文件在编译时,预处理器首先递归包含头文件,形成一个含有所有必要信息的单个源文件,这个源文件就是一个编译单元。可以把它理解为:在#include头文件的内容后(即将头文件的内容粘贴到cpp中之后)的cpp文件就是编译单元。简单说,一个编译单元就是一个经过预处理的cpp文件。

2.什么叫头文件的自给自足?

  • 暂且理解为单一定义规则:一个类、模板或者在线函数的两个定义能够被接受为同一个唯一定义的实例,当且仅当:1.它们出现在不同编译单元里。2.它们按一个个单元对应相同。3.这些单词的意义在两个编译单元中也完全一样。

相关推荐