Перевести страницу

Создание приложений для науки и производства ещё не было таким простым

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

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

    int a; a = 123;

    stdout a;

то в стандартный выходной поток будет выведено число 123. Хотя, на самом деле, операция stdout принимает своими параметрами только строки. Преобразование произойдет скрытно и автоматически. Если бы такие преобразования не поддерживались, то пришлось бы записать что-то вроде:

    int a; a = 123;

    stdout (inttostr a);


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

  • op1t1 p1t1 p2t2 (op2t2)

где op1t1 ожидает параметрами данные типа t1. Переменные p1t1 и p2t2 разных типов, а op2t2 возвращает значение типа t2. Но при этом, если компилятору известен способ преобразования типа t2 в t1, тогда выполнение будет происходить по следующей схеме:

  • сначала вызов операции op2t2;

  • возвращенное ей значение присваивается невидимой временной переменной tmp1t2;

  • значение переменной p2t2 преобразуется и записывается в невидимую временную переменную tmp2t1;

  • значение переменной tmp1t2 преобразуется и записывается в невидимую временную переменную tmp3t1;

  • вызывается операция op1t1 p1t1 tmp2t1 tmp3t1.

Выглядит сложным, но при использовании все оказывается просто. В других языках программирования, где есть разные типы данных и не поддерживается скрытое преобразование, пришлось бы для этого вызывать специальные операции, а при необходимости изменения типа переменной перекраивать весь исходный текст. В Ci все указанные преобразования происходят незаметно для прикладного программиста и почти не требуют его внимания (за исключением ряда случаев, далее описанных в этом документе). Единственное, о чем надо знать и помнить прикладному программисту – это о возможности преобразования одних типов данных в другие. То есть, о возможности указывать вместо данных одного типа данные другого, и, в небольшом числе случаев, знать, как эти данные преобразуются. Для базовых типов данных здесь приводится сводная таблица 1, показывающая возможность таких преобразований:

Таблица 1

тип из тип в

bool

int

uint

float

str

block

bool


+

+

+

-

-

int

+


+

+

+

-

uint

+

+


+

+

-

float

+

+

+


+

-

str

+

+

+

+


-

block

-

-

-

-

-



+ преобразование определено;

- преобразование не определено;

пусто там, где преобразование не требуется.


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

    float a 12.31;

    int b 101;

    bool c TRUE;

    stdout "значение a=" a ", значение b=" b ", значение c=" c "\n";

в результате будет выведено:

    значение a=1.231000E1, значение b=101, значение c=TRUE


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


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

    int i 200;

    puts (float i + 1);

будет выполнено необычным образом: сначала произойдет суммирование 200 и 1, что даст целое число 201, потом оно будет преобразовано в вещественное число, и оно, в свою очередь, будет преобразовано в строку, которая будет напечатана, в результате мы увидим, вместо 201, число 2.01E2. Для чего это нужно? Дело в том, что в ряде случаев компилятор не знает, какого типа данное он должен использовать. Чаще всего это возникает в ситуациях, когда надо вернуть результат арифметического выражения в качестве параметра функции при помощи операции par@. Например, если записать:

    par@ 0 (12 + 20);

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

    par@ 0 (int 12 + 20)

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


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

    float f 1234.5678;

    puts (int f + 1);

напечатает:

    1235

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


При освоении программирования на Ci первое время потребуется следить за тем, корректно ли смешиваются в одном выражении различные типы. Результат может отличаться от ожидаемого, если при преобразовании происходит потеря значения – например, если в цепочке преобразований чисел ошибочно указана логическая переменная, то после ее использования числа будут принимать значения только 0 или 1. Могут также возникнуть проблемы при смешивании в одном выражении знаковых и беззнаковых целых чисел. Неожиданный результат вычислений практически гарантирован, если производится преобразование вещественного числа в целое, и при этом вещественное число имеет значение, близкое к максимально допустимому целому. Конкретная «опасная» величина числа зависит от того, знаковое или беззнаковое целое число должно быть получено. Остановимся на этом подробнее. Например, если имеется Ci-компилятор, созданный при помощи компилятора Microsoft из комплекта Visual Studio 2005, и для него в Ci-программе написать:

    int i 214748360;

    puts i;

то будет корректно напечатано число 214748360, но если попытаться выполнить код:

    loop {puts i} 1 i 214748360;

то будет напечатано число

    214748352.


Это происходит из-за того, что в качестве начального значения для цикла loop может быть указано значение любого типа, а когда компилятор не знает, какой именно тип ему ожидать, то при получении строки с цифрами, он преобразует ее сначала в вещественное число. Затем, внутри функции loop, это число преобразуется в данное того же типа, что и переменная цикла i, то есть, в целое. В примере величина вещественного числа очень близка к максимальному знаковому целому, и при преобразовании (которое автоматически создает C-компилятор Microsoft) возникает потеря значения в младших разрядах. Для других компиляторов это может быть не так, и преобразования могут выполняться правильно.


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


Преобразование типов не производится в случае, если операция требует параметром обязательно переменную. В таком случае преобразование надо было бы производить после выполнения операции, что значительно усложнило бы компилятор. Реально таких ситуаций не много, наиболее частый случай – попытка использования беззнаковых целых в качестве параметра BASIC-подобного цикла for:

    uint i;

    for i 0 10 1 {};

недопустимо, поскольку for ожидает первым параметром переменную типа знаковое целое (эта переменная изменяется во время выполнения цикла, и может принимать отрицательные значения). В таких случаях указание переменной другого типа вызывает ошибку во время компиляции, даже если определены преобразования для обоих типов. Если требуется использовать в качестве параметра цикла переменную отличного типа от int, рекомендуется применить цикл loop, который гораздо мощнее и удобнее. В других языках аналога его нет.


Преобразование выполняется скрытно и автоматически не только в случае, когда параметром указана переменная другого типа. Оно также производится, когда параметром указывается операция, возвращающая значение, или литеральная константа. Например:

    puts PI " " (PI / 2);

выполнится следующим образом (курсивом обозначены скрытые автоматические действия)):

  • временная вещественная переменная tmp1 = PI / 2;

  • временная строчная переменная tmp2 = вещественное-в-строку PI;

  • временная строчная перменная tmp3 = вещественное-в-строку tmp1;

  • вызов puts tmp2, " ", tmp3;

в результате в stdout будет выведено:

    3.141593 1.570796


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


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