Escritura dinámica C

Preámbulo

Este artículo fue escrito y publicado por mí en mi sitio hace más de diez años, el sitio en sí se ha hundido en el olvido y nunca comencé a escribir algo más inteligible en términos de artículos. Todo lo que se describe a continuación es el resultado de un estudio de C como lengua por parte de un chico de veinte años y, por tanto, no pretende ser un libro de texto, a pesar del estilo de presentación. Sin embargo, espero sinceramente que anime a los desarrolladores jóvenes a sumergirse en la experimentación con C tal como lo hice una vez.





Una advertencia

Este breve artículo resultará completamente inútil para los programadores experimentados de C / C ++ , pero para algunos principiantes, puede ahorrar algo de tiempo. Quiero enfatizar que la mayoría de los buenos libros sobre C / C ++ cubren este tema de manera suficiente.





Escritura dinámica versus estática

Muchos lenguajes interpretados utilizan escritura dinámica. Este enfoque permite almacenar valores de diferentes tipos en una variable con el mismo nombre. El lenguaje C usa un tipo fuerte, que, en mi opinión, es más que correcto. Sin embargo, hay ocasiones (aunque no tan a menudo) en las que sería mucho más conveniente utilizar la escritura dinámica. A menudo, esta necesidad está directamente relacionada con un diseño deficiente, pero no siempre. No en vano, Qt tiene un tipo QVariant



.





Aquí hablaremos sobre el lenguaje C, aunque todo lo que se describe a continuación también se aplica a C ++ .





Magia de puntero del vacío

De hecho, no hay escritura dinámica en C, y no puede haberla, pero hay un puntero universal cuyo tipo es void *



. Declarar una variable de este tipo, digamos, como argumento de una función, le permite pasarle un puntero a una variable de cualquier tipo, lo que puede ser extremadamente útil. Y aquí está, el primer ejemplo:





#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;
}
      
      



Producción:





i_ptr: 22
d_ptr: 22.500000
      
      



Aquí hemos asignado punteros al mismo puntero (perdón por la tautología) tanto para el tipo int



como para el 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;
      
      



Describamos la implementación de estas funciones para diferentes estructuras, así como las funciones de inicialización para diferentes tipos de estructuras. El resultado es el siguiente:





#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;
}
      
      



Producción:





integer
round int: 12
double
round double: 13
      
      



Nota: solo las estructuras que deben usarse como argumento para un puntero vacío deben estar rodeadas por directivas del compilador.





Conclusión

En el último ejemplo, puede ver similitudes con el OPP, que, en general, es cierto. Aquí creamos una estructura, la inicializamos, la configuramos en los campos de valor clave y llamamos a la función de redondeo, que, por cierto, está extremadamente simplificada, aunque aquí hemos agregado la inferencia de tipo de argumento. Eso es todo. Y recuerde que debe usar estas construcciones con prudencia, porque en la inmensa mayoría de las tareas no se requiere su presencia.








All Articles