Повторение: указатели void
Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 49 из 61.
void* — это способ языка C сказать: «указатель на что-то, но я не скажу на что». Он может хранить адрес любого типа данных — int, float, структуры или чего-либо еще. Это делает его основой для написания обобщенного кода на C.
Подвох в том, что вы не можете разыменовать void* напрямую. Компилятор не знает, сколько байт нужно прочитать и как их интерпретировать. Чтобы использовать данные, вы должны привести их обратно к правильному типу:
void print_int(void* data) {
int* p = (int*)data;
printf("%d\n", *p);
}
int main() {
int x = 42;
print_int(&x);
return 0;
}Эта гибкость сопряжена с ответственностью. Если вы выполните приведение к неправильному типу, компилятор вас не остановит, но ваша программа выдаст мусор или завершится с ошибкой. Вот почему обобщенные функции часто используют void* в паре с индикатором типа (например, enum или флагом), чтобы функция знала, как правильно интерпретировать данные.
В следующих уроках вы будете использовать void* для создания контейнеров, которые могут хранить любые типы данных, что позволит создавать по-настоящему переиспользуемые структуры данных.
Задание
ЛегкоНапишите функцию с именем print_value, которая принимает два параметра: указатель void* на данные и флаг типа int. Функция должна интерпретировать и выводить данные на основе флага типа:
- Если флаг типа равен
0, приведите указатель к типуint*и выведите целое число. - Если флаг типа равен
1, приведите указатель к типуfloat*и выведите число с плавающей запятой с 2 знаками после запятой.
Ваша программа получит два входных значения:
- Флаг типа (
0для целого числа,1для числа с плавающей запятой) - Числовое значение
В зависимости от флага типа, сохраните значение в переменную соответствующего типа, затем вызовите print_value с указателем на эту переменную и флагом типа.
Пример вывода, когда входные данные 0 и 42:
42Пример вывода, когда входные данные 1 и 3.14:
3.14Пример вывода, когда входные данные 0 и -17:
-17Пример вывода, когда входные данные 1 и 99.5:
99.50Шпаргалка
void* — это универсальный указатель, который может хранить адрес любого типа данных. Он обеспечивает гибкость для написания обобщенного кода на языке C.
Вы не можете разыменовать void* напрямую — сначала вы должны привести его к правильному типу:
void print_int(void* data) {
int* p = (int*)data;
printf("%d\n", *p);
}
int main() {
int x = 42;
print_int(&x);
return 0;
}Обобщенные функции часто используют void* в паре с индикатором типа (например, перечислением или флагом), чтобы знать, как правильно интерпретировать данные. Приведение к неправильному типу может привести к неопределенному поведению или сбоям.
Попробуйте сами
#include <stdio.h>
// TODO: Напишите здесь вашу функцию print_value
int main() {
int type_flag;
scanf("%d", &type_flag);
if (type_flag == 0) {
int int_value;
scanf("%d", &int_value);
// TODO: Вызовите print_value с указателем на int_value и флагом типа
} else if (type_flag == 1) {
float float_value;
scanf("%f", &float_value);
// TODO: Вызовите print_value с указателем на float_value и флагом типа
}
return 0;
}В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик7Указатели на функции
Объявление указателей на функцииВызов указателей на функцииTypedef для указателей на функцииПередача функций в качестве аргументовИтоги: Диспетчеризация калькулятора10Обобщенные контейнеры
Повторение: указатели voidОбобщенная оберткаОбобщенный обмен (Swap)Обобщенное сравнениеПовторение: обобщенный массив2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга