ฉันต้องการจัดเก็บประเภทข้อมูลแบบผสมในอาเรย์ เราจะทำอย่างนั้นได้อย่างไร?
ฉันต้องการจัดเก็บประเภทข้อมูลแบบผสมในอาเรย์ เราจะทำอย่างนั้นได้อย่างไร?
คำตอบ:
คุณสามารถทำให้องค์ประกอบอาร์เรย์สหภาพ discriminated อาคาแท็กยูเนี่ยน
struct {
enum { is_int, is_float, is_char } type;
union {
int ival;
float fval;
char cval;
} val;
} my_array[10];
type
สมาชิกใช้ในการเก็บทางเลือกของการที่สมาชิกของunion
ถูกควรจะใช้สำหรับแต่ละองค์ประกอบอาร์เรย์ ดังนั้นหากคุณต้องการจัดเก็บint
องค์ประกอบแรกคุณจะทำ:
my_array[0].type = is_int;
my_array[0].val.ival = 3;
เมื่อคุณต้องการเข้าถึงองค์ประกอบของอาร์เรย์คุณต้องตรวจสอบประเภทก่อนจากนั้นใช้สมาชิกที่เกี่ยวข้องของสหภาพ switch
คำสั่งจะเป็นประโยชน์:
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
}
มันเหลือถึงโปรแกรมเมอร์เพื่อให้แน่ใจว่าสมาชิกเสมอสอดคล้องกับค่าสุดท้ายที่เก็บไว้ในtype
union
ใช้สหภาพ:
union {
int ival;
float fval;
void *pval;
} array[10];
คุณจะต้องติดตามประเภทขององค์ประกอบแต่ละอย่าง
องค์ประกอบอาเรย์ต้องมีขนาดเท่ากันนั่นคือสาเหตุที่มันเป็นไปไม่ได้ คุณสามารถแก้ไขได้ด้วยการสร้างประเภทชุดตัวเลือก :
#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;
}
ขนาดขององค์ประกอบของสหภาพคือขนาดขององค์ประกอบที่ใหญ่ที่สุด 4
มีลักษณะที่แตกต่างกันในการกำหนดแท็ก - ยูเนี่ยน (ตามชื่ออะไรก็ตาม) ที่ IMO ใช้ให้ดีกว่าการใช้โดยการลบสหภาพภายใน นี่คือสไตล์ที่ใช้ในระบบ X Window สำหรับสิ่งต่างๆเช่นกิจกรรม
ตัวอย่างในคำตอบของ Barmar ให้ชื่อval
แก่สหภาพภายใน ตัวอย่างในคำตอบของ Sp. ใช้ยูเนี่ยนนิรนามเพื่อหลีกเลี่ยงการระบุ.val.
ทุกครั้งที่คุณเข้าถึงเร็กคอร์ดชุดตัวเลือก น่าเสียดายที่โครงสร้างแบบไม่ระบุชื่อและสหภาพภายในไม่สามารถใช้งานได้ใน C89 หรือ C99 มันเป็นส่วนขยายของคอมไพเลอร์และดังนั้นจึงไม่ใช่แบบพกพา
วิธีที่ดีกว่า IMO คือการกลับคำจำกัดความทั้งหมด ทำให้แต่ละชนิดข้อมูลมีโครงสร้างของตนเองและใส่แท็ก (ตัวระบุชนิด) ลงในแต่ละโครงสร้าง
typedef struct {
int tag;
int val;
} integer;
typedef struct {
int tag;
float val;
} real;
จากนั้นคุณห่อสิ่งเหล่านี้ในสหภาพระดับบนสุด
typedef union {
int tag;
integer int_;
real real_;
} record;
enum types { INVALID, INT, REAL };
ตอนนี้มันอาจดูเหมือนว่าเรากำลังทำซ้ำตัวเองและเรามี แต่พิจารณาว่าคำจำกัดความนี้มีแนวโน้มที่จะแยกเป็นไฟล์เดียว แต่เราได้ขจัดเสียงรบกวนของการระบุสื่อกลาง.val.
ก่อนที่คุณจะได้รับข้อมูล
record i;
i.tag = INT;
i.int_.val = 12;
record r;
r.tag = REAL;
r.real_.val = 57.0;
แต่มันกลับกลายเป็นสิ่งที่น่ารังเกียจน้อยกว่า : D
สิ่งนี้ช่วยให้เป็นรูปแบบของการสืบทอด แก้ไข: ส่วนนี้ไม่ได้มาตรฐาน C แต่ใช้ส่วนขยาย 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;
หล่อขึ้นและลงหล่อ
แก้ไข: gotcha หนึ่งอันที่ต้องระวังคือถ้าคุณสร้างหนึ่งในสิ่งเหล่านี้ด้วย initializers ที่กำหนดไว้ C99 สมาชิกเริ่มต้นทั้งหมดควรผ่านสมาชิกสหภาพเดียวกัน
record problem = { .tag = INT, .int_.val = 3 };
problem.tag; // may not be initialized
.tag
initializer สามารถปฏิเสธโดยคอมไพเลอร์เพิ่มประสิทธิภาพเพราะ.int_
การเริ่มต้นที่ตามนามแฝงพื้นที่ข้อมูลเดียวกัน ถึงแม้ว่าเราจะรู้ว่ารูปแบบ (!) และมันควรจะ ok ไม่มันไม่ใช่ ใช้แท็ก "ภายใน" แทน (จะซ้อนทับแท็กด้านนอกเหมือนกับที่เราต้องการ แต่ไม่สับสนกับคอมไพเลอร์)
record not_a_problem = { .int_.tag = INT, .int_.val = 3 };
not_a_problem.tag; // == INT
.int_.val
ไม่นามแฝงพื้นที่เดียวกัน แต่เพราะคอมไพเลอร์รู้ว่าที่มากขึ้นกว่าชดเชย.val
.tag
คุณมีลิงค์สำหรับการอภิปรายเพิ่มเติมเกี่ยวกับปัญหาที่ถูกกล่าวหานี้หรือไม่?
คุณสามารถทำvoid *
อาเรย์กับอาเรย์ที่แยกจากกันsize_t.
แต่คุณสูญเสียชนิดข้อมูล
หากคุณต้องการเก็บชนิดข้อมูลในทางใดทางหนึ่งให้เก็บอาร์เรย์ที่สามของ int (โดยที่ int เป็นค่าที่แจกแจง) จากนั้นให้โค้ดฟังก์ชันที่ใช้งานขึ้นอยู่กับenum
ค่า
ยูเนี่ยนเป็นวิธีมาตรฐานในการเดินทาง แต่คุณมีวิธีแก้ไขปัญหาอื่น ๆ เช่นกัน หนึ่งในนั้นคือแท็กตัวชี้ซึ่งเกี่ยวข้องกับการจัดเก็บข้อมูลเพิ่มเติมในบิต"ฟรี"ของตัวชี้
ขึ้นอยู่กับสถาปัตยกรรมที่คุณสามารถใช้บิตต่ำหรือสูง แต่วิธีที่ปลอดภัยและพกพามากที่สุดคือการใช้บิตต่ำที่ไม่ได้ใช้โดยใช้ประโยชน์จากหน่วยความจำที่จัดตำแหน่ง ตัวอย่างเช่นในระบบ 32- บิตและ 64- บิตพอยน์เตอร์จะint
ต้องเป็นทวีคูณของ 4 (สมมติว่าint
เป็นประเภท 32- บิต) และ 2 บิตที่สำคัญน้อยที่สุดต้องเป็น 0 ดังนั้นคุณสามารถใช้พวกเขาเพื่อเก็บประเภทของค่าของคุณ . แน่นอนคุณจำเป็นต้องล้างแท็กบิตก่อนที่จะทำการอ้างอิงตัวชี้อีกครั้ง ตัวอย่างเช่นหากประเภทข้อมูลของคุณถูก จำกัด ไว้ที่ 4 ประเภทที่แตกต่างกันคุณสามารถใช้มันได้ด้านล่าง
void* tp; // tagged pointer
enum { is_int, is_double, is_char_p, is_char } type;
// ...
uintptr_t addr = (uintptr_t)tp & ~0x03; // clear the 2 low bits in the pointer
switch ((uintptr_t)tp & 0x03) // check the tag (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;
}
หากคุณมั่นใจได้ว่าข้อมูลมีการจัดตำแหน่งแบบ 8 ไบต์ (เช่นตัวชี้ในระบบ 64 บิตหรือlong long
และuint64_t
... ) คุณจะมีแท็กอีกหนึ่งบิต
นี่เป็นข้อเสียอย่างหนึ่งที่คุณต้องการหน่วยความจำเพิ่มเติมหากข้อมูลไม่ได้ถูกเก็บไว้ในตัวแปรที่อื่น ดังนั้นในกรณีที่ประเภทและช่วงข้อมูลของคุณมี จำกัด คุณสามารถจัดเก็บค่าได้โดยตรงในตัวชี้ เทคนิคนี้ใช้ในเครื่องมือ V8 ของ Chromeรุ่น 32 บิตซึ่งจะตรวจสอบที่อยู่บิตที่สำคัญน้อยที่สุดเพื่อดูว่าเป็นตัวชี้ไปยังวัตถุอื่น (เช่นคู่จำนวนเต็มใหญ่สตริงหรือวัตถุบางอย่าง) หรือ31 - ค่าบิตที่เซ็นชื่อ (เรียกว่าsmi
- จำนวนเต็มขนาดเล็ก ) หากเป็นint
เช่นนั้น Chrome จะทำการเปลี่ยนแปลงทางคณิตศาสตร์อย่างถูกต้อง 1 บิตเพื่อรับค่ามิฉะนั้นตัวชี้จะถูกยกเลิกการลงทะเบียน
ในระบบ 64 บิตปัจจุบันพื้นที่ที่อยู่เสมือนนั้นยังแคบกว่า 64 บิตดังนั้นบิตที่มีความสำคัญสูงสุดสามารถใช้เป็นแท็กได้ ขึ้นอยู่กับสถาปัตยกรรมคุณมีวิธีใช้แท็กต่างกัน สามารถกำหนดค่าARM , 68kและอื่น ๆ อีกมากมายให้ละเว้นบิตอันดับต้นๆ ทำให้คุณสามารถใช้งานได้อย่างอิสระโดยไม่ต้องกังวลกับ segfault หรืออะไรก็ตาม จากบทความ Wikipedia ที่ลิงก์ด้านบน:
ตัวอย่างที่สำคัญของการใช้ตัวชี้ที่ติดแท็กคือรันไทม์ Objective-C บน iOS 7 บน ARM64 โดยเฉพาะอย่างยิ่งที่ใช้บน iPhone 5S ใน iOS 7 ที่อยู่เสมือนมี 33 บิต (จัดเรียงตามแนวไบท์) ดังนั้นที่อยู่ที่จัดเรียงคำจะใช้ 30 บิตเท่านั้น (3 บิตที่สำคัญน้อยที่สุดคือ 0) เหลือ 34 บิตสำหรับแท็ก พอยน์เตอร์คลาส C ของ Objective จะจัดตำแหน่งคำและฟิลด์แท็กจะถูกใช้เพื่อวัตถุประสงค์หลายอย่างเช่นการจัดเก็บจำนวนการอ้างอิงและวัตถุมี destructor หรือไม่
MacOS เวอร์ชันก่อนหน้านี้ใช้ที่อยู่ที่ติดแท็กชื่อ Handle เพื่อจัดเก็บการอ้างอิงไปยังวัตถุข้อมูล บิตสูงของที่อยู่ที่ระบุว่าวัตถุข้อมูลถูกล็อค, ล้างทำความสะอาดได้และ / หรือมาจากไฟล์ทรัพยากรตามลำดับ สิ่งนี้ทำให้เกิดปัญหาความเข้ากันได้เมื่อ MacOS แอดเดรสขั้นสูงจาก 24 บิตเป็น 32 บิตในระบบ 7
เมื่อวันที่ x86_64 คุณยังสามารถใช้บิตที่สูงที่สุดเท่าที่แท็กด้วยความระมัดระวัง แน่นอนคุณไม่จำเป็นต้องใช้ 16 บิตเหล่านั้นทั้งหมดและสามารถทิ้งบิตไว้เพื่อใช้เป็นหลักฐานในอนาคต
ใน Mozilla Firefox เวอร์ชันก่อนหน้าพวกเขายังใช้การเพิ่มประสิทธิภาพจำนวนเต็มขนาดเล็กเช่น V8 ด้วย3 บิตต่ำที่ใช้ในการจัดเก็บประเภท (int, string, object ... ฯลฯ ) แต่เนื่องจากJägerMonkeyพวกเขาใช้เส้นทางอื่น ( การแทนค่า JavaScript ใหม่ของ Mozilla , ลิงก์สำรอง ) ค่านี้จะถูกเก็บไว้ในตัวแปรความแม่นยำสองเท่าแบบ 64 บิตเสมอ เมื่อdouble
เป็นค่าที่ถูกทำให้เป็นมาตรฐานมันสามารถใช้โดยตรงในการคำนวณ อย่างไรก็ตามถ้า 16 บิตสูงของมันคือ 1s ทั้งหมดซึ่งหมายถึงNaN , 32 บิตต่ำจะเก็บที่อยู่ (ในคอมพิวเตอร์ 32 บิต) เป็นค่าหรือค่าโดยตรง 16 บิตที่เหลือจะถูกนำมาใช้ เพื่อจัดเก็บประเภท เทคนิคนี้เรียกว่าNaN-Boxingหรือแม่ชีมวย มันยังใช้ใน JavaScriptCore 64 บิตของ WebKit และ SpiderMonkey ของ Mozilla ด้วยตัวชี้ที่ถูกจัดเก็บใน 48 บิตต่ำ หากประเภทข้อมูลหลักของคุณเป็นทศนิยมจุดนี้เป็นทางออกที่ดีที่สุดและให้ประสิทธิภาพที่ดีมาก
อ่านเพิ่มเติมเกี่ยวกับเทคนิคด้านบน: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations