Liên hợp (Unions)
Liên hợp là một kiểu do người dùng định nghĩa, bao gồm các trường nằm trong cùng một vùng bộ nhớ, do đó chúng chồng lấp lên nhau. Điều này cho phép ghi một giá trị của một kiểu vào liên hợp, sau đó đọc biểu diễn bên trong của nó (ở cấp độ bit) theo cách diễn giải cho một kiểu khác. Như vậy, có thể thực hiện chuyển đổi không chuẩn từ kiểu này sang kiểu khác.
Các trường của liên hợp có thể thuộc bất kỳ kiểu tích hợp nào, ngoại trừ chuỗi, mảng động và con trỏ. Ngoài ra, trong liên hợp, bạn có thể sử dụng các cấu trúc với các kiểu trường đơn giản tương tự và không có hàm tạo/hàm hủy.
Trình biên dịch cấp phát cho liên hợp một ô bộ nhớ có kích thước bằng kích thước tối đa trong số các kiểu của tất cả các phần tử. Vì vậy, đối với liên hợp có các trường như long
(8 byte) và int
(4 byte), 8 byte sẽ được cấp phát.
Tất cả các trường của liên hợp đều nằm ở cùng một địa chỉ bộ nhớ, tức là chúng được căn chỉnh ở đầu liên hợp (chúng có độ lệch là 0, điều này có thể được kiểm tra bằng offsetof
, xem phần Đóng gói cấu trúc).
Cú pháp để mô tả một liên hợp tương tự như cấu trúc nhưng sử dụng từ khóa union
. Sau đó là một định danh, rồi một khối mã với danh sách các trường.
Ví dụ, một thuật toán có thể sử dụng mảng kiểu double
để lưu trữ nhiều cài đặt khác nhau, đơn giản vì kiểu double
là một trong những kiểu có kích thước tối đa là 8 byte. Giả sử trong số các cài đặt có các số như ulong
. Vì kiểu double
không đảm bảo tái tạo chính xác các giá trị ulong
lớn, bạn cần sử dụng liên hợp để "đóng gói" ulong
thành double
và "giải gói" nó trở lại.
#define MAX_LONG_IN_DOUBLE 9007199254740992
// FYI: ULONG_MAX 18446744073709551615
union ulong2double
{
ulong U; // 8 byte
double D; // 8 byte
};
ulong2double converter;
void OnStart()
{
Print(sizeof(ulong2double)); // 8
const ulong value = MAX_LONG_IN_DOUBLE + 1;
double d = value; // có thể mất dữ liệu do chuyển đổi kiểu
ulong result = d; // có thể mất dữ liệu do chuyển đổi kiểu
Print(d, " / ", value, " -> ", result);
// 9007199254740992.0 / 9007199254740993 -> 9007199254740992
converter.U = value;
double r = converter.D;
Print(r); // 4.450147717014403e-308
Print(offsetof(ulong2double, U), " ", offsetof(ulong2double, D)); // 0 0
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Kích thước của cấu trúc ulong2double
bằng 8 vì cả hai trường của nó đều có kích thước này. Do đó, các trường U và D chồng lấp hoàn toàn.
Trong lĩnh vực số nguyên, 9007199254740992
là giá trị lớn nhất được đảm bảo lưu trữ chắc chắn trong double
. Trong ví dụ này, chúng ta đang cố gắng lưu trữ thêm một số nữa trong double
.
Việc chuyển đổi tiêu chuẩn từ ulong
sang double
dẫn đến mất độ chính xác: sau khi ghi 9007199254740993 vào biến d
kiểu double
, chúng ta đọc từ giá trị đã "làm tròn" của nó là 9007199254740992 (để biết thêm thông tin về sự tinh tế của việc lưu trữ số trong kiểu double
, xem phần Số thực).
Khi sử dụng bộ chuyển đổi, số 9007199254740993 được ghi vào liên hợp "nguyên bản", không qua chuyển đổi, vì chúng ta gán nó vào trường U kiểu ulong
. Biểu diễn của nó theo kiểu double
có sẵn, cũng không qua chuyển đổi, từ trường D. Chúng ta có thể sao chép nó sang các biến và mảng khác như double
mà không phải lo lắng.
Mặc dù giá trị double
thu được trông kỳ lạ, nó khớp chính xác với số nguyên ban đầu nếu cần trích xuất bằng cách chuyển đổi ngược: ghi vào trường D kiểu double
, sau đó đọc từ trường U kiểu ulong
.
Liên hợp có thể có hàm tạo và hàm hủy, cũng như các phương thức. Theo mặc định, các thành viên của liên hợp có quyền truy cập công khai, nhưng điều này có thể được điều chỉnh bằng các bộ điều chỉnh quyền truy cập, như trong cấu trúc.