Preamble
This article was written and published by me on my site more than ten years ago, the site itself has since sunk into oblivion, and I never started writing something more intelligible in terms of articles. Everything described below is the result of a study of C as a language by a twenty-year-old guy, and, therefore, does not claim to be a textbook, despite the style of presentation. However, I sincerely hope that it will encourage young developers to dive into experimenting with C just as I once did.
A warning
This short article will prove to be completely useless for experienced C / C ++ programmers , but for some beginners, it might save some time. I want to emphasize that most good books on C / C ++ cover this topic to a sufficient extent.
Dynamic versus static typing
Many interpreted languages โโuse dynamic typing. This approach allows storing values โโof different types in a variable with the same name. The C language uses strong typing, which, in my opinion, is more than correct. However, there are times (although not so often) when it would be much more convenient to use dynamic typing. Often, such a need is directly related to poor design, but not always. It's not for nothing that Qt has a type QVariant
.
Here we will talk about the C language, although everything described below applies to C ++ as well .
Void Pointer Magic
In fact, there is no dynamic typing in C, and there cannot be, but there is a universal pointer whose type is void *
. Declaring a variable of this type, say, as an argument to a function, allows you to pass a pointer to a variable of any type into it, which can be extremely useful. And here it is - the first example:
#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;
}
Output:
i_ptr: 22 d_ptr: 22.500000
Here we assigned pointers to the same pointer (sorry for the tautology) to both the type int
and the 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;
Let's describe the implementation of these functions for different structures, as well as the initialization functions for different types of structures. The result is below:
#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;
}
Output:
integer
round int: 12
double
round double: 13
Note: only those structures that need to be used as an argument for a void pointer should be surrounded by compiler directives.
Conclusion
In the last example, you can notice the similarities with the OPP, which, in general, is true. Here we create a structure, initialize it, set it to the key value fields and call the rounding function, which, by the way, is extremely simplified, although we have added the argument type inference here. That's all. And remember that you need to use such constructions wisely, because in the overwhelming majority of tasks their presence is not required.