Перейти к содержимому

Как сделать интерфейс в c

  • автор:

Интерфейсы — определение поведения для нескольких типов

Интерфейс содержит определения для группы связанных функций, которые должен реализовывать неавстрактный class или struct . Интерфейс может определять методы static , которые должны иметь реализацию. Интерфейс может определять реализацию по умолчанию для членов. Интерфейс не может объявлять данные экземпляра, такие как поля, автоматические реализуемые свойства или события, подобные свойствам.

С помощью интерфейсов можно, например, включить в класс поведение из нескольких источников. Эта возможность очень важна в C#, поскольку этот язык не поддерживает множественное наследование классов. Кроме того, необходимо использовать интерфейс, если требуется имитировать наследование для структур, поскольку они не могут фактически наследовать от другой структуры или класса.

Интерфейс определяется с помощью ключевого слова , interface как показано в следующем примере.

interface IEquatable

Имя интерфейса должно быть допустимым именем идентификатора C#. По соглашению имена интерфейсов начинаются с заглавной буквы I .

Любой объект (класс или структура), реализующий интерфейс IEquatable , должен содержать определение для метода Equals, соответствующее сигнатуре, которую задает интерфейс. В результате вы можете быть уверены, что класс, реализующий IEquatable , содержит метод Equals , с помощью которого экземпляр этого класса может определить, равен ли он другому экземпляру того же класса.

Определение IEquatable не предоставляет реализацию для метода Equals . Класс или структура может реализовывать несколько интерфейсов, но класс может наследовать только от одного класса.

Дополнительные сведения об абстрактных классах см. в разделе Абстрактные и запечатанные классы и члены классов.

Интерфейсы могут содержать методы экземпляра, свойства, события, индексаторы, а также любое сочетание этих четырех типов членов. Интерфейсы могут содержать статические конструкторы, поля, константы или операторы. Начиная с C# 11, элементы интерфейса, которые не являются полями, могут быть static abstract . Интерфейс не может содержать поля экземпляров, конструкторы экземпляров или методы завершения. Члены интерфейса по умолчанию являются общедоступными, и вы можете явно указать модификаторы доступа, такие как public , protected , internal , private , protected internal или private protected . Элемент private должен иметь реализацию по умолчанию.

Для реализации члена интерфейса соответствующий член реализующего класса должен быть открытым и не статическим, а также иметь такое же имя и сигнатуру, что и член интерфейса.

Когда интерфейс объявляет статические члены, тип, реализующий этот интерфейс, также может объявлять статические члены с той же сигнатурой. Они отличаются и однозначно идентифицируются типом, объявляющим элемент. Статический элемент, объявленный в типе , не переопределяет статический элемент, объявленный в интерфейсе .

Класс или структуру, реализующие интерфейс, должны предоставлять реализацию для всех объявленных членов без реализации по умолчанию, предоставляемой интерфейсом . Однако если базовый класс реализует интерфейс, то любой класс, производный от базового класса, наследует эту реализацию.

public class Car : IEquatable  < public string? Make < get; set; >public string? Model < get; set; >public string? Year < get; set; >// Implementation of IEquatable interface public bool Equals(Car? car) < return (this.Make, this.Model, this.Year) == (car?.Make, car?.Model, car?.Year); >> 

Свойства и индексаторы класса могут определять дополнительные методы доступа для свойства или индексатора, определенного в интерфейсе. Например, интерфейс может объявлять свойство, имеющее акцессор get. Класс, реализующий этот интерфейс, может объявлять это же свойство с обоими акцессорами ( get и set). Однако если свойство или индексатор использует явную реализацию, методы доступа должны совпадать. Дополнительные сведения о явной реализации см. в статьях Явная реализация интерфейса и Свойства интерфейса.

Интерфейс может наследовать от одного или нескольких интерфейсов. Производный интерфейс наследует члены от своих базовых интерфейсов. Класс, реализующий производный интерфейс, должен реализовывать все члены в нем, включая все члены базовых интерфейсов производного интерфейса. Этот класс может быть неявно преобразован в производный интерфейс или любой из его базовых интерфейсов. Класс может включать интерфейс несколько раз через наследуемые базовые классы или через интерфейсы, которые наследуются другими интерфейсами. Однако класс может предоставить реализацию интерфейса только однократно и только если класс объявляет интерфейс как часть определения класса ( class ClassName : InterfaceName ). Если интерфейс наследуется, поскольку наследуется базовый класс, реализующий этот интерфейс, то базовый класс предоставляет реализацию членов этого интерфейса. Но производный класс может повторно реализовать любые члены виртуального интерфейса и не использовать наследованную реализацию. Когда интерфейсы объявляют реализацию метода по умолчанию, любой класс, реализующий этот интерфейс, наследует эту реализацию (для доступа к реализации по умолчанию в члене интерфейса необходимо привести экземпляр класса к типу интерфейса).

Базовый класс также может реализовывать члены интерфейса с помощью виртуальных членов. В таком случае производный класс может изменять поведение интерфейса путем переопределения виртуальных членов. Дополнительные сведения о виртуальных членах см. в статье Полиморфизм.

Сводка по интерфейсам

Интерфейс имеет следующие свойства.

  • В версии C# 8.0 и более ранних интерфейс подобен абстрактному базовому классу, содержащему только абстрактные элементы. Класс (или структура), реализующий интерфейс, должен реализовывать все его элементы.
  • Начиная с C# 8.0 интерфейс может определять реализации по умолчанию для некоторых или для всех его элементов. Класс или структура, реализующие интерфейс, не должны реализовывать элементы, имеющие реализации по умолчанию. Дополнительные сведения см. в статье о методах интерфейса по умолчанию.
  • Невозможно создать экземпляр интерфейса напрямую. Его члены реализуются любым классом (или структурой), реализующим интерфейс.
  • Класс или структура может реализовывать несколько интерфейсов. Класс может наследовать базовому классу и также реализовывать один или несколько интерфейсов.

Совместная работа с нами на GitHub

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

Что есть интерфейсы на Си?

Доброго всем времени суток, интересует такой вопрос, как реализовать интерфейсы на Си(например как в С++ IApplication), как они вообще реализуются и что это будет, это будет какая-либо структура или просто набор функций? К примеру:

struct IApplication < void (*load)(void); void (*shutdown)(void); >; 

Объясните на пальцах 🙂

xterro ★★★★★
21.10.10 14:14:53 MSD

>это будет какая-либо структура или просто набор функций?

ну, как сделаешь, так и будет.
хочешь полиморфизм — структура, если нет — набор функций залепил, и все.

k0l0b0k ★★
( 21.10.10 14:19:32 MSD )
Ответ на: комментарий от k0l0b0k 21.10.10 14:19:32 MSD

А как тогда с объектом, который реализует этот интерфейс? Что ему делать? Если в С++ я наследуюсь от интерфейса и перегружаю его методы, то тут как быть, наследования же нет?

xterro ★★★★★
( 21.10.10 14:26:35 MSD ) автор топика

руки прочь от сишечки, проклятые шарперы!!

Интерфейсы — это защита от дурака. Мол, забыл какой-то метод определить — и бац, ошибка компиляции.

Никто не запрещает написать комментом

/* implements that interface */

и поналепить тот набор функций, который нужен для реализации того или иного интерфейса. Компилироваться будет так же

yoghurt ★★★★★
( 21.10.10 14:29:24 MSD )
Ответ на: комментарий от xterro 21.10.10 14:26:35 MSD

Есть тут наследование, вы просто не умеете его готовить (с)

yoghurt ★★★★★
( 21.10.10 14:30:03 MSD )
Ответ на: комментарий от xterro 21.10.10 14:26:35 MSD

struct Base < // your pointers >; struct Derived < struct Base myPrivateBase; // + заполняешь myPrivateBase своими указателями >;

если для десктопа, то лучше возьми GLib. а вообще городить ООП на C — моветон (щас меня будут бить, но это имхо)

k0l0b0k ★★
( 21.10.10 14:31:30 MSD )
Ответ на: комментарий от xterro 21.10.10 14:26:35 MSD

static void my_load(void) < . >static void my_shutdown(void) < . >struct IApplication < .load = my_load, .shutdown = my_shutdown, >MyObject;

в лучших традициях ядра линукса же 🙂

arsi ★★★★★
( 21.10.10 14:31:30 MSD )
Ответ на: комментарий от xterro 21.10.10 14:26:35 MSD

struct IApplication < void (*load)(void); void (*shutdown)(void); >; struct MyApplication < struct IApplication; void (*myFunc)(void); >; 

kulti ★★
( 21.10.10 14:32:00 MSD )
Ответ на: комментарий от yoghurt 21.10.10 14:29:24 MSD

>и поналепить тот набор функций, который нужен для реализации того или иного интерфейса. Компилироваться будет так же

будет лучше. накладники на indirect call никто не отменял еще.

k0l0b0k ★★
( 21.10.10 14:32:22 MSD )
Ответ на: комментарий от k0l0b0k 21.10.10 14:31:30 MSD

Просто работаю над сервером приложений для трёхзвенки(для своей небольшой системки), делаю на си, и решил продумать систему интерфейсов, в том числе и для поддержки плагинов, а вот как это в си будет выглядеть не представляю, посему разрабатываю этот вопрос.

xterro ★★★★★
( 21.10.10 14:36:01 MSD ) автор топика
Ответ на: комментарий от arsi 21.10.10 14:31:30 MSD

Наверно так и получится. а ядре линукса так сделано?

xterro ★★★★★
( 21.10.10 14:37:45 MSD ) автор топика
Ответ на: комментарий от k0l0b0k 21.10.10 14:32:22 MSD

Лучше, да не то же самое. Если функция принимает неизвестный тип с известными методами, то без описания интерфейса (способа доступа к методам) не обойтись.

unsigned ★★★★
( 21.10.10 14:39:46 MSD )
Ответ на: комментарий от xterro 21.10.10 14:37:45 MSD

> а ядре линукса так сделано?

да, местами. файловый в/в, например, так реализован (struct file_operations выступает как интерфейс).

arsi ★★★★★
( 21.10.10 14:44:25 MSD )
Ответ на: комментарий от xterro 21.10.10 14:36:01 MSD

не, вопрос верный, годно.
просто почему не C++? делать руками ту работу, которую делает за тебя компилятор — не самое лучшее решение.

k0l0b0k ★★
( 21.10.10 14:45:19 MSD )
Ответ на: комментарий от unsigned 21.10.10 14:39:46 MSD

>Лучше, да не то же самое. Если функция принимает неизвестный тип с известными методами, то без описания интерфейса (способа доступа к методам) не обойтись.

ну само собой. но ТС спрашивает же про оба варианта?

k0l0b0k ★★
( 21.10.10 14:45:58 MSD )
Ответ на: комментарий от k0l0b0k 21.10.10 14:45:58 MSD

А в C++ нет интерфейсов %

но ТС спрашивает же про оба варианта?

Для плагинов выбора вроде и нет.

unsigned ★★★★
( 21.10.10 14:53:32 MSD )
Ответ на: комментарий от unsigned 21.10.10 14:53:32 MSD

>А в C++ нет интерфейсов %

Руками сделать можно

yoghurt ★★★★★
( 21.10.10 14:56:33 MSD )
Ответ на: комментарий от yoghurt 21.10.10 14:56:33 MSD

Руками и в си можно, еще и красивее выйдет.

unsigned ★★★★
( 21.10.10 15:03:20 MSD )
Ответ на: комментарий от unsigned 21.10.10 14:53:32 MSD

вам видимо ключевого слова interface недостает?
абстрактные классы вполне подходят для понятия интерфейса.

k0l0b0k ★★
( 21.10.10 15:04:05 MSD )
Ответ на: комментарий от k0l0b0k 21.10.10 15:04:05 MSD

Недостает. Интерфейс — не то же, что и класс, пусть даже абстрактный, а реализация — не то же, что наследование. Это работает на практике, но криво в теории )

unsigned ★★★★
( 21.10.10 15:09:26 MSD )
Ответ на: комментарий от yoghurt 21.10.10 14:56:33 MSD

> просто почему не C++?

Начал уж на си, переделывать не хочется. Да и С++ вроде как не предлагает ничего такого чего нельзя сделать на си 🙂

xterro ★★★★★
( 21.10.10 15:10:01 MSD ) автор топика

Нормально сделать нельзя. В кривом^WЪ-Си стиле — см. ядро. Твоему «интерфейсу» не хватает указателя на объект, с которым он оперирует.

tailgunner ★★★★★
( 21.10.10 15:11:47 MSD )
Ответ на: комментарий от unsigned 21.10.10 15:09:26 MSD

>Недостает. Интерфейс — не то же, что и класс, пусть даже абстрактный, а реализация — не то же, что наследование. Это работает на практике, но криво в теории )

не то же. но и не хуже. тот же indirect call. короче мужики пишут, и не смущаются 🙂

k0l0b0k ★★
( 21.10.10 15:13:15 MSD )
Ответ на: комментарий от xterro 21.10.10 15:10:01 MSD

> Да и С++ вроде как не предлагает ничего такого чего нельзя сделать на си 🙂

Попытка троллинга не засчитана — твоя квалификация очевидно слишком низка, чтобы троллить на такую тему 😉

tailgunner ★★★★★
( 21.10.10 15:13:16 MSD )

Сторонники ООП меня заплюют сейчас, но я скажу, что интерфейсы нужны только в 10% случаев, в которых их применяют. Так что если тебе хочется их сделать, подумай ещё раз.

Может быть они тебе таки не нужны.

P.S. хорошая подробная докуменация к написанию плагина (с указанием того, что и как надо писать, чтобы работало) гораздо эффективнее, чем ругань компилятора. Всё равно без документации/просмотра кода никто не полезет писать эти плагины. А если и напишет, то у него оно работать не будет.

anonymous
( 21.10.10 15:15:34 MSD )
Ответ на: комментарий от tailgunner 21.10.10 15:13:16 MSD

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

Нормально сделать нельзя. В кривом^WЪ-Си стиле — см. ядро. Твоему «интерфейсу» не хватает указателя на объект, с которым он оперирует.

Я думал что наоборот структура объекта должна содержать указатель на интерфейс, который реализует. хммм .

xterro ★★★★★
( 21.10.10 15:18:47 MSD ) автор топика

Давно волнует вопрос, как на си сделать реалиацию двух и более интерфейсов в одном типе. Кто-нибудь извра^W занимался?

unsigned ★★★★
( 21.10.10 15:19:58 MSD )
Ответ на: комментарий от xterro 21.10.10 15:18:47 MSD

да и имел ввиду реализацию в рамках моей задачи.

Неважно, в рамках чего. Си++ позволяет решить задачу прозрачным и типобезопасным способом.

Я думал что наоборот структура объекта должна содержать указатель на интерфейс, который реализует. хммм .

У тебя и его нет. Но вообще, если уж хочешь ООП на Си, то это делается так:

struct ops < void (*foo)(void *obj, int arg1 /* итд */); >; struct obj < void *data; struct ops *ops; >; obj->ops->foo(obj->data); 

tailgunner ★★★★★
( 21.10.10 15:23:40 MSD )
Ответ на: комментарий от anonymous 21.10.10 15:15:34 MSD

Как же реализовать модульную структуру без «интерфейсов»? Думаю здесь они как раз нужны.

xterro ★★★★★
( 21.10.10 15:32:41 MSD ) автор топика

Говорят, что интерфейсы это *.h файлы, тогда как в *.c — реализация. Получается КИС модель с деревом включения вроде:

 act.h -- общий итерфейс / \ / \ anim.h \ -- интерфейс между / \ \ cat & dog / \ \ cat.c dog.c voice.c -- реализация 

quasimoto ★★★★
( 21.10.10 15:33:19 MSD )
Ответ на: комментарий от xterro 21.10.10 15:32:41 MSD

см. NPAPI к примеру.

k0l0b0k ★★
( 21.10.10 15:33:56 MSD )
Ответ на: комментарий от xterro 21.10.10 15:32:41 MSD

Если ABI сломать боишься ты и инкапсуляцию нарушить страшно тебе, не пиши код на C

ACR
( 21.10.10 15:52:24 MSD )
Ответ на: комментарий от quasimoto 21.10.10 15:33:19 MSD

Наверно эта модель узкоспецифична, либо обобщена, например у нас кошка не может лаять, а собака наоборот, мяукать, тогда если в anim.h определить два метода: bark() и mew() к примеру, тогда получается что их надо реализовать и в cat.c и в dog.c получится что собака сможет мяукать. хотя модель интересная 🙂

xterro ★★★★★
( 21.10.10 15:53:15 MSD ) автор топика
Ответ на: комментарий от xterro 21.10.10 15:53:15 MSD

Наоборот, это то что играет роль интерфейсов в си. Структуры содержащие функции в качестве полей это немного другое — интерфейс в свете наследования. У меня был абстрактный пример на тему КИС (клиент-интерфейс-сервер), если его немного переделать, то получится так:

 main.h // системный интерфейс / \ / main.c // entry point / animals.h // звериный интерфейс, объекты, аккесоры, алокаторы / | \ / | dog.c // реализация - конструкторы, / cat.c // статические методы / actions.h // интерфейс действий | voice.c // полиморфный метод 

Опять дерево — узлы это файлы интерфейса, листы — файлы реализаций, в каждый файл включается (include) только один предок. Получается модель проектирования сверху вниз — если сразу сделать эту схему неправильно, то как ни старайся — не поедет (т.к. нужно следовать правилам включения интерфейсов — будут ошибки), а если сделать правильно и следовать правилу включения — всё будет нормально. В принципе тут даже header guards не нужен — но чтобы привести пример работы я подключил в main.c интерфейсы (т.е. уже граф), ну и сделал #pragma once.

/* * animals.h -- interface for animals */ #pragma once #include "main.h" typedef char* String; /* objects */ typedef enum < CAT, DOG >Animals; typedef struct < Animals type; String name; >Animal; /* cats */ typedef enum < WHITE, BLACK, GRAY >Colors; typedef struct < Animals type; String name; Colors color; >Cat; /* dogs */ typedef enum < MASTIFF, BULLTERRIER, MONGREL >Breeds; typedef struct < Animals type; String name; Breeds breed; >Dog; /* accessors */ #define type(X) (((Animal*) (X))->type) #define name(X) (((Animal*) (X))->name) #define color(X) (((Cat*) (X))->color) #define breed(X) (((Dog*) (X))->breed) /* macro routins for constructors */ #define ALLOCATE_IT_ANIMAL(TYPE) \ TYPE *IT; \ IT = (TYPE*)malloc(sizeof(TYPE)) #define RETURN_IT_ANIMAL \ return (Animal*) IT /* predefinitions */ extern Animal *make_cat(String name, Colors color); extern Animal *make_dog(String name, Breeds breed); extern String breed_string(Dog *dog); extern String color_string(Cat *cat); 
/* * actions.h -- actions interface */ #pragma once #include "animals.h" extern void voice(Animal *animal); 
/* * main.h -- system interface */ #pragma once #include #include #include
/* * cat.c -- cat's close friends */ #include "animals.h" Animal* make_cat(String name, Colors color) < ALLOCATE_IT_ANIMAL(Cat); IT->type = CAT; IT->name = name; IT->color = color; RETURN_IT_ANIMAL; > String color_string(Cat *cat) < switch (color(cat)) < case WHITE: return "White"; case BLACK: return "Black"; case GRAY: return "Gray"; >error(-1, 1, "Bad Cat in color_string"); return NULL; > 
/* * dog.c -- dog's close personal friends */ #include "animals.h" Animal* make_dog(String name, Breeds breed) < ALLOCATE_IT_ANIMAL(Dog); IT->type = DOG; IT->name = name; IT->breed = breed; RETURN_IT_ANIMAL; > String breed_string(Dog *dog) < switch (breed(dog)) < case MASTIFF: return "Mastiff"; case BULLTERRIER: return "Bullterrier"; case MONGREL: return "Mongrel"; >error(-1, 2, "Bad Dog in breed_string"); return NULL; > 
/* * voice.c -- voice (poly) method */ #include "actions.h" void voice(Animal *animal) < switch (type(animal)) < case CAT: printf("%s cat %s say mew ^_^\n", color_string((Cat*)animal), name(animal)); break; case DOG: printf("%s dog %s say bark >. > 
/* * main.c -- the `main' entry point */ #include "main.h" #include "animals.h" #include "actions.h" int main()
# # Makefile for the `c-iface' example # .PHONY: all clean TARGET=c-iface CC = gcc LINKFLAGS = -g CFLAGS = -c -g -Wall -O3 SRCS = cat.c dog.c voice.c main.c OBJS = $(SRCS:.c=.o) all: $(TARGET) $(TARGET): $(OBJS) $(CC) $ -o $@ $^ %.o: %.c $(CC) $(CFLAGS) $^ clean: -rm -f *.o *~ $(TARGET) 
$ make all gcc -c -g -Wall -O3 cat.c gcc -c -g -Wall -O3 dog.c gcc -c -g -Wall -O3 voice.c gcc -c -g -Wall -O3 main.c gcc -g -o c-iface cat.o dog.o voice.o main.o $ ./c-iface Gray cat Barz say mew ^_^ Mastiff dog Barboz say bark >. 

quasimoto ★★★★
( 21.10.10 18:30:29 MSD )
Ответ на: комментарий от quasimoto 21.10.10 18:30:29 MSD

Интересный пример, спасибо 🙂

xterro ★★★★★
( 21.10.10 18:52:38 MSD ) автор топика
Ответ на: комментарий от quasimoto 21.10.10 18:30:29 MSD

Надо делать примерно так, как предложил arsi: объект - экземпляр структуры с указателями на функции, принимающими указатель на этот объект в качестве аргумента.

Sorcerer ★★★★★
( 21.10.10 20:37:25 MSD )
Ответ на: комментарий от quasimoto 21.10.10 18:30:29 MSD

Браво. Давно не встречал такого хорошего примера, как не надо делать.

tailgunner ★★★★★
( 21.10.10 20:44:01 MSD )
Ответ на: комментарий от quasimoto 21.10.10 18:30:29 MSD

> это то что играет роль интерфейсов в си.

тот фейспалм, что вы привели, представляет собой самую неудачную реализацию классов в Си, которые я встречал. а интерфейсы — это более абстрактная сущность. в с++, например, «настоящих» интерфейсов нет. в c++/qt уже что-то похожее прорезается (но с++ удачно пихает палки в колёса qt, так что это ещё далеко не то, чего хотелось бы). более-менее юзабильные интерфейсы есть в жабе, но и там много чего не хватает «для полного счастья».

arsi ★★★★★
( 21.10.10 21:07:44 MSD )
Ответ на: комментарий от tailgunner 21.10.10 20:44:01 MSD

Давно не встречал такого хорошего примера, как не надо делать.

quasimoto ★★★★
( 21.10.10 23:23:01 MSD )
Ответ на: комментарий от arsi 21.10.10 21:07:44 MSD

тот фейспалм, что вы привели, представляет собой самую неудачную реализацию классов в Си

Велосипед не мой, но всё равно не понятно что тут страшного - ещё немного и будет tagged pointers, которые довольно часто используются. Не говоря уже о том, что если не следовать правилу включения заголовков - вообще нифига не получится написать. В общем, это другие интерфейсы (тоже, не я их так назвал).

quasimoto ★★★★
( 21.10.10 23:25:52 MSD )

На лоре куча тредов «на C сделать как в [язык более высокого уровня]». Читаю и поражаюсь что с людьми современное образование делает. Вдолбили вам ООП, но забыли сказать что это набор ОГРАНИЧЕНИЙ которые повышают(иногда) производительность труда програмиста. Эти ограничени придумали другие програмисты, до вас, и воплотили в виде всяких языков. Чтоб язык говорил error когда вы идеологию нарушаете. Все программерские техники/идеологии это договоренность програмиста с самим собой/коллективом/сообщесвом. C дает свободу. Вы работаете один? Выдумайте свою идеологию/набор ограничений и, для простоты, запихайте ее в имена функций/переменных. Конечно компилятор за вас ваши штаны поддерживать не будет когда вы свои ограничения нарушите. Ну напишите препроцессор как Qt'шники. Пока вы этим будете заниматься поймете зачем нужны интерфейсы, чтоб не говорить «хочу как там» и поймете что реализация интерфейсов, для вашего проекта не нужна и что они хороши(иногда) когда их за вас сочинили.

Плагин - вот ведь мерзкое слово. Все кругом бегают «хотим плагинов, хотим плагинов». А зачем они? Чтоб придать расширяемость закрытому коду. А что они такое? Какоето API реализованое в ста местах сотней способов. Слово плагин ничего не говорит, кроме того, что продукт будет продаваться, и покупатель будет иметь возможность продукт расширять не видя кода. Плагины не нужны(вредны) если есть исходники и документация. Судя по тому что вы пишете "(для своей небольшой системки)" .

Структуры в C надо применять только в одном случае - когда надо кудато передать кучу переменных скопом. Когда вы спрашиваете «может из структур залудить как в том ООП фреймворке» вам дают советы как. Советы правильные. Ну теперь залудите чтонибудь вроде QString/GString и быстро поймете что stdlib рулит.

На C чем проще тем лучше. Если можно избежать longjump'а, указателя на функцию, malloc'а, пойнтерной арифметики, структуры - избегать.

imatveev13 ★
( 21.10.10 23:26:24 MSD )
Ответ на: комментарий от Sorcerer 21.10.10 20:37:25 MSD

Надо делать примерно так, как предложил arsi

Ну так это вообще простая репа - подумаешь, использовать сахар для инициализации структур, что-то вроде

struct Soo < int a; int b; >; int main () < . struct Soo foo = < .a = 1, .b = 2, >; . > 

только поля - указатели на функции, обычная пачка указателей. Конечно помогает абстрагироваться от байтикоф, но дальше что? Интерфейс это несколько более общая вещь (да, если говорить о Си - то чем является интерфейс в C++ или Java - несколько параллельно).

quasimoto ★★★★
( 21.10.10 23:34:19 MSD )

Вклинюсь в разговор.

Нашёл вот такую идею по реализации ООП на чистом Си (автор использовал там оператор . но его без проблем можно заменить сишным, в данном случае это непринципиально, важна идея).

Создаём описание структур-«классов», создаём внутри них ссылки на функции, которые будут «методами». «Методы» принимают в качестве аргумента ссылку на экземпляр класса-«объекта», с которым производится действие.

Далее делаем полиморфный «метод», выполняющий разные операции в зависимости от того, ссылку на «объект» какого типа он получил. А определяет он тип объекта следующим образом: сперва приводит объект по полученной ссылке к типу int, дальше смотрит что находится в int'е, в зависимости от этого снова приводит ссылку, уже к типу «объект_1», «объект_2» или «объект_N» и выполняет соответствующее действие.

Фишка в том, что каждая структура-«класс» первым членом обязательно должна содержать int, в котором записано уникальное значение, и по которому будет происходить преобразование ссылки, которая передаётся полиморфному «методу».

Внимание, вопрос - это будет работать? Т.е. если сделать int первым членом структуры, преобразование ссылки на структуру к типу int действительно всегда будет давать ссылку на этот член?

Внимание, вопрос 2: а если сделать первым членом указатель на функцию, присваивать этому члену адреса требуемых реализаций полиморфного «метода», после чего приводить ссылку на структуру к типу «указатель на функцию» и выполнять?

typedef void (*polymorphic)(*some_object p); void method(*some_object p) < polymorphic a = (polymorphic)p; (a)(p); // calling method at specific address >

Интерфейсы в C#

У тех, кто только начинает осваивать C# часто возникает вопрос что такое интерфейс и зачем он нужен.

Сначала о том, что можно найти по первой же ссылке в поисковике. В большинстве статей смысл интерфейса разъясняется как «договор» о том, что должен содержать класс, какие свойства и методы. Например у нас есть интерфейс:

public interface IForecast < int GetForecast(int value); int Size < get; set; >> 

Соответственно, если какой-то класс реализует данный интерфейс, то он должен содержать реализацию метода int GetForecast(int value) и свойство Size.

Практическая ценность такого договора минимальная. Если выкинуть интерфейс из проекта, то все будет замечательно работать и без него. Разве что вы захотите полностью переписать логику класса и тогда интерфейс поможет вам не забыть про необходимые методы.

Подводя итог этой части, если какой-то интерфейс реализуется только в одном единственном классе, то не тратьте время на него. Он просто не нужен.

Дополнение
Сразу после публикации на меня обрушили море критики, сначала я пытался отвечать, но потом понял, что смысла нет. Все критики упирают на то, что я написал в предыдущем абзаце и пытаются приводить сложные примеры проектов где много классов, всякие ссылки туда-сюда, поддержка командой и прочее. Я наверно был не прав, что так коротко подвел итог вступления. Но суть в том, что интерфейс нужен далеко не всегда. Если у вас ваш личный (или просто не очень большой) проект, который не планируется масштабировать, на который ни кто не ссылается и, самое главное, который успешно работает, то введение в проект интерфейсов ничего не изменит. И не надо придумывать истории, что где-то кто-то однажды реализовал интерфейс и обрел бессмертие. Если бы интерфейс был нужен везде, он был бы неотъемлемой частью класса.

Это статья о том, как можно использовать интерфейс не для связки между проектами, а внутри одного проекта. По этому поводу критики пока еще не было.

Конец дополнения

К счастью возможности интерфейса намного интереснее. Интерфейс может задать общий признак для разнородных объектов, а это открывает огромные возможности по части гибкости кода.

Например, предположим у нас есть два класса:

class First
class Second

Это два совсем разных класса, но пусть у них будет что-то общее, т.е. мы хотим, чтобы один метод работал с ними обоими без дополнительной логики. В таком случае мы можем реализовать общий интерфейс, т.е. объявить эти классы как

class First : IForecast
class Second : IForecast

Теперь мы можем сделать общий метод для них:

void AnyMethod(IForecast anyClass)

Как видно, переменная value получит значение функции GetForecast от того класса, который будет передан в качестве параметра без дополнительных действий по приведению типов и т.п.

Другой пример, мы не знаем заранее какой класс нам потребуется в ходе вычислений, однако нам надо объявить экземпляр этого класса до начала работы. В такой случае можно объявить экземпляр класса, реализующего интерфейс:

. IForecast any; … А потом: if(. ) any = new First(); else any = new Second();

Можно пойти еще дальше и объявить массив (или list) таких объектов:

var array = new IForecast[2]; array[0] = new First(); array[1] = new Second();

А потом можно например вычислить сумму всех свойств Size:

var summ = 0; foreach (var forecast in array)

У объявления через интерфейс есть один недостаток: вам будут доступны только методы и свойства, объявленные в интерфейсе и самом классе. Если ваш класс унаследован от базового, то для доступа к его методам придется делать приведение типа:

public class BaseClass < public int GetValue(int value) < return value * 2; >> public class Second: BaseClass, IForecast IForecast frc; frc = new Second(); var frcBase = (BaseClass) frc; var result = frcBase.GetValue(45);

Еще один фокус с интерфейсам можно провернуть используя возможность реализовать в одном классе два интерфейса с одинаковыми по сигнатуре, но разными по содержанию методами. Я не знаю какая от этого может быть практическая польза кроме уже упомянутой, но если вдруг вам не хватает фантазии придумывать имена функций, то можете вынести их в интерфейсы.

Например добавим в наш проект еще один интерфейс:

public interface IForecast2

И создадим новый класс, наследующий оба интерфейса:

public class Third: IForecast, IForecast2 < int IForecast.GetForecast(int value) < return value + Size; >public int Size < get; set; >int IForecast2.GetForecast(int value) < return 2 * value + Size; >>

Это называется явной реализацией интерфейса. Теперь можно построить такую конструкцию:

 var third = new Third ; var v1 = ((IForecast) third).GetForecast(100); var v2 = ((IForecast2)third).GetForecast(100); Console.WriteLine(v1+v2);

Интересно, что при реализации таких интерфейсов методам не могут быть назначены модификаторы доступа (“public” или что-то еще), однако они вполне доступны как публичные через приведение типа.

Как видно, это работает, но выглядит слишком громоздко. Чтобы как-то улучшить код, можно написать несколько иначе:

var third = new Third ; var bad = (IForecast) third; var good = (IForecast2) third; var v1 = bad.GetForecast(100); var v2 = good.GetForecast(100); Console.WriteLine(v1+v2)

bad и good будут ссылаться на third, но иметь в виду разные интерфейсы. Привидение можно сделать один раз, а его результат потом использовать многократно. Возможно, что в некоторых случаях это сделает код более читаемым.

Еще одно применение интерфейса — быстрое изготовление заглушек для функций.
Например, вы в команде строите большой проект и ваш класс зависит от класса, который пишет коллега. Но еще не написал. Мудрый начальник подготовил интерфейсы всех основных классов еще на первом этапе разработки проекта и вы теперь можете не ждать коллегу, а реализовать класс-заглушку для своего класса. Т.е. временный класс не будет содержать логики, а будет только делать вид что работает: принимать вызовы и возвращать адекватные значения. Для этого надо только реализовать тот же интерфейс, что получил ваш коллега.

Работающий проект со всеми примерами из этой статьи можно посмотреть здесь: ссылка

Интерфейсы

Для начала ознакомимся с формальным определением типа интерфейса. представляет собой не более чем просто именованный набор абстрактных членов. Абстрактные методы являются чистым протоколом, поскольку не имеют никакой стандартной реализации. Конкретные члены, определяемые интерфейсом, зависят от того, какое поведение моделируется с его помощью. Это действительно так. Интерфейс выражает поведение, которое данный класс или структура может избрать для поддержки. Более того, каждый класс (или структура) может поддерживать столько интерфейсов, сколько необходимо, и, следовательно, тем самым поддерживать множество поведений.

Нетрудно догадаться, что в библиотеках базовых классов .NET поставляются сотни предопределенных типов интерфейсов, которые реализуются в различных классах и структурах. Например, в состав ADO.NET входит множество поставщиков данных, которые позволяют взаимодействовать с определенной системой управления базами данных. Это означает, что в ADO.NET на выбор доступно множество объектов соединения (SqlConnection, OracleConnection, OdbcConnection и т.д.).

В интерфейсе ни у одного из методов не должно быть тела. Это означает, что в интерфейсе вообще не предоставляется никакой реализации. В нем указывается только, что именно следует делать, но не как это делать. Как только интерфейс будет определен, он может быть реализован в любом количестве классов. Кроме того, в одном классе может быть реализовано любое количество интерфейсов.

Для реализации интерфейса в классе должны быть предоставлены тела (т.е. конкретные реализации) методов, описанных в этом интерфейсе. Каждому классу предоставляется полная свобода для определения деталей своей собственной реализации интерфейса. Следовательно, один и тот же интерфейс может быть реализован в двух классах по-разному. Тем не менее в каждом из них должен поддерживаться один и тот же набор методов данного интерфейса. А в том коде, где известен такой интерфейс, могут использоваться объекты любого из этих двух классов, поскольку интерфейс для всех этих объектов остается одинаковым. Благодаря поддержке интерфейсов в C# может быть в полной мере реализован главный принцип полиморфизма: один интерфейс — множество методов.

Интерфейсы объявляются с помощью ключевого слова interface. Ниже приведена упрощенная форма объявления интерфейса:

interface имя< возвращаемый_тип имя_метода_1 (список_параметров); возвращаемый_тип имя_метода_2 (список_параметров); // . возвращаемый_тип имя_метода_N (список_параметров); >

где имя — это конкретное имя интерфейса. В объявлении методов интерфейса используются только их возвращаемый_тип и сигнатура. Они, по существу, являются абстрактными методами. Как пояснялось выше, в интерфейсе не может быть никакой реализации. Поэтому все методы интерфейса должны быть реализованы в каждом классе, включающем в себя этот интерфейс. В самом же интерфейсе методы неявно считаются открытыми, поэтому доступ к ним не нужно указывать явно.

Помимо методов, в интерфейсах можно также указывать свойства, индексаторы и события. Интерфейсы не могут содержать члены данных. В них нельзя также определить конструкторы, деструкторы или операторные методы. Кроме того, ни один из членов интерфейса не может быть объявлен как static.

Как только интерфейс будет определен, он может быть реализован в одном или нескольких классах. Для реализации интерфейса достаточно указать его имя после имени класса, аналогично базовому классу. Ниже приведена общая форма реализации интерфейса в классе:

class имя_класса : имя_интерфейса < // тело класса >

где имя_интерфейса — это конкретное имя реализуемого интерфейса. Если уж интерфейс реализуется в классе, то это должно быть сделано полностью. В частности, реализовать интерфейс выборочно и только по частям нельзя.

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

Методы, реализующие интерфейс, должны быть объявлены как public. Дело в том, что в самом интерфейсе эти методы неявно подразумеваются как открытые, поэтому их реализация также должна быть открытой. Кроме того, возвращаемый тип и сигнатура реализуемого метода должны точно соответствовать возвращаемому типу и сигнатуре, указанным в определении интерфейса.

Давайте рассмотрим пример:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 < // Создаем два интерфейса, описывающих абстрактные методы // арифметических операций и операций Sqrt и Sqr public interface IArOperation < // Определяем набор абстрактных методов int Sum(); int Otr(); int Prz(); int Del(); >public interface ISqrSqrt < int Sqr(int x); int Sqrt(int x); >// Данный класс реализует интерфейс IArOperation class A : IArOperation < int My_x, My_y; public int x < set < My_x = value; >get < return My_x; >> public int y < set < My_y = value; >get < return My_y; >> public A() < >public A(int x, int y) < this.x = x; this.y = y; >// Реализуем методы интерфейса public virtual int Sum() < return x + y; >public int Otr() < return x - y; >public int Prz() < return x * y; >public int Del() < return x / y; >// В данном классе так же можно реализовать собственные методы public virtual void rewrite() < Console.WriteLine("Переменная x: \nПеременная y: ",x,y); > > // Данный класс унаследован от класса А, но при этом в нем не нужно // заново реализовывать интерфейс, но при этом можно переопределить // некоторые его методы class Aa : A < public int z; public Aa(int z, int x, int y) : base(x, y) < this.z = z; >// Переопределим метод Sum public override int Sum() < return base.x + base.y + z; >public override void rewrite() < base.rewrite(); Console.WriteLine("Переменная z: " + z); >> // Данный класс унаследован от класса А, и при этом // реализует интерфейс ISqrSqrt class Ab : A, ISqrSqrt < public int Sqr(int x) < return x * x; >public int Sqrt(int x) < return (int)Math.Sqrt((double)(x)); >> class Program < static void Main() < A obj1 = new A(x: 10, y: 12); Console.WriteLine("obj1: "); obj1.rewrite(); Console.WriteLine("+ = ",obj1.x,obj1.y,obj1.Sum()); Console.WriteLine(" * = ", obj1.x, obj1.y, obj1.Prz()); Aa obj2 = new Aa(z: -3, x: 10, y: 14); Console.WriteLine("\nobj2: "); obj2.rewrite(); Console.WriteLine(" + + = ", obj2.x, obj2.y, obj2.Sum(), obj2.z); Console.ReadLine(); > > > 

Реализация интерфейсов

Обратите внимание на структуру реализации данных интерфейсов в приведенном примере. Диаграмма классов, иллюстрирующая вышеуказанный пример показана ниже:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *