Digitação dinâmica C

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.








All Articles