Pembukaan
Artikel ini ditulis dan diterbitkan oleh saya di situs saya lebih dari sepuluh tahun yang lalu, situs itu sendiri telah dilupakan, dan saya tidak pernah mulai menulis sesuatu yang lebih dapat dipahami dalam hal artikel. Semua yang dijelaskan di bawah ini adalah hasil studi C sebagai bahasa oleh seorang pria berusia dua puluh tahun, dan, oleh karena itu, tidak mengklaim sebagai buku teks, terlepas dari gaya penyajiannya. Namun, saya sangat berharap ini akan mendorong pengembang muda untuk terjun bereksperimen dengan C seperti yang pernah saya lakukan.
Sebuah peringatan
Artikel singkat ini akan terbukti sama sekali tidak berguna untuk programmer C / C ++ berpengalaman , tetapi untuk beberapa pemula, ini mungkin menghemat waktu. Saya ingin menekankan bahwa sebagian besar buku bagus tentang C / C ++ mencakup topik ini sampai batas tertentu.
Pengetikan dinamis versus statis
Banyak bahasa yang ditafsirkan menggunakan pengetikan dinamis. Pendekatan ini memungkinkan menyimpan nilai dari berbagai jenis dalam variabel dengan nama yang sama. Bahasa C menggunakan pengetikan yang kuat, yang menurut saya lebih dari benar. Namun, ada kalanya (walaupun tidak terlalu sering) ketika akan jauh lebih nyaman menggunakan pengetikan dinamis. Seringkali, kebutuhan seperti itu secara langsung berkaitan dengan desain yang buruk, tetapi tidak selalu. Bukan tanpa alasan bahwa Qt memiliki tipe QVariant
.
Di sini kita akan berbicara tentang bahasa C, meskipun semua yang dijelaskan di bawah ini juga berlaku untuk C++ .
Sihir Penunjuk Kekosongan
Faktanya, tidak ada pengetikan dinamis di C, dan tidak mungkin ada, tetapi ada pointer universal yang tipenya void *
. Mendeklarasikan variabel jenis ini, katakanlah, sebagai argumen ke suatu fungsi, memungkinkan Anda untuk meneruskan pointer ke variabel jenis apa pun ke dalamnya, yang bisa sangat berguna. Dan ini dia - contoh pertama:
#include <stdio.h>
int main()
{
void *var;
int i = 22;
var = &i;
int *i_ptr = (int *)(var);
if(i_ptr)
printf("i_ptr: %d\n", *i_ptr);
double d = 22.5;
var = &d;
double *d_ptr = (double *)(var);
if(d_ptr)
printf("d_ptr: %f\n", *d_ptr);
return 0;
}
Keluaran:
i_ptr: 22 d_ptr: 22.500000
Di sini kami telah menetapkan pointer ke pointer yang sama (maaf untuk tautologinya) untuk tipe int
dan double
.
: , void *
. , — , GCC . , , :
void *var;
int i = 22;
var = (void *)(&i);
.
. :
#include <stdio.h>
int lilround(const void *arg, const char type)
{
if(type == 0) // int
return *((int *)arg); //
// double
double a = *((double *)arg);
int b = (int)a;
return b == (int)(a - 0.5) // >= 0.5
? b + 1 //
: b; //
}
int main()
{
int i = 12;
double j = 12.5;
printf("round int: %d\n", lilround(&i, 0)); //
printf("round double: %d\n", lilround(&j, 1)); //
return 0;
}
:
round int: 12 round double: 13
, , ( , ), . , - , .
, — lilround()
:
int lilround(const void *arg, const char type)
{
return type == 0
? *((int *)arg)
: ((int)*((double *)arg) == (int)(*((double *)arg) - 0.5)
? (int)(*((double *)arg)) + 1
: (int)(*((double *)arg)));
}
, — — . 0
, int
, — double
. , , , - , .
, (struct
), . , . .
? : . , , ? : type
, , , . , , . . :
typedef struct {
char type;
int value;
} iStruct;
typedef struct {
char type;
double value;
} dStruct;
. :
typedef struct {
char type;
int value;
} iStruct;
typedef struct {
double value;
char type;
} dStruct;
, , , — , double value , , .
:
#include <stdio.h>
#pragma pack(push, 1)
typedef struct {
char type; //
int value; //
} iStruct;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
char type; //
double value; //
} dStruct;
#pragma pack(pop)
int lilround(const void *arg)
{
iStruct *s = (iStruct *)arg;
if(s->type == 0) // int
return s->value; //
// double
double a = ((dStruct *)arg)->value;
int b = (int)a;
return b == (int)(a - 0.5) // >= 0.5
? b + 1 //
: b; //
}
int main()
{
iStruct i;
i.type = 0;
i.value = 12;
dStruct j;
j.type = 1;
j.value = 12.5;
printf("round int: %d\n", lilround(&i)); //
printf("round double: %d\n", lilround(&j)); //
return 0;
}
: #pragma pack(push, 1)
#pragma pack(pop)
, . , . .
iStruct
type. , .
, , void-. , , , .. void
, C++ . , :
#include <stdio.h>
int main()
{
int i = 22;
void *var = &i; // void- i
(*(int *)var)++; // void- int-,
printf("result: %d\n", i); // i
return 0;
}
: (*(int *)var)
.
C
. "" , , , . , type
:
typedef struct {
void (*printType)(); // ,
int (*round)(const void *); // ,
} uMethods;
Mari kita jelaskan implementasi fungsi-fungsi ini untuk struktur yang berbeda, serta fungsi inisialisasi untuk berbagai jenis struktur. Hasilnya di bawah ini:
#include <stdio.h>
typedef struct {
void (*printType)(); // ,
int (*round)(const void *); // ,
} uMethods;
#pragma pack(push, 1)
typedef struct {
uMethods m; //
int value; //
} iStruct;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uMethods m; //
double value; //
} dStruct;
#pragma pack(pop)
void intPrintType() // iStruct
{
printf("integer\n");
}
int intRound(const void *arg) // iStruct
{
return ((iStruct *)arg)->value; // iStruct
}
void intInit(iStruct *s) // iStruct
{
s->m.printType = intPrintType; // printType iStruct
s->m.round = intRound; // round iStruct
s->value = 0;
}
void doublePrintType() // dStruct
{
printf("double\n");
}
int doubleRound(const void *arg) // dStruct
{
double a = ((dStruct *)arg)->value;
int b = (int)a;
return b == (int)(a - 0.5) // >= 0.5
? b + 1 //
: b; //
}
void doubleInit(dStruct *s)
{
s->m.printType = doublePrintType; // printType dStruct
s->m.round = doubleRound; // round dStruct
s->value = 0;
}
int lilround(const void *arg)
{
((iStruct *)arg)->m.printType(); // , iStruct,
return ((iStruct *)arg)->m.round(arg); //
}
int main()
{
iStruct i;
intInit(&i); //
i.value = 12;
dStruct j;
doubleInit(&j); //
j.value = 12.5;
printf("round int: %d\n", lilround(&i)); //
printf("round double: %d\n", lilround(&j)); //
return 0;
}
Keluaran:
integer
round int: 12
double
round double: 13
Catatan: hanya struktur yang perlu digunakan sebagai argumen untuk pointer kosong yang harus dikelilingi oleh arahan kompiler.
Kesimpulan
Dalam contoh terakhir, Anda dapat melihat kesamaan dengan OPP, yang secara umum benar. Di sini kita membuat struktur, menginisialisasinya, mengaturnya ke bidang nilai kunci dan memanggil fungsi pembulatan, yang, omong-omong, sangat disederhanakan, meskipun kita telah menambahkan inferensi tipe argumen di sini. Itu saja. Dan ingat bahwa Anda perlu menggunakan konstruksi seperti itu dengan bijak, karena dalam sebagian besar tugas, kehadiran mereka tidak diperlukan.