Как создать общую функцию, используя void * в c?
У меня есть функция incr
, чтобы увеличить значение на 1
Я хочу сделать его общим, потому что я не хочу создавать разные функции для одной и той же функциональности.
Предположим, что я хочу увеличивать int
, float
, char
на 1
void incr(void *vp)
{
(*vp)++;
}
Но проблема, которую я знаю, Dereferencing a void pointer is undefined behaviour
. Иногда это может привести к ошибке: Invalid use of void expression
.
My main
funciton:
int main()
{
int i=5;
float f=5.6f;
char c='a';
incr(&i);
incr(&f);
incr(&c);
return 0;
}
Проблема в том, как это решить? Есть ли способ решить его только в C
или
мне нужно определить incr()
для каждого типа данных? если да, то какое использование void *
Такая же проблема с swap()
и sort()
. Я хочу поменять местами и отсортировать все типы данных с одинаковой функцией.
Ответы
Ответ 1
Вы можете реализовать первый как макрос:
#define incr(x) (++(x))
Конечно, это может иметь неприятные побочные эффекты, если вы не будете осторожны. Это касается единственного метода C, который позволяет применять одну и ту же операцию к любому из множества типов. В частности, поскольку макрос реализован с использованием подстановки текста, к тому моменту, когда компилятор видит его, у вас есть буквальный код ++whatever;
, и он может применить ++
правильно для типа элемента, который вы предоставили. С указателем на void вы не знаете много (если вообще что-либо) о фактическом типе, поэтому вы не можете много манипулировать этими данными).
void *
обычно используется, когда соответствующая функция действительно не нуждается в точном типе данных. В некоторых случаях (например, qsort
) он использует функцию обратного вызова, чтобы избежать необходимости знать какие-либо детали данных.
Поскольку он выполняет сортировку и свопинг, давайте посмотрим на qsort немного подробнее. Его подпись:
void qsort(void *base, size_t nmemb, size_t size,
int(*cmp)(void const *, void const *));
Итак, первый - это void *
, о котором вы просили - указатель на данные, которые нужно отсортировать. Второй указывает qsort количество элементов в массиве. Третий - размер каждого элемента массива. Последнее является указателем на функцию, которая может сравнивать отдельные элементы, поэтому qsort
не нужно знать, как это сделать. Например, где-то внутри qsort будет некоторый код, например:
// if (base[j] < base[i]) ...
if (cmp((char *)base+i, (char *)base+j) == -1)
Аналогичным образом, чтобы поменять два элемента, он обычно имеет локальный массив для временного хранения. Затем он будет копировать байты от array[i]
до его темпа, затем от array[j]
до array[i]
и, наконец, от temp
до array[j]
:
char temp[size];
memcpy(temp, (char *)base+i, size); // temp = base[i]
memcpy((char *)base+i, (char *)base+j, size); // base[i] = base[j]
memcpy((char *)base+j, temp, size); // base[j] = temp
Ответ 2
Использование void *
не даст вам полиморфного поведения, которое, как я думаю, вы ищете. void *
просто позволяет вам обходить проверку типов переменных кучи. Чтобы достичь фактического полиморфного поведения, вам нужно будет передать информацию о типе в качестве другой переменной и проверить ее в своей функции incr
, затем нарисуйте указатель на нужный тип ИЛИ, передав любые операции с вашими данными в качестве указателей функций ( другие упоминали qsort
в качестве примера). C не имеет автоматического полиморфизма, встроенного в язык, поэтому вам было бы симулировать его. За кулисами языки, которые строят в полиморфизме, делают что-то вроде этого за кулисами.
Чтобы разработать, void *
- это указатель на общий блок памяти, который может быть любым: int, float, string и т.д. Длина блока памяти даже не сохраняется в указателе, пусть один тип данных. Помните, что внутренне все данные являются битами и байтами, а типы действительно являются просто маркерами для физического кодирования логических данных, поскольку по сути, биты и байты являются беспричинными. В C эта информация не хранится с переменными, поэтому вы должны предоставить ее самому компилятору, чтобы он знал, следует ли применять операции для обработки битовых последовательностей как 2 целых числа дополнений, с плавающей запятой с двойной точностью IEEE 754, символ ASCII данные, функции и т.д.; это все конкретные стандарты форматов и операций для разных типов данных. Когда вы прикладываете void *
к указателю на определенный тип, вы, как программист, утверждаете, что данные, на которые указывает, действительно, относятся к типу, на который вы его производите. В противном случае вы, вероятно, находитесь в странном поведении.
Так что же хорошего для void *
? Это полезно для обработки блоков данных без учета типа. Это необходимо для таких вещей, как выделение памяти, копирование, операции с файлами и передача указателей на функции. Однако почти во всех случаях программист С ретранслирует из этого низкоуровневого представления как можно больше, структурируя свои данные с типами, которые имеют встроенные операции; или используя структуры, причем операции над этими структурами, определенными программистом как функции.
Вы можете захотеть ознакомиться с объяснением Wikipedia для получения дополнительной информации.
Ответ 3
Вы не можете делать именно то, что вы просите - операторы, такие как приращение, должны работать с определенным типом. Итак, вы можете сделать что-то вроде этого:
enum type {
TYPE_CHAR,
TYPE_INT,
TYPE_FLOAT
};
void incr(enum type t, void *vp)
{
switch (t) {
case TYPE_CHAR:
(*(char *)vp)++;
break;
case TYPE_INT:
(*(int *)vp)++;
break;
case TYPE_FLOAT:
(*(float *)vp)++;
break;
}
}
Тогда вы назовете это так:
int i=5;
float f=5.6f;
char c='a';
incr(TYPE_INT, &i);
incr(TYPE_FLOAT, &f);
incr(TYPE_CHAR, &c);
Конечно, это не дает вам ничего, кроме определения отдельных функций incr_int()
, incr_float()
и incr_char()
- это не цель void *
.
Цель void *
реализуется, когда алгоритм, который вы пишете, не заботится о реальном типе объектов. Хорошим примером является стандартная функция сортировки qsort()
, которая объявляется как:
void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));
Это можно использовать для сортировки массивов любого типа объекта - вызывающему абоненту просто нужно предоставить функцию сравнения, которая может сравнивать два объекта.
Обе функции swap()
и sort()
попадают в эту категорию. swap()
еще проще - алгоритм не должен знать ничего, кроме размера объектов для их замены:
void swap(void *a, void *b, size_t size)
{
unsigned char *ap = a;
unsigned char *bp = b;
size_t i;
for (i = 0; i < size; i++) {
unsigned char tmp = ap[i];
ap[i] = bp[i];
bp[i] = tmp;
}
}
Теперь, учитывая любой массив, вы можете поменять местами два элемента в этом массиве:
int ai[];
double ad[];
swap(&ai[x], &ai[y], sizeof(int));
swap(&di[x], &di[y], sizeof(double));
Ответ 4
Пример использования "общего" свопа.
Этот код заменяет два блока памяти.
void memswap_arr(void* p1, void* p2, size_t size)
{
size_t i;
char* pc1= (char*)p1;
char* pc2= (char*)p2;
char ch;
for (i= 0; i<size; ++i) {
ch= pc1[i];
pc1[i]= pc2[i];
pc2[i]= ch;
}
}
И вы называете это следующим образом:
int main() {
int i1,i2;
double d1,d2;
i1= 10; i2= 20;
d1= 1.12; d2= 2.23;
memswap_arr(&i1,&i2,sizeof(int)); //I use memswap_arr to swap two integers
printf("i1==%d i2==%d \n",i1,i2); //I use the SAME function to swap two doubles
memswap_arr(&d1,&d2,sizeof(double));
printf("d1==%f d2==%f \n",d1,d2);
return 0;
}
Я думаю, что это должно дать вам представление о том, как использовать одну функцию для разных типов данных.
Ответ 5
Прежде чем разыгрывать его, вы должны направить указатель на конкретный тип. Поэтому вы также должны добавить код для передачи того, что является типом переменной указателя.
Ответ 6
Извините, если это может произойти как не ответе на широкий вопрос " Как сделать общую функцию с помощью void * в c?".. но проблемы, которые у вас есть (увеличение переменная произвольного типа и замена 2 переменных неизвестных типов) может быть намного проще с макросами, чем с функциями и указателями на void.
Приращение достаточно просто:
#define increment(x) ((x)++)
Для замены я сделал бы что-то вроде этого:
#define swap(x, y) \
({ \
typeof(x) tmp = (x); \
(x) = (y); \
(y) = tmp; \
})
..., который работает для ints, double и char указателей (строк), основанных на моем тестировании.
В то время как инкрементный макрос должен быть довольно безопасным, макрос подкачки полагается на оператор typeof()
, который является расширением GCC/clang, НЕ является частью стандартного C (если вы только когда-либо компилируете gcc или clang, это не должно быть слишком большой проблемой).
Я знаю, что уклонился от первоначального вопроса; но, надеюсь, он по-прежнему решает ваши первоначальные проблемы.
Ответ 7
Вы можете использовать типовые средства (стандарт C11). Если вы намереваетесь использовать более продвинутые математические функции (более продвинутые, чем оператор ++
), вы можете перейти к <tgmath.h>
, который является типичным определением функций в <math.h>
и <complex.h>
.
Вы также можете использовать ключевое слово _Generic
для определения универсальной функции типа как макроса. Ниже пример:
#include <stdio.h>
#define add1(x) _Generic((x), int: ++(x), float: ++(x), char: ++(x), default: ++(x))
int main(){
int i = 0;
float f = 0;
char c = 0;
add1(i);
add1(f);
add1(c);
printf("i = %d\tf = %g\tc = %d", i, f, c);
}
Вы можете найти дополнительную информацию о языке стандарта и более сложных примерах в этом сообщении из Rob blog программирования.
Что касается вопросов * void
, вопросов подкачки и сортировки, лучше обратитесь к Jerry Coffin.