C++是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言,是C语言的超集。本书是根据2003年的ISO/ANSI C++标准编写的,通过大量短小精悍的程序详细而全面地阐述了 C++的基本概念和技术,并专辟一章介绍了C++11新增的功能。
《C++ Primer Plus(第6版)中文版》分18章,分别介绍了C++程序的运行方式、基本数据类型、复合数据类型、循环和关系表达式、分支语句和逻辑运算符、函数重载和函数模板、内存模型和名称空间、类的设计和使用、多态、虚函数、动态内存分配、继承、代码重用、友元、异常处理技术、string类和标准模板库、输入/输出、C++11新增功能等内容。
《C++ Primer Plus(第6版)中文版》针对C++初学者,从C语言基础知识开始介绍,然后在此基础上详细阐述C++新增的特性,因此不要求读者有C语言方面的背景知识。《C++ Primer Plus(第6版)中文版》可作为高等院校教授C++课程的教材,也可供初学者自学C++时使用。
本章内容包括:
创建和使用数组。
创建和使用 C-风格字符串。
创建和使用 string 类字符串。
使用方法 getline( )和 get( )读取字符串。
混合输入字符串和数字。
创建和使用结构。
创建和使用共用体。
创建和使用枚举。
创建和使用指针。
使用 new 和 delete 管理动态内存。
创建动态数组。
创建动态结构。
自动存储、静态存储和动态存储。
vector 和 array 类简介。
4.1 数组
数组(array)是一种数据格式,能够存储多个同类型的值。例如,数组可以存储 60 个 int 类型的值(这些值表示游戏 5 年来的销售量)、12 个 short 值(这些值表示每个月的天数)或 365 个 float 值(这些值指出一年中每天在食物方面的开销)。每个值都存储在一个独立的数组元素中,计算机在内存中依次存储数组的各个元素。
要创建数组,可使用声明语句。数组声明应指出以下三点:
存储在每个元素中的值的类型;
数组名;
数组中的元素数。
数组的初始化规则:
C++有几条关于初始化数组的规则,它们限制了初始化的时刻,决定了数组的元素数目与初始化器中值的数目不相同时将发生的情况。我们来看看这些规则。只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组:
然而,可以使用下标分别给数组中的元素赋值。
C++标准模板库(STL)提供了一种数组替代品—模板类 vector,而 C++11 新增了模板类 array。
这些替代品比内置复合类型数组更复杂、更灵活,本章将简要地讨论它们,而第 16 章将更详细地讨论它们。
4.2 字符串
字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种。第一种来自 C
语言,常被称为 C-风格字符串(C-style string)。本章将首先介绍它,然后介绍另一种基于 string 类库的方法。
存储在连续字节中的一系列字符意味着可以将字符串存储在 char 数组中,其中每个字符都位于自己的数组元素中。字符串提供了一种存储文本信息的便捷方式,如提供给用户的消息(“请告诉我您的瑞士银行账号”)或来自用户的响应(“您肯定在开玩笑”)。C-风格字符串具有一种特殊的性质:以空字符(null character)结尾,空字符被写作\0,其 ASCII 码为 0,用来标记字符串的结尾。
4.3 string 类简介
使用 string 类时,某些操作比使用数组时更简单。例如,不能将一个数组赋给另一个数组,但可以将 一个 string 对象赋给另一个 string 对象:
函数 strlen( )是一个常规函数,它接受一个 C-风格字符串作为参数,并返回该字符串包含的字符数。函数 size( ) 的功能基本上与此相同,但句法不同:str1 不是被用作函数参数,而是位于函数名之前,它们之间用句点连接。与第 3 章介绍的 put( )方法相同,这种句法表明,str1 是一个对象,而 size( )是一个类方法。方法是一个函数,只能通过其所属类的对象进行调用。在这里,str1 是一个string 对象,而 size( )是 string 类的一个方法。总之,C 函数使用 参数来指出要使用哪个字符串,而 C++ string 类对象使用对象名和句点运算符来指出要使用哪个字符串。
4.3.4 string 类 I/O
在用户输入之前,该程序指出数组 charr 中的字符串长度为 27,这比该数组的长度要大。这里要两点需要说明。首先,为初始化的数组的内容是未定义的;其次,函数 strlen( )从数组的第一个元素开始计算字节数,直到遇到空字符。在这个例子中,在数组末尾的几个字节后才遇到空字符。对于未被初始化的数据,第一个空字符的出现位置是随机的,因此您在运行该程序时,得到的数组长度很可能与此不同。另外,用户输入之前,str 中的字符串长度为 0。这是因为未被初始化的 string 对象的长度被自动设置为 0。
4.4结构简介
C++中的结构的可以满足要求(存储篮球运动员的信息)。结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据,这使得能够将有关篮球运动员的信息放在一个结构中,从而将数据的表示合并到一起。如果要跟踪整个球队,则可以使用结构数组。结构也是C++OOP堡垒(类)的基石。学习有关结构的知识将使我们离C++的核心OOP更近。
4.5 共用体
共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。也 就是说,结构可以同时存储 int、long 和 double,共用体只能存储 int、long 或 double。共用体的句法与结 构相似,但含义不同。例如,请看下面的声明:
4.6 枚举
C++的 enum 工具提供了另一种创建符号常量的方式,这种方式可以代替 const。它还允许定义新类型,但必须按严格的限制进行。使用 enum 的句法与使用结构相似。例如,请看下面的语句:
这条语句完成两项工作。
4.7 指针和自由存储空间
对于指针,需要指出的另一点是,new分配的内存块通常与常规变量声明分配的内存块不同。变量nights和pd的值都存储在被称为栈(stack)的内存区域中,而new从被称为堆(heap)或自由存储区(free store)的内存区域分配内存。第9章将更详细地讨论这一点。
4.8 指针、数组和指针算术
指针和数组基本等价的原因在于指针算术(pointer arithmetic)和C++内部处理数组的方式。首先,我们来看一看算术。将整数变量加1后,其值将增加1;但将指针变量加1后,增加的量等于它指向的类型的字节数。将指向double的指针加1后,如果系统对double使用8个字节存储,则数值将增加8;将指向shot的指针加1后,如果系统对short使用2个字节存储,则指针值将增加2。程序清单4.19演示了这种令人吃惊的现象,它还说明了另一点:C++将数组名解释为地址。
4.8.1 程序说明
在多数情况下,C++将数组名解释为数组第 1 个元素的地址。
注意: 1 后,其增加的值等于指向的类型占用的字节数
4.8.2指针小结
刚才已经介绍了大量指针的知识,下面对指针和数组做一总结。
1.声明指针
要声明指向特定类型的指针,请使用下面的格式:
typeName pointerName;
下面是一些示例:
double pn;
/pn can point to a double value
char pci
/pc can point to a char value
其中,pn和pc都是指针,而double*和char*是指向double的指针和指向char的指针。
2.给指针赋值
应将内存地址赋给指针。可以对变量名应用&运算符,来获得被命名的内存的地址,ew运算符返回
未命名的内存的地址。
下面是一些示例:
double pn;
/pn can point to a double value
double pa;
//so can pa
char pci
/pc can point to a char value
double bubble 3.2;
pn &bubble;
/assign address of bubble to pn
pc new char;
/assign address of newly allocated char memory to pc
pa new double[30]i//assign address of 1st element of array of 30 double to pa
3.对指针解除引用
对指针解除引用意味着获得指针指向的值。对指针应用解除引用或间接值运算符(*)来解除引用。因
此,如果像上面的例子中那样,pn是指向bubble的指针,则*pn是指向的值,即3.2。
下面是一些示例:
cout <*pn;/print the value of bubble
*pc ='S';/place 's'into the memory location whose address is pc
另一种对指针解除引用的方法是使用数组表示法,例如,pO]与*pn是一样的。决不要对未被初始化
为适当地址的指针解除引用。
4.区分指针和指针所指向的值
如果pt是指向int的指针,则*pt不是指向nt的指针,而是完全等同于一个int类型的变量。pt才是指针。
下面是一些示例:
int pt new int;
/assigns an address to the pointer pt
*pt =5;
/stores the value 5 at that address
5.数组名
在多数情况下,C++将数组名视为数组的第一个元素的地址。
下面是一个示例:
int tacos [10];
/now tacos is the same as &tacos [0]
一种例外情况是,将szof运算符用于数组名用时,此时将返回整个数组的长度(单位为字节).
6.指针算术
C++允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以
将一个指针减去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个
数组(也可以指向超出结尾的一个位置)时,这种运算才有意义:这将得到两个元素的间隔。
下面是一些示例:
int tacos[10]={5,2,8,4,1,2,2,4,6,8}:
int pt tacos;
/suppose pf and tacos are the address 3000
ptpt 1;
/now pt is 3004 if a int is 4 bytes
int*pe=&tacos【9】;
/pe is 3036 if an int is 4 bytes
pepe 1;
/now pe is 3032,the address of tacos [8]
int diff pe -pt;
/diff is 7,the separation between
/tacos [8]and tacos [1]
7.数组的动态联编和静态联编
使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置:
int tacos[10];//static binding,size fixed at compile time
使用w]运算符创建数组时,将采用动态联编(动态数组),即将在运行时为数组分配空间,其长度
也将在运行时设置。使用完这种数组后,应使用delete[]释放其占用的内存:
int size;
cin >size;
int pz new int [size];/dynamic binding,size set at run time
▣。
delete [pz;
/free memory when finished
8.数组表示法和指针表示法
使用方括号数组表示法等同于对指针解除引用:
第4章复合类型
111
tacos [o]means *tacos means the value at address tacos
tacos [3]means *(tacos 3)means the value at address tacos 3
数组名和指针变量都是如此,因此对于指针和数组名,既可以使用指针表示法,也可以使用数组
表示法。
下面是一些示例:
int pt new int [10];
/pt points to block of 10 ints
*pt=5;
/set element number 0 to 5
pt[0]=6:
/reset element number 0 to 6
pt[9】=44;
/set tenth element (element number 9)to 44
int coats [10]
*(c0ats+4)=12;
/set coats(4]to 12
4.8.5 自动存储、静态存储和动态存储
根据用于分配内存的方法,C++有 3 种管理数据内存的方式:自动存储、静态存储和动态存储(有时 也叫作自由存储空间或堆)。在存在时间的长短方面,以这 3 种方式分配的数据对象各不相同。下面简要地介绍每种类型(C++11 新增了第四种类型—线程存储,这将在第 9 章简要地讨论)。
1.自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。例如,程序清单 4.22 中的 temp 数组仅当 getname( ) 函数活动时存在。当程序控制权回到 main( )时,temp 使用的内存将自动被释放。如果 getname( )返回 temp 的地址,则 main( )中的 name 指针指向的内存将很快得到重新使用。这就是在 getname( )中使用 new 的原因之一。 实际上,自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段
代码。到目前为止,我们使用的所有代码块都是整个函数。然而,在下一章将会看到,函数内也可以有代码块。如果在其中的某个代码块定义了一个变量,则该变量仅在程序执行该代码块中的代码时存在。 自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出(LIFO)。因此,在程序执行过程中,栈将不断地增大和缩小。
2.静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面 定义它;另一种是在声明变量时使用关键字 static:
的 K&R C 中,只能初始化静态数组和静态结构,而 C++ Release 0(及后续版本)和 ANSI C 中,也 可以初始化自动数组和自动结构。然而,一些您可能已经发现,有些 实现还不支对自动数组和自动 结构
第 9 章将详细介绍静态存储。自动存储和静态存储的关键在于:这些方法严格地限制了变量的寿命。 变量可能存在于程序的整个生命周期(静态变量),也可能只是在特定函数被执行时存在(自动变量)。 符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在 C++ 中被 这使得跟踪新分配内存的位置更困难。 如果没有调用 delete,则即使包含指针的内存由于
2.静态存储
3.动态存储
new 和 delete 运算
称为自由存储空间(free store)或堆(heap)。该内存池同用于静态变量和自动变量的内存是分开的。程序清单 4.22 表明,new 和 delete 让您能够在一个函数中分配内存,而在另一个函数中释放它。因此,数据的生命周期不完全受程序或函数的生存时间控制。与使用常规变量相比,使用 new 和 delete 让程序员对程序如何使用内存有更大的控制权。然而,内存管理也更复杂了。在栈中,自动添加和删除机制使得占用的内存总是连续的, 但 new 和 delete 的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。
4.9 类型组合
4.10 数组的替代品
4.11 总结
数组、结构和指针是 C++的 3 种复合类型。数组可以在一个数据对象中存储多个同种类型的值。通过 使用索引或下标,可以访问数组中各个元素。
结构可以将多个不同类型的值存储在同一个数据对象中,可以使用成员关系运算符(
.)来访问其中的成员。使用结构的第一步是创建结构模板,它定义结构存储了哪些成员。模板的名称将成为新类型的标识符,然后就可以声明这种类型的结构变量。
共用体可以存储一个值,但是这个值可以是不同的类型,成员名指出了使用的模式。
指针是被设计用来存储地址的变量。我们说,指针指向它存储的地址。指针声明指出了指针指向的对 象的类型。对指针应用解除引用运算符,将得到指针指向的位置中的值。
系列字符。字符串可用引号括起的字符串常量表示,其中隐式包含了结 尾的空字符。可以将字符串存储在 char 数组中,可以用被初始化为指向字符串的 char 指针表示字符串。函
数 strlen( )返回字符串的长度,其中不包括空字符。函数 strcpy( )将字符串从一个位置复制到另一个位置。
在使用这些函数时,应当包含头文件 cstring 或 string.h。
头文件 string 支持的 C++ strin 类提供了另一种对用户更友好的字符串处理方法。具体地说,string 对象将根据要存储的字符串自动调整其大小,用户可以使用赋值运算符来复制字符串。new 运算符允许在程序运行时为数据对象请求内存。该运算符返回获得内存的地址,可以将这个地址赋给一个指针,程序将只能使用该指针来访问这块内存。如果数据对象是简单变量,则可以使用解除引用 运算符 new 何时将内存归还给内存池。自动变量是
在函数中声明的变量,而静态变量是在函数外部或者使用关键字 static 声明的变量,这两种变量都不太灵 字符串是以空字符为结尾的一来获得其值;如果数据对象是数组,则可以像使用数组名那样使用指针来访问元素;如果数据 对象是结构,则可以用指针解除引用运算符(->)来访问其成员。
指针和数组紧密相关。如果 ar 是数组名,则表达式 ar[i]被解释为*(ar + i),其中数组名被解释为数组 第一个元素的地址。这样,数组名的作用和指针相同。反过来,可以使用数组表示法,通过指针名来访问 分配的数组中的元素。
运算符 new 和 delete 允许显式控制何时给数据对象分配内存,运算符new和delete允许显式控制何时给数据对象分配内存,何时将内存归还给内存池。自动变量是
在函数中声明的变量,而静态变量是在函数外部或者使用关键字saⅵc声明的变量,这两种变量都不太灵活。自动变量在程序执行到其所属的代码块(通常是函数定义)时产生,在离开该代码块时终止。静态变量在整个程序周期内都存在。
C++98新增的标准模板库(STL)提供了模板类vector,.它是动态数组的替代品。C++11提供了模板类array,它是定长数组的替代品。
练习题