Bài 32.1: Khái niệm và sử dụng Struct trong C++

Bài 32.1: Khái niệm và sử dụng Struct trong C++
Chào mừng các bạn quay trở lại với series blog về C++! Sau khi đã làm quen với các kiểu dữ liệu cơ bản như int
, float
, char
, string
hay các cấu trúc dữ liệu đơn giản như mảng, chúng ta nhận thấy một vấn đề: làm sao để biểu diễn những "đối tượng" phức tạp hơn trong thực tế, ví dụ như một người, một cuốn sách, hay một điểm trong không gian 2D? Một người có tên (string), tuổi (int), chiều cao (float)... Một cuốn sách có tiêu đề (string), tác giả (string), năm xuất bản (int)... Các kiểu dữ liệu cơ bản không đủ để nhóm tất cả thông tin này lại thành một "đơn vị" duy nhất.
Đây chính là lúc Struct (cấu trúc) phát huy sức mạnh của nó! Struct là một khái niệm cực kỳ quan trọng trong C++ (và cả ngôn ngữ C), cho phép chúng ta nhóm các biến có kiểu dữ liệu khác nhau thành một đơn vị logic duy nhất dưới một cái tên chung. Hãy cùng tìm hiểu chi tiết về người bạn đồng hành đắc lực này nhé!
Khái niệm Struct trong C++
Một cách đơn giản, struct
trong C++ là một kiểu dữ liệu do người dùng định nghĩa (user-defined data type). Nó cho phép bạn đóng gói (bundle) các biến (có thể là các kiểu dữ liệu cơ bản, mảng, hay thậm chí là các struct khác) lại với nhau dưới một cái tên duy nhất. Những biến được đóng gói bên trong struct được gọi là các thành viên (members) của struct đó.
Hãy tưởng tượng struct
như một bản thiết kế (blueprint) hoặc một mẫu (template) để tạo ra các "đối tượng" có cấu trúc dữ liệu giống nhau. Khi bạn định nghĩa một struct, bạn không tạo ra một biến cụ thể nào cả; bạn chỉ đơn giản là mô tả cấu trúc dữ liệu mà các biến sẽ có sau này.
Khai báo một Struct
Để định nghĩa một struct, chúng ta sử dụng từ khóa struct
, theo sau là tên của struct mà bạn muốn đặt, sau đó là một cặp dấu ngoặc nhọn {}
chứa các thành viên, và kết thúc bằng một dấu chấm phẩy ;
.
Cú pháp cơ bản:
struct TenCuaStruct {
kieu_du_lieu1 ten_thanh_vien1;
kieu_du_lieu2 ten_thanh_vien2;
// ... các thành viên khác
}; // <-- Rất quan trọng: Dấu chấm phẩy ở cuối
Ví dụ, để biểu diễn thông tin một điểm trong không gian 2D, chỉ cần tọa độ x và y:
struct Diem {
int x;
int y;
}; // Định nghĩa struct Diem với 2 thành viên kieu int
Và đây là ví dụ cho Person
mà chúng ta đã đề cập:
#include <string> // Can them thu vien string
struct Person {
string name;
int age;
float height; // chieu cao
}; // Định nghĩa struct Person
Giải thích:
struct Diem
vàstruct Person
: Chúng ta đang định nghĩa hai kiểu dữ liệu mới làDiem
vàPerson
.int x;
,int y;
: Bên trongDiem
, chúng ta khai báo hai thành viên kiểuint
làx
vày
.string name;
,int age;
,float height;
: Bên trongPerson
, chúng ta khai báo ba thành viên với các kiểu dữ liệu khác nhau làstring
,int
, vàfloat
.- Dấu chấm phẩy
;
ở cuối khối{}
là bắt buộc khi định nghĩa struct.
Lưu ý rằng việc khai báo struct thường được đặt bên ngoài hàm main()
hoặc các hàm khác, thường là ở phạm vi toàn cục hoặc trong một namespace, để nó có thể được sử dụng ở nhiều nơi trong chương trình.
Sử dụng Struct: Tạo biến và Truy cập thành viên
Sau khi đã định nghĩa struct, chúng ta có thể tạo ra các biến (hay còn gọi là đối tượng) thuộc kiểu struct đó, giống như cách bạn tạo biến kiểu int
hay float
.
Cú pháp tạo biến:
TenCuaStruct ten_bien;
Ví dụ, sử dụng các struct vừa định nghĩa ở trên:
Diem diem1; // Tao mot bien co kieu Diem
Person person1; // Tao mot bien co kieu Person
Person person2; // Tao mot bien Person khac
Khi đã có biến kiểu struct, làm thế nào để truy cập hoặc gán giá trị cho các thành viên bên trong nó? Chúng ta sử dụng toán tử chấm (.
).
Cú pháp truy cập thành viên:
ten_bien.ten_thanh_vien
Ví dụ:
#include <iostream>
#include <string> // Can them thu vien string
struct Diem {
int x;
int y;
};
struct Person {
string name;
int age;
float height;
};
int main() {
// Tao bien Diem
Diem diem1;
diem1.x = 10; // Gan gia tri cho thanh vien x
diem1.y = 20; // Gan gia tri cho thanh vien y
// In gia tri cua thanh vien Diem
cout << "Diem 1: (" << diem1.x << ", " << diem1.y << ")" << endl;
// Tao bien Person
Person person1;
person1.name = "Alice"; // Gan gia tri cho thanh vien name
person1.age = 30; // Gan gia tri cho thanh vien age
person1.height = 1.65f; // Gan gia tri cho thanh vien height (them 'f' cho float)
// In gia tri cua thanh vien Person
cout << "\nPerson 1:" << endl;
cout << " Name: " << person1.name << endl;
cout << " Age: " << person1.age << " years old" << endl;
cout << " Height: " << person1.height << " meters" << endl;
return 0;
}
Giải thích:
- Trong
main()
, chúng ta khai báoDiem diem1;
vàPerson person1;
. - Sử dụng
diem1.x = 10;
,diem1.y = 20;
, chúng ta gán giá trị 10 cho thành viênx
và 20 cho thành viêny
của biếndiem1
. - Tương tự,
person1.name = "Alice";
gán chuỗi "Alice" cho thành viênname
của biếnperson1
, và làm tương tự vớiage
vàheight
. - Để in giá trị, chúng ta cũng sử dụng toán tử chấm:
cout << diem1.x;
truy cập và in giá trị của thành viênx
trongdiem1
.
Khởi tạo Struct
Có nhiều cách để khởi tạo giá trị ban đầu cho các thành viên của struct khi bạn khai báo biến:
1. Khởi tạo từng thành viên (như ví dụ trên)
Cách này rõ ràng, nhưng có thể dài dòng nếu struct có nhiều thành viên.
2. Sử dụng danh sách khởi tạo (Initializer List)
Cách này tiện lợi và phổ biến, đặc biệt với các struct nhỏ. Bạn cung cấp các giá trị trong cặp dấu ngoặc nhọn {}
theo đúng thứ tự khai báo các thành viên trong struct.
#include <iostream>
#include <string>
struct Point3D {
int x;
int y;
int z;
};
struct Book {
string title;
string author;
int year;
};
int main() {
// Khoi tao Point3D su dung initializer list
Point3D p1 = {1, 2, 3};
cout << "Point p1: (" << p1.x << ", " << p1.y << ", " << p1.z << ")" << endl;
// Co the bo qua dau '=' (tu C++11 tro di)
Book book1 {"The Hobbit", "J.R.R. Tolkien", 1937};
cout << "\nBook 1:" << endl;
cout << " Title: " << book1.title << endl;
cout << " Author: " << book1.author << endl;
cout << " Year: " << book1.year << endl;
// Neu bo qua mot vai gia tri cuoi, chung se duoc khoi tao mac dinh (0 cho so, chuoi rong cho string...)
Point3D p2 = {10, 20}; // z se la 0
cout << "\nPoint p2: (" << p2.x << ", " << p2.y << ", " << p2.z << ")" << endl;
return 0;
}
Giải thích:
Point3D p1 = {1, 2, 3};
: Khai báop1
và gán giá trị 1 chox
, 2 choy
, 3 choz
ngay lập tức.Book book1 {"The Hobbit", "J.R.R. Tolkien", 1937};
: Tương tự, gán các giá trị chotitle
,author
,year
. Kể từ C++11, dấu=
có thể được bỏ qua.Point3D p2 = {10, 20};
: Nếu danh sách khởi tạo ít giá trị hơn số thành viên, các thành viên còn lại ở cuối sẽ được khởi tạo giá trị mặc định (0 cho kiểu số, chuỗi rỗng chostring
,false
chobool
, ...).
3. Sử dụng Constructor (Nâng cao hơn, giống class)
Struct trong C++ có thể có constructor, giống như class. Constructor là một hàm thành viên đặc biệt được gọi tự động khi một đối tượng của struct được tạo ra. Tuy nhiên, với mục đích nhóm dữ liệu đơn giản, initializer list thường đủ dùng. Chúng ta sẽ tìm hiểu sâu hơn về constructor khi nói về Class, nhưng hãy biết rằng struct cũng có thể có chúng.
Các trường hợp sử dụng Struct phổ biến
Struct rất hữu ích trong nhiều tình huống:
1. Nhóm dữ liệu liên quan
Đây là mục đích chính. Bất cứ khi nào bạn có một tập hợp các dữ liệu khác loại liên quan đến cùng một "thực thể", hãy nghĩ đến struct.
2. Trả về nhiều giá trị từ hàm
Một hàm chỉ có thể trả về một giá trị. Nếu bạn muốn một hàm tính toán và trả về nhiều thông tin (ví dụ: tính toán cả diện tích và chu vi của hình chữ nhật), bạn có thể định nghĩa một struct chứa các giá trị cần trả về, và hàm sẽ trả về một đối tượng struct đó.
#include <iostream>
struct RectangleInfo {
double area;
double perimeter;
};
RectangleInfo calculateRectangleInfo(double length, double width) {
RectangleInfo info;
info.area = length * width;
info.perimeter = 2 * (length + width);
return info; // Tra ve ca struct
}
int main() {
RectangleInfo myRect = calculateRectangleInfo(5.0, 10.0);
cout << "Rectangle Area: " << myRect.area << endl;
cout << "Rectangle Perimeter: " << myRect.perimeter << endl;
return 0;
}
Giải thích:
- Struct
RectangleInfo
nhómarea
vàperimeter
. - Hàm
calculateRectangleInfo
tạo một biếninfo
kiểuRectangleInfo
, tính toán và gán giá trị cho các thành viên, sau đó trả về toàn bộ struct đó. - Trong
main
, biếnmyRect
nhận struct được trả về và chúng ta có thể truy cập cảarea
vàperimeter
từmyRect
.
3. Struct lồng nhau (Nested Structs)
Một thành viên của struct có thể là một struct khác. Điều này giúp biểu diễn các mối quan hệ phức tạp hơn. Ví dụ, thông tin sinh viên có thể bao gồm cả địa chỉ, mà địa chỉ lại có số nhà, đường, thành phố...
#include <iostream>
#include <string>
struct Address {
int houseNumber;
string street;
string city;
};
struct Student {
string studentId;
string name;
Address homeAddress; // Thanh vien homeAddress la mot struct Address
};
int main() {
Student student1;
student1.studentId = "ST001";
student1.name = "Nguyen Van A";
student1.homeAddress.houseNumber = 123; // Truy cap thanh vien cua struct long nhau
student1.homeAddress.street = "Le Loi";
student1.homeAddress.city = "Ho Chi Minh City";
cout << "Student Info:" << endl;
cout << " ID: " << student1.studentId << endl;
cout << " Name: " << student1.name << endl;
cout << " Address: "
<< student1.homeAddress.houseNumber << " "
<< student1.homeAddress.street << ", "
<< student1.homeAddress.city << endl;
return 0;
}
Giải thích:
- Struct
Address
được định nghĩa trước. - Struct
Student
chứa một thành viênhomeAddress
có kiểu làAddress
. - Để truy cập các thành viên của
homeAddress
(là một struct lồng nhau), chúng ta sử dụng toán tử chấm hai lần:student1.homeAddress.houseNumber
.
4. Mảng Structs
Bạn có thể tạo một mảng mà mỗi phần tử của mảng là một struct. Điều này rất hữu ích để quản lý danh sách các đối tượng cùng loại (ví dụ: danh sách sinh viên, danh sách sản phẩm).
#include <iostream>
#include <string>
#include <vector> // Hoac dung vector<struct> cho linh hoat hon
struct Product {
int id;
string name;
double price;
};
int main() {
// Tao mot mang gom 3 struct Product
Product inventory[3];
// Khoi tao cac phan tu trong mang
inventory[0] = {101, "Laptop", 1200.50};
inventory[1] = {102, "Mouse", 25.00};
inventory[2] = {103, "Keyboard", 75.75};
// Truy cap va in thong tin tu mang struct
cout << "Inventory List:" << endl;
for (int i = 0; i < 3; ++i) {
cout << " ID: " << inventory[i].id
<< ", Name: " << inventory[i].name
<< ", Price: $" << inventory[i].price << endl;
}
// Ban cung co the dung vector<Product>
vector<Product> shoppingCart;
shoppingCart.push_back({201, "Monitor", 300.00});
shoppingCart.push_back({101, "Laptop", 1200.50}); // Them san pham da co
cout << "\nShopping Cart:" << endl;
for (const auto& item : shoppingCart) { // Lap qua vector su dung range-based for loop
cout << " ID: " << item.id
<< ", Name: " << item.name
<< ", Price: $" << item.price << endl;
}
return 0;
}
Giải thích:
- Struct
Product
được định nghĩa. Product inventory[3];
khai báo một mảng có 3 phần tử, mỗi phần tử là mộtProduct
.- Chúng ta có thể truy cập từng phần tử mảng bằng chỉ số (
inventory[i]
) và sau đó truy cập thành viên của struct tại vị trí đó bằng toán tử chấm (inventory[i].id
). - Ví dụ sử dụng
vector<Product>
cho thấy sự linh hoạt hơn khi số lượng sản phẩm không cố định.
Struct trong C++ so với Class trong C++ (Điểm khác biệt chính)
Một điều quan trọng cần biết trong C++ là struct
rất giống với class
. Trên thực tế, trong C++, sự khác biệt duy nhất giữa struct
và class
là mức độ truy cập mặc định của các thành viên:
- Trong
struct
, các thành viên và lớp cơ sở mặc định làpublic
. - Trong
class
, các thành viên và lớp cơ sở mặc định làprivate
.
Ngoài điểm khác biệt này, struct
và class
trong C++ là như nhau. Cả hai đều có thể chứa dữ liệu (thành viên), hàm (phương thức), constructor, destructor, có thể kế thừa, có thể bị giới hạn mức độ truy cập bằng các từ khóa public
, private
, protected
.
Theo quy ước phổ biến trong cộng đồng C++, struct
thường được sử dụng để định nghĩa các Cấu trúc dữ liệu thuần túy (Plain Old Data - POD) hoặc các nhóm dữ liệu đơn giản nơi tất cả các thành viên đều được dự định là public
và không có (hoặc rất ít) phương thức phức tạp. Class
thường được sử dụng khi bạn muốn đóng gói dữ liệu và hành vi (phương thức) cùng nhau và muốn kiểm soát chặt chẽ quyền truy cập vào dữ liệu (thường là làm cho dữ liệu là private
và cung cấp các phương thức public
để tương tác).
Đối với các trường hợp đơn giản như nhóm các biến lại với nhau, sử dụng struct
là phù hợp và làm cho ý định của code rõ ràng hơn.
Bài tập ví dụ: C++ Bài 19.A1: Thuế ở FullHouseLand
Thuế ở FullHouseLand
FullHouse Dev đang nghiên cứu về hệ thống thuế ở FullHouseLand. Ở đây, một khoản thuế 10 dồng sẽ được khấu trừ nếu tổng thu nhập lớn hơn 100 dồng.
Cho biết tổng thu nhập là X dồng, hãy tìm ra số tiền bạn nhận được sau khi trừ thuế (nếu có).
INPUT FORMAT
- Dòng đầu tiên chứa số nguyên T — số lượng bộ test.
- Mỗi bộ test gồm một dòng chứa một số nguyên X — tổng thu nhập của bạn.
OUTPUT FORMAT
- Với mỗi bộ test, in ra một dòng mới số tiền bạn nhận được sau khi trừ thuế.
CONSTRAINTS
- 1 ≤ T ≤ 100
- 1 ≤ X ≤ 1000
Ví dụ
Input
4
5
105
101
100
Output
5
95
91
100
Giải thích:
- Test 1: Tổng thu nhập của bạn là 5 dồng, nhỏ hơn 100 dồng. Do đó, không có thuế được khấu trừ và bạn nhận được 5 dồng.
- Test 2: Tổng thu nhập của bạn là 105 dồng, lớn hơn 100 dồng. Do đó, thuế 10 dồng sẽ được khấu trừ và bạn nhận được 105 - 10 = 95 dồng.
- Test 3: Tổng thu nhập của bạn là 101 dồng, lớn hơn 100 dồng. Do đó, thuế 10 dồng sẽ được khấu trừ và bạn nhận được 101 - 10 = 91 dồng.
- Test 4: Tổng thu nhập của bạn là 100 dồng, bằng 100 dồng. Do đó, không có thuế được khấu trừ và bạn nhận được 100 dồng. Chào bạn, đây là hướng dẫn giải bài tập "Thuế ở FullHouseLand" bằng C++ theo yêu cầu:
1. Phân tích bài toán:
- Bài toán yêu cầu xử lý nhiều bộ test (
T
bộ test). - Với mỗi bộ test, bạn được cho một số nguyên
X
là tổng thu nhập. - Luật thuế rất đơn giản: Nếu thu nhập
X
lớn hơn 100, thì bị trừ thuế 10 dồng. Ngược lại (thu nhập nhỏ hơn hoặc bằng 100), không bị trừ thuế. - Cần in ra số tiền nhận được sau khi trừ thuế (nếu có).
2. Hướng giải quyết:
- Đọc số lượng bộ test: Đầu tiên, bạn cần đọc số nguyên
T
từ đầu vào. - Lặp qua từng bộ test: Sử dụng một vòng lặp để thực hiện các bước xử lý cho mỗi bộ test. Vòng lặp này sẽ chạy
T
lần. - Trong mỗi bộ test:
- Đọc số nguyên
X
(tổng thu nhập) cho bộ test hiện tại. - Kiểm tra điều kiện thuế: Sử dụng câu lệnh điều kiện (
if
) để kiểm tra xemX
có lớn hơn 100 hay không. - Áp dụng luật thuế:
- Nếu
X > 100
(điều kiện đúng), tính số tiền nhận được bằng cách lấyX
trừ đi 10. - Nếu
X <= 100
(điều kiện sai), số tiền nhận được chính làX
(không trừ thuế).
- Nếu
- In kết quả: In số tiền đã tính được ra màn hình, và xuống dòng để chuẩn bị cho kết quả của bộ test tiếp theo.
- Đọc số nguyên
3. Các bước cụ thể bằng C++ (không phải code hoàn chỉnh):
- Bao gồm các thư viện cần thiết, ví dụ
iostream
cho nhập/xuất chuẩn. - Sử dụng
cin
để đọc dữ liệu đầu vào (T
và các giá trịX
). - Sử dụng
cout
để in kết quả đầu ra. - Dùng cấu trúc
int main() { ... }
là hàm chính của chương trình. - Sử dụng vòng lặp
while
hoặcfor
để lặpT
lần.while (T--)
là một cách phổ biến và ngắn gọn khi biết trước số lần lặp. - Bên trong vòng lặp:
- Khai báo một biến kiểu số nguyên (ví dụ
int
) để lưu giá trịX
. - Đọc
X
từcin
. - Sử dụng câu lệnh
if (X > 100)
:- Trong khối
if
: tínhX - 10
. - Trong khối
else
: kết quả làX
.
- Trong khối
- Cách khác ngắn gọn hơn cho việc tính toán kết quả: Bạn có thể sử dụng toán tử ba ngôi (
? :
) để gán giá trị cho biến kết quả dựa trên điều kiệnX > 100
. Ví dụ:ket_qua = (X > 100) ? (X - 10) : X;
- In giá trị
ket_qua
racout
, theo sau làendl
hoặc'\n'
để xuống dòng.
- Khai báo một biến kiểu số nguyên (ví dụ
4. Lưu ý:
- Đảm bảo sử dụng đúng cú pháp C++ cho câu lệnh điều kiện (
if
,else
) hoặc toán tử ba ngôi. - Sử dụng kiểu dữ liệu số nguyên đủ lớn cho
X
(constraints cho phép dùngint
). - Nhớ xuống dòng sau mỗi kết quả in ra.
Comments