📔 第四章 复合类型 学习笔记
👉【复习题】【编程练习题】¶
1. 数组概述¶
1.1 数组的定义¶
数组(array)是一种数据格式,能够存储多个同类型的值。每个值都存储在一个独立的数组元素中,计算机在内存中依次存储数组的各个元素。
数组声明的三个特点:
- 存储在每个元素中的值的类型
- 数组名
- 数组中的元素数
C++中可以通过修改简单变量的声明,添加中括号(其中包含元素数目)来完成数组声明。
例如:
1.2 数组的声明¶
声明数组的的一般语法格式为:
数组的大小是指定元素的数目
,必须是整型常数或const值
,也可以是常量表达式(8*sizeof(int))
1.3 复合类型的数组¶
可以使用其他的类型来创建(C语言使用术语:派生类型
)
数组的用途,可以单独访问数组元素,方法是:使用下标
或索引
对元素进行编号。从0开始编号
。
编译器不会检查下标是否有效,所以要注意下标合法性,避免程序异常问题。 C++使用索引的方括号表示法来指定数组元素。
1.4 数组的初始化规则¶
-
只有在定义数组时才能初始化,此后不能使用,也~不能将一个数值赋给另一个数组~。
-
初始化数组时,提供的值少于数组的元素数目。
-
如果只对数组的一部分进行初始化,则编译器把其他元素设置为0。
-
如果初始化为
{1}
而不是{0}
,则第一个元素被设置为1,其他元素都被设置为0. -
如果初始化数组方括号内
([])
为空,C++编译器将计算元素个数
。 例如
1.5 C++11数组初始化方法¶
C++11将使用大括号的初始化(列表初始化)
作为一种通用的初始化方式,可用于所有类型。
在C++中列表初始化就增加了一些功能:
- 初始化数组时,可省略
等号(=)
- 可不在大括号内包含任何东西,这会将所元素都设置为零。
- 列表初始化禁止缩窄转换。
例子:
2. 字符串¶
字符串是存储在内存的连续字节中的一系列字符。
2.1 C++处理字符串的两种方式:¶
-
C语言,常常被称为
C-风格字符串(C-style String)
以空字符(\0,ASCII码对应为0)来标记字符串的结尾。
-
基于String类库的方法
存储在连续字节
中的一系列字符意味着可以将字符串
存储在char数组
中。其中每个字符都位于自己的数组元素中。
使用引号
括起来的字符串,这种字符串叫 字符串常量(String constant)
或 字符串字面值(string literal)
。
字符串常量(使用双引号)不能与字符常量(使用单引号)互换。
例如:
字符串结尾的空字符
,不用直接显式包括
,机器在键盘输入,将字符串读入到char类型
中,会在结尾自动加上空字符
。
⚠️注意:确定了存储字符串所需的最短数组时,不要忘记把结尾的空字符
包括在内。
2.2 字符串常量的拼接¶
方法:直接两个引号括起来的字符串合并为一个。任何两个由空白(空格、制表符和换行符)
分隔的字符串常量都将自动拼接成一个。
2.3 在数组中使用字符串¶
将字符串存储到数组的常用方法:
- 将数组初始化为字符串常量
- 将键盘或文件输入读入到数组中。
strlen() 函数
和 sizeof()运算符
的区别
-
strlen()
函数- 返回的是
存储在数组中的字符串的长度
,而~~不是数组本身的长度~~
。 - strlen()只计算
可见的字符
,而~不把空字符计算在内~。
- 返回的是
-
sizeof()
运算符- 指出
变量
或数据类型
的字节大小
。 - 可用于获取
类、结构、共用体和其他用户自定义数据类型
的大小。
- 指出
2.4 读取一行字符串的输入¶
解决没有逐行读取输入的缺陷。
istream中提供了面向行的类成员函数:getline()
和 get()
函数
2.4.1 面向行的输入:getline()
¶
使用通过回车键输入的换行符来确定输入结尾。使用 cin.getline()
。
函数有两个参数:
- 第一个参数:存储输入行的
数组名称
。 - 第二个参数:要读取的字符数(注意包含结尾的
空字符(\0)
)。
格式:
2.4.2 面向行的输入:get()
¶
与getline()
函数类似,接受的参数相同
,解释参数的方式也相同,并读到行尾
。
区别:get()
读取并丢弃
换行符,将其留在输入队列中。
格式:
get() 将两个类成员函数拼接(合并):
⚠️注意:get() 函数读取空行后设置会失效,输入会被阻断。可用如下恢复:
混合输入数字和面向行的字符串会导致的问题:无法输入地址。
解决方法:直接使用get()进行读取之前丢弃换行符。
3. string类¶
string类
位于名称空间std
中,所以需要提供using指令
或者是直接使用std::string
进行引用。
要使用string类
,必须在程序中包含头文件string
中。
string类定义隐藏了字符串的数组性质。
3.1 string对象的方式¶
使用string对象的方式和使用字符数组相同。
C-风格字符串
来初始化string对象中。- 使用
cin来将键盘输入
存储到string对象中。 - 使用
cout
来显示string对象。 - 可以使用
数组表示方法
来访问存储在string1对象中的字符。
赋值 —— 不能将一个数组赋给另一个数组,但可以将一个string对象赋另一个string对象。
3.2 复制、拼接和附加¶
string类简化字符串合并操作。
- 利用
运算符 +
将两个string对象合并起来。
- 可以使用
运算符 +=
将字符串附加
到string对象的末尾
。
4. 结构简介¶
结构是用户定义
的类型,而结构声明定义了类型的数据属性
。
定义类型之后,就直接创建类型的变量。
结构比数组灵活,同一个结构中可以存储多种类型的数据。
4.1 创建结构的步骤:¶
-
定义结构描述 —— 描述并标记能够存储在结构中的各种数据类型
-
按描述创建结构变量(结构数据对象)。
4.2 结构的定义:¶
对于结构中的成员,使用成员运算符(.)
来进行访问各个成员。
4.3 结构的初始化(C++11)¶
- 与数组一样,列表的初始化用于结构,且
等号(=)可有可无
。
- 如果大括号内未包含任何东西,各个成员都将设置为零。
- 不允许缩窄转换
✅ 小Tips:C++允许在声明结构变量时省略关键字struct。
4.4 成员赋值¶
成员赋值(memberwise assignment):可以使用赋值运算符(=)
将结构赋另一个同类型的结构。这样结构中的每个成员都将被设置为另一个结构中相应成员的值。即使成员是数组。这种方式就是成员赋值
。
5. 共用体¶
共用体(union),也叫做联合(union)
。一种 构造数据类型
。
关键字:union
联合(union):将不同类型的数据
在一起共同占用同一段内存
存储不同的数据类型,但只能同时存储其中的一种类型
示例:
5.1 结构体和共用体的区别¶
- 结构可以
同时存储int、long和double
。 - 共用体
只能存储int、long和double
三种。 - 含义不同。
- 关键字不同
- 结构体:struct
- 共用体:union
5.2 共用体的用途:¶
- 当数据使用两种格式或更多格式(但不会同时使用)时,可以节省空间。
- 嵌入式系统编程(如控制烤箱、MP3播放器),内存非常宝贵。
- 常用于操作系统数据结构或硬件数据结构。
5.3 匿名共用体¶
匿名共用体(anonymous union)没有名称
,其成员将成为位于相同地址
处的变量。
6. 枚举¶
C++的enum工具提供了另一种创建符号常量
的方式,可以代替const,允许定义新类型,但必须有严格限制。
使用enum的语法格式与结构的使用类似。
6.1 设置枚举量的值¶
指定的值必须是整数
。也可以只显示定义其中一些枚举量的值
。
如果第一个变量未初始化,默认为0。后面没有被初始化的枚举量的值将比其前面的枚举量大1。也可以创建多个值相同的枚举量。
6.2 枚举的取值范围¶
每个枚举都有取值范围的上限,通过强制类型转换,可以将取值范围中的任何整数值赋给枚举常量,即使这个值不是枚举值。
6.3 取值范围的定义¶
- 找出上限,需要知道枚举量的最大值。
- 找到大于最大值的,最小的2的幂,减去1,得到就是取值范围的上限。
- 计算下限,知道枚举量的最小值。
- 如果不小于0,则取值范围的下限为0,否则,采用寻找上限方式相同的方式,但是要加上负号。
对于选择使用多少空间来存储枚举由编译器
决定。
7. 指针和自由空间¶
对于地址显示结果是十六进制表示法
,因为都是常常描述内存的表示法
。
- 指针与C++基本原理
面向对象编程和传统的过程性编程的区别,OOP强调的是运行阶段(而不是编译阶段)进行决策。
- **运行阶段**:程序正在运行是,取决于不同的情况。
- **编译阶段**:编译器将程序组合起来时。坚持原先设定的安排
指针用于存储值的地址。指针名表示的是地址。
*运算符
称为间接值或解除引用运算符,将其应用于指针,得到该地址处存储的值。
7.1 声明和初始化指针¶
指针的声明必须指定指向的数据的类型
。
*p_updates
的类型是int
,所以*运算符
被用于指针
,所以p_updates变量必须是指针。
运算符*两边的空格
是可选的。
在C++中,int*
是一种复合类型,是指向int的指针
。
7.2 指针的危险¶
在C++创建指针时,计算机将分配用来存储地址的内存
,但是不会分配用来存储指针所指向的数据的内存。
⚠️注意:一定要在对指针应用解除引用运算符(*)
之前,将指针初始化为一个确定
的、适当的地址
。
7.3 指针和数字¶
整数可以加减乘除等运算,而指针
描述的是位置
。
C++语言~数字不能作为地址使用~,如果要把数字当地址来使用,应通过强制类型转换
将数字转换为适当的地址类型。
7.4 使用new分配
和delete释放
内存¶
指针在运行阶段
分配未命名的内存以存储值。然后使用内存来访问内存。
C语言中,使用 库函数malloc()来分配内存。C++中使用 ———— new运算符。
7.4.1 要注意使用delete进行内存的释放¶
需要内存时,直接使用new来请求,这是内存管理数据包的一个方面。
如果使用了delete运算符
,使得在使用完内存后,能够将其归还给内存池
,这是有效使用内存的关键。
使用delete时,后面要加上指向内存块的指针。
⚠️注意点:¶
1.使用delete释放ps的内存,但是~不会删除指针ps本身~。
2.只能用delete
来释放使用new分配的内存
,但是如果是空的指针
使用delete是安全的。
使用delete的关键:用于new分配的内存
。不是要使用于new的指针,而是用于new的地址
。
❌警告:不能创建两个指向同一个内存块的指针。会增加错误地删除同一个内存块两次的可能性。
7.5 使用new创建动态数组¶
C++中,创建动态数组,只需要将数组的元素类型
和元素数目
告诉new即可。必须在类型名
后面加上方括号
,其中包含了元素数目。
通用格式:
new运算符会返回第一个元素的地址
如果使用完new分配的内存,使用delete进行内存的释放。
delete和指针直接的方括号告诉程序,应释放整个数组
,不仅仅是指针指向的元素。
delete中的方括号的有无
取决于使用new时的方括号有无
。
对于指针数组的使用,直接可以按照普通数组的使用即可。
7.6 使用new和delete时,要遵循的规则¶
- 不要使用delete来释放不是new分配的内存。
- 不要使用delete释放同一个内存块两次。
- 如果使用
new[]
为数组
分配内存时,则应使用delete[]
来释放。 - 如果使用new[]为一个
实体
分配内存,则应使用delete(没有方括号)
来释放。 - 对空指针使用delete时很安全。
8. 指针、数组和指针算术¶
指针和数组基本等价的原因:指针算术(pointer arithmetic)
和C++ 内部处理数组的方式
。
- 对
整数变量
+ 1,其值
增加1- 对
指针变量
+ 1,增加的量等于它指向的类型的字节数
。 获取数组地址的两种方式
8.1 指针问题小结¶
8.1.1 声明指针¶
要声明指向特定类型的指针,语法格式:
8.1.2 给指针赋值¶
将内存地址赋给指针。可以对变量名应用 & 运算符
,来获得被变量名的内存地址
,new运算符返回未命名的内存的地址。
示例:
8.1.3 对指针解除引用¶
对指针解除引用意味着获得指针指向的值
。
- 方法1:对指针应用解除
引用
或间接值运算符(*)
来解除引用。
- 方法2:使用
数组表示法
。不可以对未初始化为适当地址的指针解除引用。
8.1.4 数组名¶
多数情况下,C++将数组名
视为数组的第一个元素的地址
。
8.1.5 指针算术¶
C++中允许指针和整数相加
。加1 的结果等于原来的地址值
加上指向的对象占用的总字节数
。
也可以将一个指针减去另一个指针,获得两个指针的差。得到一个整数,仅当两个指针指向同一个数组(也可以指向超出结尾的一个位置)时,这种情况会得到两个元素的间隔。
8.1.6 数组的动态联编和静态联编¶
使用数组声明来创建数组时,将采用静态联编
,即数组长度在编译
时设置。
使用new[]运算符
创建数组时,将采用动态联编(动态数组)
,即将在运行时为数组分配空间,其长度为运行时设置。
使用这类数组后,要使用
delete[]
释放所占用的内存。
8.1.7 数组表示法和指针表示法¶
使用方括号数组表示法
等同于对指针解除引用
。
数组名和指针变量也是一样。所以对于指针和数组名,既可以使用指针表示法
,也可以使用数组表示法
。
8.2 指针和字符串¶
数组名是第一个元素地址
。
如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止。
在cout和多数C++表达式中,char数组名
、char指针
以及用引号括起来的字符串常量
都被解释为字符串第一个字符的地址
。
不要使用字符串常量或未被初始化的指针来接收输入。
在字符串读入程序时,应使用已分配的内存地址。该地址不是数组名,也可以使用new初始化过的指针。
strcpy()
接受两个参数,第一个:目标地址
,第二个:要复制的字符串的地址
。
要确定目标空间有足够的空间来存储副本。
8.3 使用new创建动态结构¶
对于在指定结构成员时,句点运算符
和箭头运算符
的选择时:
- 如果结构标识符是结构名
,则使用句点运算符(.)
。
- 如果标识符是指向结构的指针
,则使用箭头运算符(->)
。
把new用于结构的两个步骤 - 创建结构
要创建结构,需要同时使用结构类型和new。 - 创建访问其成员。
8.4 C++管理数据内存的方式¶
- 自动存储
在函数内部定义的常规变量使用自动存储空间,称为
自动变量
。只在特定函数被执行时存在。
自动变量时一个局部变量
,作用域为包含它的代码块
。通常存储在栈
中,遵循后进先出(LIFO)
。
- 静态存储
- 变量称为静态的方式
- 在函数外面定义
- 在声明变量时使用关键字static。
整个程序执行期间都存在的存储方式(存在于程序的整个生命周期
)。
- 动态存储
内存池(自由存储空间或堆)用于静态变量和自动变量,且内存是分开的。
- 线程存储(C++11特性)
9. 数组替代品 --- 模板类¶
模板类vector
和array
是数组的替代品。
9.1 模板类vector¶
模板类vector
类似于string
类,也是一种动态数组
。
vector对象
包含在vector头文件
中。- vector包含在名称空间std中,使用
using编译指令
、using声明
或std::vector
。 - 模板使用不同的
语法
来指出它存储的数据类型
。 - vector类使用不用的语法来指定
元素数
。
9.2 模板类array(C++11)¶
位于名称空间std
中,与数组一样,array对象的长度固定
,也使用栈(静态内存分配)
,而不是自由存储区
。
头文件 array。
9.3 数组、vector和array的区别¶
无论是数组、vector对象还是array对象,都可使用标准数组表示法
来访问各个元素。
从地址
可知,array对象和数组存储在相同的内存区域(即栈)
中,vector对象存储在自由存储区域或堆
中。
可以将一个array对象赋给另一个array对象,对于数组,必须逐个
元素复制
数据。