C中的OOP、实现和一个bug

C中的OOP、实现和一个bug,c,oop,C,Oop,我正在尝试探索C语言中的OOP。然而,我是C n00b,我想挑选stackoverflow的聪明头脑: 我的代码如下: #include <stdio.h> #include <stdlib.h> typedef struct speaker { void (*say)(char *msg); } speaker; void say(char *dest) { printf("%s",dest); } speaker* NewSpeaker() {

我正在尝试探索C语言中的OOP。然而,我是C n00b,我想挑选stackoverflow的聪明头脑:

我的代码如下:

#include <stdio.h>
#include <stdlib.h>

typedef struct speaker {
    void (*say)(char *msg);
} speaker;

void say(char *dest) {
  printf("%s",dest);
}

speaker* NewSpeaker() {
  speaker *s;
  s->say = say;
  return s;
}

int main() {
  speaker *s = NewSpeaker();
  s->say("works");
}
然而,我得到了一个segfault,如果我从say中删除所有arg并使其无效,我可以让它正常工作。我当前的代码有什么问题

还有。虽然这在C中实现了对象的一种形式,但我试图通过继承甚至重写/重载方法来进一步实现它。你认为我怎样才能实现这样的目标

谢谢大家!

在您的代码中,NewSpeaker实际上并没有创建新的扬声器。您需要使用诸如或之类的内存分配功能

例如,如果不从malloc,s的返回值中指定值,则堆栈上的malloc,s被初始化为垃圾,因此segfault。

在您的代码中,NewSpeaker实际上不会创建新的扬声器。您需要使用诸如或之类的内存分配功能


例如,在不指定值的情况下,malloc,s的返回值被初始化为堆栈上的垃圾,因此segfault。

您可以通过将父类结构嵌入子类结构的顶部来实现继承。这样,您就可以安全地从子类强制转换到父类。这里介绍如何在C中实现OO功能。如果您想要现有的解决方案,或者只是想了解更多有关实现OO的方法,请查看。

您可以通过将父类结构嵌入子类结构的顶部来实现继承。这样,您就可以安全地从子类强制转换到父类。这里是关于在C中实现OO特性的。如果您想要一个现有的解决方案,或者只是想了解更多关于实现OO的方法,请查看。

首先,正如已经指出的,您没有为“NewSpeaker”中的“speaker”对象分配内存。如果没有不必要的混乱,它将如下所示

speaker* NewSpeaker(void) 
{
  speaker *s = malloc(sizeof *s);   
  s->say = say;   
  return s;
}
注意,malloc的结果没有强制转换,“sizeof”参数中没有类型名,函数参数列表被声明为“void”,而不仅仅是

其次,如果您希望能够创建“speaker”类型的非动态对象,那么您可能希望首先提供一个就地初始化函数,然后从那里开始

speaker* InitSpeaker(speaker* s)
{
  assert(s != NULL);
  s->say = say;
  return s;
}

speaker* NewSpeaker(void)
{
  void *raw = malloc(sizeof(speaker));
  return raw != NULL ? InitSpeaker(raw) : NULL;
}
最后,如果你真的想创建一些类似于虚拟C++方法的东西,你需要为每个方法提供一个“这个”参数来访问对象的其他成员。所以它应该看起来像

typedef struct speaker 
{    
  void (*say)(struct speaker *this, char *msg);
} speaker;

void say(speaker *this, char *dest) 
{  
  printf("%s",dest);
}
当然,这需要您在每次调用方法时都传递相应的参数,但这是无法避免的

此外,我希望您知道,您的类中只需要虚拟方法的方法指针。普通的非虚方法不需要这样的指针


最后,一个传统C++类IMREMENCE不在类的每个实例中存储虚拟方法指针。相反,它们被放置在一个单独的表VMT中,指向该表的指针被添加到每个实例中。这节省了大量内存。顺便说一句,当您实现继承时,这一点特别有意义。

首先,正如前面提到的,您没有为“NewSpeaker”中的“speaker”对象分配内存。如果没有不必要的混乱,它将如下所示

speaker* NewSpeaker(void) 
{
  speaker *s = malloc(sizeof *s);   
  s->say = say;   
  return s;
}
注意,malloc的结果没有强制转换,“sizeof”参数中没有类型名,函数参数列表被声明为“void”,而不仅仅是

其次,如果您希望能够创建“speaker”类型的非动态对象,那么您可能希望首先提供一个就地初始化函数,然后从那里开始

speaker* InitSpeaker(speaker* s)
{
  assert(s != NULL);
  s->say = say;
  return s;
}

speaker* NewSpeaker(void)
{
  void *raw = malloc(sizeof(speaker));
  return raw != NULL ? InitSpeaker(raw) : NULL;
}
最后,如果你真的想创建一些类似于虚拟C++方法的东西,你需要为每个方法提供一个“这个”参数来访问对象的其他成员。所以它应该看起来像

typedef struct speaker 
{    
  void (*say)(struct speaker *this, char *msg);
} speaker;

void say(speaker *this, char *dest) 
{  
  printf("%s",dest);
}
当然,这需要您在每次调用方法时都传递相应的参数,但这是无法避免的

此外,我希望您知道,您的类中只需要虚拟方法的方法指针。普通的非虚方法不需要这样的指针


最后,一个传统C++类IMREMENCE不在类的每个实例中存储虚拟方法指针。相反,它们被放置在一个单独的表VMT中,指向该表的指针被添加到每个实例中。这节省了大量内存。顺便说一句,这在实现继承时特别有意义。

Meta注释:通过初始化为堆栈上的垃圾,我的意思是不管发生了什么。实际上,没有任何东西会将s设置为活动意义上的值。s被动地获取内存中分配给它的内容。回答得好,但从malloc返回的值进行强制转换通常是不好的做法,至少在C中是这样。捕获得好。我遇到的一些编译器需要它,已经删除了。@SixlettVariables:嗯,这不准确
没错。用垃圾初始化的非易失性变量与根本未初始化的非易失性变量之间存在差异。一个根本没有初始化的变量甚至不能保证保持一个稳定的值,而一个即使使用垃圾也初始化的变量需要保持该值,直到它被显式更改。在这种情况下,我们有一个统一化的变量,而不是一个用垃圾初始化的变量。因为用垃圾初始化的C外行提供了足够的信息来解释它为什么不能按预期工作。Meta-comment:通过在堆栈上初始化为垃圾,我指的是不管发生了什么。实际上,没有任何东西会将s设置为活动意义上的值。s被动地获取内存中分配给它的内容。回答得好,但从malloc返回的值进行强制转换通常是不好的做法,至少在C中是这样。捕获得好。我遇到的一些编译器需要它,但已删除。@SixlettVariables:嗯,这并不完全正确。用垃圾初始化的非易失性变量与根本未初始化的非易失性变量之间存在差异。一个根本没有初始化的变量甚至不能保证保持一个稳定的值,而一个即使使用垃圾也初始化的变量需要保持该值,直到它被显式更改。在本例中,我们有一个统一的变量,而不是一个用垃圾初始化的变量。对于用垃圾初始化的C外行来说,它提供了足够的信息来解释它为什么不能按预期工作。这三个都是很好的答案!我选择了投票率最高的答案,我自己也给了你们所有人投票,谢谢!我自己永远也不会明白这一点。这都是三个很好的答案!我选择了投票率最高的答案,我自己也给了你们所有人投票,谢谢!我自己从来没有想到过。有趣的是,这实际上是最好的答案…:有趣的是,这实际上是最好的答案…: