Sự khác biệt giữa các kiểu chuỗi và char [] trong C ++


126

Tôi biết một chút C và bây giờ tôi đang xem C ++. Tôi đã quen với các mảng char để xử lý các chuỗi C, nhưng trong khi tôi xem mã C ++, tôi thấy có các ví dụ sử dụng cả hai kiểu chuỗi và mảng char:

#include <iostream>
#include <string>
using namespace std;

int main () {
  string mystr;
  cout << "What's your name? ";
  getline (cin, mystr);
  cout << "Hello " << mystr << ".\n";
  cout << "What is your favorite team? ";
  getline (cin, mystr);
  cout << "I like " << mystr << " too!\n";
  return 0;
}

#include <iostream>
using namespace std;

int main () {
  char name[256], title[256];

  cout << "Enter your name: ";
  cin.getline (name,256);

  cout << "Enter your favourite movie: ";
  cin.getline (title,256);

  cout << name << "'s favourite movie is " << title;

  return 0;
}

(cả hai ví dụ từ http://www.cplusplus.com )

Tôi cho rằng đây là một câu hỏi được hỏi và trả lời rộng rãi (rõ ràng?), Nhưng thật tuyệt nếu ai đó có thể cho tôi biết chính xác sự khác biệt giữa hai cách xử lý chuỗi trong C ++ (hiệu suất, tích hợp API, theo cách mỗi người tốt hơn, ...).

Cảm ơn bạn.


Điều này có thể giúp: C ++ char * vs std :: string
Wael Dalloul

Câu trả lời:


187

Một mảng char chỉ có thế - một mảng các ký tự:

  • Nếu được phân bổ trên ngăn xếp (như trong ví dụ của bạn), nó sẽ luôn chiếm ví dụ. 256 byte cho dù văn bản chứa trong bao lâu
  • Nếu được phân bổ trên heap (sử dụng malloc () hoặc char mới []), bạn có trách nhiệm giải phóng bộ nhớ sau đó và bạn sẽ luôn có chi phí phân bổ heap.
  • Nếu bạn sao chép một văn bản gồm hơn 256 ký tự vào mảng, nó có thể bị sập, tạo ra các thông báo xác nhận xấu hoặc gây ra hành vi không thể giải thích được (mis-) ở một nơi khác trong chương trình của bạn.
  • Để xác định độ dài của văn bản, mảng phải được quét, ký tự theo ký tự, cho ký tự \ 0.

Chuỗi là một lớp có chứa một mảng char, nhưng tự động quản lý nó cho bạn. Hầu hết các triển khai chuỗi có một mảng 16 ký tự tích hợp (vì vậy các chuỗi ngắn không phân mảnh heap) và sử dụng heap cho các chuỗi dài hơn.

Bạn có thể truy cập mảng char của chuỗi như thế này:

std::string myString = "Hello World";
const char *myStringChars = myString.c_str();

Các chuỗi C ++ có thể chứa các ký tự nhúng \ 0, biết độ dài của chúng mà không cần đếm, nhanh hơn các mảng char được phân bổ heap cho các văn bản ngắn và bảo vệ bạn khỏi các lỗi tràn bộ đệm. Thêm vào đó chúng dễ đọc hơn và dễ sử dụng hơn.


Tuy nhiên, các chuỗi C ++ không (rất) phù hợp để sử dụng trên các ranh giới DLL, bởi vì điều này sẽ yêu cầu bất kỳ người dùng nào của hàm DLL đó để đảm bảo rằng anh ta sử dụng cùng một trình biên dịch và thực hiện thời gian chạy C ++, vì anh ta có nguy cơ lớp chuỗi của mình cư xử khác.

Thông thường, một lớp chuỗi cũng sẽ giải phóng bộ nhớ heap của nó trên heap gọi, do đó, nó sẽ chỉ có thể giải phóng bộ nhớ một lần nữa nếu bạn đang sử dụng phiên bản chia sẻ (dll hoặc .so) của thời gian chạy.

Tóm lại: sử dụng chuỗi C ++ trong tất cả các hàm và phương thức bên trong của bạn. Nếu bạn đã từng viết một dll hoặc .so, hãy sử dụng các chuỗi C trong các hàm công khai (dll / so-phơi) của bạn.


4
Ngoài ra, các chuỗi có một loạt các hàm trợ giúp có thể thực sự gọn gàng.
Håkon

1
Tôi không tin một chút về các kết nối DLL. Trong các trường hợp rất đặc biệt, nó có khả năng bị phá vỡ ((một DLL được liên kết tĩnh với một phiên bản thời gian chạy khác với các DLL khác được sử dụng) và những điều tồi tệ hơn có thể xảy ra đầu tiên trong những tình huống này) nhưng trong trường hợp chung là mọi người đang sử dụng mặc định phiên bản chia sẻ của thời gian chạy tiêu chuẩn (mặc định) điều này sẽ không xảy ra.
Martin York

2
Ví dụ: Bạn phân phối các nhị phân được biên dịch VC2008SP1 của một thư viện công cộng có tên libfoo, có std :: string & trong API công khai của nó. Bây giờ Ai đó tải xuống libfoo.dll của bạn và thực hiện quá trình gỡ lỗi. Chuỗi std :: của anh ấy rất có thể có một số trường gỡ lỗi bổ sung trong đó, làm cho phần bù của con trỏ cho các chuỗi động di chuyển.
Cygon

2
Ví dụ 2: Vào năm 2010, một người nào đó tải xuống libfoo.dll của bạn và sử dụng nó trong ứng dụng do VC2010 xây dựng. Mã của anh ta tải MSVCP100.dll và libfoo.dll của bạn vẫn tải MSVCP90.dll -> bạn nhận được hai đống -> bộ nhớ không thể được giải phóng, lỗi xác nhận trong chế độ gỡ lỗi nếu libfoo sửa đổi tham chiếu chuỗi và cung cấp chuỗi std :: con trỏ trở lại.
Cygon

1
Tôi sẽ chỉ gắn bó với "Tóm lại: sử dụng chuỗi C ++ trong tất cả các chức năng và phương thức nội bộ của bạn." Cố gắng để hiểu ví dụ của bạn giúp việc bộ não của tôi bật lên.
Stephen

12

Arkaitz đúng stringlà một loại được quản lý. Điều này có nghĩa với bạn là bạn không bao giờ phải lo lắng về việc chuỗi này dài bao nhiêu, bạn cũng không phải lo lắng về việc giải phóng hoặc phân bổ lại bộ nhớ của chuỗi.

Mặt khác, char[]ký hiệu trong trường hợp trên đã giới hạn bộ đệm ký tự ở chính xác 256 ký tự. Nếu bạn đã cố gắng viết hơn 256 ký tự vào bộ đệm đó, tốt nhất bạn sẽ ghi đè lên bộ nhớ khác mà chương trình của bạn "sở hữu". Tệ nhất, bạn sẽ cố ghi đè lên bộ nhớ mà bạn không sở hữu và hệ điều hành của bạn sẽ giết chương trình của bạn ngay lập tức.

Dòng dưới cùng? Chuỗi thân thiện với lập trình viên hơn rất nhiều, char [] s hiệu quả hơn rất nhiều cho máy tính.


4
Tệ nhất, những người khác sẽ ghi đè lên bộ nhớ và chạy mã độc trên máy tính của bạn. Xem thêm tràn bộ đệm .
David Johnstone

6

Chà, kiểu chuỗi là một lớp được quản lý hoàn toàn cho các chuỗi ký tự, trong khi char [] vẫn là lớp trong C, một mảng byte đại diện cho một chuỗi ký tự cho bạn.

Về mặt API và thư viện chuẩn, mọi thứ đều được triển khai theo các chuỗi chứ không phải char [], nhưng vẫn còn rất nhiều hàm từ libc nhận char [] vì vậy bạn có thể cần sử dụng nó cho các chuỗi đó, ngoài ra tôi sẽ cần sử dụng nó cho các chuỗi đó. luôn luôn sử dụng std :: chuỗi.

Về mặt hiệu quả tất nhiên, một bộ đệm thô của bộ nhớ không được quản lý hầu như sẽ luôn nhanh hơn cho nhiều thứ, nhưng hãy tính đến việc so sánh các chuỗi, ví dụ, chuỗi std :: luôn có kích thước để kiểm tra trước, trong khi với char [] bạn cần so sánh nhân vật theo nhân vật.


5

Cá nhân tôi không thấy bất kỳ lý do nào khiến người ta muốn sử dụng char * hoặc char [] ngoại trừ khả năng tương thích với mã cũ. std :: string không chậm hơn so với sử dụng chuỗi c, ngoại trừ việc nó sẽ xử lý phân bổ lại cho bạn. Bạn có thể đặt kích thước của nó khi bạn tạo nó và do đó tránh phân bổ lại nếu bạn muốn. Toán tử lập chỉ mục của nó ([]) cung cấp quyền truy cập thời gian liên tục (và theo mọi nghĩa của từ này giống hệt như sử dụng bộ chỉ mục chuỗi c). Sử dụng phương thức at cũng cung cấp cho bạn giới hạn kiểm tra an toàn, một số thứ bạn không nhận được bằng chuỗi c, trừ khi bạn viết nó. Trình biên dịch của bạn thường sẽ tối ưu hóa việc sử dụng bộ chỉ mục trong chế độ phát hành. Thật dễ dàng để gây rối với chuỗi c; những thứ như xóa vs xóa [], an toàn ngoại lệ, thậm chí làm thế nào để phân bổ lại chuỗi c.

Và khi bạn phải đối phó với các khái niệm nâng cao như có chuỗi COW và không COW cho MT, v.v., bạn sẽ cần chuỗi std ::.

Nếu bạn lo lắng về các bản sao, miễn là bạn sử dụng tài liệu tham khảo và tham chiếu const bất cứ nơi nào bạn có thể, bạn sẽ không có bất kỳ chi phí nào do bản sao và đó là điều tương tự như bạn sẽ làm với chuỗi c.


+1 Mặc dù bạn không xem xét các vấn đề triển khai như khả năng tương thích DLL, nhưng bạn có COW.

những gì về tôi biết rằng mảng char của tôi trong 12 byte? Nếu tôi khởi tạo một chuỗi cho nó có thể không thực sự hiệu quả phải không?
David Wong

@David: Nếu bạn có mã nhạy cảm cực kỳ hoàn hảo thì có. Bạn có thể coi std :: chuỗi ctor gọi như một chi phí ngoài việc khởi tạo các thành viên chuỗi std ::. Nhưng hãy nhớ tối ưu hóa sớm đã tạo ra rất nhiều cơ sở mã không cần thiết theo kiểu C, vì vậy hãy cẩn thận.
Abhay

1

Chuỗi có chức năng trợ giúp và tự động quản lý mảng char. Bạn có thể nối các chuỗi, đối với một mảng char bạn sẽ cần sao chép nó sang một mảng mới, các chuỗi có thể thay đổi độ dài của chúng khi chạy. Một mảng char khó quản lý hơn một chuỗi và một số hàm nhất định chỉ có thể chấp nhận một chuỗi làm đầu vào, yêu cầu bạn chuyển đổi mảng thành chuỗi. Tốt hơn là sử dụng chuỗi, chúng được tạo ra để bạn không phải sử dụng mảng. Nếu mảng được khách quan tốt hơn, chúng ta sẽ không có chuỗi.


0

Hãy nghĩ về (char *) là chuỗi.begin (). Sự khác biệt cơ bản là (char *) là một iterator và std :: string là một container. Nếu bạn dính vào các chuỗi cơ bản, một (char *) sẽ cung cấp cho bạn những gì std :: string :: iterator làm. Bạn có thể sử dụng (char *) khi bạn muốn lợi ích của trình lặp và khả năng tương thích với C, nhưng đó là ngoại lệ và không phải là quy tắc. Như mọi khi, hãy cẩn thận về việc vô hiệu hóa vòng lặp. Khi mọi người nói (char *) không an toàn thì đây là ý của họ. Nó an toàn như mọi trình lặp C ++ khác.


0

Một trong những khác biệt là chấm dứt Null (\ 0).

Trong C và C ++, char * hoặc char [] sẽ lấy một con trỏ tới một char làm tham số và sẽ theo dõi bộ nhớ cho đến khi đạt được giá trị bộ nhớ 0 (thường được gọi là bộ kết thúc null).

Chuỗi C ++ có thể chứa các ký tự nhúng \ 0, biết độ dài của chúng mà không cần đếm.

#include<stdio.h>
#include<string.h>
#include<iostream>

using namespace std;

void NullTerminatedString(string str){
   int NUll_term = 3;
   str[NUll_term] = '\0';       // specific character is kept as NULL in string
   cout << str << endl <<endl <<endl;
}

void NullTerminatedChar(char *str){
   int NUll_term = 3;
   str[NUll_term] = 0;     // from specific, all the character are removed 
   cout << str << endl;
}

int main(){
  string str = "Feels Happy";
  printf("string = %s\n", str.c_str());
  printf("strlen = %d\n", strlen(str.c_str()));  
  printf("size = %d\n", str.size());  
  printf("sizeof = %d\n", sizeof(str)); // sizeof std::string class  and compiler dependent
  NullTerminatedString(str);


  char str1[12] = "Feels Happy";
  printf("char[] = %s\n", str1);
  printf("strlen = %d\n", strlen(str1));
  printf("sizeof = %d\n", sizeof(str1));    // sizeof char array
  NullTerminatedChar(str1);
  return 0;
}

Đầu ra:

strlen = 11
size = 11
sizeof = 32  
Fee s Happy


strlen = 11
sizeof = 12
Fee

"Từ cụ thể, tất cả các ký tự được xóa" không, chúng không bị "xóa", in một con trỏ char chỉ in tối đa cho dấu kết thúc null. (vì đó là cách duy nhất một char * biết kết thúc) lớp chuỗi biết chính kích thước đầy đủ nên nó chỉ sử dụng nó. nếu bạn biết kích thước của char * của mình, bạn cũng có thể tự in / sử dụng tất cả các ký tự.
Puddle
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.