《c++ primer》第三章 字符串、vector、数组

news2024/11/16 22:29:20

前言

本章内容相比第二章要简单不少,里面比较重要的内容主要是vector和迭代器,这里只是很简单的介绍了一下,在后续的章节会有更详细、复杂的说明。以下记录的都是比较重要或者易混淆的知识点,对于像string、vector只列举了部分方法的例子。

文章目录

    • 一、string
      • 1.1string初始化和常见操作
      • 1.2string对象字符处理API
    • 二、标准库类型vector
      • 2.1vector常见操作
    • 三、初识迭代器
      • 3.1使用迭代器
      • 3.2迭代器运算
    • 四、数组
      • 4.1初始化
      • 4.2访问数组元素
      • 4.3指针和数组

一、string

string是C++的一个标准库类型,表示可变长的字符序列。string 定义在命名空间std中

1.1string初始化和常见操作

初始化

​ 下表是string的初始化方式。值得注意的是当我们使用=号实际上执行的是拷贝初始化,反之就是直接初始化。

image-20230112134356526

string对象操作

​ 下表是几种string的操作方式。

image-20230112134725104

​ 对于读写string对象,在使用cin读取一个对象时会自动忽略开头的空白(空格、换行符、制表符等),直到遇到下一个空白为止。如下,当我们输入一段字符串" hhhh yyyy ",程序自动忽略开头的几个空格并在遇到下一个空格后停止读入,所以最后的结果为hhhh在遇到字符串类型的算法题时,使用cin可以减轻处理字符串的工作

string s1;
cin >> s1; // 输入"   hhhh yyyy  ";
cout << s1; // 输出 "hhhh"

​ 如果我们想要保留输入的一整段字符串,可以使用getline来读取一整行数据。如下,getline需要两个参数,一个是输入流,另一个是string对象,在读取的过程中,会把整段数据包括换行符都读在缓冲区中,但是存储的时候会舍弃掉换行符,所以最后的结果并没有包含换行符,这也是为什么在输出时要加上endlendl可以结束当前行并刷新显示缓冲区

getline(cin,s1); //输入"   hhhh yyyy  ";
cout << s1 << endl; // 输出"   hhhh yyyy  ";

size()会返回当前字符串的一个大小,大部分和我一样的初学者都会想当然的以为返回的类型是一个int,但其实类型是string::size_typesize_type是一个无符号类型的值,能够存放任何string对象的大小。【代码风格:在循环访问一个字符串对象时,标识符的类型尽量定义成size_type,这样该标识不可能为负数从而减少越界等错误】。在上一章中学过auto和decltype,我们在接收一个字符串对象的长度时可以用它们来定义变量。

auto len = s1.size(); // len 的类型是size_type

​ 两个字符串比较首先看长度是否相等,如果长度不一样,不一定就是较长的字符串大,因为string对象比较遵循第一相异字符比较结果,也就是说如果第一个不同字符,较长字符串改字符的字典序小于较短字符串,那么最后的结果就是较长字符串小于较短字符串。如下。

string str = "Hello";
string str2 = "Hiya"; // str2 > str

​ 标准库允许把字面值或字符字面值转换为string对象,前提是‘+’两边至少包含一个string对象

string str = "123";
string str2 = str + "," + "456"; // 123456
string str3 = "123" + "456"; // 错误,+两边不存在string对象

1.2string对象字符处理API

​ 要处理一个字符那么首先要判断它的类型,在标准款中提供了一个cctype函数,这是C++兼容C语言的一个头文件,如下有一些判断字符类型的函数。

image-20230113101033414

​ 一般来说,我们都是从一个字符串中循环提取一个字符进行处理,C++提供了一个很方便的循环语句,范围for语句,包括在后面迭代器等章节都会经常使用,定义如下。

for(auto x : str); // 循环取出原串的一个字符,不改变原串
for(auto &x : str); // 通过引用的方式可以改变原串

​ 如果我们只需要处理一个字符串中的某个字符,可以使用下标运算符[index],里面接收的参数类型是size_type,保证大于等于0。我们也可以直接通过[index]改变相应位置上的值。

混用C风格字符串

C语言定义字符串是通过char *的方式,但是我们不能把一个string对象拿来初始化C语言风格的字符串。需要使用c_str转换,它返回的是一个指针,指向一个以空字符串结束的字符数组,这个数组存储的内容就是我们定义的string对象,返回的类型为const char *来保证不会改变字符数组的内容。

string str("asdasd");
char *str2 = str; // 错误
const char*str2 = str.c_str()

二、标准库类型vector

vector表示对象的集合,里面的对象类型都是一致,每个对象对应一个索引,用于访问该对象。vector是一个类模板,在后续的章节有专门一章来讲解模板,感觉比较难懂。除了类模板,还有一个就是函数模板,模板本身不是类或函数,编译器根据模板生成类型和函数的过程称为实例化

2.1vector常见操作

初始化

​ 定义和初始化的方法不同的API都差异不大。注意几点,当使用拷贝初始化=时,只能提供一个初始值;如果这个初始值是类内初始值(一个class定义的值),只能使用拷贝初始化或使用花括号的形式初始化;使用列表初始化是用{}

image-20230113104005448

​ 在定义一个vector时我们可能会给出一个大小n,此时程序会进行值初始化,如果 当前vector的类型是int,那就会初始化n个0。

​ 总的来说,()可以说是根据提供的值来构造对象,{}是根据提供的值来初始化对象,只有在无法初始化时才考虑其它方式。注意下面的表达式vector v4{10}

vector<int> v1(10); // 构造10个大小的整形容器
vector<int> v2{10}; // 容器含有一个元素10
vector<string> v3("hi"); //错误,不能使用字面值构建vector对象
vector<string> v4{10}; // 字面值10并不是string对象,所以不能拿来初始化,而是直接构造含有10个空串的容器

添加元素

push_back可以往一个容器的尾部添加一个元素。vectorC语言中最大的区别就是定义的时候可以不指定容量,vector可以根据当前存储的元素动态的改变容器的大小,是否要在定义时指定容量大小会在后续的章节中谈到,vector提供了一些方法允许我们提升动态添加元素的性能。

vector<int> vec;
vec.push_back(1);
cout << vec[0]; // 1 可以通过索引访问值
vec[1] = 3; // 错误,不能通过索引的添加值

​ 其它的操作如下表所示,都是比较简单的函数,这里就不一一举例了。记住v.size()返回的类型还是size_type【代码风格:如果总是忘记定义size_type去接收容器的大小,可以使用上一章学的auto/decltype自动获取】

image-20230113140316324

三、初识迭代器

​ 前面我们知道可以通过下标运算符来访问string对象或者vector对象的元素,使用迭代器也能达到此目的。除了vector外,其它的一些容器也提供了迭代器,但是部分缺不支持下标运算符,string对象虽然不是容器,但是也能使用迭代器。

3.1使用迭代器

begin和end是常用的两个迭代器成员,begin指向容器的第一个元素,end指向容器尾部后一位,这个位置不存在元素,有点类似链表最后的null,仅仅作为一个标志。对于一个空的容器,它的begin和end都返回同一个迭代器

​ 下面是常用的迭代器的运算符。*iter返回的一个元素的引用,如果要访问该元素下对应的成员还得使用iter->mem(或者(*iter).mem,()不能少)进行解引用。移动当前迭代器使用++或--

image-20230113142908949

迭代器类型

​ 迭代器也有不同的类型,定义一个迭代器vector<int>::iterator it,通过it可以去读写vector<int>的元素;常量迭代器vector<int>::const_iterator it2则只能进行读操作。如何确定迭代器的类型依据对象的类型,如果对象是一个常量,那么只能使用常量迭代器,反之都可以。同理,我们在上面使用beging和end返回的迭代器类型也是根据对象的类型。如果对于一个非常量容器,我们想得到一个常量迭代器,可以使用C++11引入的新特性cbegin和cend,直接返回一个常量迭代器。

​ 解引用迭代器可以获得迭代器所指的对象,如果对象的类型是一个类,我们可以通过iter->mem或者(*iter).mem访问类的成员函数。上面说过使用(*iter).mem方式时()不能少,如下说明。

vector<string> vec{"hhhhh"};
vector<string>::iterator it = vec.begin();
(*it).empty(); // false
*it.empty(); // it是一个迭代器,没有empty成员,所以错误

使用->相当于把解引用和访问成员融合在一起,更加的方便。

​ 前面提到,vector会动态的增长容器的大小,所以我们在使用迭代器遍历容器元素时不能向容器中添加元素,否则当前的迭代器会失效。总之当你定义了一个迭代器,任何将会改变原容器的操作都会使当前的迭代器失效。

3.2迭代器运算

​ 除了++和--让迭代器移动一步,我们还可以使用下表的方法进行多步移动。注意一下当两个迭代器相减时,返回的是这两个迭代器的距离,返回的类型是difference_type(带符号整形数)。很明显这个距离可正可负。

image-20230113152346127

四、数组

​ 数组也是一个能存放不同对象的容器,与vector不同,数组的大小一旦确定就不能改变,不能随意向数组中增加元素,在某些情况使用数组可以增加程序的运行性能,但是灵活性也大大的降低。

4.1初始化

​ 数组是一种符合类型,声明一个数组如a[d],其中a是数组的名字,d是数组的维度,维度是数组元素的个数,它必须要是一个常量表达式

unsigned cnt = 12; //非常量表达式
int arr[cnt]; // 错误
constexpr unsigned cnt1 = 12;
int arr[cnt2]; // 定义一个含有12个元素的整形数组

和内置类型的变量一样,在函数外部定义了一个数组将会根据类型进行默认初始化,在内部则会是未定义。在定义数组的时候必须要指定数组的类型,不能使用auto去根据初始值列表推断类型

不允许拷贝和赋值

​ 我们不能用一个数组去初始化另外一个数组,虽然一些编译器可能支持这个操作,但是为了程序的兼容尽量根据标准来书写代码。但是我们却可以使用数组来初始化一个vector(begin(arr), end(arr)在4.3中有讲解)

int arr[] = {1,2,3,4,5,6,67};
vector<int> vec(begin(arr), end(arr)); // 含有arr的全部元素
vector<int> vec2(arr + 2, arr + 4); // {3,4}

​ 数组本身是一个对象,所以允许定义数组的指针及数组的引用,但是不存在引用的数组。有点绕,直接看书上的例子容易理解一点。

int arr[10];
int *prr[10]; // prr是含有10个指针的数组
int &rrr[10] = ??; // 错误,不存在引用的数组
int (*Prr)[10] = &arr; // Prr指向一个含有10个整数的数组的指针
int (&Rrr)[10] = arr; // Rrr是对数组arr的引用

4.2访问数组元素

​ 与vector、string相同,数组的元素可以使用范围for循环或者下标运算符来访问。在使用下标运算符时,返回的类型是size_t,与size_type相同也是一个无符号类型。

4.3指针和数组

​ 一般来说,我们对数组名直接取地址编译器会自动的将其替换成数组首元素的地址。所以我们使用auto自动根据数组名判断时,得到的是一个指针,但是使用decltype会返回数组的类型,如下。

int arr[10];
int *p = &arr; // 等于 int *p = &arr[0];
auto arr2(arr); // arr2是一个指针
decltype(arr) arr3; // arr3是一个含有10个元素的整形数组

​ 数组虽然没有迭代器,但是数组的指针也可以看成迭代器,迭代器能完成的操作使用指针也能完成。迭代器可以通过begin和end获得容器的开头和尾后元素,虽然数组不是类类型,没有成员函数,但是我们也可以将数组作为参数传入进去实现同样的效果。

int arr[] = {1,2,3,4,5,56,6};
int *beg = begin(arr); // 指向arr第一个元素的指针
int *last = end(arr);  // 指向arr尾后元素的指针

​ 指针的运算与上面迭代器的运算基本一致,两个指针相减返回的类型为ptrdiff_t

下标和指针

​ 使用指针的同时也能使用下标访问当前指针操作后指向的元素(当然指向合理的范围)。从p2[-1]可以看出指针和标准库类型vector等使用下标运算符的区别,内置类型的下标运算符可以是一个有符号数

int arr[] = {1,2,3,4,5,56,6};
int i = arr[2]; // 3
int *p = &arr; // p指向第一个元素1
cout << p[2]; // 输出3,相当于p+2
int *p2 = &i;
cout << p2[-1]; // 输出2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Sentienl一:下载,启动

Hystrix &#xff1a;1需要自己搭建监控平台 2 没有一套web界面可以给我们进行更加细粒度化的配置流控&#xff0c;速率控制 服务熔断&#xff0c;服务降级 Sentinel: 1 单独一个组件&#xff0c;可以独立出来 2 直接界面化的细粒度统一配置 一&#xff1a;丰富的应用场景&…

【Linux】分布式版本控制工具Git的学习 | 在Linux上使用git

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;安度因的学习社区 &#x1f4d6;专栏链接&#xff1a;Linux 文章目录一、前言二、历史背景三、版本控制1、何为版本控制2、版本控制工具① 集中式版本控制工具② 分布式版本控制工具四、代码托管平…

React 学习笔记总结(八)

react-router6版本的学习笔记。 文章目录一、React Router 6二、router6版本的 安装 和 一级路由1. 安装router6版本2. Routes组件 和 Route的 element属性三、router6 之 重定向四、router6的 NavLink高亮五、router6 的 useRoutes路由表(重要)六、router6 的 嵌套路由七、rou…

编写自己的OPTEE CA/TA demo

前言 &#xff08;默认你对optee有一点点点点了解&#xff09; 一、hello_world分析 在\optee_examples\hello_world目录下&#xff0c;optee给出了一个简单的CA/TA示例。 hello_world的结构如下&#xff1a; 1、CA端 A.main.c main.c文件有效代码如下&#xff1a; #incl…

vue2组件之间的数据传递(组件之间使用mitt第三方模块创建事件中心进行订阅与发布)

目录 一、组件之间的通信 1、组件之间的关系&#xff1a;父子关系、兄弟关系、跨级关系 2、父子组件之间的通信(数据传递)&#xff1a; 3、兄弟组件之间的通信&#xff08;数据传输&#xff09;&#xff1a; 4、跨级组件之间的通信&#xff1a;provide / inject 类似于消息…

正则表达式 - 匹配开头、结尾、中间 - 某天气网站网页源代码分析

背景 爬取某天气网站数据&#xff0c;使用 Selenium 能够得到渲染数据后的页面源代码。特定日期的真实数据肯定只有1份&#xff0c;展示在页面表格中&#xff0c;但是源代码中提供了3个都有数据的 Table&#xff0c;而其中2个Table 的数据是通过 math.random 生成后填充&#…

ASP.NET Core 3.1系列(25)——Autofac中的泛型注册和程序集注册

1、前言 在实际开发业务中&#xff0c;泛型的应用非常广泛&#xff0c;而这也就产生了一个问题&#xff1a;泛型类和泛型接口该怎么注册&#xff1f;难道要开发者一行一行去写泛型构造参数吗&#xff1f;同时&#xff0c;实际业务中往往也会对项目进行分层设计&#xff0c;例如…

【十】Netty WebSocket协议栈开发

Netty WebSocket协议栈开发背景介绍HTTP 协议的弊端WebSocket 介绍WebSocket 特点WebSocket 连接建立Socket生命周期WebSocket关闭开发WebSocket 服务端功能介绍流程图代码实现jar 依赖WebSocket 服务端启动类 WebSocketServer服务端业务处理类 WebSocketServerHandlerWebSocke…

nacos的部署以及nacos启动报错“Unable to start embedded Tomcat”(部分解决)

这几天有一个基于yshop改编的SpringBoot的项目开发需求&#xff0c;本地需要下载使用nacos-server进行部署和开发&#xff0c;于是下载了nacos-server-2.0.3,并将其解压在一个没有中文路径的地方。 接下来根据nacos需求&#xff0c;设置了JAVA_HOME的环境变量&#xff1a; 修…

防火墙NAT综合实验

实验要求 1.内网网段配置动态pat将内网192.168.1.0网段映射到防火墙外网接口上 2.dmz区域服务器做静态pat将两台服务器对应到一个外网接口的不同端口 3.开启nat 控制 4.对内网网段192.168.2.0或豁免能够发访问外网 5.并通过远程进行验证 实验命令 ciscoasa# conf t cisco…

如何快速做好SEO优化?怎样综合查询seo?

本篇接着讲新手优化网站的技巧及应该注意什么方面&#xff0c;一起来看看吧&#xff01; 6.优化你的图片 从可读性的角度来看&#xff0c;图像非常重要。他们帮助可视化你的内容&#xff0c;帮助读者更容易理解。但它们对SEO也很重要&#xff0c;因为它们可以帮助你的网站被抓…

MySQL——SQL逻辑语句相同但是性能相差巨大?

在 MySQL 中&#xff0c;有很多看上去逻辑相同&#xff0c;但性能却差异巨大的 SQL 语句。对这些语句使用不当的话&#xff0c;就会不经意间导致整个数据库的压力变大。 下面通过三个案例对SQL语句进行分析&#xff1a; 案例一&#xff1a;条件字段函数操作 假设你现在维护了…

51单片机特性概览

51单片机指的是使用英特尔8051指令集的微控制器。 首先要了解什么是微控制器。 一、什么是微控制器&#xff1f; 微控制器包括&#xff1a; CPU其他组件(可能包括RAM,ROM,I/O端口&#xff0c;定时器、计数器、通信端口)&#xff0c; 而微处理器只包含CPU。 一开始只有微处…

适应性学习率

目录 适应性学习率 Adaptive learning rate 为什么不是临界点仍会导致训练停止 示例一示例二 RMSRMSPropAdam学习率还和时间有关 Learin Rate DecayWarm up 2021 - 类神经网络训练不起来怎么办(三) 自动调整学习率 (Learning Rate)适应性学习率 Adaptive learning rate 一般…

团队协作软件如何整合内容营销

每个内容营销团队都以不同的方式管理他们的流程和工作流程——无论是为成长中的团队扩展编辑流程&#xff0c;还是视频和社交媒体活动规划。优化内容营销项目管理就是降低复杂性和更有效地管理工作。 但是&#xff0c;为什么正确地做到这一点如此重要呢&#xff1f;与对外营…

第一个uni-app程序小结

工院喵开发小结 这是一篇关于uniapp新手写微信小程序的收获和踩坑总结。 目录工院喵开发小结一、架构二、收获1. 一些扩展组件的使用a. uni-uib. uni-listc. 栅格系统d. uni.scss辅助样式e. uni-easyinput 增强输入框f. uni-fab悬浮按钮g. swiper2. api管理3. 页面跳转传参4. …

iOS 语言基础初探 Xcode 工具

前言&#xff1a; 作为 iOS 开发的主要应用工具之一&#xff0c;Xcode 已经越来越被业内认可&#xff0c;本章节将针对此官方开发工具&#xff0c;为同学解读 Xcode 的基本情况&#xff0c;认识 Xcode 的工程体系&#xff0c;带领大家进入 iOS 开发第一步。 &#x1f3b6;文章目…

SAP入门技术分享四:模块化程序

模块化程序1.子程序概要2.子程序定义3.子程序参数&#xff08;1&#xff09;传递参数的方法&#xff08;2&#xff09;定义参数类型&#xff08;3&#xff09;参数与结构体&#xff08;4&#xff09;参数与内表4.调用子程序&#xff08;1&#xff09;调用程序内部子程序&#x…

vue npm link关联本地组件库

什么是 npm link 就是把你在本地开发好的文件做一个映射和链接&#xff0c;当你在 本地开发一个a项目&#xff0c;你的本地b项目想使用a项目下的组件 这时候就是需要进行npm link链接起来 a项目的运行效果 b项目的运行效果&#xff1a; 想要实现的效果&#xff1a;(在b项目上…

Oracle SQL Developer使用dbms_output.put_line显示输出

dbms输出 点击DBMS输出左侧的号&#xff0c;选择需要输出的数据库&#xff0c;点击确定 与步骤2选择相同数据库&#xff0c;右击数据库&#xff0c;选择打开SQL工作表(T) 在工作表中执行语句 declare --定义&#xff0c;相当于声明属性。t_a varchar2(20);--声明自定义属…