《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输入操作符即可读出每行中的单词。