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 Diemstruct Person: Chúng ta đang định nghĩa hai kiểu dữ liệu mới là DiemPerson.
  • int x;, int y;: Bên trong Diem, chúng ta khai báo hai thành viên kiểu intxy.
  • string name;, int age;, float height;: Bên trong Person, 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 {}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áo Diem diem1;Person person1;.
  • Sử dụng diem1.x = 10;, diem1.y = 20;, chúng ta gán giá trị 10 cho thành viên x và 20 cho thành viên y của biến diem1.
  • Tương tự, person1.name = "Alice"; gán chuỗi "Alice" cho thành viên name của biến person1, và làm tương tự với ageheight.
  • Để 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ên x trong diem1.

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áo p1 và gán giá trị 1 cho x, 2 cho y, 3 cho z ngay lập tức.
  • Book book1 {"The Hobbit", "J.R.R. Tolkien", 1937};: Tương tự, gán các giá trị cho title, 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 cho string, false cho bool, ...).
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óm areaperimeter.
  • Hàm calculateRectangleInfo tạo một biến info kiểu RectangleInfo, 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ến myRect nhận struct được trả về và chúng ta có thể truy cập cả areaperimeter 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ên homeAddress 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ột Product.
  • 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 structclassmứ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, structclass 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 xem X 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ấy X 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ế).
    • 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.

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ặc for để lặp T 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ính X - 10.
      • Trong khối else: kết quả là X.
    • 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ện X > 100. Ví dụ: ket_qua = (X > 100) ? (X - 10) : X;
    • In giá trị ket_qua ra cout, theo sau là endl hoặc '\n' để xuống dòng.

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ùng int).
  • Nhớ xuống dòng sau mỗi kết quả in ra.

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

Comments

There are no comments at the moment.