Struct và lớp
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 37 trên 104.
Trong C++, struct và class gần như giống hệt nhau. Cả hai đều có thể có biến thành viên, hàm thành viên, hàm khởi tạo và hỗ trợ tính kế thừa. Điểm khác biệt duy nhất là mức truy cập mặc định.
Trong một class, các thành viên mặc định là private. Trong một struct, các thành viên mặc định là public:
struct Point {
int x; // mặc định là public
int y; // mặc định là public
};
class Circle {
double radius; // mặc định là private
public:
double getRadius() { return radius; }
};Điều này cũng áp dụng cho tính kế thừa. Theo mặc định, struct kế thừa công khai, còn class kế thừa riêng tư:
struct DerivedStruct : Base {}; // kế thừa công khai
class DerivedClass : Base {}; // kế thừa riêng tưNgoài các giá trị mặc định này, không có khác biệt nào về mặt kỹ thuật. Tuy nhiên, quy ước vẫn rất quan trọng.
Hầu hết lập trình viên C++ dùng struct làm vùng chứa dữ liệu đơn giản với các thành viên công khai (như tọa độ hoặc cấu hình), và dùng class khi tính đóng gói và hành vi đóng vai trò quan trọng. Tuân theo quy ước này giúp các lập trình viên khác hiểu rõ hơn ý định trong code của bạn.
Thử thách
DễHãy xây dựng một hệ thống cấu hình đơn giản để minh họa khi nào nên dùng struct thay vì class theo quy ước của C++. Bạn sẽ tạo một struct cho dữ liệu đơn giản cần được truy cập công khai và một class cho dữ liệu cần đóng gói và kiểm soát quyền truy cập.
Bạn sẽ tạo hai tệp để sắp xếp code:
Config.h: Định nghĩa một struct và một lớp, đại diện cho các loại dữ liệu cấu hình khác nhau.Trước tiên, hãy tạo một
structtên làDimensions: một vùng chứa dữ liệu đơn giản gồm chiều rộng và chiều cao (cả hai đều có kiểuint). Vì các thành viên của struct mặc định là công khai, bạn có thể truy cập trực tiếp mà không cần hàm getter. Thêm phương thứcarea()để trả về tích của chiều rộng và chiều cao.Tiếp theo, hãy tạo một
classtên làAppConfigđể quản lý các thiết lập ứng dụng với tính đóng gói phù hợp. Lớp này cần lưu tên ứng dụng (chuỗi) và một đối tượngDimensionsđại diện cho kích thước cửa sổ. Vì các thành viên của lớp mặc định là riêng tư, bạn cần chủ động khai báo giao diện công khai. Hãy cung cấp:- Một hàm khởi tạo nhận tên ứng dụng, chiều rộng và chiều cao
- Một hàm getter
getAppName()trả về tên - Một hàm getter
getWindowSize()trả vềDimensions - Một phương thức
displayConfig()in ra:"App: <name>, Window: <width>x<height>, Area: <area>"
main.cpp: Minh họa sự khác nhau giữa cách dùng struct và lớp. Đọc tên ứng dụng, chiều rộng và chiều cao từ đầu vào (ba dòng riêng biệt), sau đó:- Tạo trực tiếp một struct
Dimensionsvới các giá trị chiều rộng và chiều cao, truy cập trực tiếp các thành viên để in ra:"Direct struct access: <width> x <height>" - Tạo một đối tượng
AppConfigvới cả ba giá trị - Gọi
displayConfig()để hiển thị toàn bộ cấu hình - Dùng hàm getter để lấy kích thước cửa sổ và in ra:
"Via getter - Area: <area>"
- Tạo trực tiếp một struct
Thử thách này làm nổi bật quy ước: dùng struct khi bạn có dữ liệu đơn giản, minh bạch (chẳng hạn như tọa độ hoặc kích thước), và dùng class khi bạn muốn kiểm soát quyền truy cập và bổ sung hành vi cho dữ liệu.
Chuyển đầu vào chiều rộng và chiều cao từ chuỗi sang số nguyên bằng std::stoi(). Đừng quên các header guard trong tệp header.
Tự làm thử
#include <iostream>
#include <string>
#include "Config.h"
using namespace std;
int main() {
// Đọc đầu vào
string appName;
string widthStr;
string heightStr;
getline(cin, appName);
getline(cin, widthStr);
getline(cin, heightStr);
int width = stoi(widthStr);
int height = stoi(heightStr);
// TODO: Tạo struct Dimensions với width và height
// Truy cập trực tiếp các thành viên của nó để in ra: "Direct struct access: <width> x <height>"
// TODO: Tạo đối tượng AppConfig với appName, width và height
// TODO: Gọi displayConfig() trên đối tượng AppConfig
// TODO: Dùng phương thức getter getWindowSize() để lấy kích thước và in ra: "Via getter - Area: <area>"
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