软文推广营销平台,wordpress主题 SEO优化,网站如何做微信支付宝支付,东莞大岭山镇邮政编码可以说#xff0c;C由两部分组成#xff1a;语言本身和标准库。第一部分#xff0c;即语言本身#xff0c;侧重于富有表现力的代码和严谨的语法。第二部分则为你提供工具、实用程序和算法。例如#xff0c;在C11中#xff0c;引入的lambda表达式简化了短函数对象的编写。… 可以说C由两部分组成语言本身和标准库。第一部分即语言本身侧重于富有表现力的代码和严谨的语法。第二部分则为你提供工具、实用程序和算法。例如在C11中引入的lambda表达式简化了短函数对象的编写。C14允许函数返回类型进行‘auto’类型推导这也缩短了代码长度并简化了模板代码。 C17作为C标准的一次重大更新带来了许多令人惊叹的语言特性总体上使该语言更加清晰、简洁。例如借助if constexpr你可以减少对enable_if和标签分派技术的依赖。由于结构化绑定你可以将元组tuples当作一等语言类型来使用你还能依赖并理解表达式求值顺序编写自然利用复制省略机制的代码等等
1、std::optional C17添加了一些包装类型让编写更具表现力的代码成为可能。std::optional它用于表示可空类型。借助这个工具我们的对象可以轻松表明它们没有任何值。这种行为比使用一些特殊值比如-1NULL来实现要直观的多。 如何标记一个类型不包含任何值呢 一个方法是通过使用特殊值-1无穷大nullptr来实现“可空性”。在使用前需要将对象与预定义值进行比较以查看它是否为空。这种模式在编程中很常见。例如string::find返回一个表示位置的值当找不到模式时返回npos这里npos相当于空值。 另外还可以尝试使用std::unique_ptrType并将空指针视为未初始化。这种方法可行但需要为对象分配内存并不是推荐的做法。 另一种技术是构建一个包装器为其他类型添加一个布尔标志。这样的包装器可以快速判断对象的状态。简而言之std::optional就是这样工作的。 使用场景 通常可以在以下场景中使用可选包装器 如果你想表示一个可空类型。 而不是使用特殊值如-1、nullptr、NO_VALUE等。例如用户的中间名是可选的。你可能认为空字符串在这里可行但了解用户是否输入了内容可能很重要。std::optionalstd::string可以提供更多信息。 返回某些计算处理的结果当计算未能产生值且并非错误时。 例如在字典中查找元素如果某个键下没有元素这不是错误但我们需要处理这种情况。 实现资源的延迟加载。 例如如果资源类型的构造开销很大或者没有默认构造函数你可以将其定义为std::optionalResource。以这种形式你可以在系统中传递它然后在应用程序首次访问时对其进行初始化加载资源。 向函数传递可选参数。 简单示例
// UI类...
std::optionalstd::string UI::FindUserNick() {if (IsNickAvailable())return mStrNickName; // 返回一个字符串return std::nullopt; // 等同于return { };
}// 使用:
std::optionalstd::string UserNick UI-FindUserNick();
if (UserNick)Show(*UserNick); 在上述代码中我们定义了一个函数它返回一个包含字符串的optional。如果用户的昵称可用它将返回该字符串否则返回nullopt。之后我们可以将其赋值给一个optional并通过将其转换为bool来检查它是否包含值。optional定义了operator*因此我们可以轻松访问存储的值。 std::optional的创建 创建std::optional有几种方式 初始化为空直接用值初始化使用推导指南用值初始化使用make_optional使用std::in_place从其他optional创建。 // 空初始化:
std::optionalint oEmpty;
std::optionalfloat oFloat std::nullopt;// 直接初始化:
std::optionalint oInt(10);
std::optional oIntDeduced(10); // 推导指南// make_optional
auto oDouble std::make_optional(3.0);
auto oComplex std::make_optionalstd::complexdouble(3.0, 4.0);// in_place
std::optionalstd::complexdouble o7{std::in_place, 3.0, 4.0};
// 用{1, 2, 3}直接初始化vector
std::optionalstd::vectorint oVec(std::in_place, {1, 2, 3});// 从其他optional复制:
auto oIntCopy oInt;
std::optional是一种包装类型因此我们几乎可以用与创建被包装对象相同的方式来创建optioal对象。
返回std::optional 如果我们从一个函数返回一个optional那么直接返回std::nullopt或计算出的值会非常方便。
// Optional/optional_return_rvo.cpp
std::optionalstd::string TryParse(Input input) {if (input.valid())return input.asString();return std::nullopt;
}
// 使用:
auto oStr TryParse(Input{...});我们可以看到函数返回从input.asString()计算得到的std::string并将其包装在optional中。如果值不可用则返回std::nullopt。
注意返回值时使用大括号要小心。
std::optionalstd::string CreateString() {std::string str {Hello Super Awesome Long String};return {str}; // 这会导致拷贝// return str; // 这会进行移动操作
}根据标准如果将返回值用大括号{}括起来就会阻止移动操作发生返回的对象只会被拷贝。这与不可拷贝类型的情况类似
std::unique_ptrint foo() {std::unique_ptrint p;return {p}; // 尝试拷贝unique_ptr编译会失败// return p; // 这会进行移动操作所以对于unique_ptr来说没问题
}std::optional的操作
修改值和对象的生命周期 如果我们已经有一个std::optional对象。可以通过emplace、reset、swap、assign等操作快速修改其中包含的值。如果用nullopt进行赋值或重置并且std::optional中包含一个值那么该值的析构函数将会调用。
#include optional
#include iostream
#include stringclass UserName {
public:explicit UserName(std::string str) : mName(std::move(str)) {std::cout UserName::UserName( mName )\n ;}~UserName() {std::cout UserName::~UserName( mName )\n ;}
private:std::string mName;
};int main() {std::optionalUserName oEmpty;// emplace:oEmpty.emplace(Steve);// 调用~Steve并创建新的Mark:oEmpty.emplace(Mark);// 重置使其再次为空oEmpty.reset(); // 调用~Mark // 等同于://oEmpty std::nullopt;// 赋值一个新值:oEmpty.emplace(Fred);oEmpty UserName(Joe);
}比较操作 std::optional允许我们几乎“自然的”比较其中包含的对象但当操作数中有nullopt时会有一些特殊情况。如下所示
#include optional
#include iostreamint main() {std::optionalint oEmpty;std::optionalint oTwo(2); std::optionalint oTen(10);std::cout std::boolalpha;std::cout (oTen oTwo) \n ;std::cout (oTen oTwo) \n ;std::cout (oEmpty oTwo) \n ;std::cout (oEmpty std::nullopt) \n ;std::cout (oTen 10) \n ;
}/*
true // (oTen oTwo)
false // (oTen oTwo)
true // (oEmpty oTwo)
true // (oEmpty std::nullopt)
true // (oTen 10)
*/ 当操作数都包含值且类型相同时你会得到预期的结果。担当一个操作数是nullopt时它总是比任何包含值的std::optional“小”。
性能和内存考量 使用std::optional时会增加内存占用。std::optional类包装了你的类型为其准备空间然后添加了一个布尔型的参数。这意味着它会根据对其规则扩展你原来类型的大小。从概念上讲标准库中std::optional的实现可能类似这样
template typename T
class optional {bool _initialized;std::aligned_storage_tsizeof(t), alignof(T) _storage;
public: // 操作
};std::optaional的对其规则定义如下 所包含的值应分配在std::optional存储区中适合类型T对齐的区域内。 例如假设sizeof(double) 8 且 sizeof(int) 4:
std::optionaldouble od; // sizeof 16字节
std::optionalint oi; // sizeof 8字节虽然bool类型通常只占用一个字节但std::optional类型需要遵循对齐规则所以它的大小大于sizeof(YourType) 1字节。
比如有两个类型
class Range {std::optionaldouble mMin;std::optionaldouble mMax;
};class RangeCustom {bool mMinAvailable;bool mMaxAvailable;double mMin;double mMax;
};Range占用的空间比RangeCustom更多。在第一种情况下Range占用32个字节第二种情况是24个字节。这是因为第二个类可以将布尔类型变量压缩在结构体的开头而Range中的两个std::optional对象必须对其到double类型的边界。