Phương thức và biến tĩnh
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 31 trên 104.
Bạn đã học rằng các thành viên tĩnh được dùng chung cho tất cả đối tượng. Bây giờ, hãy cùng tìm hiểu cách định nghĩa và sử dụng phương thức tĩnh đúng cách cùng với các biến tĩnh.
Các biến tĩnh được khai báo bên trong lớp phải được định nghĩa bên ngoài lớp, thường là trong tệp mã nguồn. Vì các thành viên tĩnh cần một vị trí lưu trữ duy nhất được dùng chung cho tất cả đối tượng:
class Counter {
static int count; // Chỉ khai báo
public:
Counter() { count++; }
~Counter() { count--; }
static int getCount() { // Phương thức tĩnh
return count;
}
};
// Định nghĩa: phải đặt bên ngoài lớp
int Counter::count = 0;Phương thức tĩnh thuộc về lớp, không thuộc về bất kỳ đối tượng nào. Điều này dẫn đến một hệ quả quan trọng: phương thức tĩnh không thể truy cập các thành viên của instance hoặc dùng con trỏ this, vì chúng không được gọi trên một đối tượng cụ thể nào.
class MathUtils {
static double pi;
public:
static double circleArea(double radius) {
return pi * radius * radius; // Có thể truy cập các thành viên tĩnh
}
};
double MathUtils::pi = 3.14159;
// Được gọi mà không cần đối tượng
double area = MathUtils::circleArea(5.0);Phương thức tĩnh phù hợp với các hàm tiện ích không cần trạng thái của đối tượng, các hàm khởi tạo tạo đối tượng hoặc việc truy cập dữ liệu tĩnh. Vì chúng không cần một đối tượng cụ thể, bạn gọi chúng bằng tên lớp và toán tử phân giải phạm vi: ClassName::methodName().
Thử thách
DễHãy tạo một hệ thống sinh ID duy nhất để minh họa cách các phương thức và biến static kết hợp với nhau, cung cấp chức năng ở cấp lớp mà không cần tạo đối tượng.
Bạn sẽ tạo hai tệp để sắp xếp code:
IDGenerator.h: Định nghĩa lớpIDGeneratorđể tạo các ID duy nhất theo thứ tự. Lớp của bạn cần có:- Một thành viên static riêng tư
nextIdtheo dõi ID tiếp theo cần gán (bắt đầu từ1000) - Một thành viên static riêng tư
prefix(chuỗi) lưu tiền tố có thể tùy chỉnh cho tất cả ID - Một phương thức static
setPrefix(const std::string& p)để thay đổi tiền tố - Một phương thức static
getNextId()trả về chuỗi kết hợp tiền tố với số ID hiện tại, sau đó tăngnextIdđể dùng cho lần gọi tiếp theo - Một phương thức static
peekNextId()trả về ID tiếp theo sẽ là gì mà không tăng bộ đếm - Một phương thức static
getGeneratedCount()trả về số ID đã được tạo (gợi ý: tính từ giá trị ban đầu vànextIdhiện tại) - Một phương thức static
reset()đặt lạinextIdvề1000
Nhớ định nghĩa các thành viên static bên ngoài lớp, sau phần định nghĩa lớp. Khởi tạo
prefixthành"ID-".- Một thành viên static riêng tư
main.cpp: Minh họa cách gọi các phương thức static bằng tên lớp mà không tạo đối tượng nào. Đọc một tiền tố tùy chỉnh từ đầu vào, sau đó:- In
"Peek: <peeked_id>"bằngpeekNextId() - Tạo ba ID bằng
getNextId()và in từng ID dưới dạng"Generated: <id>" - In
"Count: <count>"để hiển thị số ID đã được tạo - Thay đổi tiền tố bằng giá trị đầu vào với
setPrefix() - Tạo thêm hai ID và in từng ID dưới dạng
"Generated: <id>" - In lại
"Count: <count>" - Gọi
reset() - In
"After reset - Peek: <peeked_id>" - In
"After reset - Count: <count>"
- In
Lưu ý rằng bạn không bao giờ tạo đối tượng IDGenerator: tất cả phương thức đều được gọi trực tiếp trên lớp bằng IDGenerator::methodName(). Đây là đặc điểm chính của phương thức static: chúng thuộc về chính lớp, không thuộc về bất kỳ đối tượng cụ thể nào.
Thêm <string> và <iostream> vào tệp tiêu đề, và đừng quên header guard.
Tự làm thử
#include <iostream>
#include <string>
#include "IDGenerator.h"
using namespace std;
int main() {
// Đọc tiền tố tùy chỉnh từ đầu vào
string customPrefix;
cin >> customPrefix;
// TODO: Dùng peekNextId() để in ra "Peek: <peeked_id>"
// TODO: Dùng getNextId() tạo ba ID và in từng ID dưới dạng "Generated: <id>"
// TODO: In ra "Count: <count>" để cho biết đã tạo bao nhiêu ID
// TODO: Dùng setPrefix() để đổi tiền tố theo giá trị đầu vào
// TODO: Tạo thêm hai ID và in từng ID dưới dạng "Generated: <id>"
// TODO: In lại "Count: <count>"
// TODO: Gọi reset()
// TODO: In ra "After reset - Peek: <peeked_id>"
// TODO: In ra "After reset - Count: <count>"
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