Cửa sổ C # hình thành các phép toán logic. Các phép tính toán học

Cập nhật cuối cùng: 19.06.2017

Một tập hợp các phép toán riêng biệt biểu diễn các biểu thức điều kiện. Các phép toán như vậy trả về một giá trị boolean, nghĩa là, một giá trị của kiểu bool: true nếu biểu thức là true và false nếu biểu thức là false. Các hoạt động này bao gồm các hoạt động so sánh và hoạt động logic.

Các phép toán so sánh

Các toán tử so sánh so sánh hai toán hạng và trả về bool - true nếu biểu thức là đúng và sai nếu biểu thức là sai.

    So sánh hai toán hạng cho bằng nhau. Nếu chúng bằng nhau thì hoạt động trả về true, nếu không bằng nhau thì trả về false:

    B; // sai

    So sánh hai toán hạng và trả về true nếu các toán hạng không bằng nhau và false nếu chúng bằng nhau.

    int a = 10; int b = 4; bool c = a! = b; // true bool d = a! = 10; // sai

    Ít hơn hoạt động. Trả về true nếu toán hạng đầu tiên nhỏ hơn toán hạng thứ hai và false nếu toán hạng đầu tiên lớn hơn toán hạng thứ hai:

    int a = 10; int b = 4; bool c = a< b; // false

    Hoạt động "lớn hơn". So sánh hai toán hạng và trả về true nếu toán hạng đầu tiên lớn hơn toán hạng thứ hai, ngược lại trả về false:

    int a = 10; int b = 4; bool c = a> b; // true bool d = a> 25; // sai

    Hoạt động nhỏ hơn hoặc bằng. So sánh hai toán hạng và trả về true nếu toán hạng đầu tiên nhỏ hơn hoặc bằng toán hạng thứ hai. Nếu không, trả về false.

    int a = 10; int b = 4; bool c = a<= b; // false bool d = a <= 25; // true

    Lớn hơn hoặc bằng hoạt động. So sánh hai toán hạng và trả về true nếu toán hạng đầu tiên lớn hơn hoặc bằng toán hạng thứ hai, nếu không thì trả về false:

    int a = 10; int b = 4; bool c = a> = b; // true bool d = a> = 25; // sai

Hoạt động<, > <=, >= có mức độ ưu tiên cao hơn == và! =.

Phép toán boolean

Cũng được định nghĩa trong C # toán tử logic, cũng trả về giá trị bool. Họ chấp nhận các giá trị bool làm toán hạng. Thường được áp dụng cho các mối quan hệ và kết hợp nhiều phép toán so sánh.

    Hoạt động của phép cộng hợp lý hoặc lôgic OR. Trả về true nếu ít nhất một trong các toán hạng trả về true.

    bool x1 = (5> 6) | (4< 6); // 5 >6 - sai, 4< 6 - true, поэтому возвращается true bool x2 = (5 >6) | (4> 6); // 5> 6 - false, 4>

    Phép nhân boolean hoặc AND logic. Trả về true nếu cả hai toán hạng đều đúng cùng một lúc.

    bool x1 = (5> 6) & (4< 6); // 5 >6 - sai, 4< 6 - true, поэтому возвращается false bool x2 = (5 < 6) & (4 < 6); // 5 < 6 - true, 4 < 6 - true, поэтому возвращается true

    Các hoạt động của phép cộng logic. Trả về true nếu ít nhất một trong các toán hạng trả về true.

    bool x1 = (5> 6) || (4< 6); // 5 >6 - sai, 4< 6 - true, поэтому возвращается true bool x2 = (5 >6) || (4> 6); // 5> 6 là false, 4> 6 ​​là false, do đó, false được trả về

    Các hoạt động của phép nhân hợp lý. Trả về true nếu cả hai toán hạng đều đúng.

    bool x1 = (5> 6) && (4< 6); // 5 >6 - sai, 4< 6 - true, поэтому возвращается false bool x2 = (5 < 6) && (4 < 6); // 5 < 6 - true, 4 >6 là true vì vậy true được trả về

    Hoạt động của phủ định lôgic. Thực hiện trên một toán hạng duy nhất và trả về true nếu toán hạng sai. Nếu toán hạng là true, thì hoạt động trả về false:

    bool a = true; bool b =! a; // sai

    Hoạt động HOẶC độc quyền. Trả về true nếu toán hạng đầu tiên hoặc thứ hai (nhưng không phải cả hai) đều đúng, nếu không trả về false

    bool x5 = (5> 6) ^ (4< 6); // 5 >6 - sai, 4< 6 - true, поэтому возвращается true bool x6 = (50 > 6) ^ (4 / 2 < 3); // 50 >6 - đúng, 4/2< 3 - true, поэтому возвращается false

Ở đây chúng ta có hai cặp phép toán | và || (cũng như & và &&) thực hiện các hành động tương tự, nhưng chúng không tương đương.

Trong biểu thức z = x | y; cả giá trị x và y sẽ được tính toán.

Trong biểu thức z = x || y; đầu tiên, giá trị x sẽ được tính, và nếu nó bằng true, thì việc tính giá trị y không còn ý nghĩa nữa, vì trong mọi trường hợp chúng ta đã có z bằng true. Giá trị của y sẽ chỉ được đánh giá nếu x sai

Tương tự đối với cặp phép toán & / &&. Trong biểu thức z = x, cả hai giá trị - x và y - sẽ được tính.

Trong biểu thức z = x &, giá trị x sẽ được tính trước, và nếu nó bằng false, thì việc tính giá trị y không còn ý nghĩa nữa, vì trong mọi trường hợp chúng ta đã có z bằng false. Giá trị của y sẽ chỉ được đánh giá nếu x đúng

Do đó, các phép toán || và && thuận tiện hơn trong tính toán, vì chúng cho phép bạn giảm thời gian cần thiết để đánh giá giá trị của một biểu thức và do đó cải thiện hiệu suất. Và hoạt động | và & phù hợp hơn để thực hiện các phép toán bit trên các số.

Toán tử && được ưu tiên hơn toán tử ||. Vì vậy, trong biểu thức true || true && false biểu thức con true && false được thực thi đầu tiên.

CHƯƠNG 10 Biểu thức và toán tử

Trong chương này, chúng ta sẽ xem xét nền tảng của bất kỳ ngôn ngữ lập trình nào - khả năng thực hiện các phép gán và so sánh bằng cách sử dụng các toán tử. Chúng ta sẽ xem các toán tử trong C # là gì và mức độ ưu tiên của chúng, sau đó chúng ta sẽ đi sâu vào các loại biểu thức riêng lẻ để thực hiện các phép toán số học, gán giá trị và so sánh các toán hạng.

Các nhà khai thác

Toán tử là một ký hiệu chỉ ra một thao tác được thực hiện trên một hoặc nhiều đối số. Khi câu lệnh được thực hiện, kết quả thu được. Cú pháp để áp dụng các toán tử có phần khác với các phương thức gọi, và bạn nên biết định dạng của các biểu thức chứa các toán tử trong C # giống như mu bàn tay của bạn. Như trong hầu hết các ngôn ngữ khác, ngữ nghĩa của các toán tử trong C # tuân theo các quy tắc và ký hiệu quen thuộc với chúng ta từ thời đi học. Các toán tử cơ bản trong C # bao gồm nhân (*), chia (/), cộng và cộng một bậc (+), trừ và trừ một bậc (-), môđun (%) và phép gán (=).

Các toán tử được sử dụng để nhận một giá trị mới từ các giá trị mà thao tác được thực hiện. Này giá trị ban đầu triệu tập Toán hạng. Kết quả của hoạt động phải được lưu trữ trong bộ nhớ. Đôi khi nó được lưu trữ trong một biến chứa một trong các toán hạng ban đầu. Trình biên dịch C # tạo ra một thông báo lỗi nếu một giá trị mới không được xác định hoặc không được lưu trữ khi một toán tử được sử dụng. Đoạn mã dưới đây không thay đổi các giá trị. Trình biên dịch sẽ đưa ra thông báo lỗi, vì một biểu thức số học không thay đổi ít nhất một giá trị thường được coi là lỗi.

lớp NoResultApp

{

public static void Main ()

{

int i; int j;

i + j; // Lỗi vì kết quả không được gán cho bất kỳ thứ gì. )>

Hầu hết các nhà khai thác chỉ làm việc với kiểu số dữ liệu chẳng hạn như Byte, Ngắn, Dài, Số nguyên, Đơn, ĐôiSố thập phân. Ngoại lệ là các toán tử so sánh (== và! =). Ngoài ra, trong C #, bạn có thể sử dụng các toán tử + và - trên một lớp Chuỗi và thậm chí sử dụng các toán tử tăng (++) và (-) cho các cấu trúc ngôn ngữ bất thường như vậy làm đại biểu. Tôi sẽ thảo luận về phần sau trong chương 14.

Ưu tiên điều hành

Khi có nhiều câu lệnh trong cùng một biểu thức, trình biên dịch phải xác định thứ tự mà chúng được thực thi. Khi làm như vậy, trình biên dịch được hướng dẫn bởi các quy tắc được gọi là ưu tiên điều hành. Hiểu mức độ ưu tiên của toán tử là điều cần thiết cho đúng chính tả biểu thức - đôi khi kết quả có thể không như mong đợi.

Xét biểu thức 42 + 6 * 10. Nếu bạn thêm 42 và 6, rồi nhân tổng với 10, bạn nhận được 480. Nếu bạn nhân 6 với 10 và thêm 42 vào kết quả, bạn nhận được 102. Khi biên dịch mã, một thành phần đặc biệt của trình biên dịch - máy phân tích từ vựng - chịu trách nhiệm về thứ tự đọc của mã này. Nó là bộ phân tích từ vựng xác định mức độ ưu tiên tương đối của các toán tử không đồng nhất trong một biểu thức. Để làm điều này, nó sử dụng một số giá trị - mức độ ưu tiên - của mỗi toán tử được hỗ trợ. Các toán tử có mức độ ưu tiên cao hơn được giải quyết đầu tiên. Trong ví dụ của chúng tôi, toán tử * được ưu tiên hơn toán tử + vì * hấp thụ(Bây giờ tôi sẽ giải thích thuật ngữ này) các toán hạng của nó before + does it. Lời giải thích nằm ở các quy tắc số học chung: nhân và chia luôn có nhiều hơn ưu tiên cao hơn cộng và trừ. Hãy quay lại ví dụ: họ nói rằng số 6 hấp thụ toán tử * và trong 42 + 6 * 10 và trong 42 * 6 + 10, vì vậy các biểu thức này tương đương với 42 + (6 * 10) và (42 * 6) + 10.

Mức độ ưu tiên được xác định như thế nào trong C #

Bây giờ chúng ta hãy xem mức độ ưu tiên của toán tử được xác định như thế nào trong C #. Các toán tử được liệt kê dưới đây theo thứ tự ưu tiên giảm dần (Bảng 10-1). Tiếp theo, tôi sẽ nói rõ hơn về các loại khác nhau các toán tử được hỗ trợ trong C #.

Chuyển hướng. 10-1.Ưu tiên toán tử trong C #.

Danh mục nhà điều hành Các nhà khai thác
Giản dị (x), x.y, f (x), a [x], x ++, x -, new, typeof, sizeof, đã chọn, đã bỏ chọn
một ngôi +, -,!, ++ x, - x, (T) x
Phép nhân *,/, %
phụ gia +, -
Sự thay đổi «, »
Thái độ <, >, <=, >=, là
Bình đẳng ==
Logic AND (AND) &
Độc quyền logic HOẶC (XOR) ^
Hợp lý HOẶC (HOẶC) 1
AND có điều kiện (AND) &&
IL có điều kiện VÀ (HOẶC) II
Tình trạng 9-
Phân công = *= /= % = , + = , -= « = , » = , &=, ^ = , =

Sự kết hợp trái và phải

Tính liên kết xác định phần nào của biểu thức nên được đánh giá đầu tiên. Ví dụ: kết quả của biểu thức trên có thể là 21 hoặc 33, tùy thuộc vào phép kết hợp nào sẽ được áp dụng cho toán tử "-": left hoặc right.

Toán tử - có tính kết hợp trái, tức là 42-15 được tính đầu tiên và sau đó 6 được trừ khỏi kết quả. Nếu nó có tính kết hợp phải, trước tiên nó sẽ tính phần bên phải biểu thức (15-6), và sau đó kết quả sẽ được trừ cho 42.

Tất cả các toán tử nhị phân (toán tử có hai toán hạng), ngoại trừ toán tử gán, - liên kết trái, tức là chúng xử lý các biểu thức từ trái sang phải. Theo cách này, a + b+ Với - giống như (a + b) + c,đầu tiên được tính ở đâu a + b, và sau đó được thêm vào tổng Với. toán tử gán và câu điều kiện - kết hợp đúng đắn, nghĩa là, chúng xử lý các biểu thức từ phải sang trái. Nói cách khác, a = b = c tương đương với a = (b= Với). Nhiều người vấp phải điều này khi họ muốn đặt nhiều câu lệnh gán trên cùng một dòng, vì vậy hãy xem đoạn mã này:

sử dụng Hệ thống;

class RightAssocApp (

public static void Main () (

int a = 1; int b = 2; int c = 3;

Console.WriteLine ("a = (0) b = (1) c = (2>", a, b, c); a = b = c;

Console.WriteLine ("Sau" a = b = c -: a = (0) b = (1) c = (2) ", a, b, c);>>

Kết quả của việc chạy ví dụ này là:

a = 1 b = 2 c = 3

Sau "a = b = c": a = 3 b = 3 o = 3

Đánh giá từ phải sang trái của các biểu thức lúc đầu có thể gây nhầm lẫn, nhưng hãy tiếp cận nó theo cách này: nếu toán tử gán là liên kết trái, trình biên dịch trước tiên sẽ phải đánh giá a = b, sau đó Một sẽ là 2 và sau đó b= với và kết quả là b sẽ là 3. Kết quả cuối cùng sẽ là a = 2 b = 3 c = 3. Rõ ràng, đây không phải là những gì chúng ta mong đợi khi viết Một= b= Với, và đó là lý do tại sao các toán tử gán và toán tử điều kiện có tính chất liên kết phải.

Công dụng thực tế

Không có gì rắc rối bằng việc tìm ra một lỗi được tạo ra chỉ vì nhà phát triển không biết các quy tắc ưu tiên và liên kết. Tôi đã xem qua các bài đăng trên các hội nghị qua thư nơi những người có vẻ hợp lý đã đề xuất một loại cơ chế tự lập tài liệu - sử dụng dấu cách để chỉ định các toán tử, theo ý kiến ​​của họ, có thâm niên. Ví dụ: vì chúng ta biết rằng toán tử nhân được ưu tiên hơn toán tử cộng, chúng ta nên viết một cái gì đó như thế này trong đó dấu cách cho biết bao hàm thâm niên:

a \ u003d b * c + d;

Cách tiếp cận này về cơ bản là sai: trình biên dịch không thể phân tích cú pháp mã một cách chính xác nếu một cú pháp cụ thể không được xác định. Trình biên dịch phân tích mã theo các quy tắc được xác định bởi các nhà phát triển trình biên dịch. Mặt khác, có dấu ngoặc tròn, được sử dụng để chỉ rõ mức độ ưu tiên và tính liên kết. Ví dụ, biểu thức Một= b * c + d có thể được viết lại thành a \ u003d (b * c) + d hoặc thế nào Một= b * (c + d) và trình biên dịch sẽ đánh giá biểu thức trong ngoặc đơn đầu tiên. Nếu có nhiều cặp dấu ngoặc đơn, trình biên dịch trước tiên sẽ đánh giá các biểu thức trong dấu ngoặc đơn và sau đó là toàn bộ biểu thức, dựa trên các quy tắc về mức độ ưu tiên và tính kết hợp được mô tả.

Tôi thực sự tin rằng dấu ngoặc đơn nên luôn được sử dụng khi có nhiều toán tử trong một biểu thức. Tôi khuyên bạn nên làm điều này ngay cả khi bạn hiểu thứ tự tính toán, bởi vì những người sẽ duy trì mã của bạn có thể không biết chữ.

Toán tử C #

Tốt nhất là xem xét các toán tử theo thứ tự ưu tiên. Dưới đây tôi sẽ mô tả các toán tử phổ biến nhất.

Toán tử đơn giản

  • (x) Đây là một loại toán tử trong ngoặc để kiểm soát thứ tự đánh giá như trong Các hoạt động toán học và khi gọi các phương thức.
  • x.y Toán tử dấu chấm được sử dụng để chỉ định một thành viên của một lớp hoặc cấu trúc. Nơi đây Xđại diện cho thực thể chứa thành viên y.
  • f (x) Loại toán tử dấu ngoặc này được sử dụng để liệt kê các đối số của phương thức.
  • Ồ] Dấu ngoặc vuôngđược sử dụng để lập chỉ mục một mảng. Các dấu ngoặc này cũng được sử dụng cùng với các chỉ mục khi các đối tượng có thể được coi như một mảng. Xem Chương 7 để biết các chỉ mục.
  • x ++ Chúng ta sẽ nói riêng về toán tử tăng trong phần “Toán tử tăng và giảm”.
  • x- Toán tử giảm dần cũng sẽ được thảo luận ở phần sau.
  • new Toán tử này được sử dụng để tạo các thể hiện đối tượng dựa trên định nghĩa lớp.

loại

Sự phản xạ(phản ánh) là khả năng lấy thông tin kiểu trong thời gian chạy. Thông tin này bao gồm tên của các loại, lớp và cấu trúc thành viên. V Nền tảng NET chức năng này được liên kết với lớp Hệ thống. loại. Lớp này là gốc của tất cả các toán tử phản ánh và có thể được lấy bằng cách sử dụng toán tử loại. Chúng tôi sẽ không đi vào chi tiết của sự phản ánh ngay bây giờ (chúng tôi sẽ đi đến điều đó trong Chương 16), nhưng đây là một ví dụ đơn giản minh họa cách dễ dàng sử dụng loại"để có được hầu hết mọi thông tin về một loại hoặc đối tượng trong thời gian chạy:

sử dụng Hệ thống;

sử dụng System.Reflection;

lớp công chúng Apple (

public int nSeeds;

public void Ripen ()

{

> >

lớp công khai TypeOfApp (

public static void Main () (

Typet = typeof (Apple);

string className = t.ToStringO;

Console.IgShipe ("\ nClass 0 Thông tin (O)", className);

Console.WriteLine ("\ nMeroflH (0)", className); bàn điều khiển. WriteLine ("--------"); Các phương thức MethodInfo = t.GetMethodsO;

foreach (Phương thức MethodInfo trong các phương thức)

Console.WriteLine (method.ToString ());

}

Console.WriteLine ("\ nBce Member (O)", className); bàn điều khiển. Writel_ine ("--------"); MemberInfo allMembers = t.GetMembersO; foreach (Thành viên MemberInfo trong allMembers)

{

bàn điều khiển. WriteLine (thành viên.ToStringO);

} > }

Chương trình này chứa lớp Quả táo, chỉ có hai thành viên: trường nSeeds và phương pháp Ripen.Đầu tiên, sử dụng toán tử loại và tên lớp, tôi nhận được đối tượng Hệ thống. loại, sau đó được lưu trữ trong một biến t. VỚI bây giờ tôi có thể sử dụng đối tượng Hệ thống. loạiđể lấy tất cả các phương thức và thành viên của một lớp Quả táo.Điều này được thực hiện bằng các phương pháp GetMethodsGetMembers tương ứng. Kết quả của các phương pháp này được hiển thị trên thiết bị tiêu chuẩnđầu ra như thế này:

Thông tin về Lớp Apple Phương pháp của Apple

Int32 GetHashCodeQ

System.String ToStringQ

Void RipenO

System.Type GetTypeO

Tất cả các thành viên của Apple

Int32 nSeeds

Int32 GetHashCodeO

Boolean Equals (System.Object)

System.StringToStringO

Void RipenO

System.Type GetTypeO

Trước khi tiếp tục, tôi muốn đưa ra hai nhận xét. Đầu tiên, hãy lưu ý rằng các thành viên kế thừa của lớp cũng được suy ra. Vì lớp không được dẫn xuất rõ ràng từ lớp khác, chúng ta biết rằng tất cả các thành viên không được định nghĩa trong lớp quả táođược thừa kế từ một lớp cơ sở ngầm định Hệ thống.Object. Thứ hai, đối tượng Loại hệ thống có thể thu được bằng phương pháp Gettype.Điều này được kế thừa từ System.Object phương thức cho phép bạn làm việc với các đối tượng, không phải các lớp. Có thể sử dụng một trong hai đoạn mã sau để lấy một đối tượng Hệ thống. loại.

II Nhận một đối tượng System.Type dựa trên định nghĩa lớp. Typet1 = typeof (Apple);

// Lấy đối tượng System.Type từ đối tượng. quả táo= AppleQ mới; Gõ t2 = apple.GetTypeO;

kích thước

Nhà điều hành kích thướcđược sử dụng để lấy kích thước của kiểu được chỉ định tính bằng byte. Đồng thời, hãy nhớ riêng hai các yếu tố quan trọng. Trước hết, kích thước chỉ có thể được áp dụng cho các loại thứ nguyên. Do đó, mặc dù nó có thể được sử dụng cho các thành viên trong lớp, nhưng nó không thể được sử dụng cho các lớp như vậy. Thứ hai, kích thước chỉ có thể được sử dụng trong các phương pháp hoặc khối mã được đánh dấu là không an toàn. VỚI chúng ta sẽ thấy loại mã này trong Chương 17. Đây là một ví dụ về cách sử dụng toán tử kích thước trong một phương thức lớp được đánh dấu là không an toàn:

sử dụng Hệ thống;

lớp BasicTypes (

// Lưu ý: Mã sử ​​dụng toán tử sizeof // phải được đánh dấu là không an toàn, không an toàn tĩnh public void ShowSizesQ (

Console.WriteLine ("\ nPa3Mephi các kiểu cơ bản"); Console.WriteLine ("Pa3Mep short = (0)", sizeof (short)); Console.WriteLine ("Pa3Mep int = (0)", sizeof (int)); Console.Writel_ine ("Pa3Mep long = (0)", sizeof (long)); Console.WriteLine ("Pa3Mep bool = (0)", sizeof (bool)); ))

lớp Không an toànUpp

{

không an toàn công cộng tĩnh void Main ()

{

BasicTypes.ShowSizes ();

} }

Đây là kết quả của việc chạy ứng dụng này:

Kích thước các loại cơ bản Size ngắn = 2 kích thước int= 4 kích thước dài = 8 kích thước bool = 1

Nhà điều hành kích thước có thể được sử dụng để kích thước không chỉ các loại tích hợp đơn giản mà còn có thể tùy chỉnh các loại kích thước chẳng hạn như cấu trúc. Tuy nhiên, trong khi kết quả kích thước có thể không rõ ràng:

// Sử dụng toán tử sizeof. sử dụng Hệ thống;

struct StructWithNoMembers

struct StructWithMembers

{

quần short;

int i;

long1;

bool b; )

struct CompositeStruct

{

StructWithNoMembers a; StructWithMembersb;

StructWithNoMembers c; )

classUnSafe2App (

không an toàn công cộng tĩnh void Main () (Console.WriteLine ("\ nPa3Mep StructWithNoMembers cấu trúc = (0)",

sizeof (StructWithNoMembers)); Console.WriteLine ("\ nPa3Mep StructWithMembers cấu trúc = (0)",

sizeof (StructWithMembers)); Console.WriteLine ("\ nPa3Mep Cấu trúc CompositeStruct = (0)",

sizeof (CompositeStruct)); ))

Mặc dù có thể giả định rằng ứng dụng này sẽ xuất ra 0 cho một cấu trúc không có thành viên (StructWithNoMembers), 15 cho một cấu trúc có bốn thành viên loại cơ sở (StructWithMembers) và 15 cho một cấu trúc tổng hợp hai cấu trúc trước đó (cấu trúc tổng hợp), trong thực tế, kết quả sẽ như thế này:

Kích thước cấu trúc StructWithNoMembers = 1 Kích thước cấu trúc StructWithMembers = 16

Kích thước cấu trúc CompositeStruct = 24

Lời giải thích cho điều này là cách cấu trúc được lưu cấu trúc bởi trình biên dịch trong tệp đầu ra, trong đó trình biên dịch áp dụng biện minh và phần đệm. Ví dụ: nếu cấu trúc có kích thước 3 byte và căn chỉnh 4 byte được đặt, trình biên dịch sẽ tự động thêm 1 byte vào cấu trúc và câu lệnh kích thước chỉ ra rằng kích thước của cấu trúc là 4 byte. Hãy ghi nhớ điều này khi định cỡ cấu trúc trong C #.

đã kiểm trakhông được kiểm tra

Hai toán tử này kiểm soát việc kiểm tra tràn khi thực hiện các phép toán.

Toán tử toán học

C #, giống như hầu hết các ngôn ngữ khác, hỗ trợ các toán tử toán học cơ bản: nhân (*), chia (/), cộng (+), trừ (-) và môđun (%). Mục đích của bốn toán tử đầu tiên là rõ ràng từ tên của họ; toán tử modulo tạo thành phần còn lại của phép chia số nguyên. Đây là đoạn mã minh họa việc sử dụng các toán tử toán học:

sử dụng Hệ thống;

lớp MathOpsApp

{

public static void Main ()

{

// Lớp System.Random là một phần của thư viện lớp // .NET Framework. Trong hàm tạo mặc định của nó // phương thức Next sử dụng ngày / giờ hiện tại làm // giá trị ban đầu. Random rand = new RandomO; int a, b, c;

a = rand.Next () % một trăm; // Giá trị giới hạn 99. b = rand.NextO % một trăm; // Giá trị giới hạn 99.

Console.WriteLine ("a = (0) b = (1)", a, b);

c = a * b;

Console.WriteLineC "a * b = (0)", c);

// Lưu ý rằng số nguyên được sử dụng ở đây. // Do đó, nếu a nhỏ hơn b thì kết quả // sẽ luôn là 0. Để có kết quả chính xác hơn // bạn cần sử dụng các biến kiểu double hoặc float, c = a / b; Console.WriteLineC "a / b = (0)", c);

Console.WriteLineC "a + b = (0)", c);

Console.WriteLineC "a - b = (0)", c);

Console.WriteLineC "a X b = (0)", c); >>

Toán tử đơn nguyên

Có hai toán tử một ngôi: cộng và trừ. Toán tử trừ một bậc cho trình biên dịch biết rằng số là số âm. Vì vậy, trong đoạn mã sau Một sẽ bằng -42:

sử dụng Hệ thống; sử dụng Hệ thống;

lớp UnarylApp (

public static void Main ()

{

int a = 0;

a = -42;

Console.WriteLine ("(0)", a); ))

Tuy nhiên, mã này giới thiệu một sự mơ hồ: using System;

lớp Unary2App<

public static void Main () (

int a; int b = 2; int c = 42;

a = b * -c;

Console.WriteLine ("(0)", a); >>

Biểu hiện Một= b * -c không hoàn toàn rõ ràng. Một lần nữa, việc sử dụng dấu ngoặc đơn sẽ làm rõ biểu thức này:

// Khi sử dụng dấu ngoặc đơn, rõ ràng là chúng ta đang nhân b với một số âm c. a \ u003d b * (-c);

Nếu trừ một bậc trả về giá trị toán hạng âm, bạn có thể nghĩ rằng cộng một bậc trả về số dương. Tuy nhiên, cộng một bậc chỉ trả về toán hạng ở dạng ban đầu và không làm gì khác, tức là không ảnh hưởng đến toán hạng. Ví dụ: thực thi mã này sẽ xuất ra giá trị -84:

sử dụng Hệ thống;

classUnarySApp (

public static void MainQ (

a = b * (+ c);

Console.WriteLine ("(0)", a); ))

Để nhận giá trị dương, hãy sử dụng hàm Toán học. Mã này sẽ xuất ra giá trị 84:

sử dụng Hệ thống;

lớp Unary4App

{

public static void Main ()

int a; int b = 2; int c = -42;

a = b * Math.Abs ​​(c); Console.Writel_ine ("(0)", a); ))

Toán tử một ngôi cuối cùng mà tôi đề cập là T (x). Đây là một loại toán tử trong ngoặc cho phép bạn chuyển kiểu này sang kiểu khác. Vì nó có thể bị quá tải bằng cách tạo một biến đổi tùy chỉnh, chúng ta sẽ thảo luận về nó trong Chương 13.

Câu lệnh gán ghép

Toán tử gán ghép - nó là một sự kết hợp toán tử nhị phân và toán tử gán (=). Cú pháp cho các toán tử này là:

điệp khúc = y

ở đâu op - nó là một nhà điều hành. Lưu ý rằng trong trường hợp này, giá trị bên trái (giá trị) không được thay thế bởi quyền (rvalue), toán tử ghép có tác dụng tương tự như:

x = x op tại

giá trịđược sử dụng làm cơ sở cho kết quả của hoạt động.

Lưu ý tôi đã nói "có tác dụng tương tự." Trình biên dịch không dịch một biểu thức như x + = 5 trong X= x + 5, anh ta hành động một cách hợp lý. Cần đặc biệt chú ý đến những trường hợp giá trị là một phương pháp. Hãy xem xét mã:

sử dụng Hệ thống;

lớp CompoundAssignmentlApp (

các phần tử lnt được bảo vệ;

public int GetArrayElementO

{

trả về các phần tử;

}

CompoundAssignment1App () (

các phần tử = new int;

phần tử = 42;

}

public static void Main () (

Ứng dụng CompoundAssignmentlApp = new CompoundAsslgnment1App ();

Console.WrlteLine ("(0>", app.GetArrayElement ());

app.GetArrayElement () = app.GetArrayElement () + 5; Console.WriteLine ("(0)", app.GetArrayElement ()); ). )

Chú ý đến dòng được đánh dấu - cuộc gọi phương thức Compound-AssignmentlApp.GetArrayElement và sau đó thay đổi phần tử đầu tiên - ở đây tôi đã sử dụng cú pháp:

x = x op tại

Đây là mã MSIL sẽ được tạo: // Kỹ thuật không hiệu quả: x = x o y.

Phương thức public hldebyslg static void Main () 11 Managed

Entrypolnt

// Kích thước mã 79 (Ox4f) .maxstack 4

Người dân địa phương (lớp CompoundAssignmentlApp V_0)

IL_0000: phiên bản newobj void CompoundAssignmentlApp ::. Ctor () IL_0005: stloc.O IL_0006: Idstr "(OG IL_OOOb: ldloc.0 ILJJOOc: gọi phiên bản int32

CompoundAssignmentlApp :: GetArrayElementO

IL_0011: ldc.14.0

IL_0012: Idelema ["mscorlib"] Hệ thống.Int32

IL_0017: box [1 mscorlib "] System.Int32

IL_001c: gọi void ["mscorlib 1] System.Console :: WriteLine (class System.String, class System.Object)

IL_0021: ldloc.0

IL_0022: phiên bản gọi int32 CompoundAssignmentlApp :: GetArrayElementO

IL_0027: Idc.i4.0

IL_0028: ldloc.0

IL_0029: phiên bản gọi int32 CompoundAssignmentlApp:: GetArrayElementO

IL_002e: Idc.i4.0

IL_002f: ldelem.14

IL_0030: ldc.14.5

IL_0031: thêm

IL_0032: thép.14

IL_0033: Idstr "(0)"

IL_0038: ldloc.0

IL_0039: gọi

instance int32 CompoundAssignmentlApp :: GetArrayElement () IL_003e: ldc.14.0

IL_003f: Idelema ["mscorlib"] Systera.Int32 IL_0044: box ["msoorlib"] System.Int32 IL_0049: gọi void ["mscorlib"] System.Console :: WriteLine

(class System.String, class System.Object) IL_004e: ret

) // kết thúc của phương thức "CompoundAssignmentlApp :: Main"!

Nhìn vào các dòng được đánh dấu: phương pháp CompoundAssignmentlApp.Get-ArrayElement thực sự được gọi là hai lần! Điều này ít nhất là không hiệu quả và có thể gây bất lợi, tùy thuộc vào những gì phương pháp khác thực hiện.

Bây giờ chúng ta hãy xem xét một đoạn mã khác sử dụng cú pháp gán ghép:

sử dụng Hệ thống;

lớp CompoundAssignment2App (

các phần tử int được bảo vệ;

public int GetArrayElementO

trả về các phần tử;

}

CompoundAssignment2App () (

các phần tử = new int;

phần tử = 42;

}

public static void Main () (

Ứng dụng CompoundAssignment2App = new CompoundAssignment2App ();

Console.WriteLine ("(0)", app.GetArrayElement ());

app.GetArrayElement () + = 5; Console.WriteLine ("(0)", app.GetArrayElement ()); ))

Cách sử dụng toán tử phức hợp nhiệm vụ sẽ dẫn đến mã MSIL hiệu quả hơn nhiều:

// Kỹ thuật hiệu quả hơn: xop = y.

Phương thức public hidebysig static void Main () hoặc Managed

\ {

\ .entrypoint

I // Kích thước mã 76 (Ox4c) \ .maxstack 4

Người dân địa phương (lớp CompoundAssignmentlApp V_0, int32 V_1) \ IL_0000: phiên bản newobj void CompoundAssignmentlApp ::. Ctor () \ IL_0005: stloc.O 1 IL_0006: Idstr "(0)" 1 IL_OOOb: ldloc.0 IL_OOOc: gọi phiên bản int32

CompoundAssignmentlApp :: GetArrayElementO IL_0011: Idc.i4.0

IL_0012: Idelema [mscorlib "] System.Int32 IL_0017: box [> mscorlib -] System.Int32 lL_001c: gọi void [" mscorlib "] System.Console :: WriteLine

(class System.String, class System.Object) IL_0021: ldloc.0 IL_0022: gọi instance int32

CompoundAssignmentlApp :: GetArrayElement ()

IL_0027: trùng lặp

IL_0028: stloc.1

IL_0029: Idc.i4.0

IL_002a: ldloc.1

IL_002b: Idc.i4.0

IL_002c: ldelem.14

IL_002d: ldc.14.5

IL_002e: thêm

IL_002f: thép.14

IL_0030: Idstr "(0)"

IL_0035: ldloc.0

IL_0036: gọi trường hợp int32

CompoundAssignmentlApp :: GetArrayElementO IL_003b: Idc.i4.0

IL_003c: Idelema ["mscorlib"] Hệ thống.Int32

IL_0041: box [mscorlib "] System.Int32

IL_0046: gọi void ["mscorlib"] System.Console :: WriteLine

(class System.String, class System.Object)

IL_004b: ret) // cuối phương thức "CompoundAssignmentlApp :: Main"

Bạn có thể thấy rằng lệnh MSIL đã được sử dụng. trùng lặp. Nó sao chép phần tử trên cùng trên ngăn xếp, do đó tạo một bản sao của giá trị được phương thức trả về Phần tử mảng CompoundAssignmentlApp.Get. tôi

Từ đó có thể thấy rằng mặc dù x + = y tương đương với x = x + y, Mã MSIL khác nhau trong cả hai trường hợp. Sự khác biệt này sẽ khiến bạn băn khoăn không biết nên sử dụng cú pháp nào trong từng trường hợp. Một quy tắc chung và khuyến nghị của tôi là sử dụng các toán tử gán ghép bất cứ khi nào và bất cứ khi nào có thể.

Toán tử tăng và giảm

Được giới thiệu trong C và chuyển sang C ++ và Java, các toán tử giảm và giảm 1 cho phép bạn diễn đạt ngắn gọn rằng bạn muốn tăng hoặc giảm một giá trị số đi 1. Nghĩa là, / ++ tương đương với việc thêm 1 vào giá trị hiện tại / ".

Sự tồn tại của hai dạng toán tử tăng và giảm đôi khi gây nhầm lẫn. tiếp đầu ngữhậu tố các loại toán tử này khác nhau tại thời điểm mà giá trị được thay đổi. Trong phiên bản tiền tố của toán tử tăng và giảm (++ ai - a tương ứng) hoạt động được thực hiện đầu tiên và sau đó giá trị được tạo. Trong phiên bản postfix (a ++Một-)đầu tiên giá trị được tạo và sau đó hoạt động được thực hiện. Hãy xem xét một ví dụ:

sử dụng Hệ thống;

lớp IncDecApp (

public static void Foo (int j)

{

Console.WriteLine ("IncDecApp.Foo j = (0)", j);

>

public static void Main () (

int i = 1;

Console.WriteLineC "flo gọi Foo (i ++) = (0)", i);

Console.WriteLine ("Sau khi goi Foo (i ++) = (0)", i);

Console.WriteLine ("\ n");

\ Console.WriteLineC "flo gọi Foo (++ i) = (0)", i);

\ foo (++ l);

\ Console.WrlteLine ("Sau khi gọi Foo (++ i) = (0)", i);

l Kết quả của việc thực hiện sẽ như sau:

Trước khi gọi Foo (i ++) = 1

IncDecApp.Foo j = 1

Sau khi gọi Foo (i ++) = 2

Trước khi gọi Foo (-n-i) = 2

IncDecApp.Foo j = 3

Sau khi gọi Foo (++ i) = 3

Sự khác biệt là khi nào giá trị được tạo và toán hạng được sửa đổi. Khi được gọi foo (i ++) giá trị / "được chuyển (không thay đổi) cho phương thức foosau trả về từ phương thức / được tăng thêm 1. Nhìn vào mã MSIL sau: lệnh cộngđược thực thi sau khi đẩy giá trị vào ngăn xếp.

IL.0013: ldloc.0

IL.0014: trùng lặp

IL_0015: Idc.i4.1

IL_0016: thêm

IL_0017: stloc.O

IL_0018: gọi void IncDecApp :: Foo (int32)

Bây giờ hãy nhìn vào dạng tiền tố của nhà điều hành được sử dụng trong cuộc gọi foo (++ a). Trong trường hợp này, mã MSIL sẽ khác. Đồng thời, đội cộng thực hiện trước cách giá trị được đẩy vào ngăn xếp để gọi phương thức sau này foo.

IL.0049: ldloc.0

IL_004a: Idc.i4.1

IL_004b: thêm

IL_004c: trùng lặp

IL_004d: stloc.O

IL_004e: gọi void IncDecApp :: Foo (int32)

Toán tử quan hệ

Hầu hết các toán tử trả về giá trị số. Đối với các toán tử quan hệ, chúng tạo ra một kết quả boolean. Thay vì / thực hiện các phép toán trên một tập hợp các toán hạng, / các toán tử quan hệ phân tích mối quan hệ giữa các toán hạng và trở lạiÝ nghĩa đúng vậy, nếu tỷ lệ là đúng, sai- nếu / là sai.

Toán tử so sánh

Các toán tử quan hệ được gọi là Toán tử so sánh, tham khảo) -sya "ít hơn" (<), «меньше или равно» (<=), «больше» (>), lớn hơn hoặc bằng (> =), bằng (==) và không bằng (! =). Ứng dụng của các toán tử này đối với các con số là rõ ràng, nhưng khi được sử dụng với các đối tượng, việc triển khai chúng không quá rõ ràng. Đây là một ví dụ:

sử dụng Hệ thống;

lớp NumericTest (

public NumericTest (int 1)

{

this.i = i;

>

bảo vệ int 1; )

class RelationalOpslApp (

public static void Main () (

NumericTest testl = new NumericTest (42); NumericTest test2 = new NumericTest (42);

Console.WriteLine ("(0)", testl == test2); >)

Nếu bạn là một lập trình viên Java, bạn biết điều gì sẽ xảy ra ở đây. Tuy nhiên, các lập trình viên C ++ có lẽ sẽ ngạc nhiên khi thấy kết quả sai.Để tôi nhắc bạn: bằng cách tạo một thể hiện của một đối tượng, bạn sẽ nhận được một tham chiếu đến nó. Điều này có nghĩa là khi gặp một toán tử quan hệ so sánh hai đối tượng, trình biên dịch C # sẽ không so sánh nội dung của các đối tượng mà là địa chỉ của chúng. Để hiểu rõ hơn về điều này, hãy xem xét mã MSIL:

\ .method public hldebysig static void MainQ hoặc được quản lý

\ .entrypoint

\ // Kích thước mã 39 (0x27)

1. maxstack 3

Người dân địa phương (lớp NumericTest V_0, \ lớp NumericTest V_1, 1 bool V_2)

ILJJOOO: Idc.i4.s 42

1L_0002: phiên bản newobj void NumericTest ::. Ctor (int32)

IL_0007: stloc.O

IL_0008: Idc.i4.s 42

IL_OOOa: phiên bản newobj void NumericTest ::. Ctor (int32)

IL_OOOf: stloc.1

IL_0010: Idstr "(0)"

IL_0015: ldloc.0

IL_0016: ldloc.1

IL_0017: eeq

IL_0019: stloc.2

IL_001a: Idloca.s V_2

IL_001c: box ["mscorlib"] System.Boolean

IL_0021: gọi void ["mscorlib"] System.Console :: WriteLine

(class System.String, class System.Object)

IL_0026: ret) // cuối phương thức "RelationalOpslApp :: Main"

Nhìn vào dòng . địa phương. Trình biên dịch chỉ định rằng phương thức Chủ yếu ba biến cục bộ. Hai đối tượng đầu tiên là NumericTest, một phần ba là một biến boolean. Bây giờ chúng ta hãy chuyển sang các dòng IL_0002IL_0007.Đây là nơi một đối tượng được khởi tạo testl, và liên kết với nó bằng stlocđược lưu trữ trong biến cục bộ đầu tiên. Điều quan trọng là MSIL phải lưu địa chỉ của đối tượng mới được tạo. Sau đó, trong các dòng IL_OOOaIL_OOOf bạn thấy mã MSIL để tạo đối tượng test2 và lưu trữ tham chiếu được trả về trong một biến cục bộ thứ hai. Cuối cùng, trong các dòng 1LJ-015IL_0016 các biến cục bộ được đẩy vào ngăn xếp bằng lệnh IDloc, và trong hàng IL_0017đội xám so sánh hai giá trị ở đầu ngăn xếp (tức là tham chiếu đối tượng testltestl). Giá trị trả về được lưu trữ trong một biến cục bộ thứ ba và sau đó xuất ra bằng phương thức System.Console. writeline.

Nhưng làm thế nào để bạn so sánh các thành viên của hai đối tượng? Câu trả lời là sử dụng lớp cơ sở ngầm định của tất cả các đối tượng .NET Framework. Cụ thể, cho những mục đích này, lớp System.Object có một phương pháp Bằng nhau. Ví dụ: mã / sau so sánh nội dung của các đối tượng và kết quả đầu ra, như / bạn mong đợi, đúng: tôi

sử dụng Hệ thống; /

class RelationalOps2App

(/ public static void Main () (

Console.WriteLine ("(0)", testl.Equals (test2));

Ví dụ RelationalOpslApp sử dụng lớp "tự tạo" (Nu-mericTest) và trong ví dụ thứ hai - lớp .NET (Số thập phân). Vấn đề là phương pháp System.Object.Equals phải được ghi đè để thực hiện so sánh thành viên thực tế. Do đó, phương pháp Bằng với lớp NumericTest sẽ không hoạt động vì chúng tôi chưa ghi đè phương thức. Và đây là lớp học Số thập phân ghi đè phương thức nó kế thừa bằng, và trong trường hợp này mọi thứ sẽ hoạt động.

Một cách khác để so sánh các đối tượng đang sử dụng quá tải nhà điều hành(nạp chồng toán tử). Nạp chồng toán tử xác định các hoạt động sẽ được thực hiện trên các đối tượng của một kiểu cụ thể. Ví dụ, đối với các đối tượng chuỗi Toán tử + không thực hiện phép cộng, nhưng nối các chuỗi. Chúng tôi sẽ đề cập đến quá tải toán tử trong Chương 13.

Các toán tử chuyển nhượng đơn giản

Giá trị ở bên trái của một câu lệnh gán được gọi là giá trị, và ở phía bên phải - giá trị. Như giá trị có thể là bất kỳ hằng số, biến, số hoặc biểu thức nào có kết quả tương thích với giá trị. Trong khi đó giá trị phải là một biến của một kiểu nhất định. Vấn đề là giá trị được sao chép từ phía bên phải sang phía bên trái. Do đó, một không gian địa chỉ vật lý phải được cấp phát cho giá trị mới. Ví dụ, bạn có thể viết / "= 4, bởi vì / có một vị trí trong bộ nhớ - trên ngăn xếp hoặc trên đống - tùy thuộc vào loại /. Và đây là nhà điều hành 4 = 1 không thể được thực thi vì 4 là một giá trị, không phải là một biến có thể thay đổi nội dung trong bộ nhớ. Nhân tiện, tôi lưu ý rằng trong C # như giá trị có thể là một biến, một thuộc tính hoặc một chỉ mục. Xem Chương 7 để biết thêm về các thuộc tính và chỉ mục Trong chương này, tôi sử dụng các biến cho đơn giản. Nếu mọi thứ đủ rõ ràng với việc gán các giá trị số, thì mọi thứ phức tạp hơn với các đối tượng. Xin nhắc lại, khi bạn đang xử lý các đối tượng, bạn không phải thao tác các mục ngăn xếp, vốn rất dễ sao chép và di chuyển. Trong trường hợp các đối tượng, bạn thực sự chỉ có tham chiếu đến một số thực thể được cấp phát động bộ nhớ. Do đó, khi bạn cố gắng gán một đối tượng (hoặc bất kỳ kiểu tham chiếu nào) cho một biến, nó không phải là dữ liệu được sao chép, như trường hợp với các kiểu giá trị, mà là các tham chiếu.

Giả sử bạn có hai đối tượng: testlthử nghiệm2. Nếu bạn chỉ định testl= test2, testl sẽ không phải là một bản sao thử nghiệm2. Họ sẽ phù hợp! Một đối tượng testl trỏ đến cùng một bộ nhớ như test2, và bất kỳ thay đổi nào đối với đối tượng testl dẫn đến thay đổi thử nghiệm2.Đây là một chương trình minh họa điều này:

sử dụng Hệ thống;

lớp Foo (

công khai int i; )

lớp RefTestlApp (

public static void MainO (

Foo testl = new Foo (); testl.i = 1;

Foo test2 = new Foo (); test2.i = 2;

Console.WriteLine ("Trước khi các đối tượng được gán"); Console.WriteLine ("test1.i = (0>", testl.i); Console.WriteLine ("test2.i = (0)", test2.i); Console.WriteLine ("\ n");

testl = test2;

Console.Writel_ine ("Sau khi gán đối tượng");

Console.WriteLine ("test1.i = (0)", testl.i); Console.WriteLine ("test2.i = (0)", test2.i); Console.WriteLine ("\ n");

testl.i = 42; ; "

Console.WriteLine ("Sau khi thay doi duy nhat TEST1"); Console.WriteLine ("test1.i = (0)", testl.i); Console.WriteLine ("test2.i = (0)", test2.i); Console.WriteLine ("\ n"); ))

Khi bạn chạy mã này, bạn sẽ thấy:

Trước khi gán đối tượng

test1.i = 1

test2.i = 2

Sau khi gán một đối tượng

testt.i = 2

test2.i = 2

Sau khi chỉ thay đổi thành viên TEST1

test1.i = 42

test2.i = 42

Hãy xem điều gì xảy ra ở mỗi giai đoạn của quá trình thực hiện ví dụ này. foo- nó là một lớp k đơn giản với một thành viên duy nhất, /. Hai phiên bản của lớp này được tạo trong phương thức Main: testltest2- và các thành viên của họ tôiđược đặt thành 1 và 2 tương ứng. Các giá trị này sau đó được xuất ra và như mong đợi, testl.i bằng 1, a test2.i- 2. Và đây niềm vui bắt đầu! V hàng tiếp theo sự vật testl giao thử nghiệm2. Người đọc lập trình Java biết điều gì tiếp theo. Tuy nhiên, hầu hết các lập trình viên C ++ sẽ mong đợi một thành viên / đối tượng testl bây giờ bằng một thành viên đối tượng test2(giả sử rằng khi biên dịch một ứng dụng như vậy, một số loại toán tử sao chép thành viên đối tượng sẽ được thực thi). Kết quả đầu ra dường như xác nhận điều này. Tuy nhiên, trên thực tế, mối liên hệ giữa các đối tượng giờ đây đã sâu sắc hơn rất nhiều. Hãy gán giá trị 42 cho thuật ngữ testl.i và xuất lại kết quả. VÀ?! Khi thay đổi một đối tượng testlđã thay đổi và testZĐiều này xảy ra bởi vì đối tượng testl không còn nữa. Sau khi giao cho anh ta test2 một đối tượng testl bị mất bởi vì ứng dụng không còn tham chiếu đến nó nữa và kết quả là nó được "dọn dẹp" bởi bộ thu gom rác (bộ thu gom rác, GC). Bây giờ testltest2 trỏ đến cùng một bộ nhớ trên heap. Do đó, khi một biến thay đổi, người dùng sẽ thấy sự thay đổi trong biến kia.

Hãy chú ý đến hai dòng đầu ra cuối cùng: mặc dù chỉ có giá trị được thay đổi trong mã testl.i,Ý nghĩa test2.i cũng đã thay đổi. Một lần nữa, cả hai biến bây giờ đều trỏ đến cùng một vị trí bộ nhớ, đó là hành vi mà các lập trình viên Java mong đợi. Tuy nhiên, điều này hoàn toàn không đáp ứng được mong đợi của các nhà phát triển C ++, vì trong ngôn ngữ này, việc sao chép các đối tượng được thực hiện: mỗi biến có bản sao duy nhất của riêng nó của các thành viên và việc thay đổi đối tượng này không ảnh hưởng đến đối tượng kia. Vì đây là chìa khóa để hiểu cách các đối tượng hoạt động trong C #, chúng ta hãy lạc đề nhỏ và xem điều gì sẽ xảy ra khi bạn truyền một đối tượng vào một phương thức:

sử dụng Hệ thống;

lớp Foo (

công khai int i; )

lớp RefTest2App (

public void ChangeValue (Foo f)

{

f.i = 42;

}

public static void Main () (

Ứng dụng RefTest2App = new RefTest2App ();

Kiểm tra Foo = new Foo (); thi.i = 6;

Console.WriteLine ("Trước khi gọi phương thức"); Console.WriteLine ("test.i = (0)", test.i); Console.WriteLine ("\ n");

app.ChangeValue (thử nghiệm);

Console.WriteLine ("Sau khi gọi phương thức"); Console.WriteLine ("test.i = (0)", test.i); Console.WriteLine ("\ n"); >)

Trong hầu hết các ngôn ngữ ngoại trừ Java, mã này sẽ sao chép đối tượng đã tạo kiểm tra trong ngăn xếp cục bộ phương thức RefTest2App.ChangeValue. Trong trường hợp này, đối tượng kiểm tra,được tạo trong phương pháp chủ yếu, sẽ không bao giờ thấy các thay đổi đối với đối tượng / được thực hiện trong phương thức Thay đổi giá trị. Tuy nhiên, tôi nhắc lại rằng phương pháp Chủ yếu chuyển một tham chiếu đến một đối tượng được phân bổ theo heap kiểm tra. Khi phương pháp Thay đổi giá trị thao tác với biến cục bộ của nó // nó cũng thao tác trực tiếp với đối tượng kiểm tra phương pháp chủ yếu.

Tổng hợp

Điều chính trong bất kỳ ngôn ngữ lập trình nào là cách thực hiện phép gán, phép toán, logic và quan hệ - mọi thứ cần thiết cho công việc. ứng dụng thực tế. Trong mã, các hoạt động này được đại diện bởi các toán tử. Các yếu tố ảnh hưởng đến việc thực hiện các câu lệnh bao gồm mức độ ưu tiên và tính liên kết (trái và phải) của các câu lệnh. Tập hợp các toán tử được xác định trước mạnh mẽ trong C # có thể được mở rộng với các triển khai, đã xác định người dùng mà chúng ta sẽ thảo luận trong Chương 13.

Không có chuyển đổi boolean ngầm nào trong C #, ngay cả đối với các kiểu số học nguyên. Do đó, một mục hoàn toàn chính xác trong ngôn ngữ C ++ là:

intk1 = 7;
nếu như (k1) Bảng điều khiển. Viết dòng(" Vâng!");

bất hợp pháp trong các chương trình C #. Lỗi sẽ xảy ra trong bước dịch vì điều kiện được đánh giá thuộc loại int, và chuyển đổi ngầm từ loại này sang loại bool còn thiếu.

Trong C #, các quy tắc chặt chẽ hơn cũng áp dụng cho các phép toán logic. Có, ghi lại nếu như(k1 && (x> y)), đúng trong C ++, dẫn đến lỗi trong

Các chương trình C # vì toán tử && chỉ được định nghĩa cho các toán hạng kiểu bool, và trong biểu thức này, một trong các toán hạng có kiểu int. Trong ngôn ngữ C #, trong những tình huống này, bạn nên sử dụng ký hiệu:

nếu như(k1>0)
nếu như((k1>0) && (x> y))

Các phép toán logic được chia thành hai loại: một số được thực hiện trên các giá trị boolean của các toán hạng, một số khác thực hiện một phép toán logic trên các bit của toán hạng. Vì lý do này, có hai phủ định một ngôi trong C # - phủ định logic, được đưa ra bởi phép toán "!" và phủ định theo bit, được đưa ra bởi phép toán "~". Cái đầu tiên được xác định trên một toán hạng kiểu bool, thứ hai - trên một toán hạng của một kiểu số nguyên, bắt đầu từ kiểu int và cao hơn (int, uint, Dài, dài). Kết quả của phép toán trong trường hợp thứ hai là một toán hạng trong đó mỗi bit được thay thế bằng phần bù của nó. Đây là một ví dụ:

/// < bản tóm tắt>
/// Biểu thức Boolean
/// bản tóm tắt>
công cộngvô hiệuHợp lý() {
// các phép toán phủ định ~,!
bool b1, b2;
b1= 2*2 == 4;
b2= ! b1;
// b2 = ~ b1;
uint j1= 7, j2;
j2= ~ j1;
// j2= ! j1;
int j4= 7, j5;
j5= ~ j4;
Console.WriteLine ("uint j2= " + j2+ " int j5= " + j5);
} // Hợp lý

Đoạn này đã bình luận ra những câu dẫn đến sai sót. Trong trường hợp đầu tiên, một nỗ lực đã được thực hiện để áp dụng phép toán phủ định bit cho một biểu thức kiểu bool, trong lần thứ hai, phủ định logic được áp dụng cho dữ liệu số nguyên. Cả hai đều bất hợp pháp trong C #. Lưu ý cách giải thích khác nhau của phủ định bit cho các kiểu số nguyên không dấu và có dấu. Đối với các biến j5 j2 chuỗi bit cho giá trị giống nhau nhưng được diễn giải khác nhau. Đầu ra có liên quan là:

uintj2 = 4294967288
intj5 = -8.

Phép toán logic nhị phân " && - có điều kiện AND "và" || - OR có điều kiện "chỉ được xác định trên kiểu dữ liệu bool. Các phép toán được gọi là có điều kiện hoặc ngắn gọn vì việc đánh giá toán hạng thứ hai phụ thuộc vào giá trị của toán hạng đầu tiên đã được đánh giá. Giá trị của các phép toán Boolean có điều kiện nằm ở hiệu quả của chúng trong thời gian thực thi. Thường thì chúng cho phép bạn tính toán biểu thức boolean A có lý, nhưng ở đó toán hạng thứ hai không được xác định. Hãy lấy ví dụ về bài toán cổ điển tìm kiếm theo một mẫu trong một mảng, khi một phần tử có giá trị cho trước (mẫu) được tìm kiếm. Có thể có hoặc không có một phần tử như vậy trong mảng. Dưới đây là một giải pháp điển hình cho vấn đề này ở dạng đơn giản hóa, nhưng truyền tải được bản chất của vấn đề:

// Có điều kiện- &&
int [] ar= { 1, 2, 3 };
int search= 7;
int i= 0;
trong khi tôi< ar.Length)&& (ar [i]!= Tìm kiếm)){
i ++;
}
nếu tôi< ar.Length)Console.WriteLine ("Vật mẫuthành lập");
khácConsole.WriteLine ("Vật mẫukhông phảithành lập");

Nếu giá trị của biến Tìm kiếm(mẫu) không khớp với bất kỳ giá trị phần tử mảng nào ar, sau đó là thử nghiệm cuối cùng của điều kiện vòng lặp trong khi sẽ được thực thi với giá trị tôi, tương đương với ar. Chiều dài. Trong trường hợp này, toán hạng đầu tiên sẽ nhận giá trị sai, và mặc dù toán hạng thứ hai không được xác định, vòng lặp sẽ thoát bình thường. Toán hạng thứ hai không được xác định trong kiểm tra cuối cùng, bởi vì chỉ mục phần tử mảng nằm ngoài phạm vi (trong C #, lập chỉ mục phần tử bắt đầu từ 0).

Ba nhị phân hoạt động bitwise- «& - VÀ», «| - HOẶC "," ^ - XOR "được sử dụng theo hai cách. Chúng được định nghĩa là các kiểu số nguyên ở trên int, và trên các kiểu Boolean. Trong trường hợp đầu tiên, chúng được sử dụng như các phép toán bit, trong trường hợp thứ hai chúng được sử dụng như các phép toán logic thông thường. Đôi khi điều cần thiết là cả hai toán hạng đều được đánh giá, khi đó các phép toán này là không thể thiếu. Đây là một ví dụ về lần sử dụng đầu tiên của chúng:

// Các phép toán theo bit logic, Hoặc là, XOR(&,|,^)
int k2= 7, k3= 5, k4, k5, k6;
k4= k2& k3;
k5= k2 | k3;
k6= k2 ^k3;
Console.WriteLine ("k4= " + k4+ " k5= " + k5+ " k6= " + k6);

Kết quả đầu ra:

k4 = 5 k5 = 7 k6 =2

Đây là một ví dụ về đối sánh mẫu bằng cách sử dụng hợp lý AND: tôi= 0;

Tìm kiếm= ar;
trong khi tôi< ar.Length)& (ar [i]!= tìm kiếm)) i ++;
nếu tôi< ar.Length)Console.WriteLine ("Vật mẫuthành lập");
khác cConsole.WriteLine ("Vật mẫukhông phảithành lập");

Đoạn mã này được đảm bảo có một mẫu tra cứu trong mảng và đoạn mã sẽ chạy thành công. Trong các trường hợp tương tự khi mảng không chứa một phần tử Tìm kiếm, một ngoại lệ sẽ được ném ra. Ý nghĩa có ý nghĩa của một thủ tục như vậy - sự xuất hiện của một ngoại lệ - có thể là một dấu hiệu của lỗi trong dữ liệu, yêu cầu xử lý tình huống đặc biệt.

Cập nhật lần cuối: 19/06/2017

C # sử dụng hầu hết các hoạt động được sử dụng trong các ngôn ngữ lập trình khác. Hoạt động đại diện hành động nhất định trên toán hạng - những người tham gia hoạt động. Toán hạng có thể là một biến hoặc một số giá trị (ví dụ: một số). Các phép toán là một bậc (được thực hiện trên một toán hạng), nhị phân - trên hai toán hạng và bậc ba - được thực hiện trên ba toán hạng. Xem xét tất cả các loại hoạt động.

Phép toán số học nhị phân:

    Phép toán cộng hai số:

    int x = 10; int z = x + 12; // 22

    Phép toán trừ hai số:

    int x = 10; int z = x - 6; // 4

    Phép nhân hai số:

    int x = 10; int z = x * 5; // 50

    phép chia hai số:

    int x = 10; int z = x / 5; // 2 kép a = 10; nhân đôi b = 3; nhân đôi c = a / b; // 3,33333333

    Khi chia, hãy nhớ rằng nếu cả hai toán hạng đại diện cho số nguyên, thì kết quả cũng sẽ được làm tròn thành số nguyên:

    Đôi z = 10/4; // kết quả là 2

    Mặc dù kết quả của hoạt động cuối cùng được đặt trong loại biến gấp đôi, cho phép bạn tiết kiệm phần phân đoạn, nhưng bản thân hoạt động liên quan đến hai ký tự, theo mặc định được coi là đối tượng int, nghĩa là, số nguyên và kết quả cũng sẽ là số nguyên.

    Để thoát khỏi tình huống này, cần phải xác định các ký tự hoặc biến liên quan đến hoạt động, chính xác như loại kép hoặc nổi:

    Đôi z = 10,0 / 4,0; // kết quả là 2,5

    Phép toán lấy phần dư của một phép chia số nguyên cho hai số:

    nhân đôi x = 10,0; kép z = x% 4,0; // kết quả là 2

Ngoài ra còn có một số phép toán đơn phân trong đó một toán hạng tham gia:

    hoạt động gia tăng

    Số tăng có thể được đặt trước: ++ x - đầu tiên giá trị của x được tăng thêm 1 và sau đó giá trị của nó được trả về là kết quả của phép toán.

    Và cũng tồn tại tăng tiền tố: x ++ - đầu tiên, giá trị của biến x được trả về là kết quả của phép toán, và sau đó 1 được thêm vào đó.

int x1 = 5; intz1 = ++ x1; // z1 = 6; x1 = 6 Console.WriteLine ($ "(x1) - (z1)"); int x2 = 5; intz2 = x2 ++; // z2 = 5; x2 = 6 Console.WriteLine ($ "(x2) - (z2)");

Thao tác giảm hoặc giảm một giá trị. Ngoài ra còn có dạng tiền tố của sự giảm dần (--x) và dạng tiền tố hậu (x--).

int x1 = 5; intz1 = --x1; // z1 = 4; x1 = 4 Console.WriteLine ($ "(x1) - (z1)"); int x2 = 5; intz2 = x2--; // z2 = 5; x2 = 4 Console.WriteLine ($ "(x2) - (z2)");

Khi thực hiện một số phép tính số học cùng một lúc, cần tính đến thứ tự thực hiện của chúng. Mức độ ưu tiên của các thao tác từ cao nhất đến thấp nhất:

    Tăng giảm

    Nhân, chia, dư

    Phép cộng, phép trừ

Dấu ngoặc đơn được sử dụng để thay đổi thứ tự của các hoạt động.

Hãy xem xét một tập hợp các hoạt động:

Int a = 3; int b = 5; int c = 40; int d = c --- b * a; // a = 3 b = 5 c = 39 d = 25 Console.WriteLine ($ "a = (a) b = (b) c = (c) d = (d)");

Ở đây chúng ta đang xử lý ba phép toán: giảm, trừ và nhân. Đầu tiên, biến c giảm dần, sau đó là phép nhân b * a, và cuối cùng là phép trừ. Trên thực tế, tập hợp các thao tác trông như thế này:

Int d = (c -) - (b * a);

Nhưng với dấu ngoặc đơn, chúng ta có thể thay đổi thứ tự của các phép toán, chẳng hạn như sau:

Int a = 3; int b = 5; int c = 40; int d = (c - (- b)) * a; // a = 3 b = 4 c = 40 d = 108 Console.WriteLine ($ "a = (a) b = (b) c = (c) d = (d)");

Sự liên kết của nhà điều hành

Như đã lưu ý ở trên, các phép toán nhân và chia có cùng mức độ ưu tiên, nhưng kết quả sau đó sẽ là gì trong biểu thức:

int x = 10/5 * 2;

Chúng ta nên giải thích biểu thức này là (10/5) * 2 hay là 10 / (5 * 2)? Rốt cuộc, tùy theo cách diễn giải, chúng ta nhận được những kết quả khác nhau.

Khi các phép toán có cùng mức độ ưu tiên, thứ tự đánh giá được xác định bởi tính liên kết của các toán tử. Tùy thuộc vào tính kết hợp, có hai loại toán tử:

    Các toán tử kết hợp trái thực thi từ trái sang phải

    Các toán tử liên kết phải thực thi từ phải sang trái

Mọi thứ toán tử số học(ngoại trừ tăng và giảm tiền tố) là liên kết trái, nghĩa là chúng được thực thi từ trái sang phải. Do đó, biểu thức 10/5 * 2 phải được hiểu là (10/5) * 2, nghĩa là, kết quả sẽ là 4.