Возможно ли создать одну функцию вместо двух функций с одинаковой целью, но с разными типами аргументов? (Можно ли удалить повторяющийся код?)

Можно ли создать одну функцию вместо двух функций с той же целью, но с разными типами аргументов?

У меня есть две функции, написанные на C, они конвертируют изображение из RGB в HSV:

void png_rgb2hsv(pPNG_DATA data);
void jpg_rgb2hsv(pJPEG_DATA data);

они выполняют точно такую ​​же вещь: они берут data- > row_pointers и циклируют его в течение данных цикла → height. Затем он преобразует значения, указанные данными → row_pointers. Вот как это работает. Но единственное отличие состоит в том, что структуры данных используют разные типы. Мне кажется бессмысленным использовать две функции для одной и той же вещи. Особенно, когда я добавляю больше функций для большего количества цветовых пространств.

Как эта проблема проектирования программ на практике решена на C?

Update: Большинство читателей не поняли вопроса. Я не спрашивал о перегрузке. Это был вопрос о дизайне. Я спрашиваю, можно ли удалить избыточную функцию, "избыточный" код. Поскольку обе функции используют один и тот же код, делайте то же самое, но типы в аргументе функции различны, потому что один тип из libjpeg, а второй тип - из libpng. Я обнаружил, что это невозможно, потому что это означает использование одной переменной для двух разных типов.

Ответ 1

Это невозможно в C, но во многих других языках программирования, таких как С++, это возможно.

Тем не менее, есть некоторые трюки вокруг этого ограничения в C, см. этот вопрос для получения дополнительной информации.

Одной из распространенных практик в C является размещение типа в имени функции, как в вашем примере.

Ответ 2

Использовать общий макрос:

void png_rgb2hsv(pPNG_DATA data);
void jpg_rgb2hsv(pJPEG_DATA data);

#define rgb2hsv(X) _Generic((X), pPNG_DATA: png_rgb2hsv, pJPEG_DATA: jpg_rgb2hsv)(X)

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

Обновление:

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

void rgb2hsv_(Something *row_pointers, int height) // any member that you need goes here
{
    /* your code */
}

#define rgb2hsv(obj) \
do { \
_Generic((obj), pPNG_DATA: pPNG_DATA, pJPEG_DATA: pJPEG_DATA) tmp = (obj); \
    rgb2hsv_(tmp->row_pointers, tmp->height /*again, every member that you need should be stated here*/) \
} while (0)




Кроме того, если pJPEG_DATA и pPNG_DATA имеют абсолютно одинаковый внутренний макет (это означает, что их члены имеют один и тот же тип и указаны в одном порядке), вы можете попробовать следующее: (это не так безопасно как предыдущий, но по крайней мере он не похож на плохой хак)

void rgb2hsv(void *ptr)
{
    pPNG_DATA data = (pPNG_DATA *) ptr; // it does not matter which of 2 types you use here
    /* put your code that uses `data` here */
}

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

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

Ответ 3

Указатели Void совместимы с указателем на любой объект. Вы можете использовать void* и добавить параметр "type".

void data2hsv(void *data, int datatype) {
    if (datatype == 0) {
        /* use png */
    } else {
        /* use jpg */
    }
}

Изменить: ложь компилятору

void data2hsv(void *data) {
    pPNG_DATA source = data; // if data is of pJPG_DATA type 
                             // compiler will not catch the error
    /* use source as if it was pPNG_DATA */
}

Ответ 4

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

struct img{
    char *filetype;
    void *data;
};

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

Или вы создаете структуру с указателями функций на все функции, которые вы хотите использовать, например:

struct imgfuns{
    void (*rgb2hsv)(void *);
};

struct imgfuns *init_struct(void)
{
    struct imgfuns *funs = malloc(sizeof(*funs));
    if(funs == NULL)
        return NULL;
    if(png...)
        funs->rgb2hsv = png_rgb2hsv;
    else if(jpg...)
        funs->rgb2hsv = jpg_rgb2hsv;
    return funs;
}

int main(int argc, char *argv[])
{
    struct imgfuns *funs;
    funs = init_struct();
    if(funs == NULL)
      exit(1);
    /* get data from somewhere */
    funs->rgb2hsv(data);
    free(funs);
    return 0;
}

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

Дополнительную информацию можно найти здесь: Как работают указатели функций в C?

Ответ 5

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

void rgb2hsv(void *data, int data_type)
{
    char *row, *pix;
    switch (data_type) {
        case JPG: row= (pJPEG_DATA data)->row; break;
        case PNG: row= (pPNG_DATA  data)->row; break;
    }
    ....

Ответ 6

Вы не можете перегружать функции на C, но вы можете организовать свой код, чтобы сделать его оптимальным. Я бы создал функцию для rgb2hsv и получил бы тип параметра void *, определял бы тип и использовал бы его, обрабатывая все возможные случаи. И вы должны использовать только эту функцию. На более низких уровнях вы все еще дублируете свою функцию, на более высоком уровне вам не придется вызывать две отдельные функции. Это упростило бы использование.