Введение в программирование на Delphi

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

Показывать лекцию целиком

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

В любом языке программирования то и дело приходится преобразовывать один тип данных в другой. К примеру, нам требуется, чтобы пользователь ввел денежную сумму. Для этого нужно использовать вещественное число с учетом того, что могут быть копейки. Однако нет компонентов, которые позволят сразу ввести вещественное число, приходится пользоваться теми компонентами, которые позволяют ввести вещественное число в виде строки:

'123456,78'

Пользователь воспринимает это так, будто вводит сразу цифры, однако мы то знаем, что это строка только символов, изображающая число. И чтобы действительно превратить ее в число, приходится использовать преобразования типов. Некоторыми функциями, которые служат этим целям, мы уже пользовались, давайте упорядочим эти знания и изучим новые функции.

IntToStr()

Наиболее часто используемая функция. Как вы уже знаете, она принимает в качестве параметра целое число, и возвращает его в виде строки. Хотелось бы отметить еще одну особенность: эта функция предназначена для любого целого числа, не обязательно Integer. Если у вас есть переменная Byte, Word, Cardinal или Int64, все это переменные целого типа, и для любой из них годится функция IntToStr().

Примеры использования:

var
   b : Byte;
   w : Word;
   i : Integer;
begin
   b := 5;
   w := 10;
   i := 20;
   Edit1.Text := IntToStr(b);
   Edit2.Text := IntToStr(w);
   ShowMessage(IntToStr(i));
end;

StrToInt()

Эта функция напротив, принимает в качестве параметра строку, изображающую целое число, и возвращает его в виде целого числа. Все, что было сказано о типах данных в функции IntToStr(), справедливо и для этой функции. Примеры использования:

var
   b : Byte;
   w : Word;
   i : Integer;
begin
  b := StrToInt(Edit1.Text); //внимание! Если пользователь введет число 
                             //больше 255, произойдет ошибка!
  w := StrToInt('50000');
  i := StrToInt(Edit2.Text);
end;

Как видите, действия этих функций очень похожи, только в последнем случае приходится применять осторожность, чтобы не превысить диапазон возможных значений. Если вы заранее не знаете, какое число введет пользователь, лучше использовать Integer или Cardinal, если число без знака. Экономия памяти иной раз может вызвать серьезные ошибки в программе.

FloatToStr()

Эта функция принимает в качестве параметра вещественное число (то есть, число с запятой), и возвращает его в виде строки. Примеры применения почти аналогичны функции IntToStr(), однако есть одна особенность – если после запятой у Вас есть только нули, то функция вернет строку в виде целого числа:

s := FloatToStr(125,00); // результат: '125'

Эта функция также предназначена для всех типов вещественных чисел.

StrToFloat()

Эта функция принимает в качестве параметра строку в виде вещественного или целого числа, и возвращает это вещественное число. Пример:

f := StrToFloat('125'); //результат: 125,0

FormatFloat()

Эта функция по своему предназначению похожа на функцию FloatToStr(), однако она является гораздо более мощным инструментом. Эта функция не только возвращает вещественное число в качестве строки, но еще и позволяет задать желаемый формат этой строки. В прошлых примерах мы говорили, что функция FloatToStr() вместо строки '125,00' выведет '125'. А если нужно все-таки '125,00'? Такое сплошь и рядом встречается, особенно в финансовой документации. Для этих целей и создана функция FormatFloat.

Она имеет два параметра – строку формата и само число. Строка может содержать следующие форматы:

Возможные форматы FormatFloat()
Строка, указываемая в формате Примеры чисел
1234 -1234 0,5 0
0 1234 -1234 1 0
0.00 1234,00 -1234,00 0,50 0,00
#.## 1234 -1234 0,5 0
#,0.00 1 234,00 -1 234,00 0,50 0,00

Есть и другие варианты форматов, например, для представления числа в экспонентной форме, но они, как правило, не используются.

Поясним строку форматов подробней.

В первом случае показаны варианты возвращаемых строк, если строка форматов вообще не указана.

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

Знак запятая здесь указывают для удобного отображения разрядности числа. Например, миллион будет выглядеть как строка '1 000 000'. Заметим, что на само число этот формат не оказывает никакого влияния, он нужен только для того, чтобы выводить число в удобочитаемой форме в виде строки.

Примеры использования:

var
   f : Real;
begin
   f := 1234567,00;
   ShowMessage(FormatFloat('0,000.00' ,f)); //результат: "1 234 567,00"
   ShowMessage(FormatFloat(f)); //результат: "1234567"
end;

Другие преобразования

Иногда, чтобы преобразовать один тип данных в другой, достаточно указать этот тип:

var
   p : PChar;
   s : String;
begin
   p := 'Строка';
   s := String(p);
   p := PChar(s);

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

var
   f : Real;
   i : Integer;
begin
   i := 12;
   f := i; //Результат: 12,0
   i := f; //Ошибка! Так нельзя

Подстановка значений

Очень часто в языках программирования для элегантности кода подставляют одно значение вместо другого. Сравните два примера:

var
   s : String;
   i : Integer;
begin
   s := Edit1.Text;
   i := StrToInt(s);
end;

Все, что мы сделали в данном примере, так это присвоили переменной i число из Edit1, которое хранилось там в виде строки. Но для этого нам пришлось вначале занести эту строку в строковую переменную s, и уже ее обрабатывать функцией StrToInt(). Такой пример вполне будет работать, однако он слишком громоздок. Гораздо элегантнее выглядит следующий код:

var
   i : Integer;
begin
   i := StrToInt(Edit1.Text);
end;

В результате получилось меньше переменных, меньше работы для процессора, меньше занимаемой памяти и короче код! А нередко употребляют гораздо более сложные выражения, например:

i := StrToInt(Memo1.Lines[5]) + StrToInt(Edit1.Text);
s := IntToStr(StrToInt(Memo1.Lines[5]) + StrToInt(Edit1.Text));

В первой строке в целую переменную i мы вывели сумму целых чисел, которые хранились в виде строки в Edit1 и в пятой строке Memo1.

Вторая строка сложнее. Здесь мы вначале перевели эти же числа в целые числа и суммировали их, затем вновь преобразовали в строку, чтобы записать сумму этих двух чисел в строковую переменную s. Поначалу вам такой код может показаться очень сложным, но попробуйте применять такой подход в своих программах, и вы быстро к нему привыкнете, и сами заметите, что такой код более компактен и элегантен. Возьмите вторую строку. Такой же результат можно было бы добиться упрощенными выражениями:

i := StrToInt(Memo1.Lines[5]);
k := StrToInt(Edit1.Text);
m := i + k;
s := IntToStr(m);

Все это будет работать, и на первый взгляд, кажется проще. Однако, четыре строки вместо одной! И четыре переменных вместо одной! Любой программист назовет такой код безобразным и дилетантским. Так что привыкайте к хорошему и компактному коду!

Глобальная переменная DecimalSeparator

Это неявная глобальная переменная. Неявная, потому что вы ее нигде не описывали, и в коде модуля ее нет. Однако Delphi самостоятельно создает эту и некоторые другие глобальные переменные для каждого нового проекта, так что вы спокойно можете ей пользоваться.

Что это за переменная? Она хранит один символ – разделитель между целой и десятичной частью вещественного числа. И разделителем может быть либо точка, либо запятая. В русских версиях Windows чаще всего используется запятая, хотя можно перенастроить операционную систему так, что будет точка. А вот в английских ОС – точка. Если вы делаете программу только для личного использования, то смело можете устраивать проверку на ввод вещественных чисел, и в качестве разделителя указывать запятую. Но если вы делаете программу для клиента, то не можете быть уверены, какой разделитель там стоит. Ведь он может использовать вашу программу и на английской версии Windows! Тогда программа будет выдавать ошибку сразу, как дело дойдет до ввода вещественного числа.

Выход: во время проверки использовать не запятую или точку, а глобальную переменную DecimalSeparator, которая хранит нужный разделитель.

Создайте новый проект, и установите на него компонент Edit для ввода вещественного числа. Ниже поместите Memo, сюда мы будем выводить это число, отформатированное разными способами с помощью FormatFloat(). Еще ниже – кнопку для того, чтобы начать заполнять Memo. Первым делом мы с вами установим "защиту от дураков" - проверку компонента Edit, чтобы пользователь не смог ввести туда ничего, кроме целого или вещественного числа. При этом мы будем в качестве разделителя использовать переменную DecimalSeparator, и кроме того, проверим, чтобы она не встречалась более одного раза. Затем эту же проверку без изменений вы сможете применять в любых последующих ваших проектах, где потребуется необходимая проверка. Для Edit1 создаем обработчик события OnKeyPress, которое вызывается всякий раз, когда пользователь нажимает любую клавишу при вводе текста в компонент Edit. Впишем следующий код:

case Key of
    '0'..'9': ; //числа разрешаем
    //если разделителя еще нет - выводим правильный разделитель,
    //иначе ничего не выводим
    ',' , '.' :  if Pos(DecimalSeparator, Edit1.Text)= 0 then
                    Key := DecimalSeparator
               else Key := Chr(0);
    #8 : ; //backspace
    //разрешаем отрицательное число, если минус идет первым символом
    '-': if (Pos('-', Edit1.Text) = 1) or
            (Length(Edit1.Text) >0) then Key := Chr(0);
    #13 : Button1.SetFocus;
  else Key := Chr(0);
  end; //case

Этот код требует некоторых пояснений. Событие OnKeyPress компонента Edit самостоятельно создает параметр Key, это переменная типа Char, то есть, символ. В этой переменной содержится символ, введенный пользователем. Поскольку символ – перечисляемое значение (то есть, он может быть от 0 до 255), можно использовать конструкцию case. В качестве селектора указываем переменную Key, а в качестве значения указываем то значение, которое там может находиться. Строка

'0'..'9': ; //числа разрешаем

показывает, что если были введены символы от 0 до 9, то ничего не происходит (после знака ":" нет действующих операторов). Здесь мы можем извлечь новое правило: можно показывать диапазон числовых или символьных значений через знак "..", например, 'a'..'z'; 1..9. Далее идут строки:

',' , '.' :  if Pos(DecimalSeparator, Edit1.Text)= 0 then
                    Key := DecimalSeparator
               else Key := Chr(0);

Этот код дает нам новое правило: через запятую можно указать возможные значения. То есть, блок кода будет выполняться, если пользователь ввел либо запятую, либо точку. В самом блоке кода мы проверяем: нет ли уже в строке нужного разделителя? Если нет ( Pos() вернула ноль), то мы записываем в переменную Key нужный разделитель, иначе присваиваем ей нулевой символ (то есть пользователь ничего не ввел). При этом неважно, точку или запятую ввел пользователь – разделитель все равно будет правильным.

Далее, если пользователь ввел символ #8 (нажал <BackSpace>), то ничего не делаем – то есть, разрешаем этот символ. Надо же оставить ему возможность редактировать свой текст, исправлять ошибки. И снова правило: можно указать номер символа, используя функцию Chr(), либо просто после символа "#":

Key := Chr(8);
Key := #8;

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

Далее мы смотрим, не минус ли ввел пользователь? Если да, то смотрим, какова длина строки. Ведь если длина строки больше нуля, значит, в ней уже есть символы, и минус разрешать нельзя. В случае же, если строка еще не содержит ничего, то минус допустим, и блок кода if не выполнится.

Если пользователь нажал <Enter>, Key будет содержать символ #13. Обычно пользователь нажимает <Enter>, когда он закончил ввод текста. Строка

#13 : Button1.SetFocus;

передает фокус (то есть, выделение компонента) на кнопку, если нажата клавиша <Enter>.

Ну и в конце мы указываем, что в любом другом случае (пользователь набрал букву или какой либо другой недопустимый знак) переменной Key присваивается нулевой символ. Компьютер отреагирует так, будто пользователь ничего и не вводил.

В процедуре нажатия кнопки пишем такой код:

var
  f : Real;
begin
   f := StrToFloat(Edit1.Text);
  Memo1.Lines.Add(FormatFloat('', f));
  Memo1.Lines.Add(FormatFloat('0,000.00', f));
  Memo1.Lines.Add(FormatFloat('#,###.##', f));
  Memo1.Lines.Add(FormatFloat('#.##', f));
  Memo1.Lines.Add(FormatFloat('0.00', f));
  Memo1.Lines.Add('---------------'); //разделитель
end;

Тут все понятно – сначала преобразуем полученное число в виде строки, в вещественное число. Затем это число выведем в Memo, придав ему различные форматы. Сохраните пример, скомпилируйте его и посмотрите, как работает программа. В дальнейшей практике вам не раз придется ставить подобную "защиту от дураков", данный пример события OnKeyPress можно будет просто копировать в любую программу.

Вернуться к учебному плану