2010-07-25 50 views
33

Tôi đã được hỏi câu hỏi này trong một cuộc phỏng vấn: Là chuỗi một loại tham chiếu hoặc một loại giá trị.Tại sao chúng ta không sử dụng toán tử mới khi khởi tạo một chuỗi?

Tôi đã nói loại tham chiếu của nó. Sau đó, ông hỏi tôi tại sao chúng ta không sử dụng toán tử mới trong khi khởi tạo chuỗi? Tôi nói bởi vì ngôn ngữ C# có một cú pháp đơn giản hơn để tạo một chuỗi và trình biên dịch sẽ tự động chuyển đổi mã thành một cuộc gọi cho các nhà xây dựng của lớp System.String.

Câu trả lời này có đúng không?

+4

[Chủ yếu là đúng] (http://msdn.microsoft.com/en-us/library/system .string_members.aspx), nhưng chuỗi phức tạp hơn một chút, và được lưu trữ và chia sẻ theo những cách kỳ lạ. Chúc may mắn. – Kobi

Trả lời

29

Chuỗi là loại tham chiếu không thay đổi được. Có lệnh ldstr IL cho phép đẩy tham chiếu đối tượng mới vào chuỗi ký tự. Vì vậy, khi bạn viết:

string a = "abc"; 

Trình biên dịch kiểm tra nếu số "abc" đã được xác định trong siêu dữ liệu và nếu không khai báo. Sau đó, nó được dịch mã này vào giảng dạy IL sau:

ldstr "abc" 

Mà về cơ bản làm cho a điểm biến địa phương để các chuỗi chữ quy định tại các siêu dữ liệu.

Vì vậy, tôi sẽ nói rằng câu trả lời của bạn không hoàn toàn đúng vì trình biên dịch không dịch điều này thành lời gọi đến một hàm tạo.

4

Không. Trình biên dịch không thay đổi cấu trúc. Đối số hàm tạo nên kiểu nào? Chuỗi? ;-)

Chuỗi ký tự là các hằng số không có tên.

Bên cạnh đó, bạn có thể khởi tạo bất kỳ lớp học với một chuỗi chữ, nếu nó hỗ trợ một nhà điều hành:

public class UnitTest1 { 
     class MyStringable { 
     public static implicit operator MyStringable(string value) { 
      return new MyStringable(); 
     } 
     } 

     [TestMethod] 
     public void MyTestMethod() { 
     MyStringable foo = "abc"; 
     } 
    } 


Sửa Để được rõ ràng hơn: Như bạn hỏi, nếu chuỗi sẽ được chuyển đổi thành bất kỳ lệnh gọi hàm tạo nào, hãy xem xét mã IL.

Taken phương pháp thử nghiệm này:

[TestClass] 
    class MyClass { 
     [TestMethod] 
     public void MyTest() { 
     string myString = "foo"; 
     if (myString == "bar") 
      Console.WriteLine("w00t"); 
     } 
    } 

Tạo mã IL sau:

.method public hidebysig instance void MyTest() cil managed 
{ 
    .custom instance void [Microsoft.VisualStudio.QualityTools.UnitTestFramework]Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute::.ctor() 
    .maxstack 2 
    .locals init (
     [0] string myString, 
     [1] bool CS$4$0000) 
    L_0000: nop 
    L_0001: ldstr "foo" 
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ldstr "bar" 
    L_000d: call bool [mscorlib]System.String::op_Equality(string, string) 
    L_0012: ldc.i4.0 
    L_0013: ceq 
    L_0015: stloc.1 
    L_0016: ldloc.1 
    L_0017: brtrue.s L_0024 
    L_0019: ldstr "w00t" 
    L_001e: call void [mscorlib]System.Console::WriteLine(string) 
    L_0023: nop 
    L_0024: ret 
} 

Như bạn thấy, tất cả các giá trị chuỗi (foo, quầy bar và w00t) vẫn là chuỗi và không gọi bất kỳ hàm tạo ẩn nào.

Hy vọng điều này giải thích thêm.

+5

Đó là mát mẻ, nhưng tôi không hoàn toàn nhìn thấy nó giúp giải thích các chuỗi như thế nào. – Kobi

+0

Phần đầu tiên cố gắng giải thích. Các chuỗi là tính năng ngôn ngữ C# được tích hợp sẵn. Chuỗi ký tự là chuỗi và sẽ không bao giờ bị thay đổi bởi trình biên dịch thành bất kỳ thứ gì được cung cấp cho một hàm tạo chuỗi. Có lẽ câu trả lời quá cẩu thả ... Xin lỗi! –

+0

Tôi đã thay đổi câu trả lời của mình để rõ ràng hơn. –

11

Vâng, đúng là trình biên dịch có cú pháp đặc biệt giúp đơn giản hóa việc tạo chuỗi.

Phần về trình biên dịch tạo cuộc gọi tới hàm tạo không thực sự chính xác. Chuỗi ký tự được tạo khi ứng dụng bắt đầu, do đó, khi chuỗi ký tự được sử dụng, nó chỉ là một sự gán tham chiếu đến một đối tượng đã tồn tại.

Nếu bạn gán một chuỗi chữ trong một vòng lặp:

string[] items = new string[10]; 
for (int i = 0; i < 10; i++) { 
    items[i] = "test"; 
} 

nó sẽ không tạo ra một đối tượng chuỗi mới cho mỗi lần lặp, nó sẽ chỉ sao chép các tài liệu tham khảo cùng vào mỗi mục.

Hai điều đáng chú ý khác về chuỗi ký tự là trình biên dịch không tạo bản sao và nó sẽ tự động kết hợp chúng nếu bạn nối chúng. Nếu bạn sử dụng chuỗi chữ nhiều hơn một lần, nó sẽ sử dụng cùng một đối tượng:

string a = "test"; 
string b = "test"; 
string c = "te" + "st"; 

Các biến a, bc tất cả các điểm đến cùng một đối tượng.

Chuỗi lớp cũng có nhà xây dựng mà bạn có thể sử dụng:

string[] items = new string[10]; 
for (int i = 0; i < 10; i++) { 
    items[i] = new String('*', 42); 
} 

Trong trường hợp này bạn sẽ thực sự nhận được mười đối tượng chuỗi riêng biệt.

0

Nhưng chúng ta có thể sử dụng toán tử new thời gian để khởi tạo xâu

String str = new char[] {'s','t','r'}; 

là câu trả lời này có đúng hay không?

Không, chuỗi được lưu trữ và sử dụng, giả sử như trong IL.

28

Không chính xác câu trả lời đúng. Stringsloại đặc biệt "đặc biệt". Họ là bất biến. Bạn đúng rằng trình biên dịch làm điều gì đó bên trong, nhưng nó không phải là lời gọi hàm tạo. Nó gọi ldstr đẩy một tham chiếu đối tượng mới vào một chuỗi ký tự được lưu trữ trong siêu dữ liệu.

Mẫu C# code:

class Program 
{ 
    static void Main() 
    { 
     string str; 
     string initStr = "test"; 
    } 
} 

và đây là mã IL

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  8 (0x8) 
    .maxstack 1 
    .locals init ([0] string str, 
      [1] string initStr) 
    IL_0000: nop 
    IL_0001: ldstr  "test" 
    IL_0006: stloc.1 
    IL_0007: ret 
} // end of method Program::Main 

Bạn có thể thấy ldstr cuộc gọi trên.

Thậm chí nhiều hơn do tính bất biến của các chuỗi, nó có thể chỉ giữ các chuỗi riêng biệt/duy nhất. Tất cả các chuỗi được giữ trong bảng băm trong đó khóa là giá trị chuỗi và giá trị là tham chiếu đến chuỗi đó. Mỗi khi chúng ta có một chuỗi kiểm tra CLR mới thì đã có một chuỗi như vậy trong bảng băm. Nếu có thì không có bộ nhớ mới nào được cấp phát và tham chiếu được đặt thành chuỗi hiện tại này.

Bạn có thể chạy mã này để kiểm tra:

class Program 
{ 
    static void Main() 
    { 
     string someString = "abc"; 
     string otherString = "efg"; 

     // will retun false 
     Console.WriteLine(Object.ReferenceEquals(someString, otherString)); 

     someString = "efg"; 

     // will return true 
     Console.WriteLine(Object.ReferenceEquals(someString, otherString)); 
    } 
}  
+1

Tuyệt vời! Cảm ơn bạn đã giải thích chi tiết, đặc biệt là về bảng băm nội bộ. Tôi chưa bao giờ nghĩ về điều đó. – NDeveloper

+0

Tất cả các chuỗi không được giữ trong một bảng băm, đó chỉ là các chuỗi được thực tập. Chuỗi ký tự được tập trung, nhưng bất kỳ chuỗi mới nào được tạo sẽ không được tự động tập trung. – Guffa

+0

giải thích tuyệt vời. Tôi chỉ tự hỏi điều gì sẽ xảy ra khi chúng ta gán giá trị mới cho biến chuỗi. Vì tôi đã tìm ra chuỗi là một lớp. – ExpertLoser

0

Dưới đây là quan điểm của tôi, tôi không hoàn toàn chắc chắn, vì vậy hãy trả lời tôi với một hạt muối.

Chuỗi ký tự trong .NET là khép kín, độ dài của nó hoặc cấu trúc dữ liệu khác được bao gồm nội bộ trong chính giá trị chữ. Vì vậy, không giống như C, gán chuỗi ký tự trong .NET chỉ là vấn đề gán địa chỉ bộ nhớ của toàn bộ cấu trúc dữ liệu của chuỗi.Trong C, chúng ta cần phải sử dụng mới trong lớp chuỗi vì nó cần phân bổ các cấu trúc dữ liệu khác xung quanh chuỗi có kết thúc bằng null, ví dụ như độ dài.

1

Như mọi người đã nói, chuỗi là bất biến, vì vậy không có hàm gọi hàm dựng nào. Tôi muốn thêm tài liệu tham khảo sau đây cho bạn, có thể rõ ràng không khí nhiều hơn một chút:

String Immutability

Các vấn đề liên quan