如何将 std::string
转换为 char*
或 const char*
?
std::string
传递给需要 const char *
的函数,您可以使用 .c_str()
方法:std::string str;
const char * c = str.c_str();
如果您需要一个非const char *
,请调用.data()
:
std::string str;
char * c = str.data();
.data()
是在 C++17 中添加的。在此之前,您可以使用 &str[0]
。
请注意,如果 std::string
是 const
的,则 .data()
将返回 const char *
,就像 .c_str()
一样。
如果字符串被销毁或重新分配内存,则指针将失效。
该指针指向以空字符终止的字符串,并且终止符不计入 str.size()
。您不允许将非空字符赋值给终止符。
Given say...
std::string x = "hello";
如何获取一个字符指针,使其在x
仍在作用域内且不被进一步修改时仍然有效
C++11简化了事情;以下方式都可以访问相同的内部字符串缓冲区:
const char* p_c_str = x.c_str();
const char* p_data = x.data();
char* p_writable_data = x.data(); // for non-const x from C++17
const char* p_x0 = &x[0];
char* p_x0_rw = &x[0]; // compiles iff x is not const...
std::string("this\0that", 9)
将具有包含"this\0that\0"
的缓冲区)。char c = p[n]; // valid for n <= x.size()
// i.e. you can safely read the NUL at p[x.size()]
仅针对非const
指针p_writable_data
和从&x[0]
开始的指针:
p_writable_data[n] = c;
p_x0_rw[n] = c; // valid for n <= x.size() - 1
// i.e. don't overwrite the implementation maintained NUL
string
可以包含任意数量的NUL,它们在std::string
中没有特殊处理(在C++03中也是如此)。在C++03中,情况要复杂得多(重点区别已经突出显示):
x.data()
const char*
,但标准没有要求以NUL结尾(即可能是['h', 'e', 'l', 'l', 'o']
加上未初始化或垃圾值,意外访问会导致未定义的行为)。x.size()
个字符,即x[0]
到x[x.size() - 1]
。&x[0]
x.data()
相同,但:const
的x
,它会产生一个非const
的char*
指针;你可以覆盖字符串内容。x.c_str()
const char*
(即['h','e','l','l','o','\0'])。x.data()
和&x[0]
。x.size()
+1个字符。无论如何获取指针,都不能访问指针之后的内存,超出上述描述中保证存在的字符。试图这样做会导致未定义的行为,甚至对于读取而言也有非常真实的应用程序崩溃和垃圾结果的风险,此外还可能导致数据、堆栈损坏和/或安全漏洞。
string
进行迭代器相同)。x
离开作用域或进一步修改"。.c_str()
,对于"二进制"数据,请使用.data()
(下面会进一步解释)。.c_str()
,除非确定.data()
是足够的,并且优先使用.data()
而不是&x[0]
,因为它对空字符串是安全的。
...尝试充分了解程序以在适当时使用data()
,否则可能会犯其他错误...
由.c_str()
保证的ASCII NUL '\0'字符被许多函数用作表示相关和安全访问数据结束的哨兵值。这适用于仅限于C++的函数(例如fstream::fstream(const char* filename, ...)
)和与C共享的函数(例如strchr()
和printf()
)。.c_str()
对返回缓冲区的保证是.data()
的超集,您始终可以安全地使用.c_str()
,但有时人们不这样做,因为:
.data()
向阅读源代码的其他程序员传达数据不是ASCIIZ(相反,您正在使用字符串存储数据块(这有时甚至不是真正的文本)),或者您将其传递给将其视为"二进制"数据块的另一个函数。这可以成为确保其他程序员的代码更改继续正确处理数据的关键见解。string
实现将需要进行一些额外的内存分配和/或数据复制以准备NUL终止的缓冲区。const
)char*
,但不要求获取x.size()
,则该函数可能需要ASCIZ输入,因此.c_str()
是一个不错的选择(函数需要以某种方式知道文本何时结束,因此,如果它不是单独的参数,则只能是类似于长度前缀或标记或某些固定的预期长度的约定)。
x
离开作用域或进一步修改后仍然有效您需要将string
x
的内容复制到x
之外的新内存区域中。这个外部缓冲区可以在许多地方,例如另一个string
或字符数组变量中,由于处于不同的作用域(例如命名空间、全局、静态、堆、共享内存、内存映射文件),它可能具有不同的生命周期。
要将文本从std::string x
复制到独立的字符数组中:
// USING ANOTHER STRING - AUTO MEMORY MANAGEMENT, EXCEPTION SAFE
std::string old_x = x;
// - old_x will not be affected by subsequent modifications to x...
// - you can use `&old_x[0]` to get a writable char* to old_x's textual content
// - you can use resize() to reduce/expand the string
// - resizing isn't possible from within a function passed only the char* address
std::string old_x = x.c_str(); // old_x will terminate early if x embeds NUL
// Copies ASCIIZ data but could be less efficient as it needs to scan memory to
// find the NUL terminator indicating string length before allocating that amount
// of memory to copy into, or more efficient if it ends up allocating/copying a
// lot less content.
// Example, x == "ab\0cd" -> old_x == "ab".
// USING A VECTOR OF CHAR - AUTO, EXCEPTION SAFE, HINTS AT BINARY CONTENT, GUARANTEED CONTIGUOUS EVEN IN C++03
std::vector<char> old_x(x.data(), x.data() + x.size()); // without the NUL
std::vector<char> old_x(x.c_str(), x.c_str() + x.size() + 1); // with the NUL
// USING STACK WHERE MAXIMUM SIZE OF x IS KNOWN TO BE COMPILE-TIME CONSTANT "N"
// (a bit dangerous, as "known" things are sometimes wrong and often become wrong)
char y[N + 1];
strcpy(y, x.c_str());
// USING STACK WHERE UNEXPECTEDLY LONG x IS TRUNCATED (e.g. Hello\0->Hel\0)
char y[N + 1];
strncpy(y, x.c_str(), N); // copy at most N, zero-padding if shorter
y[N] = '\0'; // ensure NUL terminated
// USING THE STACK TO HANDLE x OF UNKNOWN (BUT SANE) LENGTH
char* y = alloca(x.size() + 1);
strcpy(y, x.c_str());
// USING THE STACK TO HANDLE x OF UNKNOWN LENGTH (NON-STANDARD GCC EXTENSION)
char y[x.size() + 1];
strcpy(y, x.c_str());
// USING new/delete HEAP MEMORY, MANUAL DEALLOC, NO INHERENT EXCEPTION SAFETY
char* y = new char[x.size() + 1];
strcpy(y, x.c_str());
// or as a one-liner: char* y = strcpy(new char[x.size() + 1], x.c_str());
// use y...
delete[] y; // make sure no break, return, throw or branching bypasses this
// USING new/delete HEAP MEMORY, SMART POINTER DEALLOCATION, EXCEPTION SAFE
// see boost shared_array usage in Johannes Schaub's answer
// USING malloc/free HEAP MEMORY, MANUAL DEALLOC, NO INHERENT EXCEPTION SAFETY
char* y = strdup(x.c_str());
// use y...
free(y);
string
生成char*
或const char*
的其他原因上面已经介绍了如何获取(const
) char*
,以及如何创建与原始string
无关的文本副本,但是你可以用它做什么呢?以下是一些随机的例子...
string
的文本提供给"C"代码,例如:printf("x is '%s'", x.c_str());
x
的文本复制到由函数调用者指定的缓冲区中(例如:strncpy(callers_buffer, callers_buffer_size, x.c_str())
),或者用于设备I/O的易失性内存中(例如:for (const char* p = x.c_str(); *p; ++p) *p_device = *p;
)x
的文本附加到已包含某些ASCIIZ文本的字符数组中(例如:strcat(other_buffer, x.c_str())
)- 注意不要溢出缓冲区(在许多情况下,您可能需要使用strncat
)const char*
或char*
(也许是出于历史原因-客户端使用您现有的API-或出于C兼容性,您不想返回一个std::string
,但确实想将您的string
数据复制到调用者的某个地方)
string
变量离开作用域后由调用者解引用的指针std::string
实现(例如STLport和编译器本地)编译/链接的共享对象项目可能会将数据作为ASCIIZ传递以避免冲突对于 const char *
,可以使用.c_str()
方法。
你可以使用&mystring[0]
来获取一个char *
指针,但有一些需要注意的问题:你可能不会得到以零结尾的字符串,并且你将无法更改字符串的大小。特别要小心不要在字符串末尾添加字符,否则会发生缓冲区溢出(并可能导致崩溃)。
在 C++11 之前,没有保证所有字符都是同一连续缓冲区的一部分,但实际上已知的所有 std::string
实现方式都是如此;参见Does “&s[0]” point to contiguous characters in a std::string?。
请注意,许多string
成员函数将重新分配内部缓冲区并使您保存的任何指针无效。最好立即使用它们,然后丢弃。
C++17(即将发布的标准)改变了模板 basic_string
的概要,添加了一个非 const 的 data()
重载:
charT* data() noexcept;
返回值:指针 p ,使得对于每个 i 在 [0,size()] 区间内都有 p + i == &operator。
std::basic_string<CharT>
获取 CharT const *
std::string const cstr = { "..." };
char const * p = cstr.data(); // or .c_str()
std::basic_string<CharT>
中的CharT *
std::string str = { "..." };
char * p = str.data();
std::basic_string<CharT>
转换为 CharT const *
std::string str = { "..." };
str.c_str();
std::basic_string<CharT>
中的CharT *
从C++11开始,标准规定:
basic_string
对象中的类似char的对象必须连续存储。也就是说,对于任何basic_string
对象s
,对于所有满足0 <= n < s.size()
的n
值,&*(s.begin() + n) == &*s.begin() + n
都应该成立。
const_reference operator[](size_type pos) const;
reference operator[](size_type pos);
返回:
*(begin() + pos)
(如果pos < size()
),否则返回一个类型为CharT
且值为CharT()
的对象引用;不得修改所引用的值。
const charT* c_str() const noexcept;
const charT* data() const noexcept;
返回一个指针p,使得对于每个满足
i ∈ [0,size()]
的i
,p + i == &operator[](i)
。
有几种可能的方法可以获得非const字符指针。
std::string foo{"text"};
auto p = &*foo.begin();
优点
缺点
std::vector<CharT>
std::string foo{"text"};
std::vector<char> fcv(foo.data(), foo.data()+foo.size()+1u);
auto p = fcv.data();
优点
缺点
N
是编译时常量(且足够小),请使用std::array<CharT,N>
std::string foo{"text"};
std::array<char, 5u> fca;
std::copy(foo.data(), foo.data()+foo.size()+1u, fca.begin());
优点
缺点
std::string foo{ "text" };
auto p = std::make_unique<char[]>(foo.size()+1u);
std::copy(foo.data(), foo.data() + foo.size() + 1u, &p[0]);
优点
缺点
std::string foo{ "text" };
char * p = nullptr;
try
{
p = new char[foo.size() + 1u];
std::copy(foo.data(), foo.data() + foo.size() + 1u, p);
// handle stuff with p
delete[] p;
}
catch (...)
{
if (p) { delete[] p; }
throw;
}
优点:
缺点:
我正在使用一个包含很多以char*
作为输入的函数的API。
为了解决这种问题,我创建了一个小类,并实现了RAII惯用语法。
class DeepString
{
DeepString(const DeepString& other);
DeepString& operator=(const DeepString& other);
char* internal_;
public:
explicit DeepString( const string& toCopy):
internal_(new char[toCopy.size()+1])
{
strcpy(internal_,toCopy.c_str());
}
~DeepString() { delete[] internal_; }
char* str() const { return internal_; }
const char* c_str() const { return internal_; }
};
你可以这样使用它:
void aFunctionAPI(char* input);
// other stuff
aFunctionAPI("Foo"); //this call is not safe. if the function modified the
//literal string the program will crash
std::string myFoo("Foo");
aFunctionAPI(myFoo.c_str()); //this is not compiling
aFunctionAPI(const_cast<char*>(myFoo.c_str())); //this is not safe std::string
//implement reference counting and
//it may change the value of other
//strings as well.
DeepString myDeepFoo(myFoo);
aFunctionAPI(myFoo.str()); //this is fine
我称这个类为DeepString
,因为它创建了一个现有字符串的深层独立副本(DeepString
不可复制)。
std
中,“c_str()代表"C-string"而不是“const string”,而
str()始终返回
std::basic_string类型,而不是
char *类型(例如
std :: stringstream :: str()`)。 - bcrist看看这个:
string str1("stackoverflow");
const char * str2 = str1.c_str();
请注意,这将返回一个const char *
。
如果需要一个char *
,请使用strcpy
将其复制到另一个char
数组中。
char* result = strcpy((char*)malloc(str.length()+1), str.c_str());
现在将C++ std字符串转换为C风格字符串非常容易。
我们有string::copy
函数,它可以轻松将std字符串转换为C风格字符串。参考链接
string::copy
函数的参数依次为:
另一个重要的事情是:
此函数不会在操作结束时追加空字符。因此,我们需要手动添加。
以下是代码示例 -
// char string
char chText[20];
// c++ string
string text = "I am a Programmer";
// conversion from c++ string to char string
// this function does not append a null character at the end of operation
text.copy(chText, text.size(), 0);
// we need to put it manually
chText[text.size()] = '\0';
// below statement prints "I am a Programmer"
cout << chText << endl;
char chText[20] = "I am a Programmer";
// using constructor
string text(chText);
string::assign
方法。// char string
char chText[20] = "I am a Programmer";
// c++ string
string text;
// convertion from char string to c++ string
// using assign function
text.assign(chText);
// char string
char chText[20] = "I am a Programmer";
// c++ string
// convertion from char string to c++ string using assignment operator overloading
string text = chText;
第三种写法也可以像下面这样写 -
// char string
char chText[20] = "I am a Programmer";
// c++ string
string text;
// convertion from char string to c++ string
text = chText;
假设有:
string str="stack";
1) 将字符串转换为字符*
char* s_rw=&str[0];
上述char*(即s_rw)是可读写的,并指向需要转换为char*的字符串的基地址
2)将字符串转换为const char*
const char* s_r=&str[0];
当将std::string
的底层char*
缓冲区传递给期望并写入char*
缓冲区的C调用时,这是特别有用的。这样你就可以兼得最好的两个世界: C++ std::string
的便利性以及直接与你从C++调用的C库的可用性。
std::string
作为一个C风格的可读/可写char*
或只读的空终止符const char*
我该如何将
std::string
转换为char*
或const char*
?
尽管这是一个非常古老且高度投票的问题,但我即将介绍的信息还没有被充分涵盖,如果有涵盖的话也是很少,因此这是必要的补充,尤其是关于需要使用.resize()
方法预先分配底层C字符串以便将其用作可写缓冲器。
以下所有用法都需要C++11或更高版本,除了char* data()
调用,它需要C++17或更高版本。
要运行和测试下面的所有示例代码以及更多内容,请参见并运行我的string__use_std_string_as_a_c_str_buffer.cpp文件在我的eRCaGuy_hello_world存储库中。
#include <string>
constexpr size_t BUFFER_SIZE = 100;
std::string str;
// IMPORTANT: pre-allocate the underlying buffer to guarantee what size it is
str.resize(BUFFER_SIZE);
// -----------------------------------------------------------------------------
// Get read-writeable access to the underlying `char*` C-string at index i
// -----------------------------------------------------------------------------
char* c_str1 = &str[i]; // <=== my favorite!
char* c_str2 = str.data() + i;
char* c_str3 = &(*str.begin()) + i;
// NB: the C-strings above are NOT guaranteed to be null-terminated, so manually
// write in a null terminator at the index location where you want it if
// desired. Ex:
//
// 1. write a null terminator at some arbitrary position you choose (index 10
// here)
c_str1[10] = '\0';
// 2. write a null terminator at the last guaranteed valid position in the
// underlying C-string/array of chars
c_str2[str.size() - i - 1] = '\0';
// -----------------------------------------------------------------------------
// Get read-only access to the underlying `const char*` C-string at index i
// -----------------------------------------------------------------------------
const char* const_c_str1 = &str[i];
const char* const_c_str2 = str.c_str() + i; // guaranteed to be null-terminated,
// but not necessarily at the
// position you desire; the
// guaranteed null terminator will
// be at index location
// `str.size()`
如果您着急并需要:
char*
C字符串:只需使用下面代码示例中的A部分技术1:char* c_str1 = &str[i];
。
str.resize(BUFFER_SIZE)
预先分配底层缓冲区大小(如有必要),以确保底层缓冲区足够大以满足您的需求。const char*
C字符串:使用与上述相同的方法(const char* const_c_str1 = &str[i]
),或者const char* const_c_str1 = str.c_str() + i;
。#include <string>
constexpr size_t BUFFER_SIZE = 100;
std::string str;
// IMPORTANT: pre-allocate the underlying buffer to guarantee what size it is
str.resize(BUFFER_SIZE);
// =============================================================================
// Now you can use the `std::string`'s underlying buffer directly as a C-string
// =============================================================================
// ---------------------------------------------------------
// A. As a read-writeable `char*` C-string
// ---------------------------------------------------------
// Technique 1 [best option if using C++11]: array indexing using `operator[]`
// to obtain a char, followed by obtaining its address with `&`
// - Documentation:
// https://en.cppreference.com/w/cpp/string/basic_string/operator_at
char* c_str1 = &str[0];
char* c_str2 = &str[10];
char* c_str3 = &str[33];
// etc.
// Technique 2 [best option if using C++17]: use the `.data()` method to obtain
// a `char*` directly.
// - Documentation:
// https://en.cppreference.com/w/cpp/string/basic_string/data
char* c_str11 = str.data(); // same as c_str1 above
char* c_str12 = str.data() + 10; // same as c_str2 above
char* c_str13 = str.data() + 33; // same as c_str3 above
// Technique 3 [fine in C++11 or later, but is awkward, so don't do this. It is
// for demonstration and learning purposes only]: use the `.begin()` method to
// obtain an iterator to the first char, and then use the iterator's
// `operator*()` dereference method to obtain the iterator's `char`
// `value_type`, and then take the address of that to obtain a `char*`
// - Documentation:
// - https://en.cppreference.com/w/cpp/string/basic_string/begin
// - https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator
char* c_str21 = &(*str.begin()); // same as c_str1 and c_str11 above
char* c_str22 = &(*str.begin()) + 10; // same as c_str2 and c_str12 above
char* c_str23 = &(*str.begin()) + 33; // same as c_str3 and c_str13 above
// ---------------------------------------------------------
// B. As a read-only, null-terminated `const char*` C-string
// ---------------------------------------------------------
// - Documentation:
// https://en.cppreference.com/w/cpp/string/basic_string/c_str
const char* const_c_str1 = str.c_str(); // a const version of c_str1 above
const char* const_c_str2 = str.c_str() + 10; // a const version of c_str2 above
const char* const_c_str3 = str.c_str() + 33; // a const version of c_str3 above
.at(i)
和.front()
std::string
方法,但我不会深入讨论它们,因为我认为我的示例已经足够了。有关它们的文档,请参见:
请参见上面的注释。我不打算涵盖使用.at(i)
和.front()
std::string
方法的技术,因为我认为我已经介绍的几种技术已经足够了。
std::string
作为可读/可写的char*
要将C++ std::string
用作C风格的可写char*
缓冲区,您必须首先通过使用.resize()
来预分配字符串的内部缓冲区以更改其.size()
。请注意,仅使用.reserve()
增加.capacity()
是不够的! cppreference.com社区维基页面std::string::operator[]
正确地说明:
如果
pos > size()
,则行为未定义。
resize()
方法是用来改变大小的,而不是 reserve()
方法,后者只会改变 capacity()
。
例如:
#include <cstring> // `strcpy()`
#include <iostream>
#include <string>
constexpr size_t BUFFER_SIZE = 100;
std::string str;
str.resize(BUFFER_SIZE); // pre-allocate the underlying buffer
// check the size
std::cout << "str.size() = " << str.size() << "\n";
对于下面的所有示例,请假设您有这些C字符串:
constexpr char cstr1[] = "abcde ";
constexpr char cstr2[] = "fghijk";
一旦您使用resize()
预先分配了足够大的底层缓冲区,您可以以至少3种方式访问作为char*
的底层缓冲区:
operator[]
进行数组索引以获取一个字符,然后使用&
获取其地址。例如:
char* c_str;
c_str = &str[0];
c_str = &str[5];
// 等等。
// 将这两个C字符串写入`std::string`的底层缓冲区
strcpy(&str[0], cstr1);
strcpy(&str[sizeof(cstr1) - 1], cstr2); // `- 1`以覆盖第一个空终止符
// 打印字符串
std::cout << str << "\n"; // 输出:`abcde fghijk`
如果您有一个指向std::string
的指针怎么办?如果您有一个指向std::string
的指针,则必须先使用*pstr
对其进行解引用,然后才能使用operator[]
将其作为数组进行索引,如&(*pstr)[0]
,因此上面的语法变得有点笨拙。以下是完整示例:
std::string str2;
std::string* pstr = &str2;
pstr->resize(BUFFER_SIZE);
c_str = &(*pstr)[0]; // <=== 先解引用指针再进行索引
// 或者,为了使优先级顺序(https://en.cppreference.com/w/cpp/language/operator_precedence)真正明显,您可以选择添加额外的括号,如下所示:
c_str = &((*pstr)[0]);
.data()
方法直接获取char*
。例如:
char* c_str;
c_str = str.data();
c_str = str.data() + 5;
// 等等。
// 将这两个C字符串写入`std::string`的底层缓冲区
strcpy(str.data(), cstr1);
strcpy(str.data() + (sizeof(cstr1) - 1), cstr2); // `- 1`以覆盖第一个空终止符
// 打印字符串
std::cout << str << "\n"; // 输出:`abcde fghijk`
.begin()
方法获取指向第一个字符的迭代器,然后使用迭代器的operator*()
解引用方法获取迭代器的char
value_type
,然后取该地址以获取char*
。例如:
char* c_str;
c_str = &(*str.begin());
c_str = &(*str.begin()) + 5;
// 等等。
// 将这两个C字符串写入`std::string`的底层缓冲区
strcpy(&(*str.begin()), cstr1);
strcpy(&(*str.begin()) + (sizeof(cstr1) - 1), cstr2); // `- 1`以覆盖第一个空终止符
// 打印字符串
std::cout << str << "\n"; // 输出:`abcde fghijk`
需要注意的一件重要事情是,当你调用 str.resize(100)
时,它会为底层字符串至少保留 100 个字节,并将字符串的 size()
设置为 100
,并将这些字符中的所有 100
个初始化为 char()
--也就是char
(在这里查看我的问题)的默认值初始化值,即二进制零空终止符 '\0'
。因此,无论字符串中是否只有"hello"
后面跟着 95 个空终止符或零,每当你调用 str.size()
它都会返回 100
。要获取字符串的长度或非空终止符数,你必须使用 C 函数 strlen()
,如下所示:
std::cout << strlen(str.c_str()) << "\n"; // prints `12` in the examples above
// instead of:
std::cout << str.size() << "\n"; // prints `100` in the examples above
std::string
作为只读的、以 null 结尾的 const char*
要从 std::string
获取一个可读的、以 null 结尾的 const char*
,请使用 .c_str()
方法。它返回一个 C 风格的字符串,保证以 null 结尾。请注意,.data()
方法不是同一件事,因为它不能保证以 null 结尾!
示例:
std::string str = "hello world";
printf("%s\n", str.c_str());
2. https://en.cppreference.com/w/cpp/string/basic_string/operator_atbasic_string的元素是连续存储的,也就是说,对于一个basic_string s,在[0, s.size())内任意n,& *(s.begin()+n) == &*s.begin() + n,或者等价的,可以将指向s[0]的指针传递给期望指向以null结尾(自C++11起)的CharT[]数组第一个元素的函数。
3. https://en.cppreference.com/w/cpp/string/basic_string/resize 4. https://en.cppreference.com/w/cpp/string/basic_string/reserve 5. https://en.cppreference.com/w/cpp/string/basic_string/data 6. https://en.cppreference.com/w/cpp/string/basic_string/c_str 7. https://en.cppreference.com/w/cpp/string/basic_string/clear返回指定位置pos处的字符的引用。不进行边界检查。如果pos > size(),则行为是未定义的。
char* dest = new char[str.length() + 1]; std::copy(str.begin(), str.end(), dest)
会更符合C++的惯用方法。使用strcpy()
和malloc()
并没有错或问题,但在同一段代码块中使用C++字符串和C库函数以及等价的C++函数看起来不太一致。 - boycy