Bài 32.5: Bài tập thực hành Struct trong C++

Bài 32.5: Bài tập thực hành Struct trong C++
Chào mừng trở lại với chuỗi bài học C++ trên FullhouseDev! Ở bài trước, chúng ta đã cùng nhau tìm hiểu về lý thuyết và cú pháp cơ bản của Struct - một kiểu dữ liệu do người dùng định nghĩa cực kỳ hữu ích để gom nhóm các dữ liệu có liên quan lại với nhau.
Lý thuyết là một chuyện, nhưng để thực sự nắm vững và áp dụng hiệu quả, chúng ta cần phải thực hành! Bài viết này sẽ tập trung vào việc thực hành sử dụng struct
thông qua nhiều ví dụ trực quan, giúp bạn củng cố kiến thức và sẵn sàng sử dụng struct
trong các dự án của mình.
Hãy cùng xắn tay áo lên và bắt đầu nhé!
Struct là gì? Nhắc lại nhanh
Trước khi đi vào bài tập, hãy nhắc lại nhanh: struct
trong C++ cho phép bạn tạo ra một kiểu dữ liệu mới bằng cách kết hợp các biến (gọi là thành viên) với các kiểu dữ liệu khác nhau dưới một cái tên duy nhất. Điều này giúp mã nguồn của bạn trở nên rõ ràng, dễ đọc và dễ quản lý hơn rất nhiều khi làm việc với các đối tượng có nhiều thuộc tính.
Cú pháp cơ bản trông như thế này:
struct TenStruct {
kieu_du_lieu1 ten_thanh_vien1;
kieu_du_lieu2 ten_thanh_vien2;
// ... các thành viên khác
};
Bây giờ, chúng ta sẽ cùng nhau thực hành các tình huống sử dụng struct
phổ biến nhất.
Bài Tập 1: Struct cơ bản - Điểm trong mặt phẳng
Mục tiêu: Tạo một struct đơn giản và truy cập các thành viên của nó.
Hãy định nghĩa một struct gọi là Point
để biểu diễn một điểm trong mặt phẳng tọa độ 2D, với hai thành viên là x
và y
(cả hai đều là số nguyên). Sau đó, tạo một biến kiểu Point
, gán giá trị cho x
và y
, và in chúng ra màn hình.
#include <iostream>
// Định nghĩa struct Point
struct Point {
int x;
int y;
};
int main() {
// Tạo một biến kiểu Point
Point p1;
// Gán giá trị cho các thành viên
p1.x = 10;
p1.y = 20;
// In giá trị của các thành viên ra màn hình
cout << "Toa do diem p1: (" << p1.x << ", " << p1.y << ")" << endl;
return 0;
}
Giải thích:
- Chúng ta khai báo
struct Point
với hai thành viênint x
vàint y
. - Trong hàm
main
, chúng ta tạo một biến tên làp1
có kiểuPoint
. - Để truy cập vào các thành viên
x
vày
củap1
, chúng ta sử dụng toán tử chấm (.
). Ví dụ:p1.x
cho phép chúng ta truy cập vào thành viênx
của đối tượngp1
. - Chúng ta gán giá trị
10
chop1.x
và20
chop1.y
. - Cuối cùng, chúng ta in giá trị của
p1.x
vàp1.y
ra màn hình.
Bài Tập 2: Struct chứa nhiều kiểu dữ liệu
Mục tiêu: Tạo một struct kết hợp các kiểu dữ liệu khác nhau (int, string, double).
Hãy định nghĩa một struct tên là Product
để lưu thông tin về một sản phẩm, bao gồm:
id
(số nguyên)name
(chuỗi ký tự, dùngstring
)price
(số thực, dùngdouble
)
Tạo một biến kiểu Product
, nhập dữ liệu cho nó (có thể gán trực tiếp hoặc nhập từ bàn phím), và in thông tin sản phẩm đó ra.
#include <iostream>
#include <string>
#include <limits> // Cần cho numeric_limits
// Định nghĩa struct Product
struct Product {
int id;
string name;
double price;
};
int main() {
// Tạo một biến kiểu Product
Product laptop;
// Nhập dữ liệu từ bàn phím
cout << "Nhap ID san pham: ";
cin >> laptop.id;
// Xóa bộ đệm input để chuẩn bị cho getline
// (Cần thiết sau khi đọc số bằng cin >>)
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "Nhap ten san pham: ";
getline(cin, laptop.name);
cout << "Nhap gia san pham: ";
cin >> laptop.price;
// In thông tin sản phẩm
cout << "\n--- Thong tin San pham ---" << endl;
cout << "ID: " << laptop.id << endl;
cout << "Ten: " << laptop.name << endl;
cout << "Gia: " << laptop.price << endl;
return 0;
}
Giải thích:
- Struct
Product
kết hợpint
,string
, vàdouble
. - Chúng ta sử dụng
cin
để đọc số nguyên (id
) và số thực (price
). - Đối với
string
(name
), chúng ta sử dụnggetline(cin, laptop.name);
. Hàm này đọc cả dòng, bao gồm cả khoảng trắng, rất phù hợp cho tên sản phẩm có nhiều từ. - Quan trọng: Sau khi đọc một số bằng
cin >>
, ký tự xuống dòng (\n
) vẫn còn lại trong bộ đệm nhập liệu. Nếu chúng ta gọigetline
ngay sau đó, nó sẽ đọc ký tự xuống dòng còn sót lại và coi đó là một dòng trống. Để tránh điều này, chúng ta dùngcin.ignore(...)
để xóa phần còn lại của dòng đó khỏi bộ đệm trước khi gọigetline
. - Chúng ta lại dùng toán tử chấm (
.
) để truy cập và gán/in giá trị cho từng thành viên.
Bài Tập 3: Truyền Struct vào và trả về từ Hàm
Mục tiêu: Hiểu cách truyền struct làm tham số cho hàm và cách hàm có thể trả về một struct.
Hãy sử dụng lại struct Point
. Viết các hàm sau:
void printPoint(Point p)
: Nhận mộtPoint
và in tọa độ của nó. Truyền tham số theo giá trị.Point movePoint(Point p, int dx, int dy)
: Nhận mộtPoint
và dịch chuyển nó đidx
theo trục x,dy
theo trục y. Hàm này sẽ trả về một Point mới đã được dịch chuyển. Truyền tham số theo giá trị và trả về theo giá trị.void movePointByRef(Point& p, int dx, int dy)
: Nhận mộtPoint
tham chiếu và dịch chuyển nó đidx
,dy
. Hàm này sẽ thay đổi đối tượng Point gốc được truyền vào. Truyền tham số theo tham chiếu.
#include <iostream>
struct Point {
int x;
int y;
};
// Hàm in Point (truyền theo giá trị)
void printPoint(Point p) {
cout << "(" << p.x << ", " << p.y << ")";
}
// Hàm di chuyển Point và trả về Point mới (truyền và trả về theo giá trị)
Point movePoint(Point p, int dx, int dy) {
Point newPoint;
newPoint.x = p.x + dx;
newPoint.y = p.y + dy;
return newPoint; // Trả về một Point mới
}
// Hàm di chuyển Point (truyền theo tham chiếu) - thay đổi Point gốc
void movePointByRef(Point& p, int dx, int dy) {
p.x = p.x + dx;
p.y = p.y + dy;
// Không cần return vì đã thay đổi trực tiếp đối tượng gốc
}
int main() {
Point p1 = {5, 8}; // Khởi tạo struct dùng danh sách khởi tạo
cout << "Diem ban dau: ";
printPoint(p1); // Truyền p1 theo giá trị
// Sử dụng movePoint (trả về Point mới)
Point p2 = movePoint(p1, 2, -3); // p1 không bị thay đổi
cout << "\nDiem sau khi di chuyen (movePoint): ";
printPoint(p2);
cout << " (Diem p1 goc van la: ";
printPoint(p1); // p1 vẫn giữ giá trị ban đầu
cout << ")" << endl;
// Sử dụng movePointByRef (thay đổi Point goc)
cout << "Diem ban dau truoc khi moveByRef: ";
printPoint(p1);
movePointByRef(p1, -1, 5); // p1 se bi thay doi
cout << "\nDiem sau khi di chuyen (movePointByRef): ";
printPoint(p1); // p1 da thay doi gia tri
cout << endl;
return 0;
}
Giải thích:
printPoint(Point p)
: Khi truyềnp1
vào hàm này, một bản sao củap1
được tạo ra và gán cho tham sốp
bên trong hàm. Mọi thay đổi trênp
bên trong hàm không ảnh hưởng đếnp1
bên ngoài.movePoint(Point p, int dx, int dy)
: Tương tự,p
là bản sao. Hàm tạo mộtPoint newPoint
mới, tính toán tọa độ mới và trả vềnewPoint
. Biếnp2
trongmain
nhận giá trị củanewPoint
được trả về.p1
không thay đổi.movePointByRef(Point& p, int dx, int dy)
: Ở đây, tham sốp
là một tham chiếu (Point&
). Điều này có nghĩa làp
bên trong hàm chính là đối tượngp1
được truyền vào từmain
. Mọi thay đổi trênp
bên trong hàm sẽ ảnh hưởng trực tiếp đếnp1
bên ngoài. Đây là cách hiệu quả để hàm có thể sửa đổi dữ liệu của struct gốc, đặc biệt với các struct lớn.Lưu ý: Sử dụng
Point p1 = {5, 8};
là một cách khởi tạo nhanh struct bằng danh sách các giá trị, theo đúng thứ tự các thành viên được khai báo trong struct.
Bài Tập 4: Mảng và Vector chứa Struct
Mục tiêu: Lưu trữ nhiều đối tượng struct trong mảng hoặc vector và xử lý chúng.
Sử dụng lại struct Product
. Tạo một vector
để lưu trữ danh sách các sản phẩm. Thêm một vài sản phẩm vào vector và sau đó lặp qua vector để in thông tin chi tiết của từng sản phẩm.
#include <iostream>
#include <string>
#include <vector> // Để sử dụng vector
#include <limits> // Cần cho numeric_limits (nếu có nhập liệu)
struct Product {
int id;
string name;
double price;
};
int main() {
// Tạo một vector để lưu trữ các đối tượng Product
vector<Product> inventory;
// Thêm các sản phẩm vào vector (có thể khởi tạo trực tiếp)
Product p1 = {101, "Laptop Dell", 1200.50};
Product p2 = {102, "Mouse Logitech", 25.99};
Product p3 = {103, "Keyboard Corsair", 80.00};
inventory.push_back(p1);
inventory.push_back(p2);
inventory.push_back(p3);
// Hoặc thêm trực tiếp object tạm
// inventory.push_back({104, "Monitor Samsung", 350.75});
// In thông tin của tất cả sản phẩm trong inventory
cout << "--- Danh sach San pham trong kho ---" << endl;
// Sử dụng vòng lặp range-based for để duyệt qua vector
for (const auto& product : inventory) {
cout << "ID: " << product.id
<< ", Ten: " << product.name
<< ", Gia: " << product.price << endl;
}
// Ví dụ truy cập một sản phẩm cụ thể trong vector
if (!inventory.empty()) {
cout << "\nSan pham dau tien: " << inventory[0].name << endl;
}
return 0;
}
Giải thích:
vector<Product> inventory;
khai báo một vector có khả năng chứa các đối tượng kiểuProduct
.- Chúng ta tạo các đối tượng
Product
riêng lẻ rồi dùnginventory.push_back(...)
để thêm chúng vào cuối vector. - Hoặc bạn có thể tạo đối tượng tạm thời ngay trong lời gọi
push_back
. - Vòng lặp
for (const auto& product : inventory)
là cách hiện đại và tiện lợi để duyệt qua từng phần tử trong vector.product
ở mỗi lần lặp sẽ là một tham chiếu hằng đến mộtProduct
trong vector. Sử dụng tham chiếu hằng (const auto&
) giúp tránh việc tạo bản sao không cần thiết của các đối tượngProduct
lớn, tăng hiệu quả chương trình. - Bên trong vòng lặp, chúng ta lại dùng toán tử chấm (
.
) để truy cập các thành viên của đối tượngproduct
hiện tại. - Truy cập phần tử cụ thể giống như với vector thông thường:
inventory[index]
.
Bài Tập 5: Struct lồng nhau
Mục tiêu: Tạo một struct mà một trong các thành viên của nó lại là một struct khác.
Hãy định nghĩa một struct Address
với các thành viên street
, city
, zipCode
(đều là string
hoặc phù hợp). Sau đó, định nghĩa một struct Person
với các thành viên name
(string
) và homeAddress
có kiểu là Address
. Tạo một biến kiểu Person
và nhập/in thông tin, bao gồm cả thông tin địa chỉ.
#include <iostream>
#include <string>
#include <limits>
// Định nghĩa struct Address
struct Address {
string street;
string city;
string zipCode;
};
// Định nghĩa struct Person, chứa một thành viên kiểu Address
struct Person {
string name;
Address homeAddress; // Thành viên kiểu Address
};
int main() {
Person person1;
// Nhập thông tin cho Person và Address lồng nhau
cout << "Nhap ten nguoi: ";
getline(cin, person1.name);
cout << "Nhap duong: ";
getline(cin, person1.homeAddress.street); // Truy cap struct con
cout << "Nhap thanh pho: ";
getline(cin, person1.homeAddress.city); // Truy cap struct con
cout << "Nhap ma buu dien: ";
getline(cin, person1.homeAddress.zipCode); // Truy cap struct con
// In thông tin
cout << "\n--- Thong tin Nguoi ---" << endl;
cout << "Ten: " << person1.name << endl;
cout << "Dia chi: " << person1.homeAddress.street
<< ", " << person1.homeAddress.city
<< ", " << person1.homeAddress.zipCode << endl;
return 0;
}
Giải thích:
- Struct
Person
có một thành viên tên làhomeAddress
với kiểu dữ liệu làAddress
. - Để truy cập các thành viên của
homeAddress
(là một structAddress
bên trongperson1
), chúng ta sử dụng hai dấu chấm liên tiếp:person1.homeAddress.street
,person1.homeAddress.city
, v.v. - Việc lồng ghép struct giúp tổ chức dữ liệu một cách logic và phân cấp.
Bài Tập 6: Tính toán với dữ liệu trong Struct
Mục tiêu: Sử dụng dữ liệu từ các thành viên của struct để thực hiện các phép tính.
Sử dụng lại struct Point
. Viết một hàm double calculateDistance(Point p1, Point p2)
để tính khoảng cách giữa hai điểm trong mặt phẳng sử dụng công thức khoảng cách Euclid: $\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$.
#include <iostream>
#include <cmath> // Cần cho sqrt và pow
struct Point {
int x;
int y;
};
// Hàm tính khoảng cách giữa hai điểm
double calculateDistance(Point p1, Point p2) {
// Tính hiệu tọa độ
double dx = p2.x - p1.x;
double dy = p2.y - p1.y;
// Tính khoảng cách sử dụng công thức Euclid
return sqrt(pow(dx, 2) + pow(dy, 2));
}
int main() {
Point point_a = {1, 2};
Point point_b = {4, 6};
// Tính và in khoảng cách
double distance = calculateDistance(point_a, point_b);
cout << "Diem A: (" << point_a.x << ", " << point_a.y << ")" << endl;
cout << "Diem B: (" << point_b.x << ", " << point_b.y << ")" << endl;
cout << "Khoang cach giua A va B: " << distance << endl;
return 0;
}
Giải thích:
- Hàm
calculateDistance
nhận hai đối tượngPoint
(p1
,p2
). - Bên trong hàm, chúng ta truy cập các thành viên
x
vày
của từng điểm (p1.x
,p2.y
, v.v.) để thực hiện phép trừ. - Sử dụng hàm
pow(base, exponent)
từ thư viện<cmath>
để tính bình phương vàsqrt(value)
để tính căn bậc hai. - Kết quả khoảng cách được trả về dưới dạng
double
.
Bài tập ví dụ: C++ Bài 19.B2: Quyết định ăn đồ ngọt
Quyết định ăn đồ ngọt
FullHouse Dev đang chuẩn bị cho Halloween bằng cách mua đồ ngọt!
Cửa hàng bán cả sô cô la và kẹo.
- Mỗi thanh sô cô la có độ ngon là X.
- Mỗi viên kẹo có độ ngon là Y.
- Một gói sô cô la chứa 2 thanh, trong khi một gói kẹo chứa 5 viên.
FullHouse Dev chỉ có thể mua một gói đồ ngọt, và phải quyết định: nên mua gói nào để tối đa hóa tổng độ ngon của món đồ mua?
In ra "Chocolate" nếu gói sô cô la ngon hơn, "Candy" nếu gói kẹo ngon hơn, và "Either" nếu chúng có cùng độ ngon.
INPUT FORMAT
- Dòng đầu tiên chứa một số nguyên T, biểu thị số lượng bộ test.
- Mỗi bộ test gồm một dòng chứa hai số nguyên X và Y cách nhau bởi dấu cách — lần lượt là độ ngon của một thanh sô cô la và một viên kẹo.
OUTPUT FORMAT
Với mỗi bộ test, in ra trên một dòng mới câu trả lời:
- "Chocolate" nếu gói sô cô la ngon hơn.
- "Candy" nếu gói kẹo ngon hơn.
- "Either" nếu chúng có cùng độ ngon.
Bạn có thể in mỗi ký tự của output bằng chữ hoa hoặc chữ thường.
CONSTRAINTS
- 1 ≤ T ≤ 100
- 1 ≤ X, Y ≤ 10
Ví dụ
Input
4
5 1
5 2
5 3
3 10
Output
Chocolate
Either
Candy
Candy
Giải thích:
- Test 1: Gói sô cô la có độ ngon là 2 × 5 = 10, trong khi gói kẹo có độ ngon là 5 × 1 = 5. Sô cô la ngon hơn.
- Test 2: Gói sô cô la có độ ngon là 2 × 5 = 10, trong khi gói kẹo có độ ngon là 5 × 2 = 10. Chúng có cùng độ ngon.
- Test 3: Gói sô cô la có độ ngon là 2 × 5 = 10, trong khi gói kẹo có độ ngon là 5 × 3 = 15. Kẹo ngon hơn.
- Test 4: Gói sô cô la có độ ngon là 2 × 3 = 6, trong khi gói kẹo có độ ngon là 5 × 10 = 50. Kẹo ngon hơn. Chào bạn, đây là hướng dẫn để giải bài tập này bằng C++:
Bao gồm thư viện cần thiết: Bạn sẽ cần thư viện để xử lý nhập/xuất dữ liệu.
<iostream>
là lựa chọn tiêu chuẩn cho việc này (cin
vàcout
).Đọc số lượng bộ test: Bắt đầu bằng cách đọc giá trị
T
từ đầu vào.Vòng lặp xử lý các bộ test: Sử dụng một vòng lặp (ví dụ:
while
hoặcfor
) để chạyT
lần. Mỗi lần lặp sẽ xử lý một bộ test. Một cách phổ biến và ngắn gọn là dùngwhile (T--)
.Đọc dữ liệu cho mỗi bộ test: Bên trong vòng lặp, đọc hai số nguyên
X
vàY
cho bộ test hiện tại.Tính toán tổng độ ngon:
- Tính tổng độ ngon của gói sô cô la: Mỗi gói có 2 thanh, mỗi thanh ngon X. Tổng ngon là
2 * X
. - Tính tổng độ ngon của gói kẹo: Mỗi gói có 5 viên, mỗi viên ngon Y. Tổng ngon là
5 * Y
.
- Tính tổng độ ngon của gói sô cô la: Mỗi gói có 2 thanh, mỗi thanh ngon X. Tổng ngon là
So sánh và in kết quả: Dùng cấu trúc điều khiển
if-else if-else
để so sánh hai giá trị tổng độ ngon vừa tính được:- Nếu tổng độ ngon sô cô la lớn hơn tổng độ ngon kẹo, in ra "Chocolate".
- Nếu tổng độ ngon kẹo lớn hơn tổng độ ngon sô cô la, in ra "Candy".
- Nếu hai tổng độ ngon bằng nhau, in ra "Either".
Xuống dòng sau mỗi kết quả: Đảm bảo rằng sau khi in kết quả của mỗi bộ test, bạn in một ký tự xuống dòng (
'\n'
hoặcendl
) để kết quả của bộ test tiếp theo xuất hiện trên dòng mới.
Tóm tắt logic:
Đọc T
Trong khi T > 0:
Đọc X, Y
tinh_do_ngon_socola = 2 * X
tinh_do_ngon_keo = 5 * Y
Nếu tinh_do_ngon_socola > tinh_do_ngon_keo:
In "Chocolate"
Nếu tinh_do_ngon_keo > tinh_do_ngon_socola:
In "Candy"
Ngược lại (bằng nhau):
In "Either"
In ký tự xuống dòng
Giảm T đi 1
Đây là hướng đi chính. Bạn chỉ cần chuyển các bước này sang cú pháp C++ sử dụng các hàm và cấu trúc điều khiển cơ bản. Chú trọng sử dụng cin
và cout
từ thư viện <iostream>
.
Comments