《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++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(