02:29 

Учимся программировать на С++. Лекция 15. Неопределенный указатель

ToxicSpider
Бритуля - Богиня
Здрасте))))

Условие:

Напишите программу, которая получает от пользователя 2 параметра:
1. стартовое число
2. количество чисел

Программа сохранит ряд чисел, начиная со стартового, а если количество чисел равно нулю, то программа напишет "Нечего делать" и выйдет. Предположите, что пользователь ввел правильное число, то есть не нужно проверять, не выходит ли число за рамки, установленные размером типа int

Пример:

1. Пользователь ввел:
стартовое число == 12
количество чисел == 3

Программа сохранит такие числа: 12, 13, 14

2. Пользователь ввел:
стартовое число == -2
количество чисел == 5

Программа сохранит такие числа: -2, -1, 0, 1, 2

В конце можно распечатать сохраненные числа, чтобы убедиться, что все сделано правильно.

Удачи!


Код:








Результат:






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



Преобразование типов


Для начала, давайте вспомним такой факт: на прошлом домашнем задании я выполнил такое, пока еще не совсем понятное действие: я отнял char от char, а результат положил в int. Я сказал, что это можно делать потому, что int хранится в большем количестве байт, чем char, значит нет потери данных, и поэтому язык позволяет это сделать.

Что же произошло, когда я отнял char от char? Создался новый char и программа сохранила его в переменной из типа int, заполнив "лишнее" место нулями. То есть произошло преобразование одного типа в другой.

Преобразование (англ. "casting") это возможность "переделать" один тип в другой. Так как все, в конечном итоге, кусок памяти, заполненный единицами и нулями, а все зависит лишь от нашего взгляда на этот кусок, то такое теоретически возможно

Хочу еще немного объяснить это. Если мы заглянем в таблицу ASCII, то мы увидим, что буква "а" это 97. То есть, в определенном куске памяти, в бинарном виде написано "97". Стоит вопрос: как смотреть на это число? Как на число или как на букву "а"? Программист, выполняя преобразование "говорит" программе каков правильный взгляд на этот, конкретный кусок памяти.

НО! во время операции в домашнем задании, когда я отнял char от char, программа сделала преобразование сама, вместо меня, поняв мои намерения потому, что я положил результат в int. Такой тип преобразования называется "неявный".

Ну, раз существует "неявный тип", то должен быть и "явный", не правда ли? И он существует! На самом деле, в С++ есть несколько способов выполнять преобразование, но я всегда пользуюсь только "простым" и на это есть причины: все остальные способы более "защищенные", то есть, программа не выполняет тупо то, что говорит ей программист, а проверяет "можно так делать или нельзя".

Почему же я не пользуюсь этим? Ответ прост: я не выполняю преобразование каждые две строки кода. Я стараюсь делать это редко и тогда, когда это ОЧЕНЬ оправдано. То есть - без этого нельзя, НО! Если уж я делаю преобразование, то я на миллион процентов уверен в том, что я делаю, поэтому дополнительные проверки со стороны программы мне не нужны. То есть, выполняя такой тип преобразования, программер как бы говорит: "Сядь и не переживай! Это - было float, а теперь int. Потому что я так хочу! Не спорь, а выполняй, тупая железяка!"

Допустим, у нас есть такой код:

int num = 97; // ascii код буквы "а"
char ch = 0;

ch = (char) num; // я "вкладываю" в int в char, выполнив "явное" преобразование

cout << ch; // на экран распечатается буква "а", а не 97.

Точно так же, можно заменить 3-ю строку таким кодом:

ch = char(num); // это считается более С++ формой преобразования

Но, в итоге - оба варианта делают одно и тоже. Лично я предпочитаю первый, просто по-привычке.

Примечание: преобразование типов - потенциально опасное место в коде, поэтому, повторюсь еще раз: оно производится только при одном условии: программист УВЕРЕН в своих действиях!

Для интереса запустите такой код:

char ch = 'a';

cout << ch << endl;
cout << (int) ch << endl;
cout << (float) ch << endl;


программа распечатает один раз "а", второй раз "97", в третий раз "97.0".


И напоследок, еще одно напоминание. Как мы помним, одно из первых домашних заданий выглядело так:

"Напишите программу, которая получает от пользователя 2 положительных числа и высчитывает среднее арифметическое между ними." (Лекция 5. Типы примитивных переменных)

Что я сделал? Я объявил переменные как float, сказав, что сделал это потому, что результат может быть дробным. Но я так же сказал, что не обязательно объявлять так:

float first_num = 0;
float second_num = 0;
float average = 0;


Кроме переменной average, можно объявить еще только одну переменную, как float, допустим переменную first_num, то есть:

float first_num = 0;
int second_num = 0;
float average = 0;


тогда код: average = (first_num + second_num) / 2; ТОЖЕ отработал бы правильно! Почему? Потому что программа, совершая действие над разными типами int и float, совершает неявное преобразование в пользу бОльшего типа, то есть в этой ситуации в float.

Однако, можно было бы поступить и так:

int first_num = 0;
int second_num = 0;
float average = 0;


То есть, изначально объявить все переменные как int (кроме average) и тогда, строка арифметического действия, ниже, выглядела бы так:

average = (float) ((first_num + second_num) / 2); ИЛИ так: average = float ((first_num + second_num) / 2);

То есть, выполнив явное преобразование.

Вы вполне можете проверить мою правоту, запустив код с теми изменениями, которые я предложил.



Неопределенный указатель или void *


Для начала, теория, чтобы понять о чем мы говорим. Думаю, что лучше всего я продемонстрирую это так:

int num = 0;
int * p_num = new int;
char ch = 'c';
char * str = "hello";
float * p_f = new float;

void * universal_pointer = NULL;

universal_pointer = & num;
universal_pointer = p_num;
universal_pointer = & ch;
universal_pointer = str;
universal_pointer = p_f;


Насколько видно из кода, можно объявить указатель на void и он обладает интересным качеством: ему можно присвоить адрес любой переменной! Однако, такой тип указателя обладает и минусом, а именно: им можно пользоваться только для того, чтобы хранить что-то, то есть код:

int a = 5;
int b = 7;
int c = 0;
void * p = &a;

c = (*p) + b;
// так писать нельзя!

но можно так:
int * p_num = (int *) p; // преобразование в указатель на int
c = (*p_num) + b; // а вот так - уже можно.

То есть, как мы видим, что указатель на void используется только в качестве хранилища. Для того, чтобы совершить с данными какое-либо действие, нужно преобразовать указатель на void в указатель конкретного типа.

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

Такой способ применяется для создания "контейнеров", которые хранят данные. Мы можем с помощью такого указателя создать контейнер, в который можно положить абсолютно любой тип переменной и передавать или хранить его, пока он не понадобится. А в функции, в которой нужно использовать эти данные - уже написать правильное преобразование и нужную обработку.

Все это немного "висит в воздухе", но дайте время))))

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

@темы: C++

URL
   

Godney

главная