C++ primer plus 第四节 复合类型
本章内容包括:
• 创建和使用数组
• 创建和使用 c-风格字符串
• 创建和使用 string 类字符串
• 使用方法`getline( )和 get( )读取字符串
• 混合输入字符串和数字
• 创建和使用结构
• 创建和使用共用休
• 创建和使用枚举
• 创建和使用指针
• 使用 new和delete 管理动态内存
• 创建动态数组
• 创建动态结构
• 自动存储、静态存储和动态存储
• vector 和 array 类简介
系列文章目录
C++ primer plus 第一节 步入C++-CSDN博客
C++ primer plus 第二节 hello world刨析-CSDN博客
C++ primer plus 第三节 数据处理-CSDN博客
目录
系列文章目录
前言
一 数组
二 字符串
三 string简介
四 结构体
五 指针
六 自动存储、静态存储和动态存储
七 vector简介
总结
前言
这里主要是针对具有C语言的基础的同学
一 数组
1 数组声明的方式
类型 名字 [ 长度 ]
数组里面的元素都是相同的类型,不可以为不同的类型2 关于数组初始化
int cat[4]; cat[4]={1,2,3,4}//erro
数组在初始化的时候最好是在定义的时候进行初始化
int cat[ ]={1,2,3,4};
这个没有明确长度,是编译器自己去计算长度是多少的3 C++11 数组初始化方法
·初始化数组时,可省略等号(=)
·可不在大括号内包含任何东西,这将把所有元素都设置为零
·列表初始化禁止缩窄转换,也就是不可以让低类型含有高类型
long a[10]={12,9.0}; //have 9.0
二 字符串
1 C语言字符串风格
C-风格字符串具有一种特殊的性质:以空字 符 (null character) 结尾,空字符被写作\0,其 ASCIl码为 0,用来标记字符串的结尾
2 数组的表现形式
👉1.数组表现形式char dog[4] = { 'd','o','g','\0'}; //yes char cat[3] = { 'c','a','t'}; //NO
如果使用 cout显示上面的 cat数组(它不是字符串), cout将打印出数组中的 8 个字母,并接着将内存中随后的各个字节解释为要打印的字符,直到遇到空字符为止,由于空字符(实际上是被设置为0的 字节)在内存中很常见,因此这一过程将很快停止。但尽管如此,还是不应将不是字符串的字符数组当作字符串来处理
疑问:那么当用strlen和sizeof计算的数组里面字符串的时候,他们分别返回啥?
我们有这样一个代码,接下来我们就来看看结果怎么样
这里看到strlen返回的是数组的长度,cout返回的是字符串的长度
如果要访问字符串其中单个字母的话就直接用索引就好了👉2.双引号表现形式
char a[5] = "this";
这个不需要后面加上\0,编译器会自动添加\0,不需要自己手动添加,
这里需要注意的是,我们在创建这个长度的时候,一定要给\0留一个位置来存放,C++对字 符串长度没有限制
注意,字符串常量(使用双引号)不能与字符常量(使用单引号)互换 ,也就是说,字符串不可以用单引号来表示
3 拼接字符串常量cout << "I'd give my right arm to be" " a great violinist.\n"; cout << "I'd give my right arm to be a great violinist.\n";
上面运用了字符串拼接
但是第一个字符串后面的\0怎那么办呢?
拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个 字符串的最后一个字符(不考虑\0) 后面。第二个字符串中的\0 字符将被第二个字符串的第一个字符取代
4 cin输入危机
由上述可知cout跟C语言的`printf一样的,遇到空格会自动截取识别
C进阶 数据的存储-CSDN博客,这个里面有printf的知识点,可以去看
5 每次读取一行字符串输入
istream中的类(如 cin) 提供了一些面向行的类成员函数:getline( )和 get( )。这两个函数都读 取一行输入,直到到达换行符。然而, 随后getline( )将丢弃换行符,而get( )将换行符保留在输入序列中。 下面详细介绍它们,首先介绍getline( )
👉1.面向行的输入:getline()
getline( )函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。要调用这种方法,可以使用cin.getline( )。该函数有两个参数。第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数
如果这个参数为 40. 则函数最多读取 19个字符,余下的空间用于存储自动在结尾处添加的空字 符。getline( )成员函数在读取指定数目的字符或遇到换行符时停止读取
cin.getline(name ,20);
这将把一行读入到name数组中——如果这行包含的字符不超过 19个。getline( )成员函数还可以接受第三个可选参数,将在后面进行介绍,现在只需要接受这些知识点
我们用这个函数是可以进行读取一行的
注意这个图上说的,换行符号会变成空白字符
👉2 面向行的输入: get()
我们来试试另一种方法,istream类有另一个名为get( )的成员函数,该函数有几种变体。其中一种变 体的工作方式与getline( )类似,它们接受的参数相同,解释参数的方式也相同,并且都读取到行尾。但get 并不再读取并丢弃换行符,而是将其留在输入队列中。假设我们连续两次调用get( )
我们不难看到,这个get()是遇到后面的换行了,所以这里就没有展现你后面的零食输入,证明他可以保存这个换行符号
由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第-个字符便是换行符。因 此get( )认为己到达行尾,而没有发现任何可读取的内容。如果不借助于帮助, get()将不能跨过该换行符
注意⚠ 这里的get()不是把换行弄没了,而是把遇到换行就终止了
幸好,get()有另外一个变体
使用不带任何参数的cin.get()调用可读取下一个字符(即使是换行符), 因此可以用它来处理换行符,为读取下一行输入做好准备
那么我们就有如下代码
另一种使用get()的方式是将两个类成员函数拼接起来〈合并),如下所示:
cin.get(name, ArSize) .get(); // concatenate member functions
getline()也是这样
这里的get()有两个版本,就体现出了函数的重载,这个后面会详细的讲述
总之,getline()使用起来简单一些,但 get()使得检查错误更简单些但是当我们输入遇到空行的时候怎么办?
std::getline()
- std::getline()用于从输入流中读取一行,直到遇到换行符`\n`为止,并将换行符从输入流中丢弃
- 如果输入流中遇到空行(即只有换行符),std::getline()会将读取到的空字符串存储到目标变量中,并且不会设置失效位(failbit)。因此,下一次调用std::getline()会从空行之后的位置继续读取
std::istream::get()
- std::istream::get()函数可以以多种方式使用,例如读取单个字符或读取一定数量的字符。
- 如果std::istream::get()读取到空行,它会将换行符存储到目标变量中,并且可能会设置失效位(failbit)。这取决于std::istream::get()的具体调用方式和输入流的状态。
- 如果失效位被设置,后续的输入操作将被阻断,直到调用std::cin.clear()来清除错误状态
std::cin.clear()的作用✅当输入流的失效位被设置后,后续的输入操作会失败。调用`std::cin.clear()`可以清除输入流的错误状态,使输入流恢复正常,从而可以继续读取输入
✅综上所述:
get()当遇到空行,则会把这个放入到目标变量里面去,但是可能会设置失效位,这个取决于具体情况
getline()当遇到空行就只有换行的时候,不会设置失效位,会放入到目标变量里面,然后再目标变量的后面进行输入
利用cin.clear()可以消除失效位
实际问题遇到空行
三 string简介
• 可以使用c-风格字符串来初始化string对象。
• 可以使用cin来将键盘输入存储到sting对象中。
• 可以使用 cout来显示string对象。
• 可以使用数组表示法来访问存储在string对象中的字符
• c++门也允许将列表初始化用于c-风格字符串和string对象
1 string的便捷
数组与string的区别
使用 string 类时,某些操作比使用数组时更简单。例如,不能将一个数组赋给另一个数组,但可以将 一个string 对象赋给另一个 string对象
2 string的便捷运算
string 类简化了字符串合并操作。可以使用运算符+将两个 string对象合并起来,还可以使用运算符+ = 将字符串附加到string对象的末尾string str3; str3 = str1 + str2; // assign str3 the joined strings str1 += str2; // add str2 to the end of str1
相比较于c语言里面string里面的函数操作,string类型显现的尤为简单,在计算长度的时候,应该是这样的
int len1 = str1.size() ; / / obtain length of str1 int len2 = strlen(charr1); // obtain length of charr1
上一个是string计算长度的方法
下一个是char数组计算长度的方法
3 string 类I/0
我们可以看到,这个第一个为22,不为20,我们由前面可知,cout读取字符串的时候C++ primer plus 第三节 数据处理-CSDN博客
可以知道,这个cout读取字符串的时候遇到\0才停止,这个strlen也一样这里两个getline怎么不一样呢?
getline在 C++ 中表现为函数重载。
在 C++ 中,函数重载是指在同一作用域内,可以有多个同名函数,但这些函数的参数列表必须不同。这种机制允许调用者根据传入的参数类型和数量来决定使用哪个函数版本。
对于 getline 函数,实际上有两个版本
1.非成员函数 std::getline(std::istream& is, std::string& str)
👉这个版本是全局函数,不是任何类的成员函数
👉它从输入流 is 中读取一行文本,直到遇到换行符 \n,然后将读取到的文本(不包括换行符)存储到 str中
👉这个函数可以处理任意长度的输入,因为std::string是一个动态数组,可以根据需要自动调整大小
2. 成员函数 std::istream::getline(char* s, streamsize n)
👉这个版本是输入流类 std::istream的成员函数
👉它从输入流中读取最多n-1个字符到字符数组 s中(留一个位置给字符串终止符 \0),直到遇到换行符或读取到n-1个字符
👉这个函数需要指定最大读取长度,以避免缓冲区溢出
综上所述:一个是用于char就是c语言里面的,一个是用于c++里面
string里面第一个参数是流 第二个是大小,但是string是一个动态数组,所以大小任意,知道遇到\n停止
c里面的char就是要输入在那个位置输入大小n-1,要留一个为\0
4 字符面量
早期的时候这个wchar是两个字节,但是这个unicode是需要4字节,还差两个,所以需要经过技术代理来扩增字节,由于繁琐,Linux等操作系统就希望有一个可以直接表示所有unicode马点的,然后就有了wchar_t来不需要代理,但是后面有了兼容问题,window是需要代理,然而Linux不需要,所以就有了char16_t和char32_t来解决这个兼容问题
四 结构体
结构体 位段 枚举 联合体基础知识点
C进阶 自定义类型-CSDN博客
C++里面结构体变量是可以省掉struct的
在C++引入了面向对象的概念是可以在里面写上函数,表示方法
顺便说一句,访问类成员函数(如 cin.getline()) 的方式是从访问结构成员变量(如`vincent.price`) 的方式衍生而来的共用体的应用
共用体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。例如, 假设管理一个小商品目录,其中有一些商品的ID为整数,而另一些的归为字符串
匿名共用体和共用体的区别
1 定义
匿名共用体:没有名称,通常作为结构体的成员定义。例如:struct MyStruct { int dataType; union { // 匿名共用体 int intValue; float floatValue; char stringValue[20]; }; };
正常共用体:有明确的名称,可以独立定义和使用。例如:
union NormalUnion { int intValue; float floatValue; char stringValue[20]; };
2 访问方式
匿名共用体:其成员可以直接通过外层结构体访问,无需通过共用体的名称 例如:MyStruct s; s.intValue = 10; // 直接访问匿名共用体的成员
正常共用体:需要通过共用体变量名访问其成员。例如:
union NormalUnion u; u.intValue = 10;
综上:定义:匿名共用体一般都是用在结构体里面做为成员
正常共用体可以独立使用使用:匿名共用体一般直接定义一个结构体变量,然后运用成员的方式就好了
正常共用体先要创建一个联合体变量然后才可以访问成员
枚举
枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型
在C语言和C++中,枚举类型(enum)本质上是一种整数类型,其枚举值(enumerator)默认是整数常量。虽然枚举类型通常用于表示一组命名的常量,但它底层仍然是整数类型
为什么不能直接用整数赋值给枚举变量?
虽然枚举值是整数,但枚举类型本身是一种强类型(在C++中更严格,C语言中稍弱)。这意味着枚举变量和整数变量之间存在类型区别。因此,直接用整数赋值给枚举变量是不允许的,除非进行显式类型转换
枚举是没有定义算术运算,所以不可以运用+ - / %
枚举类型的显示转换enum friute{ apple, bananan, oranger, }; apple = friute(10);
枚举类型的取值范围
默认情况
在大多数情况下,如果不显式指定枚举的底层类型,枚举值的取值范围将与int类型相同。这意味着:
- 在32位系统中,int通常是32位,取值范围是 -2,147,483,648 到 2,147,483,647
- 在64位系统中,int也可能是32位,但有些编译器可能默认使用64位的 int,这取决于编译器和平台
五 指针
基本知识:
c语言 指针 初学者-CSDN博客
C进阶 指针进阶_指针进阶内容-CSDN博客
1 使用new分配内存
在C语言中,可以用库函数malloc()来分配内存: 在C++中仍然可以这样做,但C++ 还有更好的方法——new运算符int * pn = new int;这样就会告诉编译器开辟一个int大小的地方
typeName * pointer_name = new typeName;内存被耗尽?
计算机可能会由于没有足够的内存而无法满足new的请求。 在这种情况下, new通常会引发异常一一 一种将在第 15 章讨论的错误处理技术;而在较老的实现中, new将返回0。 在C++中,值为 0的指针被称 为空指针(null pointer )0 C++确保空指针不会指向有效的数据,因此它常被用来表示运算符或函数失败(如 果成功,它们将返回一个有用的指针)。 将在第 6 章讨论的 if语句可帮助您处理这种问题;就目前而言, 您只需如下要点: C++提供了检测并处理内存分配失败的
2 运用delete释放内存int * ps = new int; ...... delete ps; ps = NULL; //不要编程野指针了
这将释放ps指向的内存,但不会删除指针ps本身。例如,可以将严重新指向另一个新分配的内存块。 一定要配对地使用 new和 delete; 否则将发生内存泄漏 Cmemory leak
3 使用 new来创建动态数组
如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否 使用数组,数组都在那里,它占用了内存。在编译时给数组分配内存被称为静态联编 Cstatic binding)但使用new时,如果在运行阶段需要数组,则创建它;如果不需要, 则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编 (dynamicbinding),意味着数组是在 程序运行时创建的。这种数组叫作动态数组
1.使用 new创建动态数纽
在C++中,创建动态数组很容易:只要将数组的元素类型和元素数目告诉new即可。必须在类型名后 加上方括号,其中包含元素数目。例如,要创建一个包含 10个int元素的数组,可以这样做:
`int * psome = new int [10]; // get a block of 10 ints`运用delete管理数组
`delete [ ] psome;此动态非动态
也就是大小是固定的,只不过可以用delete释放罢了,不可以对一个元素进行释放,而是整个数组
1. 数组的动态联编和静态联编 使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置
2. 使用new[]运算符创建数组时,将采用动态联编(动态数组),即将在运行时为数组分配空间,其长度 也将在运行时设置。使用完这种数组后,应使用delete[]释放其占用的内存new也可以处理结构
✅总之,使用new和delete 时,应遵守以下规则
• 不要使用 delete 来释放不是new分配的内存
• 不要使用 delete释放同一个内存块两次
• 如果使用 new[ ]为数组分配内存,则应使用 delete[ ]来释放。
• 如果使用new[ ]为一个实体分配内存,则应使用delete(没有方括号〉来释放
• 对空指针应用 delete是安全的
六 自动存储、静态存储和动态存储
根据用于分配内存的方法, C++有 3 种管理数据内存的方式:自动存储、静态存储和动态存储(有时也叫作自由存储空间或堆)。在存在时间的长短方面,以这3种方式分配的数据对象各不相同。 下面简要地 介绍每种类型 (C++11 j新增了第四种类型一一线程存储,这将在第9章简要地讨论)1 自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量 (automatic variable),这意味着它们 在所属的函数被调用时自动产生,在该函数结束时消亡。 实际上,自动变量是一个局部变量,其作用域为包含它的代码块。 代码块是被包含在花括号中的一段 代码。 到目前为止,我们使用的所有代码块都是整个函数。 然而, 在下一章将会看到,函数内也可以有代 码块。 如果在其中的某个代码块定义了一个变量,则该变量仅在程序执行该代码块中的代码时存在。 自动变量通常存储在校中。这意味着执行代码块时,其中的变量将依次加入到校中, 而在离开代码块 时,将按相反的顺序释放这些变量,这被称为后进先出 (`LlFO`)。因此, 在程序执行过程中,找将不断地 增大和缩小2 静态存储
静态存储是整个程序执行期间都存在的存储方式。 使变量成为静态的方式有两种: 一种是在函数外面 定义它:另一种是在声明变量时使用关键字static: static double fee = 56 .50; 在K&RC 中, 只能初始化静态数组和静态结构, 而C++Release 2.0 (及后续版本〉 和 `ANSIC` 中,也 可以初始化自动数组和自动结构。 然而, 一些您可能已经发现,有些C++实现还不支持对自动数组和自动 结构的初始化。 第9章将详细介绍静态存储。 自动存储和静态存储的关键在于:这些方法严格地限制了变量的寿命。 变量可能存在于程序的整个生命周期(静态变量),也可能只是在特定函数被执行时存在(自动变量)3 动态存储
new 和 delete 运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++ 中被称为自由存储空间他回悦。而)或堆 (heap)。该内存池同用于静态变量和自动变量的内存是分开的。程序 清单4.22 表明, new和 `delete` 让您能够在一个函数中分配内存,而在另一个函数中释放它。因此,数据的生命 周期不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对程序如何使用 内存有更大的控制权。然而, 内存管理也更复杂了。 在校中, 自动添加和删除机制使得占用的内存总是连续的, 但new和 delete 的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难✅综上所述:
自动存储一般都是栈的操作
静态存储一般都是常量区或只读数据段的操作
动态存储一般都是堆的操作
七 vector简介
模板类vector 类似于 strmg 类,也是一种动态数组。 您可以在运行阶段设置 vector对象的长度,可在 末尾附加新数据,还可在中间插入新数据。基本上,它是使用 new创建动态数组的替代品。实际上. vector 类确实使用 new和 delete 来管理内存,但这种工作是自动完成的
vector的语法算法 所需STL学习-CSDN博客
C++11 新增 array简介
vector 类的功能比数组强大,但付出的代价是效率稍低。 如果您需要的是长度固定的数组,使用数组 是更佳的选择,但代价是不那么方便和安全
array 对象的长度也是固定的, 也使用核(静态内存分配).而不是自由存储区,因此其 效率与数组相同,但更方便,更安全。要创建array对象,需要包含头文件array. array对象的创建语法与 vector 稍有不同:
为什么vector的效率相较于array的效率相对较低
std::vector相比原始数组(C-style array)在某些情况下效率较低,主要原因在于其动态特性和额外的功能支持。以下是具体原因:✅1 动态内存分配
std::vector是动态数组,其大小可以在运行时调整。当向vector中添加元素超出当前容量时,它会分配一个新的更大的内存块,将现有元素复制到新块中,然后释放旧块。这一过程涉及额外的内存分配和数据复制,增加了时间开销✅2 内存管理开销
std::vector需要维护额外的元数据,如当前大小(size)和容量(capacity),这会占用额外的内存。相比之下,原始数组的大小是固定的,没有动态管理的开销✅3 初始化和构造
`std::vector` 在构造时会初始化所有元素,而原始数组不会。如果不需要初始化,这会带来不必要的性能损耗。例如,对于复杂类型的元素,`vector` 会调用默认构造函数初始化所有元素,即使这些元素后续会被覆盖✅4 边界检查
std::vector提供了边界检查功能(如at()方法),虽然这增加了安全性,但也带来了额外的性能开销✅5 缓存性能
虽然std::vector和原始数组都存储在连续内存中,但在动态扩容时,vector可能会分配新的内存块,导致缓存失效✅缓存性能分析
缓存是CPU和主内存之间的一种高速存储器,用于存储计算机频繁访问的和最近访问过的数据,以减少计算机与主内存之间的访问次数,来提高计算机的速度
👉1. 缓存行:一般一个单元的缓存行以128个字节或者64个字节,当我们计算机访问网站的时候,不仅会加载网站,还会把网址放入到缓存行里面
👉 2. 连续内存和缓存的友好性:我们无论用容器还是数组,都是一段连续内存,这样会对缓存非常友好,因为连续的内存可以充分利用缓存行的位置
👉3. 缓存命中:当我们要遍历需要遍历一个数组或者容器,计算机在访问的过程中,CPU会将把当前的内存块加载到缓冲区
总结
总结
数组
数组里面的初始化和命名还有禁止压缩放入
字符串
字符串以\0结尾
我们学习了字符串的两个表现形式
sizeof和strlen分别用到字符串数组会返回什么
cin危机
get()函数的用处和重载函数的作用
getline()函数的用处和重载函数的作用
两者遇到空白字符串的时候怎么办,即换行符号
string
字符串的便捷相较于数组
字符串的运算
结构体
结构体struct可以省掉
匿名共用体和共用体的定义和访问方式
枚举和int的区别与可不可以赋值
枚举不可以进行运算,枚举的取值范围
指针
new的使用
delete的使用
实现动态数组
什么是静态联编什么是动态联编
自动存储,静态存储与动态存储
容器的使用
容器和数组和array的区别
为什么容器效率比数组慢
缓存的一系列概念
缓存行,缓存命中,缓存的友好性