《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++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

精选20个好玩又实用的的Python实战项目(有图文代码)

《精选20个好玩又实用的的Python实战项目(有图文代码)》文章介绍了20个实用Python项目,涵盖游戏开发、工具应用、图像处理、机器学习等,使用Tkinter、PIL、OpenCV、Kivy等库... 目录① 猜字游戏② 闹钟③ 骰子模拟器④ 二维码⑤ 语言检测⑥ 加密和解密⑦ URL缩短⑧ 音乐播放

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符