为啥我可以将字符分配给字符串对象而不是字符串对象的向量?

Posted

技术标签:

【中文标题】为啥我可以将字符分配给字符串对象而不是字符串对象的向量?【英文标题】: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 参数隐式转换为字符串。 【参考方案1】:

在第一个表达式中分配字符文字有效,因为 std::string 类 has 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

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

svec = 'a', 'b'; 不起作用,因为std::vector 仅重载了operator=s 取std::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 元素的容器

【讨论】:

以上是关于为啥我可以将字符分配给字符串对象而不是字符串对象的向量?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们必须通过请求对数组而不是对象进行字符串化?

将 SwiftyJSON 对象分配给字符串数组

将指针分配给字符串对象的第一个和最后一个索引

如何将字符串对象分配给observable

如何重写此代码,将字符串列表分配给字符串对象的集合?

为啥我不能将 string::back 分配给字符串?