《完美C++》第5版 (美)Walter Savitch,Kenrick Mock 萨维奇//默克 著 薛正华,沈庚,韦远科 译 出版社: 电子工业出版社
时间2019/4/11-5/14?待定一个月
第一章 C++基础
分析机不是什么问题都能解决,它只能根据我们告诉它的方式完成事情。它可以遵循我们给出的分析,但没有能力对新的分析关系或事实预测。归根到底,它只是帮助我们去做一些我们已经知道的事情。——奥古斯塔.艾达.洛夫莱斯
1.1 C++简介 总结:
语言是科学唯一的工具。——塞缪尔.约翰逊
1.C++内存管理机制:C++内存管理和C语言类似,程序员自己负责内存的申请和释放。
注意:由于C语言是C++的一个子集,因此大多数编译器允许C++程序采用C风格的内存管理方式。但是,C++有自己风格的内存管理方式,并且在编写C++程序的时候,建议采用C++风格的内存管理方式。
2.所有过程式的实体都被成为函数。其他语言中的过程、方法、函数和子程序,在C++中都被统称为函数。
注意:程序执行时,运行时会自动调用main函数。
3.int main()表明main是一个无参数、返回值为int的函数。
注意:有些编译器允许省略上面的int或用void替代,表明该函数没有返回值。但是上面的方式是C++程序中开始main函数最规范形式。
4.return 0;这个代码的执行标志着程序运行的终止。在main函数中结束main函数的运行,并设置函数的返回值为0。
注意:根据ANSI/ISO C++标准,该语句不是必需的,但是目前许多编译器要求该语句。
5.cout << "How many programming languages have you used?"; //输出引号的文本到屏幕。
cin >> numberOfLanguages;//要求用户输入一个数字,并将其存入变量numberOfLanguages中。
注意:引号部分的内容被称为字符串,或者更确切地说,C风格的字符串。建议更多使用string类来操作字符串,而不是C风格的字符串。
1.2 变量、表达式及赋值语句 总结:
一个人一旦理解了编程中变量的使用方式,那么他也就掌握了编程的精髓。——艾兹格.迪科斯彻,《结构化编程注解》
1.变量以及其他在程序中定义的项目的名字被称为标识符。
注意:1)C++中的标识符必须以字母或下划线开始,其余部分包括字母、数字或者下划线。虽然以下划线开头的标识符是合法的,但是不建议这么做,因为一般来讲,以下划线开头的标识符约定为系统或者标准函数库中的标识符。
2)C++区分大小写,因此 rate,RATE,Rate为三个不同的标识符。但是为了避免混淆,不建议在将其用在同一个程序中。
3)另外,作为约定,变量的首字母要求小写。预定义标识符,如main,cin,cout等,必须全为小写。针对面向对象编程,约定采用大小写混合的方式,变量首字母小写,词界采用大写字母标识。如:topSpeed,timeOfArrival
4)C++中标识符没有长度的限制,但是具体的编译器会忽略一定长度之后的所有字符。
2.有一些特殊的标识符叫关键字或者保留字。这些标识符预先在C++中定义,有特殊的用途,不能用作变量名或其他用途。
注意:一些预先定义的标识符,如cin和cout都不是关键字。它们不是C++语言核心,可以对它们进行重定义。虽然这些预先定义的标识符不是关键字,但是却在C++的标准库中进行了定义。若用作一般用途,会导致很多问题,应尽量避免。最安全和方便的做法是将所有预定义的标识符都当作关键字。
3.C++中每个变量在使用之前都必须先声明。变量的声明其实就是告诉编译器,该变量将存储声明类型的数据。
注意:1)声明中出现多个变量的时候,变量之间采用逗号分隔,且声明采用分号结尾。
2)变量的声明可以放在任何地方进行,当然,考虑到程序的可读性,变量应该放在一个合适的地方。一般来讲,变量的声明要么放在变量使用之前,要么放在代码块的起始部分。
3)除了保留字,所有合法的标识符都可以用作变量名。
4)C++中,变量的声明和定义是不同的。声明一个变量时,仅仅是引入该变量的名字和类型;而定义一个变量时,会分配给该变量存储空间。就本书所提到的绝大多数变量,声明一个变量包含了变量的声明和变量的定义,也就是说为变量分配了存储空间。
4.基本数据类型:char类型可以被视为整数类型,但是并不提倡这种用法。浮点类型用得最多的是double,除非有其他原因,才用到其他类型的浮点类型。bool类型有true和false两个值,它不是整数类型,但为了兼容旧的代码,它可以与任意一种数据类型互相转换。
注意:每种整数类型都有一个unsigned的版本,这种版本只包含非负值,它们在非负值域的取值范围要比short,int和long大很多(因为它们与对应的short,int和long类型相比,占用的存储空间相同,但是不用表示负数域的数值)。一般情况下,我们可能不会用到这些类型,但在一些C++库的预定义函数声明中碰到它们。
5.赋值语句计算时,首先计算等号右边表达式的值,然后将该表达式的值赋给等号左边的变量。
注意:1)C++中可以将赋值语句用作表达式。但是建议最好不要用作表达式。如:n = m =2;容易写成n = m + 2;
2)Variable Operator = Expression等价于:Variable = Variable Operator (Expression),其中Expression可以是一个变量、常数或一个复杂的算数表达式。如:amount *= cnt1 + cnt2; 等价于amount = amount *cnt1 + cnt2;
6.C++没有表示字符串的基本类型,但是提供了string类,用于对字符串进行各种操作和处理。
注意:要使用string类,必须在程序中引入string库:#include <string>
使用方法举例:string fruit;
fruit "durian";
7.陷阱:未初始化变量
事实上,变量没有初始化比变量没有值更糟糕。未初始化的变量,含有一些毫无意义的值,这些值由对应的内存状态决定(或者是内存的一种随机状态,或者是最近一个使用这部分内存的程序留下来的)。
注意:避免未初始化变量两种方式:1)在变量声明的同时对变量进行初始化。如:double rate = 0.07, time, balance = 0.00;
2)double rate(0.07), time, balance(0.00);
8.数据类型与布尔类型:bool类型的值可以存放在整数类型的变量中,同时整数类型的值可以存放在bool类型的变量中。
注意:1)将一个整数赋给bool类型的变量时,任何非零的整数都会被转化为true,而零会被转化为false。将一个bool类型的值赋给一个整型变量时,true被转化为1,而false则转化为0。
显然这是一种非常不好的编程风格,因此不建议用。
2)bool类型只有两个常量:true和false。且都为小写。
9.科学计数法 如:0.00000589最好表示成5.89e-6,其中e代表指数,表示乘以10的几次方,e既可以大写也可以小写。
注意:1)可以将字母e后边的数字理解成小数点移动的位数和方向。
2)字母e之前的数字可以包含小数点,也可以没有。但是e之后的数字一定为整数。
10.double双精度——8字节,float单精度——4字节,long double——10字节
11.char symbol = 'z';和cout << "How many programming languages have you used?";
注意:1)字符和字符串左边和右边的引号(单引号或双引号)完全是一个字符。
2)字符常量用单引号括起来,字符串常量用双引号括起来。所以,'A'和"A"是不一样的,"A"是字符串常量,只不过字符串只有一个字符。
12.反斜杠\告诉编译器,紧跟其后的字符含义发生了变化,这样的序列被称为转义序列,其中反斜杠和字符之间没有空格。
注意:1)如果想在一个字符串产量中包含一个反斜杠或双引号,必须用“\\”代替“\”,用“\''”代替“"”,以便对反斜杠和双引号进行转义。
2)字符串中一个零散的反斜杠,比如\z,在不同的编译器中含义不同。有些仅仅是简单的返回z,有的则认为这是一个错误。ANSI/ISO标准知名,未指定的转义序列具有未定义的行为,这意味着编译器可以做任何合适的事情,结果造成使用了未定义转义序列的代码不可移植。因此,不建议使用C++标准之外的转义序列。
3)\n换行符,\r回车,\t水平制表符,\a警告,\\反斜杠,\'单引号,\“双引号,后边几个很少使用: \v垂直制表符,\b空格,\f换页,\?问号
13.const:在变量前添加const关键字,可以使一个变量不可变,当程序尝试更改这些变量时,就会产生错误。如:const int BRANCH_COUNT = 10, WINDOW_COUNT = 10;
注意:关键字const又被称为修饰符,因为它改变了该被修饰变量的性质。由const修饰符修饰的变量通常被称为修饰常量。尽管C++并不要求将声明常量全部大写,但这是程序员的一个默认风格。
14.陷阱:全整数除法
注意:1)如果除法两个操作数都是int类型,除法操作只返回运算结果的整数部分。如:9/2 ->4,结果没有进行四舍五入,小数点后边的结果不管其大小,统统丢弃。另外,由于这种情况没有任何的提示和警告,很容易被忽略,因此往往看似正确的程序却产生了错误的结果。而求余运算符%弥补了整数算法丢失的信息。
2)对于负整数而言,/操作和%操作的结果会因C++实现的不同而不同。因此,应该在确保两个操作数都为非负整数的情况下,才对其采用/和%操作。
15.类型转换:考虑一般情况,m/n,其中m和n为int类型的整型变量,当m和n分别为9和2时,就特殊化为上面的表达式,为了使m/n进行浮点操作,就必须进行类型转换,将int类型转换为浮点类型。
如下所示:double ans = n/ static_cast<double>(m);所以9/2.0 ->4.5
注意:1)static_cast<double>(m)并不会改变输入变量m的值,如果表达式求值之前,m的值是2,那么表达式求值之后,m的值依然是2.
2)将浮点类型转换为整数时,会直接舍弃小数部分,而不会进行四舍五入。如:static_cast<int>(2.9)的结果是2,而不是3。
3)C++常用的四种类型转换的方法:static_cast<Type>(Expression),const_cast<Type>(Expression),dynamic_cast<Type>(Expression),reinterpret_cast<Type>(Expression),第一种最常用。const_cast用于去除变量的const属性。dynamic_cast用在继承层次中,将一种类型安全地转换为派生类型。renterpret_cast很少使用,而且其含义与具体的编译器相关。本书只讨论static_cast
4)旧的类型转换两种用法:1)将类型名像函数那样使用,如int(9.3) ->9,double(42) -> 42.0 2)将double(42)写成(double)42。
5)虽然C++保留了旧的类型转换方法,但是推荐使用新类型转换方法。如:double d = 5;这种把整数5自动转换为5.0,此为强制类型转换
16.陷阱:求值顺序——在一个复杂的表达式中使用自增和自减运算符是危险的。
注意:1)自增和自减运算符只适合单个变量,不能用于表达式。如:(x+y)++、--(x+y)、5++都为非法的表达式
2)n + (++n)假如n初值是2,如果按照从左到右的顺序,该表达式为2+3;如果按照从右至左的顺序,该表达式为3+3。由于C++没有规定子表达式的求值顺序,因此这个表达式的值可能为5,也可能为6。
例外:C++中有些运算符如&&(and)、||(or)和逗号运算符都是按照从左到右的顺序进行求值的,如(n <= 2) && (++n > 2)中&&保证了n<=2先求值为true,++n>2也为true,从而整个表达式的值为true。
3)不要将运算符的优先级顺序与表达式的求值顺序混为一谈。(n+2) * (++n) + 5中虽然总是表示((n+2) * (++n)) + 5,但是,子表达式++n和(n+2)的计算顺序并不确定。不同的编译器有不同的顺序。
1.3 控制台输入/输出 总结:
进来是垃圾,出去也是垃圾。——一句谚语
1.
注意:
2.
注意:
3.
注意:
1.4 编程风格 总结:
在非常重要的事情上,最要紧的并非真诚,而是格调。——奥斯卡.王尔德,《不可儿戏》
1.
注意:
2.
注意:
3.
注意:
1.5 库与命名空间 总结:
1.
注意:
2.
注意:
3.
注意:
第二章 流程控制
“您能告诉我,从现在开始我该往哪个方向走吗?”“这很大程度上取决于你想得到怎样的结果。”猫说。——刘易斯.卡罗尔,《爱丽丝漫游仙境》
2.1 布尔表达式 总结:
要想区分真伪,首先必须知道什么是真,什么是伪。——贝尼迪.斯宾诺沙,《伦理学》
1.
注意:
2.
注意:
3.
注意:
2.2 分支机制 总结:
如果你遇到一个好的机遇,就请牢牢抓住。——尤吉.贝拉
1.
注意:
2.
注意:
3.
注意:
2.3 循环 总结:
有些任务与其说是家务劳动,不如说是科林斯王的折磨,其无休止地重复:一遍又一遍,一天又一天。——西蒙娜.德.波伏娃
1.
注意:
2.
注意:
3.
注意:
2.4 文件输入简介 总结:
你会在美丽的四开纸上看到那些文字,整齐的文字宛如那小溪蜿蜒穿过草地的边缘。——理查德.布林斯莱.谢立丹,《造谣学校》
1.
注意:
2.
注意:
3.
注意:
第三章 函数基础
幸福来自点滴的积累。——一句俗语
3.1预定义函数 总结:
不要重新发明轮子。——一句俗语
1.函数库的使用:1)在程序中用include指令引入该函数库 2)采用using指令包含std命名空间
注意:所有预定义的函数均要求使用include指令和using std指令,使用预定义函数要注意其实参类型,返回类型及头文件
但是,某些系统下的函数库,为了程序的正常编译和运行,还需设定相关编译选项和链接选项。具体的细节随系统的不同而不同;遇到上述问题,请查阅相关操作手册或请教专业人士寻求帮助。
2.相关的函数库:1)输入cin输出cout ->iostream,
2)sqrt(开根号)函数,pow指数(乘方)运算,fabs(double类型的绝对值),ceil向上取整,floor向下取整-> cmath,
3)绝对值函数abs(int类型的绝对值)和labs(long类型的绝对值) ,exit(结束程序),rand(随机数),srand(设置随机数因子)-> cstdlib
注意:1)有三个求绝对值的函数,fabs(double类型的绝对值)-> cmath,绝对值函数abs(int类型的绝对值)和labs(long类型的绝对值) -> cstdlib
2)pow(3.0,2.0) -> 9.0,pow返回值为double类型,且需要两个参数,第一个参数为负,第二个参数就必须为整数。为安全起见,建议使用pow时,确保第一个参数为正
3)自动类型转换,结果不一定是我们想要的,但有一个例外,int类型向double类型转换是自动的
3.exit函数调用将立即结束程序的执行。
注意:exit包含一个int类型的参数,这个参数被传递给操作系统。就C++本身而言,可以使用任何int数值作为参数。但习惯上,由于错误造成的exit调用使用参数1,其他情况则使用参数0
4.随机数生成器。rand()->没有参数,返回值返回一个位于0和RAND_MAX之间的整数,包括0和RAND_MAX。RAND_MAX是一个预定义的整数常量,定义在cstdlib库中,具体的值与系统有关,当一般最小为32767。
注意:比例缩放,如果输出的10个随机数为0~10之间:rand( ) % 11
5.伪随机数:随机数生成器(如函数rand)并不产生真的随机数。函数rand的一系列调用,将产生一系列看似随机的数字。如果使计算机回到调用开始时的状态,重新调用一遍的话,就可以得到一系列相同的“随机数”
1)伪随机数的产生通常由一个被称为种子的数决定,如果以同一个seed启动随机数生成器,不管调用多少次,得到的都是相同的一组数。可以通过srand函数来设定rand的seed。
注意:给定一个seed之后产生的一系列伪随机数是随系统的不同而不同的,同一个系统使用相同的seed总是相同的。
2)函数rand没有参数,srand有一个参数,类型为unsigned int,即必须为非负整数
3)产生一个位于0.0~1.0之间的伪随机浮点数:(RAND_MAX -rand())/static_cast<double>(RAND_MAX)
3.2 自定义函数 总结:
定制西装总是比成衣更适合自己。——我的叔叔,裁缝师
1.自定义函数的实现分为两部分:1)函数声明(又称函数原型) 如:double totalCost(int numberParameter, double priceParameter); //第一个double指明了函数返回值的类型,totalCost为函数的名字,函数totalCost有两个参数:第一个为int类型,第二个为double类型。标识符numberParameter和priceParameter成为形参,或简称参数,形参可以是任何合法的标识符。
2)函数调用 如:bill = totalCost(number, price); //number和price为实参
注意:1)函数声明以分号结束;且函数声明必须先于函数调用,一般放在main函数之前。
2)函数调用以分号结束,这告诉编译器这是一条可执行的语句。
3)函数调用之前必须首先给出该函数的完整定义或者对该函数的声明。最常见的安排是在main部分之前进行相关的声明,而函数的定义则出现在另外的文件中。
4)即使函数没有参数,函数声明和函数调用中也要记得加上括号。
5)形参是一种占位符,函数调用时,实参将填充形参。
2.函数定义由函数头和函数体组成,函数定义描述了函数如何计算出返回值。
注意:1)函数体可以包含任何合法的C++语句,并在函数调用时执行。因此带有返回值的函数除了返回一个值之外,还可以完成其他操作。但一般来讲,调用带返回值的函数的主要目的是获得返回值。
2)函数体可以包含对另一个函数的调用。可以在函数定义中包含其他函数调用,但不能在一个函数定义中包含另一个函数定义。
3.return语句由关键字return和一个表达式组成。如:return (subtotal + subtotal * TAXRATE);
注意:1)括号可以省略,为了统一起见,建议为了易于阅读,加上。
2)return语句后边没有任何语句,但如果有,这些语句不会被执行。return语句的执行标志着函数调用的结束。
3)return语句以“;”结束
4)函数的返回值可以是bool类型,调用这样的函数会得到一个true或false, 如:return (((rate >= 10) && (rate < 20) ) || (rate ==0)));
5)如果想终止函数执行,可以用return;
4.函数声明的另一种形式:如:double totalCost(int numberParameter, double priceParameter);和double totalCost(int, double);等价
注意:1)通常使用第一种,因为这样可以对函数做详细的解释,如给出每个形参的描述信息。第二种多见于编程手册中。
2)这种形式仅限于函数声明,函数定义必须给出相关的形参名字。
5.陷阱:参数顺序的错误
虽然计算机会检测每个实参的类型,但并不会检查其合理性。如果实参的顺序弄混,计算机不会按你希望的那样运行。如果类型不匹配且不能进行相关的类型转换,计算机将报错。
6.示例:一个四舍五入的函数
说明:预定义函数没有包括对一个数的四舍五入的函数。函数ceil和floor有类似的功能,但不是四舍五入的函数。
函数ceil返回邻近的最大整数, 如:ceil(2.1) ->3.0而不是2.0。函数floor返回邻近的小于或等于实参的整数。如:floor(2.1) ->2.0而不是3.0
思路:使用floor函数,将原数值加0.5后进行判断,如2.4四舍五入2,floor(2.4+0.5)->2.0->再转换为int:2,2.6四舍五入3,floor(2.6+0.5)->3.0->再转换为int:3
实现:
int round(double number){
return static_cast<int>(floor(number + 0.5));
}
7.void函数的定义与带返回值的函数仅有两点区别:1)在函数返回值类型的地方用void代替,这告诉编译器这个函数没有返回值;
2)void函数的函数体中没有return语句。函数体中的最后一条语句的执行代表着函数的结束。
注意:1)C++中有两种函数:带有返回值的函数和void函数。
2)虽然void函数的结尾不需要添加return语句,但这并不代表void函数不需要return语句,有时候return语句还是必不可少的。如P99酒店管理程序,当就餐客人数为0时,if语句后的return将结束函数的执行,避免除数为0的情况。
8.编写函数注释的好办法是将其分为两部分:前提条件(precondition)和运行结果(postcondition)
如:void showInternet(double balance, double rate);
//前提条件:balance是非负的存款余额。
//rate是以百分数表示的利率,如5代表5%。
//运行结果:屏幕上输出相应的利息数。
注意:precondition和postcondition可以省略,但在设计函数及编写函数声明的注释的时候,应该按照precondition和postcondition这种方式进行考虑。
9.main函数中的return 语句可有可无?
虽然C++标准规定可以省略程序中main部分的return 0语句,但很多编译器都要求必须包含return 0,并且几乎所有的编译器都允许其存在。考虑到程序的可移植性,编写程序时应该总是在main部分中包含return 0语句。可以将程序的main部分看作是一个返回值为int的函数,因此需要包含一个return语句。将程序的main部分看作是一个返回整数的函数,这似乎不好理解,但很多编译器都这么处理。
注意:1)虽然有些编译器允许,但在你的代码中不应该对main函数进行调用。只有系统才能调用main函数,也就是在程序运行的时候。
2)C++允许定义递归函数,但是main函数不能被递归调用。
3.3 作用域规则 总结:
让最终的结局合法,让它在宪法的范围内......——约翰.马歇尔,美国最高法院首席大法官《麦克洛克诉马里兰案》(1819)
1.函数定义内部声明的变量被称为该函数的局部变量,它的作用域局限于函数的范围或该函数的作用域为该函数内部。
注意:1) 如果一个变量局限于某个函数,有时简称其为局部变量,而不特指是哪个函数的局部变量。
2)如果一个变量是局部变量,那么就可以在其他函数定义中声明同名的变量,它们将会是不同的变量,即使名字相同(特别指出的是,即使其中一个是main函数,也是如此)。
2.编程人员在使用函数时必须知道该函数能完成什么样的工作,但没有必要探究函数实现的细节。这种思想通常是将函数当作“黑箱”进行理解的。以这种黑箱思想来编写和使用函数又称为过程抽象,C++中则成为函数抽象。
注意:如何去编写一个符合黑箱原则的函数:1)函数声明的注释应该告诉程序员参数所要求的所有条件,并且描述函数运行后的结果。
2)函数体内使用的所有变量都应该在函数体中声明(形参除外)。
3.全局命名常量;1)对于常量使用const修饰符,2)语句放在程序的开头,在所有的函数体之外(包括main函数)
全局变量:不适用const修饰符,可以被文件中所有的函数定义所使用。
注意:全局命名常量放在程序的开始位置可以使程序易于阅读,而且如果需要修改这些命名常量,很容易修改,但全局变量很少使用,而且它会使程序变得难以理解和维护,因此应尽量避免使用。
4.复合语句是在一对大括号中的语句(C++代码),含有声明的复合语句通常被称为语句块,事实上,语句块和复合语句是同一个事物的两个名字。
注意:1)但是,当我们的重点在复合语句中的声明时,一般使用语句块这一术语,而且我们称这些语句块中声明的变量限定于这个语句块。
2)函数定义中的函数体就是一个语句块。因此,函数定义中的局部变量和语句块中的局部变量是相同的。
5.提示:在函数和循环语句中使用函数调用
原因switch和if-else语句允许在每个分支中使用不同的语句,如果使用复合语句,会使很难理解,更好的做法是将复合语句转化为函数定义,在分支语句中放置函数调用。
6.for循环体中的变量声明:可以在for语句的头部声明变量,这样就可以在声明的同时进行相应的初始化。
如:for(int n = 1; n <= 10; n++)
sum = sum + n;
注意:1)ANSI/ISO C++标准要求,所有符合标准的编译器在处理for循环中的初始化声明时,这些声明都将被视为该循环体内部。
2)早期的C++编译器并非如此,程序员可以自己决定编译器如何处理for循环初始值中声明的变量。考虑到移植性方面的原因,最好不要这样编写代码。幸运的是,几乎所有常用的C++编译器都遵从这条规则,但不久以后的编译器有可能遵从,也有可能不遵从。
第四章 重载与默认实参
4.1
总结:
1.
注意:
2.
注意:
3.
注意: