Теперь в программе на языке C можно косвенно вызвать функцию-член f()
.
/* вызов функции на языке C++ из функции на языке C: */
int call_f(S* p, int i);
struct S* make_S(int,const char*);
void my_c_fct(int i)
{
/* ... */
struct S* p = make_S(x, "foo");
int x = call_f(p,i);
/* ... */
}
Для того чтобы эта конструкция работала, больше о языке С++ упоминать не обязательно.
Выгоды такого взаимодействия очевидны: код можно писать на смеси языков C и C++. В частности, программы на языке C++ могут использовать библиотеки, написанные на языке C, а программы на языке C могут использовать библиотеки, написанные на языке С++. Более того, большинство языков (особенно Fortran) имеют интерфейс вызова функций, написанных на языке С, и допускают вызов своих функций в программах, написанных на языке С.
В приведенных выше примерах мы предполагали, что программы, написанные на языках C и C++, совместно используют объект, на который ссылается указатель p
. Это условие выполняется для большинства объектов. В частности, допустим, что у нас есть следующий класс:
// В языке C++:
class complex {
double re, im;
public:
// все обычные операции
};
Тогда можете не передавать указатель на объект в программу, написанную на языке С, и наоборот. Можете даже получить доступ к членам re
и im
в программе, написанной на языке C, с помощью объявления
/* В языке C: */
struct complex {
double re, im;
/* никаких операций */
};
call_f()
: функция f()
может быть virtual
. Следовательно, этот пример иллюстрирует вызов виртуальной функции из программы, написанной на языке C.
Кроме встроенных типов, простейшим и наиболее безопасным способом совместного использования типов является конструкция struct
, определенная в общем заголовочном файле языков C и C++. Однако эта стратегия серьезно ограничивает возможности использования языка С++, поэтому мы ее не рекомендуем.
27.2.5. Указатели на функции
Что можно сделать на языке С, если мы хотим использовать объектно-ориентированную технологию (см. разделы 14.2–14.4)? По существу, нам нужна какая-то альтернатива виртуальным функциям. Большинству людей в голову в первую очередь приходит мысль использовать структуру с “полем типа” (“type field”), описывающим, какой вид фигуры представляет данный объект. Рассмотрим пример.
struct Shape1 {
enum Kind { circle, rectangle } kind;
/* ... */
};
void draw(struct Shape1* p)
{
switch (p–>kind) {
case circle:
/* рисуем окружность */
break;
case rectangle:
/* рисуем прямоугольник */
break;
}
}
int f(struct Shape1* pp)
{
draw(pp);
/* ... */
}
Этот прием срабатывает. Однако есть две загвоздки.
• Для каждой псевдовиртуальной функции (такой как функция draw()
) мы должны написать новую инструкцию switch
.
• Каждый раз, когда мы добавляем новую фигуру, мы должны модифицировать каждую псевдовиртуальную функцию (такую как функция draw()
), добавляя новый раздел case в инструкцию switch
.
Вторая проблема носит довольно неприятный характер, поскольку мы не можем включить псевдовиртуальные функции ни в какие библиотеки, так как наши пользователи должны будут довольно часто модифицировать эти функции. Наиболее эффективной альтернативой является использование указателей на функции.
typedef void (*Pfct0)(struct Shape2*);
typedef void (*Pfct1int)(struct Shape2*,int);
struct Shape2 {
Pfct0 draw;
Pfct1int rotate;
/* ... */
};
void draw(struct Shape2* p)
{
(p–>draw)(p);
}
void rotate(struct Shape2* p, int d)
{
(p–>rotate)(p,d);
}
Структуру Shape2
можно использовать точно так же, как структуру Shape1
.
int f(struct Shape2* pp)
{
draw(pp);
/* ... */
}