Preâmbulo
Este artigo foi escrito e publicado por mim no meu site há mais de dez anos, o próprio site desde então caiu no esquecimento e eu nunca comecei a escrever algo mais inteligível em termos de artigos. Tudo o que é descrito a seguir é resultado de um estudo do C como linguagem por um cara de 20 anos e, portanto, não tem a pretensão de ser um livro, apesar do estilo de apresentação. No entanto, espero sinceramente que isso incentive os jovens desenvolvedores a mergulhar nas experiências com C, assim como eu fazia antes.
Um aviso
Este pequeno artigo provará ser completamente inútil para programadores C / C ++ experientes , mas para alguns iniciantes, pode economizar algum tempo. Quero enfatizar que a maioria dos bons livros sobre C / C ++ cobre esse tópico de maneira suficiente.
Tipagem dinâmica versus estática
Muitos idiomas interpretados usam digitação dinâmica. Esta abordagem permite armazenar valores de diferentes tipos em uma variável com o mesmo nome. A linguagem C usa tipagem forte, o que, em minha opinião, é mais do que correto. No entanto, há momentos (embora não com tanta frequência) em que seria muito mais conveniente usar a digitação dinâmica. Freqüentemente, essa necessidade está diretamente relacionada a um design ruim, mas nem sempre. Não é à toa que o Qt tem um tipo QVariant
.
Aqui, falaremos sobre a linguagem C, embora tudo descrito a seguir se aplique a C ++ também .
Void Pointer Magic
Na verdade, não há tipagem dinâmica em C, e não pode haver, mas existe um ponteiro universal cujo tipo é void *
. Declarar uma variável desse tipo, digamos, como um argumento para uma função, permite que você passe um ponteiro para uma variável de qualquer tipo, o que pode ser extremamente útil. E aqui está - o primeiro exemplo:
#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;
}
Resultado:
i_ptr: 22 d_ptr: 22.500000
Aqui, atribuímos ponteiros para o mesmo ponteiro (desculpe pela tautologia) tanto para o tipo int
quanto para o 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;
Vamos descrever a implementação dessas funções para diferentes estruturas, bem como as funções de inicialização para diferentes tipos de estruturas. O resultado está abaixo:
#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;
}
Resultado:
integer
round int: 12
double
round double: 13
Nota: apenas aquelas estruturas que precisam ser usadas como um argumento para um ponteiro void devem ser cercadas por diretivas de compilador.
Conclusão
No último exemplo, você pode ver semelhanças com o OPP, o que, em geral, é verdadeiro. Aqui criamos uma estrutura, inicializamos, definimos para os campos de valor chave e chamamos a função de arredondamento, que, a propósito, é extremamente simplificada, embora tenhamos adicionado a inferência de tipo de argumento aqui. Isso é tudo. E lembre-se de que você precisa usar essas construções com sabedoria, porque na grande maioria das tarefas sua presença não é necessária.