На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила ЧаВо (FAQ) разделов Паскаля
В этом разделе разрешено создавать только темы, в которых описано РЕШЕНИЕ какой-либо общей проблемы, или описание какого-либо аспекта языка Паскаль.
Обсуждение уже созданных тем разрешено, но только конструктивное, например указание на ошибку или уточнение имеющегося текста.

Также читать Требования к оформлению статей
Модераторы: volvo877, Romtek
  
> Приведение типов
    Тождественные и совместимые типы

    При передаче и преобразовании информации в программе должны соблюдаться некоторые требования, блокирующие некорректные действия программиста. Эти требования могут быть разделены на три группы, соответствующие следующим случаям:
    1. передача фактического параметра-переменной;
    2. вычисление выражения;
    3. выполнение оператора присваивания и передача фактического параметра-значения.

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

    Тождественность типов.

    Рассмотрим процедуру, в списке формальных параметров которой заданы лишь параметры-переменные (перед именем формального параметра записано слово Var).

    Пример 1.
    ExpandedWrap disabled
      Type  Ar1 = array[1..100] of real;
            Ar2 = array[1..100] of real;
            Ar3 = Ar1;
            Ar4 = Ar3;
      Var  X : Ar1;  Y : Ar2;  Z : Ar3;  W : Ar4;
           a,b : real;
           m,n : integer;
      Procedure Proc1(Var D:Ar1; Var k:integer);
      Begin
      ..........................
      End { Proc1 };


    Ранее было указано, что формальный и соответствующий ему фактический параметр должны иметь одно и то же имя типа. Это не совсем точно.
    Обозначим через Type1 имя типа формального параметра, через Type2 - имя типа соответствующего ему фактического параметра.
    Как известно, при обращении к процедуре фиктивный адрес формального параметра-переменной замещается реальным адресом фактического параметра. В этом случае формальная и фактическая переменные соответствуют одному и тому же полю памяти. Следовательно, имена типов Type1 и Type2 этих переменных должны определять переменные одинакового размера и структуры, с одинаковым множеством допустимых значений и операций по их обработке. Последнее возможно, если типы Type1 и Type2 тождественны.

    Два типа считаются тождественными, если они представляют собой одно и то же имя типа или один из них описан как эквивалентный другому типу.
    В примере 1 типы Ar1, Ar3 и Ar4 тождественны, Ar1 и Ar2 - не тождественны, хотя они и имеют одинаковое описание типа. Поэтому обращения к процедуре Proc1(X,m), Proc1(Z,m) и Proc1(W,m) считаются правильными, а при обращении Proc1(Y,m) будет выведено сообщение "Type mismatch" ("Несоответствие типов").

    Совместимость типов. Рассмотрим пример 2.

    Пример 2.
    ExpandedWrap disabled
      Var  x,y : real;
           m,n : integer;
           ch : char;
           b : boolean;
      Begin  ...............
        y:=x+2*ch;
        b:=(x<ch) and n;


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

    Основные правила совместимости типов:
    - операнды выражения имеют численные типы (вещественный, целочисленный, диапазонный);
    - операнды определены логическим типом;
    - операнды имеют строковый, символьный или диапазонный символьный типы.
    Если операнды выражения имеют различные численные типы, то при выполнении арифметических операций производится преобразование их значений к более старшему типу в соответствии со следующими приоритетами:
    - real;
    - longint;
    - integer, word;
    - shortint, byte.

    Совместимость типов по присваиванию. Рассмотрим пример 3.

    Пример 3.
    ExpandedWrap disabled
      Var  x,y : real;
           m,n : integer;
           ch : char;
           b : boolean;
      Begin  ...............
        { 1 } y:=(m<n) and b;
        { 2 } ch:=x+m;
        { 3 } m:=2*x+y;
        { 4 } y:=3*m-n;


    Числовой переменной нельзя присвоить булевское значение, символьной переменной - численное значение. Поэтому операторы 1 и 2 не могут быть выполнены, здесь нарушается требование совместимости по присваиванию.
    В правой части оператора 3 - вещественное значение, в левой части - целочисленная переменная. Если допустить выполнение такого оператора, то дробная часть вещественного значения должна быть отброшена, т.е. произошла бы потеря точности. В связи с этим считается, что в операторе 3 также нарушаются требования совместимости по присваиванию.
    В операторе 4 потеря точности не наблюдается. При его выполнении производится лишь преобразование целочисленного значения, полученного при вычислении выражения в правой части оператора, к типу real.
    Обозначим тип переменной в левой части Type1, тип значения выражения - Type2.
    Основные требования совместимости по присваиванию:
    - Type1 и Type2 имеют тождественные типы и ни один из них не является файловым типом;
    - Type1 и Type2 - целочисленные типы;
    - Type1 - вещественный тип, Type2 - вещественный или целочисленный тип;
    - Type1 и Type2 - строковые типы;
    - Type1 - строковый тип, Type2 - символьный тип.

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

    Пример 4.
    ExpandedWrap disabled
      Type  Ar1 = array[1..100] of real;
            Ar2 = array[1..100] of real;
            Ar3 = Ar1;
      Var   X,Y : Ar1;  Z : Ar2;  W : Ar3;


    Здесь типы Type1 и Type2 должны быть тождественными. Следовательно, в этом случае операторы Y:=X и Y:=W являются допустимыми, в то время как для оператора Y:=Z будет определено несоответствие типов.

    Рассмотрим теперь процедуру, в списке формальных параметров которой имеются параметры-значения.

    Пример 5.
    ExpandedWrap disabled
      Var  x,y : real;
           m,n : integer;
      Procedure Proc2(k:integer; r,t:real);
      Begin
      .............
      End { Proc2 };
      Begin
      .............
        { 1 } Proc2(m,n-1,x+m);
        { 2 } Proc2(y,x+y,x);


    Для параметра-значения в теле процедуры выделяется поле памяти в соответствии с его типом. При обращении к процедуре в это поле пересылается значение фактического параметра; другими словами, формальному параметру присваивается значение фактического параметра. Следовательно, по отношению к параметру-значению должны соблюдаться изложенные выше требования совместимости по присваиванию. В частности, в примере 5 для оператора 1 эти требования соблюдаются, для оператора 2 - не соблюдаются.




    ПРИВЕДЕНИЕ ТИПОВ ПЕРЕМЕННЫХ

    Имя любой переменной, объявленной в разделе Var, - это адрес поля памяти в машинной программе. Оператор присваивания a:=b, рассматриваемый на уровне машинной программы, - это пересылка содержимого поля памяти b в поле памяти a.
    При программировании на машинном языке или на языке ассемблера произвольному полю памяти можно присвоить значение любого другого поля памяти. В этом случае ответственность за правильность выполнения оператора присваивания полностью возлагается на программиста.
    Иная ситуация имеет место при программировании на языках высокого уровня, в частности на языке Паскаль. Имя переменной, идентифицирующей адрес поля памяти, всегда имеет вполне определенный тип. Тип переменной однозначно определяет множество значений, которые имеет право принимать переменная данного типа, и набор операций, которые допустимы при ее обработке.

    Пример.
    ExpandedWrap disabled
      Var  k,m : byte;
           p,q : char;
      Begin
        k:=65; p:='A';
        1) m:=k; q:=p;
        2) m:=p; q:=k;
        3) m:=ord(p); q:=chr(k);


    Внутреннее представление переменной k и переменной p совершенно одинаково (порядковый номер символа 'A' в таблице ASCII равен 65) и имеет вид 0110 0101.
    Действие оператора m:=k сводится к пересылке содержимого однобайтного поля k в поле m. Аналогичные действия выполняются для оператора q:=p. В то же время операторы m:=p и q:=k не могут быть выполнены, так как при их трансляции будет обнаружено несоответствие типов.
    Пересылка содержимого поля p в поле m может быть выполнена оператором m:=ord(p). Функция ord не производит никаких преобразований значения переменной p, она лишь информирует транслятор, что значение этой переменной следует рассматривать как значение целочисленной переменной и переслать соответственно в поле m. Аналогичная ситуация имеет место для оператора q:=chr(k). Следовательно, функции ord и chrвыполняют приведение типа, а не преобразование значений переменных.

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

    Синтаксис приведения типа:

    идентификатор типа + '(' + идентификатор переменной + ')'

    Здесь поле памяти, определяемое идентификатором переменной, рассматривается как экземпляр типа, представленного идентификатором типа.

    Рассмотрим суть приведения типов в Турбо Паскале на следующих двух примерах.

    Пример 1.
    ExpandedWrap disabled
      Var  ch : char;
           b1,b2 : byte;
      Begin
        ch:='A'; b1:=byte(ch); b2:=ord(ch);
        Writeln('b1=',b1,'   b2=',b2);


    Будет отпечатано:
    ExpandedWrap disabled
      b1=65   b2=65

    Выражение byte(ch) определяет "наложение" типа byte на переменную ch. В этом случае биты, содержащиеся в поле памяти ch, интерпретируются как значение переменной типа byte (ch = 010000012 = 4116 = 6510).
    Примечание. Внешняя форма записи выражения byte(ch) и функции ord(ch) одинакова, что позволяет трактовать выражение byte(ch) как функцию приведения к типу byte.

    Пример 2.
    ExpandedWrap disabled
      Type  ByteAr = array[1..2] of byte;
      Var  W : word;
      Begin
        ByteAr(W)[1]:=10; ByteAr(W)[2]:=20;
        Writeln(W,'   ',ByteAr(W)[1],'   ',ByteAr(W)[2]);
        W:=2500;
        Writeln(W,'   ',ByteAr(W)[1],'   ',ByteAr(W)[2]);


    В результате работы программы будет отпечатано:
    ExpandedWrap disabled
      5130   10   20
      2500   196   9


    B примере 2 происходит наложение "формального" поля ByteAr длиной два байта на реальное поле W, также имеющее длину два байта. При этом байты, входящие в состав поля W, рассматриваются как отдельные элементы байтового массива типа ByteAr.
    Отпечатанные выше элементы в 16 с/с имеют следующие значения:
    ExpandedWrap disabled
      140A      OA     14
      09C4      C4     09

    Cоответствие между элементами полей W и ByteAr легко просматривается, если учесть, что в переменной типа word старшим считается правый байт. Последнее определяется тем, что в процессорах типа Intel числовое значение размещается в поле памяти таким образом, что более старшие разряды числа располагаются в байтах с более старшими адресами. Это положение иллюстрируется ниже на примере переменной k типа longint.

    Переменной k выделяется 4 байта памяти. Поскольку адресом поля памяти является адрес его крайнего левого байта, то байты поля k получат следующие адреса:
    ExpandedWrap disabled
                                       k + 0           k + 1          k + 2           k + 3


    Пусть k = 2 000 100 101 = $ 77 37 1B 05. Тогда в байтах с адресами k+0 .. k+3 шестнадцатеричные цифры значения переменной k будут расположены следующим образом:

    ExpandedWrap disabled
      k + 0          k + 1          k + 2           k + 3
        05            1B              37             77


    Приведение типа T(v), где T - имя типа, v - имя переменной, позволяет трактовать на машинном уровне биты, содержащиеся в поле v, как биты, принадлежащие значению типа T.
    Пример 3. Определить численные значения байтов, входящих в состав полей памяти типов integer и real. Здесь фактически идет речь об определении внутренного представления переменных типов integer и real.

    ExpandedWrap disabled
      Program PutType;
      Type  ByteAr2 = array[1..2] of byte;
            ByteAr6 = array[1..6] of byte;
      Var  I : integer;
           R : real;
      Begin
         I:=5000; R:=5000;
         Writeln('  I:  ',ByteAr2(I)[1],'  ',ByteAr2(I)[2]);
         Writeln('  R:  ',ByteAr6(R)[1],'  ',ByteAr6(R)[2],
                 '  ',ByteAr6(R)[3],'  ',ByteAr6(R)[4],
                 '  ',ByteAr6(R)[5],'  ',ByteAr6(R)[6]);
         I:=-5000; R:=-5000;
         Writeln('  I:  ',ByteAr2(I)[1],'  ',ByteAr2(I)[2]);
         Writeln('  R:  ',ByteAr6(R)[1],'  ',ByteAr6(R)[2],
                 '  ',ByteAr6(R)[3],'  ',ByteAr6(R)[4],
                 '  ',ByteAr6(R)[5],'  ',ByteAr6(R)[6]);
      End.


    Результаты работы программы удобно представить в следующем виде:
    ExpandedWrap disabled
        I:  136  19                      I:  88  13
        R:  141  0   0   0   64   28     R:  8D  00  00  00  40  1C
        I:  120  236                     I:  78  EC
        R:  141  0   0   0   64  156     R:  8D  00  00  00  40  9C


    Здесь в левой части - печатаемые программой результаты (в десятичной системе счисления), в правой части - те же результаты в шестнадцатеричной системе счисления.

    Проверка результатов:
    ExpandedWrap disabled
      5000 = $1388 ;   -5000 = $EC78 ;
      1388 = 0,1388 * 164  = 0, 0001 0011 1000 1000 * 2^16  = 0,1001110001000 * 2^13


    Значение R в 16 с/c : 1C 40 00 00 00 8D.

    Полученные результаты четко показывают, что числовые данные располагаются в памяти таким образом, чтобы более старшие разряды находились в байтах с адресами большей величины.
    Пусть для вещественной переменной R компилятор отвел поле памяти с адресом A. Так как переменная типа real занимает 6 байтов, то в поле R входят байты с адресами A+0, A+1, A+2, A+3, A+4, A+5. Тогда для R=5000 байты будут иметь следующее содержимое:
    (A+0) = 8D ; (A+1) = 00 ; (A+2) = 00 ; (A+3) = 00 ; (A+4) = 40 ; (A+5) = 1C .

    Пример 4.

    y = 1, если x < 0 иначе y=0

    ExpandedWrap disabled
      Var  x : real;
      y : byte;
      {Вместо условного оператора}
          If x<0 then
            y:=1
          Else
            y:=0;
      {можно написать}
          y:=byte(x<0);



    Существует еще несколько способов приведения типов. Один из них это использование
    записей с вариантными полями.

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

    Рассмотрим пример:

    ExpandedWrap disabled
      var
        mem4 : record
          case byte of
            0 : (by : array[0..3] of byte);
            1 : (wo : array[0..1] of word);
            2 : (lo : longint);               { внимание! case не закрыт операторной скобкой end!}
        end;


    В этом примере запись mem4 имеет три варианта, каждый из которых занимает в памяти один и тот же участок памяти из 4 байт. В зависимости от того, к какому полю записи мы обращаемся, этот участок может рассматриваться как массив из 4 байт (поле by), массив из двух слов (поле wo) или как целое число типа Longint (поле lo). Например этой записи сначала можно присвоить значение как длинному целому, а затем просмотреть результат по байтам или словам:
    ExpandedWrap disabled
      var
        x  : word;
        xb : byte;
        xl : longint;
        m  : mem4;
      begin
        ...
        with m do
          begin
            lo:= trunc(2*pi*x);
            if wo[1] = 0 then
              if by[1] = 0 then
                xb:= by[0]
              else
                x:= wo[0]
            else
              xl:= lo
          end;
        ...
      end.


    Предложение Case... of, открывающее вариантную часть, внешне похоже на соответствующий оператор выбора, но на самом деле играет роль служебного слова, обозначающего начало вариантной части. Именно поэтому в конце вариантной части не стоит END. (Поскольку вариантная часть - всегда последняя в записи, за ней стоит все же стоит END, но он относится к Record). Ключ выбора в предложении Case... of фактически игнорируется компилятором (TP): единственное что нужно учитывать, это то, что ключ выбора должен определять некоторый стандартный или предварительно объявленный порядковый тип.

    Причем этот тип никак не влияет ни на количество вариантных частей, ни на характер констант выбора.

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

    ExpandedWrap disabled
      type
        rec1 = record
        a : byte;
        b : eord;
      end;
      rec2 = record
        c: longint;
        case x : byte of
          1: (d : word);
          2: (e : record
                    case boolean of
                      3 : (f : rec1);
                      3 : (g : single);
                     '3': (c : word)
                  end)
      end;
      var
        r: rec2;
      begin
        r.x:=255;
        if r.e.g = 0 then
          writeln('OK')
        else
          writeln(r.e.g)
      end.


    В этом примере предложение case boolean of в записи, определяемой в поле e, объявляет ключом выбора логический тип, который может иметь всего 2 значения - True или False. Константы же выбора следующих далее вариантов не только содержат не свойственный этому типу значения, но и две из них повторяются, а общее количество вариантов - 3 , а не 2, как следовало бы ожидать.

    Имена полей должны быть уникальными в пределах той записи, где они объявлены, однако, если записи содержат поля-записи, т.е. вложены одна в другую, имена могут повторятся на разных уровнях вложенности (см. поле с в последнем примере).

    Более подоробно узнать о записях и представлении переменных в памяти ЭВМ вы можете в соотвествующих разделах FAQ'а.

    По мотивам лекций Назаренко В.И. и книги Фаронова В.В. "Turbo Pascal 7.0"

    P.S. Существует еще один вариант приведения типов с использованием абсолютных переменных, но об этом я расскажу позже...
      А теперь развеем миф о том, что Pascal не даст вам выстрелить себе в ногу :D :yes:
      (здесь и далее имеется в виду BP/TP)

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

      1)разыменованный бестиповой указатель типа pointer совместим по приведению с любым типом;

      2)тип PChar можно складывать со смещением, в результате двигаясь по памяти;

      ExpandedWrap disabled
        const
          W : word               = 12345;                    { = $3039 }
          B : array[0..3]of byte =(1,2,3,4);                 { = $04030201 }
        Begin
          { стандарт -----------------------------------------------------------------------------------}
          Writeln('W=',W:5,' B[0]=',B[0]:3,' B[1]=',B[1]:3); {'W=12345 B[0]=  1, B[0]=  2'              }
          { классическое приведение типа ---------------------------------------------------------------}
          Writeln('Lo(W)=',byte(W):3);                       {'Lo(W)= 57'                     57 = $39  }
          { прицел на ногу -----------------------------------------------------------------------------}
          Writeln('Word(B[0],B[1])=',word((@B)^):5);         {'Word(B[0],B[1])=  513'        513 = $0201}
          { прицел выше --------------------------------------------------------------------------------}
          Writeln('Hi(W)=',byte((PChar(@W)+1)^):3);          {'Hi(W)= 48'                     48 = $30  }
          { контрольный выстрел в голову ---------------------------------------------------------------}
          Writeln('Word(B[2],B[3])=',word((pointer(PChar(@B)+2))^):5);
                                                             {'Word(B[2],B[3])= 1027'       1027 = $0403}
        end.
      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
      0 пользователей:


      Рейтинг@Mail.ru
      [ Script execution time: 0,0407 ]   [ 16 queries used ]   [ Generated: 25.04.24, 17:22 GMT ]