[Tự học C++] Giới thiệu về Structs trong C++ » Cafedev.vn

Có nhiều trường hợp trong lập trình mà chúng ta cần nhiều hơn một biến để thể hiện một đối tượng. Ví dụ: để thể hiện bản thân, bạn có thể muốn lưu trữ tên, ngày sinh, chiều cao, cân nặng hoặc bất kỳ đặc điểm nào khác về bản thân. Bạn có thể làm như sau:

std::string myName;
int myBirthYear;
int myBirthMonth;
int myBirthDay;
int myHeightInches;
int myWeightPounds;

Tuy nhiên, bây giờ bạn có 6 biến độc lập không được nhóm theo bất kỳ cách nào. Nếu bạn muốn truyền thông tin của bản thân cho một hàm, bạn sẽ phải truyền từng biến riêng lẻ. Hơn nữa, nếu bạn muốn lưu trữ thông tin về người khác, bạn phải khai báo thêm 6 biến cho mỗi người khác! Như bạn thấy, điều này có thể làm cho chúng ta khó kiểm soát biến và làm rối code.

May mắn thay, C ++ cho phép chúng ta tạo các kiểu dữ liệu tổng hợp do người dùng định nghĩa. Một kiểu dữ liệu tổng hợp là một kiểu dữ liệu nhóm nhiều biến riêng lẻ lại với nhau. Một trong những kiểu dữ liệu tổng hợp đơn giản nhất là struct. Một struct(cấu trúc) cho phép chúng ta nhóm các biến của các loại dữ liệu hỗn hợp lại với nhau thành một đơn vị.

1. Cách khai báo và định nghĩa structs

Vì các structs được người dùng định nghĩa, trước tiên chúng ta phải nói cho trình biên dịch biết structs của chúng ta trông như thế nào trước khi chúng ta có thể bắt đầu sử dụng nó. Để làm điều này, chúng tôi khai báo struct của chúng ta bằng cách sử dụng từ khóa struct. Dưới đây là một ví dụ về khai báo struct

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

struct Employee
{
    short id;
    int age;
    double wage;
};

Điều này nói với trình biên dịch rằng chúng ta đang định nghĩa một cấu trúc có tên là Employee. Cấu trúc nhân viên chứa 3 biến bên trong của nó: id có tên ngắn, tuổi int được đặt tên và mức lương gấp đôi. Các biến này là một phần của cấu trúc được gọi là thành viên (hoặc các trường). Hãy nhớ rằng Nhân viên chỉ là một tuyên bố – mặc dù chúng tôi đang nói với trình biên dịch rằng cấu trúc sẽ có các biến thành viên, không có bộ nhớ được phân bổ tại thời điểm này. Theo quy ước, tên struct bắt đầu bằng chữ in hoa để phân biệt chúng với tên biến.

Chú ý: Một trong những sai lầm dễ mắc phải nhất trong C ++ là quên dấu chấm phẩy ở cuối phần khai báo struct. Điều này sẽ gây ra lỗi trình biên dịch trên dòng code tiếp theo. Các trình biên dịch hiện đại như Visual Studio 2010 sẽ cung cấp cho bạn một dấu hiệu cho thấy bạn có thể đã quên dấu chấm phẩy, nhưng các trình biên dịch cũ hơn hoặc kém tinh vi hơn có thể không gây ra lỗi thực tế.

Để sử dụng struct Employee, chúng ta chỉ cần khai báo một biến loại Employee:

Employee joe; // struct Employee is capitalized, variable joe is not

Điều này khai báo một biến loại Employee có tên là joe. Như với các biến thông thường, việc khai báo một biến struct sẽ phân bổ bộ nhớ cho biến đó.

Có thể định nghĩa nhiều biến có cùng kiểu struct:

Employee joe; // create an Employee struct for Joe
Employee frank; // create an Employee struct for Frank

2. Truy cập các thành viên của struct

Khi chúng ta định nghĩa một biến như joe , joe đề cập đến toàn bộ struct (chứa các biến thành viên của struct Employee). Để truy cập các thành viên riêng lẻ, chúng ta sử dụng toán tử lựa chọn thành viên. Dưới đây là một ví dụ về việc sử dụng toán tử lựa chọn thành viên để khởi tạo từng biến thành viên:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

Employee joe; // create an Employee struct for Joe
joe.id = 14; // assign a value to member id within struct joe
joe.age = 32; // assign a value to member age within struct joe
joe.wage = 24.15; // assign a value to member wage within struct joe
 
Employee frank; // create an Employee struct for Frank
frank.id = 15; // assign a value to member id within struct frank
frank.age = 28; // assign a value to member age within struct frank
frank.wage = 18.27; // assign a value to member wage within struct frank

Như với các biến thông thường, các biến thành viên của struct không được khởi tạo và thường sẽ chứa rác. Chúng ta phải khởi tạo chúng bằng tay.

Trong ví dụ trên, rất dễ dàng để biết biến thành viên nào thuộc về Joe và thuộc về Frank. Điều này cung cấp một mức độ tổ chức cao hơn nhiều so với các biến riêng lẻ. Hơn nữa, vì các thành viên Joe, và Frank, có cùng tên, điều này cung cấp tính nhất quán trên nhiều biến có cùng kiểu struct.

Các biến thành viên của struct hoạt động giống như các biến thông thường, do đó có thể thực hiện các hoạt động bình thường trên chúng:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

int totalAge{ joe.age + frank.age };
 
if (joe.wage > frank.wage)
    std::cout << "Joe makes more than Frank\n";
else if (joe.wage < frank.wage)
    std::cout << "Joe makes less than Frank\n";
else
    std::cout << "Joe and Frank make the same amount\n";
 
// Frank got a promotion
frank.wage += 2.50;
 
// Today is Joe's birthday
++joe.age; // use pre-increment to increment Joe's age by 1

3. Khởi tạo structs

Khởi tạo struct bằng cách gán giá trị thành viên theo thành viên từng thành viên thì hơi cồng kềnh, vì vậy C ++ hỗ trợ một cách nhanh hơn để khởi tạo struct bằng cách sử dụng danh sách khởi tạo. Điều này cho phép bạn khởi tạo một số hoặc tất cả các thành viên của một cấu trúc tại thời điểm khai báo.

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

struct Employee
{
    short id;
    int age;
    double wage;
};
 
Employee joe{ 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0
Employee frank{ 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 (default initialization)

Nếu danh sách trình khởi tạo không khởi tạo cho một số phần tử thì các phần tử đó được khởi tạo thành giá trị mặc định (ví dụ kiểu int sẽ là số 0). Trong ví dụ trên, chúng ta thấy rằng frank.wage được mặc định khởi tạo thành 0,0 vì chúng ta không chỉ định giá trị khởi tạo rõ ràng cho nó

4. C++11/14:Khởi tạo các biến thành viên không phải static

Bắt đầu với C ++ 11, nó cung cấp cho các thành viên struct không tĩnh (thành viền bình thường) một giá trị mặc định:

struct Employee
 {
     short id;
     int age;
     double wage;
 };

Employee joe{ 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0
Employee frank{ 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 (default initialization)

Biến không tĩnh (bình thường) được gán giá trị mặc định:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

struct Rectangle
{
    double length{ 1.0 };
    double width{ 1.0 };
};
 
int main()
{
    Rectangle x; // length = 1.0, width = 1.0
 
    x.length = 2.0; // you can assign other values like normal
 
    return 0;
}

Thật không may, trong C ++ 11, cú pháp khởi tạo thành viên không tĩnh không thể khởi tạo bằng danh sách khởi tạo và cú pháp khởi tạo một lần. Ví dụ: trong C ++ 11, chương trình sau sẽ không biên dịch:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

struct Rectangle
{
	double length{ 1.0 }; // non-static member initialization
	double width{ 1.0 };
};
 
int main()
{
	Rectangle x{ 2.0, 2.0 }; // uniform initialization
 
	return 0;
}

Do đó, trong C ++ 11, bạn sẽ phải quyết định xem bạn muốn sử dụng khởi tạo thành viên không tĩnh hay khởi tạo một lần. Khởi tạo một lần linh hoạt hơn, vì vậy chúng ta khuyên bạn nên gắn bó với cái đó.

Tuy nhiên, trong C++ 14, hạn chế này đã được gỡ bỏ và cả hai đều có thể được sử dụng. Nếu cả hai được cung cấp, danh sách khởi tạo / cú pháp khởi tạo một lần sẽ được ưu tiên. Trong ví dụ trên, Rectangle x sẽ được khởi tạo với chiều dài và chiều rộng 2.0. Trong C ++ 14, sử dụng cả hai nên việc khởi tạo biến thành viên không tỉnh được ưu tiên, vì nó cho phép bạn khai báo một struct có hoặc không có tham số khởi tạo và đảm bảo các thành viên được khởi tạo.

Chúng ta nói về những thành viên tĩnh trong chương sau. Hiện tại bạn đừng lo lắng về nó.

5. Gán giá trị cho structs

Trước C ++ 11, nếu chúng ta muốn gán giá trị cho các thành viên của cấu trúc, chúng ta phải thực hiện riêng lẻ:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

struct Employee
{
    short id;
    int age;
    double wage;
};
 
Employee joe;
joe.id = 1;
joe.age = 32;
joe.wage = 60000.0;

Đây là một nỗi đau, đặc biệt đối với các struct có nhiều thành viên. Trong C ++ 11, giờ đây bạn có thể gán giá trị cho các thành viên struct bằng danh sách khởi tạo:

struct Employee
{
    short id;
    int age;
    double wage;
};
 
Employee joe;
joe = { 1, 32, 60000.0 }; // C++11 only

6. Structs và hàm

Một lợi thế lớn của việc sử dụng struct trên các biến riêng lẻ là chúng ta có thể chuyển toàn bộ struct cho một hàm cần làm việc với các thành viên:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

#include <iostream>
 
struct Employee
{
    short id;
    int age;
    double wage;
};
 
void printInformation(Employee employee)
{
    std::cout << "ID:   " << employee.id << "\n";
    std::cout << "Age:  " << employee.age << "\n";
    std::cout << "Wage: " << employee.wage << "\n";
}
 
int main()
{
    Employee joe { 14, 32, 24.15 };
    Employee frank { 15, 28, 18.27 };
 
    // Print Joe's information
    printInformation(joe);
 
    std::cout << '\n';
 
    // Print Frank's information
    printInformation(frank);
 
    return 0;
}

Trong ví dụ trên, chúng ta chuyển toàn bộ struct Employee cho hàm printInform() (theo giá trị, nghĩa là đối số được sao chép vào tham số). Điều này ngăn chúng ta thay đổi giá trị từng biến riêng lẻ. Hơn nữa, nếu chúng ta quyết định thêm thành viên mới vào cấu trúc Employee của mình, chúng ta sẽ không phải thay đổi khai báo hàm hoặc gọi hàm!

Kết quả chương trình:

ID:   14
Age:  32
Wage: 24.15

ID:   15
Age:  28
Wage: 18.27

Một hàm cũng có thể trả về một struct, đó là một trong số ít cách để hàm trả về nhiều biến.

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

#include <iostream>
 
struct Point3d
{
    double x;
    double y;
    double z;
};
 
Point3d getZeroPoint()
{
    // We can create a variable and return the variable.
    Point3d temp { 0.0, 0.0, 0.0 };
    return temp;
}
 
Point3d getZeroPoint2()
{
    // We can return directly. We already specified the type
    // at the function declaration (Point3d), so we don't need
    // it again here.
    return { 0.0, 0.0, 0.0 };
}
 
Point3d getZeroPoint3()
{
    // We can use empty curly braces to zero-initialize all
    // members of `Point3d`.
    return {};
}
 
int main()
{
    Point3d zero{ getZeroPoint() };
 
    if (zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0)
        std::cout << "The point is zero\n";
    else
        std::cout << "The point is not zero\n";
 
    return 0;
}

Kết quả:

The point is zero

7. Các Struct lồng nhau

Các struct có thể chứa các struct khác. Ví dụ:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

struct Employee
{
    short id;
    int age;
    double wage;
};
 
struct Company
{
    Employee CEO; // Employee is a struct within the Company struct
    int numberOfEmployees;
};
 
Company myCompany;

Trong trường hợp này, nếu chúng ta muốn biết mức lương của CEO là gì, chúng ta chỉ cần sử dụng toán tử lựa chọn thành viên hai lần: myCompany.CEO.wage;

Điều này chọn thành viên CEO từ myCompany, và sau đó chọn thành viên lương từ bên trong CEO.

Bạn có thể sử dụng danh sách trình khởi tạo lồng nhau cho các struct lồng nhau:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: [email protected]
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

struct Employee
{
    short id;
    int age;
    double wage;
};
 
struct Company
{
    Employee CEO; // Employee is a struct within the Company struct
    int numberOfEmployees;
};
 
Company myCompany{{ 1, 42, 60000.0 }, 5 };

8. Kích thước của Struct và liên kết data trong structure

Thông thường, kích thước của một struct là tổng kích thước của tất cả các thành viên của nó, nhưng không phải lúc nào cũng vậy!

Hãy xem xét struct Employee. Trên nhiều nền tảng, short là 2 byte, int là 4 byte và double là 8 byte, vì vậy chúng ta mong muốn Employee là 2 + 4 + 8 = 14 byte. Để tìm hiểu kích thước chính xác của Employee, chúng ta có thể sử dụng toán tử sizeof:

struct Employee
{
    short id;
    int age;
    double wage;
};
 
int main()
{
    std::cout << "The size of Employee is " << sizeof(Employee) << "\n";
 
    return 0;
}

Trên máy của chúng ta có thể là:

The size of Employee is 16

Hóa ra, chúng ta chỉ có thể nói rằng kích thước của một struct sẽ ít nhất bằng kích thước của tất cả các biến mà nó chứa. Nhưng nó có thể lớn hơn! Vì lý do hiệu năng, trình biên dịch đôi khi sẽ thêm các khoảng trống vào các struct (cái này được gọi là phần đệm ).

Trong struct Employee ở trên, trình biên dịch vô hình thêm 2 byte đệm sau id thành viên, làm cho kích thước của struct là 16 byte thay vì 14. Lý do nó làm điều này nằm ngoài phạm vi của hướng dẫn này.

9. Truy cập cấu trúc trên nhiều tệp

Vì khai báo struct không chiếm bất kỳ bộ nhớ nào, nếu bạn muốn chia sẻ khai báo struct trên nhiều tệp (để bạn có thể khởi tạo các biến của kiểu struct đó trong nhiều tệp), hãy đặt khai báo struct trong file header và #include file header đó bất cứ nơi nào bạn cần nó.

Các biến struct phải tuân theo các quy tắc giống như các biến thông thường. Do đó, để làm cho một biến struct có thể truy cập được trên nhiều file, bạn có thể sử dụng từ khóa extern để làm như vậy.

10. Ghi chú cuối cùng về struct

Các struct rất quan trọng trong C ++, vì hiểu các struct là bước quan trọng đầu tiên đối với lập trình hướng đối tượng! Sau này trong các hướng dẫn này, bạn sẽ tìm hiểu về một loại dữ liệu tổng hợp khác được gọi là class, được xây dựng dựa trên các struct. Hiểu rõ các struct sẽ giúp việc chuyển đổi sang các class dễ dàng hơn nhiều.

Các struct được giới thiệu trong bài học này đôi khi được gọi là các struct dữ liệu cũ đơn giản (hoặc các struct POD) vì các thành viên đều là thành viên dữ liệu (biến đơn giản). Trong tương lai (khi chúng ta thảo luận về các class), chúng ta sẽ nói về các loại thành viên khác.

Bài tập C về Struct

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!