天道酬勤,学无止境

C中的OO多态性,别名问题?(OO Polymorphism in C, aliasing issues?)

问题

我和一位同事正在尝试实现一个简单的多态类层次结构。 我们正在开发一个嵌入式系统,并且仅限于使用 C 编译器。 我们有一个基本的设计思想,可以在没有警告的情况下编译(-Wall -Wextra -fstrict-aliasing -pedantic)并且在 gcc 4.8.1 下运行良好。

但是,我们有点担心混叠问题,因为我们不完全了解这何时会成为问题。

为了演示,我们编写了一个带有“接口”IHello 和两个实现该接口“Cat”和“Dog”的类的玩具示例。

#include <stdio.h>

/* -------- IHello -------- */
struct IHello_;
typedef struct IHello_
{
    void (*SayHello)(const struct IHello_* self, const char* greeting);
} IHello;

/* Helper function */
void SayHello(const IHello* self, const char* greeting)
{
    self->SayHello(self, greeting);
}

/* -------- Cat -------- */
typedef struct Cat_
{
    IHello hello;
    const char* name;
    int age;
} Cat;

void Cat_SayHello(const IHello* self, const char* greeting)
{
    const Cat* cat = (const Cat*) self;
    printf("%s I am a cat! My name is %s and I am %d years old.\n",
           greeting,
           cat->name,
           cat->age);
}

Cat Cat_Create(const char* name, const int age)
{
    static const IHello catHello = { Cat_SayHello };
    Cat cat;

    cat.hello = catHello;
    cat.name = name;
    cat.age = age;

    return cat;
}

/* -------- Dog -------- */
typedef struct Dog_
{
    IHello hello;
    double weight;
    int age;
    const char* sound;
} Dog;

void Dog_SayHello(const IHello* self, const char* greeting)
{
    const Dog* dog = (const Dog*) self;
    printf("%s I am a dog! I can make this sound: %s I am %d years old and weigh %.1f kg.\n",
           greeting,
           dog->sound,
           dog->age,
           dog->weight);
}

Dog Dog_Create(const char* sound, const int age, const double weight)
{
    static const IHello dogHello = { Dog_SayHello };
    Dog dog;

    dog.hello = dogHello;
    dog.sound = sound;
    dog.age = age;
    dog.weight = weight;

    return dog;
}

/* Client code */
int main(void)
{
    const Cat cat = Cat_Create("Mittens", 5);
    const Dog dog = Dog_Create("Woof!", 4, 10.3);

    SayHello((IHello*) &cat, "Good day!");
    SayHello((IHello*) &dog, "Hi there!");

    return 0;
}

输出:

再会! 我是一只猫! 我的名字是手套,我 5 岁。

你好呀! 我是一只狗! 我可以发出这样的声音:汪! 我今年 4 岁,体重 10.3 公斤。

我们非常确定从 Cat 和 Dog 到 IHello 的“向上转换”是安全的,因为 IHello 是这两个结构的第一个成员。

我们真正关心的是在 SayHello 的相应接口实现中从 IHello 分别到 Cat 和 Dog 的“向下转换”。 这会导致任何严格的别名问题吗? 我们的代码是否保证符合 C 标准,或者我们只是幸运地使用了 gcc?

更新

我们最终决定使用的解决方案必须是标准 C,不能依赖于 gcc 等扩展。 代码必须能够使用各种(专有)编译器在不同的处理器上编译和运行。

这种“模式”的意图是客户端代码应接收指向 IHello 的指针,因此只能调用接口中的函数。 但是,这些调用的行为必须有所不同,具体取决于接收到的 IHello 实现。 简而言之,我们希望与实现此接口的接口和类的 OOP 概念相同的行为。

我们知道代码只有在 IHello 接口结构被放置为实现接口的结构的第一个成员时才有效。 这是我们愿意接受的限制。

根据:通过 C 强制转换访问结构的第一个字段是否违反严格别名?

第 6.7.2.1/13 节:

在结构对象中,非位域成员和位域所在的单元的地址按它们声明的顺序增加。 指向结构对象的指针,经过适当转换,指向其初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。 结构对象内可能有未命名的填充,但不是在其开头。

别名规则如下(第 6.5/7 节):

对象的存储值只能由具有以下类型之一的左值表达式访问:

  • 与对象的有效类型兼容的类型,
  • 与对象的有效类型兼容的类型的限定版本,
  • 一种类型,它是与对象的有效类型相对应的有符号或无符号类型,
  • 一种类型,它是与对象有效类型的限定版本相对应的有符号或无符号类型,
  • 在其成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含联合的成员),或
  • 一种字符类型。

根据上面的第五个要点以及结构在顶部不包含填充的事实,我们相当确定将实现接口的派生结构“向上转换”为指向接口的指针是安全的,即

Cat cat;
const IHello* catPtr = (const IHello*) &cat; /* Upcast */

/* Inside client code */
void Greet(const IHello* interface, const char* greeting)
{
    /* Users do not need to know whether interface points to a Cat or Dog. */
    interface->SayHello(interface, greeting); /* Dereferencing should be safe */
}

最大的问题是在接口函数的实现中使用的“向下转型”是否安全。 如上所示:

void Cat_SayHello(const IHello* hello, const char* greeting)
{
    /* Is the following statement safe if we know for
     * a fact that hello points to a Cat?
     * Does it violate strict aliasing rules? */
    const Cat* cat = (const Cat*) hello;
    /* Access internal state in Cat */
}

另请注意,将实现函数的签名更改为

Cat_SayHello(const Cat* cat, const char* greeting);
Dog_SayHello(const Dog* dog, const char* greeting);

并注释掉“downcast”也可以编译并运行良好。 但是,这会针对函数签名不匹配生成编译器警告。

回答1

多年来我一直在用 c 做对象,完全按照你在这里做的那种组合。 我建议你不要做你描述的简单演员,而是证明我需要一个例子。 例如,与分层实现一起使用的计时器回调机制:

typedef struct MSecTimer_struct MSecTimer;
struct MSecTimer_struct {
     DoubleLinkedListNode       m_list;
     void                       (*m_expiry)(MSecTimer *);
     unsigned int               m_ticks;
     unsigned int               m_needsClear: 1;
     unsigned int               m_user: 7;
};

当这些计时器之一到期时,管理系统调用 m_expiry 函数并传入指向对象的指针:

timer->m_expiry(timer);

然后拿一个可以做一些惊人事情的基础对象:

typedef struct BaseDoer_struct BaseDoer;
struct BaseDoer_struct
{
     DebugID      m_id;
     void         (*v_beAmazing)(BaseDoer *);  //object's "virtual" function
};

//BaseDoer's version of BaseDoer's 'virtual' beAmazing function
void BaseDoer_v_BaseDoer_beAmazing( BaseDoer *self )
{
    printf("Basically, I'm amazing\n");
}

我的命名系统在这里有一个目的,但这并不是真正的重点。 我们可以看到可能需要的各种面向对象的函数调用:

typedef struct DelayDoer_struct DelayDoer;
struct DelayDoer_struct {
     BaseDoer     m_baseDoer;
     MSecTimer    m_delayTimer;
};

//DelayDoer's version of BaseDoer's 'virtual' beAmazing function
void DelayDoer_v_BaseDoer_beAmazing( BaseDoer *base_self )
{
     //instead of just casting, have the compiler do something smarter
     DelayDoer *self = GetObjectFromMember(DelayDoer,m_baseDoer,base_self);

     MSecTimer_start(m_delayTimer,1000);  //make them wait for it
}

//DelayDoer::DelayTimer's version of MSecTimer's 'virtual' expiry function
void DelayDoer_DelayTimer_v_MSecTimer_expiry( MSecTimer *timer_self )
{
    DelayDoer *self = GetObjectFromMember(DelayDoer,m_delayTimer,timer_self);
    BaseDoer_v_BaseDoer_beAmazing(&self->m_baseDoer);
}

自 1990 年左右以来,我一直在为 GetObjectFromMember 使用相同的宏,并且在此过程中,Linux 内核创建了相同的宏并将其命名为 container_of(尽管参数的顺序不同):

  #define GetObjectFromMember(ObjectType,MemberName,MemberPointer) \
              ((ObjectType *)(((char *)MemberPointer) - ((char *)(&(((ObjectType *)0)->MemberName)))))

它依赖于(技术上)未定义的行为(取消引用 NULL 对象),但可移植到我测试过的每个旧(和新)c 编译器。 较新的版本需要 offsetof 宏,它现在是标准的一部分(显然从 C89 开始):

#define container_of(ptr, type, member) ({ \
            const typeof( ((type *)0)->member ) *__mptr = (ptr); 
            (type *)( (char *)__mptr - offsetof(type,member) );})

我,当然,更喜欢我的名字,但无论如何。 使用这种方法可以让你的代码不依赖于将基础对象放在第一位,并且还使第二个用例成为可能,我发现这在实践中非常有用。 所有别名编译器问题都在宏中管理(我认为通过 char * 进行转换,但我并不是真正的标准律师)。

回答2

从您引用的标准部分:

指向结构对象的指针,经过适当转换,指向其初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然

将像 cat->hello 这样的指针转换为 Cat 指针是绝对安全的,对于 dog->hello 也是如此,所以在你的 SayHello 函数中的转换应该没问题。

在调用站点,您正在做相反的事情:将指向结构的指针转换为指向第一个元素的指针。 这也保证有效。

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • 如何在C中模拟OO风格的多态性?(How can I simulate OO-style polymorphism in C?)
    问题 有没有办法用C编程语言编写类似于OO的代码? 也可以看看: 可以用C编写面向对象的代码吗? C语言中的面向对象 通过搜索“ [c] oo”找到。 回答1 第一个C ++编译器(“带有类的C”)实际上会生成C代码,因此这绝对是可行的。 基本上,您的基类是一个struct; 派生结构必须在第一个位置包括基本结构,以便指向“派生”结构的指针也将是指向基本结构的有效指针。 typedef struct { data member_x; } base; typedef struct { struct base; data member_y; } derived; void function_on_base(struct base * a); // here I can pass both pointers to derived and to base void function_on_derived(struct derived * b); // here I must pass a pointer to the derived class 这些函数可以作为函数指针作为结构的一部分,因此像p-> call(p)这样的语法成为可能,但是您仍然必须将指向该结构的指针显式传递给函数本身。 回答2 常见的方法是使用指向函数的指针来定义结构。 这定义了可以在任何类型上调用的“方法”。 然后
  • 可以使用模板多态性代替OO多态性吗?(Can template polymorphism be used in place of OO polymorphism?)
    问题 我正在设法将模板编程(以及将来使用的模板元编程)应用于实际场景。 我发现的一个问题是C ++模板和多态性并不总是按照我想要的方式一起发挥。 我的问题是,我尝试应用模板编程的方式是否不正确(我应该使用普通的旧式OOP),或者我是否仍停留在OOP思维方式中。 在这种特殊情况下,我正在尝试使用策略模式解决问题。 我一直遇到这个问题,我最终想要某种东西表现出多态性,而这些模板似乎不支持。 使用组成的OOP代码: class Interpolator { public: Interpolator(ICacheStrategy* const c, IDataSource* const d); Value GetValue(const double); } void main(...) { Interpolator* i; if(param==1) i = new Interpolator(new InMemoryStrategy(...), new TextFileDataSource(...)); else if(param==2) i = new Interpolator(new InMemoryStrategy(...), new OdbcDataSource(...)); else if(param==3) i = new Interpolator(new
  • OO Polymorphism in C, aliasing issues?
    Me and a colleague are trying to achieve a simple polymorphic class hierarchy. We're working on an embedded system and are restricted to only using a C compiler. We have a basic design idea that compiles without warnings (-Wall -Wextra -fstrict-aliasing -pedantic) and runs fine under gcc 4.8.1. However, we are a bit worried about aliasing issues as we do not fully understand when this becomes a problem. In order to demonstrate we have written a toy example with an 'interface' IHello and two classes implementing this interface 'Cat' and 'Dog. #include <stdio.h> /* -------- IHello -------- */
  • 是否有使用代数数据类型或多态性的 OOP 抽象类的 Haskell 等价物?(Is there a Haskell equivalent of OOP's abstract classes, using algebraic data types or polymorphism?)
    问题 在 Haskell 中,是否可以编写一个带有签名的函数,该函数可以接受两种不同(虽然相似)的数据类型,并根据传入的类型进行不同的操作? 一个例子可能会使我的问题更清楚。 如果我有一个名为myFunction的函数和两个名为MyTypeA和MyTypeB类型,我是否可以定义myFunction以便它只能接受MyTypeA或MyTypeB类型的数据作为其第一个参数? type MyTypeA = (Int, Int, Char, Char) type MyTypeB = ([Int], [Char]) myFunction :: MyTypeA_or_MyTypeB -> Char myFunction constrainedToTypeA = something myFunction constrainedToTypeB = somethingElse 在 OOP 语言中,您可以像这样编写我想要实现的目标: public abstract class ConstrainedType { } public class MyTypeA extends ConstrainedType { ...various members... } public class MyTypeB extends ConstrainedType { ...various members... }
  • 我们应该总是喜欢多态而不是枚举吗?(Should we always favor polymorphism over enums?)
    问题 看完后:The Clean Code Talks -- 继承、多态和测试 我检查了我的代码并注意到一些 switch 语句可以重构为多态性,但我也注意到我只使用带有枚举的 switch 语句。 这是否意味着枚举在 OO 设计中是“邪恶的”,应该用多态消除? 回答1 不是枚举是邪恶的,而是 switch 语句。 在 C++ FAQ Book 中对此进行了长时间的讨论,但要点是:除了有限的区域——例如对来自设备寄存器的数据的解释——一个大的开关梳表明你'重新使用数据来区分子类型。 取而代之的是,您应该只使用子类型,获得编译器的帮助以保持它的正确性,这也意味着当您(不可避免地)更改案例集时,编译器将自动添加新案例。 回答2 首先,Java 实际上有很多可以多态使用的枚举。 我不是 Java 程序员,所以其他人当然可以举一个很好的例子(我不能)。 另外,请考虑多态性通常只是矫枉过正。 我也刚刚看过这个演讲,它很棒,但它只提供了指导方针,没有灵丹妙药。 尤其不是所有的switch语句都可以替换。 状态机实际上是枚举很有意义的一种情况。 我在解析中经常使用状态机。 当然,这可以通过设计模式和类多态来完成。 但它的代码要(很多)多得多,它执行相同的工作,只是速度更慢,它的可读性一点也不多,而且它是一种仅在一个地方需要的解决方案,无需任何代码回收。 在这里使用子类化根本没有优势。 再说一次
  • C 联合和多态性 [重复](C Unions and Polymorphism [duplicate])
    问题 这个问题在这里已经有了答案: 9年前关闭。 可能的重复: 如何在 C 中模拟 OO 风格的多态性? 我正在尝试使用联合在 C 中创建多态性。我执行以下操作。 typedef struct{ ... ... } A; typedef struct{ ... ... } B; typedef union{ A a; B b; }C; 我的问题是:我怎么能有一个采用 C 类型但也允许 A 和 B 的方法。 我希望以下工作: 如果我定义一个函数: myMethod(C){ ... } 然后,我希望这个工作: main(){ A myA; myMethod(myA); } 它没有。 有什么建议么? 回答1 GNU 和 IBM 支持transparent_union扩展: typedef union __attribute__((transparent_union)) { A a; B b; } C; 然后你可以透明地使用A s 或B s 或C s: A foo1; B foo2; C foo3; myMethod(foo1); myMethod(foo2); myMethod(foo3); 请参见 transparent_union 类型属性(仅限 C)。 回答2 您必须使用显式类型转换: A myA; myMethod((C) myA); 回答3 简短的回答是,在 C 中,你不能。
  • Scala 类型类的多态性(Polymorphism with Scala type classes)
    问题 我们正在重构一个继承的method以使用类型类 - 我们希望将所有method实现集中在一个地方,因为将它们分散在实现类中会使维护变得困难。 然而,我们遇到了一些麻烦,因为我们对类型类还很陌生。 目前method定义为 trait MethodTrait { def method: Map[String, Any] = // default implementation } abstract class SuperClass extends MethodTrait { override def method = super.method ++ // SuperClass implementation } class Clazz extends SuperClass { override def method = super.method ++ // Clazz implementation } 以此类推,一共50+个具体类,层次比较浅( abstract class SuperClass > abstract class SubSuperClass > abstract class SubSubSuperClass > class ConcreteClass就这么深),一个具体类从不扩展另一个具体类。 (在实际实现中, method返回一个 Play Framework
  • 简单的结构继承和伪多态 vs 严格别名(easy struct inheritance & pseudo-polymorphism vs strict aliasing)
    问题 如果有人回答我的问题,请不要告诉我使用 C++ 。 所以,我正在用 C 语言创建一个使用面向对象方法的小型库。 我选择使用 C 中两种主要继承方法中不太常见的一种:将基类型的成员复制到派生类型的开头。 像这样的东西: struct base { int a; int b; char c; }; struct derived { int a; int b; char c; unsigned int d; void (*virtual_method)(int, char); }; 这种方法不如另一种方法流行(将基类型的实例作为派生类型的第一个成员),因为 从技术上讲,没有标准化的保证基础结构和派生结构的第一个公共成员将具有相同的偏移量。 但是,除了其中一个结构被打包而另一个没有打包的情况外,它们在大多数(如果不是全部)已知编译器上将具有相同的偏移量。 这种方法最严重的缺陷:它违反了严格别名。 将指向派生结构的指针转换为其基类型,然后取消引用该指针在技术上是未定义的行为。 但是,与其他方法相比,它也有其优点: 更简洁:访问已继承的派生结构的成员与访问未继承的成员相同,而不是转换为基类型,然后访问所需的成员; 这实际上是真正的继承而不是组合; 尽管可能需要一些预处理器滥用,但它与其他方法一样容易实现; 我们可以获得实际多重继承的半生不熟的形式,我们可以从几个基本类型继承
  • JavaScript是面向对象的吗?(Is JavaScript object-oriented?)
    问题 已锁定。 该问题及其答案被锁定,因为该问题是题外话,但具有历史意义。 它目前不接受新的答案或互动。 关于JavaScript是否是面向对象的语言存在一些问题。 甚至有这样的说法:“仅仅因为一种语言包含对象,并不能使它成为OO。 JavaScript是一种面向对象的语言吗? 回答1 IMO(这只是一种意见) ,面向对象语言的主要特征是它将支持多态。 几乎所有动态语言都可以做到这一点。 下一个特性是封装,这在Javascript中也很容易做到。 但是,在许多人的心中,继承(特别是实现继承)将在某种语言是否符合被称为面向对象的语言方面取得平衡。 JavaScript确实提供了一种通过原型继承实现的相当简单的方法,但这是以封装为代价的。 因此,如果您面向对象的标准是多态性,封装和继承的经典三人组合,那么Javascript不会通过。 编辑:提出了一个补充问题:“原型继承如何牺牲封装?” 考虑以下非原型方法的示例: function MyClass() { var _value = 1; this.getValue = function() { return _value; } } _value属性是封装的,不能由外部代码直接修改。 我们可能会在类中添加一个mutator,以完全由属于该类的代码控制的方式对其进行修改。 现在考虑对同一个类的原型方法: function MyClass(
  • 多态性或条件性会促进更好的设计吗?(Do polymorphism or conditionals promote better design?)
    问题 我最近在Google测试博客中偶然发现了有关编写更多可测试代码的准则的条目。 在这一点上,我一直与作者保持一致: 赞成多态而不是条件:如果您看到switch语句,您应该认为多态。 如果您在班级的许多地方看到相同的if条件重复出现,则应该再次考虑多态性。 多态将您的复杂类分解为几个较小的简单类,这些清晰的类明确定义了哪些代码段是相关的并一起执行。 这有助于测试,因为更简单/更小的类更易于测试。 我根本无法解决这个问题。 我可以理解使用多态性而不是RTTI(或视情况而定,还是DIY-RTTI),但这似乎是一个广泛的陈述,我无法想象它实际上已在生产代码中有效地使用了。 在我看来,相反,为具有switch语句的方法添加其他测试用例要比将代码分解成数十个单独的类要容易得多。 另外,我给人的印象是多态性可能导致各种其他细微的错误和设计问题,所以我很想知道在此进行权衡是否值得。 有人可以向我确切解释该测试指南的含义吗? 回答1 实际上,这使测试和代码更易于编写。 如果您有一个基于内部字段的switch语句,则可能在多个地方有相同的switch在做稍微不同的事情。 当您添加新案例时,这将导致问题,因为您必须更新所有switch语句(如果可以找到它们)。 通过使用多态性,您可以使用虚拟函数来获得相同的功能,并且由于新案例是新类,因此您不必在代码中搜索需要检查的内容,因为对于每个类而¨
  • “策略设计模式”仅仅是多态的基本使用吗?(Is 'Strategy Design Pattern' no more than the basic use of polymorphism?)
    问题 在策略设计模式中,我们所做的是 创建一个通用接口。 使用带有重写方法的接口实现一组类。 让运行时为与该公共接口具有相同类型的对象选择实际类,并调用将根据类正确解析的重写方法。 我的问题是,这不是我们学习的多态性和方法覆盖的基本示例吗? 除了使用抽象类的可能性,替换公共接口。 回答1 您所描述的是一种实现策略模式的方法。 您还描述了如何实现大量不同的设计,因为我们可能想要创建一个通用接口、进行不同实现并在运行时针对不同情况选择一个的原因有很多很多。 还有其他方法可以实现策略模式。 但是,您知道,设计不是代码。 设计是软件如何工作的心理模型——人的事情,而不是位。 设计模式是为常见问题组合解决方案的常用方法。 它再次发生在你的脑海中,而不是一点点。 特别是策略模式是关于使用可互换的算法制作对象,其中任何一种都可以用于特定目的。 所以,是的,你定义了一个接口……但它不是数据对象的接口——它没有 getter、setter 和 state mutators 等。接口定义了对象如何与用于的算法交互一个特定的目的。 国际象棋游戏可能使用“玩家”策略,例如,使用名为“SelectNextMove”的方法。 而且,是的,您实现了该接口,但实现并不是成为包含对象的“部分”的“事物”,它们是不同的算法,可用于执行对象所需的任何功能。 国际象棋游戏可以支持许多不同的策略
  • OO的基本概念---------多态性
    OO五大原则:SRP、OCP、LSP、DIP、ISP SRP(Single Responsibility Principle 单一职责原则) 对象的功能应该单一 OCP(Open Close Principle 开闭原则) 对修改关闭,对扩展打开 LSP(Liskov Substitution Principle 里氏替换原则) “老鼠的儿子会打洞”,子类应该具备父类的特征。 DIP(Dependence Inversion Principle 反向依赖原则) 不应该依赖“客户”的东西,后端提供方法不应该依赖前端对象。 ISP(Interface Segregation Principle 接口分隔原则) 尽量提供有针对性符合功能特点的简单接口,而不是包含很多方法的大而总接口。(转载:http://ericxu131.iteye.com/blog/183554)多态性(polymorphism)是面向对象编程的属性,它允许多个方法使用同一个接口。Java从多个方面支持多态性,其中两个方面最为突出。第一个是每个方法(标记为 final的方法除外)都可以被子类重写;第二个是设立interface关键字。 超类中的方法可以再派生类中重写,再类的层次结构中,每个子类都将它的超类特化(sepcialization)。超类的一个引用可以引用它的任何一个子类
  • 【第767期】你不懂JS:混合(淆)“类”的对象
    前言又到周五了,这周是不是被这一系列的文章看的有点晕,如果拿着一本书会觉得挺多的,不一定能看完,但每天跟着阅读一篇可能就不会觉得多了,你觉得呢?今天继续由前端早读课专栏作者@HetfieldJoe带来连载《你不懂JS》的分享。正文从这开始~你不懂JS:this与对象原型 第四章:混合(淆)“类”的对象接着我们上一章对对象的探索,我们很自然的将注意力转移到“面向对象(OO)编程”,与“类(class)”。我们先将“面向类”作为设计模式来看看,之后我们再考察“类”的机制:“实例化(instantiation)”, “继承(inheritance)”与“相对多态(relative polymorphism)”。我们将会看到,这些概念并不是非常自然地映射到JS的对象机制上,以及许多JavaScript开发者为了克服这些挑战所做的努力(mixins等)。注意: 这一章花了相当一部分时间(前一半!)在着重解释“面向对象编程”理论上。在后半部分讨论“Mixins(混合)”时,我们最终会将这些理论与真实且实际的JavaScript代码联系起来。但是这里首先要蹚过许多概念和假想代码,所以可别迷路了——坚持下去!类(Class)理论“类/继承”描述了一种特定的代码组织和结构形式——一种在我们的软件中对真实世界的建模方法。OO或者面向类的编程强调数据和操作它的行为之间有固有的联系(当然
  • OCaml 中的多态性——即席、参数化、包含/子类型化(Polymorphism in OCaml - ad hoc,parametric, inclusion/subtyping)
    问题 我在理解不同类型的多态性时遇到问题,特别是在 OCaml 方面。 我知道多态性允许 OCaml 中的多种类型表示为 'a,但我不明白不同类型的多态性是什么。 如果有人能用相对低级的语言给我一个解释,那就太棒了! 临时、参数化、包含/子类型化 回答1 这是一个近似值。 Ad-hoc 多态性通常是指能够用不同的类型声明相同的名称(通常是一个函数),例如+ : int -> int -> int和+ : float -> float -> float在 SML 中。 这些是不同的函数,它们可以以完全不同的方式运行,但是编译器或解释器会根据上下文选择合适的函数。 我想不出 OCaml 中的任何临时多态性实例。 然而,它在 C++ 和 Java 中很常见。 参数多态是指单个函数可以处理任何类型的参数,因为不尝试查看该参数的结构。 例如, cons : 'a -> 'a list -> 'a list能够将任何类型的值v到相同类型的值列表中,因为cons的结构(布局)无关紧要v是什么,或者它支持什么操作。 在C而言, cons并不需要“解除引用”的指针,或执行任何操作v特定于实际类型的v 。 请注意,与 ad-hoc 多态性不同, cons必须以相同的方式对所有类型起作用。 因此,参数多态性和临时多态性在某种程度上是彼此自然的“对立面”。 参数多态性是 OCaml
  • 方法签名中的新关键字(new keyword in method signature)
    问题 在执行重构时,我最终创建了一个类似于以下示例的方法。 为了简单起见,已更改数据类型。 我以前有一个这样的赋值语句: MyObject myVar = new MyObject(); 偶然地将其重构为: private static new MyObject CreateSomething() { return new MyObject{"Something New"}; } 这是我剪切/粘贴错误的结果,但是private static new的new关键字有效并且可以编译。 问题: new关键字在方法签名中表示什么? 我认为这是C#3.0中引入的吗? 这与override有何不同? 回答1 来自MSDN的新关键字参考: MSDN参考 这是我在网上从Microsoft MVP上找到的一个很好的例子:链接到原始 public class A { public virtual void One(); public void Two(); } public class B : A { public override void One(); public new void Two(); } B b = new B(); A a = b as A; a.One(); // Calls implementation in B a.Two(); // Calls implementation
  • 在 C 中扩展结构(Extending a struct in C)
    问题 我最近遇到一个同事的代码,看起来像这样: typedef struct A { int x; }A; typedef struct B { A a; int d; }B; void fn(){ B *b; ((A*)b)->x = 10; } 他的解释是,由于struct A是struct B的第一个成员,所以b->x将与b->ax相同并提供更好的可读性。 这是有道理的,但这被认为是好的做法吗? 这会跨平台工作吗? 目前这在 GCC 上运行良好。 回答1 是的,它可以跨平台工作(a) ,但这并不一定使它成为一个好主意。 根据 ISO C 标准(以下所有引用均来自 C11), 6.7.2.1 Structure and union specifiers /15 ,不允许在结构的第一个元素之前填充 此外, 6.2.7 Compatible type and composite type指出: 如果它们的类型相同,则两种类型具有兼容类型 毫无疑问, A和A-within-B型是相同的。 这意味着对A字段的内存访问在A和B类型中都是相同的,更明智的b->ax可能是您应该使用的,如果您对将来的可维护性有任何担忧。 而且,虽然您通常不得不担心严格的类型别名,但我认为这不适用于这里。 给指针别名是非法的,但标准有特定的例外。 6.5 Expressions /7说明了其中一些例外情况
  • 是什么使语言成为面向对象的语言?(What makes a language Object-Oriented?)
    问题 由于没有有意义的术语的辩论是没有意义的,我想我会指着房间里的大象问:到底是什么使语言成为“面向对象”? 我不是在这里寻找教科书的答案,而是根据您对OO语言的经验,无论在哪种领域都可以在您的域中很好地工作。 一个可能有助于首先回答的相关问题是:面向对象语言的原型是什么?为什么? 回答1 面向对象的定义当然是一大堆蠕虫,但这是我的2美分: 对我而言,面向对象就是所有通过发送消息进行协作的对象。 对我来说,这就是面向对象语言的最重要的特征。 如果我必须列出面向对象语言必须具备的所有功能的有序列表,则它将看起来像这样: 向其他对象发送消息的对象一切都是对象后期装订亚型多态性继承或类似表达的东西,例如委托封装形式信息隐藏抽象化 显然,此列表引起很大争议,因为它排除了被广泛认为是面向对象的多种语言,例如Java,C#和C ++,所有这些语言都违反了第1、2和3点。但是,毫无疑问,这些语言允许进行面向对象的编程(但C允许),甚至可以促进编程(C则不能)。 因此,我将满足这些要求的语言称为“纯粹面向对象”。 作为典型的面向对象语言,我将其命名为Self和Newspeak。 两者都满足上述要求。 两者都受到Smalltalk的启发和继承,并且从某种意义上说,两者实际上都是“更多面向对象的”。 我喜欢Self和Newspeak的地方在于,它们都将信息发送范式带到了极致
  • 如何向过程程序员教授面向对象的编程? [关闭](How to teach object oriented programming to procedural programmers? [closed])
    问题 从目前的情况来看,这个问题不适合我们的问答形式。 我们希望答案得到事实,参考或专业知识的支持,但是这个问题可能会引起辩论,争论,民意测验或进一步的讨论。 如果您认为此问题可以解决并且可以重新提出,请访问帮助中心以获取指导。 8年前关闭。 我被要求开始向一组程序程序员教授C#和OO概念。 我一直在寻找从哪里开始的想法,但是除了最初要避免的话题​​之外,还在寻找关于达成共识的普遍共识。 编辑 我打算每周分30分钟介绍一次信息,直到不再有意义为止。 这些演示针对从新手到专家的各种技能水平的同事。 回答1 您可以做的最好的事情是:进行大量的问答。 Wikipedia的过程编程(PP)文章确实在您应该开始的地方找到了: 程序编程使用过程来对数据结构进行操作,而面向对象的编程将两者捆绑在一起,因此“对象”在其“自己的”数据结构上进行操作。 一旦理解了这一点,我认为很多事情都会落到实处。 一般来说 OOP是可能需要花费一些时间才能“到达”的事情之一,每个人都走自己的路才能到达那里。 用C#编写代码时,并不是每行代码都在尖叫:“我正在使用OO原理! ”。 这更像是一个微妙的事情,例如foreach循环或string连接。 设计中心 始终使用使得它之前的东西(反复)。 首先,使用一个对象,并演示与PP的基本区别。 喜欢: static void Main(string[] args) {
  • 泛型与类型安全? 在C中使用void *(Genericity vs type-safety? Using void* in C)
    问题 来自OO(C#,Java,Scala)的我非常重视代码重用和类型安全的原则。 以上语言中的类型参数可以完成工作,并启用通用的数据结构,这些数据结构都是类型安全的,并且不会“浪费”代码。 当我陷入C的困境时,我意识到我必须做出让步,我希望它是正确的选择。 我的数据结构在每个节点/元素中都有一个void * ,并且失去了类型安全性,或者我不得不为要使用它们的每种类型重新编写我的结构和代码。 代码的复杂性是一个显而易见的因素:遍历数组或链表很简单,而在结构体上添加*next则无需额外的精力; 在这些情况下,不要尝试重用结构和代码是有意义的。 但是对于更复杂的结构,答案并不那么明显。 还有模块化和可测试性:将类型及其操作与使用该结构的代码分开可以使测试变得更加容易。 反之亦然:在尝试执行其他操作时在结构上测试某些代码的迭代会变得混乱。 那么您有什么建议呢? void *以及重用或类型安全和重复的代码? 有什么一般原则吗? 我是否要在不适合使用OO时强制将其应用于过程? 编辑:请不要推荐C ++,我的问题是关于C的! 回答1 我会说使用void *以便您可以重新使用代码。 重新实现例如链表的工作要比确保正确获取/设置列表中的数据要多。 从glib中获得尽可能多的提示,我发现它们的数据结构非常好并且易​​于使用,并且由于丢失类型安全性而带来的麻烦也很小。 回答2
  • C 的 FILE 有面向对象的接口吗?(Does C's FILE have an object-oriented interface?)
    问题 通过标准 C 函数fopen等使用的FILE类型是否具有面向对象的接口? 我正在寻找带有推理而非绝对答案的意见,因为 OO 的定义因您问的人而异。 它满足或不满足哪些重要的 OO 概念? 为了回应下面 JustJeff 的评论,我不是在问 C 是否是一种面向对象的语言,也不在问 C 是否(容易与否)允许面向对象编程。 (这不是一个单独的问题吗?) 回答1 从学术上讲,当然实际的文件是对象。 它们具有属性,您可以对它们执行操作。 并不意味着FILE是一个class ,只是说,有一定程度的 OO-ness 需要考虑。 然而,试图说 stdio FILE 接口符合 OO 的问题在于 stdio FILE 接口不能很好地表示文件的“对象性”。 您可以以面向对象的方式在普通的旧 C 下使用 FILE,但当然您会失去 Java 或 C++ 提供的语法清晰度。 可能应该进一步补充说,虽然您不能从 FILE 生成“继承”,但这进一步取消了它作为 OO 的资格,但您可能会争辩说这更像是它的环境(纯 C)的错误,而不是文件的抽象概念 - 作为-对象本身。 事实上..你可能会认为 FILE 类似于 java 接口。 在linux世界中,你几乎可以通过open/close/read/write/ioctl调用来操作任何一种I/O设备; FILE 函数只是覆盖在这些函数之上; 因此,在 FILE 中