Struct
Struct đơn giản là một kiểu dữ liệu do người dùng tự định nghĩa từ các kiểu dữ liệu khác(có thể từ các kiểu dữ liệu cơ bản như int, short, long, char... hoặc từ những kiểu dữ liệu khác mà người dùng đã định nghĩa). Nó cho phép bạn nhóm các phần tử có kiểu dữ liệu khác nhau, trái ngược với mảng vì mảng cho phép nhóm các phần tử cùng kiểu.Struct thường dùng như một bản ghi, ví dụ như khi bạn muốn lưu thông tin một quyển sách thì bạn có thể định nghĩa một struct chứa các phần tử như sau:
- char * title: tiêu đề sách.
- char * author: tác giả.
- int year: năm phát hành.
Để khai báo 1 struct ta có nhiều cách, ở đây mình giới thiệu cách thông dụng nhất. Cấu trúc khai báo 1 struct như sau:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct struct_name { | |
data_type1 data_name1;// khai báo thành viên dữ liệu thứ 1 | |
data_type2 data_name2;// khai báo thành viên dữ liệu thứ 2 | |
data_type3 data_name3;// khai báo thành viên dữ liệu thứ 3 | |
........ | |
};// chú ý dấu ; ở cuối khai báo struct |
Mình sẽ lấy ví dụ về kiểu int và kiểu struct_name(vừa định nghĩa) để so sánh:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
int val;// khai báo biến val kiểu int | |
struct_name val;// khai báo biến val kiểu struct_name | |
int* val;// khai báo biến val là con trỏ int | |
struct_name* val;// khai báo biến val là con trỏ kiểu struct_name |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct Student { | |
char* name; | |
char sex; | |
int age; | |
float totalPoint; | |
}; |
Khởi tạo 1 sinh viên, có 2 cách: hoặc là khai báo như khai báo biến hoặc là cấp phát động.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// cách 1: khai báo như khai báo biên, bộ nhớ được cấp phát trong stack | |
Student student1; | |
// cách 2: khai báo bằng cấp phát động, bộ nhớ được cấp phát trong heap | |
// chú ý phải giải phóng bộ nhớ sau khi sử dụng | |
Student* student2 = new Student(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// với cách 1: ta sử dụng dấu . để truy suất đến từng thành viên dữ liệu của struct | |
Student student1;// khai báo student1 là 1 sinh viên thuộc kiểu Student | |
student1.sex = 1;// gán 1 cho biến sex của student1 | |
student1.age = 20;// gán tuổi của student1 là 20 | |
printf("Total point is %0.2f\n", student1.totalPoint);// hiển thị tổng điểm của student1 | |
// với cách 2: ta sử dụng -> để truy suất đến từng thành viên dữ liệu của struct | |
Student * student2 = new Student();// khai báo student2 là 1 sinh viên thuộc kiểu Student | |
student2->sex = 1;// gán 1 cho biến sex của student2 | |
student2->age = 20;// gán tuổi của student2 là 20 | |
printf("Total point is %0.2f\n", student2->totalPoint);// hiển thị tổng điểm của student2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* với cách 1 */ | |
Student student1;// khai báo student1 | |
// gán thông tin cho student1 | |
.... | |
Student student2;// khai báo student2 | |
// gán thông tin choh studen2 | |
.... | |
student1 = student2;// gán thông tin của student2 cho student1 | |
/* với cách 2 */ | |
Student* student1 = new Student();// khai báo student1 | |
// gán thông tin cho student1 | |
.... | |
Student* student2 = new Student();// khai báo student2 | |
// gán thông tin choh studen2 | |
.... | |
*student1 = *student2;// gán thông tin của student2 cho student1 | |
// với cách 2 này ta không dùng student1 = student2 vì như thế là gán 2 con trỏ cho nhau |
Về cơ bản thì cách sử dụng struct đã được mình đề cập ở bên trên, bây giờ ta sẽ đến với phần hay ho hơn đó là đi sâu vào tìm hiểu struct.
Vấn đề bộ nhớ trong struct
Kích thước bộ nhớ của 1 struct được tính bằng tổng kích thước của các thành viên, như ở ví dụ trên thì tổng kích thước Student sẽ là 4 + 1 + 4 + 4 = 13 bytes. Dữ liệu các biến thành viên sẽ được lưu liên tiếp nhau trong bộ nhớ nghĩa là địa chỉ của struct chính là địa chỉ của biến đầu tiên được định nghĩa trong struct, ở ví dụ trên thì địa chỉa của 1 biến Student chính là địa chỉ của biến name trong struct Student đó. Bây giờ ta sẽ tìm hiểu khái niệm Alignment, một khái niệm khá hay ho trong C++. Để đi vào tìm hiểu nó ta sẽ chạy 1 đoạn ví dụ nhỏ như sau:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
// total = 4 + 1 + 4 + 4 = 13 bytes | |
struct Student { | |
char* name; // 4 bytes | |
char sex; // 1 byte | |
int age; // 4 bytes | |
float totalPoint; // 4 bytes | |
}; | |
int main() { | |
printf("%d\n", sizeof(Student)); | |
} |
Vâng! 16 bytes, 3 bytes ở đâu được thêm vào thế nhỉ! Nếu chú ý thì khi dùng sizeof 1 struct bất kỳ kết quả sẽ luôn là bội của 4, lý do ở đây là cách cấp phát bộ nhớ cho từng biến trong C++.
Để tăng tốc độ truy suất thì C++ chia khối bộ nhớ thành các block, mỗi block mặt định sẽ là 4 bytes, mỗi biến trong struct sẽ nằm trong 1 hoặc nhiều block liên tiếp nhau. Mỗi lần truy suất dữ liệu của 1 biến thì nó sẽ đọc luôn cả 1 hoặc nhiều block. Giả sử ta có 1 biến char 1 byte nhưng nó vẩn chiếm trọn 1 block 4 bytes nghĩa là thừa ra 3 bytes, mỗi lần truy suất đến biến char đó thì hệ thống phải đọc hoặc ghi 1 lúc 4 bytes, điểm lớn nhất ở đây chính là tốc độ đọc ghi nhanh hơn nhiều so với việc truy suất từng byte. Kích thước mỗi block có thể được thay đổi trong thiết đặt trình biên dịch và nó chính là Alignment mà mình muốn nói đến. Alignment chính là cách mà trình biên dịch cấp phát cho từng biến thành viên của struct trong bộ nhớ.
Ở ví dụ trên thì các block của Student được chia như sau:
Dưới đây là đoạn code thực tế để kiểm tra sơ đồ bên trên:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
// total = 4 + 1 + 4 + 4 = 13 bytes | |
struct Student { | |
char* name; // 4 bytes | |
char sex; // 1 byte | |
int age; // 4 bytes | |
float totalPoint; // 4 bytes | |
}; | |
int main() { | |
printf("%d\n", sizeof(Student)); | |
Student student; | |
printf("%d %d %d %d %d\n", &student, | |
&student.name, | |
&student.sex, | |
&student.age, | |
&student.totalPoint); | |
} |
Như đã thấy, địa chỉ của biến student chính là địa chỉ của biến name, biến name tốn 4 bytes từ 36 đến 39(36, 37, 38 và 39), tiếp theo biến sex tốn 1 byte là 40 nhưng biến age lại bắt đầu từ 44 nghĩa là 3 bytes 41, 42 và 43 đã bị bỏ qua(chúng là các bytes thừa), tiếp theo sau 4 bytes của biến age là 4 bytes của biến totalPoint từ địa chỉ 48 trở đi.
Các bạn có thể thay đổi giá trị kích thước của mỗi block bằng cách vào properties của project và chọn như hình:
Mặt định default sẽ là 4 bytes, kích thước tối đa hổ trợ ở đây là 16 bytes. Nếu muốn xác định cụ thể kích thước thật của struct hoặc không muốn sử dụng chức năng Alignment thì bạn có thể set kích thước block tới 1 byte.
Sử dụng struct nâng cao
Ở phần này mình sẽ hướng dẩn 1 số cách sử dụng nâng cao của struct trong C++.Đầu tiên là hàm dựng trong struct, nếu bạn chưa tìm hiểu qua OOP thì bạn có thể hiểu hàm dựng là phương thức sẽ được gọi đầu tiên nhất khi ta khai báo mỗi biến struct. Hàm dựng là hàm có tên trùng với tên struct và không có kiểu trả về(thực ra nó sẽ trả về chính thực thể struct mới được tạo ra).
Ở ví dụ trên ta có thể viết hàm dựng cho struct Student của ta như sau:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// total = 4 + 1 + 4 + 4 = 13 bytes | |
struct Student { | |
char* name; // 4 bytes | |
char sex; // 1 byte | |
int age; // 4 bytes | |
float totalPoint; // 4 bytes | |
Student() { | |
name = new char[10]; | |
strcpy(name, "sontx"); | |
sex = 1; | |
age = 20; | |
totalPoint = 7.0; | |
} | |
Student(const char* stname, char stsex, int stage, float stpoint) { | |
name = new char[10]; | |
strcpy(name, stname); | |
sex = stsex; | |
age = stage; | |
totalPoint = stpoint; | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Student * student1 = new Student("sontx", 1, 21, 8.0); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// total = 4 + 1 + 4 + 4 = 13 bytes | |
struct Student { | |
char* name; // 4 bytes | |
char sex; // 1 byte | |
int age; // 4 bytes | |
float totalPoint; // 4 bytes | |
Student() { | |
name = new char[10]; | |
strcpy(name, "sontx"); | |
sex = 1; | |
age = 20; | |
totalPoint = 7.0; | |
} | |
Student(const char* stname, char stsex, int stage, float stpoint) { | |
name = new char[10]; | |
strcpy(name, stname); | |
sex = stsex; | |
age = stage; | |
totalPoint = stpoint; | |
} | |
~Student() { | |
delete[] name; | |
} | |
}; |
Tiếp theo là hàm dựng sao chép(hay có thể gọi là hàm khởi tạo sao chép), được gọi khi bạn khai báo 1 biến và gán giá nó cho 1 biến khác ngay lúc khai báo. Ví dụ như sau:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// khởi tạo 1 biến student1 | |
Student student1; | |
// gán biến student1 cho biến student2 ngay lúc khai báo biến student2 | |
Student student2 = student1; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// total = 4 + 1 + 4 + 4 = 13 bytes | |
struct Student { | |
char* name; // 4 bytes | |
char sex; // 1 byte | |
int age; // 4 bytes | |
float totalPoint; // 4 bytes | |
Student() { | |
name = new char[10]; | |
strcpy(name, "sontx"); | |
sex = 1; | |
age = 20; | |
totalPoint = 7.0; | |
} | |
Student(const char* stname, char stsex, int stage, float stpoint) { | |
name = new char[10]; | |
strcpy(name, stname); | |
sex = stsex; | |
age = stage; | |
totalPoint = stpoint; | |
} | |
Student(const Student& instance) { | |
name = new char[10]; | |
strcpy(name, instance.name); | |
sex = instance.sex; | |
age = instance.age; | |
totalPoint = instance.totalPoint; | |
} | |
~Student() { | |
delete[] name; | |
} | |
}; |
Một phần quan trọng nữa về struct trong C++ đó là struct có hướng đối tượng, nó chỉ khác class ở 1 điểm đó là mặt định các biến thành viên của nó là public còn class lại là private. Nếu bạn chưa tìm hiểu về OOP thì chưa cần tìm hiểu về vấn đề này.
Việc tiếp theo trong struct đó là nạp chồng toán tử, ở bài viết này mình sẽ không đề cập đến điều này vì "lười".
Các vấn đề khác trong struct
Vấn đề đầu tiên mình muốn nói đến chính là việc gán 2 biến của cùng 1 struct cho nhau. Nếu trong các biến thanh viên của struct không có con trỏ thì bạn không cần quan tâm đến vấn đề này vì cơ chế khi gán biến(gọi là thực thể trong hướng đối tượng) này cho biến khác(cùng 1 struct nhé) trong struct là nó sẽ gán lần lượt từng giá trị thành viên dữ liệu của biến này cho biến kia kia. Nếu trong struct có chứa con trỏ thì nó sẽ gán địa chỉ mà con trỏ này đang lưu vào con trỏ kia, bây giờ ta có 2 con trỏ cùng trỏ đến 1 vùng nhớ, chuyển gì sẽ xảy ra nếu ta giải phóng 1 trong 2 biến struct(dĩ nhiên là sẽ phải giải phóng các vùng nhớ được cấp phát động cho các biến thành viên trước đó), vùng nhớ mà 2 con trỏ trong 2 biến struct đang trỏ chung đến sẽ bị giải phóng trong khi vẩn còn 1 biến struct được sử dụng, 1 bug rất nguy hiểm. Ví dụ như bên dưới:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <string.h> | |
// total = 4 + 1 + 4 + 4 = 13 bytes | |
struct Student { | |
char* name; // 4 bytes | |
char sex; // 1 byte | |
int age; // 4 bytes | |
float totalPoint; // 4 bytes | |
Student() { | |
name = new char[10]; | |
strcpy(name, "sontx"); | |
sex = 1; | |
age = 20; | |
totalPoint = 7.0; | |
} | |
Student(const char* stname, char stsex, int stage, float stpoint) { | |
name = new char[10]; | |
strcpy(name, stname); | |
sex = stsex; | |
age = stage; | |
totalPoint = stpoint; | |
} | |
~Student() { | |
delete[] name; | |
} | |
}; | |
int main() { | |
Student* student1 = new Student(); | |
Student student2 = *student1; | |
delete student1; | |
printf("%s\n", student2.name); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Student(const Student& instance) { | |
name = new char[10]; | |
strcpy(name, instance.name); | |
sex = instance.sex; | |
age = instance.age; | |
totalPoint = instance.totalPoint; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Student& operator = (const Student& instance) { | |
if(name != NULL) | |
delete[] name; | |
name = new char[10]; | |
strcpy(name, instance.name); | |
sex = instance.sex; | |
age = instance.age; | |
totalPoint = instance.totalPoint; | |
} |
Union
Tiếp theo ta sẽ tìm hiểu về anh em họ của struct là union. Union khá giống struct và chỉ khác 1 điểm đó là kích thước của union bằng với kích thước của thành viên dữ liệu có kích thước lớn nhất, các thành viên dữ liệu của union sẽ chia sẽ chung 1 không gian nhớ. Ta có ví dụ sau:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
union MyUnion { | |
int a; | |
int b; | |
char c; | |
bool d; | |
}; |
Ví dụ nhỏ về sử dụng kết hợp giữa struct và union
Phần cuối này mình sẽ hướng dẩn cách lưu giá trị màu sắt trong C++. Như đã biết, 1 màu sẽ gồm 3 giá trị là R, G và B, một số khác sẽ có thêm giá trị A(alpha) với mỗi giá trị sẽ là 1 số nguyên từ 0 đến 255(tốn 1 byte lưu trữ). Với màu hổ trợ alpha ta cần 4 bytes lưu trữ, bài toán của ta là làm sao lưu trữ và chuyển đổi giữa giá trị nguyên của 1 màu sang các giá trị thành phần A, R, G và B. Bên dưới là đáp án của chúng ta:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
union Color { | |
int value; | |
struct { | |
unsigned char a; | |
unsigned char r; | |
unsigned char g; | |
unsigned char b; | |
}; | |
}; |
Ta có ví dụ về việc sử dụng union trên như sau:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <string.h> | |
union Color { | |
int value; | |
struct { | |
unsigned char a; | |
unsigned char r; | |
unsigned char g; | |
unsigned char b; | |
}; | |
}; | |
int main() { | |
Color color; | |
color.a = 0x22; | |
color.r = 0x1F; | |
color.g = 0x05; | |
color.b = 0xAC; | |
printf("%0.8X\n", color.value); | |
} |
Như đã thấy, giá trị biến value chính là giá trị của 4 bytes từ 4 biến a, r, g và b. Chú ý là bit thấp ở bên phải vì thế nên giá trị của a sẽ nằm bên phải cùng tiếp đến là biến r, g và b ở bên trải cùng(big-endian). Bạn có thể sử dụng union để tách các byte của một biến, ví dụ bạn có một biến long 8 bytes và bạn muốn tách biến này ra làm 8 bytes riêng lẻ để lưu vào 8 biến khác thì bạn có thể định nghĩa một union tương tự như Color bên trên.
0 nhận xét :
Post a Comment