开始学习C++
进入C++
通常main()被启动代码调用,而启动代码是由编译器添加到程序中的,是程序和操作系统的桥梁。
C++必修包含一个名为main()的函数,大小写拼写都要正确。如果没有main(),程序将不完整,编译器将指出未定义main()函数。
存在一些例外情况。例如,在 Windows编程中,可以编写一个动态链接库(DLL)模块,这是其他 Windows程序可以使用的代码。由于DLL模块不是独立的程序,因此不需要 main ()。
C++注释 (/ / )以及(/ 和 /)
C++预处理器和iostream文件
C++和C一样,也使用一种预处理器,该程序在进行主编译之前对源文件进行处理(有些C++实现使用翻译器程序将C++程序转换为C程序。虽然翻译器也是一种预处理器,但这里不讨论这种预处理器,而只讨论处理名称以 # 开头的编译指令的预处理器)。不必执行任何特殊的操作来调用该预处理器,它会在编译程序时自动运行。
实际上,iostream文件的内容将取代程序中的代码行# include < iostream>。原始文件没有被修改,而是将源代码文件和iostream组合成一个复合文件,编译的下一阶段将使用该文件。
头文件名
像iostream这样的文件由于它们被包含在其他文件中叫做包含文件(include file);由于它们被包含在文件起始处,也叫头文件(header file)。C++编译器自带了很多头文件,每个头文件都支持一组特定的工具。
C++的用法发生了变化。现在,对老式C的头文件保留了扩展名h(C++程序仍可以使用这种文件),而C++头文件则没有扩展名。有些C头文件被转换为C++头文件,这些文件被重新命名,去掉了扩展名h(使之成为C++风格的名称),并在文件名称前面加上前缀c(表明来自C语言)。例如,C++版本的math.h为 cmath头文件。有时C头文件的C版本和C++版本相同,而有时候新版本做了些修改。对于纯粹的C++头文件(如 iostream)来说,去掉h不只是形式上的变化,没有h的头文件也可以包含名称空间。
名称空间
这叫做using编译指令。
名称空间支持是C++中一项较新的特性,它是为了使编写将多个厂商已有的代码组合起来的程序更简单而设计的。一个潜在的问题是:可能使用两个已封装好的产品,而它们都包含一个名为 wanda()的函数。这样,使用 wanda函数时,编译器将不知道指的是哪个版本。名称空间让厂商能够将其产品封装在一个叫做名称空间的单元中,这样就可以用名称空间的名称来指出想使用哪个厂商的产品。因此, Microflop Industries可以将其定义放到一个名为 Microflop的名称空间中。这样,其 wanda函数的全称为Microflop:: wanda():同样, Piscine公司的 wandao版本可以表示为 Piscine: :wanda()。这样,程序就可以使用名称空间来区分不同的版本了:
iostream是C++的标准库,标准库都被放置在命名空间std(standard)中。仅当头文件没有扩展名h时,情况才这样,也就是采用c++的新式风格,即采用了名称空间。头文件为.h由于没有采用名称空间,所以在采用不同库时,是存在命名冲突的风险的,没办法,自己想办法解决吧;)。1
2
3C语言与c++,C语言确实是最底层,但是语法不完善,开发效率很低,c++就是为了解决某些问题才诞生的。
鸡先生蛋还是蛋先生鸡,许多语言的编译都是c写的,那么c语言的编译器又是谁写的呢。
1970年Tomphson和Ritchie在BCPL(一种解释型语言)的基础上开发了B语言,1973年又在B语言的基础上成功开发出了现在的C语言。在C语言被用作系统编程语言之前,Tomphson也用过B语言编写过操作系统。可见在C语言实现以前,B语言已经可以投入实用了。因此第一个C语言编译器的原型完全可能是用B语言或者混合B语言与PDP汇编语言编写的。我们现在都知道,B语言的执行效率比较低,但是如果全部用汇编语言来编写,不仅开发周期长、维护难度大,更可怕的是失去了高级程序设计语言必需的移植性。所以早期的C语言编译器就采取了一个取巧的办法:先用汇编语言编写一个C语言的一个子集的编译器,再通过这个子集去递推完成完整的C语言编译器。
1 | 编译器为什么会生成汇编语言而不是机器语言? |
c++在使用函数前需要有函数声明,有了函数声明还要有函数定义。c++不允许将函数定义嵌套在另一个函数定义中。每个函数定义都是独立的,所有函数
的创建都是平等的。
int main()时return 0将返回值返回给了操作系统。例如。UNIX外壳脚本和DOS批处理文件都被设计成运行程序。很多操作系统都可以使用程序的返回值(通常也叫退出值)。通常的约定是,退出值为0则意味着程序运行成功,为非0则意味着存在问题。因此,如果c++程序无法打开文件,可以将它设计为返回一个非零值。然后,便可以设计一个外壳脚本或批处理文件来运行该程序,如果程序发出指示失败的消息,则采取其他措施。
处理数据
简单变量
变量名
整型
short、int和long
如果在所有的系统中,每种类型的宽度都相同,则使用起来将非常方便。但是生活并非那么简单,C++提供了一种灵活的标准,它确保了最小长度(从C语言借鉴而来):
- short至少16位
- int至少和short一样长。
- long至少32位,且至少与int一样长。
当前很多系统都使用最小长度,即shot为16位,long为32位。这仍然为int提供了多种选择,其宽度可以是16位、24位或32位,同时又符合标准。通常,在老IMPC的实现中,int的宽度为16位(与short相同),而在 Windows98、 Windows nt、 Windows XP、 Macintosh OS X、VAX和很多其他微型计算机的实现中,为32位(与long相同)。有些实现允许选择如何处理int类型的宽度随实现而异,这可能在将C艹程序从种环境移到另…种环境时引发问题。但只要小心一点就可以最大限度地减少这种问题。
实际上,short是short int的简称,而long是long int的简称。这三种类型(int,short和long)都是符号类型,意思是有一位的符号位。
sizeof操作符返回类型或变量的长度,单位为字节。头文件 climits(在老式实现中为 limits h)中包含了关于整型限制的信息。具体地说,它定义了表示各种限制的符号名称。例如, INT_MAX为int的最大取值, CHAR_BIT为字节的位数。
符号常量—预处理器方式
# define编译指令是C语言遗留下来的。C艹有一种更好的创建符号常量的方法(使用关键字 const),所以不会经常使用 # define。不过,有些头文件,尤其是那些被设计成可用于C和C艹中的头文件,必须使用 # define
无符号类型
要创建无符号版本的基本整形,只需要使用关键字unsigned来修改声明即可:
climits文件与limits.h文件;老式编译器可能需要使用文件limits.h,有些非常老的编译器可能根本没有这两个文件。
选择整型的类型
通常,int被设置为对目标计算机而言最为“自然”的长度。自然长度( natural size)指的是计算机处理起来效率最高的长度。如果没有非常有说服力的理由来选择其他类型,则应使用int。
如果知道变量可能表示的整数值大于16位整数的最大可能值,则使用long。即使系统上int为32位也应这样做。这样,当程序移植到16位的系统中时,程序就不会突然无法正常工作。
如果 short比int小,则使用 short可以节省内存。通常,仅当有大型整型数组时,才有必要使用 short(数组是一种数据结构,在内存中连续存储同类型的多个值)。如果节省内存很重要,则应使用 short而不是使用int,即使它们的长度是一样的。例如,假设要将程序从int为16位的 DOS PC系统移到int为32位的Windows XP系统,则用于存储int数组的内存量将加倍,但 short数组不受影响。记住,节省一点是一点。如果只需要一个字节,可使用char。
整型常量
十进制,八进制,十六进制。不管书写为10、012还是0xA,都将以相同的方式存储在计算机中—被存储为二进制数。
C++如何确定常量的类型
程序将把常量1492存储为int、long还是其他整型呢?答案是,除非有理由存储为其他类型(如使用了特殊的后缀来表示特定的类型,或者值太大,不能存储为int),否则C++将整型常量存储为int类型。
首先来看看后缀。后缀是放在数字常量后面的字母,用于表示类型。整数后面的1或L后缀表示该整数为long常量,u或U后缀表示 unsigned int常量,ul(可以采用任何一种顺序,大写小写均可)表示 unsigned long常量(由于小写1看上去像1,因此应使用大写L作后缀)。例如,在int为16位、long为32位的系统上,数字22022被存储为int,占16位,数字2022L被存储为long,占32位。同样,22022LU和2202UL都被存储为 unsigned long。
接下来考察长度。在C艹中,对十进制整数采用的规则,与十六进制和八进制稍微有些不同。对于不带后缀的十进制整数,将使用下面几种类型中能够存储该数的最小类型来表示:int、long或 unsigned long。在int为16位、long为32位的计算机系统上,20000表示为int类型,40000被表示为long类型,3000000被表示为 unsigned long类型。对于不带后缀的十六进制或八进制整数,将使用下面几种类型中能够存储该数的最小类型来表示;int、 unsigned int、long或 unsigned long。在将40000表示为long的计算机系统中,六进制数Ox9C40(40000.将被表示为 unsigned int这是因为十六进制常用来表示内存地址,而内存地址是没有符号的,因此unsigned int比long更适合用来表示16位的地址。
char类型:字符和小整数
顾名思义,char类型是专为存储类型(如字母和数字)而设计的。编程语言通过字母的数值编码解决了字母的存储问题。因此,char类型是另一种整型(所以可以加1)。它足够长,能够表示目标计算机系统中所有基本符号—所有的字母、数字、标点符号等。实际上,多数系统所支持的字符都不超过256种,因此用一个字节就可以表示所有的符号。因此,虽然char最常被用来处理字符,但也可以将它用做比short更小的整型。在美国,最常用的符号集是ASCII字符集,不过不能很好的满足国际需要,c++支持的宽字符类型可以存储更多的值,如国际Unicode字符集使用的值。
wcha_t
const限定符
提示:如果读者在学习C艹之前学习过C语言,并打算使用 # define来定义符号常量,请不要这样做,而应使用 const。
浮点数
书写浮点数
第一种是标准小数点表示法12.34。第二种表示浮点值的方法叫做E表示法3.45E(e)6
浮点类型
C++也有3种浮点类型float、double和long double。这些类型是按它们可以表示的有效位数和允许的指数最小范围来描述的。有效位是数字中有意义的位。事实上,C和C++对于有效位数的要求是,foat至少32位, double至少48位,且不少于float, long double至少和 double样多。这种类型的有效位数可以一样多。不过,通常foat为32位, double为64位,long double为80、96或128位。另外,这3种类型的指数范围至少是-37到37。可以从头文件cfloat或 float. h中找到系统的限制( float是C语言的 float h文件的C++版本)。
浮点数的优缺点
与整数相比,浮点数有两大优点。首先,它们可以表示整数之间的值。其次,由于有缩放因子,它们可以表示的范围大得多。另一方面,浮点运算的速度比整数运算慢,至少在没有数学协处理器的计算机上是如此,而且精度将降低。
C++算术操作符
操作符优先级和结合性
除法分支
求模操作符
类型转换
C++自动执行很多类型转换:
- 将一种算术类型的值赋给另一种算术类型的变量时,c++将对值进行转换。
- 表达式中包含不同的类型时,C++将对值进行转换。
- 将参数传递给函数时,C++将对值进行转换。
赋值时进行的转换
将0赋值给bool变量时,将被转换为false,而非零值将被转换为true。表达式中的转换
当同一个表达式中包含两种不同的算术类型时,C++将执行两种自动转换:首先,一些类型在出现时便会自动转换。其次,有些类型在与其他类型同时出现在表达式中将被转换。 还有其他些整型提升: 如果short比int短,则unsigned short 类型将被转换为int;如果两种类型的长度相同,则unsigned short类型将被转换为unsigned int。这种规则确保了在对unsigned short进行提升时不会损失数据。
同样,wchar_t被提升成为下列类型中第一个宽度足够存储wchar_ t 取值范围的类型: int. unsigned int、long或unsigned long.
将不同类型进行算术运算时, 也会进行一些转换,例如将int和float相加时。当运算涉及到两种类型时,较小的类型将破转换为较大的类型。例如,程序清单3.11中的程序用9.0除以5。 由于9.0的类型为double, 因此程序在用5除之前,将5转换为double类型。总之,编译器通过校验表来确定在算术表达式
中执行的转换。下面是一一个列表, 编译器将依次查阅该列表:
①如果有一个操作数的类型是long double, 则将另一个操作数转换为long double.
ANSI C遵循的规则与C++相同,但传统K&R C的规则稍有不同。例如,传统C语言总是将float提升为double,即使两个操作数都是float。
传递参数时转换
传递参数时的类型转换通常由C++函数原型控制。不过,也可以取消原型对参 数传递的控制,尽管这样做并不明智。在这种情况下,C++将对char和 short类型( signed和 unsigned)应用整型提升。另外,为保持与传统C语言中大量代码的兼容性,在将参数传递给取消原型对参数传递控制的函数时,C+将float参数提升为 double。
强制类型转换
C++还允许通过强制类型转换机制显式地进行类型转换,强制类型转换的格式有两种。
第一种格式来自C语言,第二种格式是纯粹的C++。新格式的想法是,要让强制类型转换就像是函数调用。这样对内置类型的强制类型转换就像是为用户定义的类设计的类型转换。
复合类型
数组
计算机在内存中依次存储数组的各个元素。数组中的每一个元素可以看做一个简单变量。要创建数组,可使用声明语句,应指出三点。
- 存储在每个元素中的值的类型
- 数组名
- 数组中的元素数
数组的声明
表达式arraySize指定元素数目,它必须是整型常数(如10)或const值,也可以是常量表达式(如8*sizeof (in)),即其中所有的值在编译时都是已知的。具体地说,arraySize 不能是变量,变量的值是在程序运行时设置的。不过,可以使用new操作符来避开这种限制。
数组的初始化规则
只有在定义数组时才能使用初始化,此后就不能使用了, 也不能将一 个数组赋给另一个数组:
不过, 可以使用下标分别给数组中的元素赋值。
初始化数组时,提供的值可以少于数组的元素数目。如果只对数组的部分进行初始化, 则编译器将把其他元素设置为0。
如果初始化数组时方括号内([])为空,C++编译器将计算元素个数。
通常,让编译器计算元素个数是一种很糟的做法,因为其计数可能与您想象的不一样。不过,这种方法对于将字符数组初始化为一个字符串来说比较安全。
C++标准模板库(STL)一种数组替代品—模板类vector,它比内置复合类型数组更复杂,也更灵活。
字符串
字符串是存储在内存的连续字节中的一系列字符。C++处理字符串有两种方式。一,来自C语言,被称为C-风格字符串,还有一种就是基于string类库的方法。
存储在连续字节中的一-系列字符意味着可以将字符串存储在char数组中,每个字符都位于自己的数组元素中。C风格具有一种特殊的性质,以空字符结尾,空字符被写作\ 0 ,其ASCII码为0,用来标记字符串的结尾。
这两个数组都是char数组,但只有第二个数组是子符串。空子符对C-风格子付事心后里大里安。例如,C++有很多处理字符串的函数,其中包括cout使用的那些函数。它们都逐个地处理字符串中的字符, 直到到达空字符为止。如果使用cout显示上面的cat这样的字符串,则将显示前4个字符,发现空字符后停止。但是,如果使用cout显示上面的dog数组(它不是字符串),cout 将打印出数组中的5个字母,并接着将内存中随后的各个字节解释为要打印的字符,直到遇到空字符为止。由于空字符(实际上是被设置为0的字节)在内存中很常见,因此这一过程将很快停止。但尽管如此,还是不应将不是字符串的字符数组当作字符串来处理。
有一种更好地将字符数组初始化为字符串的方法,使用用引号括起来的字符串
用引号括起的字符串隐式地包括结尾的空字符,因此不用显式地包括它。
应确保数组足够大,能够存储字符串中所有字符包括空字符。使用字符串常量初始化字符数组是这样的一种情况,即让编译器计算元素数目更为安全。让数组比字符串长没有什么害处,只是会浪费一些空间而已。这是因为处理字符串的函数根据空字符的位置,而不是数组长度来进行处理。C++对字符串长度没有限制。
记住:在确定存储字符串所需的最短数组时,记得将结尾的空字符计算在内。
注意,字符串常量(使用双引号)不能与字符常量(使用单引号)互换。字符常量(如S)是字符串编码的简写表示。在ASCⅡ系统上,S只是83的另一种写法。因此,下面的语句:
是将内存地址赋给变量。
拼接字符串常量
任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个。
拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符(不考虑\ 0 )后面。第一个字符串的\ 0 字符将会被第二个字符串的第一个字符取代。
在数组中使用字符串
要将字符串存储到数组中,最常用的方法有两种,一是将数组初始化为字符串常量、二将键盘或文件输入读入数组中。
字符串输入
cin
每次读取一行字符串输入
面向行的输入:getline()
getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。要调用这种方法,可以使用 cin. getline()。该函数有两个参数。第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。
面向行的输入:get()
混合输入字符串和数字
混合输入数字和面向行的字符串会导致问题。
string类简介
string类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。
要使用string类,必须在程序中包含头文件string。string类位于名称空间std中,需要提供一条using编译指令,或者使用std::string来引用它。string类定义隐藏了字符串的数组性质,可以像处理普通变量那样处理字符串。
赋值、拼接和附加
使用 string类时,某些操作比使用数组时更简单。例如,不能将一个数组赋给另一个数组,但可以将个 string对象赋给另一个string对象。string类简化的字符串合并操作。可以使用操作符+将两个string对象合并起来,还可以使用操作符+ =一个字符串附加到个 string对象的末尾。
string类的其他操作
在C艹新增 string类之前,程序员也需要完成诸如给字符串赋值等工作。对于C语言式的字符串,程序员使用C语言库中的函数来完成这些任务。头文件cstring(以前为 string. h)提供了这些函数。例如,可以使用函数 strcpy()将字符串复制到字符数组中,使用函数 strcat()将字符串附加到字符数组末尾:
string类I/O
正如读者知道的,可以使用cin和操作符<<来将输入存储到 string对象中,使用cout和操作符>>来显示 string对象,其句法与处理C-风格字符串相同。但每次读取一行而不是一个单词时,使用的句法不同。
结构简介
C++运行在声明结构变量时省略关键字struct。
可以使用成员操作符(.)来访问各个成员。访问类成员函数的方式也是类似的。
在程序中使用结构
结构声明的位置很重要。可以将声明放在main()函数中,紧跟在开始括号的后面,另一种选择是将声明放在main()的前面。
C++不提倡使用外部变量但提倡使用外部结构声明。
初始化结构:
当然也可以把它们放在同一行中。
结构可以将string类作为成员吗
大体上说,答案是肯定的。
其他结构属性
C++使用户定义的类型与内置类型尽可能相似。例如, 可以将结构作为参数传递给函数, 也可以让函数返同—个结构。另外,还可以使用赋值操作符(=) 将结构賦给另一个同类型的结构, 这样结构中每个成员都将被设置为另一个结构中相应成员的值,即使成员是数组。这种赋值被称为成员赋值。
结构数组
结构中的位字段
与C语言一样,C++也允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或枚举(稍后将介绍),接下来是冒号,冒号后面是一一个数字,它指定了使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段(bit field)。下面是一个例子:
位字段通常用在低级编程中。
共用体
共用体(union)是一种数据格式,能够存储不同的数据类型,但只能同时存储其中的一种类型。也就是说,结构可以同时存储int、long和double,共用体只能存储int、long或double。共用体的句法与结构相似,但含义不同。
声明:
可以使用one4all变量来存储int、long或double,条件是在不同的时间进行:
因此,pail 有时可以是int变量,而有时义可以是double变量。成员名称标识了变量的容量。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的长度为其最大成员的长度。在存储了double后int值就丢失了。
由于共用体是匿名的,因此id_ num和id_ char被视为prize 的两个成员,它们的地址相同,所以不需要中间标识符id_ val。程序员负责确定当前哪个成员是活动的。
枚举
C++的enum工具提供了另一种创建符号常量的方式,这种方式可以代替const。使用enum的句法与使用结构相似。
这条语句完成两项工作:
第一让spectrum称为新类型的名称,spectrum被称为枚举,就像struct变量被称为结构一样。
第二将red,orange,yellow等作为符号常量,它们对应整数值0-7.这些常量叫做枚举量。
可以用枚举名来声明这种类型的变量:
在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量。
对于枚举,只定义了赋值操作符,具体说就是没有为枚举定义算术运算:
对于最后一个式子
枚举时整型,可被提升为Int类型,但int类型不能自动转换为枚举类型:
枚举的规则相当严格,实际上,枚举更常被用来定义相关的符号常量,而不是新类型。如果打算只使用常量而不创建枚举类型的变量,则可以省略枚举类型的名称。
设置枚举量的值
可以使用赋值操作符来显示地设置枚举量的值。
指定的值必须是整数。 也可以只显式地定义其中一些枚举量的值:
这里,first在默认情况下为0,后面没有被初始化的枚举量的值将比其前面的枚举量大1,因此,third的值为101。
还可以创建多个值相同的枚举量:
在早期版本中,只能将int值(或提升为int的值)赋给枚举量,现在这种限制已经取消了,因此可以使用long类型的值。
枚举的取值范围
最初,对于枚举来说,只有声明中指出的那些值是有效的。不过,C++现在通过强制类型转换,增加了可赋给枚举变量的合法值。每个枚举都有取值范围(range), 通过强制类型转换, 可以将取值范围中的任何整数值赋给枚举变量, 即使这个值不是枚举值。
则如下代码将是合法的:
其中6不是枚举值,但它位于枚举定义的取值范围中。
取值范围的定义如下。首先, 要找出上限,需要知道枚举量的最大值。找到大于这个最大值的、最小的2的幂,将它减去1, 得到的便是取值范围的上限。例如, 前面定义的bigstep的最大值枚举值是101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127。要计算下限,需要知道枚举量的最小值。如果它不小于0,则取值范围的下限为0;否则,采用与寻找上限方式相同的方式, 但加上负号。例如, 如果最小的枚举量为-6, 而比它小的、最大的2的幂是-8 (加上负号),因此下限为-7。
选择用多少空间来存储枚举由编译器决定。对于取值范围较小的枚举,使用一个字节或更少的空间而对于包含long类型值的枚举,则使用4个字节。
指针和自由存储空间
指针是一个变量,其存储的是值的地址而不是值本身,对变量应用地址操作符(&)就可以获得它的位置,如home是一个变量,则&home是它的地址。
面向对象编程与传统过程性编程的区别在于,OOP强调的是在运行阶段(而不是编译阶段)进行决策。即使用new和指定固定长度的数组的区别。
声明和初始化指针
计算机需要跟踪指针指向的值的类型。例如,char 的地址与double的地址看上去没什么两样,但char和double使用的字节数是不同的,它们存储值时使用的内部格式也不同。因此,指针声明必须指定指针指向的数据的类型。但是地址的格式是相同的。
需要强调的是,int * 是一种类型,是指向int的指针。在哪里添加空格对于编译器是没有任何区别的。以下声明:
将创建一个指针p1和一个常规int变量p2。对每个指针变量名,都需要使用一个 * 。
指针的危险
在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。
警告:一定要在对指针应用解除引用操作符( * )之前,将指针初始化为一个确定的、适当的地址。
指针和数字
指针不是整型,虽然计算机通常把地址当做整数来处理。在C99标准发布前,C语言允许直接对指针赋值整数,但C++在类型一致性上要求更严格,编译器将显示一条错误信息,要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:
注意,pt是int值的地址,并不意味着pt本身的类型是int。
使用new来分配内存
指针的真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数malloc()来分配内存;在C++中仍然可以这样做,但C++还有更好的方法—使用new操作符。
在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。这里的关键所在是C++的new操作符。程序员要告诉new,需要为哪种数据类型分配内存; new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。
在之前是通过如下方式指定指针的地址的。
两种情况都是将一个int变量的地址赋给了指针。第二种情况下还可以通过名称来访问该int,第一种情况下,则只能通过该指针进行访问。pn指向的内存没有名称,该怎么称呼呢?我们称pn指向一个数据对象。这里的对象不是面向对象中的那个对象,术语数据对象比变量更通用,指为数据项分配的内存块,因此,变量也是数据对象,但pn指向的内存不是变量。乍一看,处理数据对象的指针方法可能不太好用,但它使程序在管理内存方面有更大的控制权。
为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:
需要在两个地方指定数据类型:用来指定需要什么样的内存和用来声明合适的指针。由于内存地址形式一样,所以得通过指针的类型知道需要读几个内存单元的值。
内存被耗尽
计算机可能会由于没有足够的内存而无法满足new的请求。此时,new将返回0。在C++中,值为0的指针被称为空值指针(null pointer)。C++确保空值指针不会指向有效的数据,因此它常被用来表示操作符或函数失效,如果成功,它们将返回一个有用的指针。可以通过if语句来检查new是否返回的是空值指针,从而防止程序超界。如果无法分配内存,new除返回空值指针外,还可能引发bad_alloc异常。
使用delete来释放内存
当需要内存时,可以使用new来请求,这只是C++内存管理数据包中有魅力的一个方面。另一个方面是delete操作符,它使得在使用完内存后, 能够将其归还给内存池,这是通向最有效地使用内存的关键一步。归还或释放(free)的内存可供程序的其他部分使用。使用delete时,后面要加.比指向内存块的指针(这些内存块最初是用new分配的):
这将释放ps指向的内存,但不会删除指针ps本身。例如,可以将ps重新指向另一个新分配的内存块。一定要配对地使用new和delete: 否则将发生内存泄漏(memory leak), 也就是说,被分配的内存再也无法使用了。如果内存泄漏严重,则程序将由于不断寻找更多内存而终止。
不要尝试释放已经释放的内存,这样做的结果是不确定的,不能使用delete来释放声明变量获得的内存。
只能用delete来释放使用new分配的内存,不过对空指针使用delete是安全的。
使用delete的关键在于,将它用于new分配的内存。这并不意味着要使用用于new的指针,而是用于new的地址:
一般来说, 不要创建两个指向同一个内存块的指针,因为这将增加错误地删除同一个内存块两次的可能性。
使用new来创建动态数组
如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组都在那里,它占用了内存。在编译时给数组分配内存被称为静态联编( static binding),意味着数组是在编译时加入到程序中的。但使用new时,如果在运行阶段需要数组,则创建它:如果不需要, 则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编(dynamic binding),意味着数组是在程序运行时创建的。这种数组叫作动态数组(dynamic array)使用静态联编时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
使用new创建动态数组
在C++中,创建动态数组很容易;只要将数组的元素类型和元素数目告诉new即可。必须在类型名后加上方括号,其中包含元素数目。
new操作符返回第一个元素的地址,当程序结束使用内存块后,应使用delete释放它们。
使用new创建数组时,应使用另一种格式的delete,它能指出所要释放的是一个数组。
方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。请注意delete和指针之间的方括号。如果使用new时,不带方括号,则使用delete时,也不应带方括号。如果使用new时带方括号,则使用delete时也应带方括号。
总之, 使用new和delete时,应遵守以下规则:
- 不要使用delete来释放不是new分配的内存。
- 不要使用delete释放同一个内存块两次。
- 如果使用new[ ]为数组分配内存,则应使用delete[ ]来释放。
- 如果使用new[ ]为一个实体分配内存,则应使用delete (没有方括号)来释放。
- 对空值指针应用delete是安全的。
由于动态数组返回的是指向第一个元素的指针,所以程序员需要跟踪内存块中的元素个数,实际上程序跟踪了分配的内存量以便在使用delete[]操作符时能正确地释放这些内存。但是这些信息是不公用的,如,不能使用sizeof操作符来确定动态分配的数组包含的字节数。
为数组分配内存的通用格式:
使用new操作符可以确保内存块足以存储num_elements 个类型为type_ name的元素,而pointer_ name将指向第1个元素。1
2对于new与delete的问题
一般来说在程序结束后操作系统会收回分配的所有资源,不delete也不会造成影响。但是如果程序常驻内存只new不delete就会很快耗尽内存,造成内存泄露,所以要养成new delete的习惯。
使用动态数组
将指针当做数组名使用即可,对于第一个元素,即为p[0]。
数组名和指针的根本区别在于,不能修改数组名的值,但指针是变量,因此可以修改它的值。相邻的int地址通常相差2个字节或4个字节,而将p3加1后,它将指向下一个元素的地址,这表明指针算术有:些特别的地方。情况确实如此。
指针、数组和指针算术
指针和数组基本等价的原因在于指针算术( pointer arithmetic和C++内部处理数组的方式。算术。将整数变量加1后,其值将增加1;但将指针变量加1后,增加的量等于它指向的类型的字节数。将指向double的指针加1后,如果系统对double使用8个字节存储,则数值将增加8;将指向short的指针加1后,如果系统对 short使用2个字节存储,则指针值(存储的地址值)将增加2。C++将数组名解释为地址。
如数组表达式stack[1],C++编译器将该表达式看作是*(stacks+1),这意味着先计算数组第2个元素的地址,然后找到存储在那里的值。
区别之一在于可以修改指针的值,而数组名是常量。
另一个区别是,对数组应用sizeof操作符得到的数组的长度,而对指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组。
指针算术:
C++允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以将一个指针减去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个数组(也可以指向超出结尾的一个位置)时,这种运算才有意义;这将得到两个元素的间隔。
指针和字符串
cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止。在C++中,用引号括起来的字符串像数组名一样,也是第一个元素的地址。
在cout和多数C++表达式中, char数组名、指向char的指针以及用引号括起的宇符串常量都被解释为字符串第一个字符的地址。
使用new创建动态结构
将new用于结构由两步组成:创建结构和访问其成员。要创建结构,需要同时使用结构类型和new。
这将把足以存储infatable结构的 块可用内存的地址赋给ps。 这种句法和C++的内置类型完全相同。
箭头成员操作符(->),可用于指向结构的指针,就像点操作符可用于结构名一样。如ps指向一个inflatable结构,则ps->price是被指向的结构的price成员。如果结构标识符是结构名,则使用句点操作符;如果标识符是指向结构的指针,则使用箭头操作符。
另一种访问结构成员的方法是,如果ps是指向结构的指针,则 * ps 就是被指向的值一结构本身。 由于 * ps是一个结构,因此( * ps) .price 是该结构的price 成员。C++的操作符优先规则要求使用括号。
自动存储、静态存储和动态存储
根据用于分配内存的方法,C++有3种管理数据内存的方式: 自助存储、静态存储和动态存储(有时也叫作自由存储空间或堆)。
自动存储
在函数内部定义的常规变量使用自助存储空间,被称为自动变量(automatic variable),这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。
实际上,自动变量是一个局部变量,其作用域为包它的代码块。代码块是被包含在花括号中的一段代码。
静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它:另一种是在声明变量时使用关键字static。
自动存储和静态存储的关键在于:这些方法严格地限制了变量的寿命。变量可能存在于程序的整个生命周期(静态变量),也可能只是在特定函数被执行时存在(自动变量)。
动态存储
new和delete操作符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间(free store)。内存池同用于静态变量和自动变量的内存是分开的。new和delete允许在一个函数中分配内存,而在另一个函数中释放它。因此,数据的生命周期就不完全受到程序或函数的生存时间的控制了。与使用常规变量相比, 使用new和delete使程序员对程序如何使用内存有更大的控制权。
堆栈、堆和内存泄露
如果使用new在自由空间(或堆)上创建变量后,没有调用delete,即使指针由于作用域规则和对象声明周期的原因而被释放,在自由存储空间上动态分配的变量或结构也将继续存在。实际上,将会无法访问自由存储空间中的结构,因为指向这些内存的指针无效。这将导致内存泄露。被泄露的内存将在程序的整个生命周期都不可使用。
循环和关系表达式
for循环
循环只执行一次初始化。
测试表达式结果为真,则程序将执行循环体,为假循环结束。
C++并没有将test expression的值限制为只能为真或假。可以使用任意表达式,C++将把结果强制转换为bool类型。因此, 值为0的表达式将被转换为bool值false,导致循环结束。 如果表达式的值为非零,则被强制转换为bool值true。
表达式与语句
在C++中,每个表达式都有值。
通常值是很明显的,如22+27
但x=20这个表达式由两个值和一个赋值操作符组成。C++将赋值表达式的值定义为左侧成员的值。因此maids=(x=20)+2可得maids为22。对于x=y=z=0,赋值操作符是从右向左结合的,因此首先将0赋给z,然后将z=0赋给y,以此类推。
从表达式到语句的转变是很小的一步,只要加一个分号就可以完成。
递增操作符(++)和递减操作符(—)
a++意味着使用a的当前值计算表达式,然后将a的值加1;而++b的意思是先将b的值加1, 然后使用新的值来计算表达式。
组合赋值操作符
关系表达式
可能犯的错误
==与=
while循环
for与while
在C++中, for和while循环本质上是相同的。
可以相互改写。
类型别名
C++为类型建立别名的方式有两种。一种是使用预处理器
这样,预处理器将在编译程序时用char替换所有的BYTE,从而使BYTE成为char的别名。
第二种方法是使用C++(和C)的关键字typedef来创建别名。
如
也可以使用#define,不过声明一系列变量时,这种方法不适用。
typedef方法不会有这样的问题。它能够处理更复杂的类型别名,这使得与使用#define相比,使用typedef时一种更佳的选择,有时也是唯一的选择。
注意, typedef不会创建新类型,而只是为已有的类型建立一个新名称。
do while循环
嵌套循环和二维数组
声明数组:
初始化二维数组
对于二维数组,由于每个元素本身就是一个数组,因此可以使用与上述代码类似的格式来初始化每一个元素。
};
分支语句和逻辑操作符
if语句
if else语句
格式化if else语句
如果需要多条语句,需要用花括号将它们括起来,组成一个块语句。和有些语言不同的是,由于C++不会自动将if和else之间的所有代码视为一个代码块,因此必须使用花括号将这些语句组合成一个语句块。
if else if else结构
逻辑表达式
逻辑OR操作符:| |
逻辑AND操作符:&&
逻辑NOT操作符:!
逻辑操作符细节
OR和AND操作符的优先级都低于关系操作符。这意味着
另一方面!操作符的优先级高于所有的关系操作符和算术操作符。因此,要对表达式求反,必须用括号将其括起来。
逻辑AND操作符的优先级高于逻辑OR操作符。
其他表示方式
字符函数库cctype
C++从C语言继承了一个与字符相关的、非常方便的函数软件包,它可以简化诸如确定字符是否为大写字母、数字、标点符号等工作,这些函数的原型是在头文件cctype(老式风格为ctype.h)中定义的。
?:操作符
C++有一个常被用来代替if else语句的操作符,这个操作符被称为条件操作符(?:),它是C++中唯一一个需要3个操作数的操作符。该操作符通用格式如下:
如果expressionl 为true, 则整个条件表达式的值为expression2 的值; 否则,整个表达式的值为expression3的值。
switch语句
注意使用break。
将枚举量作为标签
switch和if else
switch并不是为处理取值范围而设计的。switch语句中的每一个case标签都必须是一个单独的值。另外这个值必须是整数(包括char),因此switch无法处理浮点测试。如果既可以使用if else语句,也可以使用switch语句,则当选项不少于3个时,应使用switch语句。
break和continue语句
简单文件输入/输出
文本输入
- 必须包含头文件iostream
- 头文件iostream定义了个用处理输入的istream类。
- 头文件iostream声明了一个名为cin的istream变量(对象)。
- 必须指明名称空间std;例如,为引用元素cin,必须使用编译指令 using或前缀std::。
- 可以结合使用cin和操作符<<来读取各种类型的数据。
- 可以使用cin和get()方法来读取一个字符,使用cin和 getline()来读取一行字符。
- 可以结合使用cin和eof()、fail()方法来判断输入是否成功。
- 对象cin本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值true,否则被转换为false。
文件输出
- 必须包含头文件fstream
- 头文件 fstream定义了一个用于处理输入的 ifstream类。
- 需要声明一个或多个 ifstream变量(对象),并以自己喜欢的方式对其进行命名,条件是遵守常用的命名规则。
- 必须指明名称空间std:例如,为引用元素ifstream, 必须使用编译指令using 或前缀std:.。
- 需要将ifstream对象与文件关联起来。为此, 方法之一是使用open()方法。
- 使用完文件后,应使用close()方法将其关闭。
- 可结合使用ifstream对象和操作符<<来读取各种类型的数据。
- 可以使用ifstream 对象和get() 方法来读取一个字符, 使用ifstream对象和getine()来读取一行字符。
- 可以结合使用ifstream 和eof()、 fail()等方法来判断输入是否成功。
- ifstream对象本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值true,否则被转换为false。