1

我正在使用 C 进行继承(即使用子类数据类型调用超类的函数),但遇到了别名问题。

在下面,shape函数是用rectangle对象调用的。

在 的情况下me->superxy是正确的。然而,他们错了(Shape*)me

我更喜欢的原因(Shape*)meme->super我想对客户隐藏结构实现。

C标准不应该保证吗?根据第 6.7.2.1.13 节

“……一个指向结构对象的指针,经过适当的转换,指向它的初始成员。结构对象内可能有未命名的填充,但不是在其开头”。

/*shape.h*/
#ifndef SHAPE_H
#define SHAPE_H

typedef struct Shape Shape;

Shape* Shape_ctor(int x, int y);
int Shape_getX(Shape* me);
int Shape_getY(Shape* me);

#endif

/*shape.c*/
#include <stdlib.h>
#include "shape.h"

struct Shape
{
    int x;
    int y;
};

Shape* Shape_ctor(int x, int y)
{
    Shape* me = malloc(sizeof(struct Shape));
    me->x = x;
    me->y = y;

    return me;
}

int Shape_getX(Shape* me)
{
    return me->x;
}

int Shape_getY(Shape* me)
{
    return me->y;
}

/*rectangle.h*/
#ifndef RECT_H
#define RECT_H

#include "shape.h"

typedef struct Rectangle Rectangle;


Rectangle* Rectangle_ctor(int x, int y, unsigned int width, unsigned int height);

int Rectangle_getWidth(Rectangle* me);
int Rectangle_getHeight(Rectangle* me);

#endif

/*rectangle.c*/
#include <stdlib.h>
#include "rectangle.h"
#include "stdio.h"

struct Rectangle
{
    Shape* super;
    unsigned int width;
    unsigned int height;
};

Rectangle* Rectangle_ctor(int x, int y, unsigned int width, unsigned int height)
{
    Rectangle* me = malloc(sizeof(struct Rectangle));
    me->super = Shape_ctor(x, y);
    me->width = width;
    me->height = height;

    printf("x: %d\n", Shape_getX(me->super)); //correct value
    printf("y: %d\n", Shape_getY(me->super)); //correct value

    printf("x: %d\n", Shape_getX((Shape*)me)); // wrong value
    printf("y: %d\n", Shape_getY((Shape*)me)); // wrong value

    return me;
}

int Rectangle_getWidth(Rectangle* me)
{
    return me->width;
}

int Rectangle_getHeight(Rectangle* me)
{
    return me->height;
}

/*main.c*/
#include <stdio.h>
#include "rectangle.h"

int main(void) {

  Rectangle* r1 = Rectangle_ctor(0, 2, 10, 15);
  printf("r1: (x=%d, y=%d, width=%d, height=%d)\n", Shape_getX((Shape*)r1)
                                                  , Shape_getY((Shape*)r1)
                                                  , Rectangle_getWidth(r1)
                                                  , Rectangle_getHeight(r1));

  return 0;
}
4

3 回答 3

2

您应该将基类型作为第一个成员。不是指向基本类型的指针。

struct Rectangle {
    Shape super;
    ...
}

此外,您应该重新设计Shape_ctor. 我建议将指针作为参数并将内存管理委托给调用者。


Shape* Shape_ctor(Shape *me, int x, int y)
{
    me->x = x;
    me->y = y;

    return me;
}

矩形的构造函数是:

Rectangle* Rectangle_ctor(Rectangle *me, int x, int y, unsigned int width, unsigned int height)
{
    Shape_ctor(&me->super, x, y); // call base constructor
    me->width = width;
    me->height = height;

    printf("x: %d\n", Shape_getX(&me->super)); //correct value
    printf("y: %d\n", Shape_getY(&me->super)); //correct value

    return me;
}

典型用法:

Rectangle rect;
Rectangle_ctor(&rect, ...);

或者更多异国情调的变体,例如:

Rectangle* rect = malloc(sizeof *rect);
Rectangle_ctor(rect, ...);

// or
Rectangle* rect = Rectangle_ctor(malloc(sizeof *rect), ...);

// or even kind of automatic pointer
Rectangle* rect = Rectangle_ctor(&(Rectangle){0}, ...);

只有实现虚拟方法(如Shape_getArea().

struct Shape {
  ...
  double (*getArea)(struct Shape*);
};

double Shape_getArea(Shape *me) {
  return me->getArea(me);
}
double Rectangle_getArea(Shape *base) {
  Rectangle *me = (Rectangle*)base; // the only cast
  return (double)me->width * me->height;
}

Rectangle* Rectangle_ctor(Rectangle *me, int x, int y, unsigned int width, unsigned int height) {
  ...
  me->super.getArea = Rectangle_getArea;
  ...
}

// usage:
Rectangle rect;
Rectangle_ctor(&rect, 0, 0, 3, 2);

Shape *shape = &rect.super;

Shape_getArea(shape); // should return 6

编辑

为了隐藏内部Shape结构,在结构中放置一个指向其私有数据的指针。用 中的相关数据初始化这个指针Shape_ctor

struct Shape {
  void *private_data;
  // non private fields
};
于 2021-10-27T09:43:29.457 回答
1

的初始成员Rectangle是 a Shape*,而不是 a Shape。的初始成员Shape是一个 int , not a Shape*`。你假设的共性不存在。

如果您查看 C++ 实现如何实现继承,在单继承的情况下,它们会将基本子对象放置在完整对象内的偏移量 0 处。指向基类子对象的指针用于虚拟继承,但这很快就会变得复杂。

于 2021-10-27T09:46:01.917 回答
-1

在编写 C99 时,委员会成员之间存在冲突,他们拒绝将标准定性为一些利用通用初始序列保证的有用结构是非法的,而那些拒绝将其定性为会破坏依赖于此类的代码的非法优化的委员会成员之间存在冲突。构造,但对于没有的代码很有用。

这种冲突是通过编写模棱两可的规则来解决的,双方都可以将其解释为表达了他们想要表达的意思。由于从未就规则的含义达成任何共识,因此有关规则真正含义的问题本质上是无法回答的。

即使回到 C89,规则真正有意义的唯一方法是,如果将“通过特定类型的左值”这一短语解释为适用于从适当类型的事物中新可见地派生的取消引用指针。否则,给出如下内容:

struct s { int x[2]; } foo;

访问foo.x[1]将违反类型别名规则,因为它被定义为等效于*(foo.x+1). 内部表达式(foo.x+1)是一个类型指针int*,它与类型无关struct sint也不属于可用于访问类型对象的类型struct s。任何体面的编译器都应该清楚地认识到指针是从一个类型的对象新可见的派生而来struct s, 但是,并将访问视为通过该类型执行。指针何时“新鲜可见”的问题是标准管辖范围之外的实施质量问题,期望编译器会做出合理的努力来注意到这些事情,无论标准是否强制他们这样做。大多数冲突都围绕着这样一个事实,即一些对销售编译器不感兴趣的编译器维护者故意对任何形式的派生视而不见,而不是避免使结构完全无用。

据我所知,所有不基于 clang 和 gcc 的编译器都将支持利用通用初始序列规则的通用构造,即使启用了基于类型的别名分析也是如此。此外,当启用基于类型的别名分析时,即使在某些严格符合标准的程序上,clang 和 gcc 有时也会执行错误的“优化”。因此,与其试图跳过障碍以兼容 clang 和 gcc 的优化模式,我建议简单地记录代码要求实现,作为“符合语言扩展”的一种形式,做出合理的努力涉及通用初始序列保证的过程构造很有用。

于 2021-10-29T17:29:41.893 回答