在 C 中模拟接口、重写方法、多态性和调用重写的超级方法
Simulating Interfaces, Overriding Methods, Polymorphism and calling the overridden super method in C
一个静态库,可以通过 void *
接收任何结构,并且能够访问它的成员变量,无论是嵌入式结构还是指向函数的指针。
保证嵌入式结构的成员变量将存在并可通过 super
访问,并且作为函数指针的成员变量将可用,称为 execute
。
问题 -(简而言之如何仅使用 void *
访问基础结构成员)
我想这与泛型编程有关,我们能否以某种方式通过内存地址或任何其他替代方式访问以下内容。
转换为特定结构在编译时不可用
struct 肯定会有一个执行指针函数和超级变量。保证。
// from src/library.c (someStruct a void *, casting to specific struct is not available)
someStruct->execute();
someStruct->super->execute();
尝试 1Reference
struct Super *pointer = someStruct + 8; // works
pointer->execute(); // prints overridden method!
// how to access second member which is a pointer to function
接受任何形式的解决方案,无论是低级别的内存地址以访问每个成员还是任何其他替代方式。
预期输出:
overridden method!
super method
src/library.h
#ifndef POLY_LIBRARY_H
#define POLY_LIBRARY_H
struct Library {
void (*passSomeStruct)(void *someStruct);
};
struct Library *newLibrary();
#endif
src/library.c
#include "library.h"
#include <stdlib.h>
void passSomeStruct(void *someStruct) {
// can we somewhow access the following via memory addresses or any other alternate way.
// casting to specific structure is not available at compile time
// struct will sure have an execute pointer function and super variable
someStruct->execute();
someStruct->super->execute();
}
struct Library *newLibrary() {
struct Library *library = malloc(sizeof(struct Library));
library->passSomeStruct = passSomeStruct;
return library;
}
src/super.h
#ifndef POLY_SUPER_H
#define POLY_SUPER_H
struct Super {
void (*execute)();
};
struct Super *newSuper();
#endif //POLY_SUPER_H
src/super.c
#include "super.h"
#include <stdio.h>
#include <stdlib.h>
static void execute() {
printf("super method\n");
}
struct Super *newSuper() {
struct Super *super = malloc(sizeof(struct Super));
super->execute = execute;
return super;
}
测试
test/library_test.h
#ifndef POLY_LIBRARY_TEST_H
#define POLY_LIBRARY_TEST_H
#endif //POLY_LIBRARY_TEST_H
test/library_test.c
#include "../src/library.h"
#include "temp.h"
int main() {
struct Library *library = newLibrary();
struct Temp *temp = newTemp();
library->passSomeStruct(temp);
return 0;
}
test/temp.h
#ifndef TEST_SUPER_H
#define TEST_SUPER_H
#include <stdlib.h>
struct Temp {
struct Super *super;
void (*execute)();
};
struct Temp *newTemp();
#endif //TEST_SUPER_H
test/temp.c
#include "temp.h"
#include <stdio.h>
#include "../src/super.h"
static void execute() {
printf("overridden method!\n");
}
struct Temp *newTemp() {
struct Temp *temp = malloc(sizeof(struct Temp));
temp->super = newSuper();
temp->execute = execute;
return temp;
}
MakeFile
VPATH := src test
all: libTest
libTest: super.o library.o
mkdir -p bin
ar rcs bin/libTest $^
test: library_test
./library_test
library_test: library_test.o library.o super.o temp.o
$(CC) -o $@ $^
%.o: %.c %.h
我终于揭开了它的神秘面纱,但我愿意接受建议,任何人都可以改进这个答案并让它变得更好,或者向我解释这里发生了什么吗?
这是某种神奇的数字 8,它起到了作用。
void passSomeStruct(void *someStruct) {
struct Super *pointer = someStruct + 8;
pointer->execute(); // outputs "overridden method!"
pointer = someStruct + 16;
pointer->execute(); // outputs "super method"
}
编辑
我的struct定义,我想知道为什么先定义的super
是16偏移量而我定义的方法execute
是8偏移量,内存分配是反的而不是在我定义它们的顺序?
struct Temp {
struct Super *super;
void (*execute)();
};
恐怕我不能给你一个完美的例子来说明 OOP 应该如何在
C,无论如何在几分钟内都不会出现在我的脑海中。还有其他
Stack Overflow 中的线程更详细地介绍了这一点,就像这样
一:
Can you write object-oriented code in C?
我强烈建议您阅读该主题,那里有很多很好的答案。
我还建议,如果您真的想在代码库中使用 OOP,请不要
自己写,很复杂,会很费时间。在那里面
如果我建议您使用 GObject
这是代码的一部分
glib
图书馆。我告诉你这一切是因为在评论中你说
yes, that's the part I need to know more, I don't know much about that domain, byte layout, casting to char*
etc.
当谈到这样的事情时,了解这些事情是最基本的。
我将给你一个非常简短的例子来说明我在评论中的意思
内存布局等
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#define TEND 0
#define TINT 1
#define TSTRING 2
#define TDOUBLE 3
typedef struct typeinfo {
int type;
size_t offset;
} TypeInfo;
typedef struct K1 {
int a;
int b;
char c[32];
} K1;
void print_K1(K1 *obj)
{
printf("K1: %d %s %d\n", obj->a, obj->c, obj->b);
}
TypeInfo *init_k1(K1 *obj, int a, int b, const char *c)
{
TypeInfo *types = calloc(4, sizeof *types);
obj->a = a;
obj->b = b;
strcpy(obj->c, c);
// filling type info
// we know the structure, we can provide this information
types[0].type = TINT;
types[0].offset = offsetof(K1, a);
types[1].type = TINT;
types[1].offset = offsetof(K1, b);
types[2].type = TSTRING;
types[2].offset = offsetof(K1, c);
types[3].type = TEND;
return types;
}
typedef struct K2 {
int k;
double o;
} K2;
void print_K2(K2 *obj)
{
printf("K2: %d %lf\n", obj->k, obj->o);
}
TypeInfo *init_k2(K2 *obj, int k, double o)
{
TypeInfo *types = calloc(3, sizeof *types);
obj->k = k;
obj->o = o;
// filling type info
// we know the structure, we can provide this information
types[0].type = TINT;
types[0].offset = offsetof(K2, k);
types[1].type = TDOUBLE;
types[1].offset = offsetof(K2, o);
types[2].type = TEND;
return types;
}
void somefunc(void *ptr, TypeInfo *types)
{
char *base = ptr, *offset;
while(1)
{
offset = base + types->offset;
switch(types->type)
{
case TINT:
{
int *p = (int*) offset;
*p *= -1;
break;
}
case TSTRING:
{
char *p = offset;
p[0] = ' ';
break;
}
case TDOUBLE:
{
double *p = (double*) offset;
*p *= 0.35;
}
case TEND:
return;
}
types++;
}
}
int main(void)
{
K1 k1;
K2 k2;
TypeInfo *t1, *t2;
t1 = init_k1(&k1, 33, -23, "Hello Peter");
print_K1(&k1);
somefunc(&k1, t1);
print_K1(&k1);
t2 = init_k2(&k2, 112, 122.12);
print_K2(&k2);
somefunc(&k2, t2);
print_K2(&k2);
free(t1);
free(t2);
return 0;
}
我有两个结构 K1
和 K2
具有不同的成员,这意味着他们的记忆
布局不同。对于 K1
和 K2
有使用
helper struct TypeInfo
存储结构成员的偏移量和类型
他们自己的class。他们知道此信息,因此他们可以构建
如此 table 的信息。 offsetof
returns字段成员的偏移量
从结构类型开始。
函数somefunc
对结构一无所知,所以它不能
将 void
指针转换为结构指针。但是它得到了一个列表
来自调用者的类型。此函数更改每个 int
成员的符号,
将每个字符串的第一个字母替换为空字符,并且
将每个 double 乘以 0.35
。感谢 types
它可以通过
使用 types
.
提供的偏移量
main
函数初始化每个结构的一个对象,打印原始
状态,用对象和类型信息调用 somefunc
并打印
再次对象。在输出中,您可以看到值已更改
因此。
输出
K1: 33 Hello Peter -23
K1: -33 ello Peter 23
K2: 112 122.120000
K2: -112 42.742000
虽然这可能有效,但我认为这不是一个优雅的解决方案。这是一个
基本示例,但在处理更复杂的问题时可能会变得非常复杂
结构。这向您展示了如何访问您所在的结构的内容
只有一个 void
指针。但是没有类型信息,这不可能
完毕。出于这个原因,我鼓励你使用一些已经
实施了打字系统。 GObject
非常成熟,广泛用于 GTK+ 应用程序。
Glib 和 GTK+ 人员多年来一直在使用它,所以我相信他们
在任何自行实施的解决方案上实施。
一个静态库,可以通过 void *
接收任何结构,并且能够访问它的成员变量,无论是嵌入式结构还是指向函数的指针。
保证嵌入式结构的成员变量将存在并可通过 super
访问,并且作为函数指针的成员变量将可用,称为 execute
。
问题 -(简而言之如何仅使用 void *
访问基础结构成员)
我想这与泛型编程有关,我们能否以某种方式通过内存地址或任何其他替代方式访问以下内容。 转换为特定结构在编译时不可用 struct 肯定会有一个执行指针函数和超级变量。保证。
// from src/library.c (someStruct a void *, casting to specific struct is not available)
someStruct->execute();
someStruct->super->execute();
尝试 1Reference
struct Super *pointer = someStruct + 8; // works
pointer->execute(); // prints overridden method!
// how to access second member which is a pointer to function
接受任何形式的解决方案,无论是低级别的内存地址以访问每个成员还是任何其他替代方式。
预期输出:
overridden method!
super method
src/library.h
#ifndef POLY_LIBRARY_H
#define POLY_LIBRARY_H
struct Library {
void (*passSomeStruct)(void *someStruct);
};
struct Library *newLibrary();
#endif
src/library.c
#include "library.h"
#include <stdlib.h>
void passSomeStruct(void *someStruct) {
// can we somewhow access the following via memory addresses or any other alternate way.
// casting to specific structure is not available at compile time
// struct will sure have an execute pointer function and super variable
someStruct->execute();
someStruct->super->execute();
}
struct Library *newLibrary() {
struct Library *library = malloc(sizeof(struct Library));
library->passSomeStruct = passSomeStruct;
return library;
}
src/super.h
#ifndef POLY_SUPER_H
#define POLY_SUPER_H
struct Super {
void (*execute)();
};
struct Super *newSuper();
#endif //POLY_SUPER_H
src/super.c
#include "super.h"
#include <stdio.h>
#include <stdlib.h>
static void execute() {
printf("super method\n");
}
struct Super *newSuper() {
struct Super *super = malloc(sizeof(struct Super));
super->execute = execute;
return super;
}
测试
test/library_test.h
#ifndef POLY_LIBRARY_TEST_H
#define POLY_LIBRARY_TEST_H
#endif //POLY_LIBRARY_TEST_H
test/library_test.c
#include "../src/library.h"
#include "temp.h"
int main() {
struct Library *library = newLibrary();
struct Temp *temp = newTemp();
library->passSomeStruct(temp);
return 0;
}
test/temp.h
#ifndef TEST_SUPER_H
#define TEST_SUPER_H
#include <stdlib.h>
struct Temp {
struct Super *super;
void (*execute)();
};
struct Temp *newTemp();
#endif //TEST_SUPER_H
test/temp.c
#include "temp.h"
#include <stdio.h>
#include "../src/super.h"
static void execute() {
printf("overridden method!\n");
}
struct Temp *newTemp() {
struct Temp *temp = malloc(sizeof(struct Temp));
temp->super = newSuper();
temp->execute = execute;
return temp;
}
MakeFile
VPATH := src test
all: libTest
libTest: super.o library.o
mkdir -p bin
ar rcs bin/libTest $^
test: library_test
./library_test
library_test: library_test.o library.o super.o temp.o
$(CC) -o $@ $^
%.o: %.c %.h
我终于揭开了它的神秘面纱,但我愿意接受建议,任何人都可以改进这个答案并让它变得更好,或者向我解释这里发生了什么吗?
这是某种神奇的数字 8,它起到了作用。
void passSomeStruct(void *someStruct) {
struct Super *pointer = someStruct + 8;
pointer->execute(); // outputs "overridden method!"
pointer = someStruct + 16;
pointer->execute(); // outputs "super method"
}
编辑
我的struct定义,我想知道为什么先定义的super
是16偏移量而我定义的方法execute
是8偏移量,内存分配是反的而不是在我定义它们的顺序?
struct Temp {
struct Super *super;
void (*execute)();
};
恐怕我不能给你一个完美的例子来说明 OOP 应该如何在 C,无论如何在几分钟内都不会出现在我的脑海中。还有其他 Stack Overflow 中的线程更详细地介绍了这一点,就像这样 一:
Can you write object-oriented code in C?
我强烈建议您阅读该主题,那里有很多很好的答案。
我还建议,如果您真的想在代码库中使用 OOP,请不要
自己写,很复杂,会很费时间。在那里面
如果我建议您使用 GObject
这是代码的一部分
glib
图书馆。我告诉你这一切是因为在评论中你说
yes, that's the part I need to know more, I don't know much about that domain, byte layout, casting to
char*
etc.
当谈到这样的事情时,了解这些事情是最基本的。
我将给你一个非常简短的例子来说明我在评论中的意思 内存布局等
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#define TEND 0
#define TINT 1
#define TSTRING 2
#define TDOUBLE 3
typedef struct typeinfo {
int type;
size_t offset;
} TypeInfo;
typedef struct K1 {
int a;
int b;
char c[32];
} K1;
void print_K1(K1 *obj)
{
printf("K1: %d %s %d\n", obj->a, obj->c, obj->b);
}
TypeInfo *init_k1(K1 *obj, int a, int b, const char *c)
{
TypeInfo *types = calloc(4, sizeof *types);
obj->a = a;
obj->b = b;
strcpy(obj->c, c);
// filling type info
// we know the structure, we can provide this information
types[0].type = TINT;
types[0].offset = offsetof(K1, a);
types[1].type = TINT;
types[1].offset = offsetof(K1, b);
types[2].type = TSTRING;
types[2].offset = offsetof(K1, c);
types[3].type = TEND;
return types;
}
typedef struct K2 {
int k;
double o;
} K2;
void print_K2(K2 *obj)
{
printf("K2: %d %lf\n", obj->k, obj->o);
}
TypeInfo *init_k2(K2 *obj, int k, double o)
{
TypeInfo *types = calloc(3, sizeof *types);
obj->k = k;
obj->o = o;
// filling type info
// we know the structure, we can provide this information
types[0].type = TINT;
types[0].offset = offsetof(K2, k);
types[1].type = TDOUBLE;
types[1].offset = offsetof(K2, o);
types[2].type = TEND;
return types;
}
void somefunc(void *ptr, TypeInfo *types)
{
char *base = ptr, *offset;
while(1)
{
offset = base + types->offset;
switch(types->type)
{
case TINT:
{
int *p = (int*) offset;
*p *= -1;
break;
}
case TSTRING:
{
char *p = offset;
p[0] = ' ';
break;
}
case TDOUBLE:
{
double *p = (double*) offset;
*p *= 0.35;
}
case TEND:
return;
}
types++;
}
}
int main(void)
{
K1 k1;
K2 k2;
TypeInfo *t1, *t2;
t1 = init_k1(&k1, 33, -23, "Hello Peter");
print_K1(&k1);
somefunc(&k1, t1);
print_K1(&k1);
t2 = init_k2(&k2, 112, 122.12);
print_K2(&k2);
somefunc(&k2, t2);
print_K2(&k2);
free(t1);
free(t2);
return 0;
}
我有两个结构 K1
和 K2
具有不同的成员,这意味着他们的记忆
布局不同。对于 K1
和 K2
有使用
helper struct TypeInfo
存储结构成员的偏移量和类型
他们自己的class。他们知道此信息,因此他们可以构建
如此 table 的信息。 offsetof
returns字段成员的偏移量
从结构类型开始。
函数somefunc
对结构一无所知,所以它不能
将 void
指针转换为结构指针。但是它得到了一个列表
来自调用者的类型。此函数更改每个 int
成员的符号,
将每个字符串的第一个字母替换为空字符,并且
将每个 double 乘以 0.35
。感谢 types
它可以通过
使用 types
.
main
函数初始化每个结构的一个对象,打印原始
状态,用对象和类型信息调用 somefunc
并打印
再次对象。在输出中,您可以看到值已更改
因此。
输出
K1: 33 Hello Peter -23
K1: -33 ello Peter 23
K2: 112 122.120000
K2: -112 42.742000
虽然这可能有效,但我认为这不是一个优雅的解决方案。这是一个
基本示例,但在处理更复杂的问题时可能会变得非常复杂
结构。这向您展示了如何访问您所在的结构的内容
只有一个 void
指针。但是没有类型信息,这不可能
完毕。出于这个原因,我鼓励你使用一些已经
实施了打字系统。 GObject
非常成熟,广泛用于 GTK+ 应用程序。
Glib 和 GTK+ 人员多年来一直在使用它,所以我相信他们
在任何自行实施的解决方案上实施。