C++17之std::optional <optional> tcy

 dd 

1.1.用途:     
 1)optional是一个可空nullopt值类型,它要么含值要么不含值;不需要分配内存 
 2)模拟类似指针语义,指针是可空nullptr引用类型;必须分配内存或指向一个值         
 3)optional可用作变量函数参数返回值及类的成员变量
 
1.2.说明:
 1)std::nullopt_t  nullopt常量用于指示 optional 类型有未初始化状态     
 2)std::optional对象的哈希值是包含的非常量类型(如果有的话)的哈希值 
    3)特殊构造函数允许将参数直接传递给所包含的类型 
    4)optional的参数类型为基本类型类optional类型,但要特别注意bool,指针,
          optional类型的一些特殊用法 
     
1.3.移动语义
      optional支持移动语义。
      如将对象作为一整体移动,则会复制状态并移动所包含的对象(如果有的话)
      因此一个从对象中移出的对象仍具有相同状态,但移动后值会变成未指定的可以重新赋值。
  
1.4.比较操作     
  1)类重载bool因此你能用optional<int> opt; if(opt)判断是否含值。   
  2)可用通常比较操作符。操作数是optional对象、包含类型对象和std::nullopt     
       a)如两操作数都具有值对象则用所包含类型的对应操作符   
       b)如两操作数都是没值对象则认为是相等(所有其他比较false)   
       c)如只有一操作数是有值对象则没有值操作数被认为小于另一个操作数   
1.5.注意:
     特定可选值类型可能导致特殊或意外的行为bool或者原始指针的optional
     使用比较操作符与使用optional对象作为布尔值具有不同的语义
     如果包含的类型是bool或指针类型,这就会变得很混乱 
2.1.Classes     
 optional                             可能包含或可能不包含对象的包装器
 bad_optional_access                  optional无值访问抛出的异常;派生自std:: Exception  
 std::hash<std::optional>             hash算法    
 nullopt_t                            具有未初始化状态的可选类型的指示器 
 nullopt_t nullopt                    常数-没有值的对象  
2.2.std::optional成员     
 constructors                     创建optional对象(可能调用包含类型的构造函数)    
 make_optional<>()                创建optional对象    
     
 emplace()                        为所包含的类型分配一个新值    
 reset()                          销毁任何值(使对象为空)    
 has_value()                      返回对象是否具有值    
     
 *                                访问值(如没值未定义行为)    
 ->                               访问值的成员(如没值未定义行为)    
     
 value()                          访问值(如没值抛出异常)    
 value_or()                       访问值(如没值则使用提供的值)    
 swap()                           交换两个对象值    
 hash<>                           函数对象类型来计算哈希值    
     
    Comparison                    比较方式   
 ==,!=,<,<=,>,>=                  比较两个optional对象   

3.实例: 

 #include <iostream>
 #include<complex>
 #include<optional>
 #include<string>
 #include<set>
 #include<map>
 #include<cassert>

 using namespace std;

 //3.1.构造函数测试:
 void test_constructors() {
  //实例1.1:
  std::optional<int> o1;                                                 //创建无值对象-必须指定类型
  std::optional<int> o2(std::nullopt);                             //这里不调用任何构造函数

  //传递字面值来初始化所包含类型-不必指定类型
  std::optional o3{ 42 };
  std::optional o4{ "hello" };                                           // optional<const char*>
  optional<std::string> o5{ o4 };                                    //复制对象(包括类型转换)

  //in_place_t in_place来初始化带有多个参数的可选对象的值<utility>
  optional o6{ std::complex{3.0, 4.0} };                         //创建该对象
  optional<complex<double>> o7{ in_place, 3.0, 4.0 };//添加为首参数(类型无法推断)

  //注意:第二种形式避免创建临时对象

  //实例1.2:传递初始化器列表和附加参数:
  auto sc = [](int x, int y) {   return abs(x) < abs(y); };
  std::optional<set<int, decltype(sc)>> o8{ in_place, {4, 8, -7, -2, 0, 5}, sc };

  //实例2:make_optional<>()允许用单或多个参数初始化(不需in_place)
  auto op0 = make_optional(3.0);                                  // optional<double>
  auto op1 = make_optional("hello");                             // optional<const char*>
  auto op2 = make_optional<std::complex<double>>(3.0, 4.0);

  //实例3:
  //无构造函数接受一值并据此决定是用值或nullopt初始化,可用操作符?:
  map<string, string> m;
  auto pos = m.find("Tom");
  auto op3 = pos != m.end() ? std::optional{ pos->second } : std::nullopt;//optional<std::string>

  //类模板参数推导optional(pos->second)对nullopt类模板参数推导不起作用
  //但是运算符 ? : 在推导表达式的结果类型时也将其转换为这种类型
 }

//3.2.optional基本测试:

 void test_operator() {
      optional o{ 1 };
      optional o1{ std::pair{"Tom",20} };

      assert(o.has_value() == true);              //检查对象是否有值
      if (o1) cout << "exists value" << endl;

      assert(*o == 1);                                     //访问值
      assert(o.value() == 1);                          //无值抛出bad_optional_access
      o.reset(); assert(o.value_or(-1) == -1); //如无值则返回-1;回退参数作为rvalue引用传递

      assert(o1->first == string("Tom") && (*o1).second == 20);

      //修改值
      o = std::nullopt; if (o) *o = 21;                 //需要修改一个已存在的值
      o.reset(); o.emplace(22);
      o = {}; o = make_optional(23);

      optional<std::complex<double>> o2;
      optional ox{ 10 };                                     // optional<int> with value 10
      o2 = 11;                                                   // value=complex(11.0, 0.0)
      o2 = { 9.9, 4.4 };                                      // value=complex(9.9, 4.4)
      o2 = ox;                                                   // int converts to complex<double>

      o2 = nullopt;                                            //删除该值效果同reset()或赋值为空花括号
      o2.emplace(5.5, 7.7);                              // value =complex(5.5, 7.7)

      if (o) {
           *o2 = 88;                                             // complex(88.0, 0.0)
           *o2 = { 1.2, 3.4 }; // complex(1.2, 3.4)
      }
  }

//3.3.比较测试:
 void test_compare() {                                  //比较操作
      //注意:optional的bool值或原始指针值可能会导致一些意外

      optional<int> o0;
      optional<int> o1{ 0 };

      assert((o0 == std::nullopt) == true);
      assert((o0 != 0) == true);
      assert((o0 < 0) == true);
      assert((o0 > 0) == false);
      assert((o1 == 0) == true);
      assert((o0 < o1) == true);

      optional<unsigned> uo;
      assert((uo < 0) == true);

      optional<bool> bo;
      assert((bo < false) == true);

      //支持底层类型的隐式类型转换:
      optional<int> o3{ 42 };
      optional<double> o4{ 42.0 };
      assert((o4 == 42) == true);
      assert((o3 == o4) == true);                    //warning有符号/无符号不匹配
 }

//3.4.数据类型为类测试:

 void test_classType() {
      struct Data { int x{ 0 }; };
      optional<Data> opt;
      if (opt) cout << opt->x << endl;               //类没有被构造无输出

      opt = Data{ 1 };
      assert(opt->x == 1);
 }

//3.5.移动语义测试:

 void test_move() {                                       //移动语义
      std::optional<std::string> os;
      std::string s = "Bob";
      os = std::move(s);                                 // OK, moves 可以给移动后的对象分配一个新的值
      assert(os.value() == string("Bob"));
  
      std::string s2 = *os;                               // OK copies
      assert(s2 == string("Bob"));

      //os仍然有一个字符串值,但是对于os对象这个值未指定
      std::string s3 = std::move(*os);             // OK, moves
      assert(s3 == string("Bob"));
      cout << os.has_value() << endl;            //1

      os.emplace("Tom");
      assert(os.value() == string("Tom"));
 }

//3.6.数据类型为bool,指针,optional特定情况测试:

 //特定的可选值类型可能导致特殊或意外的行为
 void test_bool_and_pointer() {
      std::optional<bool> ob{ false };                                       // has value, which is false
      if (!ob) cout << "not output it is false" << endl;               // yields false
      if (ob == false) cout << "putput it is true" << endl;          // yields true
    
      std::optional<int*> op{ nullptr };
      if (!op) cout << "int* empty it is false" << endl;               // yields false
      if (op == nullptr) cout << "int* empty it is true" << endl;  // yields true
    
      //Optional的Optional
      optional<optional<string>> oos1;
      optional<optional<string>> oos2 = "hello";
      optional<optional<string>> oos3{ std::in_place, std::in_place, "hello" };
      optional<optional<complex<double>>> ooc{ std::in_place, std::in_place, 4.2, 5.3 };

      //你也可以分配新的值,即使隐式转换:
      oos1 = "Tom";                                                                  // OK: assign new value
      assert(oos1.value().value()==string("Tom"));
  
      ooc.emplace(std::in_place, 0.2, 0.3);
      assert(ooc.value().value().real() == 0.2);
      assert(ooc.value().value().imag() == 0.3);
  

      //由于optional无值有两个层次
      //optional的optional使“无值”出现在外部或内部,可以有不同的语义含义 :
      *oos1 = std::nullopt;                                                             // inner optional has no value
      oos1 = std::nullopt;                                                              // outer optional has no value

      //但必须特别注意处理optional值:
      if (!oos1) std::cout << "no value\n";                                     //true
      if (oos1 && !*oos1) std::cout << "no inner value\n" ;           //false
      if (oos1 && *oos1) std::cout << "value: " << **oos1 << '\n';//false
 }

//3.7.函数返回值:

 std::optional<int> asInt(const std::string& s)//返回值
 {
      try { return std::stoi(s); }
      catch (...) { return std::nullopt; }                //表明没有int值 
 }
 /*
 std::optional<int> asInt(const std::string& s)//实现相同行为
 {
      std::optional<int> ret;
      try { ret = std::stoi(s); }
      catch (...){}
      return ret;
 }
 */
 void test_asFunctionParameter() {
      for (auto s : { "42", " 077", "hello", "0x33" }) {
           std::optional<int> oi = asInt(s);
           if (oi) cout << *oi << "\n";
           else cout << "can't convert " << s << "\n";
      }
 }

//3.8.类成员变量:

 class Name {
 private:
      optional<string> givenname;//教名
      string name;                          //名字
      string surname;                     //姓
 public:
      //对于具有值语义类型,定义初始化相应成员的构造函数的最佳方法是按值获取参数并将参数移动到成员
      Name(optional<std::string> g,string n, string s):
   :     givenname{ std::move(g) }, name{ move(n) }, surname{ move(s) } {}
      friend std::ostream& operator << (ostream& strm, const Name& n)
      {
           strm << n.givenname.value_or("") << ' '<< n.name << ' '<< n.surname;
           return strm;
      }
 };

 void test_class() {
       Name n{ std::nullopt,"John",  "Wilson" };
       std::cout <<"约翰.维尔逊 "<< n << '\n';         //约翰.维尔逊  John Wilson
       Name m{ "DEdward", "Adam", "Davis" };
       std::cout <<"爱德华.亚当.戴维 "<< m << '\n';//爱德华.亚当.戴维 DEdward Adam Davis
 }

int main() {
     test_constructors();
     test_operator();
     test_compare();
     test_classType();
     test_asFunctionParameter();
     test_class();
     test_move();
     test_bool_and_pointer();
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值