C++ 和 C 语言一样有数组、字符串、指针、结构体、共用体、枚举这些复合类型,它们的基本使用都是差不多的。
数组
和 C 语言一样,C++ 的数组也是存储多个相同类型的数据格式。
数组声明
typeName arrayName [Size];
。
数组初始化
延续 C 语言部分:
只有在声明数组时才可以初始化,此后就再也不能初始化了。
不允许用数组直接给另一个数组赋值。
可以使用列表初始化。初始化数组时,提供的值少于数组元素数目,省略的元素被设置为 0。
C++ 新增部分:
首先,初始化数组时可以省略等号(=)。
int arr[10] {1, 23, 43, 2}; // C++ 允许
其次,可以不在大括号中包含任何东西,这将所有元素都设置为 0。(现在很多 C 编译器也允许这样做)
int arr[10] {}; // C++将arr的所有数组元素都设置为0.
第三,列表初始化禁止缩窄转换。
int arr1[] = {25, 92, 3.0}; // C++不允许
char arr2[] {'h', 11223344, '\0'}; // C++ 不允许
char arr3[] {'h', 112, '\0'}; // C++ 允许
在上述代码中,第一条语句不能通过编译,因为将浮点型转换为整型是缩窄操作,即使浮点数小数点后面是 0,也不可以。第二条语句也不能通过编译,因为 11223344 超过了 char 类型能表示的范围。第三条语句可以通过编译,虽然 112 是一个 int 值,但它在 char 变量的取值范围内。
数组访问
可以使用下标作为索引来进行数组访问,下标从 0 开始计数。
数组替代品:
C++ 标准模板库 (STL) 提供了一种数组的替代品 —— 模板类 vector,并且 C++11 新增了 array 模板类。这些替代品比内置复合类型数组更复杂、更灵活。
字符串
字符串是存储在内存的连续字节中的一系列字节。C++ 处理字符串的方式有两种 —— 来 C 语言的 C-风格字符串,以及 C++ 基于 string 类库的方法。
C-风格字符串
存储在连续字节中的一系列字符意味着可以将字符存储在 char 数组中,其中每个字符都位于自己的数组元素中。字符串提供了一种存储文本信息的便捷方式。
C-风格字符串有一种特殊的性质:以空字符串 (’\0’) 结尾,空字符的 ASCII 码为 0,用来标记字符串的结尾。因此 C-风格字符串是一种特殊的字符数组 —— 以空字符串为结尾的字符数组。
char arr1[] = {'h', 'e', 'l', 'l', 'o'}; // 是字符数组,不是字符串
char arr2[] = {'h', 'e', 'l', 'l', 'o', '0'}; // 是字符数组,也是字符串
这两个数组都是字符数组,但只有第二个是字符串。空字符对 C-风格字符串至关重要,C++ 很多处理字符串的函数,其中包括 cout 对象使用的哪些函数。它们的处理逻辑都是逐个处理字符串中的字符,直到处理到空字符为止。因此,用 cout 处理 arr1 将会多显示一些我们不需要的字符,直到它遇到空字符为止。
虽然字符串是字符数组的子集,但是在编程中见到的基本都是字符串,字符串的使用比不是字符串的字符数组要频繁的多。
字符串初始化:
可以使用一种简洁的字符串初始化的方法 —— 字符串常量(也称字符串字面量)。字符串常量是由双引号括起来的字符串。双引号括起来的字符串隐式包含结尾的空字符,所以不必显式的包含它。
char arr[] = "Hello, World"; // 字符串
PS:再确定存储字符串所需的最短数组时,别忘了将结尾的空字符计算在内。
string 类
ISO/ANSI C++98 标准通过添加 string 类扩展了 C++ 库,因此现在可以用 string 类的对象代替字符数组来存储字符串。string 类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。
要使用 string 类,必须在程序中包含 string 头文件,string 类位于名称空间 std 中,因此还必须提供一条 using 编译指令,或者用 std::string 来引用它。string 类隐藏了字符串的数组性质,可以像处理普通变量那样处理字符串。
在很多方面,使用 string 对象的方式与使用字符数组相同。
- 可以使用 C-风格字符串来初始化 string 对象。
- 可以使用 cin 来将键盘输入存储到 string 对象中。
- 可以使用 cout 来显示 string 对象。
- 可以使用数组表示法来访问存储在 string 对象中的字符。
string 对象和字符数组之间的主要区别是,可以将 string 对象声明为简单变量,而不是数组。string 类设计为可以让程序能够自动处理 string 的大小。这使得与使用数组相比,使用 string 对象更方便,也更安全。从理论上来说,可以将 char 数组视为一组用于存储一个字符串的 char 存储单元,而 string 类变量是一个表示字符串的实体。
原始字符串
在 C++11 中新增了一种类型 —— 原始字符串。在原始字符串中,字符表示的就是自己,例如,序列 \n 不再代表换行符,而是代表两个常规字符 —— 斜杠和 n。原始字符串使用前缀 R 来表示,并用 “( 和 )” 作为定界符。
cout << R"(Jim "King" Tutt uses \n instead of endl.)";
上述代码将显示:Jim "King" Tutt uses "\n" instead of endl.
结构
数组是存储多个相同类型数据,而结构可以存储多种类型数据的数据格式。例如,要存储有关篮球运动员的信息,则可能需要存储他的姓名、工资、身高、体重、平均得分、命中率、助攻次数等。可以通过结构将这些信息存储在一个单元中,而数组不能,因为数组虽然可以存储多个元素,但是所有元素的类型必须相同。
结构是用户定义的类型,而结构声明定义了这种类型的数据属性。创建结构包括两步,首先,定义结构描述,然后按描述创建结构变量。
struct Student {
char name[20];
int age;
};
int main(void) {
Student stu = {"Zhang San", 14};
return 0;
}
和 C 语言不同,C++ 允许在声明结构变量时省略关键字 struct。在 C++ 中,结构标记的用法与基本类型名相同,这种变化强调的是,结构声明创建了一种新类型。
通过成员运算符(.)来访问各个成员,就像通过索引能访问数组元素一样。顺便说一句,访问成员函数的方式就是从访问结构的成员变量的方式衍生的。
共用体
共用体也是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。也就是说,结构可以同时存储 int、long 和 double,共用体只能存储 int、long 或 double。共用体的句法与结构相似,但含义不同。
共用体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可以使用共用体来节省空间。
枚举
枚举提供了另一种创建符号常量的方式,这种方式可以代替 const。它还允许定义新类型,但必须按照严格的限制进行。枚举的句法和使用结构相似。
enum color {red, orange, yellow, green, blue, voilet};
上述语句完成了两项工作:1. 让 color 成为新类型的名称;将 red,yellow,voilet 等作为符号常量,对应整数值 0~5,这些常量叫做枚举量。
I. 默认情况下,将第一个枚举量被赋值为 0,此后的枚举量的值依次加一。也可以显式对枚举量赋值。
enum color { red, orange, yellow, green, blue, voilet };
// red=0, orange=1, yellow=2, green=3, blue=4, voilet=5
enum week { monday=3, tuesday, wednesday=2, thursday, friday=-1, saturday, sunday};
// monday=3, tuesday=4, wednesday=2, thursday=3, friday=-1, saturday=0, sunday=1
II. 枚举中,可以有值相同的枚举量 (例如,monday 和 thursday)。
III. 枚举量可以赋值给 int 类型变量,但 int 类型不能自动转换为枚举类型。即不能直接将整型数值赋值给枚举类型的变量。
enum color tmp1 = red; // ok
enum color tmp2 = 3; // 不被允许
enum color tmp3 = (enum color) 4; // 使用强制类型转换,ok
IV. 枚举类型只定义了赋值运算符,具体地说,没有为枚举定义算术运算。
V. 枚举值只能是整数。
指针
指针是一个变量,该变量存储的是地址,而不是值本身。指针是 C++ 内存管理编程理念的核心。
动态内存分配
可以将指针初始化为变量的地址,变量是在编译时分配的有名称的内存,而指针的这种用法只是为可以通过名称直接访问的内存提供了一个别名来间接访问。
指针的真正用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在 C 语言中,可以用库函数 malloc() 来分配内存,在 C++ 中依旧可以这样做,但是 C++ 有更好的方法 —— new 运算符。
申请内存 —— new
程序员使用 new 运算符时需要告诉 new,需要为那种类型分配内存,new 将会根据这种类型需要的内存找到一个正确的内存块,并返回该内存的地址。程序员的职责是将该地址赋值给一个指针。
int* p = new int;
为一个数据对象获得并指定分配内存的通用格式为:typeName* poniter_name = new typeName;
,需要在两个地方指定数据类型:用来声明合适的指针和指定需要什么样的内存。
重点知识:new 分配的内存块通常与常规变量声明的内存块不同。变量存储在栈(stack)中,而 new 从被称为堆(heap)或自由存储区(free store)的内存区域分配内存。
因此,指针变量 p 位于 stack 中,而 p 指向的地址位于 heap 中。
释放内存 —— delete
和 stdlib.h 库中的 malloc() 库函数分配内存之后需要用 free() 来释放不需要的内存一样,C++ 提供了 delete 运算符来释放 new 运算符申请的内存,这样在使用完内存之后,能够将其归还给内存池,这是有效使用内存的关键一步。如果只申请内存,在使用完之后不释放内存,就会发生内存泄漏(memory leak),也就是说,被分配的内存再也无法使用了。
注意:1. 只能用 delete 来释放 new 申请的内存;2. 不要释放已释放的内存块,这样做的结果是不确定的;3. 对空指针使用 delet 是安全的。
建议:一般来说,不要创建两个指向同一块内存块的指针,因为这样将会增加错误地删除同一个内存块两次的可能性。
动态数组
对于大型数据,如数组、字符串、结构,应该使用 new,这正是 new 的用武之地。例如,假设要编写一个程序,它是否需要数组取决于运行时用户提供的信息。如果通过声明来创建数组,则程序被编译时将会给它分配内存空间。不管程序最终是否使用数组,数组都会在那里占据内存。
在编译时给数组分配内存被称为静态联编,意味着数组是在编译时加入到程序中的。但使用 new 时,如果在运行阶段需要数组,则创建它,如果不需要,则不创建它,还可以在运行时选择数组的长度,这称为动态联编。这意味着数组是在程序运行时创建的,这种数组称为动态数组。
在 C++ 中,创建动态数组很容易,只要将数组的元素类型和元素数目告诉 new 即可。必须在类型名后面加上方括号,其中包括元素数目。例如,要创建一个包含 10 个 int 元素的数组,可以这样做:
int* p = new int [10];
对于动态数组,释放时使用 delete[] :
delete [] p;
方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。如果使用 new 的时候不带 [],则使用 delete 时也不应带 [];如果使用 new 时带 [],则使用 delete 时也应带 []。
注意事项总结
- 不要使用 delete 释放不是 new 分配的内存。
- 不要使用 delete 释放同一个内存两次。
- 如果使用 new[] 为数组分配内存,则应该使用 delete [] 来释放。
- 如果使用 new 为一个实体分配内存,则应该使用 delete 来释放。
- 对空指针应用 delete 是安全的。
- 不能用 sizeof 运算符来确定动态分配的数组包含的字节数。
指针是功能最强大的 C++ 工具之一,但也最危险,因为它们允许执行对计算机不友好的操作。
指针和数组的区别
区别 1:可以修改指针值,但不能修改数组名,数组名是常量。
区别 2:对数组名应用 sizeof 运算符得到的是数组的长度,而对指针应用 sizeof 运算符得到的是指针的长度,即使指针指向的是一个数组。而指针的长度在 64 位系统中为 8 字节,在 32 位系统中为 4 字节。