Đã nói ...
std::string x = "hello";
Lấy một `char *` hoặc `const char *` từ một chuỗi`
Làm cách nào để có được một con trỏ ký tự hợp lệ trong khi x
vẫn ở trong phạm vi và không được sửa đổi thêm
C ++ 11 đơn giản hóa mọi thứ; tất cả những điều sau đây cung cấp quyền truy cập vào cùng bộ đệm chuỗi bên trong:
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...
Tất cả các con trỏ trên sẽ giữ cùng một giá trị - địa chỉ của ký tự đầu tiên trong bộ đệm. Ngay cả một chuỗi rỗng cũng có "ký tự đầu tiên trong bộ đệm", bởi vì C ++ 11 đảm bảo luôn giữ thêm một ký tự kết thúc NUL / 0 sau khi nội dung chuỗi được gán rõ ràng (ví dụ: std::string("this\0that", 9)
sẽ có bộ đệm "this\0that\0"
).
Cho bất kỳ con trỏ trên:
char c = p[n]; // valid for n <= x.size()
// i.e. you can safely read the NUL at p[x.size()]
Chỉ dành cho const
con trỏ p_writable_data
và từ &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
Viết một NUL ở nơi khác trong chuỗi không thay đổi string
's size()
; string
Chúng được phép chứa bất kỳ số lượng NUL nào - chúng không được xử lý đặc biệt bằng std::string
(tương tự trong C ++ 03).
Trong C ++ 03 , mọi thứ phức tạp hơn đáng kể (các khác biệt chính được tô sáng ):
x.data()
- trả về
const char*
bộ đệm bên trong của chuỗi mà Tiêu chuẩn không yêu cầu để kết luận với NUL (nghĩa là có thể được ['h', 'e', 'l', 'l', 'o']
theo sau bởi các giá trị rác chưa được xác định hoặc với các truy cập ngẫu nhiên có hành vi không xác định ).
x.size()
ký tự được an toàn để đọc, tức là x[0]
thông quax[x.size() - 1]
- đối với các chuỗi trống, bạn được đảm bảo một số con trỏ không phải là NULL mà 0 có thể được thêm một cách an toàn (Hurrah!), nhưng bạn không nên bỏ qua con trỏ đó.
&x[0]
- đối với các chuỗi rỗng, hành vi này không xác định (21.3.4)
- ví dụ như
f(const char* p, size_t n) { if (n == 0) return; ...whatever... }
bạn không được gọi f(&x[0], x.size());
khi x.empty()
- chỉ cần sử dụng f(x.data(), ...)
.
- mặt khác, theo
x.data()
nhưng:
- đối với phi
const
x
này sẽ mang lại một const
char*
con trỏ không ; bạn có thể ghi đè nội dung chuỗi
x.c_str()
- trả về
const char*
giá trị đại diện ASCIIZ (chấm dứt NUL) của giá trị (nghĩa là ['h', 'e', 'l', 'l', 'o', '\ 0']).
- mặc dù vài nếu có triển khai đã chọn để làm như vậy, C ++ 03 chuẩn bị diễn đạt để cho phép việc thực hiện chuỗi sự tự do để tạo ra một bộ đệm NUL-chấm dứt phân biệt một cách nhanh chóng , từ khả năng phi NUL chấm dứt đệm "tiếp xúc" bởi
x.data()
và&x[0]
x.size()
+ 1 ký tự an toàn để đọc.
- được đảm bảo an toàn ngay cả đối với các chuỗi trống (['\ 0']).
Hậu quả của việc truy cập các chỉ số pháp lý bên ngoài
Dù bạn có con trỏ bằng cách nào, bạn cũng không được truy cập bộ nhớ dọc theo con trỏ so với các ký tự được đảm bảo có trong các mô tả ở trên. Những nỗ lực để làm như vậy có hành vi không xác định , với khả năng rất cao là sự cố ứng dụng và kết quả rác ngay cả khi đọc và thêm dữ liệu bán buôn, ngăn chặn tham nhũng và / hoặc lỗ hổng bảo mật để ghi.
Khi nào những con trỏ bị vô hiệu?
Nếu bạn gọi một số string
hàm thành viên sửa đổi string
hoặc dự trữ dung lượng thêm, mọi giá trị con trỏ được trả về trước bởi bất kỳ phương thức nào ở trên đều bị vô hiệu . Bạn có thể sử dụng các phương thức đó một lần nữa để có được một con trỏ khác. (Các quy tắc tương tự như đối với các trình vòng lặp thành string
s).
Xem thêm Làm thế nào để có được một con trỏ ký tự hợp lệ ngay cả sau khi x
phạm vi lá hoặc được sửa đổi thêm bên dưới ....
Vì vậy, cái nào tốt hơn để sử dụng?
Từ C ++ 11, sử dụng .c_str()
cho dữ liệu ASCIIZ và .data()
cho dữ liệu "nhị phân" (giải thích thêm bên dưới).
Trong C ++ 03, sử dụng .c_str()
trừ khi chắc chắn .data()
là đủ và thích hợp .data()
hơn &x[0]
vì nó an toàn cho các chuỗi trống ....
... Cố gắng hiểu chương trình đủ để sử dụng data()
khi thích hợp, hoặc có thể bạn sẽ mắc các lỗi khác ...
Ký tự ASCII NUL '\ 0' được đảm bảo bởi .c_str()
nhiều chức năng được sử dụng làm giá trị trọng tâm biểu thị sự kết thúc của dữ liệu có liên quan và an toàn khi truy cập. Điều này áp dụng cho cả C ++ - chỉ các chức năng như nói fstream::fstream(const char* filename, ...)
và chia sẻ với các chức năng như strchr()
, và printf()
.
Với sự .c_str()
đảm bảo của C ++ 03 về bộ đệm được trả về là một bộ siêu tập hợp .data()
, bạn luôn có thể sử dụng một cách an toàn .c_str()
, nhưng đôi khi mọi người không vì:
- sử dụng
.data()
giao tiếp với các lập trình viên khác đang đọc mã nguồn rằng dữ liệu không phải là ASCIIZ (thay vào đó, bạn đang sử dụng chuỗi để lưu trữ một khối dữ liệu (đôi khi thậm chí không thực sự bằng văn bản)) hoặc bạn đang truyền nó tới một chức năng khác coi nó như một khối dữ liệu "nhị phân". Đây có thể là một cái nhìn sâu sắc quan trọng trong việc đảm bảo rằng các thay đổi mã của các lập trình viên khác tiếp tục xử lý dữ liệu đúng cách.
- Chỉ C ++ 03: có một cơ hội nhỏ rằng
string
việc triển khai của bạn sẽ cần thực hiện một số cấp phát bộ nhớ bổ sung và / hoặc sao chép dữ liệu để chuẩn bị bộ đệm kết thúc NUL
Một gợi ý nữa, nếu các tham số của hàm yêu cầu ( const
) char*
nhưng không khăng khăng nhận x.size()
, thì hàm đó có thể cần đầu vào ASCIIZ, vì vậy, đó .c_str()
là một lựa chọn tốt (hàm cần biết văn bản kết thúc bằng cách nào đó, vì vậy nếu không một tham số riêng biệt, nó chỉ có thể là một quy ước như tiền tố độ dài hoặc sentinel hoặc một số độ dài dự kiến cố định).
Làm thế nào để có được một con trỏ ký tự hợp lệ ngay cả sau khi x
rời khỏi phạm vi hoặc được sửa đổi thêm
Bạn sẽ cần sao chép nội dung của vùng string
x
nhớ mới bên ngoài x
. Bộ đệm ngoài này có thể ở nhiều nơi như một string
biến mảng hoặc ký tự khác, nó có thể có hoặc không có thời gian tồn tại khác với x
do ở một phạm vi khác (ví dụ: không gian tên, toàn cầu, tĩnh, heap, bộ nhớ chia sẻ, tệp ánh xạ bộ nhớ) .
Để sao chép văn bản từ std::string x
thành một mảng ký tự độc lập:
// 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);
Các lý do khác để muốn một char*
hoặc const char*
được tạo ra từ mộtstring
Vì vậy, ở trên bạn đã thấy làm thế nào để có một ( const
) char*
và làm thế nào để tạo một bản sao của văn bản độc lập với bản gốc string
, nhưng bạn có thể làm gì với nó? Một sự phá vỡ ngẫu nhiên của các ví dụ ...
- cấp quyền truy cập mã "C" cho
string
văn bản của C ++ , như trongprintf("x is '%s'", x.c_str());
- sao chép
x
văn bản vào bộ đệm được chỉ định bởi người gọi chức năng của bạn (ví dụ strncpy(callers_buffer, callers_buffer_size, x.c_str())
) hoặc bộ nhớ dễ bay hơi được sử dụng cho I / O của thiết bị (ví dụ for (const char* p = x.c_str(); *p; ++p) *p_device = *p;
)
- nối
x
văn bản của bạn vào một mảng ký tự đã chứa một số văn bản ASCIIZ (ví dụ strcat(other_buffer, x.c_str())
) - hãy cẩn thận để không tràn bộ đệm (trong nhiều tình huống bạn có thể cần sử dụng strncat
)
- trả về một
const char*
hoặc char*
từ một chức năng (có thể vì lý do lịch sử - khách hàng đang sử dụng API hiện tại của bạn - hoặc để tương thích với C mà bạn không muốn trả lại std::string
, nhưng muốn sao chép string
dữ liệu của bạn ở đâu đó cho người gọi)
- cẩn thận không trả về một con trỏ có thể bị hủy bởi người gọi sau khi một
string
biến cục bộ mà con trỏ trỏ trỏ có phạm vi rời
- một số dự án với các đối tượng được chia sẻ được biên dịch / liên kết cho các
std::string
triển khai khác nhau (ví dụ STLport và trình biên dịch gốc) có thể truyền dữ liệu dưới dạng ASCIIZ để tránh xung đột