4.1 数组
- 存储类型统一的多组数据值
- 声明数组的通用格式
typeName arrayName[arraySize];
- 数组是复合类型,是因为数组是由其他类型来创建的,数组并不是一个数据类型,如果一点要说,它算是一个数据结构
- 调用元素方式
a[i]; // 使用[]号来调用数组元素 int b[3] = {1, 2, 3}; // 使用大括号的方式进行初始化
4.1.1
- 如果变量在初始化时没有声明,则该变量中存储的值是以前留驻在该内存中的值
4.1.2 数组的初始化规则
- C++数组初始化规则:
- 只有在定义的时候才能对数组进行初始化,之后就不能了
- 初始化时提供给数组的值可以少于数组元素数目
- 如果只初始化一部分数组元素,则剩下未初始化的数组元素会默认初始化为0
- 不能将一个数组赋值给另一个数组
- 可以使用下标单独对数组元素进行赋值
- 声明数组时[]内可以不填,编译器会自动计算花括号内的元素个数
sizeof 数组名
是合法代码,返回数组的大小,以字节为单位(没有将数组名用sizeof括号括起来)
- C++中大括号的初始化方式是一种通用的初始化方式,适用于所有类型的初始化
- C++列表初始化新增功能:
- 初始化数组时,可以省略等号例如:
double earns[3] {1, 2, 3};
- 大括号内可以不包含任何元素,这样会默认初始化为零
- 列表初始化禁止窄缩转换(高级数据类型降级为低级数据类型)
- 初始化数组时,可以省略等号例如:
4.2 字符串
- 以空字符(
\0
)结尾 - 数组字符串的初始化方法:
char dog[8] = {'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b'}; // 不是字符数组 char dog[8] = {'b', 'b', 'b', 'b', 'b', 'b', 'b', '\0'}; //是字符数组,结尾处是表示停止录入的空字符 char bird[11] = "Mr.cheeps"; //是字符串,这种写法被称为字符串常量,不需要显示写出空字符 char sheep[] = "Mr.sheep"; //字符串的另一种声明和初始化
- 通过键盘输入时,C++会自动在结尾加上空字符
- 在给出字符数组的存储长度时,长度=字符数量+空字符(长度为1)
- 字符串使用双引号,单个字符使用单引号
- 单个字符的字符串
"S"
实际上表示的是字符串所在的内存地址
4.2.1 拼接字符串常量
- C++允许两个并列的字符串自动拼接
cout << "i must eat a " "burger"; // 合法代码
4.2.2 在数组中使用字符串
- cstring库
4.2.3 字符串输入
- cin使用空白(空格、制表符、换行符)来确定字符串的结束位置,所以没办法输入带空格的字符
4.2.4 每次读取一行字符串输入
4.2.4.1 面向行的输入:getline()
- 该函数读取一整行,通过回车键入换行符来确定输入结尾,会将换行符一并读取到字符串中
- 调用方法:
cin.getline(); // 读取一行 cin.getline(name, 20); //第一个是数组名称,第二个是读取字符+空字符的和,表示将读取的字符串存入字符数组中
4.2.4.2 面向行的输入:get()
- 该函数读取一整行,和getline()一样的用法,但不会将换行符纳入字符串中,而是留在输入流中,举例如下:
cin.get(); //读取一个字符 cin.get(name, "hello"); // 读入字符串hello,将字符串末尾的换行符保留在输入流中 cin.get(name, "world"); // 本来应该读取world,但由于上一行代码中的换行符仍在输入流,因此该行代码会发生错误 // 正确做法 cin.get(name, "hello"); cin.get(); // 抵消换行符 cin.get(name, "world");
- 由于cin.get()和cin.getline()调用后会返回一个cin对象,因此可以进行拼接操作
cin.get(name, "hello").get()
cin.getline(name, "hello").getline(name, "world");
总之,getline使用起来简单,但个体检查错误更简单
4.2.4.3 空行和其他问题
- 当get读取空行后,将设置失效位,意味着接下来的输入将被阻断,可以使用cin.clear()来恢复输入
- 如果输入行包含的字符数比制定的多,则get和getline将余下的字符留在输入队列中;且getline会设置失效位,关闭后面的输入
4.2.5 混合输入字符串和数字
- 混合输入数字和字符时,
cin
也会只读取数字(int,float等)部分,将换行符留在输入流中,导致下一行读入失败,正确解决应该是:int year; char adress[80]; cin >> year; cin.get(); // 将year保留的换行符消除 (cin.get()).get(); // 也可以在cin后拼接 cin.getline(adress, "in my world");
4.3 string类简介
- C++98新增了string类来处理字符串,而不是像之前那样只能用字符数组来存储字符串
- 要使用string类需要在头文件中包含该库:
#include<string>
,且string类位于std空间中 - 可以使用数组表示法来访问存储在string对象中的字符
- string类字符串相比数组字符串更方便,也更安全
操作更加方便;第二是string能自动管理内存
4.3.1 C++11 字符串初始化
- C++11允许将列表初始化用于字符数组和字符串:
char second_date[] = {"hello world"}; string third_date = {"hello C++"};
4.3.2 赋值、拼接、附加
- 不能将一个数组赋给另一个数组,但字符串可以
- 可以使用
+
将两个字符串合并,并且可以使用+=
对字符串进行操作,将字符串加到操作符左边变量的末尾
4.3.3 string类的其他操作
在cstring库中:
- strcpy(ch1, ch2):将ch2中的字符串复制到ch1中
- strcat(ch1, ch2):将ch2中的字符串拼接到ch1的结尾
- strncat(ch1, ch2):接受指出目标数组最大允许长度的第三个参数,避免了strcat时复制过长数组直接报错
- strncpy(ch1, ch2):同上
- strlen(ch):接受C-风格字符串作为参数,返回字符串包含的字符数
- string.size():同上
4.3.4 string类I/O
- 对于未被初始化的字符数组,由于数组未被定义,因此第一个空字符出现的位置是随机的,所以未初始化的字符数组长度与声明不符是正常的;而未被初始化的string字符,默认长度为0
- istream类中有处理double,int和其他基本类型的方法,但没有处理string对象的方法
4.3.5 其他形式的字符串字面值
- C++11新增了char16_t, char32_t类型;指出Unicode字符编码方案;
- C++11还新增了一种原始字符串,在原始字符串中,字符串表示的就是本身,例如
\n
表示的字符就是\n;原始字符串使用“(和)”作为界定符,并且使用R来标识原始字符串:cout << R"(hello "world"! \n here is just a string)" << "\n";
4.4 结构简介
- 数组用于存储同种数据类型的数据;如果要存储不同数据类型的数据,则使用结构数据类型来存储
.
:成员运算符- 访问类成员函数的方式是从访问结构成员变量衍生而来
4.4.1 在程序中使用结构
- 结构变量可以使用大括号的方式进行初始化
- 结构在外部声明可以被后面的任何函数使用,内部声明只能被该声明所属的函数使用
- 变量也可以在函数内部和外部声明,外部变量由所有函数共享
- 结构变量中存在字符数组变量时,调用该变量,使用该变量的时候可以使用字符数组调用元素的方式调用字符数组中的字符
- 结构类型的变量不能使用数组调用元素的方法来调用结构体中的变量
4.4.2 C++11 结构初始化
- 结构支持和数组相同的,不带等号的大括号初始化方式进行初始化;也不允许缩窄转换
4.4.3 结构可以将string类作为成员
- 也可以将其他自定义类作为结构体成员;支持所有变量类型的变量作为结构体成员
4.4.4 其他结构属性
- 可以让函数返回一个结构
- 可以使用赋值运算符(=)将结构赋值给另一个同类型的结构,此时结构中的每个成员都将被设置为另一个结构中相应成员的值,这种叫做成员赋值
- 结构可以同时完成定义结构和创建结构变量的工作,例如:
struct perks{ int key_number; char car[12]; }mr_smith, mr_john; // 此时定义了结构体,并创建了两个变量:mr_smith,mr_john
- 可以声明没有名称的结构类型,省略名称的同时定义一种结构类型和一个这种类型的变量,例如:
但因为该类型结构体没有名称,所以只能在创建的时候申请该类型的结构体变量,之后就无法申请了struct{ int x; int y; } position; // 创建一个名为position的结构体,并声明一个名为position的变量
- 与C语言中的结构体不同,C++的结构体特性更多,还支持除了成员变量以外的成员函数,但成员函数大多存在于类中
4.4.5 结构数组
- 使用struct申明结构后,可以创建结构类型的数组,数组的每一个元素是一个结构实例
- 结构数组的数组层面初始化和数组初始化相同,元素的初始化和结构实例初始化相同;最终初始化的形式是两个大括号对结构数组进行初始化,第一个大括号初始化数组,第二个大括号初始化结构元素
4.4.6 结构中的位字段
- 位字段主要用于指明变量所占位数
struct torgle{ unsigned int SN : 4; // 4 bit for SN value unsigned int : 4; // 4 bit unused bool goodIn :1; // 1 bit };
4.5 共用体
- 共用体是一种数据格式,存储不同的数据类型,但只能同时存储其中的一种类型,且每次只能存储一个值,与结构体类似
union one4all { int int_val; long long_val; double double_val; }; // 可以用来存储 int、long或者double,通过元素名称来记录存储该数据的数据类型
- 共用体的长度为最大成员的长度
- 结构体中可以定义共用体
- 共用体的用途:当数据项使用两种或更多种格式数据,但不同同时使用时,可以节省空间;例如商品名称有的是编号,有的是数字
- 匿名共用体没有名称,
4.6 枚举
- 枚举类型是整型,是一种数据类型
- 枚举类型创建和说明
此处创建了一个spectrum枚举类型,red orange green等作为符号常量,对应的值为0~7,一共八个值,每个枚举类型只能有8个符号常量,这些符号常量叫做枚举量;按照声明顺序从0开始给枚举量赋值,例如red值为0,orange值为1,以此类推;可以通过显示指定来替代默认值enum spectrum {red, orange, green, blue, violet, indigo}; spectrum blue; // valid spectrum 2000; // invalid
- 为了最大限度保持可移植性,非enum变量赋值给enum变量将会报错;允许枚举量给枚举量赋值
blue = band;
- 枚举只有赋值运算,没有算术运算
- 枚举量是整型,可以提升为int类型,但int类型不能自动转换成枚举类型
int color = blue; // valid green = 3; // invalid color = 3 + blue; // valid
- 如果int值是有效的(0~7),则可以通过强制转换,将int类型转换成枚举量
- 如果只打算使用枚举常量,不使用枚举变量,可以做下面的声明:
enum {red, orange, green, blue, violet, indigo};
4.6.1 设置枚举量的值
- 可以使用赋值运算符来显式设置枚举量的值:
enum bits { one=1, two=2, four=4, eight=8}; //可以指定一部分枚举量 enum bits { one, two=2, four, eight=8}; //可以指定一部分枚举量的值,这里的one处于第一位,默认情况下赋值为0 enum bits { one, second_one=0, four, third_one=0}; // 可以创建多个值相同的枚举量
4.6.2 枚举量的取值范围
- 对于枚举来说,只有在声明中指出的枚举量的值是有效的,然而C++现在通过强制类型转换,增加可赋给枚举变量的合法值
- 选择用多少空间来存储枚举是由编译器决定的,可以通过尝试寻找最大值下最大2次幂来确定枚举类型的最大可赋值范围
4.7 指针和自由存储空间
- 指针是一种独立的数据类型,声明该类型的语法为: (int *) ,因此并不能使用其他数据类型来赋值一个指针数据类型
- 指针是一个变量,存储的值为地址,地址指向的位置才存储的是值本身
- 常规变量通过使用地址运算符(&)来获取地址,例如home是变量,则&home表示home的地址
- 面向对象编程(OOP)强调的是在运行阶段进行决策,编译阶段决策不管什么条件下都坚持预先设定的执行
- C++采用关键字new请求正确数量的内存,以及使用指针来跟踪新分配的内存的位置
- *运算符被称为间接值运算符/解除引用运算符,通过对指针变量使用间接值运算符来获取指针中地址指向的值
4.7.1 声明和初始化指针
- 指针和变量的关系,一个有意思的例子:
int * p; // 中立的写法 int* p; // 声明一个int*类型的指针p,指针指向变量* p;C++中int*是一个复合数据类型 int *p; // 声明一个int类型的变量*p,p是存储该变量地址的指针
- 指针指向不同的数据类型,数据类型的长度可能不相同,但不同指针本身的长度是相同的
- 使用&地址运算符获取变量的地址,赋值给指针变量
4.7.2. 指针的危险
- 创建指针时,系统会分配存储地址的内存,但不会分配地址指向的数据的内存,例如:
int* p; // 声明一个指针,但并未进行初始化,危险在此处 *p = 22322; // 会报错
4.7.3 指针和数字
- 指针存储的地址虽然是整数,但类型并不是整数,直接赋值会出现错误:
int* pt; pt = 0xB8000000; // 错误,将整型赋值给指针变量 pt = (int *) 0xB8000000; // 正确赋值
4.7.4 使用new来分配内存
-
一般在程序运行的过程中使用new进行内存申请;C语言可以使用malloc()函数申请内存,而C++一般使用new来申请内存
-
使用new申请内存的格式如下:
数据类型 *符号常量 = new 数据类型
-
变量是在编译时分配的有名称的内存,指针是为可以通过名称直接访问的内存提供了一个别名(指针是内存的别名)
-
指针的用武之地在与,在运行阶段分配未命名的内存以备储值,意思是说,变量取值需要寻址,寻找变量的地址,再通过变量的地址取出变量的值,但如果使用指针,那么就减少了寻址这一步,直接取到地址
-
通过new声明的内存和变量,只能使用指针来访问;而通过变量赋值的指针,可以通过变量本身和指针来访问
-
计算机可能会由于没有足够的内存而无法满足new请求
-
值为0的指针被称为空指针,通常表示运算符或函数失败
4.7.5 使用delete释放内存
- 使用new分配指针后,需要使用delete删除,否则会发生内存泄漏,就是说被分配后的内存不能再使用,如果内存严重泄漏,程序会因为不断寻找更多内存而终止
- 不能使用delete释放变量声明的内存,只能用delete释放使用new分配的内存,对空指针使用delete是安全的
- 一般来说,不要创建两个指向同一内存块的指针;需要多个指针同时指向同一个地址的,有其他类型的指针解决该问题
4.7.6 使用new来创建动态数组
- 对于大型数据(数组、字符串、结构等)建议使用new来创建指针
- 静态联编:在编译时就为数组分配内存,且数组长度需要指定
- 使用new时,如果在运行阶段需要数组,则创建,如果不需要,则不创建
- 动态联编:在程序运行时选择数组的长度,对应数组被称为动态数组
- 使用new创建动态数组
int *p = new int [10]; // 其中10表示数组长度,需要方括号来声明;new运算符返回第一个元素的地址,该地址被赋予p delete [] p; // 方括号告诉程序释放整个数组,不仅是指针指向的元素;new和delete对于方括号的使用需要同一,同用则用,不同用则不用
- 在指针数组中,指针名可以当作数组名来使用;在C/C++中数组和指针基本等价;
int *p = new int [10]; p[0]; // 第一个元素 p[5]; // 第六个元素
- 指针+1表示指针元素从当前指向下一个,并不是内存意义上的+1
4.8 指针、数组和指针算术
- 指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式
- 指针+1后,其增加的值等于指向的类型占用的字节数;也说明C++将数组名解释为地址
- 数组存在下面的等式:
double wages[3]; double* pw = wages; wages == &wages[0];
4.8.1 程序说明
- 指针变量加一,表示指针增加了其指向类型占用的字节数。例如 int 类型占用 4 字节,那么指向 int 类型的指针 +1,指针指向的内存增加四个字节。
- 因此,在很多情况下,使用指针名和数组名进行操作都是相同的方式,区别在于指针可以修改指针的值,而数组名是常量
- sizeof 对指针使用,获取的是指针长度;对数组使用,则获取整个数组的长度
- 数组名指针指向整个数组,而指针指向单个元素
4.8.2 指针小结
- &获取被命名的内存地址,new获取未命名的内存地址
- C++将数组名视为数组的第一个元素的地址
- C++允许同类型指针相减,得到两个指针变量元素数量的整数
- 数组的静态联编是指在编译时就确定数组大小;动态联编指在运行过程中为数组分配空间
- 指针解除引用:*
- 数组方括号表示法等价于指针的解除引用
4.8.3 指针和字符串
- 如果给 cout 提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止;意思就是说,如果声明了一个字符类型的指针,那么只需要给出指针名称给cout,一样也能正确输出指针指向的字符串。
- 要打印字符串地址,需要使用
(int *)
进行强制转换,将字符类型强制转换为整型。 - 字符操作库:cstring
- 在获取字符串副本时,应该使用strcpy函数或strncpy函数进行拷贝,前者不考虑字符串复制的溢出,只接收两个参数;后者接收三个参数,第三个参数为拷贝数组的容量。注意需要为拷贝数组留出一个存放空字符的位置。