【问题标题】:Why can I assign characters to string objects but not to vectors of string objects?为什么我可以将字符分配给字符串对象而不是字符串对象的向量?
【发布时间】:2020-10-15 19:07:09
【问题描述】:

C++ Primer 说字符和字符串文字可以转换为strings。

我尝试将一个字符分配给 string 为:

std::string s;
s = 's';

它没有给我任何错误。

但是,当我尝试将字符分配给 vectorstrings 对象时:

std::vector<std::string> svec;
svec = {'a', 'b'};

它给了我一个错误。为什么?

【问题讨论】:

  • 因为std::vector 无法将 char 参数隐式转换为字符串。

标签: c++ string vector character variable-assignment


【解决方案1】:

在第一个表达式中分配字符文字有效,因为 std::stringhas an overload for the assignment operator 采用 char

第二个表达式中的字符文字参数不能隐式转换为字符串,就像字符串文字一样(即svec = {"a", "b"}),因为std::stringhas a constructor for const char* but not for char

表达式:

svec = {"a", "b"};

使用构造函数

string (const char* s);

表达式:

svec = {'a', 'b'};

无法工作,因为不存在采用单个字符参数的构造函数。

它所拥有的是一个接受initializer_list 的构造函数(如您在上一个链接中所见):

string (initializer_list<char> il); 

Available since C++11.

所以要使用字符文字初始化std::string,您需要使用大括号(即braced initializer list):

std::vector<std::string> svec;
svec = {{'a'}, {'b'}};

如您所知,这将在向量的前 2 个位置初始化 2 个字符串,一个包含 "a",另一个包含 "b"

对于向量第一个位置的单个字符串,您可以使用:

svec = {{'a','b'}};

【讨论】:

    【解决方案2】:

    C++ Primer 说字符和字符串文字可以转换为strings。

    C 风格的字符串文字可以隐式转换为std::string,但chars 不能。这与分配不同。

    s = 's'; 有效,因为std::string 有一个重载的operator= 占用char

    1. 用字符ch 替换内容,就像assign(std::addressof(ch), 1)

    svec = {'a', 'b'}; 不起作用,因为std::vector 仅重载了operator=sstd::vectorstd::initializer_list,它们都不能从braced-init-list {'a', 'b'} 构造。您可能认为这种情况下可以使用std::initializer_list&lt;std::string&gt; 的重载,但是char 不能隐式转换为std::stringstd::string 没有这样的converting constructor 采用char),然后无法从{'a', 'b'} 构造std::initializer_list&lt;std::string&gt;

    作为解决方法,您可以将代码更改为

    svec = {"a", "b"};
    

    "a"const char[2] 类型并衰减const char*,可以隐式转换为std::string(通过std::string's converting constructor 采用const char*),然后std::initializer_list&lt;std::string&gt;{"a", "b"} 构造而成传递给std::vector::operator=。当然svec = {std::string("a"), std::string("b")};(或svec = {"a"s, "b"s};)也可以,std::initializer_list&lt;std::string&gt; 将直接构造,无需隐式转换为std::string

    【讨论】:

      【解决方案3】:

      理解这一点的关键是初始化列表。

      首先,请注意这不起作用:

        std::string s('a');
      

      但确实如此:

        std::string s{'a'};
      

      原因是第一个需要一个带有单个char 的ctor,但std::string 没有这样的构造函数。另一方面,第二个创建一个initializer_list&lt;char&gt;std::string 确实有一个 ctor。

      同样的推理也适用于

        std::vector<std::string>> vs{ 'a', 'b' };
      

        std::vector<std::string>> vs{ {'a'}, {'b'} };
      

      第一个想要使用一个不存在的 std::string ctor 来获取一个 char,第二个使用初始化列表。

      至于原代码,原因

        std::string s;
        s = 'a';
      

      有效的是,虽然 std::string 缺少采用 char 的 ctor,但它确实有一个采用 char 的赋值运算符。

      【讨论】:

        【解决方案4】:

        考虑以下示例:

        #include <initializer_list>
        #include <vector>
        #include <string>
        #include <type_traits>
        
        int main() {
            std::vector<std::string> svec;
            
            // Non-ambigously deduced to std::initializer_list<char>.
            auto a = {'a', 'b'};
            static_assert(std::is_same_v<std::initializer_list<char>, decltype(a)>);
            
            // (A)
            // error: no match for 'operator=' (... std::vector<std::string> and 'std::initializer_list<char>')
            //svec = a;  
            //svec = std::initializer_list<char>{'a', 'b'};
            
            // error:
            // error: unable to deduce 'std::initializer_list<auto>' from '{{'a', 'b'}}'
            //auto b = {{'a', 'b'}};
            
            // (B)
            // OK. 
            // After all the following copy assingnments, svec.size() is 1 (a single "ab" element).
            svec = {{'a', 'b'}};
            svec = std::initializer_list<std::string>{{'a', 'b'}};
            svec = std::initializer_list<std::string>{std::initializer_list<char>{'a', 'b'}};
            svec = std::initializer_list<std::string>{"ab"};
        }
        

        (A) 处的错误消息是不言自明的:不存在用于 std::vector&lt;std::string&gt; 的复制赋值运算符来为其分配 std::initializer_list&lt;char&gt; 参数。 std::vector&lt;T,Allocator&gt;::operator= 唯一可行的重载需要std::initializer_list&lt;T&gt; 类型的参数:

        vector& operator=( std::initializer_list<T> ilist );
        

        用初始化列表ilist标识的内容替换内容。

        但是在这个例子中Tstd::string,因此这个重载是不可行的。

        另一方面,在(B)中,我们使用嵌套的大括号初始化语法{{'a', 'b'}},使得内部大括号初始化列表代表std::initializer_list&lt;char&gt;,可以使用the following std::string constructor:

        basic_string( std::initializer_list<CharT> ilist,
        
                        const Allocator& alloc = Allocator() );
        

        创建一个 single std::string 临时,它又是外部大括号初始化列表的一部分,利用上面提到的std::vector 复制赋值运算符:

        vector& operator=( std::initializer_list<T> ilist )
        

        (B)的不同变化的结果是相同的:复制分配给svec向量一个单个std::string元素的向量

        最后,请注意,如果您要使用" 分隔的字符串 的花括号初始化列表将assign 复制到svec,结果是复制分配两个元素的向量

        svec = {"a", "b"};  // svec.size() is now 2
        

        因为我们没有直接创建 两个元素std::initializer_list&lt;std::string&gt;,而上面的几个字符std::initializer_list&lt;char&gt;会导致单个字符串。即,单个std::string 对象也是(零或)多个char 元素的容器

        【讨论】:

          猜你喜欢
          • 2015-12-24
          • 1970-01-01
          • 2012-02-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-02
          • 1970-01-01
          • 2023-03-26
          • 1970-01-01
          相关资源
          最近更新 更多