《C++编程思想》(Thinking in C++)部分精华提取

1、声明与定义:

首先,必须知道“声明”和“定义”之间的区别,因为这两个术语在全书中会被确切地使用。“声明”向计算机介绍名字,它说,“这个名字是什么意思”。而“定义”为这个名字分配

存储空间。无论涉及到变量时还是函数时含义都一样。无论在哪种情况下,编译器都在“定义”处分配存储空间。对于变量,编译器确定这个变量占多少存储单元,并在内存中产生存放它们的空间。对于函数,编译器产生代码,并为之分配存储空间。函数的存储空间中有一个由使用不带参数表或带地址操作符的函数名产生的指针。定义也可以是声明。如果该编译器还没有看到过名字A,程序员定义int A,则编译器马上为这个名字分配存储地址。声明常常使用于e x t e r n关键字。如果我们只是声明变量而不是定义它,则要求使用e x t e r n。对于函数声明, e x t e r n是可选的,不带函数体的函数名连同参数表或返回值,自动地作为一个声明。函数原型包括关于参数类型和返回值的全部信息。int f(float,char);是一个函数原型,因为它不仅介绍f这个函数的名字,而且告诉编译器这个函数有什么样的参数和返回值,使得编译器能对参数和返回值做适当的处理。C + +要求必须写出函数原型,因为它增加了一个重要的安全层。

extern int i;//声明

extern float f(float);//声明

float b;//声明+定义

float f(float a){//定义 

return a+1.0;

}

int i;//定义

int h(int x){//声明+定义

return x+1;

}

main(){

b=1.0;

i=2;

f(b);

h(i);

}

在函数声明时,参数名可给出也可不给出。而在定义时,它们是必需的。这在C语言中确实如此,但在C + +中并不一定。

2、句柄类:

C + +中的存取控制允许将实现与接口部分分开,但实现的隐藏是不完全的。编译器必须知道一个对象的所有部分的声明,以便创建和管理它。我们可以想象一种只需声明一个对象的公共接口部分的编程语言,而将私有的实现部分隐藏起来。但C + +在编译期间要尽可能多地做静态类型检查。这意味着尽早捕获错误,也意味着程序具有更高的效率。然而这对私有的实现部分来说带来两个影响:一是即使程序员不能轻易地访问实现部分,但他可以看到它;二是造成一些不必要的重复编译。
可见的实现部分:
有些项目不可让最终用户看到其实现部分。例如可能在一个库的头文件中显示一些策略信息,但公司不想让这些信息被竞争对手获得。比如从事一个安全性很重要的系统(如加密算法),我们不想在文件中暴露任何线索,以防有人破译我们的代码。或许我们把库放在了一个“有敌意”的环境中,在那里程序员会不顾一切地用指针和类型转换存取我们的私有成员。在所有这些情况下,就有必要把一个编译好的实际结构放在实现文件中,而不是让其暴露在头文件中。
减少重复编译:
在我们的编程环境中,当一个文件被修改,或它所依赖的文件包含的头文件被修改时,项目负责人需要重复编译这些文件。这意味着无论何时程序员修改了一个类,无论是修改公共的接口部分,还是私有的实现部分,他都得再次编译包含头文件的所有文件。对于一个大的项目而言,在开发初期这可能非常难以处理,因为实现部分可能需要经常改动;如果这个项目非常大,用于编译的时间过多就可能妨碍项目的完成。解决这个问题的技术有时叫句柄类( handle classes)或叫“Cheshire Cat”[ 1 ]。有关实现的任何东西都消失了,只剩一个单一的指针“ s m i l e”。该指针指向一个结构,该结构的定义与其所有的成员函数的定义一样出现在实现文件中。这样,只要接口部分不改变,头文件就不需变
动。而实现部分可以按需要任意更动,完成后只要对实现文件进行重新编译,然后再连接到项目中。这里有个说明这一技术的简单例子。头文件中只包含公共的接口和一个简单的没有完全指定的类指针。这是所有客户程序员都能看到的。这行
struct cheshire;
是一个没有完全指定的类型说明或类声明(一个类的定义包含类的主体)。它告诉编译器,cheshire 是一个结构的名字,但没有提供有关该结构的任何东西。这对产生一个指向结构的指针来说已经足够了。但我们在提供一个结构的主体部分之前不能创建一个对象。在这种技术里,包含具体实现的结构主体被隐藏在实现文件中。
#ifndef HANDLE_H

#define HANDLE_H

class handle{

struct cheshire;

cheshire *smile;

public:

void initialize();

void cleanup();

int read();

void change(int);

};

#endif

这是所有客户程序员都能看到的。这行
struct cheshire;
是一个没有完全指定的类型说明或类声明(一个类的定义包含类的主体)。它告诉编译器,cheshire 是一个结构的名字,但没有提供有关该结构的任何东西。这对产生一个指向结构的指针来说已经足够了。但我们在提供一个结构的主体部分之前不能创建一个对象。在这种技术里,包含具体实现的结构主体被隐藏在实现文件中。

#include "handle.h"

struct handle::cheshire{

int i;

};

void handle::initialize(){

smile=(cheshire *)malloc(sizeof(cheshire));

assert(smile);

smile->i=0;

}

void handle::cheanup(){

free(smile);

}

int handle::read(){

return smile->i;

}

void handle::change(int x){

smile->i=x;

}

cheshire 是一个嵌套结构,所以它必须用范围分解符定义
struct handle::cheshire {
在h a n d l e : : i n i t i a l i z e ( )中,为cheshire struct分配存储空间[ 1 ],在h a n d l e : : c l e a n u p ( )中这些空间被释放。这些内存被用来代替类的所有私有部分。当编译H A N D L E . C P P时,这个结构的定义被隐藏在目标文件中,没有人能看到它。如果改变了c h e s h i r e的组成,唯一要重新编译的是H A N D L E . C P P,因为头文件并没有改动。句柄(h a n d l e)的使用就像任何类的使用一样,包含头文件、创建对象、发送信息。

#include "handle.h"

main(){

handle u;

u.initialize();

u.read();

u.change(2);

}

客户程序员唯一能存取的就是公共的接口部分,因此,只是修改了在实现中的部分,这些文件就不须重新编译。虽然这并不是完美的信息隐藏,但毕竟是一大进步

3、重载:

返回值重载:

void f();

int f();

当编译器能从上下文中唯一确定函数的意思时,如int x = f();这当然没有问题。然而,在C中,我们总是可以调用一个函数但忽略它的返回值,在这种情况下,编译器如何知道调用哪个函数呢?更糟的是,读者怎么知道哪个函数会被调用呢?仅仅靠返回值来重载函数实在过于微妙了,所以在C + +中禁止这样做。

4、const :

与使用# d e f i n e一样,使用c o n s t必须把c o n s t定义放进头文件里。这样,通过包含头文件,可把c o n s t定义单独放在一个地方并把它分配给一个编译单元。C + +中的c o n s t默认为内部连接,也就是说,c o n s t仅在c o n s t被定义过的文件里才是可见的,而在连接时不能被其他编译单元看到。当定义一个常量(c o n s t)时,必须赋一个值给它,除非用e x t e r n作了清楚的说明:
extern const bufsize;
虽然上面的e x t e r n强制进行了存储空间分配(另外还有一些情况,如取一个c o n s t的地址,也要进行存储空间分配),但是C + +编译器通常并不为c o n s t分配存储空间,相反它把这个定义保存在它的符号表里。当c o n s t被使用时,它在编译时会进行常量折叠。

5、volatile:

v o l a t i l e的语法与c o n s t是一样的,但是v o l a t i l e的意思是“在编译器认识的范围外,这个数据可以被改变”。不知何故,环境正在改变数据(可能通过多任务处理),所以,v o l a t i l e告诉编译器不要擅自做出有关数据的任何假定—在优化期间这是特别重要的。如果编译器说:“我已经把数据读进寄存器,而且再没有与寄存器接触”。一般情况下,它不需要再读这个数据。但是,如果数据是v o l a t i l e修饰的,编译器不能作出这样的假定,因为可能被其他进程改变了,它必须重读这个数据而不是优化这个代码。就像建立c o n s t对象一样,程序员也可以建立v o l a t i l e对象,甚至还可以建立const volatile对象,这个对象不能被程序员改变,但可通过外面的工具改变。下面是一个例子,它代表一个类,这个类涉及到硬件通信:
就像c o n s t一样,我们可以对数据成员、成员函数和对象本身使用v o l a t i l e,可以并且也只能为v o l a t i l e对象调用v o l a t i l e成员函数。函数i s r ( )不能像中断服务程序那样使用的原因是:在一个成员函数里,当前对象( t h i s)的地址必须被秘密地传递,而中断服务程序I S R一般根本不要参数。为解决这个问题,可以使i s r ( )成为静态成员函数,这是下面章节讨论的主题。v o l a t i l e的语法与c o n s t是一样的,所以经常把它们俩放在一起讨论。为表示可以选择两个中的任何一个,它们俩通称为c - v限定词。

6、内联函数(inline):

在解决C + +中宏存取私有的类成员的问题过程中,所有和预处理器宏有关的问题也随着消失了。这是通过使宏被编译器控制来实现的。在C + +中,宏的概念是作为内联函数来实现的,而内联函数无论在任何意义上

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
一、下载须知: .................本书无目录 .................本书经过内容识别处理,所以好处就是书上的案例源码可以直接粘贴复制到编辑器中,当然有个别括号什么的可能需要自己纠正一下。 ................本书高清 ...............本书是和代码结合在一起讲解的,其中也讲到了运行时类的识别等等。 二、截取部分章节 《第18章:运行时类型识别》 RTTI的两种使用方法 使用RT T I有两种不同的方法。第一种就像s i z e o f ( ),因为它看上就像一个函数。但实际上它是 由编译器实现的。t y p e i d ( )带有一个参数,它可以是一个对象引用或指针,返回全局t y p e i n f o类的 常量对象的一个引用。可以用运算符“= =”和“!=”来互相比较这些对象。也可以用n a m e ( )来 获得类型的名称。注意,如果给t y p e i d ( )传递一个s h a p e *型参数,它会认为类型为s h a p e *,所以如 果想知道一个指针所指对象的精确类型,我们必须逆向引用这个指针。比如,s是个s h a p e * , cout << typeid(*s).name()<<endl; 将显示出s所指向的对象类型。 也可以用b e f o r e ( t y p e i n f o & )查询一个t y p e i n f o对象是否在另一个t y p e i n f o对象的前面(以定 义实现的排列顺序),它将返回t r u e或f a l s e。如果写: if(typeid(me).before(typeid(you))) //... 那么表示我们正在查询m e在排列顺序中是否在y o u之前。 RT T I的第二个用法叫“安全类型向下映射”。之所以用“向下映射”这个词也是由于类继 承的排列顺序。如果映射一个c i r c l e *到s h a p e *叫向上映射的话,那么将一个s h a p e *映射成一个 c i r c l e *就叫向下映射了。当然一个c i r c l e *也是一个s h a p e *,编译器允许任意的向上映射,但一 个s h a p e *不一定就是c i r c l e *,所以编译器在没有明确的类型映射时并不允许我们完成一个向下 映射任务。当然可以用原有的C风格的类型映射或C + +的静态映射(s t a t i c c a s t ,将在本章末介绍) 来强制执行,这等于在说:“我希望它实际上是一个c i r c l e *,而且我打算要求它是。”由于并没 有明确地知道它实际上是c i r c l e,因此这样做是很危险的。在开发商制定的RT T I中一般的方法 是:创建一个函数来试着将s h a p e *指派为一个c i r c l e * (在本例中),检查执行过程中的数据类型。 如果这个函数返回一个地址,则成功;如果返回n u l l,说明我们并没有一个c i r c l e *对象。 C + +的RT T I的“安全类型向下映射”就是按照这种“试探映射”函数的格式,但它(非常 合理地)用模板语法来产生这个特殊的动态映射函数( d y n a m i c c a s t),所以本例变成: 动态映射的模板参数是我们想要该函数创建的数据类型,也就是这个函数的返回值。函数 参数是我们试图映射的源数据类型。 通常只要对一种类型作这种工作(比如将三角型变成紫色),但如果想算出各种s h a p e的数 目,可以用面下例子的框架: 当然这是人为的—我们可能已经在各个类型中放了一个静态数据成员并在构造函数中对 第18章运行时类型识别383 下载 shape* sp = new circle; circle* cp = dynamic_cast<circle*>(sp); if(cp) cou七<< "cas 七successful"; circle* cp = dynamic_cas区circle*>(sh); square* sp = dynamic_cast<square*>(sh); triangle* 七p = dynamic_cast<triangle*>(sh); 它自增。如果可以控制类的源代码并可以修改它,当然可以这样做。下面这个例子用来计算 s h a p e的个数,它用了静态数据成员和动态映射两种方法: 384 C + +编程思想 下载 //: RTSHAPES.CPP -- Counting shapes #include <iostream.h> #include< 七ime.h> #include <typeinfo.h> #include"·.\14\tstash.h" class shape { protected: S 七atic in七coun店 public: shape() { count++; vir七ual -shape() = O { count--; } vir七ual void draw () const = O; static in七quan七让y() { return count; }; in七shape : : count = O; class rectangle : public shape void operator=(rectangle&); // Disallow protected: static int count; public: rectangle() { count++; rectangle(const rectangle&) { count++;} -rectangle() { count--; } void draw () cons 七{ cout << "rec 七angle: : draw ()" << endl; S 七atic int quantity() { return count; } ... ' int rectangle: : count = 0; class ellipse : public shape void operator=(ellipse&}; // Disallow protected: static int count; public: ellipse () { count++; } ellipse (const ellipse&) { count++; } ~ellipse() { count 一;} void draw() cons 七{ cout << "ellipse: :draw()" << endl; 第18章运行时类型识别385 下载 } static int quantity() { return count; } }; int ellipse::count = O; class circle: public ellipse { void operator=(circle&); // Disallow protected: static int count; public: circle() { count++; } circle(cons 七circle&) { count++; } 一circle() { count--; } void draw() const { cout << "circle: :draw()" << endl; } static in七quantity() { return count; } } ; int circle::count = 0; main() { 七stash<shape> shapes; time_t t; II Seed random number generator: srand((unsigned)time(&t)); const mod= 12; for(int i = 0; i < rand() 令mod; i++) shapes.add(new rectangle); for(int j = O; j < rand() % mod; j++) shapes.add(new ellipse); for(int k = O; k < rand() 兮mod; k++) shapes.add(new circle); int Ncircles = O; int Nellipses = O; int Nrects = O; int Nshapes = O; for(int u = O; u < shapes.count(); u++) { shapes[u]->draw(); if(dynamic_cast<circle*>(shapes[u])) Ncircles++; if(dynamic_cast<ellipse*>(shapes[u])) Nellipses++; if(dynamic_cast<rectangle*>(shapes[u])) Nrects++; 对于这个例子,两种方法都是可行的,但静态数据成员方法只能用于我们拥有源代码并已 安装了静态数据成员和成员函数时(或者开发商已为我们提供了这些),另外RT T I可能在不同 的类中用法不同。 18.3 语法细节 本节详细介绍RT T I的两种形式是如何运行的以及两者之间的不同。 18.3.1 对于内部类型的typeid() 为了保持一致性, t y p e i d ( )也可以运用于内部类型,所以下面的表达式结果为t r u e: 18.3.2 产生合适的类型名字 t y p e i d ( )必须在所有的状况下都可以运行,比方说,下面的类中包含了一个嵌套类: 386 C + +编程思想 下载 } if(dynamic_cast<shape*>(shapes[u])) Nshapes++; cout << endl << endl <<"circles=• << Ncircles << endl <<"ellipses=• << Nellipses << endl << "rec 七angles="<< Nrects << endl <<"shapes="<< Nshapes << endl << endl << "circle: :quantity() =• << circle: :quantity() << endl << "ellipse: :quantity() = " << ellipse: :quantity() << endl << "rectangle: :quantity() =" << rectangle: : quantity () << endl << "shape: :quantity() =• << shape: :quantity() << endl;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值