Bài 2: Biến, biểu thức, toán tử, hàm số ngôn ngữ lập trình C(p2).

09:06
Lượt xem:  lần
Trong phần 1 của bài 2, bạn đã biết các khái niệm biến số, hàm số và ý nghĩa của chúng. Trong bài này chúng ta sẽ tìm hiểu sơ qua về toán tử và biểu thức.
4.Thế nào là toán tử:
Toán tử (operator) là một thao tác trên các toán hạng (operands). Ví dụ, phép cộng là một toán tử (toán tử cộng), các số hạng trong phép cộng đó gọi là toán hạng, kết quả của phép toán đó là tổng của 2 số hạng.
Có thể coi toán tử giống như hàm số và toán hạng giống như tham số của hàm. Ví dụ, trong phép cộng 1 + 2, các số 1, 2 là toán hạng, khi coi phép cộng giống như hàm số thì có thể viết +(1, 2); khi đó, 1, 2 là tham số của hàm và tổng chính là giá trị trả về (return value) của hàm.
Như vậy toán tử có ý nghĩa giống hàm số, chỉ có điều cách viết trong chương trình khác với cách viết khi gọi hàm, tùy thuộc vào ngôi và vị trí của toán tử. Số toán hạng của toán tử gọi là ngôi của toán tử đó (ví dụ phép cộng là 1 toán tử 2 ngôi, vì có 2 tham số: 5 + 6). Vị trí của toán tử có thể là ở phía trước của toán hạng (prefix), ở phía sau (postfix) hay ở giữa (infix). Ví dụ, toán tử cộng là một toán tử infix. Toán tử lấy số đối của một số là toán tử prefix (ví dụ -5.3).
5.Độ ưu tiên và tính kết hợp của toán tử:

Độ ưu tiên và tính kết hợp của toán tử (operator precedence and associativity) thể hiện thứ tự thực hiện toán tử khi có nhiều toán tử đứng cùng nhau. Ví dụ, bạn luôn nhớ quy tắc "nếu không có dấu ngoặc thì nhân chia làm trước cộng trừ làm sau". Điều đó có nghĩa là phép nhân có độ ưu tiên lớn hơn phép cộng. Vì thế, phép toán 1 + 2 * 3 sẽ được hiểu là 1 + (2 * 3) = 7, chứ không phải là (1 + 2) * 3 = 9.
Hơn nữa, khi các toán tử có cùng độ ưu tiên đứng cùng nhau (ví dụ toán tử cộng (+) và toán tử trừ (-) là 2 toán tử có cùng độ ưu tiên), ta cần quy định thứ tự kết hợp của chúng để tránh hiểu nhầm (thực hiện nhầm thứ tự của phép tính). Thứ tự kết hợp đó gọi là associativity. Ví dụ, trong biểu thức 5 - 1 + 2, bạn biết rằng cần phải thực hiện các phép tính từ trái sang phải, nếu không sẽ dẫn đến kết quả sai (nếu thực hiện phép 1 + 2 trước, bạn sẽ có kết quả là 5 - (1 + 2) = 2, trong khi 5 - 1 + 2 = 6). Ta nói, tính kết hợp của phép cộng và trừ là từ trái qua phải. Ngược lại, trong biểu thức 223, bạn cần phải thực hiện phép lũy thừa từ phải qua trái (kết quả là 28, không phải là 43).
Các ngôn ngữ lập trình luôn quy định các quy tắc ưu tiên và tính kết hợp rất chặt chẽ để tránh hiểu nhầm khi viết nhiều toán tử cạnh nhau thành biểu thức.
Bạn sẽ biết danh sách độ ưu tiên của các toán tử ở cuối bài này.
6.Giới thiệu một số toán tử của ngôn ngữ C:
6.1. Các toán tử cộng, trừ, nhân, chia, lấy phần dư:
Trong ngôn ngữ C, các phép tính cộng trừ nhân chia mang ý nghĩa bình thường như trong Toán học:
int x, k;
double y, z;
x = 5;
y = 6;
k = x + 1; // k = 6
z = x + 1; // z = 6.0
z = x + 1 / 2; // z = 5.0, because 1/2 means 1/2 in integer (i.e, 0)
z = x + 1.0 / 2 // z = 5.5
k = x / 2; // k = 2, because x is integer and 2 is also integer
k = x % 2; // modulo
Bạn cần chú ý phép chia: khi cả 2 toán hạng đều là số nguyên thì kết quả của phép chia sẽ là số nguyên (int). Vì thế, 7 / 2 = 3 (chứ không phải 3.5).

6.2. Toán tử sizeof:
Toán tử sizeof là một toán tử prefix, giá trị trả về của nó là cỡ của biến số (hoặc kiểu biến) dưới đơn vị byte. Ví dụ:
// This will output 4, 8, 4, 8 as size of x, y, z and the type "double"
int x;
double y;
float z;
printf( "The size of x is: %d bytes\n", sizeof(x) );
printf( "The size of y is: %d bytes\n", sizeof(y) );
printf( "The size of z is: %d bytes\n", sizeof(z) );
printf( "The size of double is: %d bytes\n", sizeof(double) );
Chú ý: mặc dù cách viết giống hệt gọi hàm, nhưng "sizeof" là một toán tử của C, không phải là một hàm số. Bạn sẽ nghĩ về lý do của việc này trong phần bài tập.

6.3. Toán tử gán:
Toán tử gán (assignment) là một toán tử infix. Nó có tác dụng như sau: lấy giá trị của biểu thức phía bên phải gán cho một địa chỉ bộ nhớ phía bên trái.
Ví dụ: x = 5 + 7; là một lệnh gán. Giá trị phía bên phải sau khi được tính toán (12) sẽ được gán cho biến x, tức là ghi giá trị 12 vào ô nhớ tương ứng với biến x trong bộ nhớ. (hãy nhớ lại ý nghĩa trừu tượng của biến số ở phần trước để hiểu tại sao lại là "ghi giá trị 12 vào bộ nhớ", chú ý: để đơn giản ta tạm coi thanh ghi (register) cũng là một dạng của bộ nhớ).

Vì phép gán là một toán tử nên nó cũng có giá trị trả về (return value) như mọi toán tử bình thường khác. Giá trị trả về của toán tử gán chính là giá trị được gán (trong ví dụ trên là 12).
Vì thế, bạn có thể viết phép toán sau đây và trình biên dịch C sẽ coi là hợp lệ:
// This will output "x = 5, y = -5, z = -5"
int x, y, z;
x = 5;
y = z = -x;
printf( "x = %d, y = %d, z = %d\n", x, y, z );
Như bạn đã thấy, biểu thức "y = z = -x;" là hợp lệ bởi vì phép gán có tính kết hợp phải (thực hiện từ phải qua trái) và phép gán z = -x có giá trị trả về là -x. Như vậy, phép gán "y = z = -x" sẽ được hiểu như sau:
y = assign( z, -x );
trong đó, phép gán z = -x tạm thời được viết về kiểu hàm số (một hàm số giả tạo có tên là assign (bạn sẽ không viết được hàm số này)) cho dễ nhìn.
Biểu thức ở phía bên trái của phép gán phải có một dạng đặc biệt (không giống biểu thức phía bên phải) để biểu thị bộ nhớ (hoặc thanh ghi) ứng với nó. Biểu thức như thế gọi là "Lvalue" (hay left-value, tức là value ở bên trái). Hiện nay bạn mới chỉ biết một loại Lvalue, đó chính là biến số.
Các giá trị số nguyên không phải là Lvalue, vì thế phép gán sau đây là bất hợp lệ:
5 = 3;
Khi compiler thấy một biểu thức ở bên trái của phép gán không phải là Lvalue, nó sẽ báo lỗi: "Lvalue required".

Ngoài toán tử gán ra, C còn có một số toán tử gộp của toán tử gán và một trong các toán tử cộng, trừ, nhân, chia, ví dụ:
x = 6;
x += 5; // now x becomes 11
x *= 2; // now x becomes 22
x--; // now x becomes 21 because x-- means x = x - 1;
//but, be careful if you use assignment here
y = x--; // now x = 20, but y = 21 because the execution of x = x - 1 (postfix --) is delayed after the assigment (y = x--).
y = --x; // if the "--" operator is prefix then the execution is not delayed (y = 19, x = 19)
y = 100 + x++; // compute it yourself!
y = 100 + ++x; // compute it yourself!


6.4. Các toán tử so sánh:
Toán tử bằng (==), toán tử lớn hơn (>), toán tử nhỏ hơn (<), toántử khác (!=), toán tử above (>=), bellow (<=) là các toán tử so sánh. Chúng là các toán tử hai ngôi và infix.
Giá trị trả về của chúng là một số khác 0 nếu biểu thức có giá trị đúng (true) và 0 nếu biểu thức có giá trị sai (false).
Ví dụ:
5 == 5; // trả về 1 (hoặc một số khác 0, biểu thị cho true)
5 != 5; // trả về 0 (biểu thị cho false)
5 != 4; // trả về 1
5 > 4; // trả về 1 
Các biểu thức chứa toán tử so sánh thường được dùng làm biểu thức điều kiện trong các câu lệnh rẽ nhánh (if-then-else), lặp (for, while) sẽ học ở bài sau.

6.5. Các toán tử logic:
Toán tử logic là các toán tử mà toán hạng của nó có ý nghĩa logic (đúng / sai, true / false). Giá trị trả về của chúng cũng có ý nghĩa logic (true/false).
Các toán tử logic là: toán tử và (&&), toán tử hoặc (||), toán tử phủ định (!).
Ví dụ:
/*
File: logicop.c
Logic operators demo
*/ 

#include<stdio.h>
int main(){
  int x, y, z, t;
  x = (5 == 5); // x = true
  y = (5 > 9); // y = false
  z = x || y; // z = (x or y)
  t = x && y;
  printf( "x = %d, y = %d, z = %d, t = %d\n", x, y, z, t );
  printf( "not(x) = !x = %d\n", !x );
  return 0;
}

7. Bảng tóm tắt các toán tử trong C
Sau đây là bảng tóm tắt các toán tử và độ ưu tiên, tính kết hợp của chúng. Có một số toán tử bạn chưa học, nhưng đừng lo, hãy tạm thời không để ý đến chúng.
Operator
Description
Associativity
()
[]
.
->
++  --
Parentheses (function call)
Brackets (array subscript)
Member selection via object name
Member selection via pointer
Postfix increment/decrement
left-to-right
++  --
+  -
!  ~
(type)
*
&
sizeof
 
 
Prefix increment/decrement
Unary plus/minus
Logical negation/bitwise complement
Cast (change type)
Dereference
Address
Determine size in bytes
right-to-left
*  /  %Multiplication/division/modulusleft-to-right
+  -Addition/subtractionleft-to-right
<<  >>Bitwise shift left, Bitwise shift rightleft-to-right
<  <=
>  >=
Relational less than/less than or equal to
Relational greater than/greater than or equal to
left-to-right
==  !=Relational is equal to/is not equal toleft-to-right
&Bitwise ANDleft-to-right
^Bitwise exclusive ORleft-to-right
|Bitwise inclusive ORleft-to-right
&&Logical ANDleft-to-right
||Logical ORleft-to-right
?:Ternary conditionalright-to-left
=
+=  -=
*=  /=
%=  &=
^=  |=
<<=  >>=
Assignment
Addition/subtraction assignment
Multiplication/division assignment
Modulus/bitwise AND assignment
Bitwise exclusive/inclusive OR assignment
Bitwise shift left/right assignment
right-to-left
,
Comma (separate expressions)left-to-right

8.Biểu thức:
Biểu thức (expression) hiểu đơn giản là sự kết hợp của các giá trị, biến số, toán tử và hàm số theo một quy tắc nhất định. Ví dụ "1 + 2" là một biểu thức,
((1 + 2) * 3 == 10) && (1 <= 2) 
cũng là một biểu thức.
Một ví dụ khác là 1 + add_two_numbers(100, 200 + 30 * 5) cũng là một biểu thức, trong đó có lời gọi hàm "add_two_numbers".
Bạn sẽ được học về định nghĩa biểu thức một cách rõ ràng hơn trong các bài sau.
Câu hỏi ôn tập:
1. Toán tử là gì?. Cho biết sự tương đồng giữa toán tử với hàm số?. Toán tử có giá trị trả về (return value) không?.
2. Cho ví dụ về một số toán tử (trong Toán học, không nhất thiết phải có trong ngôn ngữ C) : một ngôi, hai ngôi, ba ngôi / infix, prefix, postfix?
3. Thế nào là độ ưu tiên và thứ tự kết hợp của toán tử?. Vì sao phải định nghĩa độ ưu tiên và thứ tự kết hợp?
4(*). Thử nghĩ xem vì sao sizeof lại phải là một toán tử (không phải là một hàm số)?. (gợi ý: hãy nhớ về các bài tập theo kiểu "ra lệnh cho ai?" ở phần trước).

5. Chương trình sau đây định nghĩa một "hàm" để thực hiện toán tử gán ("hàm" cbd_assign, thực ra không phải là hàm nhưng bạn có thể hiểu nó là hàm vì nó gọi hàm assign_func). Nhờ có "hàm" cbd_assign và hàm assign_func mà bạn có thể thực hiện phép gán mà không cần toán tử gán của C. Hãy biên dịch và chạy thử chương trình này: assign.c (right click, chọn Save As... để download).
Hãy cho biết lệnh z = cbd_assign( y, -x ) tương đương với lệnh nào nếu dùng toán tử gán (tức không dùng hàm cbd_assign, mà dùng dấu bằng (=))
6. Hãy viết sửa lệnh z = cbd_assign( y, -x ) trong file assign.c (vừa download về) sao cho không cần dùng bất kỳ 1 dấu bằng (=) nào mà vẫn có kết quả tương đương. Từ đó trả lời các câu hỏi sau:
a) Lệnh gán (toán tử gán) là sự trừu tượng hóa của hành động gì?
b) Nhờ có sự trừu tượng hóa này bạn được lợi ích gì?
c) File assign.c sau khi sửa không dùng toán tử gán mà vẫn thực hiện được lệnh gán. Chỉ ra nơi cụ thể hóa (tức ngược với "trừu tượng hóa") lệnh gán trong file assign.c (tức là hãy thử chỉ ra đoạn code thực hiện lệnh gán trong file đó)
7. Lvalue là gì?
8. a) Không biên dịch chương trình, hãy cho biết chương trình sau đây có hợp lệ không, vì sao? (bạn phải tự phán đoán, không viết code ra rồi nhờ trình biên dịch đoán hộ!).
/*
File: cprog2_8.c
*/ 

#include<stdio.h>
int main(){
  int x, y;
  x = 5;
  (y + 3) = x;
  return 0;
}
b)(*) Hãy copy và paste chương trình trên vào file cprog2_8.c rồi thử biên dịch nó để xem phán đoán của bạn có đúng không?.
Giải thích về ý nghĩa của đoạn mã ở bên trái phép gán thứ 2: (y + 3) = x; từ đó giải thích kết quả biên dịch?
c) (**) Giả sử bạn là người thiết kế ngôn ngữ lập trình C (khi đó bạn có toàn quyền quy định ý nghĩa của các cấu trúc của ngôn ngữ như biến số, toán tử, hàm số ...). Hãy cho biết cần phải quy định ý nghĩa của đoạn mã ở bên trái phép gán thứ 2 ((y + 3) = x;) như thế nào để có kết quả biên dịch đối lập với kết quả ở câu (b). (chú ý: câu hỏi này chỉ là giả định, bạn không nên áp dụng vào thực tế khi lập trình)
9(*). Hãy đọc lại phần 2.3 của bài học số 2 (bài trước) (nhất là ví dụ về công tắc điện) và trả lời câu hỏi sau.
Khi bạn viết (a) y = x; và (b) x = 6; thì ý nghĩa của x ở trường hợp (a) và (b) có gì khác nhau, xét về mặt trừu tượng hóa (tức hành động thực chất mà việc viết đó làm đơn giản hóa đi, theo định nghĩa của trừu tượng hóa).
Gợi ý: trả lời các câu hỏi sau: nếu coi "y = x;" là việc bật công tắc điện, thì việc nối dây (để cho mạch điện thành kín, có dòng đi qua) tương đương với hành động gì ở bài này?; nếu coi "x = 6;" là việc tắt công tắc điện thì việc ngắt dây (để cho mạch điện hở, không có dòng đi qua) tương đương với hành động gì ở bài này?.
10. Không biên dịch chương trình, hãy trả lời kết quả của các biểu thức sau trong ngôn ngữ C. Hãy viết kết quả ra giấy và sau đó biên dịch & chạy chương trình, xem kết quả bạn đoán có đúng không?
Đối với các biểu thức logic, bạn chỉ cần trả lời true hay false (khi so sánh với kết quả chạy, hãy nhớ rằng true là giá trị khác 0, false là 0).
Bạn có thể vừa nhìn chương trình vừa nhìn phần 7 của bài học này (bảng độ ưu tiên toán tử)
/*
File: op_demo.c
Operators demo
*/ 

#include<stdio.h>
int main(){
  int x, y, z, t, u, v;
  t = 100;
  z = ++t - 1;
  x = (z == t);
  y = (z == t) && (1 < 2) || (1 > 0);
  u = (z == t) && ((1 < 2) || (1 > 0));
  printf( "x = %d, y = %d, z = %d, t = %d, u = %d\n", x, y, z, t, u );
  v = t++ - 1;
  printf( "Does v equal to t?: %d\n", (v == t) );
  printf( "Well, guess my meaning?: %d\n", x = y = z = u = -t++ );
  printf( "x = %d, y = %d, z = %d, t = %d, u = %d\n", x, y, z, t, u );
  return 0;
}
11. a) Hãy cho biết lệnh sau có hợp lệ không, vì sao?
int x, y;
x = 5;
y = ++x++;

b) Hãy thử viết 1 chương trình dùng lệnh này và biên dịch xem phán đoán của bạn có đúng không?.
c) Nêu ý nghĩa của lệnh x++; và viết nó dưới dạng toán tử gán thông thường (tức dùng dấu =).
d)(*) Hãy viết lại đoạn mã ở câu (a) mà không dùng toán tử gán, toán tử ++, chỉ dùng "hàm" cbd_assign ở file assign.c và toán tử cộng (+) (xem bài 6) (sau khi viết lại thì đoạn mã của bạn không còn dấu bằng và dấu ++, chỉ có cbd_assign và dấu +), từ đó giải thích kết quả biên dịch chương trình ở câu (b).
12. Hãy cho biết toán tử gán (=) và toán tử bằng (==) khác nhau như thế nào?. Nếu bạn dùng nhầm toán tử gán và toán tử bằng thì sẽ có chuyện gì xảy ra?
13(*). Hãy cho biết sự khác nhau giữa toán tử và hàm số. Nếu không dùng toán tử mà dùng toàn hàm số (kiểu cbd_assign) thì có gì khó khăn?
14. Biểu thức là gì?.
Hãy cho biết trong các đoạn mã sau đây đoạn nào là biểu thức, đoạn nào không phải là biểu thức trong C? (giả sử x là một biến số nguyên (int) đã được khai báo)
a)(x + 1) - 2 * 3
b)((x++ + 2) * 3 == 40) && (1 != 3)
c)1 + 
d)(100 - )
e)-(100)
f)(-100)
g)--100
h) + 1 + 2 + 3 + 4 + 5
i) * 1 * 2 * 3 * 4 * 5
j) x = + 1 + 2 + 3 + 4 + 5

Cuối cùng, hãy viết chương trình thử xem phán đoán của bạn có đùng không bằng cách sau:
printf( "%d\n", expression_you_want_to_test_goes_here );
Ví dụ:
printf( "%d\n", *1 * 2 * 3 * 4 * 5 );
Nếu là biểu thức đúng thì khi biên dịch sẽ không bị lỗi, nếu không sẽ bị báo lỗi.

Thấy Hay Thì Chia Sẻ Giúp Mình Nha (^^)

Bài Viết Liên Quan

Previous
Next Post »

Nội Quy Khi Gửi Bình Luận:

  • - Vui lòng gõ có dấu khi sử dụng tiếng việt.
  • - Nghiêm cấm spam link khác.
  • - Sử dụng ngôn ngữ có văn hóa khi comment.
  • - Chèn hình ảnh bằng code Link hình ảnh
  • - Chèn video bằng code [iframe] Link nhúng video [/iframe]
  • - Ngoài ra bạn có thể thêm những smile bên dưới vào bình luận để thêm sinh động
Biểu Tượng VuiBiểu Tượng Vui