《Beginning C++20 From Novice to Professional》第九章 Vocabulary Types

2024-05-06 05:12

本文主要是介绍《Beginning C++20 From Novice to Professional》第九章 Vocabulary Types,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

有一些类型虽然不是基本类型,但是和基本类型一样常用,都是用来替代相同功能的C版本特性的,比如std::unique_ptr<>, std::shared_ptr<>, std::string, std::array<>, std::vector<>,分别用来替代raw pointers, const char* strings, low-level dynamic memory

本章讲的主要也是一些小特性,不过不是用来替代什么类型,是为了增加代码可读性、提高编码效率设计的

其实还有两个,variant用来替代联合体union,any用来替代void*指针,但是这两者使用频率较低,用到的时候再学也可以

Working with Optional Values

看这个函数原型,我们会猜测这个函数是用来在字符串中寻找某字符出现的最后位置的函数,但是如果我们没有在s中找到需要的字符呢?或者我们不需要起始位置想要搜索整个字符串s呢?

有一种方案是我们可以规定一些特殊数字用来返回没有结果的情况,比如数组和字符串经常用-1表示没有找到某元素,-1用来表示搜索整个字符串或范围等等;还有的只要是负数都可以

实际上string也正是这么做的,标准库的find方法返回的就是std::string::npos这个特殊数字

但是我们不可能去记住所有用来代表特殊参数或特殊输出的标记,有的用0有的用负数或者其他,所以一般像start_index这种可选参数,我们都会给一个默认值

回顾之前看到的find方法,起始位置都是有一个默认参数0表示搜索的范围是整个字符串

但是这个方法不适用于返回值,而且有的类型也不像int这种比较好默认处理

像这个想要读取某些配置文件的函数

我们只看原型是无法假设当配置没有读取到的返回值是0?-1?还是其他数字,所以这种情况下一般处理方法有两种

第一种如果没有找到就返回default;第二种没有找到就返回false,找到了通过output输出结果

虽然这些方法也可行,但是C++提供了一个新的类型来处理类似情景:optional<>

虽然这里也可以使用,但是在类里面定义相关类型应该更能体现这个类型的作用

std::optional

C++17引入了这个类型,用来表示可选的输入输出值,使用optional之后函数原型就变成这样了:

这样更简洁,一目了然

optional<>是个类模板,和vector、array一样,下面通过一个例子看看这个类型如何使用:

# include <optional>; // std::optional<> is defined in the <optional> module
# include <iostream>;
# include <string>;std::optional<size_t> find_last(const std::string& string, char to_find,std::optional<size_t> start_index = std::nullopt); // or: ... start_index = {});
int main() {const auto string = "Growing old is mandatory; growing up is optional.";const std::optional<size_t> found_a{find_last(string, 'a')};if (found_a)std::cout << "Found the last a at index " << *found_a << std::endl;const auto found_b{find_last(string, 'b')};if (found_b.has_value())std::cout << "Found the last b at index " << found_b.value() << std::endl;// following line gives an error (cannot convert std::optional<size_t> to size_t)// const size_t found_c{ find_last(string, 'c') };const auto found_early_i{find_last(string, 'i', 10)};if (found_early_i != std::nullopt)std::cout << "Found an early i at index " << *found_early_i << std::endl;
}std::optional<size_t> find_last(const std::string& string, char to_find,std::optional<size_t> start_index) {// code below will not work for empty stringsif (string.empty())return std::nullopt; // or: 'return std::optional<size_t>{};'// or: 'return {};'// determine the starting index for the loop that follows:size_t index = start_index.value_or(string.size() - 1);while (true) // never use while (index >= 0) here, as size_t is always >= 0!{if (string[index] == to_find)return index;if (index == 0)return std::nullopt;--index;}
}

因为我们的函数使用了无符号数作为索引,所以第三个参数的默认值我们不能假设是-1或者其他数了,那么标准库定义了std::nullopt这个值来作为optional的默认值,表示没有初始化的状态

注意代码中函数的第三次调用,我们传入了一个整数10,那么函数会将这个参数转换为optional类型

使用这个类型的两个关键问题是,如何确定里面是否有值而不是nullopt;如何获得其中的有效值

对于第一个问题我们可以在代码中看到对应的解决方法:

  1. 如果有值optional对象可以转换为true
  2. has_value()返回值为bool,如果为true说明有值
  3. 和nullopt相比,不相等说明有值

第二个问题我们也已经看到输出,要么解引用获取值,要么调用value()成员函数

我们再来细想一下这个类的使用逻辑,如果有值就可以取用,如果没值就和nullopt相等,那这样我们可以使用另外一个非常好用的函数value_or,可以看函数内部的下标赋值就是这么做的,这个函数可以合二为一,如果没有值我们就从字符串最后位置开始查找,如果有值就用opt里的值给start_index赋值

同样,本书也只是介绍一部分,剩下的可以去看ref学习,值得一提的是,opt不仅支持解引用还支持箭头运算符->,所以如果是复杂类型我们也可以这样写:

虽然这样让opt看着像指针,但是opt不是指针,这个类会把给的值复制一份存在对象里而不是堆上,总体行为是值传递而不是指针的间接寻址

String Views: The New Reference-to-const-string

我们学引用的时候就知道,复制一个大对象成本很高最好使用引用;const引用看起来很完美但还是有不足,并不能完全避免复制行为,来看下面的代码:

我们传入一个C串但是参数类型是const string&的时候,其实还是发生了复制行为,因为我们需要用一个C串构造出临时string,然后将引用绑定到这个临时string上

有没有什么办法能够不复制作为输入参数的字符串,然后还能使用string提供的这么多好用的方法呢?

std::string_view provides read-only access to an existing string (a C-style string, a std::string, or another std::string_view) without making a copy.

5.10 — Introduction to std::string_view – Learn C++ 

顾名思义,string_view是字符串的视图,对字符串只读不能进行修改,因此维护成本也很低,内部只存储一个指针和长度

也就是说string_view相当于const string

Using String View Function Parameters

把之前的const string&改成string_view是更好的做法,尤其是在输入参数这里,string_view可以接收三类字符串参数,只要我们不修改字符串

注意两点,string_view不提供c_str()函数但是仍提供data();此外string_view由于不可修改因此不支持连接操作


后面一节没怎么看懂。。。

Spans: The New Reference-to-vector or -array

考虑下面的情景:

之前说过这种逻辑类似的函数我们可以使用下一章介绍的函数模板,但是目前这两个函数都是针对一个数组,一个顺序容器进行的操作,C++20引入了一个新的类型叫span<>,在<span>头文件里

使用span之后我们不用再重载了,而且可以使用下标[]以及range-for,而且span传参是非常高效的,和string_view类似,一般内部也就只存储一个指针和长度

std::span - cppreference.com​​​​​​

除了vector、string、array都支持的一些操作外,span还有一些自己的方法

这样写的话处理函数基本没有需要更改的了,我们已经实现了对相同类型的元素集合这种参数的重载,只是调用的时候需要我们注意一下:

考虑一般的数组,我们可以使用第四个版本的构造函数,直接使用数组引用构造span;如果是一个临时list,那么第八个版本会适用,array有第五和六个版本,其他类型也可以很方便的构造span:

可以看到array直接传进去,span就可以构造出来

如果我们传入的类型长度不能推导出来比如是个起始位置的指针,那么我们是需要第二个参数构造span的:

Spans vs. Views

span比view功能多的地方在于,span和view虽然都不直接存储序列的元素,但是span允许对序列进行修改,而view不允许

front表示首元素,可以直接赋值修改;也支持下标修改元素

这两个操作加上back三个方法都返回的是元素引用,所以可以修改元素;而string_view的下标返回的是const引用所以对元素是只读的

但是span和view一样,不允许我们添加或删除序列元素,也就是不支持push_back等操作

Spans of const Elements

span允许我们修改元素,而const对象不允许修改,因此任何const序列都不能构造span,否则逻辑矛盾,比如下面的代码就不能通过编译:

这也导致一个问题,那些使用span参数的函数无法传入一个const序列

std::string_view is thus most similar to a std::span<const char>.

所以我们需要修改当时的那个查找序列最大数字的函数原型:

把span的元素类型改为const即可解决这个问题

Use span<const T> instead of const vector<T>&. Similarly, use span<T> instead of vector<T>&, unless you need to insert or remove elements.

Fixed-Size Spans

span也可以指定第二个参数表示定长序列

Use span<T,N> instead of array<T,N>& or T(&)[N], and span<const T,N> instead of
const array<T,N>& or const T(&)[N].

但是我没搞懂对于标准库array来说为什么要指定长度,可能有些函数就是处理定长序列才设计的,上面这个求所有数平均值的好像没有必要写这个长度

写成这样照样可以出结果

这篇关于《Beginning C++20 From Novice to Professional》第九章 Vocabulary Types的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/963501

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给