《C++ Primer 第三版》是一本不错的书,下面将自己在读书笔记贴出来,请大家批评指正(呵呵,此书以就没有看完,现在拾起来再读读,又感收获不少。
一、第一章 概述
1.1预处理器
using namespace std; 一定要记住使用标准库的命名空间
预处理器指示符用# 号标识处理
这些指示符的程序被称为预处理器(preprocessor)( 通常捆绑在编译器中)。
#ifndef BOOKSTORE_H
#define BOOKSTORE_H /* Bookstore.h 的内容*/
#endif
BOOKSTORE_H是一个预编译器常量
只要不存在“两个必须包含的头文件要检查一个同名的预处理器常量”这样的情形这个策略就能够很好地运作。
编译C++程序时,编译器自动定义了一个预处理器名字__cplusplus
编译标准C 时,编译器将自动定义名字__STDC__
另外两个比较有用的预定义名字是:__LINE__和__FILE__。
__LINE__记录文件已经被编译的行数;
__FILE__包含正在被编译的文件的名字。
另外两个预定义名字分别包含当前被编译文件的
编译时间(__TIME__ ),时间格式为hh:mm:ss
和日期(__DATE__ ),month day year
assert()是C 语台标准库中提供的一个通用预处理器宏。在代码中常利用assert()来判断一个必需的前提条件以便程序能够正确执行。
1.2输入输出
#include <iostream>
标准输入输出:
终端输入也被称为标准输入(standard input),cin
向终端输出也被称为标准输出(standard output),cout
cerr称为标准错误(standard error) ,cerr 通常用来产生给程序用户的警
告或错误信息。
除了显式地使用换行符外“\n”我们还可以使用预定义的iostream 操纵(manipulator )endl
文件的输入输出:
iostream库也支持文件的输入和输出
所有能应用在标准输入和输出上的操作符也都可以应用到已经被打开的输入或输出或两者兼有文件上为了打开一个文件供输入或输出除了iostream 头文件外还必须包含头文件:
#include <fstream>
二、第二章 浏览
2.1数组
C++为基本算术数据类型(如整数类型)提供了内置的支持
C++还支持布尔类型以及用来存放字符集中单个元素的字符类型
C++为算术数据类型提供了赋值一般算术运算以及关系运算的内置支持
C++不支持数组的抽象abstraction ,也不支持对整个数组的操作。数组类型本身没有自我意识,它不知道自己的长度。
标准库还支持基本类抽象的组合(如字符串和复数)
在内置数据类型与标准库类的类型之间是复合类型(compound type) 特别是指针和数组类型
2.2动态内存分配和指针
在C++中对象可以静态分配和动态分配
静态内存分配是在程序执行之前进行的因而效率比较高,缺乏灵活性。
存储未知数目的元素需要动态内存分配的灵活性。
char* c = new char;
Array* array = new Array [100];
当用完了动态分配的对象或对象的数组时我们必须显式地释放这些内存!!!
delete c;
delete []array;
2.3基于对象的设计
(Remember, a class declaration is simply a logical construct that does not have physical reality.)
1.const
需要注意const修饰符的作用,
在const value中的作用
在const Member function中的作用:
public:
Date( int mn, int dy, int yr );
int getMonth() const; // A read-only function
void setMonth( int mn ); // A write function;
C++不允许成员函数与数据成员共享同一个名字
2.inline
一般来说函数调用比直接访问内存的开销要大得多,因而信息隐藏是否给程序的执行效率增加了严重的额外负担:
答案是不,C++提供的解决方案是内联函数inline function 机制,内联函数在它的调用点上被展开。一般来说内联函数不会引入任何函数调用。
for ( int index = 0; index < array.size(); ++index )
àfor ( int index = 0; index < array.size; ++index)
3.constructor
程序设计中的一个常见错误是:使用事先并没向被正确初始化的对象实际上这是一个极为常见的错误。所以C++为用户定义的类提供了一种自动初始化机制类构造函数(classconstructor)
可以给类定义多个构造函数尽管它们都具有相同的名字但只要编译器能够根据参数表区分它们就行。
注意在进行构造的时候,需要注意避免两个错误:
1.提供给程序的内存是否够用(内存不是无限的)。
2.传递给构造函数的参数必须是有效的(构造函数内进行判断),提高接口的健壮性。
4.destructor
每个类对象在被程序最后一次使用之后它的析构函数就会被自动调用,一般地析构函数会释放在类对象使用和构造过程中所获得的资源。
注意:
构造函数和析构函数是程序员提供的函数,它们既不构造也不销毁类的对象(编译器自动把它们作用到这些对象上),因此构造函数constructor 和析构函数destructor 这两个词多少有些误导。
5.operator overloading
C++语言支持操作符重载operator overloading ,这样就可以为特定的类类型定义新的操作符实例
类提供一个或多个赋值操作符、等于操作符,可能还有一个或多个关系操作符以及iostream 输入和输出操作符。
2.4 面向对象的设计
1 public, private, protected 三种访问级别:
1.派生类与基类只共享公共的接口。
2.如果希望防止派生类直接访问某个成员,我们就把该成员声明为基类的private 成员。如果确信某个成员提供了派生类需要直接访问的操作或数据存储,而且通过这个成员派生类的实现会更有效则我们把该成员声明为protected。
2 虚拟函数:
对于一个非虚拟函数的调用编译器在编译时刻选择被调用的函数而虚拟函数调用的决定则要等到运行时刻。在执行程序内部的每个调用点上,系统根据被调用对象的实际基类或派生类的类型来决定选择哪一个虚拟函数实例。
需要注意的是虚拟函数不能够使用内联inline。
3 派生类的构造函数:
派生类对象实际上由几部分构成,每个基类是一个类的子对象subobject,它在新定义的派生类中有独立的一部分。
派生类对象的初始化过程是这样的:首先自动调用每个基类的构造函数来初始化相关的基类子对象,然后再执行派生类的构造函数。从设计的角度来看,派生类的构造函数应该只初始化那些在派生类中被定义的数据成员而不是某类中的数据成员。
4 C++的继承:
C++支持另外两种形式的继承
1. 多继承multiple inheritance 也译多重继承也就是一个类可以从两个或多个基类派生而来。
2. 虚拟继承virtual inheritance 在这种继承方式下基类的单个实例在多个派生类之间共享。
2.5 范型设计
C++的模板设施提供了一种机制,它能够将类成函数定义内部的类型和值参数化。关键字template 引入模板参数由一对尖括号< > 括起来。
如template < class T>
class Array
{ …….};
编译器必须为相关的对象分配内存,为了做到这一点,形式模板参数被绑定到指定的实际参数类型上。
实例声明就是在类模板名的后面加上一对尖括号,然后在里面写上数组的实际类型。
如
Array<int> ia; Array<char> ca;
模板机制也支持面向对象的程序设计类模板可以作为基类或派生类。
注意:
1.对于模版类来说,不是所有的成员函数都能自动地随类模板的实例化而被实例化。只有真正被程序使用到的成员函数才会被实例化,这一般发生在程序生过程中的一个独立阶段。
2.在进行范型设计时,一定要注意自己所定义的类型不一定支持一些内置类型支持的操作!比如int就支持 = < >,
但自己定义的如:template < class type> ,type就不一定支持这些操作!此时就需要自己添加方法(重载操作符等)
2.6 基于异常的设计
异常exception 是指在运行时刻程序出现的反情形如:
1 数组下标越界;
2 打开文件失败;
3 可用动态内存耗尽;
等等。
(注意:除数为0而导致结果是一个未知数时,此时并没有标准抛出;)
这也就是说当遇到这些操作的时候一定要尽量地使用异常处理!
异常处理exception handling 为响应运行时刻的程序异常提供了一个标准的语言级的设施,它支持统一的语法和风格,也允许每个程序员进行微调。
异常处理机制的主要构成如下:
1 程序中异常出现的点
在C++中异常处理使用throw来进行抛出。
当异常被抛出时,正常的程序就被挂起直到异常被处理完毕。
2 程序中异常被处理的点
找到处理代码通常要涉及到展开程序调用栈Program call stack。
一旦异常被处理完毕就恢复正常的程序执行,但不是在发生异常的地方恢复执行过程,而是在处理异常的地方恢复执行过程。
try
{
}
catch( )
{
}
注意:
1. 如果没有与异常对应的catch处理语句,那么函数就会被终止。然后异常机制会在上一层函数中查找处理函数,直到main()函数,如果main()函数中仍然没有就会调用标准库函数terminate()
2.如果是在try中的第一条语句就抛出了错误,那么在异常处理完后应该也恢复到异常处理语句的下一语句,而无法回到发生异常的地方,即无法执行try第一条语句中后面的语句。
3.在异常处理语句中最好加入对出现异常的对象的操作(删除)以供后面的语句判断对象是否可使用。
2.7 用其他名字来命名数组
1.不同人编写的类(类名相同的话),如果放在一起时就会产生冲突。标准C++的名字空间机制是C++语言针对这个问题提供的语言一级的解决方案。
名字空间机制允许我们封装名字,否则这些名字就有可能会污染影响全局名字空间(pollute the global namespace),如:
namespace Cplusplus_Primer_3E
{
template <class elemType>
class Array { ... };
// ...
}
在访问时我们可以使用限定修饰名字符(qualified name notation),如:
namespace Lsis_Lab
{
class Array {…};
class Matrix(…);
}
namespace Commu_Lab
{
class Array{….};
class Matrix(…);
}
访问时就因该采用,Lsis_Lab::Array Larray; Commu_Lab::Array Carray
2.如果在1的情况下仍然有别名的情况的话或者觉得麻烦的话,名字空间别名namespace alias 允许用一个可替代的短的或更一般的名字与一个现有的名字空间关联起来,如:
namespace LL = Lsis_Lab;
namespace CL = Commu_Lab;
别名也可以用来封装正在使用的实际名字空间。我们可以通过改变分配给别名的名字空间来改变所使用的声明集,而无需改变“通过别名访问这些声明”的实际代码。
3.如果程序员还是觉得麻烦的话,using 指示符using directive 使名字空间内的所有声明都可见,这样这些声明能够不加限定地使用。所以为了不使用限定修饰名字符,这是最常用的访问方法。但需要注意的是,被引用的名字空间必须已经被声明了否则会引起编译错误。
using namespace Lsis_Lab;
int main()
{
// ok: Lsis_Lab:Matrix
Matrix mat( 4,4 );
// ok: Lsis_Lab::Array
Array ia( 1024 );
// ...
}
using 声明using declaration 提供了选择更为精细的名字可视性机制它允许使名字中间中的单个声明可见。如
using Lsis_Lab::Matrix;
int main()
{
// ok: Lsis_Lab:Matrix
Matrix mat( 4,4 );
// ok: Lsis_Lab::Array are not visible
}
对于标准库:
为了防止标准C++库的组件污染用户程序的全局名字空间所有标准C++库的组件都声明在一个被称为std 的名字空间内,如:
#include <string>
using namespace std;
// ok: string 是可见的
string current_chapter = "A Tour of C++";
但是使用以上方法指示符using 使头文件<string>中声明的、并且位于名字空间std 中的所有组件在程序文本文件中都是可见的,这又将全局名字空间污染问题带回来了。它增加了C++标准库组件的名字与我们程序中声明的全局名字冲突的机会,所以建议使用带有精细选择功能的using 声明代替using 指示符。
#include <string>
std::string current_chapter = "A Tour of C++";
或者:
#include <string>
using std::string;
string current_chapter = “A Tour of C++”;
2.8 标准数组——向量
向量(vector)
在标准C++中数组类是C++标准库的一部分,现在它不叫数组而叫向量vector了:
1.可以在运行时刻动态增长(如果程序员希望使用这个特性的话)
2.更加广泛代表了设计方法的重要转变。只提供了一个最小集,如等于小于操
作符size()、 empty()等操作;而一些通用的操作如sort() 、min() 、max()和find()等等,则是作为独立的泛型算法generic algorithm 被提供的。
向量的遍历操作:
1.第一种方法:标准vector类模板也支持使用下标操作符,结合size( )进行循环遍历。
2.第二种方法:迭代器对(iterator pair) 来标记向量的起始处和结束处迭代器是一个支持指针类型抽象的类对象。vector类模板提供了一对操作begin( )和end( ),它们分别返回指向向量开始处和结束处后1 个的迭代器。
范型算法
前面已经提到了能够应用到向量上的操作惊人地多,但是它们并不是作为vector类模板的成员函数提供的。它们是以一个独立的泛型算法集的形式由标准库提供,泛型算法接受一对迭代器它们标记了要遍历元素的范围。
搜索(search)算法:find() 、find_if() 、search()、 binary_search() 、count()和count_if()
分类排序(sorting)与通用排序(ordering)算法:sort()、partial_sort()、 merge()、partition()、 rotate()、 reverse()和random_shuffle()
删除(deletion) 算法:unique()和remove()
算术(numeric) 算法:accumulate() 、partial_sum()、 inner_product()和adjacent_difference()
生成(generation) 和变异(mutation) 算法:generate()、 fill()、 transformation()、
copy()和for_each()
关系Relational 算法:equal()、 min()和max()
注意:使用范型算法时需要:#include <algorithm>
关联数组(map)
标准库还提供了对map关联数组的支持,即数组元素可以被整数值之外的其他东西索引。