C++ Primer 阅读小结

一、变量和基本类型

         类型是C++程序设计的基础。

         每种类型都定义了其存储空间要求和可以在该类型的所有对象上执行的操作。C++提供了一组基本内置类型,如int、char等。这些类型与它们在机器硬件上的标示方式紧密相关。

         类型可以为const或非const;const对象必须要初始化,且其值不能被修改。另外,我们还可以定义复合类型,如引用。引用为对象提供了另一个名字。复合类型是用其他类型定义的类型。

         C++语言支持通过定义类来自定义类型。标准库使用类设施来提供一组高级的抽象概念,如IO和string类型。

         C++是一种静态类型语言:变量和函数在使用前必须先声明。变量可以声明多次但是只能定义一次。定义变量时就进行初始化几乎总是个好主意。

 

二、标准库类型

         C++标准库定义了几种更高级的抽象数据类型,包括string和vector类型。String类型提供了变长的字符串,而vector类型则可用于管理同一类型的对象集合。

         迭代器实现了对存储于容器对象的间接访问。迭代器可以用于访问和遍历string类型和vector类型的元素。

 

三、数组和指针

         数组和指针所提供的功能类似于标准库的vector类与string类和相关的迭代器。我们可以把vector类型理解为更灵活、更容易管理的数组,同样,string是C风格字符串的改进类型,而C风格字符串是以空字符结束的字符数组。

         迭代器和指针都能用于间接地访问所指向的对象。Vector类型所包含的元素通过迭代器来操纵,类似地,指针则用于访问数组元素。尽管道理都很简单,但在实际应用中,指针的难用是出了名的。

         某些低级任务必须使用指针和数组,但由于使用指针和数组容易出错而且难以测试,应尽量避免使用。一般而言,应该优先使用标准库抽象类而少用语言内置的低级数组和指针。尤其是应该使用string类型取代C风格以空字符结束的字符数组。现代C++程序不应使用C风格字符串。

 

四、表达式

         C++提供了丰富的操作符,并定义了用于内置类型值时操作符的含义。除此之外,C++还支持操作符重载,允许由程序员自己来定义用于类类型时操作符的含义。

         要理解复合表达式(即含有多个操作符的表达式)就必须先了解优先级、结核性以及操作数的求职次序。每一个操作符都有自己的优先级别和结合性。优先级规定复合表达式中操作符结合的方式,而结合性则决定同一个优先级的操作符如何结合。

         大多数操作符没有规定其操作数的求职顺序:由编译器自由选择先计算左操作数还是右操作数。通常,操作数的求职顺序不会影响表达式的结果。但是,如果操作符的两个操作数都与同一个对象相关,而且其中一个操作数改变了该对象的值,则程序将会因此而产生严重的错误——而且这类错误很难发现。

         最后,可以使用某种类型编写表达式,而实际需要的是另一类型的值。此时,编译器自动实现类型转换(既可以是内置类型也可以是为类类型而定义的),将特定类型转换为所需的类型。C++还提供了强制类型转换显式地将数值转换为所需的数据类型。

 

五、语句

         C++提供了种类相当有限的语句,其中大多数都会影响程序的控制流:

         while、for以及do while语句,实现反复循环;

         if和switch,提供条件分支结构;

         continue,终止当前循环;

         break,退出一个循环或switch语句;

         goto,将控制跳转到某个标号语句;

         try、catch语句,实现try块的定义,该块语句包含一个可能抛出异常的语句序列,catch子句则用来处理在try块里抛出的异常;

         throw表达式,用于退出代码块的执行,将控制转移给相关的catch子句。

         此外,C++还提供表达式语句和声明语句。表达式语句用于求解表达式。

 

六、函数

         函数是有名字的计算单元,对程序(就算是小程序)的结构化至关重要。函数的定义由返回类型、函数名、形参表(可能为空)以及函数体组成。函数体是调用函数时执行的语句块。在调用函数时,传递给函数的实参必须与相应的形参类型兼容。

         给函数传递实参遵循变量初始化的规则。非引用类型的形参以相应实参的副本初始化。对(非引用)形参的任何修改仅作用于局部副本,并不影响实参本身。

         复制庞大而复杂的值有昂贵的开销。为了避免传递副本的开销,可将形参指定为引用类型。对引用形参的任何修改会直接影响实参本身。应将不需要修改相应实参的引用形参定义为const引用。

         在C++中,函数可以重载:内联函数和成员函数。将函数指定为内联是建议编译器在调用点直接把函数代码展开。内联函数避免了调用函数的代价。成员函数则是身为类型成员的函数。

 

七、标准IO库

         C++使用标准库类处理输入和输出:

         ●iostream类处理面向流的输入和输出。

         ●fstream类处理已命名文件的IO。

         ●stringstream类处理内存中字符串的IO。

         所有的这些类都是通过继承相互关联的。输入类继承了istream,而输出类则继承了ostream。因此,可在istream对象上执行的操作同样适用于ifstream或istringstream对象。而继承ostream的输出类也是类似的。

         所有IO对象都有一组条件状态,用来指示是否可以通过该对象进行IO操作。如果出现了错误(例如遇到文件结束符)对象的状态将标志无法再进行输入,直到修正了错误为止。标准库提供了一组函数设置和检查这些状态。

 

八、顺序容器

         C++标准库定义了一系列顺序容器类型。容器是用于存储某种给定类型对象的模板类型。在顺序容器中,所有元素根据其位置排列和访问。顺序容器共享一组通用的已标准化的接口:如果两种顺序容器都提供某一操作,那么该操作具有相同的接口和含义。所有容器都提供(有效的)动态内存管理。程序员在容器中添加元素时,不必操心元素存放在哪里。容器自己实现其存储管理。

         最经常使用的容器类型是vector,它支持对元素的快速随机访问。可高效地在vector容器尾部添加和删除元素,而在其他任何位置上的插入或删除运算则要付出比较昂贵的代价。deque类与vector相似,但它还支持在deque首部的快速插入和删除运算。list类只支持元素的顺序访问,但在list内部任何位置插入和删除元素都非常快速。

         容器定义的操作非常少,只定义了构造函数、添加或删除元素的操作、设置容器长度的操作以及返回指向特殊元素的迭代器的操作。

         在容器中添加或删除元素可能会使已存在的迭代器失效。当混合使用迭代器操作和容器操作时,必须时刻留意给定的容器操作是否会使迭代器失效。许多使一个迭代器失效的操作,例如insert或erase,将返回一个新的迭代器,让程序员保留容器中的一个位置。使用改变容器长度的容器操作的循环必须非常小心其迭代器的使用。

 

九、关联容器

         关联容器的元素按键排序和访问。关联容器支持通过键高效地查找和读取元素。键的使用,使关联容器区别于顺序容器,顺序容器的元素是根据位置访问的。

         map和multimap类型存储的元素是键-值对。它们使用在utility头文件中定义的标准库pair类,来表示这些键-值对元素。对map或multimap迭代器进行解引用将获得pair类型的值。pair对象的first成员是一个const键,而second成员则是该键所关联的值。set和multiset类型则专门用于存储键。在map和set类型中,一个键只能关联一个元素。而multimap和multiset类型则允许多个元素拥有相同的键。

         关联容器共享了顺序容器的许多操作。除此之外,关联容器还定义了一些新的操作,并对某些顺序容器同样提供的操作重新定义了其含义或返回类型,这些操作的差别体现了关联容器中键的使用。

         关联容器的元素可用迭代器访问。标准库保证了迭代器按照键的次序访问元素。begin操作将获得拥有最小键的元素,对此迭代器做自增运算则可以按非降序依次访问各个元素。

 

十、泛型算法

         C++标准化过程做出了更重要的贡献之一是:创建和扩展了标准库。容器和算法库是标准库的基础。标准库定义了超过一百个算法。幸运的是,这些算法具有相同的结构,是他们更易于学习和使用。

         算法与类型无关:它们通常在一个元素序列上操作,这些元素可以存储在标准库容器类型、内置数组甚至是生成的序列(例如读写流程所生成的序列)上。算法基于迭代器操作,从而实现类型无关性。大多数算法使用一对指定元素范围的迭代器作为其头两个参数。其他的迭代器实参包括指定输出目标的输出迭代器,或者用于指定第二个输入序列的另一个或一对迭代器。

         迭代器可通过其所支持的操作来分类。标准库定义了五种迭代器类别:输入、输出、前向、双向和随机访问迭代器。如果一个迭代器支持某种迭代器类别要求的运算,则该迭代器属于这个迭代器类别。

         正如迭代器根据操作来分类一样,算法的迭代器形参也通过其所要求的迭代器操作来分类。只需要读取其序列的算法通常只要求输入迭代器的操作。而写目标迭代器的算法则通常只要求输出迭代器的操作,依此类推。

         查找某个值的算法通常提供第二个版本,用于查找使用谓词函数返回非零值的元素。对于这种算法,第二个版本的函数名字以_if后缀标识。类似地,很多算法提供所谓的复制版本,将(修改过的)元素写到输出序列,而不是写回输入范围。这种版本的名字以_copy结束。

         第三种模式是考虑算法是否对元素读、写或者重新排序。算法从不直接改变它所操纵的序列的大小。(如果算法的实参是插入迭代器,则该迭代器会添加新元素,但算法并不直接这么做。)算法可以从一个位置将元素复制到另一个位置,但不能直接添加或删除元素。

 

十一、类

         类是C++中最基本的特征,允许定义新的类型以适应应用程序的需要,同时使用程序更短且更易于修改。

         数据抽象是指定义数据和函数成员的能力,而封装是指从常规访问中保护类成员的能力,它们都是类的基础。成员函数定义类的接口。通过将类的实现所用到的数据和函数设置为private来封装类。

         类可以定义构造函数,它们是特殊的成员函数,控制如何初始化类的对象。可以重载构造函数。每个构造函数应初始化每个数据成员。初始化列表包含的是名-值对,其中的名是一个成员,而值则是该成员的初始值。

         类可以将对其非public成员的访问权授予其他类或函数,并通过将其他的类或函数设为友元来授予其访问权。

         类也可以定义mutable或者static成员。mutable成员永远都不能为const:它的值可以在const成员函数中修改。static成员可以是函数或数据,独立于类类型的对象而存在。

 

十二、复制控制

         类除了定义该类型对象上的操作,还需要定义复制、赋值或撤销该类型对象的含义。特殊成员函数(赋值构造函数、赋值操作符和析构函数)可用于定义这些操作。这些操作统称为“复制控制”函数。

         如果类没有定义这些操作中的一个或多个,编译器将自动定义它们。合成操作执行逐个成员初始化、赋值或撤销:合成操作依次取得每个成员,根据成员类型进行成员的复制、赋值或撤销。如果成员为类类型的,合成操作调用该类的相应操作(即,复制构造函数调用成员的复制构造函数,析构函数调用成员的析构函数,等等)。如果成员为内置类型或指针,则直接复制或赋值,析构函数对撤销内置类型或指针类型的成员没有影响。如果成员为数组,则根据元素类型以适当方式复制、赋值或撤销数组中的元素。

         与复制构造函数和赋值操作符不同,无论类是否定义了自己的析构函数,都会创建和运行合成析构函数。如果类定义了析构函数,则在类定义的析构函数结束之后运行合成析构函数。

         定义复制控制函数最为困难的部分通常在于认识到它们的必要性。

         分配内存或其他资源的类几乎总是需要定义复制控制成员来管理所分配的资源。如果一个类需要析构函数,则它几乎也总是需要定义复制构造函数和赋值操作符。

 

十三、重载操作符与转换

         通过定义内置操作符的重载版本,我们可以为自己的类型(即,类类型或枚举类型)的对象定义同样丰富的表达式集合。重载操作符必须具有至少一个类类型或枚举类型的操作数。应用于内置类型时,重载操作符与对应操作符具有同样数目的操作数、同样的结合性和优先级。

         大多数重载操作符可以定义为类成员或普通非成员函数,赋值操作符、下标操作符、调用操作符和箭头操作符必须为类成员。操作符定义为成员时,它是普通成员函数。具体而言,成员操作符有一个隐式this指针,该指针一定是第一个操作数,即,一元操作符唯一的操作数,二元操作符的左操作数。

         重载了operator()(即,函数调用操作符)的类的对象,称为“函数对象”。这种对象通常用于定义与标准算法结合使用的谓词函数。

         类可以定义转换,当一个类型的对象用在需要另一个不同类型对象的地方时,自动应用这些转换。接受单个形参且未指定为explicit的构造函数定义了从其他类型到类类型的转换,重载操作符转换函数则定义了从类类型到其他类型的转换。转换操作符必须为所转换类型的成员,没有形参并且不定义返回值,转换操作符返回操作符所具有类型的值,例如,operator int返回int。

         重载操作符和类类型转换都有助于更容易、更自然地使用类型,但是,必须注意避免设计对用户而言不明显的操作符和转换,而且应避免定义一个类型与另一类型之间的多个转换。

 

十四、面向对象编程

         继承和动态绑定的思想,简单但功能强大。继承使我们能够编写新类,新类与基类共享行为但重定义必要的行为。动态绑定使编译器能够在运行时根据对象的动态类型确定运行函数的哪个版本。继承和动态绑定的结合使我们能够编写具有特定类型行为而又独立于类型的程序。

         在C++中,动态绑定仅在通过引用或指针调用时才能应用于声明为虚的函数。C++程序定义继承层次接口的句柄类很常见,这些类分配并管理指向继承层次中对象的指针,因此能够使用户代码在无须处理指针的情况下获得动态行为。

         继承对象由基类部分和派生类部分组成。继承对象是通过在处理派生部分之前对对象的基类部分进行构造、复制和赋值,而进行构造、复制和赋值的。因为派生类对象包含基类部分,所以可以将派生类型的引用或指针转换为基类类型的引用或指针。

         即使另外不需要析构函数,基类通常也定义一个虚析构函数。如果经常会在指向基类的指针实际指向派生类对象时删除它,析构函数就必须为虚函数。

 

十五、模板与泛型编程

         模板是C++语言与众不同的特性,是标准库的基础。模板是独立于类型的蓝图,编译器可以用它产生多种特定类型的实例。我们只需编写一次模板,编译器将为使用模板的不同类型实例化模板。既可以编写函数模板又可以编写类模板。

         函数模板是建立算法库的基础,类模板是建立标准库容器和迭代器类型的基础。

         编译模板需要编程环境的支持。语言为实例化模板定义了两个主要策略:包含模型和分别编译模型。这些模型规定了模板定义应该放在头文件还是源文件中,就此而言,它们影响着构建系统的方式。现在,所有编译器实现了包含模型,只有一些编译器实现了分别编译模型。编译器的用户指南应该会说明系统怎样管理模板。

         显式模板实参使我们能够固定一个或多个模板形参的类型或值。显式实参使我们能够设计无需从对应实参推断模板类型的函数,也使我们能够对实参进行转换。

         模板特化是一种特化的定义,它定义了模板的不同版本,将一个或多个形参绑定到特定类型或特定值。对于默认模板定义不适用的类型,特化非常有用。

 

十六、用于大型程序的工具

         C++的大部分特征都可以应用于范围很广的问题——从几小时便可解决的问题到大团队花几年时间才能解决的问题。其中有一些特征则最适用于大规模问题的情况,这些特征包括异常处理、命名空间、多重继承和虚继承。

         通过异常处理我们能够将程序的错误检测部分与错误处理部分分开。在抛出异常的时候,会终止当前正在执行的函数并开始查找最近的catch子句,在查找catch子句的时候,作为异常处理的一部分,将撤销退出函数内部定义的局部变量。这种撤销对象提供了一个重要的编程技术,称为“资源分配即初始化”(RAII)。

         命名空间是一种机制,用于管理用独立供应商开发的代码建立的大型复杂应用程序。一个命名空间就是一个作用域,其中可以定义对象、类型、函数、模板和其他命名空间。标准库就定义在名为std的命名空间中。

         通过using声明,当前作用域中就都可以访问某个命名空间中的名字了。当然,也可以通过using指示将一个命名空间中的所有名字带入当前作用域,但这种做法很不安全。

         从概念上来看,多重继承很简单:派生类可以继承多个直接基类,派生类对象由派生部分和每个基类所贡献的基类部分构成。虽然多重继承概念简单,但细节可能非常复杂,尤其是,继承多个基类引入了新的名字冲突可能性,并且会导致对对象基类部分中的名字的引用出现的二义性。

         一个类继承多个直接基类的时候,那些类有可能本身还共享另一个基类。在这种情况下,中间类可以选择使用虚继承,声明愿意与层次中虚继承同一基类的其他类共享虚基类。用这种方法,后代派生类中将只有一个共享虚基类的副本。

 

十七、特殊工具与技术

         C++讲述了几个专门针对一些特定问题的特殊设施。

         类的自定义内存管理有两种方式:定义自己的内部内存分配,以简化自己的数据成员的分配;定义自己的、类特定的operator new和operator delete函数,在分配类类型的新对象时使用它们。

         一些程序需要在运行时直接询问对象的动态类型。运行时类型识别(RTTI)为这类程序设计提供了语言级支持。RTTI只适用于定义了虚函数的类,没有定义虚函数的类型的类型信息是可用的但反映静态类型。

         普通对象的指针都是有类型的。定义类成员的指针的时候,指针类型必须也封装指针所指向的类的类型。可以将成员指针绑定到具有相同类型的任意类成员,引用成员指针的时候,必须指定从中获取成员的对象。

         C++还定义了另外几个聚集类型:

         ●嵌套类,它是在另一个类的作用域中定义的类,这样的类经常定义为其外围类的具体实现类。

         ●联合,是只能包含简单数据成员的一种特殊类。union类型的对象在任意时刻只能为它的一个数据成员定义值。联合经常嵌套在其他类类型的内部。

         ●局部类,是局部于函数而定义的非常简单的类。局部类的所有成员必须定义在类定义体中,局部类没有静态数据成员。

         C++还支持几种固有的不可移植的特征,包括位域和volatile(它们可使与硬件接口更容易)以及链接指示(它使得与其他语言编写的程序接口更容易)。

posted @ 2013-07-24 00:49  iak  阅读(391)  评论(0编辑  收藏  举报