Наследование структур в C

Могу ли я наследовать структуру в C? Если да, то как?


person Community    schedule 11.07.2009    source источник
comment
В C нет такой вещи, как наследование   -  person Ed S.    schedule 11.07.2009
comment
... Если вы не реализуете это.   -  person Philip    schedule 20.03.2013


Ответы (12)


Самое близкое, что вы можете получить, это довольно распространенная идиома:

typedef struct
{
    // base members

} Base;

typedef struct
{
    Base base;

    // derived members

} Derived;

Поскольку Derived начинается с копии Base, вы можете сделать это:

Base *b = (Base *)d;

Где d является экземпляром Derived. Так что они как бы полиморфны. Но наличие виртуальных методов — еще одна проблема — для этого вам потребуется эквивалент указателя vtable в Base, содержащий указатели на функции, которые принимают Base в качестве своего первого аргумента (которую вы могли бы назвать this).

К этому моменту вы уже можете использовать C++!

person Daniel Earwicker    schedule 11.07.2009
comment
Ну, это при условии, что для вашей платформы доступен компилятор C++! - person ; 13.07.2009
comment
Если доступен компилятор C, то доступен и компилятор C++ — просто используйте тот, который производит C в качестве вывода. - person Daniel Earwicker; 13.07.2009
comment
Уф.. ты спас мне жизнь. Обычно я кодирую на Java, и когда я столкнулся с кодом, похожим на то, что вы опубликовали, я подумал, что это композиция, и был чертовски смущен, когда они его использовали. - person Surya Wijaya Madjid; 11.09.2012
comment
Вы можете увидеть его использование в сложных реальных проектах (таких как git). См. log-tree.c. тег структуры наследуется от структурировать объект - person albfan; 07.04.2013
comment
Я делал то же самое. Проблема в том, что если вы реализуете косвенность методов, вы получите довольно плохую читабельность, например personDAO->getPersonById(personDAO, personDTO->getId(personDTO));. К сожалению, другого пути я не вижу. - person Powerslave; 09.04.2015
comment
Простите, как это Base *b = (Base *)d; возможно. Один Derived*, а другой Base*. Поскольку Derived будет иметь больше членов, указатели будут другими. Как вы можете сделать этот бросок? - person Doga Oruc; 10.09.2020
comment
@DogaOruc C настолько низкоуровневый, что указатель - это просто указатель. У него нет типа. Как говорится в ответе, если расположение указателя начинается с содержимого Base, его можно использовать как Base. В этом случае ячейка памяти Derived начинается точно так же, как ячейка памяти Base. (В этом комментарии могут быть ошибки, это моя интерпретация.) - person entonio; 22.10.2020

C не имеет явного понятия наследования, в отличие от C++. Однако вы можете повторно использовать структуру в другой структуре:

typedef struct {
    char name[NAMESIZE];
    char sex;
} Person;

typedef struct {
    Person person;
    char job[JOBSIZE];
} Employee;

typedef struct {
    Person person;
    char booktitle[TITLESIZE];
} LiteraryCharacter;
person Community    schedule 11.07.2009
comment
Насколько я знаю, вы можете иметь член структуры/класса внутри другого в С++. - person Tyler Millican; 11.07.2009
comment
C говорит, что перед первым членом структуры не появляется отступ. Таким образом, вы фактически можете (и разрешено) привести LiteraryCharacter* к Person* и обращаться с ним как с человеком. +1 - person Johannes Schaub - litb; 12.07.2009
comment
@JohannesSchaub-litb ваш комментарий был лучшим объяснением, чем сам ответ :) - person Greg; 15.02.2012
comment
Важно отметить, что вы должны передавать только эти типы в качестве ссылок. Вы не можете копировать их в объект Person, иначе они будут объединены. - person Kevin Cox; 16.03.2014

Мне нравится и я использую идею Typesafe наследования в C.

Например:

struct Animal
{
    int weight;
};

struct Felidae
{
    union {
      struct Animal animal;
    } base;
    int furLength;
};

struct Leopard
{
    union {
      struct Animal animal;
      struct Felidae felidae;
    } base;

    int dotCounter;
};

Использование:

struct Leopard leopard;
leopard.base.animal.weight = 44;
leopard.base.felidae.furLength = 2;
leopard.dotCounter = 99;
person Martin    schedule 05.10.2011
comment
Я никогда не думал об этом. А если союз сделать анонимным, то вполне аккуратно. Однако недостатком является то, что вам нужно перечислить всех родителей, чтобы избежать вложенных переменных. - person Kevin Cox; 16.03.2014
comment
Интересный подход. Однако, как только вы наберете leopard.base, весь смысл наследования/полиморфизма будет устранен. - person Powerslave; 09.04.2015
comment
У вас тут нет проблемы наследования бриллиантов? leopard.base.felidae.base.animal.weight и leopard.base.animal.weight? - person Alexander Torstling; 16.02.2016
comment
@ Александр Торстлинг нет, не будешь. leopard.base.felidae.base.animal.weight - это просто другое имя для leopard.base.animal.weight - это то же самое в том же месте в памяти. - person Martin; 22.02.2016
comment
Это выглядит великолепно. Спасибо. - person PSkocik; 16.12.2016
comment
Я бы сказал, что в некоторых случаях вам не нужно полиморфное поведение, только унаследованные способности. Тем не менее, вы все равно можете получить полиморфизм, поскольку каждая вещь, основанная на Animal, должна иметь пространство имен .base.animal, хотя это немного надумано. - person Kurt E. Clothier; 05.02.2018

Если ваш компилятор поддерживает анонимные структуры, вы можете сделать это:

typedef struct Base
{
    // base members
} Base_t;

typedef struct
{
   struct Base;  //anonymous struct

   // derived members

} Derived_t;

Таким образом, к элементам базовой структуры можно получить прямой доступ, что удобнее.

person user836773    schedule 26.08.2012
comment
Суффикс _t зарезервирован в POSIX. Делайте все, что хотите, просто имейте в виду, что вы, вероятно, столкнетесь с конфликтами, если будете писать свой код для системы POSIX (например, Linux), или кто-то в конечном итоге захочет перенести ваш код в систему POSIX. - person L0j1k; 01.02.2014
comment
На самом деле это не работает в стандартном C (даже не в C11). - person Chase; 11.03.2014

Если вы хотите использовать некоторую магию gcc (которая, как я полагаю, будет работать с компилятором Microsoft C), вы можете сделать что-то вроде:


struct A
{
   int member1;
};

struct B
{
   struct A;
   int member2;
}

С помощью gcc вы можете скомпилировать это с помощью -fms-extensions (позволяет использовать неименованные члены структуры, как это делает компилятор Microsoft). Это похоже на решение, данное Даниэлем Эрвикером, за исключением того, что оно позволяет вам получить доступ к memeber1 в экземпляре структуры B. то есть B.member1 вместо B.A.member1.

Вероятно, это не самый переносимый подход, и он не будет работать при использовании компилятора C++ (другая языковая семантика означает, что он повторно объявляет/определяет структуру A вместо ее создания).

Однако, если вы живете только в стране gcc/C, он будет работать и делать именно то, что вы хотите.

person Matt    schedule 13.05.2013
comment
Разве это не композиция? - person Sumit Gera; 21.09.2013
comment
Нет, это правильное наследование. Предполагая, что у вас есть структура типа struct B с именем b, b.member1 будет компилироваться и работать так, как вы ожидаете. Композиция будет выглядеть примерно так: b.base.member1. GCC выполняет эту магию для вас. На самом деле в этом случае структура B определяется как два целых числа. - person Matt; 21.09.2013
comment
Возможно ли это только в C, а не в C++? Если нет, посетите это. - person Sumit Gera; 21.09.2013
comment
Это только C. Синтаксис недопустим в C++. Хотя C++ имеет правильное наследование структур, как и классов. - person Matt; 21.09.2013
comment
Теперь я чувствую себя намного лучше! - person Sumit Gera; 21.09.2013
comment
Это стандартный C или просто GCC? - person Zorgatone; 12.11.2015
comment
Это может быть стандартизировано в c11, я не уверен. Это должно работать в msvc gcc и clang, хотя - person Matt; 13.11.2015
comment
Описывать это как магию GCC, которая может работать в MSVC, когда она скопирована из MSVC и требует включения флага -fms-extensions, очень странно. - person Michael Mrozek; 26.05.2020
comment
Здравствуйте, я не могу заставить это работать в GCC (компилятор, который я использовал) даже с флагом -std=c11, может Я спрашиваю, какие флаги вы использовали? - person WENDYN; 24.07.2021

Вы можете сделать вышеупомянутое

typedef struct
{
    // base members

} Base;

typedef struct
{
    Base base;

    // derived members

} Derived;

Но если вы хотите избежать приведения указателей, вы можете использовать указатели на union из Base и Derived.

person MighMoS    schedule 11.07.2009

Это работает при компиляции с -fms-extensions

Изображение диаграммы

main.c

#include "AbstractProduct.h"
#include "Book.h"
#include "Product.h"
#include "TravelGuide.h"

/***********************/

int main() {

    Product p = Product_new();  
    p.set_id(&p, 2);
    p.set_name(&p, "name2");
    p.set_description(&p, "description2");
    p.set_price(&p, 2000);  
    p.display(&p);

    TravelGuide tg = TravelGuide_new(); 
    tg.set_id(&tg, 1);
    tg.set_name(&tg, "name1");
    tg.set_description(&tg, "description1");        
    tg.set_price(&tg, 1000);
    tg.set_isbn(&tg, "isbn1");
    tg.set_author(&tg, "author1");
    tg.set_title(&tg, "title1");
    tg.set_country(&tg, "country1");
    tg.display(&tg);

}

AbstractProduct.c

#include "AbstractProduct.h"

/*-------------------------------*/

static void set_id(AbstractProduct *this, int id) {
    this->id = id;
}

/*-------------------------------*/

static void set_name(AbstractProduct *this, char *name) {
    strcpy(this->name, name);
}

/*-------------------------------*/

static void set_description(AbstractProduct *this, char *description) {
    strcpy(this->description, description);
}

/*-------------------------------*/

static int get_id(AbstractProduct *this) {
    return this->id;    
}

/*-------------------------------*/

static char *get_name(AbstractProduct *this) {
    return this->name;  
}

/*-------------------------------*/

static char *get_description(AbstractProduct *this) {
    return this->description;   
}

/*-------------------------------*/

static void display(AbstractProduct *this) {

    printf("-AbstractProduct- \n"); 
    printf("id: %d\n", this->get_id(this)); 
    printf("name: %s\n", this->get_name(this)); 
    printf("description: %s\n", this->get_description(this));   
    printf("\n");
}

/*-------------------------------*/

void AbstractProduct_init(AbstractProduct *obj) {

    obj->set_id = set_id;
    obj->set_name = set_name;
    obj->set_description = set_description; 
    obj->get_id = get_id;
    obj->get_name = get_name;
    obj->get_description = get_description;
    obj->display = display;

}

/*-------------------------------*/

AbstractProduct AbstractProduct_new() {

    AbstractProduct aux;
    AbstractProduct_init(&aux);
    return aux;
}

AbstractProduct.h

#ifndef AbstractProduct_H
#define AbstractProduct_H

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

/***********************/

typedef struct AbstractProduct{

    int id;
    char name[1000];
    char description[1000];

    void (*set_id)();
    void (*set_name)();
    void (*set_description)();  
    int (*get_id)();    
    char *(*get_name)();    
    char *(*get_description)(); 
    void (*display)();  

} AbstractProduct;

AbstractProduct AbstractProduct_new();
void AbstractProduct_init(AbstractProduct *obj);

#endif

Book.c

#include "Book.h"

/*-------------------------------*/

static void set_isbn(Book *this, char *isbn) {
    strcpy(this->isbn, isbn);
}

/*-------------------------------*/

static void set_author(Book *this, char *author) {
    strcpy(this->author, author);
}

/*-------------------------------*/

static void set_title(Book *this, char *title) {
    strcpy(this->title, title);
}

/*-------------------------------*/

static char *get_isbn(Book *this) {
    return this->isbn;  
}

/*-------------------------------*/

static char *get_author(Book *this) {
    return this->author;    
}

/*-------------------------------*/

static char *get_title(Book *this) {
    return this->title; 
}

/*-------------------------------*/

static void display(Book *this) {

    Product p = Product_new();
    p.display(this);    

    printf("-Book- \n");
    printf("isbn: %s\n", this->get_isbn(this)); 
    printf("author: %s\n", this->get_author(this)); 
    printf("title: %s\n", this->get_title(this));   
    printf("\n");
}

/*-------------------------------*/

void Book_init(Book *obj) {

    Product_init((Product*)obj);

    obj->set_isbn = set_isbn;
    obj->set_author = set_author;
    obj->set_title = set_title; 
    obj->get_isbn = get_isbn;
    obj->get_author = get_author;
    obj->get_title = get_title; 
    obj->display = display;
}
/*-------------------------------*/

Book Book_new() {

    Book aux;   
    Book_init(&aux);
    return aux;
}

Book.h

#ifndef Book_H
#define Book_H

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

#include "Product.h"

/***********************/

typedef struct Book{

    Product;
    char isbn[1000];
    char author[1000];
    char title[1000];

    void (*set_isbn)();
    void (*set_author)();
    void (*set_title)();    

    char *(*get_isbn)();
    char *(*get_author)();
    char *(*get_title)();   
    // void (*display)();   


} Book;

Book Book_new();
void Book_init(Book *obj);

#endif

Товар.c

#include "Product.h"

/*-------------------------------*/

static void set_price(Product *this, double price) {
    this->price = price;
}

/*-------------------------------*/

static double get_price(Product *this) {
    return this->price; 
}

/*-------------------------------*/

static void display(Product *this) {

    AbstractProduct p = AbstractProduct_new();
    p.display(this);    

    printf("-Product- \n"); 
    printf("price: %f\n", this->get_price(this));   
    printf("\n");
}

/*-------------------------------*/

void Product_init(Product *obj) {

    AbstractProduct_init((AbstractProduct*)obj);

    obj->set_price = set_price;
    obj->get_price = get_price; 
    obj->display = display;

}

/*-------------------------------*/

Product Product_new() {

    Product aux;    
    Product_init(&aux);
    return aux;
}

Product.h

#ifndef Product_H
#define Product_H

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "AbstractProduct.h"

/***********************/

typedef struct Product{

    AbstractProduct;
    double price;

    void (*set_price)();
    double (*get_price)();  
    // void (*display)();   

} Product;

Product Product_new();
void Product_init(Product *obj);

#endif

TravelGuide.c

#include "TravelGuide.h"

/*-------------------------------*/

static void set_country(TravelGuide *this, char *country) {
    strcpy(this->country, country);
}

/*-------------------------------*/

static char *get_country(TravelGuide *this) {
    return this->country;   
}

/*-------------------------------*/

static void display(TravelGuide *this) {

    Book b = Book_new();
    b.display(this);

    printf("-TravelGuide- \n"); 
    printf("country: %s\n", this->get_country(this));   
    printf("\n");
}

/*-------------------------------*/

void TravelGuide_init(TravelGuide *obj) {

    Book_init((Book*)obj);
    obj->set_country = set_country;
    obj->get_country = get_country;
    obj->f = obj->display;
    obj->display = display;

}

/*-------------------------------*/

TravelGuide TravelGuide_new() {

    TravelGuide aux;
    TravelGuide_init(&aux);
    return aux;
}

TravelGuide.h

#ifndef TravelGuide_H
#define TravelGuide_H

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

#include "Book.h"

/***********************/

typedef struct TravelGuide{

    Book;
    char country[1000];
    void (*f)();

    void (*set_country)();
    char *(*get_country)();
    // void *(*display)();

} TravelGuide;

TravelGuide TravelGuide_new();
void TravelGuide_init(TravelGuide *obj);

#endif

Создать файл

.PHONY: clean
define ANNOUNCE_BODY

    ***********************************************
    ************          start make **************
    ***********************************************
endef

all:
    $(info $(ANNOUNCE_BODY))    

    clear;
    if [ -f binary/main ]; then rm binary/main; fi;

# compiler 

    gcc $(INC) -c -fms-extensions main.c -o binary/main.o
    gcc $(INC) -c -fms-extensions AbstractProduct.c -o binary/AbstractProduct.o
    gcc $(INC) -c -fms-extensions Product.c -o binary/Product.o
    gcc $(INC) -c -fms-extensions Book.c -o binary/Book.o
    gcc $(INC) -c -fms-extensions TravelGuide.c -o binary/TravelGuide.o

# linker    

    gcc binary/main.o \
        binary/AbstractProduct.o \
        binary/Product.o \
        binary/Book.o \
        binary/TravelGuide.o \
        -o \
        binary/main
person MoaLaiSkirulais    schedule 13.03.2016
comment
хорошо для доказательства концепции, но если вам нужно пройти весь этот путь, чтобы достичь ООП в C, вам лучше использовать C++. ИМХО C не предназначен для ООП, и если он действительно должен быть чистым ООП (с большими усилиями, затраченными на это), вы используете неправильный язык. Я, например, не хотел бы поддерживать такой код. - person Alex; 12.04.2016
comment
Кроме того, при таком подходе я думаю, что вы тратите немного памяти. В отличие от C++, для каждого экземпляра будет дополнительный sizeof(function pointer) * количество методов. В C++ sizeof(class) не включает указатели на методы, а при наличии виртуальных методов - содержит 1 дополнительный указатель на vtable. - person Alex; 29.06.2016

Небольшое изменение ответа анона (и других подобных). Для одного уровня глубокого наследования можно сделать следующее:

#define BASEFIELDS              \
    char name[NAMESIZE];        \
    char sex

typedef struct {
    BASEFIELDS;
} Person;

typedef struct {
    BASEFIELDS;
    char job[JOBSIZE];
} Employee;

typedef struct {
    BASEFIELDS;
    Employee *subordinate;
} Manager;

Таким образом, функции, принимающие указатель на Person, будут принимать указатель на Employee или Manager (с приведением), как и в других ответах, но и в этом случае инициализация будет естественной:

Employee e = {
    .name = "...";
    ...
};

vs

# as in anon's answer
Employee e = {
    .person.name = "...";
    ...
};

Я считаю, что так делают некоторые популярные проекты (например, libuv)

ОБНОВЛЕНИЕ: также есть несколько хороших примеров похожей (но не такой же) концепции в реализации событий libsdl с использованием структур и объединений.

person Alex    schedule 23.02.2016
comment
Это имеет сложные последствия алиасинга. Насколько я понимаю, вы можете переназначать по цепочке наследования только до тех пор, пока структуры находятся в динамической памяти. (См. gustedt.wordpress.com/2016/08/17. /эффективные типы и псевдонимы) Без динамической памяти подход с объединением, вероятно, лучше. - person PSkocik; 16.12.2016
comment
@PSkocik Очевидно, что при игре с такими вещами следует соблюдать осторожность, так как многое может пойти не так. Я верю, что пример будет работать и для указателей на переменные стека. При этом я согласен с тем, что когда дело доходит до кастинга в таких сценариях, Союз всегда является лучшим решением. - person Alex; 16.12.2016
comment
Неважно, как распределяются объекты (автоматически, статически, динамически). Одним из примеров потенциальных проблем является то, что разные объекты могут иметь разное количество отступов между полями name и sex, но есть и другие возможности. Основная проблема заключается в том, что нет никаких гарантий, что это работает, что делает этот код ненадежным и, следовательно, непригодным для использования. - person user694733; 16.12.2016
comment
@user694733 user694733 Я не согласен с заявлением о заполнении. Нас интересуют только BASEFIELDS, и заполнение для них является детерминированным на каждой данной платформе, при условии, что они всегда размещаются в начале структуры. Очевидно, что в реальном сценарии BASEFIELDS должен будет содержать идентификатор типа, чтобы обеспечить правильную обработку полей, и довольно скоро все станет грязным. Я согласен, однако, что такого рода хакерство нехорошо, и если кому-то нужно наследование в стиле С++, им следует использовать С++. - person Alex; 16.12.2016
comment
CPython также использует подобный синтаксис для построения типов python, см. docs.python.org/2/extending /newtypes.html, здесь PyObject_HEAD определяет общие поля, необходимые для типов Python. - person Glen Fletcher; 05.03.2017

C не является объектно-ориентированным языком и, следовательно, не имеет наследования.

person txwikinger    schedule 11.07.2009

Вы можете имитировать это, но вы не можете действительно наследовать.

person luiscubal    schedule 11.07.2009
comment
что такое реальность? C++ — это просто очень простая библиотека времени выполнения для диспетчеризации и множество синтаксиса компилятора для ее вызова при необходимости. в конце концов, исходные компиляторы C++ создавали код C. (и очень читаемый C на самом деле) - person Javier; 11.07.2009

Нет, ты не можешь. imo лучший подход к ООП в C - это использование ADT.

person Macarse    schedule 11.07.2009
comment
Вы можете, но да, шаблон ADT — лучший шаблон в C для такого типа работы. - person christopher clark; 19.11.2020

Нет, ты не можешь. C не поддерживает концепцию наследования.

person JaredPar    schedule 11.07.2009
comment
не поддерживает, но и не мешает. - person Javier; 11.07.2009