Menu
CoddyTech

Hàm thành viên hằng

Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 29 trên 104.

Ở bài học trước, bạn đã thấy từ khóa const trong các phương thức getter. Nhưng khi const xuất hiện sau danh sách tham số của hàm, chính xác thì nó có nghĩa là gì? Một hàm thành viên const cam kết không sửa đổi bất kỳ biến thành viên nào của đối tượng.

class Rectangle {
    int width;
    int height;
public:
    Rectangle(int w, int h) : width(w), height(h) {}
    
    int getArea() const {      // Hàm thành viên (member function) const
        return width * height;  // Có thể đọc các thành viên
    }
    
    void setWidth(int w) {     // Không phải const: thay đổi đối tượng
        width = w;
    }
};

Từ khóa const sau danh sách tham số cho trình biên dịch biết: "Hàm này sẽ không thay đổi trạng thái của đối tượng." Nếu bạn cố thay đổi biến thành viên bên trong một hàm const, trình biên dịch sẽ báo lỗi.

Điều này trở nên thiết yếu khi làm việc với đối tượng const hoặc tham chiếu const. Đối tượng const chỉ có thể gọi các hàm thành viên const:

void printArea(const Rectangle& rect) {
    std::cout << rect.getArea();    // OK: getArea() là const
    // rect.setWidth(10);           // ERROR: setWidth() không phải là const
}

Đánh dấu các hàm bằng const khi chúng không thay đổi đối tượng là một cách làm tốt. Cách này thể hiện rõ ý định của bạn, cho phép hàm hoạt động với các đối tượng const và giúp trình biên dịch phát hiện những thay đổi ngoài ý muốn. Bạn nên đánh dấu const cho mọi hàm thành viên chỉ đọc dữ liệu.

challenge icon

Thử thách

Dễ

Hãy xây dựng một bộ chuyển đổi nhiệt độ để minh họa khi nào và vì sao nên dùng các hàm thành viên const. Bạn sẽ tạo một lớp có một số phương thức chỉ đọc dữ liệu (và nên là const), trong khi những phương thức khác sửa đổi trạng thái của đối tượng.

Bạn sẽ tạo hai tệp để sắp xếp code:

  • Temperature.h: Định nghĩa lớp Temperature để lưu giá trị nhiệt độ và cung cấp nhiều cách đọc cũng như sửa đổi giá trị đó. Lớp của bạn cần có:
    • Một thành viên riêng tư celsius (double) để lưu nhiệt độ
    • Một hàm khởi tạo nhận giá trị Celsius ban đầu
    • Một phương thức getCelsius() trả về giá trị đã lưu. Phương thức này nên là const vì chỉ đọc dữ liệu
    • Một phương thức getFahrenheit() tính và trả về nhiệt độ theo Fahrenheit bằng công thức celsius * 9.0 / 5.0 + 32.0, cũng là const vì không sửa đổi gì
    • Một phương thức getKelvin() trả về nhiệt độ theo Kelvin bằng celsius + 273.15, cũng là const
    • Một phương thức setCelsius(double value) cập nhật nhiệt độ đã lưu. Phương thức này không thể là const vì nó sửa đổi đối tượng
    • Một phương thức adjustBy(double delta) cộng delta vào nhiệt độ hiện tại, phương thức này cũng không phải const
  • main.cpp: Minh họa cách các hàm thành viên const hoạt động với cả đối tượng thông thường lẫn đối tượng const. Đọc giá trị nhiệt độ ban đầu từ đầu vào, sau đó:
    • Tạo một đối tượng Temperature với giá trị đầu vào
    • In ra "Initial: <celsius>C = <fahrenheit>F = <kelvin>K"
    • Điều chỉnh nhiệt độ thêm 10.0 độ
    • In ra "After adjustment: <celsius>C"
    • Tạo một hàm trợ giúp void printReadings(const Temperature& temp) nhận tham chiếu const và in ra "Reading: <celsius>C, <fahrenheit>F". Hàm này chỉ có thể gọi các phương thức const trên temp
    • Gọi printReadings() với đối tượng nhiệt độ của bạn
    • Đặt nhiệt độ thành 0.0 (điểm đóng băng)
    • In ra "Freezing point: <celsius>C = <fahrenheit>F"

Định dạng mọi giá trị nhiệt độ với một chữ số thập phân bằng std::fixed và std::setprecision(1) từ <iomanip>.

Điểm mấu chốt ở đây là printReadings() nhận một tham chiếu const, nên hàm chỉ có thể gọi các phương thức được đánh dấu là const. Vì vậy, việc đánh dấu đúng các phương thức getter là const rất quan trọng. Điều này cho phép chúng hoạt động trong những ngữ cảnh không thể sửa đổi đối tượng.

Tự làm thử

#include <iostream>
#include <iomanip>
#include "Temperature.h"
using namespace std;

// TODO: Triển khai các phương thức của lớp Temperature tại đây
// Hàm khởi tạo
Temperature::Temperature(double initialCelsius) {
    // TODO: Khởi tạo celsius
}

// TODO: Triển khai getCelsius() dưới dạng const

// TODO: Triển khai getFahrenheit() dưới dạng const

// TODO: Triển khai getKelvin() dưới dạng const

// TODO: Triển khai setCelsius(double value)

// TODO: Triển khai adjustBy(double delta)


// TODO: Tạo hàm hỗ trợ printReadings nhận tham số const Temperature& 
// và in ra "Reading: <celsius>C, <fahrenheit>F"
// Lưu ý: Hàm này chỉ có thể gọi các phương thức const trên temp!


int main() {
    double initialTemp;
    cin >> initialTemp;

    // Thiết lập định dạng đầu ra
    cout << fixed << setprecision(1);

    // TODO: Tạo đối tượng Temperature với giá trị đầu vào

    // TODO: In ra "Initial: <celsius>C = <fahrenheit>F = <kelvin>K"

    // TODO: Điều chỉnh nhiệt độ thêm 10.0 độ

    // TODO: In ra "After adjustment: <celsius>C"

    // TODO: Gọi printReadings() với đối tượng Temperature của bạn

    // TODO: Đặt nhiệt độ thành 0.0 (điểm đóng băng)

    // TODO: In ra "Freezing point: <celsius>C = <fahrenheit>F"

    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C++ online