《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++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新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的