2013-09-02 33 views

Trả lời

232

Bạn có thể làm cho các phần tử mảng trở thành một công đoàn phân biệt đối xử, còn gọi là tagged union.

struct { 
    enum { is_int, is_float, is_char } type; 
    union { 
     int ival; 
     float fval; 
     char cval; 
    } val; 
} my_array[10]; 

Thành viên type được sử dụng để tổ chức lựa chọn mà thành viên của union được nên được sử dụng cho từng phần tử mảng. Vì vậy, nếu bạn muốn lưu trữ một int trong các yếu tố đầu tiên, bạn sẽ làm gì:

my_array[0].type = is_int; 
my_array[0].val.ival = 3; 

Khi bạn muốn truy cập vào một phần tử của mảng, trước tiên bạn phải kiểm tra các loại, sau đó sử dụng các thành viên tương ứng của công đoàn . Một tuyên bố switch rất hữu ích:

switch (my_array[n].type) { 
case is_int: 
    // Do stuff for integer, using my_array[n].ival 
    break; 
case is_float: 
    // Do stuff for float, using my_array[n].fval 
    break; 
case is_char: 
    // Do stuff for char, using my_array[n].cvar 
    break; 
default: 
    // Report an error, this shouldn't happen 
} 

Nó để lại cho các lập trình viên để đảm bảo rằng type thành viên luôn luôn tương ứng với giá trị cuối cùng được lưu trữ trong union.

+21

+1 Đây là ý nghĩa của nhiều ngôn ngữ diễn giải được viết bằng C – texasbruce

+8

@texasbruce còn được gọi là "công đoàn được gắn thẻ". Tôi cũng đang sử dụng kỹ thuật này bằng ngôn ngữ của chính mình. ;) –

+0

Wikipedia sử dụng trang định hướng cho "[công đoàn phân biệt] (http://en.wikipedia.org/wiki/Discriminated_union)" - "công đoàn rời rạc" trong lý thuyết tập hợp và, như @ H2CO3 đã đề cập, "liên kết được gắn thẻ" trong khoa học máy tính. – Izkata

6

Bạn có thể thực hiện mảng void *, với mảng được phân tách là size_t. Nhưng bạn bị mất loại thông tin.
Nếu bạn cần giữ kiểu thông tin theo cách nào đó giữ một mảng thứ ba của int (trong đó int là giá trị được liệt kê), sau đó mã hàm hoạt động phụ thuộc vào giá trị enum.

32

Sử dụng một sự kết hợp:

union { 
    int ival; 
    float fval; 
    void *pval; 
} array[10]; 

Bạn sẽ phải theo dõi các loại của mỗi yếu tố, mặc dù.

20

Yếu tố mảng cần phải có cùng kích thước, đó là lý do tại sao không thể. Bạn có thể làm việc xung quanh nó bằng cách tạo ra một variant type:

#include <stdio.h> 
#define SIZE 3 

typedef enum __VarType { 
    V_INT, 
    V_CHAR, 
    V_FLOAT, 
} VarType; 

typedef struct __Var { 
    VarType type; 
    union { 
    int i; 
    char c; 
    float f; 
    }; 
} Var; 

void var_init_int(Var *v, int i) { 
    v->type = V_INT; 
    v->i = i; 
} 

void var_init_char(Var *v, char c) { 
    v->type = V_CHAR; 
    v->c = c; 
} 

void var_init_float(Var *v, float f) { 
    v->type = V_FLOAT; 
    v->f = f; 
} 

int main(int argc, char **argv) { 

    Var v[SIZE]; 
    int i; 

    var_init_int(&v[0], 10); 
    var_init_char(&v[1], 'C'); 
    var_init_float(&v[2], 3.14); 

    for(i = 0 ; i < SIZE ; i++) { 
    switch(v[i].type) { 
     case V_INT : printf("INT %d\n", v[i].i); break; 
     case V_CHAR : printf("CHAR %c\n", v[i].c); break; 
     case V_FLOAT: printf("FLOAT %f\n", v[i].f); break; 
    } 
    } 

    return 0; 
} 

Kích thước của các yếu tố của công đoàn là kích thước của phần tử lớn nhất, 4.

9

Có một phong cách khác nhau của định tag-đoàn (bởi bất kỳ tên nào) mà IMO làm cho nó đẹp hơn để sử dụng, bằng cách loại bỏ liên minh nội bộ. Đây là kiểu được sử dụng trong Hệ thống Cửa sổ X cho những thứ như Sự kiện.

Ví dụ trong câu trả lời của Barmar đưa ra tên val cho liên minh nội bộ. Ví dụ trong câu trả lời của Sp. sử dụng một liên minh ẩn danh để tránh phải chỉ định .val. mỗi lần bạn truy cập bản ghi biến thể. Thật không may là các cấu trúc bên trong "vô danh" và các công đoàn không có sẵn trong C89 hoặc C99. Đó là một phần mở rộng trình biên dịch, và do đó vốn không di động.

Cách tốt hơn IMO là đảo ngược toàn bộ định nghĩa. Làm cho mỗi loại dữ liệu cấu trúc riêng của nó, và đặt thẻ (loại specifier) ​​vào mỗi struct.

typedef struct { 
    int tag; 
    int val; 
} integer; 

typedef struct { 
    int tag; 
    float val; 
} real; 

Sau đó, bạn bọc chúng trong một liên minh cấp cao nhất.

typedef union { 
    int tag; 
    integer int_; 
    real real_; 
} record; 

enum types { INVALID, INT, REAL }; 

Bây giờ nó có thể xuất hiện mà chúng ta đang lặp lại chính mình, và chúng tôi .Nhưng hãy xem xét rằng định nghĩa này có thể bị cô lập thành một tệp duy nhất. Nhưng chúng tôi đã loại bỏ tiếng ồn của việc chỉ định trung gian .val. trước khi bạn truy cập dữ liệu.

record i; 
i.tag = INT; 
i.int_.val = 12; 

record r; 
r.tag = REAL; 
r.real_.val = 57.0; 

Thay vào đó, nó sẽ kết thúc, ở đó ít khó chịu hơn. : D

Một điều khác cho phép là hình thức kế thừa. Chỉnh sửa: phần này không phải là tiêu chuẩn C, nhưng sử dụng phần mở rộng GNU.

if (r.tag == INT) { 
    integer x = r; 
    x.val = 36; 
} else if (r.tag == REAL) { 
    real x = r; 
    x.val = 25.0; 
} 

integer g = { INT, 100 }; 
record rg = g; 

Đúc và đúc.


Edit: Một Gotcha phải nhận thức được là nếu bạn đang xây dựng một trong những việc này với initializers C99 được chỉ định. Tất cả các thành viên khởi tạo nên thông qua cùng một thành viên công đoàn.

record problem = { .tag = INT, .int_.val = 3 }; 

problem.tag; // may not be initialized 

Các .tag initializer có thể được bỏ qua bởi một trình biên dịch tối ưu hóa, bởi vì .int_ initializer rằng sau bí danh khu vực cùng một dữ liệu. Mặc dù chúng tôi biết bố cục (!) Và nó nên là ok. Không, không phải vậy. Sử dụng thẻ "nội bộ" thay vào đó (nó chồng lên thẻ bên ngoài, giống như chúng ta muốn, nhưng không nhầm lẫn trình biên dịch).

record not_a_problem = { .int_.tag = INT, .int_.val = 3 }; 

not_a_problem.tag; // == INT 
+0

'.int_.val' không bí danh cùng một khu vực mặc dù trình biên dịch biết rằng' .val' có độ lệch lớn hơn '.tag'. Bạn có liên kết để thảo luận thêm về vấn đề bị cáo buộc này không? –

2

Union là cách tiêu chuẩn để đi. Nhưng bạn cũng có các giải pháp khác.

Một là tagged pointer. Điều đó tận dụng lợi thế của bộ nhớ liên kết, trong đó các bit địa chỉ thấp luôn bằng không. Ví dụ trong hệ thống 32 bit, con trỏ tới int phải là bội số của 4 và 2 bit thấp phải là 0, do đó bạn có thể sử dụng nó để lưu trữ loại giá trị của bạn. Tất nhiên bạn cần phải xóa các bit trước khi giá trị dereferencing.

void* tp; // tagged pointer 
enum { is_int, is_double, is_char_p, is_char } type; 
// ... 
intptr_t addr = (intptr_t)tp & ~0x03; // clear the 2 low bits in the pointer 
switch ((intptr_t)tp & 0x03) // check the 2 low bits for the type 
{ 
case is_int: // data is int 
    printf("%d\n", *((int*)addr)); 
    break; 
case is_double: // data is double 
    printf("%f\n", *((double*)addr)); 
    break; 
case is_char_p: // data is char* 
    printf("%s\n", (char*)addr); 
    break; 
case is_char: // data is char 
    printf("%c\n", *((char*)addr)); 
    break; 
} 

Nếu bạn có thể đảm bảo dữ liệu được căn chỉnh 8 byte, bạn sẽ có thêm một chút cho thẻ. Trên hầu hết các hệ thống 64 bit hiện tại, địa chỉ ảo vẫn là 48 bit, do đó 16 bit cao cũng có thể được sử dụng làm thẻ.

Điều này có một sự mất khả năng bạn sẽ cần nhiều bộ nhớ hơn nếu dữ liệu chưa được lưu trữ ở bất kỳ đâu. Do đó trong trường hợp loại và phạm vi dữ liệu của bạn bị giới hạn, bạn có thể lưu trữ các giá trị trực tiếp trong con trỏ. Điều này đã được sử dụng trong động cơ V8 của Chrome, nơi nó kiểm tra bit ít quan trọng nhất của địa chỉ để xem đó có phải là con trỏ để tăng gấp đôi hoặc giá trị đã ký 31 bit (được gọi là số nguyên nhỏ) không. Nếu đó là int, Chrome chỉ cần thực hiện phép dịch số học 1 bit để nhận giá trị, nếu không con trỏ sẽ bị hủy tham chiếu.

Trong các phiên bản trước của Mozilla Firefox, họ cũng sử dụng tối ưu hóa số nguyên nhỏ như V8, với 3 bit thấp được sử dụng để lưu trữ loại (int, string, object ... vv). Nhưng kể từ khi JaegerMonkey họ lấy một con đường khác (Mozilla’s New JavaScript Value Representation). Giá trị bây giờ luôn được lưu trữ trong một biến chính xác đôi 64 bit. Khi double là một chuẩn hóa, nó có thể được sử dụng trực tiếp trong các phép tính. Tuy nhiên nếu 16 bit cao của tất cả là 1s, biểu thị một NaN, 32 bit thấp sẽ lưu địa chỉ (trong máy tính 32 bit) vào giá trị hoặc giá trị trực tiếp, 16 bit còn lại sẽ được sử dụng để lưu trữ loại. Kỹ thuật này được gọi là NaN-boxing. Nếu loại dữ liệu chính của bạn là dấu phẩy động, đây là giải pháp tốt nhất và mang lại hiệu suất rất tốt.Trong các máy 64 bit, nó cũng có thể được sử dụng, vì địa chỉ thường chỉ có 48 bit như đã nêu ở trên.

Đọc thêm về các kỹ thuật trên: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations