3.9.
Приоритет
Важно е да бъде научен приоритета на операциите, т.е. реда, в
който те се изпълнявт в смесените изрази, за да бъдат избягнати много от
източниците на програмни грешки. Какъв, например, е резултата от следния
аритметичен израз?
6 + 3 * 4 / 2 + 2 Едно изчисление от ляво на дясно
дава резултат 20. Други възможни резултати са 9, 14 и 36. Кой е правилния?
14.
В С++ умножението и делението имат по-висок приоритет от събирането.
Това означава, че те се изпълняват първи. Обаче, умножението и делението имат
еднакъв приоритет. Операции, които имат еднакъв приоритет се изпълняват от дясно
на ляво. Следователно редът за изчисляване на израза е:
1. 3 * 4 => 12
2. 12 / 2 => 6 3. 6 + 6 => 12 4. 12 + 2 => 14
Ето един смесен
израз, в който има коварна грешка. Проблемът се състои в това, че операторът за
неравенство ("!=") има по-висок приоритет от оператора за
присвояване:
while ( ch = nextChar() != ‘�’ )
Намерението на
програмиста е да присвои на ch следващия символ и тогава да провери дали той не
е ‘�’. Обаче, фактически се проверява дали следващият символ е ‘�’. След това
на ch се присвоява стойност истина или лъжа като резултат от проверката. Никога
на ch не са присвоява следващия символ. Може да бъде зададен друг ред на
изпълнение на операциите, като се обособят подизрази чрез използуване на скоби.
При изчисляване на смесени изрази първо се пресмятат всички затворени в скоби
подизрази. Всеки подизраз се замества от резултата му; изчислението продължава.
Най-вътрешните скоби се изчисляват преди по-външните. Например,
4 * 5 + 7
* 2 ==> 34
4 * ( 5 + 7 * 2 ) ==> 76
4 * ( ( 5 + 7 ) * 2 ) ==> 96
Ето и по-горе споменатия смесен израз, в който са добавени скоби съобразно
намерението на програмиста:
while ( (ch = nextChar()) != ‘�’
)
Таблица 3.4. представя пълния набор от С++ опирации, подредени според
приоритета си. 17R трябва да се чете като “ниво на приоритет 17, с асоциативно
правило от дясно на ляво”. Съответно, 7L трябва да се чете като “ниво на
приоритет 7, с асоциативно правило от ляво на дясно”. Оператор с по-висок
приоритет има по-високо приоритетно ниво.
Упражнение 3-8. Използвайки
таблица 2.4. определетте реда на изчисление в следните смесени
изрази:
(a) ! ptr == ptr->next
(b) ~ uc ^ 0377 & ui <<
4
(c) ch = buf[ bp++ ] != ‘�’
Упражнение 3-9. Трите израза по-горе се
изчисляват по начин, противоречащ на намеренията на програмиста. Поставете скоби
така, като считате, че програмистът би желал.
Упражнение 3-10. Защо се
получава грешка от следния кодов фрагмент? Как бихте могли да я
откриете?
void doSomething();
main() {
int i = doSomething(), 0;
}
3.10. Преобразуване на типове
На машинно ниво
всички даннови типове се загубват в последователността от битове. Информацията
за типовете е предварително описание от вида: “вземете х на брой битове и ги
интерпретирайте като използвате следния шаблон ...” Преобразуването на един
предварително дефиниран тип към друг обикновено променя едно или две свойства на
типа, но не влияе на основния битов шаблон. Размерът може да бъде увеличен или
намален, а разбира се, и интерпретацията ще бъде променена.
Някои от
преобразуванията не са безопасни; обикновено компилаторът предупреждава за тях.
Преобразуването на по-широкообхватен даннов тип към по-теснообхватен е
определено една небезопасна операция. Ето три примера за това:
long lval;
unsigned char uc;
int (3.14159); (signed char) uc; short
(lval);
Приоритет на операциите и асоциативност
Ниво -
Оператор - Функция
17R :: глобален обхват (унарна)
17R :: класов
обхват (бинарна)
16R ->,. селектор на член
16R [] индекс на
масив
16R () извикване на функция
16R () контруктор на тип
16R sizeof
размер в битове
15R ++,-- увеличаване/намаляване с 1
15R ~
поразр.логическо допълване до 1
15R ! логическо не
15R +,- унарни
минус,плюс
15R *,& указаван, адрес на
15R () преобразуване на
тип
15R new,delete управление на свободна памет
14L ->*,.* селектор за
член-указател
13L *,/,% мултипликативни оператори
12L +,- аритметични
оператори
11L <<,>> побитово изместване
10L
<,<=,>,>= операции за сравнение
9L ==,!=
равенство,неравенство
8L & поразредно и
7L ^ поразредно изключващо
или
6L | поразредно или
5L && логическо и
4L || логическо
или
3L ?: аритметичен оператор if
2R =,*=,/= оператори за
присвояване
2R %=,+=,-=,<<=,
2R >>=,&=,|=,^=
1L ,
оператор запетая
Следните два записа,
type (expr)
(type)
expr
Могат да бъдат наречени конвертиращи. Те представят явното изискване
на програмиста за преобразуване на expr към тип type. Трите примера илюстрират
потенциалната опастност от стесняването при преобразуването на
типовете.
При първия случай се загубва дробната част. Например,
//
3.14159 != 3.0
3=14159 != double (int (3.141559) );
При втория случай
интерпретацията на битовата схема ще бъде променена за половината от възможните
стойности (128 255). Най-левият бит сега е знаков бит.
При третия случай
за всяка стойност на lval, за която са необходими битове, повече от тези за
short, резултатът от преобразуването е недефиниран.
Някои пребразувания
са безопасни върху някои машини, но при други предизвикват стесняване. За
повечето машини, например, числото int има същия размер както short или long, но
не и както двете. Едно от следните преобразувания няма да бъде безопасно върху
произволна машина, която не реализира int, short и long в три различни
размера.
unsigned short us;
unsigned int ui;
int( us );
long( ui
);
Последствията от преобразуването на типовете може да бъдат доста
объркващи, но това е нещо, което програмистът трябва непременно да разбере.
Следващите два раздела разглеждат неявното и явното преобразуване на типовете.
Раздел 6.5 обсъжда дефинирано от потребителя преобразуване на типа
клас.
Неявно преобразуване на типове
Неявното конвертиране на
типовете е преобразуване, което се изпълнява от компилатора без намесата на
програмиста. Това неявно конвертиране се прилага най-общо когато се смесват
различни типове данни. То се прави съобразно набор от предварително дефинирани
правила, наречени стандартни преобразувания.
Когато дадена стойност се
присвоява на някакъв обект тя се конвертира съобразно типа му. Изпращането на
стойност при извикване на функция предизвиква преобразуването на типа й
съобразно типа на аргументите на функцията. Например,
void ff( int
);
int val = 3.14159; // converts to int 3
ff( 3.14159 ); // converts to
int 3
И в двата случая константата от тип double 3.14159 се преобразува
към тип int от компилатора. Може да бъде издадено предупреждение когато
конвертирането предизвиква стесняване. При аритметичните изрази преобразуването
се насочва към по-широкообхватни типове данни. Например,
val +
3.141559;
По-широкообхватният тип данни в този аритметичен израз е типа
double. val неявно се конвертира към типа double чрез разширяване (наричано също
повишаване на типа).
Нейната стойност 3 става 3.0 и се добавя към
3.141519. Резултатът от израза е 6.14159.
Забележете, че стойността на
val остава 3. Операцията за преобразуване на типа се прилага върху копие на
стойността на val. Променливата не се записва в процеса на преобразуване на
типа. val = val + 3.14159;
В този израз има две конвертирания. Стойността
на val се повишава до тип double. Резултатът 6.14159 се свива до типа int.
Получената стойност се присвоява на val. val сега съдържа стойността
6.
Процедурата е съвсем същата когата изразът е записан така:val +=
3.14159;
Например, стойността-резултат на следните два израза е 23, а не
20:
int i = 10;
i *= 2.3; // 23,
not 20
Отрязването на
дробната част на константата от тип double става след
умножението.