Últimamente tuve algo de experiencia con punteros de función en C.
Entonces, siguiendo con la tradición de responder sus propias preguntas, decidí hacer un pequeño resumen de los conceptos básicos, para aquellos que necesitan una inmersión rápida en el tema.
Los punteros de función en C se pueden usar para realizar programación orientada a objetos en C.
Por ejemplo, las siguientes líneas están escritas en C:
String s1 = newString();
s1->set(s1, "hello");
Sí el ->
y la falta de un new
operador es un regalo muerto, pero seguro parece implicar que estamos configurando el texto de algunos String
clase para ser "hello"
.
Mediante el uso de punteros de función, es posible emular métodos en C.
¿Cómo se logra esto?
Él String
la clase es en realidad un struct
con un montón de punteros de función que actúan como una forma de simular métodos. La siguiente es una declaración parcial de la String
clase:
typedef struct String_Struct* String;
struct String_Struct
{
char* (*get)(const void* self);
void (*set)(const void* self, char* value);
int (*length)(const void* self);
};
char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);
String newString();
Como puede verse, los métodos de String
class son en realidad punteros de función a la función declarada. Al preparar la instancia de la String
la newString
Se llama a la función para configurar los punteros de función a sus respectivas funciones:
String newString()
{
String self = (String)malloc(sizeof(struct String_Struct));
self->get = &getString;
self->set = &setString;
self->length = &lengthString;
self->set(self, "");
return self;
}
por ejemplo, el getString
función que se llama invocando el get
método se define de la siguiente manera:
char* getString(const void* self_obj)
{
return ((String)self_obj)->internal->value;
}
Una cosa que se puede notar es que no existe el concepto de una instancia de un objeto y tener métodos que en realidad son parte de un objeto, por lo que se debe pasar un “objeto propio” en cada invocación. (Y el internal
es solo un oculto struct
que se omitió de la lista de códigos anterior; es una forma de ocultar información, pero eso no es relevante para los punteros de función).
Entonces, en lugar de poder hacer s1->set("hello");
se debe pasar el objeto para realizar la acción en s1->set(s1, "hello")
.
Con esa pequeña explicación que tiene que pasar en una referencia a ti mismo fuera del camino, pasaremos a la siguiente parte, que es herencia en C.
Digamos que queremos hacer una subclase de String
di un ImmutableString
. Para hacer la cadena inmutable, el set
el método no será accesible, mientras se mantiene el acceso a get
y length
y forzar al “constructor” a aceptar un char*
:
typedef struct ImmutableString_Struct* ImmutableString;
struct ImmutableString_Struct
{
String base;
char* (*get)(const void* self);
int (*length)(const void* self);
};
ImmutableString newImmutableString(const char* value);
Básicamente, para todas las subclases, los métodos disponibles son, una vez más, punteros de función. Esta vez, la declaración de la set
método no está presente, por lo tanto, no se puede llamar en un ImmutableString
.
En cuanto a la implementación de la ImmutableString
el único código relevante es la función “constructor”, el newImmutableString
:
ImmutableString newImmutableString(const char* value)
{
ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));
self->base = newString();
self->get = self->base->get;
self->length = self->base->length;
self->base->set(self->base, (char*)value);
return self;
}
Al instanciar el ImmutableString
la función apunta a la get
y length
los métodos en realidad se refieren a la String.get
y String.length
método, pasando por el base
variable que se almacena internamente String
objeto.
El uso de un puntero de función puede lograr la herencia de un método de una superclase.
Podemos continuar más polimorfismo en C.
Si por ejemplo quisiéramos cambiar el comportamiento del length
método para regresar 0
todo el tiempo en el ImmutableString
clase por alguna razón, todo lo que tendría que hacerse es:
- Agregue una función que va a servir como anulación
length
método.
- Vaya al “constructor” y establezca el puntero de función en la anulación
length
método.
Agregar una anulación length
método en ImmutableString
puede realizarse agregando un lengthOverrideMethod
:
int lengthOverrideMethod(const void* self)
{
return 0;
}
Entonces, el puntero de función para el length
El método en el constructor está conectado al lengthOverrideMethod
:
ImmutableString newImmutableString(const char* value)
{
ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));
self->base = newString();
self->get = self->base->get;
self->length = &lengthOverrideMethod;
self->base->set(self->base, (char*)value);
return self;
}
Ahora, en lugar de tener un comportamiento idéntico para el length
método en ImmutableString
clase como la String
clase, ahora la length
se referirá al comportamiento definido en el lengthOverrideMethod
función.
Debo agregar un descargo de responsabilidad de que todavía estoy aprendiendo a escribir con un estilo de programación orientado a objetos en C, por lo que probablemente haya puntos que no expliqué bien, o que pueden estar fuera de lugar en términos de la mejor manera de implementar OOP. en C. Pero mi propósito era tratar de ilustrar uno de los muchos usos de los punteros de función.
Para obtener más información sobre cómo realizar la programación orientada a objetos en C, consulte las siguientes preguntas:
- ¿Orientación a objetos en C?
- ¿Puedes escribir código orientado a objetos en C?
La función Comenzar desde cero tiene alguna dirección de memoria desde donde comienzan a ejecutarse. En lenguaje ensamblador, se llaman como (llame “dirección de memoria de la función”). Ahora regrese a C. Si la función tiene una dirección de memoria, entonces pueden ser manipulados por punteros en C. Por lo tanto, según las reglas de C
1. Primero debe declarar un puntero a la función 2. Pase la dirección de la función deseada
****Nota->las funciones deben ser del mismo tipo****
Este programa simple ilustrará cada cosa.
#include<stdio.h>
void (*print)() ;//Declare a Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
//The Functions should Be of Same Type
int main()
{
print=sayhello;//Addressof sayhello is assigned to print
print();//print Does A call To The Function
return 0;
}
void sayhello()
{
printf("\n Hello World");
}
Después de eso, veamos cómo la máquina los entiende. Un vistazo a las instrucciones de la máquina del programa anterior en una arquitectura de 32 bits.
El área de la marca roja muestra cómo se intercambia y se almacena la dirección en eax. Entonces hay una instrucción de llamada en eax. eax contiene la dirección deseada de la función.
Uno de mis usos favoritos para los punteros de función es como iteradores baratos y fáciles:
#include <stdio.h>
#define MAX_COLORS 256
typedef struct {
char* name;
int red;
int green;
int blue;
} Color;
Color Colors[MAX_COLORS];
void eachColor (void (*fp)(Color *c)) {
int i;
for (i=0; i<MAX_COLORS; i++)
(*fp)(&Colors[i]);
}
void printColor(Color* c) {
if (c->name)
printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}
int main() {
Colors[0].name="red";
Colors[0].red=255;
Colors[1].name="blue";
Colors[1].blue=255;
Colors[2].name="black";
eachColor(printColor);
}
Además: para un análisis un poco más profundo de los punteros C, consulte blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge. También, Programación desde cero muestra cómo funcionan a nivel de máquina. Comprender el “modelo de memoria” de C es muy útil para comprender cómo funcionan los punteros de C.
– Abbafei
22 de mayo de 2013 a las 11:17
Gran información. Sin embargo, por el título, hubiera esperado ver realmente una explicación de cómo “funcionan los punteros de función”, no cómo están codificados 🙂
– Bogdan Alexandru
28 de agosto de 2014 a las 12:39
La siguiente respuesta es más corta y mucho más fácil de entender: stackoverflow.com/a/142809/2188550
– usuario2188550
08/01/2021 a las 22:31