Ôn thi HSG Tin học THPT – Bài 7: Chương trình con (Function)

1. Mục tiêu

  • Hiểu rõ khái niệm, vai trò và lợi ích của chương trình con (hàm) trong lập trình.
  • Nắm vững cú pháp để định nghĩa một hàm trong Python bằng từ khóa def.
  • Biết cách sử dụng tham số (parameters) để truyền dữ liệu vào hàm.
  • Hiểu và sử dụng câu lệnh return để trả về giá trị từ một hàm.
  • Áp dụng hàm để cấu trúc hóa chương trình, giải quyết các bài toán một cách module hóa và hiệu quả.

2. Giới thiệu và phạm vi ứng dụng

Trong quá trình xây dựng các chương trình phức tạp, việc lặp lại các đoạn mã giống nhau ở nhiều nơi là điều khó tránh khỏi. Tình trạng này không chỉ làm cho mã nguồn trở nên dài dòng, khó đọc mà còn gây khó khăn trong việc bảo trì và sửa lỗi. Chương trình con, hay còn gọi là hàm (function), ra đời để giải quyết vấn đề này.

Hàm là một khối các câu lệnh được đặt tên, được thiết kế để thực hiện một công việc cụ thể. Thay vì viết lại toàn bộ khối lệnh mỗi khi cần, người lập trình chỉ cần “gọi” tên hàm. Điều này mang lại các lợi ích then chốt trong lập trình thi đấu và phát triển phần mềm:

  • Tính tái sử dụng (Reusability): Một hàm có thể được định nghĩa một lần và được gọi thực thi nhiều lần từ các vị trí khác nhau trong chương trình.
  • Tính module hóa (Modularity): Chia một bài toán lớn và phức tạp thành các bài toán nhỏ hơn, mỗi bài toán nhỏ được giải quyết bởi một hàm riêng biệt. Điều này làm cho chương trình trở nên rõ ràng, có cấu trúc và dễ quản lý hơn.
  • Khả năng bảo trì (Maintainability): Khi cần thay đổi hoặc sửa lỗi một chức năng, chỉ cần chỉnh sửa nội dung bên trong hàm đó mà không ảnh hưởng đến các phần khác của chương trình.

3. Cú pháp và các ví dụ minh họa

3.1. Cấu trúc cú pháp chuẩn

Trong Python, một hàm được định nghĩa bằng từ khóa def. Cấu trúc tổng quát như sau:

def ten_ham(tham_so_1, tham_so_2, ...):
    """
    Docstring (chuỗi tài liệu) mô tả chức năng của hàm (không bắt buộc).
    """
    # Khối lệnh của hàm
    # Các câu lệnh được viết thụt vào trong so với dòng def
    # ...
    return gia_tri_tra_ve  # Không bắt buộc
  • def: Từ khóa bắt buộc để bắt đầu việc định nghĩa một hàm.
  • ten_ham: Tên do người lập trình đặt, tuân theo quy tắc đặt tên biến của Python (thường dùng chữ thường và dấu gạch dưới, ví dụ: tinh_tong).
  • (tham_so_1, tham_so_2, …): Danh sách các tham số của hàm, được đặt trong cặp dấu ngoặc đơn. Tham số là các biến nhận giá trị đầu vào cho hàm. Một hàm có thể không có tham số nào.
  • :: Dấu hai chấm bắt buộc sau danh sách tham số.
  • Khối lệnh của hàm: Toàn bộ các câu lệnh bên trong hàm phải được thụt lề một cách nhất quán (thường là 4 dấu cách).
  • return gia_tri_tra_ve: Câu lệnh tùy chọn, dùng để trả về một giá trị từ hàm sau khi đã thực thi xong. Nếu không có câu lệnh return, hàm sẽ tự động trả về giá trị None.

3.2. Ví dụ 1: Trường hợp cơ bản (Hàm không có tham số và không trả về giá trị)

Đây là loại hàm đơn giản nhất, thường được dùng để thực hiện một hành động cố định.

# Định nghĩa hàm in lời chào
def in_loi_chao():
    """
    Hàm này in ra màn hình một thông điệp chào mừng.
    """
    print("Xin chào! Chúc bạn học tốt lập trình thi đấu.")
    print("-----------------------------------------")

# Gọi hàm để thực thi
print("Bắt đầu chương trình.")
in_loi_chao()  # Lời chào sẽ được in ra tại đây
print("Kết thúc chương trình.")

Phân tích: Hàm in_loi_chao được định nghĩa để thực hiện một nhiệm vụ duy nhất là in hai dòng văn bản. Khi chương trình chạy đến dòng in_loi_chao(), luồng thực thi sẽ nhảy vào bên trong hàm, thực hiện các lệnh print và sau đó quay trở lại vị trí đã gọi nó để tiếp tục.

3.3. Ví dụ 2: Trường hợp nâng cao (Hàm có tham số và có giá trị trả về)

Đây là dạng hàm phổ biến nhất, nhận dữ liệu đầu vào qua tham số, xử lý và trả về kết quả.

# Định nghĩa hàm tính tổng hai số nguyên
def tinh_tong(so_a, so_b):
    """
    Hàm này nhận vào hai số a và b, sau đó trả về tổng của chúng.
    so_a: số hạng thứ nhất
    so_b: số hạng thứ hai
    """
    tong = so_a + so_b
    return tong

# Gọi hàm và sử dụng kết quả trả về
x = 15
y = 27
ket_qua = tinh_tong(x, y) # Giá trị 15 được gán cho so_a, 27 được gán cho so_b

print(f"Tổng của {x}{y} là: {ket_qua}")

# Có thể sử dụng trực tiếp trong một biểu thức khác
ket_qua_2 = tinh_tong(100, -50) * 2
print(f"Kết quả phép tính thứ hai là: {ket_qua_2}")

Phân tích: Khi gọi tinh_tong(x, y), giá trị của biến x (là 15) được truyền vào tham số so_a, và giá trị của y (là 27) được truyền vào so_b. Bên trong hàm, phép cộng 15 + 27 được thực hiện. Câu lệnh return tong sẽ trả giá trị 42 về nơi đã gọi hàm, và giá trị này được gán cho biến ket_qua.

4. Trực quan hóa và gỡ lỗi với Thonny

Môi trường lập trình Thonny cung cấp một công cụ gỡ lỗi trực quan, rất hữu ích để hiểu cách hàm hoạt động. Khi bạn chạy chương trình từng bước (step-by-step), Thonny sẽ hiển thị cửa sổ “Call Stack” (Ngăn xếp cuộc gọi).

Mỗi khi một hàm được gọi, một “khung” (frame) mới sẽ được đẩy vào Call Stack, chứa các biến cục bộ (tham số và các biến được tạo bên trong) của hàm đó. Khi hàm thực thi xong và return, khung của nó sẽ được xóa khỏi Call Stack.

Việc quan sát Call Stack giúp nhận biết luồng thực thi của chương trình và trạng thái của các biến tại mỗi thời điểm, là một kỹ năng gỡ lỗi vô cùng quan trọng.

5. Bài tập vận dụng

5.1. Bài tập 1

5.1.1. Đề bài

Viết một hàm có tên kiem_tra_so_nguyen_to nhận vào một tham số là một số nguyên dương n (với n > 1). Hàm này phải trả về giá trị True nếu n là số nguyên tố, và False nếu ngược lại.

5.1.2. Lời giải và Phân tích

import math

def kiem_tra_so_nguyen_to(n):
    """
    Kiểm tra một số nguyên dương n > 1 có phải là số nguyên tố hay không.
    Trả về True nếu n là số nguyên tố, False nếu không phải.
    """
    # Một số nguyên tố là số chỉ chia hết cho 1 và chính nó.
    # Ta chỉ cần kiểm tra các ước từ 2 đến căn bậc hai của n.
    
    # Nếu n nhỏ hơn 2, nó không phải là số nguyên tố.
    if n < 2:
        return False
        
    # Duyệt qua các số từ 2 đến phần nguyên của sqrt(n).
    for i in range(2, int(math.sqrt(n)) + 1):
        # Nếu n chia hết cho một số nào đó trong khoảng này, nó không phải là số nguyên tố.
        if n % i == 0:
            return False  # Trả về False và kết thúc hàm ngay lập tức
    
    # Nếu vòng lặp kết thúc mà không tìm thấy ước nào, n là số nguyên tố.
    return True

# Chương trình chính để kiểm tra hàm
so_can_kiem_tra = 29
if kiem_tra_so_nguyen_to(so_can_kiem_tra):
    print(f"{so_can_kiem_tra} là số nguyên tố.")
else:
    print(f"{so_can_kiem_tra} không phải là số nguyên tố.")

so_can_kiem_tra_2 = 30
if kiem_tra_so_nguyen_to(so_can_kiem_tra_2):
    print(f"{so_can_kiem_tra_2} là số nguyên tố.")
else:
    print(f"{so_can_kiem_tra_2} không phải là số nguyên tố.")

Phân tích:

  • Thuật toán: Dựa trên định nghĩa số nguyên tố. Một số n là số nguyên tố nếu nó không có ước số nào trong khoảng từ 2 đến sqrt(n).
  • Cài đặt:
    • Hàm nhận đầu vào n.
    • Sử dụng vòng lặp for để duyệt qua các số i từ 2 đến int(math.sqrt(n)).
    • Bên trong vòng lặp, nếu n % i == 0 (nghĩa là i là một ước của n), chương trình ngay lập tức trả về False và thoát khỏi hàm. Đây là một điểm quan trọng: câu lệnh return sẽ chấm dứt việc thực thi của hàm ngay tại thời điểm nó được gọi.
    • Nếu vòng lặp hoàn thành mà không tìm thấy ước nào, điều đó có nghĩa n là số nguyên tố, và hàm trả về True.

5.2. Bài tập 2

5.2.1. Đề bài

Viết một hàm có tên tinh_giai_thua nhận vào một số nguyên không âm n và trả về giá trị giai thừa của n (ký hiệu là n!). Biết rằng 0! = 1.

5.2.2. Lời giải và Phân tích

def tinh_giai_thua(n):
    """
    Tính giai thừa của một số nguyên không âm n.
    n!: n * (n-1) * ... * 1.
    Quy ước: 0! = 1.
    """
    # Trường hợp cơ bản: nếu n là 0, trả về 1.
    if n == 0:
        return 1
    
    # Khởi tạo biến tích lũy kết quả.
    giai_thua = 1
    
    # Dùng vòng lặp để nhân lần lượt các số từ 1 đến n.
    for i in range(1, n + 1):
        giai_thua = giai_thua * i
        
    # Trả về kết quả cuối cùng.
    return giai_thua

# Chương trình chính để kiểm tra hàm
so_n = 5
ket_qua_gt = tinh_giai_thua(so_n)
print(f"Giai thừa của {so_n} là: {ket_qua_gt}") # Kết quả: 120

so_n_2 = 0
ket_qua_gt_2 = tinh_giai_thua(so_n_2)
print(f"Giai thừa của {so_n_2} là: {ket_qua_gt_2}") # Kết quả: 1

Phân tích:

  • Thuật toán: Giai thừa của n là tích của các số nguyên từ 1 đến n.
  • Cài đặt:
    • Xử lý trường hợp đặc biệt n = 0 theo định nghĩa.
    • Khởi tạo một biến giai_thua với giá trị ban đầu là 1. Biến này đóng vai trò là phần tử trung hòa của phép nhân.
    • Sử dụng vòng lặp for chạy từ 1 đến n (bao gồm cả n).
    • Trong mỗi lần lặp, nhân giá trị hiện tại của giai_thua với biến lặp i.
    • Sau khi vòng lặp kết thúc, biến giai_thua sẽ chứa giá trị n!, và hàm trả về giá trị này.

6. Các lưu ý và lỗi thường gặp

  • Quên dấu hai chấm (:): Lỗi cú pháp phổ biến nhất khi định nghĩa hàm là quên dấu : ở cuối dòng def.
  • Sai thụt lề (IndentationError): Toàn bộ mã bên trong hàm phải được thụt lề một cách nhất quán. Python rất nghiêm ngặt về vấn đề này.
  • Nhầm lẫn giữa print và return:
    • print chỉ có tác dụng hiển thị giá trị ra màn hình cho người dùng xem. Nó không trả về giá trị nào để chương trình có thể tiếp tục sử dụng.
    • return dùng để “gửi” một giá trị từ hàm ra ngoài. Giá trị này có thể được gán cho một biến hoặc dùng trong các biểu thức khác. Một hàm chỉ in kết quả ra màn hình sẽ không thể dùng trong các phép tính.
  • Số lượng đối số không khớp: Lỗi TypeError sẽ xảy ra nếu số lượng giá trị truyền vào khi gọi hàm không khớp với số lượng tham số được định nghĩa.
  • Hàm không có return: Nếu một hàm kết thúc mà không gặp câu lệnh return nào, nó sẽ mặc định trả về giá trị đặc biệt None.

7. Tổng kết

Chương trình con (hàm) là một khái niệm nền tảng trong lập trình. Việc sử dụng thành thạo hàm cho phép chia nhỏ các vấn đề phức tạp, tăng cường khả năng tái sử dụng mã và làm cho chương trình trở nên dễ đọc, dễ gỡ lỗi hơn.

Nắm vững cách định nghĩa hàm với def, cách truyền dữ liệu qua tham số và cách nhận kết quả qua return là kỹ năng thiết yếu đối với mọi lập trình viên.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *