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á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ễ đọcdễ 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à xy (cả hai đều là số nguyên). Sau đó, tạo một biến kiểu Point, gán giá trị cho xy, 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ên int xint y.
  • Trong hàm main, chúng ta tạo một biến tên là p1 có kiểu Point.
  • Để truy cập vào các thành viên xy của p1, 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ên x của đối tượng p1.
  • Chúng ta gán giá trị 10 cho p1.x20 cho p1.y.
  • Cuối cùng, chúng ta in giá trị của p1.xp1.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ùng string)
  • price (số thực, dùng double)

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ợp int, 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ụng getline(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ọi getline 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ùng cin.ignore(...) để xóa phần còn lại của dòng đó khỏi bộ đệm trước khi gọi getline.
  • 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ột Point 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ột Point và dịch chuyển nó đi dx 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ịtrả về theo giá trị.
  • void movePointByRef(Point& p, int dx, int dy): Nhận một Point tham chiếu và dịch chuyển nó đi dx, 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ền p1 vào hàm này, một bản sao của p1 được tạo ra và gán cho tham số p bên trong hàm. Mọi thay đổi trên p bên trong hàm không ảnh hưởng đến p1 bên ngoài.
  • movePoint(Point p, int dx, int dy): Tương tự, p là bản sao. Hàm tạo một Point newPoint mới, tính toán tọa độ mới và trả về newPoint. Biến p2 trong main nhận giá trị của newPoint đượ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ượng p1 được truyền vào từ main. Mọi thay đổi trên p bên trong hàm sẽ ảnh hưởng trực tiếp đến p1 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ểu Product.
  • Chúng ta tạo các đối tượng Product riêng lẻ rồi dùng inventory.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 đạitiệ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ột Product 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ượng Product 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ượng product 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 struct Address bên trong person1), 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 logicphâ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ượng Point (p1, p2).
  • Bên trong hàm, chúng ta truy cập các thành viên xy 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++:
  1. 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 (cincout).

  2. Đọc số lượng bộ test: Bắt đầu bằng cách đọc giá trị T từ đầu vào.

  3. Vòng lặp xử lý các bộ test: Sử dụng một vòng lặp (ví dụ: while hoặc for) để chạy T 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ùng while (T--).

  4. Đọc dữ liệu cho mỗi bộ test: Bên trong vòng lặp, đọc hai số nguyên XY cho bộ test hiện tại.

  5. 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.
  6. 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".
  7. 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ặc endl) để 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 cincout từ thư viện <iostream>.

Làm thêm nhiều bài tập miễn phí tại đây

Comments

There are no comments at the moment.