Bài 10.3: Bài tập thực hành hàm trả về trong C++

Chào mừng các bạn quay trở lại với series học lập trình C++ của FullhouseDev! Ở các bài trước, chúng ta đã làm quen với khái niệm cơ bản về hàm, cách khai báo, định nghĩa và sử dụng các hàm void - những hàm thực hiện một tác vụ nào đó nhưng không trả lại một giá trị cụ thể cho nơi gọi nó.

Hôm nay, chúng ta sẽ tiến thêm một bước quan trọng: tìm hiểu về hàm trả về giá trị (functions returning values). Đây là một bước đột phá giúp các hàm của chúng ta trở nên mạnh mẽlinh hoạt hơn rất nhiều. Thay vì chỉ thực hiện hành động, hàm trả về cho phép chúng ta tính toán, xử lý dữ liệu và gửi kết quả đó ngược lại cho phần còn lại của chương trình sử dụng.

Hãy cùng đi sâu vào cách chúng hoạt động và thực hành qua các ví dụ nhé!

Hàm Trả Về Giá Trị Hoạt Động Như Thế Nào?

Khác với hàm void, hàm trả về giá trị được khai báo với một kiểu dữ liệu trả về cụ thể (ví dụ: int, double, bool, string,...). Kiểu dữ liệu này quy định loại giá trị mà hàm sẽ gửi trả lại sau khi hoàn thành công việc của mình.

Cú pháp cơ bản khi định nghĩa một hàm trả về:

kieu_du_lieu_tra_ve ten_ham(tham_so_1, tham_so_2, ...) {
    // Các câu lệnh xử lý logic của hàm
    // ...

    // Câu lệnh return để trả về giá trị
    return gia_tri_can_tra_ve;
}

Điểm mấu chốt ở đây là câu lệnh return. Khi chương trình gặp câu lệnh return gia_tri; bên trong hàm, nó sẽ thực hiện các việc sau:

  1. Tính toán hoặc xác định gia_tri_can_tra_ve.
  2. Dừng ngay lập tức việc thực thi các câu lệnh còn lại trong hàm đó.
  3. Gửi gia_tri_can_tra_ve này trở lại cho nơi đã gọi hàm. Kiểu dữ liệu của gia_tri_can_tra_ve phải tương thích với kieu_du_lieu_tra_ve đã khai báo cho hàm.

Nơi gọi hàm có thể lưu trữ giá trị trả về này vào một biến, hoặc sử dụng nó trực tiếp trong một biểu thức hoặc câu lệnh khác.

Hãy bắt đầu với một ví dụ đơn giản nhất: một hàm tính tổng hai số nguyên.

Ví Dụ 1: Hàm Tính Tổng Hai Số Nguyên

Chúng ta muốn một hàm nhận hai số nguyên ab, sau đó trả về kết quả của a + b.

#include <iostream>

int add(int a, int b) {
    return a + b;
}

int main() {
    int s1 = 10;
    int s2 = 20;

    int kq = add(s1, s2);
    cout << "Tong cua " << s1 << " va " << s2 << " la: " << kq << endl;
    cout << "Tong cua 5 va 7 la: " << add(5, 7) << endl;

    return 0;
}

Output:

Tong cua 10 va 20 la: 30
Tong cua 5 va 7 la: 12

Giải thích code:

  • Dòng int add(int a, int b): Khai báo một hàm tên là add. Nó nhận hai tham số kiểu intab. Quan trọng nhất, int đứng trước tên hàm add cho biết hàm này sẽ trả về một giá trị có kiểu int.
  • Dòng return a + b;: Đây là câu lệnh return. Nó tính tổng của ab, sau đó gửi kết quả này trở lại nơi hàm add được gọi.
  • Dòng int kq = add(s1, s2); trong main: Khi add(s1, s2) được gọi, hàm add thực thi, tính 10 + 20 = 30. Giá trị 30 này được trả về và gán vào biến kq kiểu int trong hàm main.
  • Dòng cout << add(5, 7) << endl;: Minh họa việc sử dụng trực tiếp giá trị trả về của hàm add mà không cần lưu vào biến trung gian.

Ví dụ này cho thấy sự trao đổi thông tin hiệu quả giữa hàm add và hàm main thông qua giá trị trả về. Hàm add làm công việc tính toán, và main nhận kết quả để sử dụng tiếp.

Ví Dụ 2: Tính Diện Tích Hình Tròn

Hãy viết một hàm nhận bán kính hình tròn (kiểu double) và trả về diện tích của nó (kiểu double).

#include <iostream>
#include <cmath>

double tinh_dt(double r) {
    if (r < 0) {
        cerr << "Loi: Ban kinh khong the am!" << endl;
        return -1.0;
    }
    return M_PI * r * r;
}

int main() {
    double r1 = 5.0;
    double r2 = 10.0;
    double r3 = -2.0;

    double dt1 = tinh_dt(r1);
    if (dt1 >= 0) {
        cout << "Dien tich hinh tron ban kinh " << r1 << " la: " << dt1 << endl;
    }

    cout << "Dien tich hinh tron ban kinh " << r2 << " la: " << tinh_dt(r2) << endl;

    double dt3 = tinh_dt(r3);
    if (dt3 < 0) {
         cout << "Khong the tinh dien tich voi ban kinh am." << endl;
    }

    return 0;
}

Output:

Loi: Ban kinh khong the am!
Dien tich hinh tron ban kinh 5 la: 78.5398
Dien tich hinh tron ban kinh 10 la: 314.159
Loi: Ban kinh khong thể am!
Khong the tinh dien tich voi ban kinh am.

Giải thích code:

  • Dòng double tinh_dt(double r): Hàm này nhận một tham số r kiểu double và được khai báo sẽ trả về một giá trị kiểu double (diện tích).
  • Câu lệnh return M_PI * r * r;: Tính toán diện tích và gửi kết quả về.
  • Phần kiểm tra if (r < 0): Đây là một ví dụ về cách xử lý các trường hợp ngoại lệ. Nếu bán kính âm, chúng ta thông báo lỗi và trả về một giá trị đặc biệt (-1.0) để báo hiệu cho nơi gọi biết rằng có vấn đề xảy ra, thay vì trả về một kết quả tính toán sai.
  • Trong main, chúng ta gọi tinh_dt(r1) và lưu kết quả vào dt1. Sau đó, chúng ta kiểm tra giá trị của dt1 trước khi in ra, minh họa cách xử lý giá trị trả về, kể cả các giá trị báo lỗi.

Ví Dụ 3: Kiểm Tra Số Chẵn/Lẻ (Sử Dụng bool)

Hàm trả về kiểu bool rất phổ biến khi chúng ta muốn kiểm tra một điều kiện nào đó và nhận kết quả là true hoặc false.

#include <iostream>

bool la_chan(int n) {
    return n % 2 == 0;
}

int main() {
    int n1 = 14;
    int n2 = 7;

    if (la_chan(n1)) {
        cout << n1 << " la so chan." << endl;
    } else {
        cout << n1 << " khong la so chan." << endl;
    }

    if (la_chan(n2)) {
        cout << n2 << " la so chan." << endl;
    } else {
        cout << n2 << " khong la so chan." << endl;
    }

    cout << boolalpha;

    cout << "So 10 co phai so chan? " << la_chan(10) << endl;
    cout << "So 9 co phai so chan? " << la_chan(9) << endl;

    return 0;
}

Output:

14 la so chan.
7 khong la so chan.
So 10 co phai so chan? true
So 9 co phai so chan? false

Giải thích code:

  • Dòng bool la_chan(int n): Hàm la_chan nhận một int và được khai báo trả về một bool.
  • Dòng return n % 2 == 0;: Biểu thức n % 2 == 0 cho kết quả là true nếu n chia hết cho 2, và false nếu không. Giá trị true hoặc false này được trả về bởi hàm.
  • Trong main, giá trị bool trả về từ la_chan được sử dụng trực tiếp trong điều kiện của câu lệnh if. Nếu la_chan(n1) trả về true, khối lệnh của if được thực thi. Nếu trả về false, khối lệnh của else được thực thi.
  • boolalpha là một "manipulator" (bộ điều khiển) của cout giúp chúng ta hiển thị giá trị bool dưới dạng từ khóa true hoặc false thay vì số 1 hoặc 0.

Ví Dụ 4: Tìm Số Lớn Nhất (Sử Dụng max)

Một hàm trả về cũng có thể trả về một trong các giá trị đầu vào sau khi xử lý logic.

#include <iostream>
#include <algorithm>

int lay_max(int a, int b) {
    return max(a, b);
}

int main() {
    int x = 100;
    int y = 200;

    int lon_nhat = lay_max(x, y);
    cout << "So lon nhat giua " << x << " va " << y << " la: " << lon_nhat << endl;
    cout << "So lon nhat giua 50 va 30 la: " << lay_max(50, 30) << endl;

    return 0;
}

Output:

So lon nhat giua 100 va 200 la: 200
So lon nhat giua 50 va 30 la: 50

Giải thích code:

  • Dòng int lay_max(int a, int b): Hàm này nhận hai int và trả về int (số lớn hơn).
  • Dòng return max(a, b);: Đây là cách dùng hàm có sẵn max để tìm giá trị lớn nhất giữa hai số. max cũng là một hàm trả về giá trị. Nó tính toán và trả về số lớn hơn giữa ab.
  • Trong main, giá trị trả về từ lay_max được gán vào biến lon_nhat hoặc sử dụng trực tiếp.

Ví Dụ 5: Hàm Trả Về Chuỗi (string)

Hàm cũng có thể trả về các kiểu dữ liệu phức tạp hơn như chuỗi ký tự (string).

#include <iostream>
#include <string>

string tao_loi_chao(const string& ten) {
    return "Xin chao, " + ten + "!";
}

int main() {
    string ten = "Doc gia C++";
    string loi_chao = tao_loi_chao(ten);
    cout << loi_chao << endl;
    cout << tao_loi_chao("FullhouseDev") << endl;

    return 0;
}

Output:

Xin chao, Doc gia C++!
Xin chao, FullhouseDev!

Giải thích code:

  • Dòng string tao_loi_chao(const string& ten): Hàm này nhận một string và được khai báo sẽ trả về một string. Việc sử dụng const string& cho tham số là một kỹ thuật tối ưu hiệu năng khi truyền chuỗi lớn, chúng ta sẽ tìm hiểu kỹ hơn sau, tạm thời hiểu nó vẫn nhận vào một chuỗi.
  • Dòng return "Xin chao, " + ten + "!";: Nối chuỗi "Xin chao, " với giá trị của tham số ten và chuỗi "!" rồi trả về kết quả là một string mới.
  • Trong main, chuỗi trả về được gán vào biến loi_chao hoặc in trực tiếp.

Ví Dụ 6: Kết Hợp Nhiều Hàm Trả Về

Sức mạnh thực sự của hàm trả về nằm ở khả năng xâu chuỗi các thao tác xử lý. Kết quả trả về của một hàm có thể trở thành đầu vào cho hàm khác.

#include <iostream>

int nhan_doi(int n) {
    cout << "* Buoc 1: Nhan " << n << " voi 2..." << endl;
    return n * 2;
}

int cong_nam(int n) {
    cout << "* Buoc 2: Cong " << n << " voi 5..." << endl;
    return n + 5;
}

int main() {
    int bd = 10;

    int kq1 = nhan_doi(bd);
    int kqf = cong_nam(kq1);

    cout << "Gia tri ban dau: " << bd << endl;
    cout << "Ket qua cuoi cung: " << kqf << endl;

    cout << "\n--- Goi truc tiep ---" << endl;
    cout << "Ket qua khi goi truc tiep: " << cong_nam(nhan_doi(bd)) << endl;

    return 0;
}

Output:

* Buoc 1: Nhan 10 voi 2...
* Buoc 2: Cong 20 voi 5...
Gia tri ban dau: 10
Ket qua cuoi cung: 25

--- Goi truc tiep ---
* Buoc 1: Nhan 10 voi 2...
* Buoc 2: Cong 20 voi 5...
Ket qua khi goi truc tiep: 25

Giải thích code:

  • Chúng ta có hai hàm đơn giản, nhan_doicong_nam, cả hai đều nhận một int và trả về một int.
  • Trong main, nhan_doi(bd) được gọi đầu tiên. Nó thực hiện công việc của mình (nhân 10 với 2) và trả về 20. Giá trị 20 này được lưu vào biến kq1.
  • Tiếp theo, cong_nam(kq1) được gọi. Lưu ý rằng kq1 chính là giá trị 20 được trả về từ hàm trước. Hàm cong_nam nhận 20, thực hiện công việc của mình (cộng 5), và trả về 25. Giá trị 25 này được lưu vào kqf.
  • Việc gọi lồng nhau cong_nam(nhan_doi(bd)) minh họa rõ ràng hơn cách giá trị trả về của hàm bên trong (nhan_doi) trở thành tham số đầu vào cho hàm bên ngoài (cong_nam).

Cách tiếp cận này cho phép chúng ta xây dựng các chương trình phức tạp bằng cách kết hợp các hàm nhỏ, chuyên biệt. Mỗi hàm thực hiện một nhiệm vụ cụ thể và trả về kết quả, kết quả đó lại được sử dụng làm đầu vào cho nhiệm vụ tiếp theo.

  • Để tạo hàm trả về, hãy khai báo nó với một kiểu dữ liệu trả về (khác void).
  • Sử dụng câu lệnh return để gửi giá trị từ hàm về nơi gọi.
  • Giá trị trả về phải tương thích với kiểu dữ liệu trả về đã khai báo.
  • Nơi gọi hàm có thể lưu trữ giá trị trả về vào một biến hoặc sử dụng trực tiếp.
  • Hàm trả về cho phép xâu chuỗi các phép tính và làm cho code của chúng ta ngăn nắp, dễ đọcdễ bảo trì hơn.

Comments

There are no comments at the moment.