2009-02-10 29 views
8

Tôi có các câu hỏi sau về chuỗi trong C++trong C++

1 >> đó là một lựa chọn tốt hơn (xem xét hiệu suất) và tại sao?

1.

string a; 
a = "hello!"; 

HOẶC

2.

string *a; 
a = new string("hello!"); 
... 
delete(a); 

2 >>

string a; 
a = "less"; 
a = "moreeeeeee"; 

cách chính xác quản lý bộ nhớ được xử lý trong C++ khi một chuỗi lớn hơn là sao chép vào một cảnh nhỏ hơn ng? Các chuỗi C++ có thể thay đổi được không?

+0

Bạn có đang sử dụng một lớp chuỗi cụ thể hay chỉ các chuỗi ký tự không được chấm dứt tiêu chuẩn không? – tsellon

+0

Tôi sẽ giả định câu hỏi là về std :: string. – rmeador

Trả lời

8

Tất cả những điều sau đây là trình biên dịch ngây thơ sẽ làm gì. Tất nhiên miễn là nó không thay đổi hành vi của chương trình, trình biên dịch là miễn phí để thực hiện bất kỳ tối ưu hóa.

string a; 
a = "hello!"; 

Trước tiên, bạn khởi tạo để chứa chuỗi rỗng. (đặt độ dài thành 0 và một hoặc hai thao tác khác). Sau đó, bạn chỉ định giá trị mới, ghi đè giá trị độ dài đã được đặt. Nó cũng có thể phải thực hiện một kiểm tra để xem bộ đệm hiện tại lớn đến mức nào và có nên cấp phát bộ nhớ nhiều hơn hay không.

string *a; 
a = new string("hello!"); 
... 
delete(a); 

Gọi mới yêu cầu hệ điều hành và bộ nhớ cấp phát để tìm một đoạn bộ nhớ miễn phí. Đó là chậm. Sau đó, bạn khởi tạo nó ngay lập tức, vì vậy bạn không gán bất cứ điều gì hai lần hoặc yêu cầu bộ đệm được thay đổi kích cỡ, giống như bạn làm trong phiên bản đầu tiên. Sau đó, điều gì đó xấu xảy ra và bạn quên gọi xóa, và bạn bị rò rỉ bộ nhớ, thêm ngoài vào chuỗi cực kỳ chậm để phân bổ. Vì vậy, điều này là xấu.

string a; 
a = "less"; 
a = "moreeeeeee"; 

Giống như trong trường hợp đầu tiên, trước tiên bạn khởi tạo để chứa chuỗi rỗng. Sau đó, bạn gán một chuỗi mới và một chuỗi khác. Mỗi một trong số này có thể yêu cầu cuộc gọi mới để cấp phát thêm bộ nhớ. Mỗi dòng cũng yêu cầu độ dài và có thể phải gán các biến nội bộ khác.

Thông thường, bạn muốn phân bổ nó như thế này:

string a = "hello"; 

Một dòng, thực hiện khởi tạo một lần, chứ không phải là mặc định-khởi tạo, và sau đó gán giá trị mà bạn muốn đầu tiên.

Nó cũng giảm thiểu lỗi, vì bạn không có chuỗi trống vô nghĩa ở bất kỳ đâu trong chương trình của bạn. Nếu chuỗi tồn tại, nó chứa giá trị bạn muốn.

Giới thiệu về quản lý bộ nhớ, google RAII. Trong ngắn hạn, chuỗi cuộc gọi mới/xóa nội bộ để thay đổi kích thước bộ đệm của nó. Điều đó có nghĩa là bạn không bao giờ cần phải phân bổ chuỗi mới. Đối tượng chuỗi có kích thước cố định và được thiết kế để được cấp phát trên ngăn xếp, sao cho hàm hủy là tự động được gọi khi nó nằm ngoài phạm vi. Các destructor sau đó đảm bảo rằng bất kỳ bộ nhớ được phân bổ được giải phóng. Bằng cách đó, bạn không phải sử dụng mới/xóa trong mã người dùng của bạn, có nghĩa là bạn sẽ không bị rò rỉ bộ nhớ.

+0

string a = "hello"; Cũng có thể được viết bằng cách sử dụng hàm tạo rõ ràng chuỗi a ("hello"); Hoặc trong C++ 0x: chuỗi a = {"hello"}; –

+0

Vâng, nó sẽ không phải là C++ nếu không có một số sự mơ hồ với ít nhất 3 cách để làm điều tương tự, phải không? ;) Nhưng tất cả đều có tác dụng tương tự. Chuỗi được tạo và khởi tạo trong hàm tạo, thay vì đầu tiên gọi hàm tạo, sau đó gán sau đó. – jalf

2
string a; 
a = "hello!"; 

2 hoạt động: gọi std constructor mặc định: string() và sau đó gọi các nhà điều hành :: =

string *a; a = new string("hello!"); ... delete(a); 

chỉ có một hoạt động: gọi std constructor: string (const char *) nhưng bạn không nên quên nhả con trỏ của bạn.

Còn khoảng chuỗi a ("hello");

+0

Bạn có chắc chắn về tất cả những điều đó không? Bạn có nhìn vào mã được trình biên dịch tạo ra không? Là ctor trống thực sự được gọi là? – Tim

+0

Tất nhiên trình biên dịch có thể tối ưu hóa nó đi, nếu nó đủ thông minh để xác định rằng nó không có tác dụng phụ, và vì vậy việc khai báo và khởi tạo có thể được kết hợp lại với nhau. Nhưng đó không phải là một. – jalf

+0

Phiên bản con trỏ có thực sự chỉ là một thao tác không? Liệu sự gián đoạn không gây ra chi phí? – yungchin

14

Nó gần như là không bao giờ cần thiết hoặc mong muốn để nói

string * s = new string("hello"); 

Sau khi tất cả, bạn sẽ (hầu như) không bao giờ nói:

int * i = new int(42); 

Bạn thay vì nên nói

string s("hello"); 

hoặc

string s = "hello"; 

Và có, chuỗi C++ có thể thay đổi.

4

Có lý do cụ thể khiến bạn liên tục sử dụng bài tập thay vì intialization không? Đó là, tại sao bạn không viết

string a = "Hello"; 

v.v ...? Điều này tránh được việc xây dựng mặc định và chỉ có ý nghĩa hơn về mặt ngữ nghĩa. Tạo một con trỏ tới một chuỗi chỉ vì mục đích phân bổ nó trên heap không bao giờ có ý nghĩa, tức là trường hợp 2 của bạn không có ý nghĩa và kém hiệu quả hơn một chút.

Đối với câu hỏi cuối cùng của bạn, có, các chuỗi trong C++ có thể thay đổi trừ khi được khai báo const.

0

Trong trường hợp 1.1, chuỗi của bạn thành viên (trong đó bao gồm con trỏ đến dữ liệu) được tổ chức tại stack và bộ nhớ bị chiếm đóng bởi các cá thể của lớp được giải phóng khi a đi ra khỏi phạm vi.

Trong trường hợp 1.2, bộ nhớ cho các thành viên được phân bổ động từ đống quá.

Khi bạn chỉ định hằng số char* cho một chuỗi, bộ nhớ sẽ chứa dữ liệu sẽ được realloc 'chỉnh sửa để phù hợp với dữ liệu mới.

Bạn có thể xem lượng bộ nhớ được phân bổ bằng cách gọi string::capacity().

Khi bạn gọi string a("hello"), bộ nhớ sẽ được cấp phát trong hàm tạo.

Cả nhà xây dựng và toán tử gán đều gọi cùng một phương thức nội bộ cho bộ nhớ được cấp phát và sao chép dữ liệu mới tại đó.

0

Nếu bạn nhìn vào docs cho lớp chuỗi STL (Tôi tin rằng tài liệu SGI tuân thủ thông số kỹ thuật), nhiều danh sách phương pháp đảm bảo độ phức tạp. Tôi tin rằng rất nhiều sự bảo đảm phức tạp được cố tình để lại mơ hồ để cho phép triển khai khác nhau. Tôi nghĩ rằng một số triển khai thực sự sử dụng một phương pháp sao chép-sửa đổi, chẳng hạn như gán một chuỗi cho một chuỗi khác là hoạt động liên tục, nhưng bạn có thể phải trả một chi phí bất ngờ khi bạn cố sửa đổi một trong các trường hợp đó. Không chắc chắn nếu đó vẫn là sự thật trong STL hiện đại mặc dù.

Bạn cũng nên kiểm tra chức năng capacity(), sẽ cho bạn biết chuỗi độ dài tối đa bạn có thể đưa vào một thể hiện chuỗi nhất định trước khi nó buộc phải phân bổ lại bộ nhớ. Bạn cũng có thể sử dụng reserve() để tạo sự phân bổ lại cho một số tiền cụ thể nếu bạn biết bạn sẽ lưu trữ một chuỗi lớn trong biến sau đó.

Như những người khác đã nói, theo như ví dụ của bạn đi, bạn thực sự nên khởi tạo trên các phương pháp khác để tránh việc tạo ra các đối tượng tạm thời.

+0

sao chép-trên-viết thường không còn được sử dụng, bởi vì nó trở nên không hiệu quả trong một môi trường đa luồng. – jalf

0

Tạo chuỗi trực tiếp trong heap thường không phải là ý tưởng hay, giống như tạo loại cơ sở. Nó không phải là giá trị nó kể từ khi đối tượng có thể dễ dàng ở lại trên ngăn xếp và nó có tất cả các nhà xây dựng bản sao và nhà điều hành chuyển nhượng cần thiết cho một bản sao hiệu quả.

Chuỗi std: có một bộ đệm trong heap có thể được chia sẻ bởi một số chuỗi tùy thuộc vào việc triển khai.

Đối intsance, với thực hiện STL của Microsoft bạn có thể làm điều đó:

string a = "Hello!"; 
string b = a; 

Và cả hai chuỗi sẽ chia sẻ cùng một bộ đệm cho đến khi bạn thay đổi nó:

a = "Something else!"; 

Đó là lý do tại sao nó là rất xấu đến lưu trữ c_str() để sử dụng sau này; c_str() garentee chỉ có hiệu lực cho đến khi một cuộc gọi khác đến đối tượng chuỗi đó được thực hiện.

Điều này dẫn đến lỗi đồng thời rất khó chịu mà yêu cầu chức năng chia sẻ này để được tắt với một định nghĩa nếu bạn sử dụng chúng trong một ứng dụng đa luồng

0

Nhiều khả năng

string a("hello!"); 

là nhanh hơn so với bất cứ điều gì khác.

0

Bạn đến từ Java, phải không? Trong C++, các đối tượng được xử lý giống nhau (theo nhiều cách) như các kiểu giá trị cơ bản. Các đối tượng có thể nằm trên ngăn xếp hoặc trong bộ nhớ tĩnh và được truyền theo giá trị. Khi bạn khai báo một chuỗi trong một hàm, phân bổ trên stack tuy nhiên nhiều byte đối tượng chuỗi mất. Chính đối tượng chuỗi không sử dụng bộ nhớ động để lưu trữ các ký tự thực, nhưng đó là minh bạch đối với bạn. Điều khác cần nhớ là khi hàm thoát và chuỗi bạn khai báo không còn trong phạm vi, tất cả bộ nhớ mà nó sử dụng được giải phóng. Không cần thu gom rác thải (RAII là bạn thân nhất của bạn).

Trong ví dụ của bạn:

string a; 
a = "less"; 
a = "moreeeeeee"; 

này đặt một khối bộ nhớ trên stack và tên nó một, sau đó các nhà xây dựng được gọi và một là khởi tạo một chuỗi rỗng. Trình biên dịch lưu trữ các byte cho "less" và "moreeeeeee" trong (tôi nghĩ) phần .rdata của exe của bạn. Chuỗi a sẽ có một vài trường, như trường độ dài và char * (tôi đơn giản hóa rất nhiều). Khi bạn gán "less" cho a, phương thức operator =() được gọi. Nó sẽ tự động cấp phát bộ nhớ để lưu trữ giá trị đầu vào, sau đó sao chép nó vào. Khi bạn gán "moreeeeeee" cho một, phương thức operator =() được gọi lại và nó phân bổ lại đủ bộ nhớ để giữ giá trị mới nếu cần, sau đó sao chép nó vào bộ đệm bên trong.

Khi phạm vi của chuỗi thoát, chuỗi hủy được gọi và bộ nhớ được phân bổ động để giữ các ký tự thực được giải phóng. Sau đó, con trỏ ngăn xếp được giảm đi và bộ nhớ được giữ không còn "trên" ngăn xếp.