(2012.01.05)《C++ Primer》全书第一轮学习笔记汇总

来源:互联网 发布:小猪微信营销系统源码 编辑:程序博客网 时间:2024/06/05 10:52
 

《C++ Primer》全书第一轮学习笔记汇总(2011.10.02)

 目录:(2012.01.05插入)

(2011.09.08)《C++ Primer》整书学习笔记前言
                             http://blog.csdn.net/neicole/article/details/6761312
(2011.09.08)《C++ Primer》第一部分学习笔记汇总——基本语言
                             http://blog.csdn.net/neicole/article/details/6761314
《C++ Primer》 第01章学习笔记 --快速入门
《C++ Primer》 第02章学习笔记 --变量和基本类型
《C++ Primer》 第03章学习笔记 --标准库类型
《C++ Primer》 第04章学习笔记 --数组和指针
《C++ Primer》 第05章学习笔记 --表达式
《C++ Primer》 第06章学习笔记 --语句
《C++ Primer》 第07章学习笔记 --函数
《C++ Primer》 第08章学习笔记 --标准IO库

(2011.09.25)《C++ Primer》第二部分学习笔记汇总——容器与算法
                             http://blog.csdn.net/neicole/article/details/6820222
《C++ Primer》 第09章学习笔记 --容器与算法
《C++ Primer》 第10章学习笔记 --关联容器
《C++ Primer》 第11章学习笔记 --泛型算法

(2011.09.27)《C++ Primer》第三部分学习笔记汇总——类和数据抽象
                             http://blog.csdn.net/neicole/article/details/6825995
《C++ Primer》 第12章学习笔记 --类
《C++ Primer》 第13章学习笔记 --复制控制
《C++ Primer》 第14章学习笔记 --重载操作符与转换

(2011.10.01)《C++ Primer》第四部分学习笔记汇总——面向对象编程与泛型编程
                             http://blog.csdn.net/neicole/article/details/6839444
《C++ Primer》 第15章学习笔记 --面向对象编程
《C++ Primer》 第16章学习笔记 --模板与泛型编程

(2011.10.02)《C++ Primer》第五部分学习笔记汇总——高级主题
                             http://blog.csdn.net/neicole/article/details/6840544
《C++ Primer》 第17章学习笔记 --异常处理
《C++ Primer》 第18章学习笔记 --特殊工具与技术

 

 

正文:

《C++ Primer》整书学习笔记前言

第00章:前言

——此次决定了写《C++ Primer》的学习笔记,刚开始时没有想到怎么个写法,是学完一章后直接写章结还是怎样,不知道,后来,在学习的时候,发觉,有很多细节还是很有用的,不想忘了它们,而之前我又学过《C++ Primer Plus》,也有一些内容已经熟记在心里了,所以,现在决定了写这个学习笔记的模式,方法,框架是怎样了。

——首先,我会整章阅读一次,在认为对自己很有帮助,或者是之前还没有接触过的知识点做上记号;然后,这一章结束后,有时我会将其先写到草稿本上;之后,我会将其录入电脑,在录入的时候顺带再强化记忆,然后,如果有些有地方有想说的话的时候,会在后面加一个“摘录有想”说出自己想说的东西,不过有时候太累赘的话就不说了。

——我会经常反复阅读自己的笔记,当然,知道最主要的是上机实践,不懂的时候再返回书本中的内容去看,去练。

——有人说,这本书你可以直接当作字典去查,不用这样一章章地看下去,但是,我想的是,假如连字典中有些什么内容也不知道,提到过的一些知识点不知道的话,需要用到的时候一点反应都没有,怎么往这里去查呢?所以,我还是决定,将这本书看一遍,有些地方细看,有些地方粗看,那么等真正需要用的时候,就可以很快地想出曾经有这么一个东西可以用,曾经书上有提过这么一个东西了。

——我从08月21日开始看了这本书,但是那时还没有开始想到要做笔记,因为害怕看了书后也会没看过一样,吸收不到什么,不知道自己看过什么,所以,我决定了做笔记。之前一段时间,一直有不少的琐事做着,让我不得连续在这个阵线上学习。现在,事情很大部分已经解决了,除了每天的上课课程这些学习类的东西干扰这个任务外,基它就基本没有什么了,所以,我接下来,会在兼顾现在的学校的专业课教学内容的同时,做完这本《C++ Primer》的学习笔记,现在已经是2011年09月07日了,大二了,计划2011年10月15日前,将此任务完成。

——当然,会做好计划往往赶不上变化的准备。那时就,具体问题具体分析。嗯,加油!

 

 

《C++ Primer》第一部分学习笔记汇总——基本语言

 

《C++ Primer》 第1章学习笔记

第01章:快速入门

  这一章,书上用了一个例子贯穿每部分的内容,“书店的书本销售情况”(包括销售册数与单价)。

 

第二节:介绍输入与输出

@ 学习摘录001:

——endl称为操纵符(manipulator),可刷新与设备相关联的缓冲区。

——在刷新缓冲区时,用户可立即看到写入到流中的输出。

——程序员经常在调试过程中插入输出语句,这些语句都应刷新输出流。

摘录有想001:

——这几句让我想起之前自己以及朋友们出现过的错误,当if(!cin)时,只是cin.clear()是不够的,还需要用cin.sync()清空缓冲区或者用while(cin.get() != ‘\n’)continue;提取多余字符。看来以后得多注意输入输出缓冲区了。

 

第三节:关于注释

@ 学习摘录002:

——当注释多行时,最好能直观指明每一行都是注释。

摘录有想002:

——这让我注意到了之前没怎么注意的问题,看《数据结构与算法分析》的一书上是用了这样的格式的,才发觉原来那书的编译习惯是不错的。

/*

 * now, for a example.

 * like this.

 */

 

第四节:控制结构

 

@ 学习摘录003:

——关于控制结构我想到的是之前《C++ Primer Plus》上提到过,循环(判断条件),条件if(判断条件)都会将括号内的内容转换为bool型作为执行与否的依据的。

 

@ 学习摘录004:

——编译器能查出的最普通的错误1.语法错误 2.类型错误 3.声明错误

摘录有想004:

——知道编译能检查出错误的话,在编译习惯上有些就可以利用这一点了,可以避免在调试的时候才发现错误了,这也是一个网友跟我说过起的。如:if(i = 1),将其写为if (1 = i)的话就能在编译时检测出自己要写的是if ( 1 == i)了。

 

第五节:类的简介

@ 学习摘录005:

——什么是成员函数:成员函数是由类定义的函数,有时称为“类方法”(method)

 

@ 学习摘录006:

——使用类时需注意的三个问题:

——1. 类的名字是什么? 2. 它在哪里定义? 3. 它支持什么操作?

摘录有想006:

——很多同学不明确的一点是第3点,之前我看同学的程序时,问同学你的这个类想要实行什么样的功能时,他自己也答不上。

 

 

 

《C++ Primer》 第02章学习笔记

第02章:变量和基本类型

这一章,主要讲述了常量,变量和一些类型的使用方法,注意事项。

 

第三节:变量

 

@ 学习摘录007:

——C++是一门静态类型语言,在编译的时候会作类型检查,静态类型检查能帮助我们更早地发现错误。

摘录有想007:

——静态类型使得编译器必须能识别程序中每个实体的类型。假如没有定义或定义错的时候就能检查出来啦。

 

@ 学习摘录008:

——左值:lvalue,左值可以出现在赋值语句的左边或右边。

——右值:rvalue,右值只能出现在赋值的右边,不能出现在赋值语句的左边。

——变量是左值,因此可以出现在赋值语句的左边,数字字面是右值,因此不能被赋值。

摘录有想008:

——在我理解中,因为变量可在左也可在右,因此它为左值,而数字则只能出现在右边,因此它为右值。

 

@ 学习摘录009:

——变量提供了可以操作的有名字的存储区,对象就是内存中具有类型的区域。

摘录有想009:

——例如:int a; class b{}; b c; 这里,a 和c属于对象。

 

@ 学习摘录010:

——初始化变量不是赋值。

——初始化:指创建变量并给它赋初始值。

——赋值:是擦除对象的当前值并用新值代替。

 

@ 学习摘录011:

——初始化变量有两种形式。

——1. int ival(1024);  // direct-initialization  直接初始化

——2.int ival = 1024;  // copy-initialization  复制初始化

 

@ 学习摘录012:

——复制初始化和直接初始化之间的差别是很微妙的。

——现在我们只需知道,直接初始化的效率更高。

 

@ 学习摘录013:

——未初始化变量引起的错误难以发现,永远不要依赖未定义行为,使用未初始化的变量是常见的程序错误。虽然许多编译器都至少会提醒不要使用未初始化变量,但是编译器并未被要求去检测未初始化变量的使用。而且,没有一个编译器能检测出所有未初始化变量的使用。

 

@ 学习摘录014:

——extern声明不是定义,也不分配存储空间。

——事实上,它只是说明变量定义在程序的其他地方。

摘录有想014:

——extern的使用得注意,一个文件里面放定义,另外一个文件里面放声明才可以,上次同学就是犯了这样的错误,没有在另一个文件中声明就想用extern变量了。

 

@ 学习摘录015:

——只有当声明也是定义时,声明才可以有初始化式,因此只有定义才分配存储空间。

 

@ 学习摘录015:

——看来下次写程序时真的要直接在声明的时候就定义好,那样的话就安全多了。

 

@ 学习摘录016:

——作用域可以分为三种:

——1.全局作用域(global scope)

——2.局部作用域(local scope)

——3.语句作用域(statement scope)

摘录有想016:

——有语句 for(int val = 1; val <= 10; ++val) sum += val;

——此处,val 定义在for语句的作用域中,只能在for语句中使用,而不能在main 函数的其他地方。

 

@ 学习摘录017:

——通常把一个对象定义在它首次使用的地方是一个很好的办法。放置声明的一个约束是,变量只在从其定义处开始到声明所在的作用域的结束才可以访问。

 

第五节:引用

 

@ 学习摘录018:

——“const引用”的意思是“指向const 对象的引用”。

 

@ 学习摘录019:

——非const引用只能绑定到与该引用同类型的对象。

——const引用则可以绑定到不同但相关的类型对象或绑定到右值。

 

第八节:类类型

@ 学习摘录020:

——每类都定义了一个接口(interfer)和一个实现。

——接口由使用该类的代码需要执行的操作实组成。

——实现一般包括该类所需要的数据。

——类体定义了组成该类型的数据和操作。操作称为成员函数,数据则称为数据成员。

 

@ 学习摘录021:

——用class和sturct关键定义类的唯一差别在于默认访问级别:默认情况下,struct的成员为pulbic,而class的成员为private.

 

@ 学习摘录022:

——当我们在头文件中定义了const变量后,每个包含该头文件的源文件都有了自己的const 变量,其名称和值一样。

 

@ 学习摘录023:

——避免多重包含,为了避免多重包含,避免名字冲突,预处理器变量经常用全大写字母表示。

#ifndef ABC_H

#define ABC_H // Definition of ABC class and related functions goes here

#endif

摘录有想023:

——看了这么多书,这么多个例子,终于知道为什么它总是用大写来表示了。

 

 

 

《C++ Primer》 第03章学习笔记

第03章:标准库类型

C++还定义了一个内容丰富的抽象数据类型标准库,其中最重要的标准库类型是string和vector,它们分别定义了大小可变的字符串和集合。另一种标准库类型bitset提供了一种抽象方法来操作位集合。

 

第一节:命名空间using声明

@ 学习摘录024:

——一旦使用了using声明,我们就可以直接引用名字,而不需要再引用该名字的命名空间:

——// using declaration states our intent to use these names from the namespace std.

——using std::cin;

——using std::string;

@ 学习摘录025:

——如果在头文件中放置using声明,就相当于在包含该文件的每个程序中都放置了同一个using声明,不论该程序是否需要using声明。

摘录有想025:

——我想这相当于全局变量跟局部变量的作用吧。

 

@ 学习摘录026:

——通常头文件中应该只定义确实必要的东西。请养成这个习惯。

摘录有想026:

——书上建议少用using namespace std 这样的风格,以后写程序就尽量用using声明吧,少用using编译指令。

 

第二节:标准库string类型

 

@ 学习摘录027:string操作

——s.empty() // 检查字符是否为空,bool类型

——s.size(); // 返回s中字符的个数

——s[n];  // 返回s中位置为n的字符,位置从0开始计数

摘录有想027:

——以前还不知道原来有empty()这一操作,初见empty还以为要清空这个对象的数据呢,其实不然,是检查字符串是否为空。 If(s.empty()) // ok, empty

 

@ 学习摘录028:

——size操作返回的是string::size_type类型的值。String类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关(machine-independent)。Size_type就是这些配套类型中的一种。它定义为unsigned型(unsigned int 或unsigned long)具有相同的含义,而且可以保证足够大能够存储任意string对象的长度。

摘录有想028:

——写程序时不要把size的返回值赋给一个int变量了。

 

@ 学习摘录029:

——如在有16位int型的机器上,int类型变量最大只能表示32767个字符的string对象,而能容纳一个文件内容的string对象轻易就会超过这个数字。因此,为了避免溢出,保存一个string对象size的最安全的方法就是使用标准库类型string::size_type

 

@ 学习摘录030:

——string对象比较操作是区分大小写的,即同一个字符的大小写形式被认为是两个不同的字符。在多数计算机上,大写的字母位于小写字母之前;任何一个大写字母都小于任意的小写字母。

摘录有想030:

——在比较时需注意大小写了,想起以前cctype有一个用于转换字符的函数,把大写转为小写之类的功能可以很容易实现。

 

@ 学习摘录031:

——string类型通过下标操作符([])来访问string对象中的单个字符。下标操作符需要一个size_type类型的值,来标明要访问字符的位置。这个下标中的值通常被称为“下标”或“索引(index)”。

 

@ 学习摘录032:

——string对象的下标从0开始,而s[s.size() – 1]则表示s的最后一个字符。

 

第三节:标准库vector类型

 

@ 学习摘录033:

——虽然可以对给定元素个数的vector对象预先分配内存,但更有效的方法是先初始化一个空vector对象,然后再动态地增加元素。

 

@ 学习摘录034:

——C++程序员习惯于优先选用!=而不是<来编写循环判断条件。

 

@ 学习摘录035:

——我们倾向于在每次循环中测试size的当前值,而不是进入循环前,存储size值的副本。调用size成员而不保存它返回的值,这反映了一种良好的编程习惯。

摘录有想035:

——以后在使用for时,可以注意一下这个问题了。

 

第四节:迭代器简介。

 

@ 学习摘录036:

——迭代器是一种检查容器内元素并遍历元素的数据类型。若一种类型支持一组确定的操作(这些操作可用来遍历容器内的元素并访问这些元素的值),我们就称这种类型为迭代器。

 

@ 学习摘录037:

——vector<int>::iterator iter = ivec.begin();

——由end操作返回的迭代器指向vector的“末端元素的下一个”,通常称为超出未端迭代器(off-the-end iterator),表明它指向了一个不存在的元素。如果vector为空,begin返回迭代器与end返回的迭代器相同。

 

@ 学习摘录038:

——由end操作返回的迭代器并不指向vector中任何实际的元素,相反,它只是起一个哨兵(sentinel)的作用,表示我们已经处理完vector中所有的元素。

 

@ 学习摘录039:

——迭代器类型可以使用解引用操作符(*操作符)来访问迭代器所指向的元素:*iter = 0;由于end返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作。

 

@ 学习摘录040:

——使用const_iterator类型时,我们可以得到一个迭代器,它自身的值可以改变,但不能用来改变其指向的元素的值。可以对迭代器进行自增以及使用引用操作符来读取值,但不能对该元素值赋值。

 

 

 

《C++ Primer》 第04章学习笔记

第04章:数组和指针

 

第二节:指针的引入

 

@ 学习摘录041:

——指针的概念很简单,指针用于指向对象。与迭代器一样,指针提供对其所指对象的间接访问,只是指针结构更通用一些。与迭代器不同的是,指针用于指向单个对象,而迭代器只能用于访问容器内的元素。

 

@ 学习摘录042:

——如果可能的话,除非所指向的对象已经存在,否则不要先定义指针,这样可避免定义一个未初始化的指针,如果必须分开定义指针和其所指针的对象,则将指针初始化为0,因为编译器可检测出0值的指针,程序可判断该指针并未指向一个对象。

摘录有想042:

——我想这也就是说,定义指针最好有初始化,避免不必要的错误。

 

@ 学习摘录043:

——预处理器变量不是在std命名空间中定义的,因此其名字应为NULL,而非std::NULL。

 

@ 学习摘录044:

——C++提供了一个特殊的指针类型void*,它可以保存任何类型对象的地址。

——void*主要用于以下三种操作:

——1. 与另一个指针进行比较

——2. 向函数传递void*指针或从函数返回void*指针

——3. 给另一个void*指针赋值

double obj = 3.14;

double *pd = &obj;

// ok, void * can hold the address value of any data pointer type

void * pv = & obj;  // obj can be an object of anytype

pv = pd;         // pd can be a pointer to any type

 

@ 学习摘录045:指针与引用的区别

——指针与引用的相同点:都可间接访问另一个值

——第一区别:引用总指向某个对象,定义引用时设有初始化是错误的。

——第二区别:赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而不使引用与别一个对象关联。

 

@ 学习摘录046:

——C++允许计算数组或对象的超出未端的地址,但不允许对此进行解引用操作(*解引用操作),而计算数组超出未端位置之后或数组首地址之前的地址都是不合法的。

 

第三节:C风格字符串

 

@ 学习摘录047:C风格字符串与C++的标准库类型string的比较

——以下两段程序反映了使用C风格字符串与C++的标准库类型string的不同之处,使用string类型的版本更短、更容易理解,而且出错的可能性更小。

// C-style character string implementation

const char * pc = “a very long literal string.”;

const size_t len = strlen(pc);   // space to allocate

// performance test on string allocation and copy

for (size_t ix = 0; ix != 1000000; ++ix)

{

char * pc2 = new char[len + 1];   // allocate the space

  strcpy(pc2, pc);               // do the copy

  if(strcmp(pc2, pc)             // user the new string

           ;                 // do nothing

  delete [] pc2;                // free memory

}

 

// string implementation

string str(“a very long literal string”);

// performance test on string allocation and copy

for(size_t ix = 0; ix != 1000000; ++ix)

{

string str2 = str; // do the copy, automatically allocated

if(str != str2)

   ; // do nothing

}                            // str2 is automatically freed

 

第四节:多维数组

 

@ 学习摘录048:

——严格来说,C++中没有多维数组,通常所指的多维数组的数组;

// array of size 3, each element is an array of ints of size 4

int ia[3][4];

——在使用多维数组时,记住这一点有利于理解其应用。

 

术语:

@ 学习摘录049:

——ptrdiff_t:在cstddef头文件中定义的与机器相关的有符号整型,该类型具有足够大小存储两个指针的差值,这两个指针指向同一个可能的最大数组。

——size_t:在cstddef头文件中定义的与机器相关的无符号整型,它具有足够大小存储一个可能的最大数组。

 

 

 

《C++ Primer》 第05章学习笔记

第05章:表达式

 

第五节:自增和自减操作符

 

@ 学习摘录050:自增和自减操作符

——建议:只有在必要时才使用后置操作符。

——前置操作需要做的工作更少,只需加1后返回加1后的结果即可。

——而后置操作符则必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。

——对于int型对象和指针,编译器可优化掉这项额外工作。但是对于更多的复杂迭代器类型,这种额外工作可能花费更大的代价。

——养成使用前置操作这个好习惯,就不必操心性能差异的问题。

 

@ 学习摘录051:在单个表达式中组合使用解引用和自增操作

——*iter++的意思:等效于*(iter++)。子表达式iter++使iter加1,然后返回iter原值的副本作为该表达式的结果。

vector<int>::iterator iter = ivec.begin();

// prints 10 9 8 … 1

while(iter != ivec.end())

  cout << *iter++ << endl;  // iterator postfix increment

摘录有想:

——很多C++程序员都会习惯用这种方法的,可以简洁清晰不冗长。

 

第十一节:new 和delete 表达式

 

@ 学习摘录052:动态创建对象的默认初始化

——在动态创建对象时,(几乎)总是对它做初始化也是一个好方法。

——同样也可以对动态创建的对象做值初始化

string *ps = new string();  // initialized to empty string

int *pi = new int();  // pi points to an int value-initialized to 0

cls *pc = new cls();  // pc points to a value-initialized object of type cls

——对比下面的不同初始化方式的不同

int *pi = new int;  // pi points to an uninitialized int

int *pi = new int();  // pi points to an int value-initialized to 0

 

@ 学习摘录053:

——一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。

摘录有想:

——如果用了delete *p; 之后 p = NULL;

 

第十二节:显式转换

 

@ 学习摘录054:

——命名的强制类型转换 cast-name<type>(expression);

——dynamic_cast,支持运行时识别指针或引用指向的对象。

——const_cast,将转换掉表达式的const性质。

——static_cast,编译器隐式执行的任何类型转换都可以由static_cast显式完成。

——reinterpret_cast,通常为操作数的位模式提供较低层次的重新解释。

摘录有想:

——我想一般现在写程序的时候用到最多的是static_cast吧,而且,书上也建议,不要常用强制转换,如果程序写得好的话,就根本不用用上这个功能。

 

 

 

《C++ Primer》 第06章学习笔记

第06章:语句

 

第二节:复合语句(块)

 

@ 学习摘录055:

——复合语句(compound statement),通常被称为块(block),是用一对花括号括起来的语句序列(也可能是空的)。

——块标识了一个作用域,在块中引入的名字只能在该块内部或嵌套在块中的子块里访问。

 

第十二节:goto语句

 

@ 学习摘录056:

——从上世纪60年代后期开始,不主张使用goto语句,goto语句使跟踪程序控制流程变得很困难,并且使程序难以理解,也难以修改。

——所有使用goto的程序都可以改写为不用goto语句,因此也就没有必要使用goto语句了。

 

 

 

《C++ Primer》 第07章学习笔记

第07章:函数

——本章介绍(function)的定义和声明。

 

第二节:参数传递

 

@ 学习摘录057:数组形参性质

——数组形参有两个特殊的性质,影响我们定义和使用作用在数组上的函数。

——1.不能复制数组

——2.使用数名字时,数组名会自动转化为指向其第一个元素的指针

 

@ 学习摘录058:数组形参在函数中声明方法

// three equivalent definitions of printValues

void printValues(int *) { /*… */ }

void printValues(int[]) { /* … */ }

void printValues(int[10]) { /* … */ }

——虽然不能直接传递数组,但是函数的形参可以写成数组的形式。

——虽然形参表示方式不同,但可将使用数组语法定义的形参看作指向数组元素类型的指针。

 

@ 学习摘录059:通过引用传递数组

——和其它类型一样,数组形参可声明为数组的引用。

——编译器检查数组实参的大小与形参的大小是否匹配。

——这个版本的printValues函数只严格地接受含有10个int型数值的数组,这限制了哪些数组可以传递。然而,由于形参是引用,在函数体中依赖数组的大小是安全的。

// ok: parameter is a reference to an array; size of array is fixed

void printValues(int (&arr)[10])

{

  for (size_t i = 0; i != 10; ++i)

  {

   cout << arr[i] << endl;

   }

}

 

第三节:return语句

 

@ 学习摘录060:主函数main的返回值

——允许主函数main没有返回值就可结束,如果程序控制执行到主函数main的最后一个语句都还没有返回,那么编译器会隐式地 return 0; 这是返回类型不是void的函数必须返回一个值的规则的例外情况。

——主函数main返回的值视为状态指示器,返回0表示程序运行成功,其它大部分返回值则表示失败。

 

第八节:重载函数

 

@ 学习摘录061:

——重载函数定义:出现在相同作用域中的两个函数,具有相同的名字而形参表不同的函数。

 

 

 

《C++ Primer》 第08章学习笔记

第08章:标准IO库

——C++的输入/输出(input/output)由标准库提供。标准库定义了一族类型,支持对文件和控制窗口等设备的读写(IO)。

 

第一节:面向对象的标准库

 

@ 学习摘录062:iostream定义读写控制窗口的类型

——istream 从流中读取

——ostream 写到流中去

——iostream 对流进行读写;从istream和ostream派生而来

 

@ 学习摘录063:fstream定义读写已命名文件的类型

——ifstream 从文件中读取;由istream派生而来

——ofstream 写入到文件中;由ostream派生而来

——fstream读写文件;由iostream派生而来

 

@ 学习摘录064:sstream定义的类型用于读写存储在内存中的string对象

——istringstream从string对象中读取;由istream派生而来

——ostringstream写入到string对象中去;由ostream派生而来

——stringstream对string对象进行读写,由iostream派生而来

 

第二节:条件状态(condition state)

 

@ 学习摘录065:IO错误例子:

——以下例子,如果在标准输入设备输入Borges。

——则cin在尝试将输入的字符串读为int型数据失败后,会生成一个错误状态。

——如果输入文件结束符(end-of-file)。

——则cin也会进入错误状态。

——而如果输入1024,则成功读取,cin将处于正确的无错误状态。

——流必须处于无错误状态,才能用于输入或输出。

——检测流是否可用的最简单的方法是检查其真值。

if(cin) // ok to use cin, it is in a valid state

while(cin >> word) // ok: read operation successful…

 

@ 学习摘录066:各种条件状态的定义

——s.bad(),badbit标志着系统级的故障,如无法恢复的读写错误。

——s.fail(),failbit标志着出现可恢复的错误,这种导致设置failbit的问题通常是可以修正的。

——s.eof(),eofbit遇到文件结束符时设置的。

——s.good(),如果bad、fail或者eof中的任意一个为true,则检查流本身将显示该流处于错误状态。如果这三个条件没有一个为true,则good操作将返回true。

——s.clear(),clear操作将条件重设为有效状态。

 

@ 学习摘录067:流状态的查询和控制

——回顾逗号操作符的求解过程:首先计算它的每一个操作数,然后返回值右边的操作数作为整个操作的结果。

int ival;

// read cin and test only for EOF; loop is executed even if there are other IO failures

while(cin >> ival, !cin.eof() )

{

  if (cin.bad())                   // input stream is corrupted; bail out

      throw runtime_error(“IO stream corrupted”);

  if (cin.fail())                   // bad input

  {

cerr << “bad data, try again”;  // warn the user

cin.clear(istream::failbit);     // reset the stream

continue;                  // get next input

   }

  // ok to process ival

}

 

第三节:输出缓冲区的管理

 

@ 学习摘录068:缓冲区的刷新

——下面五种情况将导致缓冲区的内容被刷新,即写入到真实的输出设备或者文件:

——1. 程序正常结束。作为main返回工作的一部分,将清空所有的输出缓冲区。

——2. 在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会写到下一个值之前刷新。

——3. 用操纵符(manipulator)显式地刷新缓冲区,例如行结束符endl.

——4. 在每次输出操作执行完后,用unitbuf操纵符设置流的内部状态,从而清空缓冲区。

——5. 可将输出流与输入流关联(tie)起来。在这种情况下,在读输入流将刷新其关联的输出缓冲区。

 

@ 学习摘录069:unitbuf操纵符与flush操纵符

——如果需要刷新所有输出,最好使用unitbuf操纵符。

——unitbuf操纵符在每次执行完写操作后都刷新流:

——cout << unitbuf << “first” << “second” << nounitbuf;

——等价于 cout << “first” << flush << “second” << flush;

 

第四节:文件的输入与输出

 

@ 学习摘录070:读取一个存放文件名的容器,打开每个文件

——此例中,如果忽略clear的调用,则循环只能读入第一个文件。

ifstream input;

vector<string>::const_iterator it = files. begin();

// for each file in the vector

while( it != files.end() )

{

  input.open(it -> c_str());  // open the file

  // if (!input)

     break;             // error: bail out!

while(input >> s)     // do the work on this file

   process(s);

input.close();        // close file when we’re done with it

input.clear();        // reset state to ok

++it;               // increment iterator to get next file

}

 

第五节:字符串流

 

@ 学习摘录071:操纵每行中的每个单词的实例

string line, word;              // will hold a line and word from input, respectively

while(getline(cin, line))         // read a line from the input into line

{  // do per-line processing

   istringstream stream(line);   // bind to stream to the line we read

   while(stream >> word)      // read a word from line

   {

     // do per-word processing

   }

}

——使用getline函数从输入读取整行内容。然后为了获得每行中的单词,将一个istringstream对象与所读取的行绑定起来,这样只需使用普通的string输入操作符即可读出每行中的单词。

 

 

《C++ Primer》第二部分学习笔记汇总——容器与算法

 

《C++ Primer》 第09章学习笔记

第09章:容器与算法

 

@学习摘录072:顺序容器

——将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素,这就是顺序容器。

——vector  支持快速随机访问

——list     支持快速插入/删除

——deque  双端队列

 

@学习摘录073:顺序容器适配器

——适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型。

——stack                           后进先出(LIFO)栈

——queue                          先进先出(FIFO)队列

——priority_queue          有优先级管理的队列

 

第一节:顺序容器的定义(初始化)

 

@学习摘录074:新建一个容器初始化为另一个容器的副本

——vector <int> ivec;

——vector<int> ivec2(ivec);            // ok, ivec is vector<int>

——list<int> ilist(ivec);                      // error: ivec is not list<int>

——vector<double> dvec(ivec);    // error: ivec holds int now double

——将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。

摘录有想074:

——这也就是说,容器复制的时候,只需考虑的是容器类型和元素类型,并不需要考虑原容器中的元素数量了,以前曾经想过这个问题。也会想一个问题,在定义新容器的时候,会不会出现溢出的情况呢?

 

@学习摘录075:初始化为一段元素的副本

——系统允许通过传递一对迭代器间接实现该功能。使用迭代器时,不要求容器类型相同,容器内的元素类型也可以不相同,只要它们相互兼容能进行转换即可。

——// initialize slist with copy of each element of svec

——list<string> slist(svec.begin(), svec.end() );

——// find midpoint in the vector

——vector<string>::iterator mid = svec.begin() + svec.size() / 2;

——// initialize front with first half of svec: The elements up to but note include *mid

——deque<string> front(svec.begin(), mid);

——// initialize back with second half of svec: The elements *mid through end of svec

——deque<string> back(mid, svec.end() );

摘录有想075:

——利用迭代器复制相当的方便,减少了很多限制,还可以不同类型的复制,只是也要考虑到一个方面,稳定性,如果需要隐式转换的地方还是少用的好。一段段的复制挺好的。同时,注意考虑它的区间是左闭右开的区间来的。

 

@学习摘录076:容器内元素的类型约束

——元素类型必须支持赋值运算。

——元素类型的对象必须可复制。

摘录有想076:

——这个问题,虽然不常发生,int型之类的内置类型不用考虑这个问题,而一些自己定义的类类型的话,真的得注意了。

 

@学习摘录077:容器的容器

——定义容器的容器时,有一个问题必须得注意的:

——vector< vector<string> > lines; // ok:space required between cblose>

——vector<vector<string>> lines; // error: >> treated as shift operator

——必须用空格隔开两个相邻的>符号,以示这是两个分开的符号。

——否则,系统会认为>>是单个符号,为右移操作符,并结果导致编译时的错误。

摘录有想077:

——网上的帖子看过很多人出现这个错,经典错误啊,就一个空格之差,让人看得纠结,写程序时,细节决定成败。

 

第二节:迭代器和迭代器范围

 

@学习摘录078:迭代器范围

——C++语言使用一对迭代器标记迭代器范围,这两个迭代器分别指向同一个容器中的两个元素或超出末端的下一位置。

——通常,这两个迭代器命名为first和last,或 beg和end,用于标记容器中的一段元素范围。

——该范围内的元素包括迭代器first指向的元素,以及从first开始一直到迭代器last指向的位置之前的所有元素。

——此类元素范围称“左闭合区间(left-inclusive interval) 表达式: [first, last]

摘录有想078:

——要记得啊,常常出现的字眼“超出末端的下一位置”。这一知识点,记住左闭区间是关键。

 

@学习摘录079:使用左闭合区间的编程意义

——左闭合区间有两个方便使用的性质,得记住:

——1. 当first 与last 相等时,迭代器范围为空;

——2. 当first与 last 不相等时,迭代器范围内至少有一个元素,而first指向该区间的第一个元素。

——while(first != last)

——{ // safe to use *first because we know there is at least one element

—— ++first;

——}

摘录有想079:

——我想:左闭合区间的使用,总的来说可以概括为三个字吧“安全性”。

 

第三节:顺序容器的操作

——每种顺序容器都提供了一组有用的类型定义以及以下操作:

——1. 在容器中添加元素

——2. 在容器中删除元素

——3. 设置容器的大小

——4. (如果有的话)获取容器内的第一个和最后一个元素。

 

@学习摘录080:在顺序容器中添加元素

——在容器中添加元素时,系统是将元素值复制到容器里。

摘录有想080:

——这令我想到了指针,使用指针,改变的是值,而地址不变。这个原理吧。

 

@学习摘录081:在容器中的指定位置添加元素

——看代码后,你懂的! s.insert(迭代器, 插入的东西); 新元素是插入在迭代器指向的位置之前。返回指向新添加元素的迭代器。

——s.insert(iter, element); // insert element just before iter

摘录有想081:

——考虑到有一种特例:push_back和push_front可以相当于iter为s.begin()和s.end() 时。

 

@学习摘录082:插入一段元素

——看代码后,你懂的!迭代器插入位置加上迭代器的前后位置构成的左闭合区间。

——string sarray[4] = {“quasi”, “samba”, “frollo”, “scar”}; 

——// insert all the element in sarray at end of slist

——slist.insert(slist.end(), sarray, sarray + 4);

 

@学习摘录083:关系操作符(比较大小)

——/*

——  ivec1: 1 3 5 7 9 12

——  ivec2: 0 2 4 6 8 10 12

——  ivec3: 1 3 9

——  ivec4: 1 3 5 7

——  ivec5: 1 3 5 7 9 12

——*/

—— // ivec 1 and ivec2 differ at element[0]: ivec1 greater than ivec2

——// ivec1 < ivec2 // false

——// ivec2 < ivec1 // true

——// ivec1 and ivec3 differ at element[2]: ivec1 less than ivec3

——ivec1 < ivec3 // true

——// all elements equal, but ivec4 has fewer elements, so ivec1 is greater than ivec4

——ivec1 < ivec4 // false

——ivec1 == ivec5 // true; each element equal and same number of elements

——ivec1 == ivec4 // false; ivec4 has fewer element

—— ivec1 != ivec4 // true; ivec4 has fewer elements than ivec1

摘录有想083:

——很明显,在容器中,比较大小;

——1.逐位对比,先比大小。 (大者为大)

——2.再比容器长度。(长者为大)

 

@学习摘录084:删除容器内所有的元素

——slist.clear(); // delete all the element within the container

——slist.erase(slist.begin(), slist.end() ); // equivalent

摘录有想084:

——要删除容器内所有的元素,可以调用clear函数,或将begin和end迭代器传递给erase函数。

 

@学习摘录085:容器中的赋值操作符

——赋值操作符首先删除其左操作数容器中的所有元素;

——然后将右操作数容器的所有元素插入到左边容器中;

——赋值后,左右两边的容器相等;

——赋值前可能两个容器长度不相等,但赋值后两个容器都具有右操作数的长度。

——c1 = c2; // replace contents of c1 with a copy of elements in c2

——// equivalent operation using erase and insert

——c1.erase(c1.begin(), c1.end() ); // delete all elements in c1

——c1.insert(c1.begin(), c2.begin(), c2.end() ); // insert c2

 

@学习摘录086:重设容器

——c.assign(b,e) // 重新设置c的元素:将迭代器b和e标记的范围内所有的元素复制到c中。b和e必须不是指向c中元素的迭代器。

——c.assign(n, t) // 将容器c重新设置为存储n个值为t 的元素

——// equivalent to: slist1.clear();

——// followed by slist1.insert(slist1.begin(), 10, “Hiya!”);

——slist1.assign(10, “Hiya!”); // 10 elements; each one is Hiya!

——执行了上述语句后,容器slist1有10个元素,每个元素的值都是Hiya!

摘录有想086:

——assign操作跟赋值操作符的操作原理差不多,都是先清空一个容器,然后再对已清空的容器进行插入操作。

 

@学习摘录087:交换容器

——swap操作实现交换两个容器内所有元素的功能。

——vector<string> svec1(10); // vector with 10 elements

——vector<string> svec2(24); // vector with 24 elements

——svec1.swap(svec2);

——执行swap后,容器svec1 中存储24个string类型的元素,而svec2则存储10个元素。

——关于swap的一个重要问题:

——1.该操作不会删除或插入任何元素;

——2.保证在常量时间内实现交换。

——3.由于容器内没有移动任何元素,因此迭代器不会失效。

摘录有想087:

——对此表示疑惑,为什么没有移动元素就没失效?它的原理是?猜测可能是变了变量的地址,其它一切无发生改变。

 

第四节:vector容器的自增长

 

@学习摘录088:vector的增长效率

——为了使vector容器实现快速的内存分配,其实际分配的容量要比当前所需的空间多一些。

——vector容器预留了这些额外的存储区,用于存放新添加的元素。

——于是,不必为每个新元素重新分配容器。

——所分配的额外内存容量的确切数目因库的实现不同而不同。

——比起每添加一个新元素就必须重新分配一次容器,这个分配策略带来显著的效率。

——事实上,其性能非常好,因此在实际应用中,比起list和deque容器,vector的增长效率通常会更高。

 

@学习摘录089:capacity成员

——弄清capacity(容量)与size(长度)的区别非常重要。

——size指容器当前拥有的元素个数;

——而capacity则指容器在必须分配新存储空间之前可以存储的元素总数。

——vector<int> ivec;

——// size should be zero; capacity is implementation defined

——cout << “ivec: size: “ << ivec.size()

——    << “ capacity: “ << ivec.capacity() << endl;

——// give ivec 24 elements

——for (vector<int>::size_type ix = 0; ix != 24; ++x)

——         ivec.push_back(ix);

——// size should be 24; capacity will be >= 24 and is implementation defined

——cout << “ivec: size: “ << ivec.size()

——  << “ capacity: “ << ivec.capacity() << endl;

——结果:

—— ivec: size: 0 capacity: 0

—— ivec: size: 24 capacity: 32

 

@学习摘录090:选择容器

——下面列举了四种选择容器的法则。

——1. 如果程序要求随机访问元素,则应使用vector或deque容器。

——2. 如果程序必须在容器的中间位置插入或删除元素,则应采用list容器。

——3. 如果程序不是在容器的中间位置,而是在容器首部或尾部插入或删除元素,则应采用deque容器。

——4. 如果只需在读取输入时在容器的中间位置插入元素,然后需要随机记问元素,则可考虑在输入时将元素读入到一个list容器,接着对此容器重新排序,使其适合顺序访问,然后将排序后的list容器复制到一个vector容器。

 

第六节:string类型

 

@学习摘录091:string类型的查找操作

——几乎所有的查找操作,返回的是string::size_type类型的值,以下标形式标记查找匹配所发生的位置;

——当查找没有匹配值,将返回名为string::npos的特殊值。

 

@学习摘录092:string类型的定义(一少部分)

——s.find(args)                      在s中查找args的第一次出现

——s.find_first_of(args) 在s中查找args的任意字符的第一次出现

——s.find_last_of(args)  在s中查找args的任意字符的最后一次出现

——s.find_first_not_of(args)  在s中查找第一个不属于args的字符

——s.find_last_not_of(args)  在s中查找最后一个不属于args的字符

摘录有想092:

——这几个定义,看上去,个人将其归纳两点:

——1. find操作可以分为第一次出现的关键字和最后次出现的关键字进行查找的标准。

——2. find操作可以分为查找属于args的字符和不属于args的字符。

 

 

《C++ Primer》 第10章学习笔记

第10章:关联容器

——关联容器(associative container)支持通过键来高效地查找和读取元素。

 

@学习摘录093:关联容器和顺序容器的本质差别

——关联容器通过键(key)存储和读取元素;

——顺序容器则通过元素在容器中的位置顺序存储和访问元素。

 

@学习摘录094:关联容器的主要特点

——关联容器的独特之处在于支持键的使用。

——关联容器根据键的次序排。

——在迭代遍历关联容器时,我们可以确保按键的顺序访问元素,而与元素在容器中的存放位置完全无关。(与插入顺序也无关)

 

@学习摘录095:关联容器的类型

——1.  map     关联数组;元素通过键来存储和读取

——2.  set           大小可变的集合,支持通过键实现的快速读取

——3.  multimap      支持同一个键多次出现的map类型

——4.  multiset       支持同一个键多次出现的set类型

 

@学习摘录096:两种基本关联容器的基本特点

——1. map的元素以键-值(key-value)对的形式组织。

—— 附:键,用作元素在map的索引。

——2. set仅包含一个键,能有效地支持关于某个键是否存在的查询。

 

@学习摘录097:四种关联容器需要注意的地方

——1. set 和 map类型的对象所包含的元素都具有不同的键,不允许为同一个键添加第二个元素。

——2. multimap和multiset 类型允许多个元素拥有相同的键,即用于一个键必须对应多个实例的情况下。(这两种类型不支持下标运算)

 

第一节:pair类型——#include <utility>

 

@学习摘录098:pair的创建与初始化

——pair<string, int> elem(“OK”, 110);  // holds a string and an int

——pair<string, vector<int> > line;    // holds string and vector<int>

 

@学习摘录099:pair对象的操作

——pair类,可直接访问其数据成员:成员都是公有的,分别命名为first和second

——pair< string, int> elem;

——elem.first = “OK”;

——elem.second = 3;

——cout << elem.first << elem.second; // 就这样,就给它们赋值了。

 

@学习摘录100:生成新的pair对象(make_pair)

——pair<string, string> next_auth;

——string first, last;

——while(cin >> first >> last)

——{

——// generate a pair from first and last

——next_auth = make_pair(first, last);

——// process next_auth..

——}

 

第三节:map类型——#include <map>

——map类型通常可理解为“关联数组”——通过键获取值,键-值相关联。

 

@学习摘录101:键类型的约束

——默认情况下标准库使用键类型定义 < 操作符来实现键的比较。

——所用的比较函数必须在键类型上定义严格弱排序(strict weak ordering)

摘录有想101:

——这也就是说,map<first, second> 中的first类型必须支持 < (小于)操作符。

 

@学习摘录102:map类定义的类型

——map<K, V>::key_type      在map容器中,用作索引的键的类型

——map<K, V>::mapped_type  在map容器中,键所关联的值的类型

——map<K, V>::value_type     一个pair类型,它的first元素具有key_type类型,second元素具有mapped_type类型

 

@学习摘录103:map类定义类型的访问方法及需要注意的地方

——当使用了map<K, V>::value_type时,该类型中的key_type类型是const性质,不可修改。

——当使用了map<K, V>::iterator时,该类型中的key_type类型是const性质,不可修改。

摘录有想103:

——即 map<string, int> ok; map<string, int>::value_type vt; map<string, int>::iterator = ok.begin();

——vt -> first; 只能读取,不能赋值。 iterator -> first也是只能读取不能赋值。

 

@学习摘录104:下标在map中的行为

——假如定义了一个名为ok的map<string, int>对象

——用下标操作ok[“in”] = 12 会有以下结果:

——当in存在,赋ok -> second值为12;

——当 in 不存在,新建(即插入) ”in” 元素并赋值为12.

摘录有想104:

——与顺序容器不同的一点是,用下标访问有可能会使map容器添加一个新的元素。

 

@学习摘录105:map中常用的insert操作

——m.insert(make_pair(“ok”, 12);

——m.insert(map<string, int>::value_type(“ok”, 1);

——m.inert(map<string, int>:: iterator a = m.begin(), map<string, int>::iterator b=++a );

——这三种操作,返回的都是void类型。

 

@学习摘录106:查找map中的元素

——m.find(k); 返回迭代器,存在,返回符合位置的迭代器,不存在,返回超出末端迭代器。

——m.count(k); 返回m中k的出现次数。

摘录有想106:

——count 操作根据map的性质,只能回返回0或1.

 

@学习摘录107:从map中删除元素

——// erase of a key returns number of element removed

—— if(word_count.erase(removal_word) )

——      cout << “ok: “ << removal_word << “ removed\n”;

—— else cout << “oops: “ << removal_word << “ not found! \n”;

——m.erase(k) 删除m中键为k的元素。返回size_type类型的值,表示删除的元素个数。

 

第五节:multimap和multiset类型

 

@学习摘录108:multimap和multiset类型元素的添加

——由于键不要求是唯一的,因此每次调用insert总会添加一个元素。

——multimap<string, string> authors;

——// adds first elemt with key a

—— authors.insert(make_pair( string(“a”), string(“b”) );

——ok: adds second element with key a

——authors.insert(make_pair(string(“a”), string(“c”) );

 

@学习摘录109:multimap和multiset类型元素的删除

——带有一个键参数的erase版本将删除拥有该键的“所有”元素,并返回删除元素的个数。

——multimap<string, string> ::size_type cnt = authors.erase( “a”);

 

@学习摘录110:multimap和multiset类型元素的查找――第一种方法

——直接见实例,当使用iter与find结合时,iter能保存所有find找到的键的结果。

// author we’ll look for

string search_item(“A”);

typedef multimap<string, string>::size_type sz_type;

sz_type entries = authors.count(search_item);

// get iterator to the first entry for this author

multimap<string, string>:: iterator iter = authors.find(search_item);

// loop through the number of entries there are for this author

for(sz_type cnt = 0; cnt != entries; ++cnt, ++iter)

   cout << iter -> second << end;  // print each title

——上例中,count函数求出某键出现次数,而find则返回一个迭代器,指向第一个正在查找的实例。

 

@学习摘录110:multimap和multiset类型元素的查找――第二种方法

——m.lower_bound(k)  返回一个迭代器,指向键不小于k的第一个元素

——m.upper_bound(k)  返回一个迭代器,指向键大于k的第一个元素

// definitions of authors and search_item as above

// beg and end denote range of elements for this author

typedef multimap<string, string>::iterator authors_it;

authors.it beg = authors.lower_bound(search_item);

authors.it end = authors.upper_bound(search_item);

// loop through the number of entries there are for this author

while( beg != end)

{

  cout << beg -> second << endl;   // print each title

  ++beg;

}

摘录有想110:

——这里利用了迭代器取得元素在multimap中的区间,能这样操作是因为,在multimap中,它的元素是以“键”的 < (小于)的顺序排序的

 

@学习摘录111:multimap和multiset类型元素的查找――第三种方法

——m.equal_range(k)  返回一个迭代器的pair对象,它的first成员等价于m.lower_bound(k),它的second成员等价于m.upper_bound(k);

——直接见实例:

typedef multimap<string, string>::iterator authors_it;

// definitions of authors and search_item as above

// pos holds iterators that denote range of elements for this key

pair<authors_it, authors_it> pos = authors.equal_range(search_item);

// loop through the number of entries there are for this author

while(pos.first != pos.second)

{

  cout << pos.first -> second << endl;  // print each title

  ++pos.first;

}

摘录有想111:

——这种使用方法,相当于第二种方法的简化,只是引用了一种pair类型。

 

@学习摘录112:*operator(解引用操作符在map、set、multimap、multiseet)

——在这些应用的时候,解引用操作符将生成一个value_type类型的值。

——对于map和multimap容器,value_type是pair类型。

 

 

《C++ Primer》 第11章学习笔记

第11章:泛型算法

——标准库提供一组不依赖特定的容器类型的算法作用在不同类型的容器和不同类型的元素上。

 

@学习摘录113:算法重要性质

——算法也许会改变存储在容器中的元素的值,也许会在容器内移动元素。

——但是,算法从不直接添加或删除元素。如果需要添加或删除元素,则必须使用容器操作。

 

第二节:初窥算法

—— #include <algorithm>   // 使用泛型算法

—— #include <numeric>    // 泛化的算术算法(generalized numeric algorithm)

 

@学习摘录114:迭代器实参类型

——大多数算法(前两个参数指定范围):

——通常:泛型算法都是在标记容器(或其他序列)内的元素范围的迭代器上操作的。

——标记范围的两个实参类型必须精确匹配,而迭代器本身必须标记一个范围:

——它们必须指向同一个容器中的元素(或者超出容器末端的下一位置),

——并且如果两者不相等,则第一个迭代器通过不断地自增,必须可以到达第二个迭代器。

——有些算法(带有两对迭代器参数)

——每对迭代器中,两个实参的类型必须精确匹配,但不要求两对之间的类型匹配。

——当元素存储在不同类型的序列中时,只要这两个序列中的元素可以比较即可。

 

第三节:再谈迭代器

——C++语言还提供了另外三种迭代器

 

@学习摘录115:三种容器的简单介绍

——插入迭代器(insert iterator):这类迭代器与容器绑定在一起,实现在容器中插入元素的功能。

——iostream迭代器(iostream iterator):这类迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的IO流。

——反向迭代器(reverse iterator):这类迭代器实现向后遍历,而不是向前遍历。

 

@学习摘录116:三种插入迭代器

——这里有三种插入器,其差别在于插入元素的位置不同:

——1. back_inserter, 创建使用push_back实现插入迭代器

——2. front_inserter, 使用push_front实现插入。

——3. insert, 使用insert实现插入操作。

 

@学习摘录117:三种inserter的综合应用

——在使用front_inserter时,元素始终在容器的第一个元素前面插入。

——使用inserter时,元素则在指定位置前面插入。

list<int> ilst, ilst2, ilst3;  // empty lists

// after this loop ilst contains: 3 2 1 0

for (list<int>::size_type i = 0; i != 4; ++i)

   ilst.push_front(i);

// after copy ilst2 contains: 0 1 2 3   // 与原容器顺序相反

copy(ilst.begin(), ilst.end(), front_inserter(ilst2));

// after copy, ilst3 contains: 3 2 1 0  // 与原容器顺序没变

copy(ilst.begin(), ilst.end(), inserter(ilst3, ilst3.begin() ));

 

@学习摘录118:iostream迭代器的构造函数

——istream_iterator<T> in(strm);  创建从输入流strm中读取T类型对象的istream_iterator对象

——istream_iterator<T> in;        istream_iterator 对象的超出末端迭代器

——ostream_iterator<T> in(strm);  创建将T类型的对象写到输出流strm的ostream_iterator对象

——ostream_iterator<T> in(strm, delim); 创建将T类型的对象写到输出流strm的ostream_iterator 对象,在写入过程中使用delim作为元素的分隔符。delim是以空字符结束的字符数组。

 

@学习摘录119:流迭代器的定义

istream_iterator<int> cin_in(cin);   // reads ints from cin

istream_iterator<int> end_of_stream;  // end iterator value

// writes Sales_items from the ofstream named outfile

// each element is followed by a space

ofstream outfile;

ostream_iterator<Sales_item> output(outfile, “ “);

 

@学习摘录120:istream_iterator对象上的操作

——下面的程序,衔从cin中读取int型数据,然后,使用eof作为结束符;

——在循环中,从cin中读取int型数据,并将读入的内容保存在vec中,再实现自增。

istream_iterator<int> in_iter(cin);   // read ints from cin

istream_iterator<int> eof;         // istream “end” iterator

// read until end of file, storing what was read in vec

while(in_iter != eof)

  // increment advances the stream to the next value

  // dereference reads next value from the istream

  vec.push_back(*in_iter++);

——也能直接用构造函数的方法新建对象:如下

istream_iterator<int> in_iter(cin);    // read ints from cin

istream_iterator<int> eof;          // istream “end” iterator

vector<int> vec(in_iter, eof);        // construct vec from an iterator range

 

@学习摘录121:ostream_iterator对象的使用

——可使用ostream_iterator对象将一个值序列写入流中,其操作的过程与使用迭代器在将一个组值逐个赋给容器中的元素相同。

// write one string per line to the standard output

ostream_iterator<string> out_iter(cout, “\n”);

// read strings from standard input and the end iterator

istream_iterator <string> in_iter(cin), eof;  // 此处会先输入第一次

// read until eof and write what was read to the standard output

while(in_iter != eof)

  // write value of in_iter to standard output

  // and then increment the iterator to get the next value from cin

  *out_iter++ = *in_iter++;    // 由于那次已输入了第一次,这次会再要求输入一次再显示

摘录有想121:

——程序经测试,每当输入一行字符后,则在下次输入另一行字符后显示上一行输入的字符。

——该程序可从while语句入手去想,逐行输入,放入in_iter,之后out_iter读取并显示。

——解引操作符能够使用数据。

 

@学习摘录122:与算法一起使用迭代器

——先看下面的例子:

istream_iterator<int> cin_it(cin);   // reads ints from cin

istream_iterator<int> end_of_stream;  // end iterator value

// initialize vec from the standard input:

vector<int> vec (cin_it, end_of_stream);

sort(vec.begin(), vec.end() );

// writes ints to cout using “ “ as the delimiter

ostream_iterator<int> output(cout, “ “);

// write only the unique elements in vec to the standard output

unique_copy(vec.begin(), vec.end(), output);

摘录有想122:

——以前总是看不懂这类程序,我想,现在看完这章的内容后,基本上还是理解了这类程序写的是什么了。

——前两行不断输入,直到遇到结束符,第三行,将输入的全部int逐个放入vec中;

——第四行,对容器进行排序。

——第五行,在每个cout 之间输入” “; 

——最后行,符合算法的格式,unique_copy算法,unique的“复制”版本,将输入范围内不重复的值输出,每个复制值后输出一个空格。

 

第四节:泛型算法的结构

——算法最基本的性质是需要使用的迭代器种类

 

@学习摘录123:根据对元素的操作将算法分为下面三种:

——1. 只读算法,不改变元素的值和顺序。

——2. 给指定元素赋新值的算法。

——3. 将一个元素的值移给另一个元素的算法。

 

@学习摘录124:算法的形参模式

——1. alg(beg, end, other parms);

——2. alg(beg, end, dest, other parms);

——3. alg(beg, end, dest, other parms);

——4.alg(beg, end, beg2, end2, other parms);

——注:dest形参是一个迭代器,用于指定存储输出数据的目标对象。

 

@学习摘录125:迭代器的分类

——迭代器可通过其所支持的操作来分类。

——1. 输入迭代器(input iterator):读,不能写;只支持自增运算

——2. 输出迭代器(output iterator):写,不能读;只支持自增运算

——3. 前向迭代器(forward iterator):读和写,只支持自增运算

——4. 双向迭代器(bidirectional iterator):读和写,支持自增和自减运算

——5. 随机访问迭代器(random-access iterator):读和写,支持完整的迭代器算术运算

 

 

《C++ Primer》第三部分学习笔记汇总——类和数据抽象

 

《C++ Primer》 第12章学习笔记

第12章:类

——在C++中,用类来定义自己的抽象数据类型(abstract data type)。

 

第一节:类的定义和声明

 

@学习摘录126:数据抽象和封装

——类背后蕴涵的基本思想是:数据抽象和封装

 

@学习摘录127:类的数据抽象

——数据抽象是一种依赖于接口和实现分离的编程(和设计)技术。

——类设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节。

 

@学习摘录128 :类的封装

——封装是一项将低层次的元素组合起来形成新的、高层次实体的技术。

——函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中。

——被封装的元素隐藏了它们的实现细节——可以调用一个函数但不能访问它所执行的语句。

——类也是一个封装的实体:它代表若干成员的聚集,大多数(良好设计的)类类型隐藏了实现该类型的成员。

 

@学习摘录129:三种特殊的类型讨论抽象与封装性

——标准库类型vector同时具备数据抽象和封装的特性。

——数组在概念上类似于vector,但既不是抽象的,也不是封装的。可以通过访问存放数组的内存来直接操纵数组。

——标准库中的pair类就是一个实用的、设计良好的具体类而不是抽象类。具体类会暴露而非隐藏其实现细节。pair类型只是将两个数据成员捆绑成单个对象。

 

@学习摘录130:数据抽象与封装的好处

——1. 避免类内部出现无意的、可能破坏对象状态的用户级错误。

——2. 随时间推移可以根据需求改变或缺陷(bug)报告来完善类实现,而无须改变用户级代码。

——仅在类的私有部分定义数据成员,类的设计者就可以自由地修改数据。

——如果类的内部状态是私有的,则数据成员的改变只可能在有限的地方发生。

——如果数据是私有的并且没有改变成员函数的接口,则操纵类对象的用户函数无须改变。

 

@学习摘录131:可变数据成员

——可变数据成员永远都不能为const,甚至当它是const对象的成员时也如此。

——当成员声明为mutable成员时,则能破坏const性质,可以改变类的数据成员。

——如:

class Screen

{

public:

// interface member functions

private:

  mutable size_t access_ctr;   // may change in a const members

// other data members as before

};

 

第三节:类作用域

 

@学习摘录132:函数返回类型不一定在类作用域中

——如果返回类型使用由类定义的类型,则必须使用完全限定名。

class Screen

{

public:

  typedef std::string::size_type index;

  index get_cursor() const;

};

inline Screen::index Screen::get_cursor() const

{

 return cursor;

}

摘录有想132:

——个人看书理解:完全限定名是指全整的命名空间的名字

——如: Screen::index Screen::get_cursor()

 

@学习摘录133:类定义的处理阶段

——类定义实际上是在两个阶段处理:

——1. 首先,编译成员声明;

——2. 只有在所有成员出现之后,才编译它们的定义本身。

 

@学习摘录134:类成员声明的名字查找

——按以下方式确定在类成员的声明中用到的名字。

——1. 检查出现在名字使用之前的类成员的声明。

——2. 如果第一步查找不成功,则检查包含类定义的作用域中出现的声明以及出现在类定义之前的声明。

——例如:

typedef double Money;

class Account

{

public:

  Money balance()  {return bal;}

private:

  Money bal;

  // …

};

——在处理balance函数的声明时,编译器首先在类Account的作用域中查找Money的声明。——编译器只考虑出现在Money使用之前的声明。

——因为找不到任何成员声明,编译器随后在全局作用域中查找Money的声明。

——必须在类中先定义类型的名字,才能将它们用作数据成员的类型,或者成员函数的返回类型或形参类型。

 

@学习摘录135:类成员定义中的名字查找

——按以下方式确定在成员函数的函数体中用到的名字。

——1. 首先检查成员函数局部作用域中的声明。

——2. 如果在成员函数中找不到该名字的声明,则检查对所有类成员的声明。

——3. 如果在类中找不到该名字的声明,则检查在此成员函数定义之前的作用域中出现的声明。

 

第四节:构造函数的初始化式

——从概念上讲,可以认为构造函数分为两个阶段执行

——1. 初始化阶段

——2. 普通的计算阶段。

——不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。

——初始化发生在计算阶段开始之前。

 

@学习摘录136:有时需要构造函数初始化列表

——有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。

——没有默认构造函数的类类型的成员,以及const或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。

 

@学习摘录137:建议使用构造函数初始化列表

——在许多类中,初始化和赋值严格来讲都是低效率的:数据成员可能已经被直接初始化了,还要对它进行初始化和赋值。

——比效率更重要的是,某些数据成员必须要初始化,这是一个事实。

——当类成员需要使用初始化列表时,通过常规地使用构造函数和初始化列表,就可以避免发生编译时错误。

 

@学习摘录138:成员初始化的次序

——成员被初始化的次序就是定义成员的次序

——初始化的次序常常无关紧要。但若一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的。

——建议:按照成员声明一致的次序编写构造函数初始化列表。

——此外,尽可能避免使用成员来初始化其他成员。

 

@学习摘录139:合成的默认构造函数

——只要一个类定义了一个构造函数,编译器再也不会生成默认构造函数。

——只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。

 

@学习摘录140:抑制由构造函数定义的隐式转换

——可以通过将构造函数声明为explicit,来防止在需要隐式转换的上下文中使用构造函数。

——explicit关键字只能用于类内部的构造函数声明上。

——通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit.

 

@学习摘录141:类成员的显式初始化

——显式初始脂类类型对象的成员有三个得大缺点

——1. 要求类的全体数据成员都是public

——2. 将初始化每个对象的每个成员的负担放在程序员身上。

——  这样的初始化是乏味且易于出错的,因为容易遗忘初始化式或提供不适当的初始化式。

——3. 如果增加或删除一个成员,必须找到所有的初始化并正确更新。

 

@学习摘录142:从C语言继承过来类的全部数据成员为public的显式初始化

——见实例:

struct Data

{

  int ival;

  char *ptr;

};

// val1.ival = 0;  val1.ptr = 0;

Data val1 = {0, 0};

// val2.ival = 1024;  val2.ptr = “Anna Livia Plurabelle”

Data val2 = {1024, “Anna Livia Plurabelle” };

 

第五节:友元

——友元(friend)机制允许一个类将其非公有成员的访问权授予指定的函数或类。

——通常,将友元声明成组地放在类定义的开始或结尾是个好主意。

 

第六节: static类成员

 

@学习摘录143:使用类的static成员的三大优点

——1. static成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。

——2. 可以实施封装。static成员可以是私有成员,而全局对象不可以。

——3. 通过阅读程序容易看出static成员是与特定类关联的。这种可见性可清晰地显示程序员的意图。

 

@学习摘录144:定义static成员

——对象没有与static数据成员对应的数据成员,但是,存在一个单独的interestRate对象,由Account类型的全体对象共享。

class Account

{

public:

// interface functions here

void applyint()  {amount += amount * interestRate;}

static double rate() {return interestRate;}

static void rate(double);  // sets a new rate

private:

std:string owner;

double amount;

static double interestRate;

static double initRate();

};

——类外使用:rate = Accout::rate();

 

@学习摘录145:static成员函数

——当我们在类的外部定义static成员时,无须重复指定static保留字,该保留字只出现在类定义体内部的声明处。

void Account::rate(double newRate)

{

         interestRate = newRate;

}

 

@学习摘录146:static函数没有this指针

——static成员是类的组成部分但不是任何对象的组成部分,因此,static成员函数没有this指针。通过使用非static成员显式或隐式地引用this是一个编译时错误。

 

@学习摘录147:static数据成员的初始化

——static数据成员必须在类定义体的外部定义(正好一次)。不像普通数据成员,static成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。

——初始化static数据成员,先指定类型名,接着是成员的完全限定名:

—— double Account::interestRate = initRate();

——这规则有一个例外是,只要初始化式是一个常量表达式,整型const static数据成员就可以在类的定义体中进行初始化。

——如: private:  static cons tint period = 30;

 

@学习摘录147:static成员不是类对象的组成部分

——static成员独立于任何对象而存在,不是类类型对象的组成部分。

——static数据成员的类型可以是该成员所属的类类型。

——如:

class Bar

{

public:

// ….

private:

static Bar mem1;  // ok

Bar *mem2;     // ok

Bar mem3;      // error

};

 

 

《C++ Primer》 第13章学习笔记

第13章:复制控制

——复制构造函数、赋值操作符和析构函数总称为复制控制(copy control)。

——有一种特别常见的情况需要类定义自己的复制控成员的:类具有指针成员。

 

第一节:复制构造函数

——只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为复制构造函数。

——复制构造函数可由编译器隐式调用。

 

@学习摘录148:复制构造函数与默认构造函数

——一般来说,最好显式或隐式定义默认构造函数和复制构造函数。

——只有不存在其他构造函数时才合成默认构造函数。

——如果定义了复制构造函数,也必须定义默认构造函数。

 

@学习摘录149:初始化的复制形式和直接形式

——直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。

——复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。

——有些类的构造函数定义为explicit。如果构造函数是显式的,则初始化失败;如果构造函数不是显式的,则初始化成功。

——class Foo { Foo(const Foo&); // copy constructor };

 

@学习摘录150:合成的复制构造函数

——如果我们没有定义复制构造函数,编译器会为我们合成默认构造函数(synthesized copy constructor)。

——即使我们定义了其他构造函数,也会合成复制构造函数。

 

@学习摘录151:逐个成员初始化(memberwise initialize)

——合成复制构造函数执行逐个成员初始化,将新对象初始化为原对象的副本。

——逐个成员初始化最简单的概念模型是,将合成复制构造函数看作每个数据成员在构造函数初始化列表中进行初始化。

 

@学习摘录152:禁止复制

——有些类需要完全禁止复制,为了防止复制,类必须显式声明其复制构造函数为private。

——如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。

——当声明一个(private)复制函数但不对其定义时,可以连友元和成员中的复制也禁止。

——声明而不定义成员函数是合法的,但是,使用未定义成员的任何尝试将导致链接失败。

 

第二节:赋值操作符

 

@学习摘录153:赋值操作符的定义

——赋值操作符的返回类型应该与内置类型赋值运算返回的类型相同。

——class Sales_item{ Sales_item& operator = (const Sales_item & ); };

 

@学习摘录154:合成赋值操作符

——合成赋值操作符会执行逐个成员赋值:右操作数对的每个成员赋值给左操作数对象的对应成员。

——除数组之外,每个成员用所属类型的常规方式进行赋值。对于数组,给每个数组元素赋值。

 

第三节:析构函数

——析构函数通常用于释放在构造函数或在对象生命期内获取的资源。

 

@学习摘录155:何时编写显式析构函数

——如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验法则,称为三法则(rule of three)。

 

@学习摘录156:合成析构函数

――与复制构造函数或赋值操作符不同,编译器总会为我们合成一个析构函数。

——析构函数与复制函数或赋值操作符之间的一个重要区别是,即使我们编写了自己的析构函数,合成析构函数仍然运行。

——合成析构函数按对象创建时的逆序撤销每个非static成员,因此,它按成员在类中声明次序的逆序撤消成员。

 

第五节:管理指针成员

——包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。

 

@学习摘录157:管理指针成员的方法

——1. 指针成员采取常规指针型行为。 这样的类具有指针的所有缺陷但无需特殊的复制控制。

——2. 类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止悬垂指针。(悬垂指针主要使用的是使用计数技术)

——3. 类采取值型行为。指针所指向的对象是唯一的,由每个类对象独立管理。

摘录有想157:

——之前一直对智能指针不怎么懂,今天,再次看了几个例子以后,自己打了一次代码以后,发觉,原来,这个智能指针也没有想像中的复杂,只是多了一个计数的功能。

 

@学习摘录158:智能指针

——一个行为类似指针但也提供其他功能的类。

——智能指针的一个通用形式接受指向动态分配对象的指针并负责删除该对象。

——用户分配对象,但由智能指针删除它。

——智能指针类需要实现复制控制成员来管理指向共享对象的指针。

——只有在撤消了指向共享对象的最后一个智能指针后,才能删除该共享对象。

——使用计数是实现智能指针类的最常用的方式。

 

 

 

《C++ Primer》 第14章学习笔记

第14章:重载操作符与转换

——这里说一下,这章的学习笔记比较少,因为将本章看了一遍后,感觉可以收益的地方,的确不算多。

 

@学习摘录159:输入和输出操作符最主要区别

——输入和输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性。

 

第九节:转换与类类型

 

@学习摘录160:避免转换函数的过度使用

——转换操作符有两个潜在的缺陷:

——1. 定义太多转换操作符可能导致二义性代码;

——2. 一些转换可能利大于弊。

——避免二义性最好的方法是:

——保证最多只有一种途径将一个类型转换为另一类型。

——做到这一点,最好的方法是限制操作符的数目,尤其中,到一种内置类型应该只有一个转换。

 

@学习摘录161:转换和操作符

——如果类既定义了转换操作符又定义了重载操作符,容易产生二义性。

——1. 不要定义相互转换的类,即如果类Foo具有接受类Bar的对象的构造函数,不要再为类Bar定义到类型Foo的转换操作符。

——2. 避免到内置算术类型的转换。

 

 

《C++ Primer》第四部分学习笔记汇总——面向对象编程与泛型编程

——继承,动态绑定,数据抽象,函数模板

 

《C++ Primer》 第15章学习笔记

第15章:面向对象编程

——面向对象编程基于三个基本概念:数据抽象,继承,动态绑定。

——用类进行数据抽象

——用类派生从一个类继承另一个类:派生类继承基类成员;

——动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数。

 

@学习摘录162:继承与动态绑定的作用:

——能够容易地定义与其他类相似但又不相同的新类,能够更容易地编写忽略这些相似类型之间区别的程序。

 

@学习摘录163:多态性

——面向对象编程的关键思想是多态性(polymorphism)

——继承而相关联的类型为多态类型。

 

@学习摘录164:继承

——派生类(derived class)能够继承基类(base class)定义的成员

——派生类可以无须改变而使用那些与派生类型具体特性不相关的操作

——派生类可以重定义那些与派生类型相关的成员函数,将函数特化,考虑派生类型的特性。

——在C++中,基类必须指出希望派生类重定义哪些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

 

@学习摘录165:动态绑定(dynamic binding)

——我们能够编写程序使用继承层次中任意类型的对象,无须关心对象的具体类型。

 

@学习摘录166:protected成员

——可以认为protected访问标号是private和public的混合:

——1. 像private成员一样,protected成员不能被类的用户访问。

——2. 像public成员一样,protected成员可被该类的派生类访问。

——派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权限。

 

@学习摘录167:派生类类型接口

——简单地说:提供给派生类型接口是protected 成员和public成员的集合。

 

@学习摘录168:派生类

——为了定义派生类,使用类派生列表(class derivation list)指定基类。

——class classname: access-label base-class

——这里的access-label 是public、protected或private, base-class 是已定义的类的名字。

 

@学习摘录169:派生类和虚函数

——一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。

——派生类重定义虚函数时,可以使用virtual保留字,但不是必须这样做。

 

@学习摘录170:virtual与其他成员函数

——C++函数默认不使用动态绑定。

——要触发动态绑定,必须满足两个条件:

——第一,要将成员函数指定为虚函数。(默认的成员函数都是非虚函数)

——第二,要通过基类类型的引用或指针进行函数调用。

 

@学习摘录171:静态类型与动态类型的概念

——静态类型(static type)在编译时可知的引用类型或指针类型。

——动态类型(dynamic type)指针或引用所绑定的对象的类型,这是仅在运行时可知的。

 

@学习摘录172:C++中的多态性

——引用和指针的静态类型与动态类型可以不同,这是C++用以支持多态性的基石。

 

@学习摘录173:派生类函数调用基类版本

——只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制。

——覆盖虚函数机制,最常见的理由是为了派生类虚函数调用基类中的版本。

——派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。

 

@学习摘录174:继承与组合

——定义一作为另一个类的公用派生类时,派生类应反映与基类的“是一种(Is A)”关系。

——在书店的例子中,基类表示按规定价格销售的书的概念,Bulk_item是一种书,但具有不同的定价策略。

——类型之间另一种常见的关系是称为“有一个(Has A)”的关系。

——书店的例子的类具有价格和ISBN.

 

@学习摘录175:友元关系与继承

——基类或派生类可以使其他类或函数成为友元。

——友元可以访问类的private和protected数据。

——友元关系不能继承。

 

@学习摘录176:转换与继承

——每个派生类对象包含一个基类部分。

——可以将派生类对象的引用转换为基类子对象的引用,对指针也类似。

——没有从基类引用(或基类指针)到派生类引用(或派生类指针)的(自动)转换。

——没有从派生类型对象到基类类型对象的直接转换。

 

第四节:构造函数和复制控制

——构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。

 

@学习摘录177:合成的派生类默认构造函数

——派生类的合成默认构造函数与非派生的构造函数只有一点不同:

——除了初始化派生类的数据成员外,还要初始化派生类对象的基类部分。

——基类部分由基类的默认构函数初始化。

 

@学习摘录178:派生类的构造次序

——构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行次序。

——首先初始化基类,然后根据声明次序初始化派生类的成员。

 

@学习摘录179:只能初始化直接基类

——一个类只能初始化自己的直接基类。(直接基类就是在派生列表中指定的类。)

——如果类C从类B派生,类B从类A派生,那B是C的直接基类。

 

@学习摘录180:重构(refactioring)

——重构包括重新定义类层次,将操作和/或数据从一个类移到另一个类。

——为了适应应用程序的需要而重新设计类以便增加新函数或处理其他改变时,最有可能需要进行重构。

——然而,对类进行重构,或以任意其他方式改变类,使用这些类的任意代码都必须重新编译。

 

@学习摘录181:尊重基类接口

——构造函数只能初始化其直接基类的原因是每个类都定义了自己的接口。

——一旦定义了自己的接口,与该类对象的所有交互都应该通过接口。

 

@学习摘录182:定义派生类复制构造函数

——如果派生类显式定义自己的复制构造函数或赋值操作符,则该定义将完全覆盖默认定义。

——被继承类的复制构造函数和赋值操作符负责对基类成分以及类自己的成员进行复制或赋值。

 

@学习摘录183:派生类析构函数

——析构函数的工作与复制构函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。

——每个析构函数只负责清除自已的成员,对象的撤销顺序与构造顺序相反:首先运行派生类析构函数,然后按继承层次依次向上调用各基类析造函数。

——如果析构函数为虚函,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同。

——即使析构函数没有工作要做,继承层次的根类也应该定义一个虚件构函数。

 

@学习摘录184:构造函数和赋值操作符不是虚函数

——在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。

——将类的赋值操作符设为虚函数很有可能会令人混淆,而且不会有什么好处。

 

第五节:继承情况下的类作用域

——在继承情况下,派生类的作用域嵌套在基类作用域中。如果不能在派生类作用域中确定名字,就在外围基类作用域查找该名字的定义。

 

@学习摘录185:名字冲突与继承

——与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。

——可以使用作用域操作符访问被屏蔽的基类成员。

 

@学习摘录186:纯虚函数

——含有(或继承)一个或多个纯虚函数的类是抽象基类(abstract base class)。

——除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象。

 

@学习摘录187:容器与继承

——因为派生类对象在赋值给基类对象时会被“切掉”,所以容器与通过继承相关的类型不能很好地融合。

 

第八节:句柄类与继承

——C++中面向对象编程中一个颇具讽刺意味的地方是,不能使用对象支持面对对象编程,相反,必须使用指针或引用。

 

@学习摘录188:定义包装类和句柄类

——C++中一个通用的技术是定义包装(cover)类或句柄(handle)类。

——句柄类,存储和管理基类指针。

——指针所指对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类型对象。

 

@学习摘录189:包装了继承层次的句柄有两个重要的设计考虑因素:

——1. 像对任何保存指针的类一样,必须确定对复制控制做些什么。

——2. 句柄类决定句柄接口屏蔽还是不屏蔽层次,如果不屏蔽层次,用户必须了解和使用基本层次中的对象。

 

 

 

《C++ Primer》 第16章学习笔记

第16章:模板与泛型编程

——泛型编程,就是以独立于任何特定类型的方式编写代码。

——使用泛型程序时,我们需要提供具体程序实例所操作的类型或值。

 

 

@学习摘录190:补充概念:多态性

——面向对象编程所依赖的多态性称为运行时多态性。

——泛型编程所依赖的多态称为编译时多态性或参数式多态性。

 

@学习摘录191:定义函数模板

——函数模板(function template)是一个独立于类型的函数,可作为一种方式,产生函数的特定类型的版本。

——模板定义以关键字template开始,后接模板形参表(template parameter list)。

——模板形参表是用尖括号括住的一个或多个模板形参(template parameter)的列表。

——模板形参表不能为空。

 

@学习摘录192:使用函数模板——实例化

——产生模板的特定类型的过程称为实例化。

——要进行实例化,编译器必须能够访问定义模板的源代码。

——使用函数模板时,编译器会推断哪个模板实参绑定到模板形参。

——一旦编译器确定了实际的模板实参,就称它为实例化(instantiate)了函数模板的一个实例。

 

@学习摘录193:typename与class的区别

——在函数模板形参中,关键字typename和class相同含义,可互用,只有有时候typename比class要直观。

 

@学习摘录194:链接时的编译时错误

——编译模板时,编译器可能会在三个阶段中标识错误:

——1. 第一阶段是编译模板定义本身时。普通的语法错误类的。

——2. 第二阶段时在编译器见到模板的使用时,检查实参与形参是否相对映。

——3. 第三阶段是在实例化时,只有在这个时候可以发现类型相关的错误。

 

@学习摘录195:类模板中的友元声明

——1. 普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。

——2. 类模板或函数模板的友元声明,授予对友元所有实例的访问权。

——3. 只授予对类模板或函数模板的特定实例的访问权的友元声明。

-—如:

template<typename Type> class Bar

{

  // grants access to ordinary, nontemplate class and function

  friend class FooBar;

  friend void fcn();

};

——FooBar的成员和fcn函数可以访问Bar类任意实例的private成员和protected成员。

摘录有想195:

——假如A类要在B类中使用,那么,要在B类中对A进行友元声明。友元声明不是在需要使用A类时才声明调用,而是在需要使用到的类中先声明。

 

@学习摘录196:成员模板

——任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板(member template),成员模板不能为虚。

 

第六节:模板特化

——模板特化是指一个或多个模板形参的实际类型或实际值是指定的。

 

@学习摘录197:模板特化转换方式

——template <> int compare <const char*> (const char* const & v1, const char* const &v2);

——当调用compare函数的时候,传给它两个字符指针,编译器将调用特化版本。

 

@学习摘录198:普通作用域规则适用于特化

——当编译器看到一个函数调用时,它必须知道这个版本需要特化,否则,编译器可能从模板定义实例化该函数。

――对于具有同一模板实参集的同一模板,程序不能既有显式特化又有实例化。

 

@学习摘录199:显式模板实参

——显式模板实参使我们能固定一个或多个模板形参的类型或值。

——显式实参使我们能够设计无需从对应实参推断模板类型的函数,也使我们能够对实参进行转换。

 

@学习摘录200:模板特化

——模板特化是一种特化的定义,它定义了模板的不同版本,将一个或多个形参绑定到特定类型或特定值。

——对于默认模板定义不适用的类型,特化非常有用。

 

@学习摘录201:泛型句柄类(generic handle class)

——保存和管理指向其他类的指针的类。

 

@学习摘录202:实例化(instantiation)

——用实际模板实参产生模板特定实例的编译器过程,在该实例中,用对应实参代替形参。

 

@学习摘录203:模板特化(template specialization)

——类模板或类模板的成员的重定义,其中指定了模板形参。

 

 

《C++ Primer》第五部分学习笔记汇总——高级主题

 

《C++ Primer》 第17章学习笔记

第17章:用于大型程序的工具

——异常处理,命名空间,多重继承与虚继承

 

@学习摘录204:概念

——大规模编程对程序设计语言的要求往往比小程序员团队更高。

——1. 更严格的正常运转时间以及更健壮的错误检测和错误处理。

——2. 能够用各种库(可能包含独立开发的库)构造程序。

——3. 能够处理更复杂的应用概念。

 

第一节:异常处理

 

@学习摘录205:异常处理的作用

——通过异常我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题。

 

@学习摘录206:抛出类型的异常

——异常是通过抛出(throw)对象而引发(raise)的。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个。

——执行throw的时候,不会执行跟在throw后面的语句,而是将控制从throw转移到匹配的catch.

 

@学习摘录207:被抛出的对象

——被抛出的对象将会自动转换为一个指针,不存在数组或函数类型的异常。

——如果抛出一个数组,被抛出的对象转换为指向数组首元素的指针。

——如果抛出一个函数,函数被转换为指向该函数的指针。

 

@学习摘录208:异常对象

——在处理异常的时候,抛出异常的块中的局部存储不存在了。

——因为在处理异常的时候会释放局部存储,所以被抛出的对象就不能再局部存储了,而是用throw表达式初始化一个称为异常对象的特殊对象。

——异常对象将传给对应的catch,并且在完全处理了异常之后撤销。

——当抛出一个表达式的时候,被抛出对象的解态编译时类型将决定异常对象的类型。

 

@学习摘录209:异常与指针

——用抛出表达式抛出静态类型时,比较麻烦的一种情况是,在抛出中对指针解引用。

——抛出指针的时候,必须确定进入处理代码时指针所指向的对象存在。

——如果抛出指向局部对象的指针,而且处理代码在另一函数中,则执行处理代码时指针所指向的对象将不再存在。

——抛出指针通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。

 

@学习摘录210:栈展开

——沿嵌套函数调用链接继续向上,直至为异常找到一个catch子句,称之为栈展开(stack unwinding)。

——抛出异常时,将暂停当前函数的妨行,开始查找匹配的catch子句。

——首先检查throw本身是否在try块内部

——如果找到匹配的catch,就处理异常

——如果找不到,就退出当前函数,并且继续在调用函数中查找。

——如果一个函数声明没有指定异常说明,则该函数可抛出任意类型的异常。

 

@学习摘录211:栈释放资源

——栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。

——如果一个块直接分配资源,而且在释放资源之前发生异常,在栈展开期间将不会释放该资源。

 

@学习摘录212:析构函数应该从不抛出异常

——在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将导致调用标准库terminate函数。

——一般而言,terminate函数将调用abort函数,强制从整个程序非正常退出。

 

@学习摘录213:异常与构造函数

——与析构函数不同,构造函数内部所做的事情经常会抛出异常。

——类似地,在初始化数组或其他容器类型的元素的时候,也可能发生异常,同样,也要保证将会适当地撤销已构造的元素。

 

@学习摘录214:捕获异常

——catch子句(catch clause)中的异常说明符(exception specifier)是在其后跟一个(可选)形参名的类型名。

——说明符的类型必须是内置类型。

——在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将先中第一个找到的可以处理该异常的catch.

 

@学习摘录215:异常说明符与继承

——如果被抛出的异对象是派生类类型的,但由接受基类类型的catch处理,那么,catch不能使用派生类特有的任何成员。

——通常,如果catch子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。

 

@学习摘录216:重新抛出

——有可能单个catch不能完全处理一个异常。

——catch可以通过重新抛出将异常传递给函数调用链中更上层的函数。

——如果在处理代码不活动时碰到空throw,就调用terminate函数。

 

@学习摘录217:捕获所有异常的处理代码

——若不知道可能被抛出的所有异常,可以使用捕获所有异常catch子句(catch-all)的

——如果catch(…)与其他catch子句结合使用,它必须是最后一个,否则,任何跟在它后面的catch子句都将不能被匹配。

 

@学习摘录218:函数测试块与构造函数

——为了处理来自构造函数初始化式的异常,必须将构造函数编写为函数测试块(function try block).可以使用函数测试块将一组catch子句与函数联成一个整体。

——构造函数要处理来自构造函数初始化式的异常,唯一的方法是将构造函数编写为函数测试块。

 

@学习摘录219:auto_ptr对象

——auto_ptr和内置指针对待复制和赋值有非常关键的重要区别。

——当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态。

 

@学习摘录220:auto_ptr的缺陷

——1. 不要使用auto_ptr对象保存指向静态分配对象的指针。

——2. 永远不要使用两个auto_ptr对象指向同一对象。

——3. 不要使用auto_ptr对象保存指向动态分配数组的指针。

——4. 不要将auto_ptr对象存储在容器中。

 

第二节:命名空间

 

@学习摘录221:命名空间的定义

——命名空间可以在全局作用域或其他作用域内部定义,但不能在函数或类内部定义。

——命名空间名字后面接着由花括号括住的一块声明和定义,可以在命名空间中放入可以出现在全局作用域的任意声明:类、变量、函数、模板以及其它命名空间。

——命名空间作域不能以分号结束。

 

@学习摘录222:命名是可以是不连续的

——与其他作用域不同,命名空间可以在几个部分中定义。

——命名空间由它的分离定义部分的部和构成,命名空间的累积的。

 

@学习摘录223:全局命名空间(global namespace)

——定义在全局作用域的名字(在任意类、函数或命名空间外部声明的名字)是定义在全局命名空间中的。

——全局命名空间是隐式声明的,存在于每个程序中,在全局作用域定义实体的每个文件将那些名字加到全局命名空间。

——可以用作用域操作符引用全局命名空间的成员。

——因为全局命名空间是隐含的,它没有名字,所有记号::member_name引用全局命名空间的成员。

 

@学习摘录224:未命名的命名空间(unnamed namespace)

——未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件。

 

@学习摘录225:命名空间成员的使用

——除了在函数或其他作用域内部,文件不应该包含using指示或using声明

 

@学习摘录226:命名空间别名(namespace alias)

——namespace a = b; 此时a 为b 的别名。

 

@学习摘录227:避免using指示

——using指示注入来自一个命名空间的所有名字,它的使用是靠不住的。

——如果应用程序使用许多库,并且用using指示使得这些库中的名字可见,那么,全局命名空间污染问题就会重新出现。

——相对于依赖于using指示,对程序中使用的每个命名空间名字使用using声明更好,这样做减少注入到命名空间中的名字数目,由using声明引起的二义性错误在声明点而不是使用点检测,因此更容易发现和修正。

 

@学习摘录228:虚继承

——在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。

——class iostream: public virtual ios{…};  class ostream: virtual public ios{…};

——无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。

 

 

《C++ Primer》 第18章学习笔记

第18章:特殊工具与技术

 

@学习摘录229:operator new 函数和operator delete 函数

——0.使用new表达式时实际发生的三个步骤

——1. 首先,该表达式调用名为operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;

——2. 接下来,运行该类型的一个构造函数,用指定初始化式构造对象;

——3. 最后,返回指向新分配并构造的对象的指针。

——0.使用delete表达式时实际发生的两个步骤

——1. 首先,对指针指向的对象运行适当的析构函数

——2. 然后,通过调用名为operator delete 的标准库函数释放该对象所用内存。

摘录有想229:

——简洁一点概括一下吧

—— new -> 分配内存(给指定对象)-> 构造对象 -> 返回对象指针

—— delete -> 指向对象 -> 释放内存 

 

@学习摘录230:定位new表达式(placement new)

——它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。

——new(place_address) type;    new (place_address)type (initializer-list)

 

@学习摘录231:一个内存分配器基类

——预先分配一块原始内存来保存未构造的对象,创建新元素的时候,可以在一个预先分配的对象中构造;释放元素的时候,将它们放回预先分配对象的块中,而不是将内存实际返还给系统。这称为自由列表(freelist)。

 

@学习摘录232:volatile限定符

——volatile的确切含义与机器相关,只能通过阅读编译器文档来理解。

——使用volatile的程序在移到新的机器或编译器通常必须改变。

——关键字volatile是给编译器的指示,指出对这样的对象不应该执行优化。

 

 

 

 

原创粉丝点击