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.
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ớpTemperatuređể 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ứccelsius * 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ằngcelsius + 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
- Một thành viên riêng tư
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
Temperaturevớ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"
- Tạo một đối tượng
Đị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;
}
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
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online