第四章 复合类型
C++ Primer Plus自学笔记, 可能不适用于所有人, 仅供参考
4.1 数组
1. 创建数组
例: 创建一个名为months
的数组,该数组有 12 个元素,每个元素存储一个short
类型的值:
short months[12];
months
的类型不是"数组", 而是"short
数组".- 强调了数组是使用
short
类型创建的.
若将一个值赋给不存在的元素months[101]
, 编译器不会报错, 但程序运行后可能引发一些严重问题.
- 必须确保程序只使用有效下标值, 避免数组越界.
// arrayone.cpp
#include <iostream>
int main() {
using namespace std;
int yams[3]; //创建有3个元素的int型数组
yams[0] = 7;
yams[1] = 8;
yams[2] = 6;
int yamcosts[3] = {20, 30, 5}; // 创建初始化数组;
// 使用数组元素
cout << "Total yams = ";
cout << yam[0] + yams[1] + yams[2] << endl;
cout << "The package with " << yams[1] << " yams costs ";
cout << yamcosts[1] << " cents per yam.\n";
int total = yams[0] * yamcosts[0] + yams[1] * yamcosts[1];
total = total + yams[2] * yamcosts[2];
cout << "The total yam expenses is " << total << " cents.\n";
// 数组及元素的 size 大小
cout << "\nSize of yams array = " << sizeof yams << " bytes.\n";
cout << "Size of one element = " << sizeof yams[0] << " bytes.\n";
return 0;
}
- 该程序的输出:
Total yams = 21
The package with 8 yams costs 30 cents per yam.
The total yam expense is 410 cents.
Size of yams array = 12 bytes.
Size of one element = 4 bytes.
- 数组的初始化:
int cards[4] = {3, 6, 8, 10};
int hand[4];
float hotelTips[5] = {5.0, 2.5}; // 只初始化前两个元素.
long totals[500] = {0}; // 将所有元素都初始化为 0
short things[] = {1, 5, 3, 8}; // things 数组将包含 4 个元素.
// C++11 数组初始化方法
double earning[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4}; // 省略等于号
unsigned int counts[10] = {}; // 将所有元素初始化为0.
float balances[100] {}; // 同上
long plifs[] = {25, 92, 3.0}; // 不可行: 禁止缩窄转换
char slifs[4] {'h', 'i', 1122011, '0'}; // 不可行
char slifs[4] {'h', 'i', 112, '0'}; // 可行: 没有发生缩窄转换.
- 确定数组的大小:
short things[] = {1, 5, 3, 8};
int num_elements = sizeof(things) / sizeof(short);
// 即可得到数组元素个数(4).
4.2 字符串
- C语言风格字符串
char dog[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; // 不是字符串: 字符串应以 '\0' 结尾. char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // 是字符串. char bird[11] = "Mr. Cheeps"; // 隐式的'\0'. char fish[] = "Bubbles"; // 让编译器计算数组长度. // 注意: 结尾填充空字符'\0' // 例如: char boss[8] = "Bozo"; // -> {'B', 'o', 'z', 'o', '\0', '\0', '\0', '\0'}
'S'
是字符常量, 而"S"
不是."S"
是字符串,包含了字符'S'
和'\0'
.- 实际上,
"S"
表示的是字符串所在的内存地址. - 下面的字符串将一个内存地址赋值给
char
类型:char shirt_size = "S"; // 不合法, 会报错.
- 在数组中使用字符串
- 引用头文件:
<cstring>
; 使用strlen()
函数, 计算字符串长度, 而不是数组本身的长度. (数组本身长度会包含'\0'
.)
- 引用头文件:
cin
:- 使用空格, tab, 换行符来确定字符串的结束位置.
- 因此,
cin
只能读取一个单词.
- 因此,
- 使用空格, tab, 换行符来确定字符串的结束位置.
- 面向行的输入:
getline()
:- 读取整行, 通过回车键确定输入结尾.
- 使用
cin.getline(数组名称, 读取的字符数)
.- 例如, 假设使用
cin.getline(name, 20)
, 则该行字符不应超过19个, 余下位置存储空字符'\0'
.
- 例如, 假设使用
- 面向行的输入:
get()
:- 不读取并丢弃行尾的换行符, 而是保留在输入队列中.
- 使用
cin.get()
的正确示例:cin.get(name, ArSize); // 读取第一行 cin.get(); // 读取换行符 cin.get(dessert, ArSize); // 读取第二行
- 混合使用
cin
和cin.getline()
可能会出现问题.- 原因是
cin
将回车键输入的换行符留在了输入队列中, 并被cin.getline()
识别. - 正确的示例:
// 1. 单独进行调用 cin >> year; cin.get(); // 或者 cin.get(ch); // 2. 将调用拼接起来: (cin >> year).get(); // 或者 (cin >> year).get(ch);
- 原因是
- C++常使用指针处理字符串, 而不是数组.
4.3 string
类
- 必须在程序中包含头文件
string
. - 位于命名空间
std
中, 故需要使用std::string
引用. - 例如:
#include <iostream> #include <string> int main() { using namespace std; char charr1[20]; // 创建空数组 char charr2[20] = "jaguar"; // 创建初始化数组 string str1; // 创建空字符串对象 string str2 = "panther"; // 创建初始化字符串 // ... }
- 可以使用C语言风格字符串来初始化
string
对象. - 可以对
string
对象使用cin
和cout
. - 可以用数组表示法访问
string
中的字符. (如:str[2]
)
- 可以使用C语言风格字符串来初始化
- C++11 字符串初始化
char first_date[] = {"Le Chapon Dodu"}; char second_date[] {"The Elegant Place"}; string third_date = {"The bread Bowl"}; string fourth_date {"Hank's Fine Eats"};
- 字符串赋值, 拼接, 附加
char charr1[20]; // 创建空数组 char charr2[20] = "jaguar"; // 创建初始化数组 string str1; // 创建空字符串对象 string str2 = "panther"; // 创建初始化字符串 // 赋值 charr1 = charr2; // 无效, 没有数组分配 str1 = str2; // 有效, 可以分配对象 // 合并 string str3 = str1 + str2; // str3 为 str1 与 str2 拼接. str1 += str2; // 在 str1 末尾附加 str 2.
- C-风格字符串: 头文件
<cstring>
strcpy(charr1, charr2); // 将 charr2 复制到 charr1 strcat(charr1, charr2); // 将 charr2 拼接到 charr1 末尾
- C-风格字符串: 头文件
- 确定字符串中字符数的方法:
int len1 = str1.size(); int len2 = strlen(charr1);
string
类输入输出:cin.getline(charr, 20); // 需要指定最大长度 getline(cin, str); // 不需要指定长度. cin 为参数
- 原始字符串 (C++11): 使用
R"()"
包括的内容.cout << R"(Jim "King" Tutt uses "\n" instead of endl.)" << '\n'; // 输出原始字符串 -> Jim "King" Tutt uses "\n" instead of endl. cout << "Jim \"King\" Tutt uses \"\\n\" instead of endl." << '\n'; // 需要使用转义字符.
4.4 结构体struct
- 结构体的声明, 初始化, 使用
#include <iostream> // 声明结构体 struct inflatable { char name[20]; float volume; double price; }; // 指定结构体中的位字段 (博主实践中暂时没遇到过) struct torgle_register { unsigned int SN : 4; // SN 的值为 4 位 unsigned int : 4; // 4 位未使用 bool goodIn : 1; // 布尔值: 有效输入 (1 bit) bool goodTorgle : 1; } // 初始化示例: torgle_register tr = {14, true, false}; int main() { using namespace std; inflatable guest = { // guest 是类型 inflatable 的一个结构体变量 "Glorious Gloria", // name 的值 1.88, // volume 的值 29.99 // price 的值 }; inflatable pal = { // pal 是类型 inflatable 的第二个变量 "Audacious Arthur", // name 3.12, // volume 32.99 // price }; // 如, pal.name[0] == 'A'. // 但是 pal[0] 没有意义, 因为 pal 是结构体, 不是数组. // 也可以把成员变量放在同一行中: inflatable duck = {"Daphne", 0.12, 9.98}; inflatable mayor {}; // mayor.name 每个字节都为 0; // mayor.volume = 0; // mayor.price = 0. // 可以将结构体赋值给同类型的结构体: inflatable choice; choice = guest; // 使用结构体中的成员变量: cout << "Expand your guest list with " << guest.name; cout << " and " << pal.name << "!\n"; cout << "You can have both for $"; cout << guest.price + pal.price << "!\n"; // 结构体数组 // 创建包含 100 个 inflatable 结构体的数组: inflatable gifts[100]; cin >> gifts[0].volume; cout << gifts[99].price << endl; // 结构体数组初始化 inflatable guests[2] = { {"Bambi", 0.5, 21.99}, // 数组中的第一个结构体 {"Godzilla", 2000, 565.99} // 数组中的第二个结构体 } return 0; }
4.5 共用体(联合体) union
- 与
struct
类似. - 能存储不同的数据类型, 但只能同时存储其中的一种类型.
- 常用于节省内存: 当数据项使用两种或更多种格式, 但不会同时使用时, 可节省空间.
- 例如, 一些商品的
ID
为整数,而另一些的ID
为字符串:
struct widget { char brand[20]; int type; // 1. 使用联合体: union id { long id_num; // type == 1 的商品 char id_char[20]; // 其他商品 } id_val; // 2. 使用匿名联合体: union { long id_num; // type == 1 的商品 char id_char[20]; // 其他商品 }; }; ... widget prize; // 结构体变量 prize, 类型为 widget ... // 1. 使用联合体的情况: if (prize.type == 1) { cin >> prize.id_val.id_num; } else { cin >> prize.id_val.id_char; } // 2. 使用匿名联合体的情况: // id_num 和 id_char 被视为 prize 的两个成员, // 地址相同, 不需要中间标识符 id_val. if (prize.type == 1) { cin >> prize.id_num; } else { cin >> prize.id_char; }
- 例如, 一些商品的
4.6 枚举 enum
- 等用到的时候再回来看.
4.7 指针和自由存储空间
#include <iostream>
int main() {
using namespace std;
int donuts = 6;
double cups = 4.5;
cout << "donuts value = " << donuts; // 数值
cout << " and donuts address = " << &donuts << endl; // 地址, 十六进制 (&, 取地址符)
cout << "cups value = " << cups; // 数值
cout<< " and cups address = " << &cups << endl; // 地址, 十六进制
return 0;
}
- C++使用
new
请求正确数量的内存,以及使用指针来跟踪新分配的内存的位置. &
运算符被称为"取地址".- 假设
home
是一个变量, 则&home
表示它的地址.
- 假设
*
运算符被称为"解引用".- 假设
manly
是一个指针, 则manly
表示的是一个地址,*manly
表示存储在该地址的值. *manly
与常规的int
类型等效.
- 假设
int updates = 6;
int* p_updates;
p_updates = &updates; // 指针 p_updates 指向 updates 的地址.
cout << "Value: updates = " << updates; // 输出: 6
cout << ", *p_updates = " << *p_updates << endl; // 输出: 6
cout << "Address: &updates = " << &updates; // 输出: updates 的地址
cout << ", p_updates = " << p_updates << endl; // 输出: updates 的地址
- 声明和初始化指针
- 强调
*ptr
是一个int
类型的值 (C常用) :int *ptr;
- 强调
int*
是一种类型 - 指向int
的指针 (C++常用) :int* ptr;
- 事实上, 在哪里添加空格对编译器来说没有任何区别.
- 强调
- 使用指针容易出现的危险
long* fellow; *fellow = 223323;
- 上述代码没有指定指针
fellow
指向的地址, 没有被初始化, 它可能有任何值, 程序都会将223323
存储在该地址上, 可能会导致隐匿的bug发生.
- 上述代码没有指定指针
- 指针和数字
- 不可以直接将数字赋值给指针:
int* pt; pt = 0xB8000000; // 报错; 类型不匹配.
- 要将数字作为地址值来使用, 应该进行强制类型转换:
int* pt; pt = (int*) 0xB8000000; // 类型匹配.
- 不可以直接将数字赋值给指针:
- 使用
new
分配内存- 相当于C语言中的库函数
malloc()
分配内存.int* pn = new int;
- 需要适合存储
int
的内存, 找到这样的内存, 并将该内存的地址值赋给pn
. pn
指向一个"数据对象", 指为数据项分配的内存块.- 为一个数据对象 (结构, 基本类型) 获得并指定分配内存的通用格式:
typeName* pointer_name = new typeName;
- 相当于C语言中的库函数
- 使用
delete
释放内存- 相当于C语言中的
free()
.
int* ps = new int; delete ps; // 可行 delete ps; // 此时不可行 int jugs = 5; int* pi = &jugs; // 可行 delete pi; // 不可行: delete 只能对 new 分配的内存使用.
- 一般来说, 不要创建两个指向同一个内存块的指针, 可能会导致对同一个内存块
delete
两次.
- 相当于C语言中的
- 使用
new
来创建动态数组int* psome = new int[10]; // 包含 10 个 int 元素的数组 delete[] psome; int* pt = new int; short* ps = new short[500]; delete[] pt; // 未定义行为, 不要使用! delete ps; // 未定义行为, 不要使用!
- 不要使用
delete
释放不是由new
分配的内存. - 不要使用
delete
释放同一个内存块两次. - 如果使用
new[]
为数组分配内存, 则应使用delete[]
来释放. - 如果使用
new
为一个实体分配内存, 则应使用delete
来释放. - 可以对空指针使用
delete
. - 为数组分配内存的通用格式:
type_name* pointer_name = new type_name[num];
- 不要使用
- 使用动态数组
- 相当于数组的使用.
注意其中double* p3 = new double[3]; p3[0] = 0.2; p3[1] = 0.5; p3[2] = 0.8; cout << "p3[1] is " << p3[1] << ".\n"; // 输出 -> p3[1] is 0.5. p3++; // 指针前进一位 cout << "Now p3[0] is " << p3[0] << " and "; cout << "p3[1] is " << p3[1] << ".\n"; // 输出 -> Now p3[0] is 0.5 and p3[1] is 0.8. p3--; // 指针回退 delete[] p3; // 释放内存
delete[]
的使用.
- 相当于数组的使用.