Bài 32.2: Struct trong bài toán thực tế trong C++

Chào mừng trở lại với loạt bài về C++! Trong các bài học trước, chúng ta đã làm quen với các kiểu dữ liệu cơ bản như int, double, char, bool, cùng với các cấu trúc dữ liệu mảng hay vector để lưu trữ một tập hợp các giá trị cùng kiểu. Tuy nhiên, trong thế giới thực, các "thực thể" mà chúng ta muốn mô hình hóa thường bao gồm nhiều thuộc tính với các kiểu dữ liệu khác nhau. Làm thế nào để chúng ta nhóm các thuộc tính liên quan này lại với nhau một cách gọn gàng và ý nghĩa trong C++? Câu trả lời chính là struct.

Sự Cần Thiết Của struct

Hãy tưởng tượng bạn cần lưu trữ thông tin về một sinh viên. Một sinh viên có:

  • Tên (kiểu string)
  • Mã số sinh viên (kiểu int hoặc string)
  • Tuổi (kiểu int)
  • Điểm trung bình (kiểu double)

Nếu không có cách nào để nhóm các thông tin này lại, bạn sẽ phải sử dụng các biến riêng lẻ như tenSinhVien1, maSinhVien1, tuoiSinhVien1, diemTrungBinh1, rồi tenSinhVien2, maSinhVien2, v.v. Điều này nhanh chóng trở nên rất cồng kềnh, khó quản lý, và dễ gây nhầm lẫn, đặc biệt khi bạn có danh sách nhiều sinh viên.

struct (viết tắt của structure) cho phép chúng ta tạo ra một kiểu dữ liệu tùy chỉnh mới bằng cách nhóm các biến (gọi là thành viên hoặc member) có liên quan lại với nhau dưới một tên duy nhất. Nó giống như việc tạo ra một "khuôn mẫu" hoặc "bản thiết kế" cho một thực thể.

Định Nghĩa 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, và bên trong cặp dấu ngoặc nhọn {} là danh sách các thành viên của nó. Kết thúc định nghĩa struct là dấu chấm phẩy ;.

Cú pháp cơ bản:

struct TenCuaStruct {
    kieu_du_lieu_1 ten_thanh_vien_1;
    kieu_du_lieu_2 ten_thanh_vien_2;
    // ...
    kieu_du_lieu_n ten_thanh_vien_n;
}; // <-- Dấu chấm phẩy là bắt buộc!

Ví dụ về struct SinhVien:

#include <string>

struct SinhVien {
    string ten;
    int maSo;
    int tuoi;
    double diemTrungBinh;
}; // Kết thúc định nghĩa struct SinhVien

Đoạn code trên không tạo ra bất kỳ sinh viên cụ thể nào. Nó chỉ định nghĩa một khuôn mẫu cho cái gọi là "SinhVien", quy định rằng mỗi "SinhVien" sẽ có các thuộc tính ten, maSo, tuoi, và diemTrungBinh với các kiểu dữ liệu tương ứng.

Sử Dụng struct: Tạo Đối Tượng 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 thuộc kiểu struct đó. Mỗi biến như vậy được gọi là một đối tượng (object) hoặc thể hiện (instance) của struct.

Để tạo một đối tượng SinhVien:

SinhVien sv1; // Khai báo một đối tượng SinhVien có tên là sv1

Để truy cập hoặc gán giá trị cho các thành viên của một đối tượng struct, chúng ta sử dụng toán tử chấm (.).

#include <iostream>
#include <string>

struct SinhVien {
    string ten;
    int maSo;
    int tuoi;
    double diemTrungBinh;
};

int main() {
    SinhVien sv1; // Tạo đối tượng sv1

    // Gán giá trị cho các thành viên của sv1
    sv1.ten = "Nguyen Van A";
    sv1.maSo = 101;
    sv1.tuoi = 20;
    sv1.diemTrungBinh = 8.5;

    // Truy cập và in giá trị các thành viên của sv1
    cout << "Thong tin Sinh Vien 1:" << endl;
    cout << "Ten: " << sv1.ten << endl;
    cout << "Ma so: " << sv1.maSo << endl;
    cout << "Tuoi: " << sv1.tuoi << endl;
    cout << "Diem trung binh: " << sv1.diemTrungBinh << endl;

    // Tạo một đối tượng SinhVien khác
    SinhVien sv2;
    sv2.ten = "Tran Thi B";
    sv2.maSo = 102;
    sv2.tuoi = 21;
    sv2.diemTrungBinh = 9.0;

    cout << "\nThong tin Sinh Vien 2:" << endl;
    cout << "Ten: " << sv2.ten << endl;
    cout << "Ma so: " << sv2.maSo << endl;
    cout << "Tuoi: " << sv2.tuoi << endl;
    cout << "Diem trung binh: " << sv2.diemTrungBinh << endl;

    return 0;
}

Giải thích:

  • Chúng ta định nghĩa struct SinhVien.
  • Trong main, chúng ta khai báo hai biến kiểu SinhViensv1sv2. Mỗi biến này là một bản sao độc lập của khuôn mẫu SinhVien.
  • Sử dụng toán tử ., chúng ta truy cập vào các thành viên như sv1.ten, sv1.maSo, v.v., và gán hoặc in giá trị cho chúng.
struct Trong Các Bài Toán Thực Tế Khác

Structs không chỉ hữu ích cho việc quản lý thông tin con người. Chúng có thể mô hình hóa bất cứ thứ gì có nhiều thuộc tính:

  • Điểm trong mặt phẳng: Một điểm có tọa độ x và y.
    struct Point {
        double x;
        double y;
    };
    
  • Màu sắc: Một màu có thể được biểu diễn bằng các giá trị Đỏ, Xanh lá, Xanh dương (RGB).
    struct Color {
        int r; // Red (0-255)
        int g; // Green (0-255)
        int b; // Blue (0-255)
    };
    
  • Sản phẩm trong cửa hàng: Tên, mã sản phẩm, giá, số lượng tồn kho.
    struct Product {
        string name;
        string code;
        double price;
        int stockQuantity;
    };
    
Sử Dụng struct Với Mảng và Vector

Một trong những sức mạnh lớn của struct là khả năng tạo ra các tập hợp các đối tượng struct. Thay vì quản lý danh sách sinh viên bằng các mảng riêng lẻ (tenSinhVien[], maSinhVien[], ...), chúng ta có thể dùng một mảng hoặc vector duy nhất chứa các đối tượng SinhVien.

Ví dụ với vector:

#include <iostream>
#include <string>
#include <vector> // Cần include để sử dụng vector

struct SinhVien {
    string ten;
    int maSo;
    int tuoi;
    double diemTrungBinh;
};

int main() {
    // Tạo một vector để lưu trữ danh sách SinhVien
    vector<SinhVien> danhSachSinhVien;

    // Tạo và thêm các đối tượng SinhVien vào vector
    SinhVien sv1;
    sv1.ten = "Nguyen Van A";
    sv1.maSo = 101;
    sv1.tuoi = 20;
    sv1.diemTrungBinh = 8.5;
    danhSachSinhVien.push_back(sv1); // Thêm sv1 vào vector

    SinhVien sv2;
    sv2.ten = "Tran Thi B";
    sv2.maSo = 102;
    sv2.tuoi = 21;
    sv2.diemTrungBinh = 9.0;
    danhSachSinhVien.push_back(sv2); // Thêm sv2 vào vector

    // Có thể tạo và thêm trực tiếp (sử dụng initializer list hoặc constructor trong C++11 trở lên)
    // SinhVien sv3 = {"Le Van C", 103, 22, 7.8}; // Cú pháp initializer list
    // danhSachSinhVien.push_back(sv3);

    danhSachSinhVien.push_back({"Le Van C", 103, 22, 7.8}); // Thêm sv3 trực tiếp (C++11+)


    // Duyệt qua vector và in thông tin từng sinh viên
    cout << "Danh sach Sinh Vien:" << endl;
    for (const auto& sv : danhSachSinhVien) { // Vòng lặp dựa trên phạm vi (range-based for loop)
        cout << "  - Ten: " << sv.ten
                  << ", Ma so: " << sv.maSo
                  << ", Tuoi: " << sv.tuoi
                  << ", Diem TB: " << sv.diemTrungBinh << endl;
    }

    return 0;
}

Giải thích:

  • Chúng ta khai báo vector<SinhVien> danhSachSinhVien;. Vector này bây giờ có thể chứa các đối tượng kiểu SinhVien.
  • Chúng ta tạo các đối tượng sv1, sv2 và thêm chúng vào vector bằng push_back().
  • Cú pháp {"Le Van C", 103, 22, 7.8} là một cách tiện lợi (từ C++11 trở lên) để khởi tạo một đối tượng struct ngay tại chỗ và thêm vào vector. Các giá trị phải tương ứng với thứ tự các thành viên trong struct.
  • Vòng lặp for (const auto& sv : danhSachSinhVien) duyệt qua từng đối tượng SinhVien trong vector. sv ở đây là một tham chiếu hằng đến mỗi đối tượng trong vector, giúp tránh việc sao chép đối tượng lớn và đảm bảo không sửa đổi dữ liệu gốc trong vòng lặp.
  • Bên trong vòng lặp, chúng ta lại sử dụng toán tử chấm (.) để truy cập các thành viên của đối tượng sv hiện tại.
Truyền struct Vào/Ra Hàm

Structs là các kiểu dữ liệu như bao kiểu dữ liệu khác, nên chúng ta có thể truyền chúng làm đối số cho hàm hoặc trả về chúng từ hàm.

  • Truyền theo giá trị: Hàm nhận một bản sao của struct. Thay đổi bên trong hàm sẽ không ảnh hưởng đến bản gốc. Thích hợp cho các struct nhỏ.
  • Truyền theo tham chiếu (&): Hàm làm việc trực tiếp trên đối tượng struct gốc. Thay đổi bên trong hàm sẽ ảnh hưởng đến bản gốc. Hiệu quả hơn cho các struct lớn vì tránh sao chép.
  • Truyền theo tham chiếu hằng (const &): Giống truyền theo tham chiếu, nhưng hàm không được phép thay đổi đối tượng. Cách này được khuyến khích nhất khi chỉ cần đọc dữ liệu từ struct để đảm bảo an toàn và hiệu quả.
  • Trả về struct: Hàm tính toán và trả về một đối tượng struct mới.

Ví dụ: Hàm hiển thị thông tin sinh viên và hàm tính khoảng cách giữa hai điểm.

#include <iostream>
#include <string>
#include <cmath> // Cần cho sqrt và pow

// Định nghĩa struct SinhVien
struct SinhVien {
    string ten;
    int maSo;
    int tuoi;
    double diemTrungBinh;
};

// Hàm hiển thị thông tin một sinh viên (truyền theo tham chiếu hằng)
void hienThiThongTinSinhVien(const SinhVien& sv) {
    cout << "  - Ten: " << sv.ten
              << ", Ma so: " << sv.maSo
              << ", Tuoi: " << sv.tuoi
              << ", Diem TB: " << sv.diemTrungBinh << endl;
}

// Định nghĩa struct Point
struct Point {
    double x;
    double y;
};

// Hàm tính khoảng cách giữa hai điểm (truyền theo giá trị)
double tinhKhoangCach(Point p1, Point p2) {
    double dx = p2.x - p1.x;
    double dy = p2.y - p1.y;
    return sqrt(pow(dx, 2) + pow(dy, 2));
}

// Hàm tạo một điểm mới (trả về struct)
Point taoDiem(double x_val, double y_val) {
    Point p;
    p.x = x_val;
    p.y = y_val;
    return p; // Trả về đối tượng Point
}


int main() {
    // Ví dụ với SinhVien và hàm hienThiThongTinSinhVien
    SinhVien sv3 = {"Le Van C", 103, 22, 7.8};
    cout << "Su dung ham de hien thi thong tin sinh vien:" << endl;
    hienThiThongTinSinhVien(sv3);

    // Ví dụ với Point và hàm tinhKhoangCach
    Point diemA = {1.0, 2.0}; // Khoi tao su dung initializer list
    Point diemB = taoDiem(4.0, 6.0); // Tao diem B su dung ham

    double khoangCachAB = tinhKhoangCach(diemA, diemB);
    cout << "\nKhoang cach giua Diem A(" << diemA.x << ", " << diemA.y << ") va Diem B(" << diemB.x << ", " << diemB.y << ") la: " << khoangCachAB << endl;

    return 0;
}

Giải thích:

  • Hàm hienThiThongTinSinhVien nhận đối số kiểu const SinhVien&. Điều này vừa hiệu quả (không sao chép cả struct lớn) vừa an toàn (không thể thay đổi sv bên trong hàm).
  • Hàm tinhKhoangCach nhận hai đối số kiểu Point theo giá trị. Vì struct Point khá nhỏ (chỉ 2 double), việc truyền theo giá trị không gây quá nhiều overhead và làm cho hàm đơn giản hơn.
  • Hàm taoDiem tạo ra một đối tượng Point cục bộ, gán giá trị và trả về đối tượng đó.
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 mô hình hóa các mối quan hệ phức tạp hơn.

Ví dụ: Một hình tròn có tâm là một điểm và bán kính.

#include <iostream>

// Định nghĩa struct Point trước
struct Point {
    double x;
    double y;
};

// Định nghĩa struct Circle, có một thành viên là Point
struct Circle {
    Point center; // Thanh vien 'center' la mot doi tuong Point
    double radius;
};

int main() {
    // Tao mot doi tuong Circle
    Circle myCircle;

    // Gan gia tri cho tam cua hinh tron (su dung hai lan dau cham)
    myCircle.center.x = 5.0;
    myCircle.center.y = 10.0;

    // Gan gia tri cho ban kinh
    myCircle.radius = 3.5;

    // Truy cap va in thong tin
    cout << "Hinh tron co:" << endl;
    cout << "  Tam tai: (" << myCircle.center.x << ", " << myCircle.center.y << ")" << endl;
    cout << "  Ban kinh: " << myCircle.radius << endl;

    return 0;
}

Giải thích:

  • Struct Circle có một thành viên tên là center, mà bản thân nó lại là một đối tượng kiểu Point.
  • Để truy cập tọa độ x của tâm hình tròn myCircle, chúng ta sử dụng myCircle.center.x. Toán tử chấm đầu tiên (myCircle.center) truy cập đến thành viên center (đối tượng Point) bên trong myCircle. Toán tử chấm thứ hai (.x) truy cập đến thành viên x bên trong đối tượng Point đó.

Bài tập ví dụ: C++ Bài 19.A2: Tính tổng số khóa học

Tính tổng số khóa học

FullHouse Dev đang quản lý một phần của FullHouse Learn, cụ thể là phần "Learn Problem Solving". Trong phần này, mỗi ngôn ngữ lập trình có hai khóa học. Ví dụ, đối với Python có "Python Beginner - Part 1" và "Python Beginner - Part 2". Những khóa học này giúp bạn chuẩn bị cho các cuộc thi trên FullHouse.

Hiện tại, có khóa học cho 4 ngôn ngữ, do đó có tổng cộng 8 khóa học trong phần này. Tuy nhiên, nếu có khóa học cho N ngôn ngữ, hãy tính tổng số khóa học trong phần này.

INPUT FORMAT

  • Dòng duy nhất chứa một số nguyên N, biểu thị số lượng ngôn ngữ có khóa học.

OUTPUT FORMAT

  • In ra một dòng duy nhất chứa tổng số khóa học trong phần này.

CONSTRAINTS

  • 1 ≤ N ≤ 100
Ví dụ

Input

4

Output

8

Giải thích: Nếu có 4 ngôn ngữ, thì sẽ có tổng cộng 2 * 4 = 8 khóa học.

Input

9

Output

18

Giải thích: Nếu có 9 ngôn ngữ, thì sẽ có tổng cộng 2 * 9 = 18 khóa học. Chào bạn, đây là hướng dẫn để giải bài tập "Tính tổng số khóa học" bằng C++.

Bài toán này khá đơn giản, chỉ yêu cầu bạn thực hiện một phép tính cơ bản dựa trên đầu vào.

  1. Phân tích bài toán:

    • Đầu vào là một số nguyên N, biểu thị số lượng ngôn ngữ.
    • Mỗi ngôn ngữ có đúng 2 khóa học.
    • Đầu ra là tổng số khóa học.
    • Quan hệ giữa số lượng ngôn ngữ và tổng số khóa học là gì? Nếu có N ngôn ngữ và mỗi ngôn ngữ có 2 khóa học, thì tổng số khóa học sẽ là N * 2.
  2. Các bước thực hiện trong C++:

    • Đầu tiên, bạn cần đọc giá trị của N từ đầu vào chuẩn.
    • Sau khi có giá trị N, bạn thực hiện phép nhân N * 2 để tính tổng số khóa học.
    • Cuối cùng, bạn in kết quả của phép tính này ra đầu ra chuẩn.
  3. Sử dụng thư viện chuẩn (std) và code ngắn gọn:

    • Để đọc và ghi dữ liệu trong C++, bạn nên sử dụng luồng nhập/xuất chuẩn được cung cấp bởi thư viện <iostream>. Cụ thể là cin để đọc và cout để in.
    • Bạn cần khai báo một biến kiểu số nguyên (ví dụ: int) để lưu giá trị của N.
    • Thay vì lưu kết quả N * 2 vào một biến tạm rồi mới in, bạn có thể tính toán N * 2 trực tiếp bên trong lệnh in để làm cho code ngắn gọn hơn.
    • Đừng quên thêm dòng endl hoặc '\n' vào cuối output để đảm bảo output đúng định dạng.

Tóm lại, bạn chỉ cần:

  • Include header <iostream>.
  • Trong hàm main:
    • Khai báo một biến integer.
    • Đọc giá trị cho biến đó bằng cin.
    • In kết quả của phép nhân biến đó với 2 ra màn hình bằng cout.

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

Comments

There are no comments at the moment.