您的位置:首页 > 编程语言 > C语言/C++

C++Primer 读书笔记 第2章 浏览

2007-03-13 16:53 239 查看
解引用操作符*解除pint的引用.
在C++中,指针的主要作用是管理和操纵动态分配的内存。对动态分配的内存,唯一的访问方式是通

过指针间接的访问。

/////////////以下参考别人的//////////////////////////////////////////////////////////////////////

2.1节到2.2节

很多人都会觉得第二章很难,作者在第一篇的引言中也提到:如果读者觉得第二章
的内容难以理解,就跳过他。而个人认为这样做不是最好的选择,事实上,第一篇
的目的在于对这语言有个很好的整体的理解,思维上的习惯,方法论的形成才是本
章的重点,前面这些废话,是对我们将要面对的难度以及我们要采取的态度作一个
简单的说明和建议,

我们在第一章中学的那些东西,都是比较现成的、可以直接使用的、在用的过程中
不太会受到语法上的限制的东西,我想大家还记得我在读到数据类型的时候,有过
一个疑问:为什么那个时候作者不说数组?在2.1节里,作者给出了个答案:数组不
属于基本型,什么是基本类型呢?作者的解释是:语言本身对该类型的赋值、一般
算术运算和关系运算提供内置支持。显然数组和指针都不能满足这个条件,按作者
的原话:他们不是一等公民,

关于这两节的知识点。我想要说的并不多,对于学过一点编程的人来说数组和指针
不是什么陌生的东西。在这本书的前言里作者就说过"这本书适合c++的初学者,但
不是适合编程的初学者",但是这里仍然有两个很多初学者会犯的低级错误:

数组方面:我来举个例子:

int a[8];.

a[8]=10;

这两个8几乎完全相同。事实是:在意义、作用上都完全是两回事,前者是size,后
者是下标、这里我不得不产生一个疑问:[]运算符到底是干嘛的?在这里我们几乎第
一次看到了一符多用的例子(也许不是,姑且这样认为)。

指针方面。我见过很多类似这样的用法:

int *p = NULL;

cout << *p << end ;

显然,一个合格的程序员不会写这样的代码的、但是要不是一个朋友的提醒。我也
就自以为是的认为它只能是个笨蛋的错误,这里有两个问题:NULL是什么?我以前
很天真的把它和ASCII联系起来,事实上。它就是0。为一能的赋予p的值。接下来就
是空间分配的问题了,我们都知道NULL的作用是不让指针指向任何对程序有用的空
间。那么0放那呢?显然这儿有个关于空间的误解,那就是指针的空间和指针指向的
空间是不同的。指针的空间是定义时已经分配了,但它只能放内存地址和NULL(p)
, 指向的空间是容器里的东西(*p),显然,上面的*p事实上并不存在。取一个不
存在的东西,当然是不能不出毛病的。

作者在这里很讲到new。一直以来我对new总是有点恐惧感,他远远不如c的函数来的
明白,作者告诉我们它是动态分配内存的运算符。可是请注意作者对于动态分配的
解释:"运行时刻库函数的行为"。库函数?no运算符?这里我们几乎感觉到了,函
数和运算符之间存在着某种联系。再深入点思考,想想我们在开头提到的关于数组
的缺点,数组不能进行某些运算?well,如果我们的假设成立,函数和运算符存在
联系,那么我们自己是不是可以完善下我们的数组呢?事实证明我们是正确的。这
就引出了ADT的思想-类的设计(后面的内容就是个不断设计完善的过程)。可见我们
以前的学习方法存在一个大问题,我们不知道自己为什么要学一样东西,作者已经
暗示我们这样的答案。需求引发灵感,灵感带动设计,设计成就技术,技术造成学
习。学习满足需求

2.3节

早在2.1里作者就留给我们一个问题:要成为c++的一等公民需要那些条件(练习2.
2)?个人认为这个问题非常重要。它直接关系到我们对类的设计思想和努力的方向
,显然我们的方向该是:尽可能的让我们设计的类成为语言的一等公民,换句话说
,在类的设计中,对于运算符的支持要比一般的成员函数更重要,这样做的另一个
考虑是为我们以后学习STL提供思维的延续,可见。如果我们只把c++当成oo的语言
,是不会这样想的 ,

对于初学者来说,想在本节完全学会很好类的代码实现,几乎是不现实的,在本节
的内容里,我们的任务是理解设计思路、了解类的基本结构,学会用伪代码设计一
个能满足需求的class,下面是我在本节中所理解的东西,

第一步:明确设计需求

我们可以仔细看一下作者写出的功能表,显然,就功能而言,它并不完善,但能满
足一方的需求,这里我们可以感觉一下作者的专业品质:以用户需求为目标,我们
的软件常常有这样问题,几百mb的程序,事实上我们真正用到的可能不足百分之一
,成本和资源都得到很大的浪费,好像只满足了设计者的虚荣心,

第二步:确定要封装的数据

当然,我们先要明白的是:封装的目的是为了保证对象数据的相对安全,要通分理
解封装的涵义,比如:

class a {

int i

public:

….};

class b {

a s;

public:

….}

几天前就有人问我这样的问题。,b是否可以操作s中的数据,答案是:yes。但是如
果这样问。。s中的数据在b是否可用,答案则是,no,原因在于对于封装的数据而
言,只有通过s自身的方法才有权使用。那么b如何操作s的数据呢?这就引出了第三
步.

第三步,设计公有接口

个人觉得接口和方法是有区别的,接口包括运算符和方法,当然他们都靠函数完成
。但对函数的实现目标有着明显的不同,运算符考虑的是对象之间的关系。而方法
考虑的是对象本身属性的操作,对于设计一个数据类型来说显然前者更重要,对于
内置的数组来说。我们并没有考虑其本身的东西。但是,谁能说他不算class呢?不
过不是我们设计的罢了,

接下来,谈谈一些习惯问题,可能先看国内教材的人都会发现。在本书中public写
在private之前。看起来好像没有多大的区别,其实我们应该发现,如果私有成员写
前面关键字private可以不写,这不利于代码的可读性,我们要时刻为自己以后的合
作提供良好习惯和代码风格。这样也该是程序员的基本素质之一吧?

2.4节

在上一节中作者带着我们初步建立一个class。正如作者自己所说:它能满足一些用
户的需要,但是我们都知道我们的世界是多元的。当然需求也就会分不同的层次,
但是生产的各类资源都极其有限,因此对工作效率的追求就成了我们这个时代技术
发展的主要目标。

对于编程工作来说。效率主要来自代码的重用性。因为计算机硬件技术的发展成果
,可以让程序员合理的忽视一些代码执行效率的考虑(并非完全忽视)。对于代码
的重用性,根据作者的描述,我想把他理解为两个方面的努力,以方法(函数)的
调用来划分。

在调用者这一端:

我们希望写下的一句代码,由计算机自己决定其代码的具体实现方法。正如书上所
写的,当我们写下:

int x = max( a, b );

我们希望计算机自己根据a,b的数据类型来决定具体该调用那个max()实例。在就是
重载。(其实这是上节的内容。但我自己觉得把它放到这里来总结更有利理解其作
用和本质)

在被调用的一端:

我们一方面希望自己的代码能被不同的程序调用,另一方面我们也希望自己代码能
根据被调用端的具体情况做出相应的反应,这两个方面都造就了伟大的技术。前者
导致了oo理论的诞生,后者引出了泛型设计思想。

终于看到了oo,呵呵。在高兴之余,发现oo并没有想象的那么复杂,那么神秘。是
他本身就不难?no,不是这样的,这完全归功于我们伟大的作者,他让我们从一开
始就走对了路,从技术产生的根源出发。更容易理技术的本质涵义。我们会惊讶的
发现原来如此,正如下面的例子:

1. 同类的不同对象方法具体实现细节不同?ok,虚函数应运而生。

2. 想简单的加入几项功能形成另一相关的class?ok,继承机制就是为了这个。

3. 如果还想新类博采各类之长。多继承。

可以看到,在这种思想下,很多难以理解的东西。就变的简单多了。

注意:

在方便了编程方式的同时我们不要忘记这些方便都是,放弃运行效率为代价的。有
时候我们不得不回过头来,考虑一下这些麻烦的问题。作者也提供了一些思考的方
向,如。inline技术等等,但是正如上面所说,这些不是最重要的,

最后,我想说明下。从2.3节开始一直到本章结束,这里所有的内容都是值得玩味的
。每次重读都会有不同的感受。 强烈建议在以后的学习中要不断的回头读这些内容
,你会发现这样做的必要性,正如我在这里回避了代码细节级的技术。

2.4节

随着这本书的深入,越来越发现自己对c++的理解是多么的浅薄,现在的我对于这些
笔记真是诚惶诚恐,要知道这些自以为是的理解也许在今后的某个日子里信手翻阅
,恐怕自己都会问。这些废话是那个笨蛋写的?简直一无是处,但是,我们的成熟
正是来自这些一无是处的错误,作为初学者的我,希望在这里留下学习的思维旅程
,不管自己的水平如何。这本笔记我还是会坚持,

之所以说上面那些感言,是因为自己觉得到了本节,作者将带领我们超越我们还有
待深入理解的oo,关于泛型的讨论在各个技术社区随处可见,对于初学者来说,很
少能看得懂这是在说什么?这无疑是个神秘的,令人恐惧的领域。然而,正如上节
一样。跟着作者的思路我们不难理解泛型设计给程序员带来的好处,在读完作者行
云流水般的讲述之后,回过头来看看自己真的理解了那些东西?结果是令人遗憾的
,在读到第六章之前,我对这块总是疑问不断,主要如下:

一些疑问

什么是泛型?

只是模板吗?至少在本节范围内看来几乎是这么回事,为了提高代码的通用性,将
数据类型参数化,让该代码的用户(本身也是编程者)决定类实例的数据类型,就
这么简单?就从模板设计后对编译速度的影响,我们就可以肯定显然不是这么简单
,以我们现在的水平无法真正回答这个问题,但是我们必须清楚的是:泛型设计不
等于模板设计和简单的实例化类和函数。而模板一定是泛型设计的基础。

泛型设计是完全一种高于oop的设计思想吗?

我的感觉好像不是,从过程到基于对象到面向对象的努力方向几乎都是数据处理的
相对独立性,考虑的是数据的相对安全,而我所理解的泛型设计(主要是模板的用
法),几乎只是为了代码的通用性,当然更大可能是我对泛型的理解本身是个错误
,可见在第一个问题没解决之前。这个问题无从谈起,但是对于这节内容来说,带
着几个笨问题离开,总比自以为是的态度更有价值。

应当理解的内容

当然,对于本节的内容,上面的那些牛角尖的事情不是完全必要做的。我们要掌握
的是模板最基本的用法,以及模板在实例化过程中的一些编译期的特点,总结要点
如下。

模板是对数据类型的参数化:

说得非专业点,就是拿数据类型作"变量",只不过在使用时,这些"变量"的值是类
型而不是数据。比如:

tenplate<class xxx> class A {…….};

int main() {

A<int> obj;

Return 1 ;

}

上面的xxx就是我所谓的"变量",理论上可以是你喜欢的任何标识符(当然
它得符合标识符的基本要求)。

模板实例化的过程:

关于这些,书中讲的非常详细,我唯一能说的是,要仔细看,特别是对p42的内容(
以第三版中文版为准)。

对于oo的模板支持,

在理解前面的内容的基础上,这几乎没什么难度,值得看看的是作者的代码。恐怕
在国内教材里没有什么地方能找到如此优雅,聪明的代码了。这本书中代码哪怕只
是抄一遍,也是好处不可估量的。呵呵。

笔记范围:2.6节

每个程序员都知道,在程序运行过程中,一些情况是不可预料的,无论程序的设计
看起来是多么完善,在某个特定的环境里同样会出错,但是这样的错误往往会有些
共同之处,比如new的时候也许会空间不足。显然这样的错误。不能完全说是程序设
计的问题,但是如果你的程序能对这些情况做出反应,那么这样的程序实用性就会
更强。异常机制正是为此而生。

注意,这里用了"机制"这个词,也就意味者规则,正如本节开头所说的:对于异常
各人都有自己的处理方式。比如就用if语句处理。本来这是没什么问题的,但是我
们生活在一个不幸的时代,程序的规模大得远远超出了个人能力能够接受的程度。
我们需要合作,因此统一的编码风格成了必要的事情了,这也是为什么程序员经常
告诉初学者编程是最不具备个性化的东西之一的一个原因,

使用异常机制还有一个好处。他可以大大减少代码的长度和规模。对于相同的异常
情况可以集中统一处理。这样说很空泛,我们来举一个小例子.,假设我们写个函数
分别为,int, char 的指针分配相同单位的空间,如不要异常机制,这样写:

bool new_space( int *&pev,char *&ch,int size ) {

pev = new int[ size ];

ch = new char[ size ];

if( !pev )

return false;

if( !ch )

retuen false;

return true;

}

这样的东西恐怕没人喜欢,因为如果我们这样调用他,new_space( p, c, 6 ),无
论空间分配成功与否。调用者都不知道( 问:你不会定义个bool量吗??答:会。
烦。我用别的 ),恩,得让我们的东西人性化点。修改如下:

bool new_space( int *&pev,char *&ch,int size ){

pev = new int[ size ];

ch = new char[ size ];

if( !pev ) {

cerr << "err1" <<endl;//在帮助系统里把err1说明成空间不足

return false;

}

if( !ch ) {

cerr << "err1" <<endl;

retuen false;

}

return true;

}

呵呵,这下对得起可爱的用户了吧?可是发现有点对不起自己。cerr << "err1" <
<endl; retuen false; 我写了两遍,这是两个指针,如果十个呢?天哪。老板,
加工资吧(回答你们一定知道),那好,试试异常机制,再改。

bool new_space( int *&pev,char *&ch,int size ){

try

{

string word ( "err1" );

pev = new int[ size ];

ch = new char[ size ];

if( !pev )

throw word;

if( !ch )

throw word;

}

catch( string err )

{

cerr << err << endl;

return false;

}

return true;

}

呵呵,这下比较舒服了。大家可能看到上面那函数好像并没有减少什么长度,yes,
但如果处理的参数不只两个,很多,你就会发现他省了你很多力气,

上面主要想说明异常机制的好处和用法(水平有限,可能例子并不好)在本节中
还有个重要的内容,那就是程序处理异常的方式和顺序。比如上面的程序,我们想
让它在当分配成功时显示ok,那么cout << "ok" << endl;写在那儿呢?如果我们
的try抛出的异常在函数里没有相应的catch怎么办呢?这些书上都说很详细。仔细
看吧,一定收获不少。

笔记范围:2.7节

对于大多数人而言,学习编程的第一步就是模仿,说的直接点就是从教材上抄几段
代码(比如那个知名的hello world)到机器上,然后慢慢的习惯,不错,这是一个
学习语言的好方法。但是这里有个问题,人们对于已经成为习惯的东西往往不会给
以思考,导致很长时间以后对于自己几乎每天都在写的句子却不知道它的具体涵义
,举个例子:只要你用的是c++,那么相信你每次都少不了写这样的句子:"using
namespace std;",很多第一次见到c++源程序的初学者多数会问:what is it?而
真正学了一段时间的人对于这个问题却没感觉了,然而真正该思考这个问题的却是
后者,这不能不说是这个学习方法的一个大弊端,这也是我一直坚持先搞清基本概
念再动手的原因。

正如我在上篇笔记中所说的:为了合作的需要使得编程成为了这个世界上最不具
备个性化的行为之一,因此程序员在命名组件的时候选择的名字往往都差不多。而
全局名字又不可避免,那么名字污染的概率也就非常大,理解了这些,相信对于na
mespac的概念的理解应该不是问题了,无非是改变了名字的可视性,当然,这不是
我们最关心的,我们更想知道的是如何用好它。这样就要解决下面两个个问题。

第一:如何定义?

书上给出一个比较明确的回答,形如这样:

namespace owl {

int x;

char y;

class obj { ….. };

void max( const int* );

}

但是这样的回答,我们并不是很满意,比如,我们都知道,为了减少编译时间和避
免重复函数定义。往往把函数的声明放在头文件中(abc.h),而把实现放在相应的
源文件中(abc.cpp)。那么名字空间该如何定义呢?难道一个"{"在abc.h,一个"
}"在abc,.cpp中吗?如果真的能这样,多个头文件又该如何呢?显然这是不可能的
,这个我们可以到8.5节中得到解释。现在我们只要提出这样的问题,并把它留在脑
子里,不要让这个概念成为麻木的习惯,

第二:怎么用它?

本节给出了三种用法,下面我们通过那个hello world 程序,作一个简单的比较。

1 using提示符:

#include

#include

using namespace std;

int main()

{

cout << "hello world!" << endl;

system( "pause" );

return 0;

}

呵呵。很舒服?是吧?是的。大多数人是这个用法,但是正如书上所说,这个做法
让名字空间形同虚设,我就遇到过这样的事情。有一次我写main()所在文件的时候
忘了写using提示符,编译器居然给我通过了。当时真的吓了一跳的。后来才发现我
include的文件已经写了。可见这个写法的弊端,万一我在不想用此名字空间的时候
include了该文件,后果是有点麻烦的,当然对于std来说,这样的忧虑有些多余了
,因为没人会去和ISO抢名字(恩,某些特殊人士除外)

2. 名字空间修饰符

#include

#include

int main()

{

std::cout << "hello world!" << std::endl;

system( "pause" );

return 0;

}

这个写法好是好。就是太烦,每次都得写std ,程序越长写的越多,老板又不加工
资,咱不干。(什么?设别名?namespac A = std; ? 你干吧。咱还是不干)

3 using声明。

#include

#include

using std::cout;

using std::endl;

int main()

{

cout << "hello world!" << endl;

system("pause");

return 0;

}

恩。只声明要用的,这样不错。尽管多写了些,程序越长写的越值,呵呵。等等,
这样的话。如果声明在头文件中,岂不是又回到第一个问题上去了,可以再改下,

#include

#include

int main()

{

using std::cout;

using std::endl;

cout << "hello world!" << endl;

system("pause");

return 0;

}

啊哈,妙极了,using 可以进一步改变名字的域,这个时候你肯定会问,using提示
符可以这样用吗? 如果能,岂不更省事?很遗憾,不能的,你在8.5节中会找到答案


2.8节

从本章开始,我们就一直沿着一条主线前进,跟着作者设计了一个比较完整的arra
y类,从设计的角度来说,(当然除了库的实现者外)我们没有必要花那么多时间在
实现数据结构上,只要调用各种库,我们就可以把自己精力放在更重要的设计上了
,在今天,对于库的使用,几乎成了程序设计能力的重要指标之一,作为初学者,
首先要学的当然是已经和语言合为一体的标准库了,而在所有的数据结构中,用的
最多的无疑是线性表了。本书的标准库描述当然也从这里开始,

本节的内容这章来说算是最简单的了,vector的一般用法和内置数组几乎没什么差
别,初学者很容易理解,当然个人认为。如果仔细看的话还是有些东西值得注意和
思考的,下面将其列出:

一.标准库的设计理念。

正如作者提到的:vector并不是像前几节中设计array那样提供一个大而全的方法集
合,而是提供了最小的接口集合,把那些具有通用性的方法单独组成泛型算法。可
以使用在各种数据结构上,这个思想正好反映了作者在本章开头提出的问题,设计
一个类,是要尽量让他成为语言的一等公民,而不是设计一个面面俱到的东西。

二.对于iterator的初步认识,

这个也许是本节唯一的难点了,迭代器?很多初学者对这个陌生的术语不理解。但
他几乎是这个泛型算法的基础,因此我们现在至少要对于有些初步的认识,以保证
下面的阅读,为了达到这个目的,我们仔细的看了下书中的例子、不难发现他与指
针的用法几乎完全相同。因此事实上,你就可以把他当成指针的一种类模板,当你
读到十五章重载->的时候你就可以理解这样做的好处了,另外还有一些特殊的iter
ator,比如begin().end().书上都有详细的说明,仔细看看,我的建议是不求全解
,但起码得会模仿,

一些题话。

本章结束了,我们对于c++也有了一个比较整体的理解了,下面我想对于一些朋友对
我的笔记的一些建议,作一个简单的回答,到现在为止我写的东西,都是一些人人
都知道的东西,作为真正的笔记是罗索了点,正如这两章的目的想让初学者有个语
言的整体理解一样,我的这些笔记的根本目的也是尽可能的向各位初学c++的朋友推
荐这本好书,和让大家习惯作者的描述方式的思路,因此我评论了些书以外的东西
,对比下,以便更好的推荐这本书。作为学生,我对于一切教育工作者(比如谭浩
强老师)都是尊敬的。但是这本书的确比他们写的好些,

从下章开始,让笔记回归本来的面貌把。我不再严格按照小节来写他了。毕竟这本
书我已经看完了。我将根据具体内容。把整本书中前后提到的相关内容结合起来,
把我自己完整的理解写出来。当然这样做会暴露出更多的问题。希望大家给我更多
的指点,谢谢大家的支持,尤其饮水思源和水木清华及复旦光华站的各位朋友

第二章 一些基本概念

关于指针的一些讨论

距离本笔记的上一篇已经过了很长时间了,很多人问我为什么不写了,其实理由很简
单,我写不出什么东西,正如上篇笔记所说的在这之前的东西只是为了向朋友们推
荐一本好书以及帮助初学者熟悉作者的思考习惯,这个很容易,但也很肤浅,这样
的东西是不合适写的太多的(地球人都知道的东西,还是看书为好)。这就意味着
必须选择话题来写(当然也大大增加了笔者犯错的机会)。上面这些话算是对一直
支持我的朋友们做个解释,也希望再得到你们的支持。

在本篇文字中,我选择的话题是指针,是的,我已经听到很多人开始抱怨这个麻烦
的该死的东西,但是我不得不说离开了这个东西(那个麻烦的该死的)你很难干得
成什么事情,对于c/c++程序员来说这是个不可能回避的问题,那么我们最好还是看
看这个东西到底麻烦在那儿,这样比抱怨更能解决问题。

第一,指针的定义和初始化:

在本书的3.3节在这方面作了详细的描述,很简单,指针是一种间接操作对象的方式
,指针中存放的是所操作对象在内存中的地址,但请注意,如果指针表示的仅仅是
地址的话,事情就好办得多,但是指针还必须与他表示的对象的类型保持一致,就
象本书p73的例子:

int *p = NULL;"

double dval = 3.14;

p = &dval; // error

不是p物理上不能存放dval的地址,而是两种类型的内存布局和内容的解释完全不同
(对编译器而言),编译器只好从一开始就拒绝这种赋值形式,当然,从你开始看
上面的代码的那一刻,我就知道你不以为然了,一个学过编程的人是不会犯这种低
级错误的,真的是这样吗?那么好,我想有时候你会希望对指针直接赋个地址,你
是否会这样写?

int *p = 0x00001010;

然后,编译器就开始骂人了,"0x00001010是那个对象的地址?什么类型?一定是i
nt吗?你让我咋解释他呢?重写!"呵呵,也难怪他脾气这么大,你这个地址根本就
没有告诉编译器类型信息嘛,当然如果你非要这样做,要么告诉他你需要这个地址
的类型:

int *p = (int*)0x00001010;

要么干脆先不管类型的事情:"

void *p = 0x00001010;

但不管你选那个方式,cast总是免不了,建议少用。

第二,野指针:

当指针赋值的时候,编译器会检查地址的类型信息,这很好,但赋值之后他就不管
了,这个正是一切麻烦的根源,比如有个典型的代码:

#include <iostream>

#include <cstdlib>

using namespace std;

int*f()

{

int a = 0;

return &a;

}

int g()

{

double a;

cin >> a;

return 0;

}

int main()

{

int *p = f();

cout << *p << endl;

g();

cout << *p << endl;

system("pause");

return 0;

}

我们发现编译器没有报错,而p中存放的地址也没被改变,但两次提领的结果完全不
同,这是因为一旦函数f结束后,局部对象a进入了栈粉碎机,但是,请注意,栈粉
碎机除去的不是该对象的地址编号和数据。而是该对象地址的类型标志(当然这是
编译器行为,和纯内存无关)。而指针则仍然按照之前的约定对该对象地址进行提
领等操作,一旦该对象地址被改变了类型信息,就必然错误。一个指针指向的对象
的类型信息被改变或者被消除,从而使得指针操作的结果不可预测,就成为野指针


第三,内存泄漏:

这个问题跟堆上分配有直接的关系,首先,我们要明白什么是内存泄漏,下面这个
函数f内存泄漏了吗?

int*f()

{

int *p = new(int);

return p;

}

int main()

{

int *p = f();

*p = 11;

cout << *p << endl;

system("pause");

delete p;

return 0;

}

我们还是引用《effective c++》里的一段话来回答这个问题:

"引起内存泄露的原因在于内存分配后指向内存的指针丢失了。如果没有垃圾处理或
其他语言之外的机制,这些内存就不会被收回。"

显然我们没有丢掉控制该地址的指针,被分配的内存仍掌握在我们手里。只不过要
相当注意罢了。特别是当有异常出现的时候,不要忘记在程序退出之前delete这个
内存。(当然有时候auto_ptr也是个不错的选择)

///////////////////////////////////////////////////////////////////////////////////////////////////////

2.1.1 main() 函数

预处理编译指令 #include 和 编译指令 using namespace. ???

通常,main() 被启动代码调用,而启动代码是编译器添加到程序中的,是程序和操作系统之间的桥梁。事实上,函数头 int main()描述的是main()和操作系统之间的接口。

在Windows 编程中,动态链接库(DLL)模块不是独立的程序,因此不需要 main(),而用与专用环境的程序---如机器人中的控制器芯片---可能不需要main()。但常规的独立程序都需要main()。

2.1.3 C++与处理器和iostream文件

#include <iostream> // a Preprocessor directive

该编译指令导致预处理器将iostream文件的内容添加到程序中。这是一种典型的预处理器操作:在源代码被编译之前,替换或添加文件。实际上,iostream文件的内容将取代程序中的代码行 #include <iostream>。原始文件没有被修改,而是江源代码文件和 iostream 组合成一个符合文件,编译的下一个阶段将是使用该文件。

2.1.4 头文件名

像 iostream 这样的文件叫做包含文件 (include file) ---由于他们被包含在其它文件中: 也叫做头文件 (header file) ---由于它们被包含在起始处。C++编译器自带了很多头文件,每个头文件都支持一组特定的工具。C语言的传统是,头文件使用扩展名 h, 将其作为一种通过名称表示文件类型的简单方式。最初C++也是这样做的,不过,最近,C++的用法发生了变化。现在,对老式C的头文件保留了扩展名h(C++ 程序仍可以使用这种文件),而C++头文件则没有扩展名。ANSI/ISO委员会在头文件使用那种扩展名(.hx 或 .hxx)产生分歧,所以最终他们一致同意不使用任何扩展名(-_”)。

2.15 名称空间

如果使用 iostream,而不是 iostream.h,则应该使用名称空间编译指令来使 iostream 中的定义对程序可用:

using namespace std;

这叫做 using 编译指令 (directive)。

名称空间支持是C++ 中一项比较新的特征,它是为了使编写将多个厂商已有的代码组合起来的程序更简单而设计的。名称空间让厂商能够将其产品封装在一个叫做名称空间的单元中,这样就可以使用名称空间的名称来指出想使用那个厂商的产品。如:

Microflop::wanda (“go dancing”); //use Microflop namespace version

Piscine::wanda (“a fish named Desire”); //use Piscine namespace verion

按这种方式,类、函数和变量便是 C++ 编译器的标准组件,他们现在都被放置在名称空间 std 中,仅当头文件没有扩展名 h 时。这意味着在 iostream 中定义的、用于输出的 cout 变量实际上是 std::cout, 而 endl 实际上是 std::endl.因此,可以省略 using 编译指令,以下述方式进行编码:

std::cout << “Come up and C++ me some time”;

std::cout << std::endl;

当然,亦可以使用 using 编译指令:

using namespace std;

更好的方法是,只使所需的名称可用,这可以通过使用 using 声明来实现:???

using std::cout; //make cout available

using std::endl; //make endl available

using std::sin; //make cin available

2.1.6使用 cout 进行 C++ 输出

cout << “Come up and C++ me some time”;

cout 是一个预定义的对象,知道如何显示字符串、数字和单个字符等。于是,我们可以体会到对象的长处之一---不了解对象的内部情况,就可以使用它,只需要知道它的接口。cout 对象有一个简单的接口,如果string 是一个字符串,则下面的代码将显示该字符串:

cout << string;

以上两个语句实际上是将一个字符串插入到输出流中。<<是 cout的对象属性的一员,是一个操作符重载的例子。

诸如 endl等对于 cout 来说有特殊函数的特殊符号被称为控制符 (manipulator)。

换行符/n是一种被称为 “转义序列”的按键组合。

2.1.7 C++ 源代码的格式化

在 C++ 中,回车的作用就和空格或制表符相同。也就是说,在 C++ 中,通常可以在能够使用回车的地方使用空格,反之亦然。

一行代码中不可分割的元素叫做标记 (token)。通常,必须用空格、制表符或回车将两个标记分开,空格、制表符和回车统统称为空白(white space)。

2.2 C++ 语句

编译器负责分配和标记内存的细节。C++ 可以处理多种类型的数据,而 int 是最基本的数据类型。在C 和 Pascal 中,所有的变量声明通常都位于函数或过程的开始位置,但 C++ 没有限制,实际上,C++ 通常的做法是,在首次使用变量前声明它。

2.2.3 cout 的新花样

看如下语句:

carrots = 25;

cout << carrots;

首先, cout 将 carrots 替换为其当前值 25;然后,把值转换为合适的输出字符。

记住,cout 可用于数字和字符串。但整数25与字符串,”25”有天壤之别。字符串存储的是书写该数字时使用的字符,即字符2和5。程序在内部存储的是字符2和5的编码。要打印字符串,cout 只需打印字符串中各个字符即可。单证书25被存储为数值,计算机不是单独存储每一个数字,而是将25存储为二进制。在打印之前,cout必须将整数形式的数字转换为字符串形式。另外,cout很聪明,知道carrots是一个需要转换的整数。与老式printf区别在于cout 的聪明程度,如printf必须用特殊代码 (%s 和 %d)来指出是要打印字符串还是整数。Cout的智能行为源自于C++的面向对象特征。

实际上, C++ 插入操作符 (<<) 将根据气候的数据类型相应的调整其行为,这是一个操作符重载的例子。另外,cout 是可扩展的 (extensible)。也就是说,可以重定义 << 操作符,使 cout能够识别和显示所开发的新数据类型。

2.3 其他 C++ 语句

输入时,cin 使用 >> 操作符从输入流中抽取字符。

2.3.3 类简介

C++ 之所以有如此的吸引力,很大程度上是由于存在大量支持 UNIX、Macintosh和 Windows 编程的类库。

C++提供了两种发消息的方式:一方式是重定义操作符,另一种是使用类方法。cout 和 cin 使用的是前者。

参考出处:

http://blog.csdn.net/yitaohust/archive/2005/11/27/537913.aspx

http://www.1to2.us/C-Primer-Plus-(1-2-)-a167795.htm
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: