Указатели типа void*
В си существует особый тип указателей – указатели типа void или пустые указатели. Эти указатели используются в том случае, когда тип переменной не известен. Так как void не имеет типа, то к нему не применима операция разадресации (взятие содержимого) и адресная арифметика, так как неизвестно представление данных. Тем не менее, если мы работаем с указателем типа void, то нам доступны операции сравнения.
Если необходимо работать с пустым указателем, то сначала нужно явно привести тип. Например
#include #include int main()
Переменная не может иметь типа void, этот тип определён только для указателей. Пустые указатели нашли широкое применение при вызове функций. Можно написать функцию общего назначения, которая будет работать с любым типом. Например, напишем функцию swap, которая обменивает местами содержимое двух переменных. У этой функции будет три аргумента – указатели на переменные, которые необходимо обменять местами и их размер.
#include #include #include #include void swap(void *a, void *b, size_t size) < char* tmp; //создаём временную переменную для обмена tmp = (char*) malloc(size); memcpy(tmp, a, size); memcpy(a, b, size); memcpy(b, tmp, size); free(tmp); >int main()
Наша функция может выглядеть и по-другому. Обойдёмся без дорогостоящего выделения памяти и будем копировать побайтно.
void swap(void *a, void *b, size_t size) < char tmp; size_t i; for (i = 0; i < size; i++) < tmp = *((char*) b + i); *((char*) b + i) = *((char*) a + i); *((char*) a + i) = tmp; >>
Пустые указатели позволяют создавать функции, которые возвращают и принимают одинаковые параметры, но имеют разное название. Это пригодится в дальнейшем, при изучении указателей на функции. Например
int cmpInt(void* a, void* b) < return *((int*) a) - *((int*) b); >int cmpString(void *a, void* b) < return strcmp((char*) a, (char*) b); >int cmpFloat(void* a, void* b) < float fdiff = *((float*) a) - *((float*) b); if (fabs(fdiff) < 0.000001f) < return 0; >if (fdiff < 0) < return -1; >else < return 1; >>
ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students
Всё ещё не понятно? – пиши вопросы на ящик
Тип void*
В языках C и C++ есть специальный тип данных void * . Это — «универсальный» указатель, который просто соответствует указателю на какой-либо адрес в памяти компьютера, безотносительно типа данных, на которые указывает этот указатель. То есть указатель типа void * нельзя разыменовать, так как разыменование — это доступ к переменной, которая хранится по адресу данного указателя.
Указатель типа void * используется для передачи параметром в функции и для возврата функциями значений, если функции работают с областями памяти, просто как с двоичными данными.
Примеры таких функций
Заголовочный файл
void *memset(void *s, int c, size_t n) — заполнить область памяти, на которую указывает s одинаковым значением байта c. Заполняется n байт памяти.
void *memcpy(void *dest, const void *src, size_t n) — скопировать n байт памяти из области памяти src в область памяти dst. Копируемые данные и область назначения данных не должны пересекаться.
void *memcpy(void *dest, const void *src, size_t n) — аналогично memcpy, но допускает пересечение областей, в этом случае для копирования используются вспомогательная память. Эта функция работает медленнее, чем memcpy.
Заголовочный файл
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) — из файла steam считываются данные блоками размером size, количество считываемых блоков равно nmemb. Данные записываются в область памяти, на которую указывает ptr. Функция возвращает количество успешно считанных блоков (то есть если все блоки были считаны успешно, то функция возвращает nmemb).
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) — записывает двоичные данные в файл. Параметры и возвращаемое значение аналогичны функции fread.
Заголовочный файл
Функции динамического распределения памяти malloc и free, а также calloc и realloc также работают с указателями типа void * .
Не могу понять, что такое VOID в С++?
> или неопределенное значение
все-таки я бы говорил о пустом значении или даже об отсутствии значения. Неопредленное это что-то вроде variant или any. А void это такой странный кейворд, что его можно даже писать ВМЕСТО параметров функции, когда их у нее нет: void f(void). Так что правильно вы сказали, «больше себе голову этим забивать не стоит» ))
Станислав Макаров: все же неопределенное значение ей тоже присуще. Например, когда нужно хранить в контейнере vector значения разных типов (int, float, char и др.): std::vector
Как их различать и рационально ли это — уже второстепенный вопрос =)
Добавлю, что это не конечный пример использования: можно также использовать для универсальной функции суммы двух значений, избежав при этом макросы и перегрузки (возможно, тоже не так универсально). Так что всё зависит от извращенности фантазии, в общем =)
Заблокировали просто так!
void функция1() cout <<функция1; //ничего не напечатает int функция2() cout
Ответ написан более трёх лет назад
Комментировать
Нравится 1 Комментировать
Ответы на вопрос 5
Максим Гречушников @maxyc_webber
Web-программист
вот видите бублик? вы выполняете функцию и сьедаете его. что у вас остается от бублика? true? false? null? void!
Ответ написан более трёх лет назад
Нравится 7 5 комментариев
Максим Гречушников @maxyc_webber
Old Odessa @vs_convoy Автор вопроса
то есть только один раз возвращается значение?
Old Odessa @vs_convoy Автор вопроса
Максим Гречушников @maxyc_webber
Влад Конвой: он возвращает ничего. темную материю
Old Odessa @vs_convoy Автор вопроса
Максим Гречушников: хороший пример.
Евгений Иванович @makrushin-evgenii
void = процедура
Ответ написан более трёх лет назад
Комментировать
Нравится 2 Комментировать
C++ developer
void указывают в качестве типа возвращаемого значения функции, если она ничего не возвращает. Например функция int get_speed() которая возвращает скорость юнита как целое число, определяется с типом int. А вот void set_speed(int speed) ничего не возвращает, а наоборот задает скорость. Поэтому используем void.
Ответ написан более трёх лет назад
Комментировать
Нравится 1 Комментировать
Дмитрий @EvilsInterrupt
System programming, Reversing Engineering, C++
Влад Конвой Все просто. Из курса Computer science. Рекомендую почитать SICP.
В математике На вопрос "Как это выглядит?" отвечает "функция". А на вопрос "Как же это получить?" отвечает "процедура".
В С-подобных языках именованные вычисления оформляются в виде функций. Но функции в С-подобных языках отвечают на оба вопроса и нет ни одного способа разграничить. Функция в С-подобных всегда должна что-то вернуть! Какой-то результат! Какой-то новый объект. Но что-то новая вернуть по-любому должна!
Теперь, а как же быть если хочется хоть как-то разграничить на функции и процедуры? Вот для этого есть "костыль" в виде 'void'! Другими словами это способ сказать читающему код: "мы ничего не возвращаем, мы только создаем". Это и есть "как это получить?" в таких функциях только производство чего-то нового. Нарисовать окно, зашифровать диск, послать другу . А в в функциях возвращающих результат можно уже написать в терминах математики, то есть как что выглядит, но ни как что получается ;)
Урок №92. Указатели типа void
Указатель типа void (или «общий указатель») — это специальный тип указателя, который может указывать на объекты любого типа данных! Объявляется он как обычный указатель, только вместо типа данных используется ключевое слово void:
void * ptr ; // ptr - это указатель типа void
Указатель типа void может указывать на объекты любого типа данных:
int nResult ;
float fResult ;
struct Something
Something sResult ;
ptr = & nResult ; // допустимо
ptr = & fResult ; // допустимо
ptr = & sResult ; // допустимо
Однако, поскольку указатель типа void сам не знает, на объект какого типа он будет указывать, разыменовать его напрямую не получится! Вам сначала нужно будет явно преобразовать указатель типа void с помощью оператора static_cast в другой тип данных, а затем уже его разыменовать:
int value = 7 ;
void * voidPtr = & value ;
//std::cout << *voidPtr << std::endl; // запрещено: нельзя разыменовать указатель типа void
int * intPtr = static_cast < int * >( voidPtr ) ; // однако, если мы конвертируем наш указатель типа void в указатель типа int,
std :: cout
Результат выполнения программы:
Возникает вопрос: «Если указатель типа void сам не знает, на что он указывает, то как мы тогда можем знать, в какой тип данных его следует явно конвертировать с помощью оператора static_cast?». Никак, это уже на ваше усмотрение, вам самим придется выбрать нужный тип. Например:
void printValue ( void * ptr , Type type )
switch ( type )
std :: cout << * static_cast < int * >( ptr ) << '\n' ; // конвертируем в указатель типа int и выполняем разыменование<>
case DOUBLE :
std :: cout << * static_cast < double * >( ptr ) << '\n' ; // конвертируем в указатель типа double и выполняем разыменование<>
case CSTRING :
std :: cout << static_cast < char * >( ptr ) << '\n' ; // конвертируем в указатель типа char (без разыменования)<>
// std::cout знает, что char* следует обрабатывать как строку C-style.
// Если бы мы разыменовали результат (целое выражение), то тогда бы вывелся просто первый символ из массива букв, на который указывает ptr
int nValue = 7 ;
double dValue = 9.3 ;
char szValue [ ] = "Jackie" ;
printValue ( & nValue , INT ) ;
printValue ( & dValue , DOUBLE ) ;
printValue ( szValue , CSTRING ) ;
Результат выполнения программы:
Указателям типа void можно присвоить нулевое значение:
void * ptr = 0 ; // ptr - это указатель типа void, который сейчас является нулевым
Хотя некоторые компиляторы позволяют удалять указатели типа void, которые указывают на динамически выделенную память, делать это не рекомендуется, так как результаты могут быть неожиданными.
Также не получится выполнить адресную арифметику с указателями типа void, так как для этого требуется, чтобы указатель знал размер объекта, на который он указывает (для выполнения корректного инкремента/декремента). Также нет такого понятия, как ссылка на void.
Заключение
В общем, использовать указатели типа void рекомендуется только в самых крайних случаях, когда без этого не обойтись, так как с их использованием проверку типов данных ни вам, ни компилятору выполнить не удастся. А это, в свою очередь, позволит вам случайно сделать то, что не имеет смысла, и компилятор на это жаловаться не будет. Например:
int nResult = 7 ;
printResult ( & nResult , CSTRING ) ;
Здесь компилятор промолчит. Но что будет в результате? Непонятно!
Хотя код, приведенный выше, кажется аккуратным способом заставить одну функцию обрабатывать несколько типов данных, в языке C++ есть гораздо лучший способ сделать то же самое (через перегрузку функций), в котором сохраняется проверка типов для предотвращения неправильного использования. Также для обработки нескольких типов данных можно использовать шаблоны, которые обеспечивают хорошую проверку типов (но об этом уже на следующих уроках).
Если вам все же придется использовать указатель типа void, то убедитесь, что нет лучшего (более безопасного) способа сделать то же самое, но с использованием других механизмов языка C++!
Тест
В чём разница между нулевым указателем и указателем типа void?
Ответ
Указатель типа void — это указатель, который может указывать на объект любого типа данных, но он сам не знает, какой это будет тип. Для разыменования указатель типа void должен быть явно преобразован с помощью оператора static_cast в другой тип данных. Нулевой указатель — это указатель, который не указывает на адрес. Указатель типа void может быть нулевым указателем.