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 SV {
    string ten;
    int ma;
    int tuoi;
    double dtb;
};

Đ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:

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

Để 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>

using namespace std;

struct SV {
    string ten;
    int ma;
    int tuoi;
    double dtb;
};

int main() {
    SV s1;

    s1.ten = "Nguyen Van A";
    s1.ma = 101;
    s1.tuoi = 20;
    s1.dtb = 8.5;

    cout << "Thong tin Sinh Vien 1:" << endl;
    cout << "Ten: " << s1.ten << endl;
    cout << "Ma so: " << s1.ma << endl;
    cout << "Tuoi: " << s1.tuoi << endl;
    cout << "Diem trung binh: " << s1.dtb << endl;

    SV s2;
    s2.ten = "Tran Thi B";
    s2.ma = 102;
    s2.tuoi = 21;
    s2.dtb = 9.0;

    cout << "\nThong tin Sinh Vien 2:" << endl;
    cout << "Ten: " << s2.ten << endl;
    cout << "Ma so: " << s2.ma << endl;
    cout << "Tuoi: " << s2.tuoi << endl;
    cout << "Diem trung binh: " << s2.dtb << endl;

    return 0;
}

Output:

Thong tin Sinh Vien 1:
Ten: Nguyen Van A
Ma so: 101
Tuoi: 20
Diem trung binh: 8.5

Thong tin Sinh Vien 2:
Ten: Tran Thi B
Ma so: 102
Tuoi: 21
Diem trung binh: 9

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 Diem {
        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 Mau {
        int r;
        int g;
        int b;
    };
    
  • Sản phẩm trong cửa hàng: Tên, mã sản phẩm, giá, số lượng tồn kho.
    struct SP {
        string ten;
        string ma;
        double gia;
        int slTon;
    };
    
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>

using namespace std;

struct SV {
    string ten;
    int ma;
    int tuoi;
    double dtb;
};

int main() {
    vector<SV> ds;

    SV s1;
    s1.ten = "Nguyen Van A";
    s1.ma = 101;
    s1.tuoi = 20;
    s1.dtb = 8.5;
    ds.push_back(s1);

    SV s2;
    s2.ten = "Tran Thi B";
    s2.ma = 102;
    s2.tuoi = 21;
    s2.dtb = 9.0;
    ds.push_back(s2);

    ds.push_back({"Le Van C", 103, 22, 7.8});

    cout << "Danh sach Sinh Vien:" << endl;
    for (const auto& s : ds) {
        cout << "  - Ten: " << s.ten
                  << ", Ma so: " << s.ma
                  << ", Tuoi: " << s.tuoi
                  << ", Diem TB: " << s.dtb << endl;
    }

    return 0;
}

Output:

Danh sach Sinh Vien:
  - Ten: Nguyen Van A, Ma so: 101, Tuoi: 20, Diem TB: 8.5
  - Ten: Tran Thi B, Ma so: 102, Tuoi: 21, Diem TB: 9
  - Ten: Le Van C, Ma so: 103, Tuoi: 22, Diem TB: 7.8

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>

using namespace std;

struct SV {
    string ten;
    int ma;
    int tuoi;
    double dtb;
};

void hienThiSV(const SV& s) {
    cout << "  - Ten: " << s.ten
              << ", Ma so: " << s.ma
              << ", Tuoi: " << s.tuoi
              << ", Diem TB: " << s.dtb << endl;
}

struct Diem {
    double x;
    double y;
};

double kc(Diem d1, Diem d2) {
    double dx = d2.x - d1.x;
    double dy = d2.y - d1.y;
    return sqrt(pow(dx, 2) + pow(dy, 2));
}

Diem taoDiem(double x, double y) {
    Diem d;
    d.x = x;
    d.y = y;
    return d;
}

int main() {
    SV s3 = {"Le Van C", 103, 22, 7.8};
    cout << "Su dung ham de hien thi thong tin sinh vien:" << endl;
    hienThiSV(s3);

    Diem dA = {1.0, 2.0};
    Diem dB = taoDiem(4.0, 6.0);

    double kcAB = kc(dA, dB);
    cout << "\nKhoang cach giua Diem A(" << dA.x << ", " << dA.y << ") va Diem B(" << dB.x << ", " << dB.y << ") la: " << kcAB << endl;

    return 0;
}

Output:

Su dung ham de hien thi thong tin sinh vien:
  - Ten: Le Van C, Ma so: 103, Tuoi: 22, Diem TB: 7.8

Khoang cach giua Diem A(1, 2) va Diem B(4, 6) la: 5

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>

using namespace std;

struct Diem {
    double x;
    double y;
};

struct Tron {
    Diem tam;
    double r;
};

int main() {
    Tron h;

    h.tam.x = 5.0;
    h.tam.y = 10.0;

    h.r = 3.5;

    cout << "Hinh tron co:" << endl;
    cout << "  Tam tai: (" << h.tam.x << ", " << h.tam.y << ")" << endl;
    cout << "  Ban kinh: " << h.r << endl;

    return 0;
}

Output:

Hinh tron co:
  Tam tai: (5, 10)
  Ban kinh: 3.5

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.
#include <iostream>

int main() {
    int n;
    cin >> n;
    cout << n * 2 << endl;
    return 0;
}

Input:

4

Output:

8

Input:

9

Output:

18

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

Comments

There are no comments at the moment.