C++ Primer 4th 读书笔记(第一部分)

虽然,有一定的c++基础(因为本科就学会了这一种,哈哈),但是还是决定系统的读一遍这本书(之前有零星看过数次。。汗)。

留作自己以后参考。(内容会不定期更改,不断学习(此处应为长音~~))

大部分都是自己掌握的不够扎实的地方和以前没有注意的一些细节。

书中好多地方,详述了知识出现的缘由,最起码是指出为了解决什么问题而出现的!!


前言部分

1.“...大量使用了前后交叉引用..."(挺适合自己的,我想知道原文是怎么的,希望以后有机会对原版的时候补充~)

2.作者对本书的定位是本指南性读物。。。

3.作者假定读者已经掌握了一种现代结构化语言!

4.学到足够的知识去创建自己的抽象(于第三到第五部分讨论)


快速入门部分

1.‘操作系统通过main函数返回的值来确定程序是否成功执行完毕。’

2.IDE (Integrated Development Environment) 集成开发环境(书中不介绍~~)

3.注释comment

4.读入未知数目的整数输入(利用 输入操作符>> 返回其左操作数实现)

#include "stdafx.h"
#include<iostream>
using namespace std;


int main()
{
int sum=0,value;
while(cin>>value)
sum+=value;
cout<<sum<<endl;
return 0;

}

  关于输入结束,详见读入未知数目的输入(输入非整数结束)


变量和基本类型

1.基本字符集(char)

   扩展字符集(wchar_t)

2.整型(integral type):表示整数、字符和布尔值的算术类型的合称

3.字面值规则

20 0240X14

20L 20UL 20FL(F单精度)

科学计数法

字符串字面值:为了兼容C语言,C++中所有的字符串字面值都由编译器自动在末尾添加一个空字符。(类型:const char类型的数组)

4.转义字符 \***

5.初始化(创建变量并赋值) 赋值(擦除对象的当前值并用新值代替)

int a = 10;copy-initialization  更灵活且效率更高

int a(10);direct-initialization

6.构造函数(constructor)定义如何进行初始化的成员函数。(可以有多个)

PS:(P43第二小节末一句有点小瑕疵,可以这样理解)有多个初始化参数时不能使用复制初始化。

7.定义(definition)用于为变量分配存储空间,还可以为变量指定初始值。

   声明(declaration)用于向程序表明变量的类型和名字 extern

	int i;				//declares and defines i
	extern int i;		//declares but does not define i
	extern int i = 1;	//definition
8.非const变量默认为extern。const变量默认时是定义该变量的文件的局部变量。

	int i;					//全局变量
	const int i = 1;			//文件的局部变量
	extern const int i = 1;			//const定义时必须初始化

(const变量默认时是定义该变量的文件的局部变量。)why?允许const变量定义在头文件中。见13

const变量定义时必须初始化!(利用定义只有一次,达到不能修改的目的)

可以用const对象初始化非const对象,反之亦然。因为初始化复制了初始化式的值。

9.引用(reference)就是它绑定对象的另一个名字。引用返回左值。主要用作函数的形式参数。

a reference must be initialized, and initializer must be an object. 

当引用初始化后,只要引用存在就保持绑定到初始化时指向的对象。不可能将引用绑定到另一个对象。

复合类型(compound type 用其它类型定义的类型)(注:不能定义引用类型的引用)

10.typedef可以用来定义类型的同义词

typedef的目的之一:一种类型用于多个目的时,使得每次使用该类型的目的明确。

PS :本书部分翻译确实不太准确,虽然我还没看原版,但是感觉是酱紫的。。

11.枚举enumeration:解决数值的相关联问题。

枚举成员值可以是不唯一的。

每个enum都定义一种唯一的类型。枚举类型的对象的初始化或赋值,只能通过其枚举成员或同一枚举类型的其它对象来进行!

12.class和struct关键字定义类的唯一差别在于默认访问级别:默认情况下,struct的成员为public;class的成员为private。

13.头文件(header file)不应该含有定义!

三个例外:类、(值在编译时就已知道的)const对象、inline函数。

原因:编译器需要知道他们的定义来生成代码。

const对象定义在头文件中时,必须是用常量表达式初始化的const对象!ps:后续章节-const对象定义在头文件中(待完善)

14.#include设施是C++预处理器(preprocessor)的一部分。

#include指示只接受一个参数:头文件名。预处理器用指定的头文件的内容替代每个#include。

15.头文件保护符(header guard),用于避免在已经见到头文件的情况下重新处理该头文件的内容。(实际就是防止多重定义)
预处理器指示 #ifndef #define #endif

预处理器变量:有两种状态(已定义和未定义)。不能用预处理器变量给自定义的变量命名(如 a=NULL //error)。


标准库类型

1.抽象数据类型ADT(abstract data type)

2.头文件中应该只定义确实必要的东西。

使用完全限定的标准库名字,即不要using声明。(因为包含头文件的程序也会有此using声明,不管其是否需要)

3.字符串字面值与标准库string类型不是同一种类型。

4.读入未知数目的string对象

<pre name="code" class="html">string word;
//read until end-of-file, writing each word to a new line
while(cin >> word){
	cout <<word<<endl;
}


5.读入未知数目的行

6.不要把string的size操作的返回值赋值给一个int变量!(其返回值为:string::size_type类型,不返回int类型的目的是machine-independent)

保存一个string对象size的最安全的方法就是使用标准库类型string::size_type。

string对象的索引变量最好也用string::size_type类型。

7.标准库类型尽量设计得和基本数据类型一样方便易用。

8.string库类型的赋值操作:释放原内存,分配新内存,复制。

9.C标准库头文件命名形式为name.h,而C++版本的此头文件的命名形式为cname(c表示这个头文件源自C标准库)。例cctype和ctype.h

通常,C++程序应该采用cname这种头文件的版本。

10.vector对象动态增长

11.vector对象的size,返回相应vector类定义的size_type的值。

vector<int>::size_type   //ok
vector::size_type        //error
12.向vector添加元素

string word;
vector<string> text;
while(cin>>word){
<span style="white-space:pre">	</span>text.push_back(word);
}
13.C++程序员习惯于优先选用!=而不是<来编写循环判断条件。(泛型编程)

14.迭代器(iterator)是一种检查容器内元素并遍历元素的数据类型。
现代C++程序更倾向于使用迭代器而不是下标操作访问容器元素。

迭代器(iterator)和迭代器类型(例vector<int>::iterator)

由end操作返回的迭代器指向vector的“末端元素的下一个”(off-the-end iterator)。只是起一个哨兵(sentinel)的作用,表示我们已处理完vector中所有元素。

解引用操作符(*操作符)*iter = 0;

const_iterator

任何改变vector长度的操作都会使已存在的迭代器失效。

15.用string对象初始化bitset对象(从string对象读入位集的顺序是从右向左!)


数组和指针

1.设计良好的程序只有在强调速度时才在类实现的内部使用数组和指针。

2.数组缺点:长度固定;无size操作;不允许直接复制和赋值

但是还是要会~~

3.数组下标的正确类型:size_t

4.缓冲区溢出(buffer overflow)

5.理解指针声明语句时,请从右向左阅读。

6.如果必须分开定义指针和其所指向的对象,则将指针初始化为0.(NULL等效于0,是继承自C的预处理变量,位于cstdlib头文件中)

7.void*指针表面该指针与一地址相关,但不清楚存储在此地址上的对象的类型。只支持有限的操作:

与另一个指针进行比较;

向函数传递void*指针或从函数返回void*指针;

给另一个void*指针赋值。

8.指针和引用的比较

引用总是指向某个对象:定义引用时必须初始化(引用一经初始化,就始终指向同一个特定对象)。

赋值行为的差异:引用赋值修改的是该引用所关联的对象的值。

9.两个指针减法操作的结果是标准库类型ptrdiff_t的数据。在cstddef头文件中定义。

10.只要指针指向数组,就可以对它进行下标操作。

int *P = &ia[2];
int j = p[0];
int k = p[-2];
11.指向const对象的指针(理解为“自以为指向const的指针”),常用作函数的形参(以此来确保传递给函数的实际对象在函数中不因为形参而被修改)

      可以将指向const对象的指针初始化为指向非const对象,但不可以让指向非const对象的指针指向const对象。

12.阅读const声明语句产生的部分问题,源于const限定符既可以放在类型前也可以放在类型后

例:指针和typedef

typedef string *pstring;
//all three decreations are the same type.they are all const pointers to string.
const pstring cstr1;	
string *const cstr2;
pstring const cstr3;
13.C风格字符串(C-style character string)不能确切的归结为C语言的类型,也不能归结为C++语言的类型,而是以空字符null结束的字符数组
C++通过(const)char*类型的指针来操纵C风格字符串。(如果没有null结尾...)

const char *cp = "some value";
while(*cp){
<span style="white-space:pre">	</span>//do something to *cp
<span style="white-space:pre">	</span>cp++;
}
永远不要忘记字符串结束符null

14.自由存储区(free store)或堆(heap):每个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象。

C语言:malloc和free

C++语言:new和delete

15.动态数组

使用跟在数组长度后面的一对空圆括号,对数组元素做初始化。

int *pia = new int[10];<span style="white-space:pre">		</span>//array of 10 uninitialized ints
int *pia2 = new int[10]();<span style="white-space:pre">	</span>//array of 10 int value-initialized to 0
调用new创建长度为0的数组是合法的(但不能被解引用)
char arr[0];<span style="white-space:pre">			</span>//error:can not define zero-length arrary
char *cp = new char[0];<span style="white-space:pre">		</span>//ok:but cp can not be dereferenced
动态空间的释放(内存泄露memory leak)delete[]
16.可以使用C风格字符串对string对象进行初始化或赋值。

需要借助c_str()来实现string对象对C风格字符串的初始化或赋值。

const char *str = st2.c_str();
17.指向多维数组的指针
int *ip[4];//array of pointers to int
int (*p)[4];//pointer to an array of 4 ints
用typedef简化指向多维数组的指针


表达式

1.C++提供了丰富的操作符,并定义操作数为内置类型时,这些操作符的含义。(操作符operator的含义,取决于操作数operand的类型)

unary operator、binary operator、ternary operator

要理解由多个操作符组成的表达式,必须先理解操作符的优先级(precedence)、结合性(associativity)和操作数的求值顺序(order of evaluation)。

precedence:例 低优先级的应用

int i;
while((i = get_value()) != 42){  //get_value() returns an int
//do something
}

associativity:(例:左结合cin>>a>>b  右结合int a=b=0)

order of evaluation:C++中规定了操作数计算顺序的操作符有&&、||、逗号运算符、条件运算符。例见3 short-circuit evaluation

2.操作符%(remainder / modulus)两个操作数为正正、负负、正负、负正时的结果。(后两种取决于机器)

3.短路求值(short-circuit evaluation):逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数的值无法确定该表达式的结果时,才会求解其右操作数。

4.对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned整型操作数。

<<补零,>>依据具体

5.复合赋值操作符(左操作数只计算了一次,而使用相似的长表达式时则计算了两次)

6.只有在必要时才使用后置操作符。(因为前置操作需要做的工作更少)

7.箭头操作符(->)C++为在点操作符后使用的解引用操作定义的同义词。

8.sizeof的一点点说明,注意理解。

将 sizeof 应用在表达式 expr 上,将获得该表达式的结果的类型长度,但是却没有计算表达式 expr 的值!

(特别是在 sizeof *p 中,指针 p 可以持有一个无效地址,因为不需要对 p 做解引用操作。)

9.逗号操作符:结果是其最右边表达式的值;常用于for循环。

10.类型转换:隐式类型转换(implicit type conversion)、算术转换(arithmetic conversion )、显式转换(强制类型转换cast)

1)何时发生隐式类型转换

混合类型的表达式、条件表达式被转换为bool类型、用表达式初始化(或赋值)变量时、函数调用。

2)强制类型转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)(尽量避免使用)

旧式强制类型转换

type (expr);//Function-style cast notation
(type) expr;//C-language-style cast notation


语句

1.使用空语句时应该加上注释,以便任何读这段代码的人都知道该语句是有意省略的。

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

复合语句用在语法规则要求使用单个语句但程序逻辑却需要不止一个语句的地方。(例:循环语句的循环体)->复合语句在语法上是单个语句

3.一个类类型能否用在条件表达式中取决于类本身。(IO类型可以)

4.switch语句提供了一种更方便的方法来实现深层嵌套的if/else逻辑。

case label的执行流:从该点开始执行,并跨越case边界继续执行其他语句,直到switch结束或遇到break语句为止。(故意省略case后面的break语句时,应提供一些注释说明)

default label提供了相当于else子句的功能。(定义default标号是为了告诉它的读者,表面这种情况已经考虑到了)

为了避免出现代码跳过变量的定义和初始化的情况,对于switch结构,只能在它的最后一个case标号或default标号后面定义(作用域为整个switch的)变量。块语句例外。

5.do while语句总是以分号结束。

6.break语句用于结束最近的while、do while、for或switch语句,并将程序的执行权传递给紧接在被终止语句之后的语句。

7.continue语句导致最近的循环语句的当次迭代提前结束。只能出现在for、while或do while循环中。

8.throw表达式、try块(try block)、catch子句(catch clause)/处理代码(handler)、异常类(exception class)

寻找处理代码的过程与函数调用链刚好相反。(balabala)

9.使用预处理器进行调试

思想:程序所包含的调试代码仅在开发过程中执行。当应用程序已经完成,并且准备提交时,就会将调试代码关闭。

int main(){
#ifndef NDEBUG
cerr<< "starting main"<<endl;
#endif
//...
使用NDEBUG预处理变量以及assert预处理宏进行调试。

与异常(异常用于处理程序执行时预期要发生的错误)不同,程序员使用assert来测试“不可能发生”的条件。


函数

形参(parameter)、调用操作符(call operator即一对圆括号)、实参(argument)

1.函数调用(做了两件事情):用对应的实参初始化函数的形参;并将控制权转移给被调用函数。主调函数(calling function)的执行被挂起,被调函数(called function)开始执行。

2.在C语言中,具有const形参或非const形参的函数并无区别。C++为了兼容C,所以、、、

3.复制实参的局限性。解决办法:将形参定义为引用或指针类型。(从C语言背景转到C++的程序员习惯通过传递指针来实现对实参的访问。在C++中,使用引用形参则更安全和更自然)

4.非const形参:避免复制实参;且使用引用形参返回额外的信息(也可通过额外的引用参数返回)。

const形参:避免复制实参,且防止修改相应实参。

应该将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时不太灵活:既不能用const对象初始化,也不能用字面值或产生右值的表达式实参初始化。

5.传递指向指针的引用

void ptrswap(int *&v1, int *&v2){  //int *&v1的定义应从右至左理解:v1是一个引用,与指向int型对象的指针相关联。
int *tmp = v2;
v2 =  v1;
vi = tmp;
}
交换指向对象的指针,来代替,交换指针指向的对象~(提高性能?只是某些时候吧?)
6.C++更倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器。

7.数组形参,编译器忽略任何数组形参指定的长度。

通过引用传递数组,数组大小成为形参和实参类型的一部分,编译器检查数组实参的大小与形参的大小是否匹配。

f(int (&arr)[10]) //arr is a reference to an array of 10 ints
f(int &arr[10])   //arr is an array of references
8.多维数组的传递:编译器忽略第一维的长度。

void printValues(int matrix[][10], int rowSize)
9.任何处理数组的程序都要确保程序停留在数组的边界内。有三种常见的编程技巧:

1)在数组本身放置一个标记来检测数组的结束。(例如,C风格字符串)

2)传递指向数组第一个和最后一个元素的下一个位置的指针。(标准库中的技术)

3)将第二个形参定义为表示数组的大小。这种用法在C程序和标准化之前的C++程序中十分普遍。

10.省略符形参:暂停了类型检查机制。

11.return语句

没有返回值的函数:return;(或者隐式的)

具有返回值的函数:return expression;

在含有return语句的循环后没有提供return语句是很危险的,因为大部分的编译器不能检测出这个漏洞。
千万不要返回局部对象的引用或指针!

12.函数原型(function prototype)=函数返回值+函数名+形参列表

13.默认实参:既可以在函数声明也可以在函数定义中指定默认实参。但是在一个文件中,只能指定一次默认实参。

通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。(另:只有文件中包含默认实参时,默认实参才是有效的)

14.自动对象(automatic object)、static局部对象(static local object)

size_t count_calls(){
<span style="white-space:pre">	</span>static size_t ctr = 0;
<span style="white-space:pre">	</span>return ++ctr;
}
15.内联函数避免函数调用的开销(调用函数要比求解等价表达式要慢得多)。

将函数指定为内联函数,就是讲它在程序中每个调用点上“内联地”展开。

内联机制适用于优化小的、只有几行的而且经常被调用的函数。

内联函数应该在头文件中定义!

16.类的成员函数:this指针、常量成员函数(const member function)、构造函数(constructor)

this指针,每个成员函数(除了static成员函数外)都有一个额外的、隐含的形参this。

常量成员函数(const member function),const this指针

构造函数的初始化列表(constructor initializer list)

合成的默认构造函数(synthesized default constructor),不会自动初始化内置类型的成员!一般适用于仅包含类类型成员的类。

17.重载函数(overloaded function):出现在相同作用域中、具有相同的名字、形参表不同

函数重载(function overloading)与重复声明(redeclaration)的区别

1)重载与作用域

一般来说,局部地声明函数是一种不明智的选择。(会屏蔽而不是重载在外层作用域中声明的同名函数)

在C++中,名字查找发生在类型检查之前。

2)函数重载确定(overload resolution,即函数匹配function matching)是将函数调用与重载函数集合中的一个函数相关联的过程。

3)函数重载确定的过程:

第一步,确定该调用所考虑的重载函数集合。该集合中的函数称为候选函数(candidate function),它是与被调函数同名的函数,并且在调用点上它的声明可见。

第二步,选择可行函数(viable function):从候选函数中选择一个或多个函数,它们能用该调用中指定的实参来调用。

第三步,寻找最佳匹配

4)实参类型转换

精确匹配(exact match)

通过类型提升(promotion)实现的匹配

通过标准转换(standard conversion)实现的匹配

通过类类型转换(class-type conversion)实现的匹配

5)可以基于函数的引用或指针形参是指向const对象还是指向非const对象,实现函数重载。(不能基于指针本身是否为const来实现函数的重载)

仅当形参是引用或指针时,形参是否为const才有影响

18.指向函数的指针

bool (*pf)(const string &, const string &);//pf points to function
bool *pf(const string &, const string &);  //pf is a function that returns a bool*
1)用typedef简化函数指针的定义(见下例)

2)可用函数名对函数指针做初始化或赋值(见下例)。(直接引用函数名等效于在函数名上应用取地址操作符)

3)指向函数的指针,可以不需要使用解引用操作符直接通过指针调用函数:

typedef bool (*cmpFcn)(const string &, const string &);
cmpFcn pf = lengthCompare; //lengthCompare is the name of a function
<pre name="code" class="html">lengthCompare("hi","bye"); //direct call
pf("hi","bye");
(*pf)("hi","bye");


4)函数指针的形参

5)返回指向函数的指针(理解的最佳方法:从声明的名字开始由里而外理解)

6)指向重载函数的指针

ps:4、5、6待补充。


标准IO库

1.IO类型通过继承关联,所以可以只编写一个函数,而将它应用到三种类型的流上:控制台、磁盘文件、字符串流。

2.出于某些原因,标准库类型不允许做复制和赋值操作(阐述类和继承时说明原因)。有两层含义:

1)不存在存储流对象的指针或引用;(只有支持分支的元素类型可以存储在vector或其他容器类型里)

2)形参和返回类型也不能为流类型。(传递和返回IO对象时,需要用引用和指针)







郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。