Chương 4: Câu lệnh điều kiện và đệ quy Java

Trở về Mục lục cuốn sách

4.1  Toán tử chia dư

Toán tử chia dư tính với các số nguyên (cùng các biểu thức số nguyên) và cho kết quả là phần dư của phép chia số thứ nhất cho số thứ hai. Trong Java, toán tử chia dư có kí hiệu là dấu phần trăm, %. Cú pháp cũng giống như các toán tử khác:

    int quotient = 7 / 3; 
    int remainder = 7 % 3;

Với toán tử thứ nhất, chia nguyên, kết quả là 2. Với toán tử thứ hai ta được kết quả bằng 1. Như vậy 7 chia cho 3 bằng 2 dư 1.

Toán tử số dư bất ngờ trở nên có ích. Chẳng hạn, bạn có thể kiểm tra xem một số có chia hết cho số khác không: nếu x % y bằng không thì x chia hết cho y.

Hơn nữa, bạn còn có thể lọc ra những chữ số cuối cùng bên phải từ số ban đầu. Chẳng hạn, x % 10 cho ta số hàng đơn vị của x (trong hệ thập phân). Tương tự, x % 100 cho ta hai chữ số hàng chục và đơn vị.

4.2  Thực hiện lệnh theo điều kiện

Để viết được những chương trình hữu ích, chúng ta thường luôn phải kiểm tra những điều kiện và thay đổi biểu hiện tương ứng của chương trình. Các câu lệnh điều kiện cung cấp cho ta khả năng này. Dạng đơn giản nhất là lệnh if:

  if (x > 0) { 
    System.out.println("x la so duong"); 
  }

Biểu thức ở trong cặp ngoặc tròn được gọi là điều kiện. Nếu nó được thoả mãn thì đoạn lệnh bên trong cặp ngoặc nhọn được thực thi. Nếu không, sẽ chẳng có điều gì xảy ra.

Điều kiện có thể chứa bất kì toán tử so sánh nào, vốn đôi khi còn được gọi là toán tử quan hệ:

  x == y // x bằng y 
  x != y // x không bằng y 
  x > y // x is lớn hơn y 
  x < y // x nhỏ hơn y
  x >= y // x lớn hơn hoặc bằng y 
  x <= y // x nhỏ hơn hoặc bằng y

Mặc dù có thể bạn đã quen thuộc với những phép toán này, cú pháp dùng trong Java vẫn hơi khác những biểu thức như =, ≠ và ≤. Một lỗi thường mắc phải là dùng một dấu = thay vì hai ==. Hãy nhớ rằng = là toán tử gán, còn == là toán tử so sánh. Ngoài ra không có toán tử nào được viết là =< hoặc =>.

Hai vế trong một biểu thức điều kiện phải có cùng kiểu dữ liệu. Bạn chỉ được phép so sánh int với ints hoặc double với double.

Hai toán tử == và != cũng làm việc với các chuỗi kí tự, nhưng cách hoạt động của chúng không giống như bạn đã dự kiến. Còn tất cả những toán tử quan hệ khác thì không có tác dụng gì đối với chuỗi. Ta sẽ xem cách so sánh chuỗi ở Mục 8.10.

4.3  Thực hiện chọn lựa

Dạng thứ hai của thực hiện teho điều kiện  thực hiện lệnh theo lựa chọn, trong đó có hai khả năng và điều kiện được đặt ra để căn cứ vào đó mà lựa chọn thực hiện một trong hai. Cú pháp có dạng như sau:

    if (x%2 == 0) { 
      System.out.println("x la so chan"); 
    } else { 
      System.out.println("x la so le"); 
    }

Nếu phần dư của phép chia x cho 2 là 0, thì chúng ta biết rằng x là số chẵn, và chương trình sẽ hiển thị thông báo điều này. Nếu điều kiện không được thoả mã thì lệnh thứ hai sẽ được thực hiện. Vì điều kiện hoặc là được thoả mãn, hoặc không; nên luôn chỉ có một trong hai phương án được thực hiện. 

Nhân tiện nói thêm, nếu bạn có ý định thường xuyên kiểm tra tính chẵn lẻ, có thể bạn sẽ muốn “gói” đoạn mã lệnh này vào trong một phương thức, như sau:

  public static void printParity(int x) { 
    if (x%2 == 0) { 
      System.out.println("x la so chan"); 
    } else { 
      System.out.println("x la so le"); 
    } 
  }

Bây giờ bạn có một phương thức tên là printParity để in ra thông báo thích hợp cho mỗi số nguyên bạn cung cấp cho nó. Trong main bạn sẽ kích hoạt phương thức này như sau:

    printParity(17);

Hãy luôn nhớ rằng khi bạn kích hoạt một phương thức, thì không nhất thiết phải khai báo các kiểu của đối số được cung  cấp. Java có thể hình dung ra kiểu dữ liệu là gì. Bạn phải kiềm chế để tránh viết những lệnh kiểu như:

    int number = 17; 
    printParity(int number); // SAI!!!

4.4  Các điều kiện xâu chuỗi

Đôi khi bạn cần phải kiểm tra một số các điều kiện có liên quan và chọn trong một số những hành động.  Một cách thực hiện việc này là xâu chuỗi một loạt các if và else:

    if (x > 0) { 
      System.out.println("x la so duong"); 
    } else if (x < 0) { 
      System.out.println("x la so am"); 
    } else { 
      System.out.println("x bang khong"); 
    }

Việc xâu chuỗi như vậy có thể dài tùy ý, mặc dù chúng có thể khó đọc nếu đi quá đà. Một cách làm để dễ đọc hơn là sử dụng quy tắc thụt đầu dòng tiêu chuẩn, như đã trình bày trong các ví dụ trên. nếu bạn giữ cho các câu lệnh và các ngoặc nhọn được thẳng hàng với nhua thì ít có khả năng gây lỗi cú pháp hơn, và nếu có thì cũng dễ tìm thấy hơn.

4.5  Các điều kiện lồng ghép

Ngoài việc xâu chuỗi, bạn còn có thể lồng ghép một điều kiện bên trong điều kiện khác. Ta có thể viết lại ví dụ trên như sau:

    if (x == 0) { 
      System.out.println("x bang khong"); 
    } else { 
      if (x > 0) { 
        System.out.println("x la so duong"); 
      } else { 
        System.out.println("x la so am"); 
      } 
    }

Bây giờ thì câu lệnh điều kiện bên ngoài có hai nhánh. Nhánh thứ nhất chỉ chứa một lệnh print, nhánh thứ hai lại chứa một câu lệnh điều kiện khác, mà bản thân nó lại có hai nhánh. Hai nhánh này đều chứa những câu lệnh print đơn giản, mặc dù dĩ nhiên chúng có thể là những câu lệnh điều kiện khác.

Tuy cách viết thụt vào trong làm cho cấu trúc rõ ý, nhưng các lệnh điều kiện lồng ghép trở nên rất khó để người đọc nhanh. Ta nên cố gắng tránh dùng chúng.

Mặt khác, dạng cấu trúc lồng ghép này cũng thường thấy, và sau này ta còn gặp chúng, do vậy bạn cũng làm quen với nó.

4.6  Câu lệnh return

Câu lệnh return cho phép bạn kết thúc việc thực thi của một phương thức trước khi đến cuối phương thức đó. Một lí do dùng câu lệnh này là nếu bạn phát hiện ra điều kiện gây lỗi:

  public static void printLogarithm(double x) { 
    if (x <= 0.0) { 
      System.out.println("Yêu cau nhap vao so duong."); 
      return; 
    } 
    double result = Math.log(x); 
    System.out.println("Gia tri log cua x bang " + result); 
  }

Mã lệnh này định nghĩa một phương thức có tên printLogarithm; nó nhận tham số là một double có tên x. Phương thức này kiểm  tra xem liệu x có nhỏ hơn hoặc bằng 0 hay không, và trong trường hợp như vậy thì in ra một thông báo lỗi rồi dùng return để thoát khỏi phương thức. Luồng thực thi sẽ lập tức trở lại chỗ gọi phương thức đó và những dòng còn lại của phương thức sẽ không được thực hiện.

Tôi đã dùng một giá trị dấu phẩy động ở bên vế phải của điều kiện vì vế trái biểu thức này là một biến phẩy động.

4.7  Chuyển đổi kiểu

Bạn có thể tự hỏi rằng làm sao chương trình của ta có thể êm xuôi với biểu thức kiểu như "Gia tri log cua x bang " + result, bởi một toán hạng là String còn toán hạng kia là double. Truong trường hợp này Java đã thông minh để thay ta chuyển giá trị double thành String trước khi thực hiện việc ghép chuỗi.

Mỗi khi bạn thử “cộng” hai biểu thức, mà một trong số đó là String, Java sẽ chuyển đổi cái còn lại thành String rồi mới thực hiện ghép chuỗi. Bạn nghĩ điều gì sẽ xảy ra nếu thực hiện phép cộng giữa một số nguyên với một giá trị phẩy động?

4.8  Đệ quy

Ở chương trước tôi đã nói rằng việc một phương thức kích hoạt phương thức khác là hợp lệ, và a đã xét vài ví dụ. Tôi chưa đề cập rằng một phương thức kích hoạt chính nó cũng hợp lệ. Mặc dù bề ngoài thì có thể điều này không rõ hay dở ra sao, nhưng thực ra đó chính là một trong những đặc điểm hay nhất trong lập trình.

Chẳng hạn, hãy xét phương thức sau:

  public static void countdown(int n) { 
    if (n == 0) { 
      System.out.println("Bum!"); 
    } else { 
      System.out.println(n); 
      countdown(n-1); 
    } 
  }

Phương thức có tên là countdown và nó nhận tham số là một số nguyên. Nếu tham số bằng 0 hoặc âm, chương trình sẽ in ra chữ, “Bùm!” Còn nếu không, nó sẽ in ra giá trị tham số và sau đó kích hoạt một phương thức có tên countdown—nghĩa là chính nó—nhưng truyền vào đối số n-1.

Điều gì sẽ xảy ra khi ta kích hoạt một phương thức kiểu như thế này?

  countdown(3);

Việc thực hiện countdown bắt đầu với n=3, và do n lớn hơn 0, nó đưa ra giá trị 3, và rồi gọi chính nó…

Việc thực hiện countdown bắt đầu với n=2, và do n lớn hơn 0, nó đưa ra giá trị 2, và rồi gọi chính nó…

Việc thực hiện countdown bắt đầu với n=1, và do n lớn hơn 0, nó đưa ra giá trị 1, và rồi gọi chính nó…

Việc thực hiện countdown bắt đầu với n=0, và do n không còn lớn hơn 0, nó đưa ra dòng chữ “Bùm!” và rồi quay về.

Phương thức countdown ứng với n=1 quay về.

Phương thức countdown ứng với n=2 quay về.

Phương thức countdown ứng với n=3 quay về.

Và rồi bạn trở về với main. Như vậy, toàn bộ kết quả đầu ra như sau:

3
2
1
Bum!

Ví dụ thứ hai là hãy xem lại các phương thức newLine và threeLine.

  public static void newLine() { 
    System.out.println(""); 
  } 
  public static void threeLine() { 
    newLine(); newLine(); newLine(); 
  }

Mặc dù cách này có tác dụng, nhưng sẽ không giúp ích được nhiều trong trường hợp ta cần in 2, hoặc 106 dòng mới. Một cách làm hay hơn là

  public static void nLines(int n) { 
    if (n > 0) { 
      System.out.println(""); 
      nLines(n-1); 
    } 
  }

Chương trình này tương tự như countdown; khi n còn lớn hơn 0, nó sẽ in ra một dòng mới và sau đó sẽ kích hoạt chính nó để in thêm n - 1 dòng mới nữa. Như vậy số dòng kết quả sẽ là 1 + (n-1), tức là bằng n.

Khi một phương thức kích hoạt chính nó, điều này gọi là đệ quy, và những phương thức đó có tính đệ quy.

4.9  Biểu đồ ngăn xếp cho các phương thức đệ quy

Trong chương trước, chúng ta đã dùng một biểu đồ ngăn xếp để biểu thị trạng thái của một chương trình trong quá trình phương thức được kích hoạt. Loại biểu đồ này cũng tiện dùng cho việc diễn giải một phương thức đệ quy.

Hãy nhơ rằng mỗi khi phương thức được kích hoạt, Java tạo ra một “khung” mới trong đó có chứa phiên bản mới của các biến cục bộ và tham số trong phương thức.

Hình vẽ này minh hoạ một sơ đồ ngăn xếp cho phương thức countdown khi gọi với n = 3:

Có một khung dành cho main và bốn khung countdown, mỗi khung có một giá trị riêng cho tham biến n. Đáy của ngăn xếp, countdown với n=0, được gọi là trường hợp cơ sở. Nó không thực hiện lời gọi đệ quy, do đó không có thêm khung countdown nào.

Khung chứa main thì rỗng vì main không chứa bất kì tham số hay biến nào.

4.10  Thuật ngữ

toán tử module:
Toán tử dùng với hai số nguyên và trả lại phần dư trong phép chia giữa hai số đó. Trong Java, toán tử này được kí hiệu bởi dấu phần trăm (%).
lệnh điều kiện:
Một khối lệnh có thể được thực thi hay không tùy theo một điều kiện nào đó.
xâu chuỗi:
Cách nối nhiều lệnh điều kiện thành dãy liên tục.
lồng ghép:
Cách đặt một lệnh điều kiện này vò trong một hoặc cả hai nhánh của một lệnh điều kiện khác.
định kiểu:
Một toán tử giúp chuyển đổi từ kiểu dữ liệu này sang kiểu khác. Trong Java nó có dạng tên một kiểu dữ liệu viết giữa cặp ngoặc tròn, như (int).
đệ quy:
Quá trình kích hoạt chính phương thức đang được thực thi.
trường hợp cơ sở:
Một điều kiện để cho phương thức đệ quy không kích hoạt đệ quy nữa.

4.11  Bài tập

Bài tập 1   Hãy vẽ một biểu đồ ngăn xếp biểu diễn trạng thái chương trình ở Mục 4.8 sau khi main kích hoạt nLines với tham số n=4, ngay trước khi lần kích hoạt cuối cùng của nLines trả về.
Bài tập 2  Bài tập này ôn lại luồng thực thi, bằng một chương trình với nhiều phương thức. Hãy đọc mã lệnh dưới đây rồi trả lời những câu hỏi đi theo.

public class Buzz { 

  public static void baffle(String blimp) { 
    System.out.println(blimp); 
    zippo("ping", -5); 
  } 

  public static void zippo(String quince, int flag) { 
    if (flag < 0) { 
      System.out.println(quince + " zoop"); 
    } else { 
      System.out.println("ik"); 
      baffle(quince); 
      System.out.println("boo-wa-ha-ha"); 
    } 
  } 

  public static void main(String[] args) { 
    zippo("rattle", 13); 
  } 
}
  1. Hãy viết số 1 kế bên câu lệnh đầu tiên được thực thi của chương trình này. Hãy cẩn thận để tách biệt những thứ thuộc về câu lệnh với những thứ khác.
  2. Viết số 2 kế bệnh câu lệnh thứ hai, và cứ như vậy đến cuối chương trình. Nếu một câu lệnh được thực hiện nhiều lần thì cuối cùng ta có thể sẽ thấy kết quả in ra chứa nhiều con số ghi bên cạnh nó.
  3. Giá trị của tham số blimp khi baffle bị kích hoạt là gì?
  4. Kết quả của chương trình này là gì?
Bài tập 3  Câu đầu trong lời bài hát  “99 Bottles of Beer” là:

99 bottles of beer on the wall, 99 bottles of beer, ya’ take one down, ya’ pass it around, 98 bottles of beer on the wall.

Những câu tiếp theo cũng như vậy chỉ khác là số chai bia cứ giảm dần đi một, đến câu cuối cùng:

No bottles of beer on the wall, no bottles of beer, ya’ can’t take one down, ya’ can’t pass it around, ’cause there are no more bottles of beer on the wall!

Và sau đó thì cuối cùng bài hát cũng kết thúc.

Hãy viết chương trình in ra toàn bộ lời bài hát “99 Bottles of Beer.” Chương trình này cần có một phương thức đệ quy để giải quyết phần khó khăn, nhưng có thể bạn còn muốn viết thêm những phương thức phụ trợ việc phân chia những tính năng cơ bản của chương trình.

Trong quá trình phát triển mã lệnh, hãy thử chạy với một số ít các câu hát, như “3 Bottles of Beer.”

Mục đích của bài tập này là tiếp nhận bài toán rồi chia nhỏ nó thành những bài toán con, và giải bài toán con bằng cách viết những phương thức đơn giản.

Bài tập 4  Kết quả của chương trình sau đây là gì?

public class Narf { 

  public static void zoop(String fred, int bob) { 
    System.out.println(fred); 
    if (bob == 5) { 
      ping("not "); 
    } else { 
      System.out.println("!"); 
    } 
  } 

  public static void main(String[] args) { 
    int bizz = 5; 
    int buzz = 2; 
    zoop("just for", bizz); 
    clink(2*buzz); 
  } 

  public static void clink(int fork) { 
    System.out.print("It's "); 
    zoop("breakfast ", fork) ; 
  } 

  public static void ping(String strangStrung) { 
    System.out.println("any " + strangStrung + "more "); 
  } 
}
Bài tập 5   Định lý cuối cùng của Fermat phát biểu rằng không có các số nguyên ab, và c nào thoả mãn

an + bn = cn

trừ trường hợp n = 2.Viết một phương thức có tên là check_fermat nhận vào bốn tham số—abc và n—rồi kiểm tra xem có thoả mãn định lý Fermat không. Nếu n lớn hơn 2 và hoá ra an + bn = cn, thì chương trình sẽ in ra “Trời, Fermat đã lầm!” Còn nếu không thì chương trình sẽ in ra, “Không, vẫn không đúng”.

Bạn cần phải giả sử rằng có một phương thức tên là raiseToPow ; phương thức này nhận đối số là hai số nguyên rồi nâng đối số thứ nhất lên lũy thừa số thứ hai. Chẳng hạn:

    int x = raiseToPow(2, 3);

sẽ gán giá trị 8 cho x, bởi 23 = 8.

1 Phản hồi

Filed under Think Java

One response to “Chương 4: Câu lệnh điều kiện và đệ quy Java

  1. Pingback: Think Java: Cách suy nghĩ như nhà khoa học máy tính | Blog của Chiến

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s