Можно ли использовать спецификаторы формата в качестве аргументов функции

Я пытаюсь написать функцию, которая позволяет мне инициализировать каждый элемент матрицы заданным значением. Я бы хотел, чтобы эта функция была как можно более общей, то есть она могла бы обрабатывать матрицы любого типа данных (float, char и т. Д.). Очевидно, что функции в качестве одного из аргументов потребуется значение, которым пользователь хочет инициализировать элементы матрицы. Поскольку это значение может относиться к любому типу данных, я не знаю, как это сделать. Стандартные функции, такие как printf и scanf, могут принимать аргументы любого типа благодаря использованию спецификаторов формата (% d,% f и т. Д.). Это заставило меня задуматься: как и возможно ли вообще использовать спецификаторы формата в функции, определяемой программистом? В идеале моя функция выглядела бы примерно так:

void initMatrix(void*** matrixToInit, int nRows, int nCols, const char format, void(?) value)

Чтобы после его вызова мне пришлось написать что-то вроде:

char matrixOfAs[3][3];

initMatrix(&matrixOfAs, 3, 3, "%c", 'A');

Возможно ли что-то подобное? Есть ли другие решения этой проблемы?


person Gian    schedule 14.05.2019    source источник
comment
Связанный Пример использования varargs в C   -  person mch    schedule 14.05.2019
comment
Почему бы не определить несколько функций инициализации, по одной для требуемого типа данных, и, учитывая тип элементов матрицы, вызвать подходящую.   -  person Sourav Ghosh    schedule 14.05.2019
comment
Google _Generic, который находится на C с C11. Вы также можете сделать это с помощью макроса, что проще, но опаснее. Или, возможно, самое простое решение - сделать разные функции для каждого интересующего вас типа: foo_int (), foo_flt (), foo_dbl (), ...   -  person alx    schedule 14.05.2019
comment
Просто передайте функции размер типа элемента и указатель на его начальное значение. Поскольку эта функция не зависит от семантики типа, она может полностью работать путем копирования байтов. Требуется некоторая осторожность с указателями: если данные, хранящиеся в памяти, представляют собой, скажем, int *, приведение указателя на них (int **) к void ** технически некорректно, использование нескольких уровней указателей для реализации многомерных массивов в любом случае является плохой практикой. Предпочтительнее использовать массивы массивов.   -  person Eric Postpischil    schedule 14.05.2019
comment
&matrixOfAs - это char (*)[3][3], который не похож на void *** matrixToInit.   -  person Ian Abbott    schedule 14.05.2019


Ответы (2)


Предполагая, что матрица является правильным 2-мерным массивом типа элемента (массивом массива типа элемента), а не 1-мерным массивом указателя на тип элемента, вы можете передать указатель void * на первый элемент матрицы для инициализации, размеры матрицы, размер элемента и указатель на начальное значение элемента, как показано ниже:

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

void initMatrix(void *matrixToInit, unsigned int nRows, unsigned int nCols,
        size_t elemSize, const void *elemVal)
{
    size_t offset, end;

    end = nRows * nCols * elemSize;
    for (offset = 0; offset < end; offset += elemSize) {
        memcpy((char *)matrixToInit + offset, elemVal, elemSize);
    }
}

int main(void)
{
    char matrixOfAs[3][3];
    int i, j;

    initMatrix(&matrixOfAs[0][0], 3, 3, sizeof(char), (char []){ 'A' });
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            printf(" %c", matrixOfAs[i][j]);
        }
        printf("\n");
    }
    return 0;
}

Если вам не нравится значение составного литерального массива (char []){ 'A' }, которое я передал параметру elemVal (позволяя значению массива распадаться до указателя на его первый элемент), вы можете заменить его указателем на инициализированную переменную типа char в этом случай, например определите char a = 'A'; и замените (char []){ 'A' } на &a.

person Ian Abbott    schedule 14.05.2019

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

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

Например, сделайте функцию initMatrix как:

void initMatrix(void*** matrix, int nRows, int nCols, void(*creatematrix)(void ***, int, int));

Теперь создайте другую функцию, которая обрабатывает создание int **matrix, позвольте ей вызвать void CreateIntMatrix(void ***matrix, int m, int m); и передать указатель на эту функцию в качестве аргумента функции initmatrix. Теперь, когда вы вызываете initMatrix для обработки типов данных int, просто назовите это так:

void initMatrix(&matrixOfAs, 3, 3, CreateIntMatrix);

вы также должны создать функцию, которая обрабатывает char, double и т. д.

Теперь, когда вы создаете функцию initMatrix, создайте ее так:

void initMatrix(void*** matrix, int nRows, int nCols, void(*creatematrix)(void ***, int, int)){
    /*Make something*/
    creatematrix(matrix, nRows, nCols)//initialize any type of matrix
    /*Make something*/
}
person Yoni Newman    schedule 14.05.2019