2.6. Операторът sizeof
Операторът sizeof връща размера в байтове на израз или
типов спецификатор. Той може да бъде използуван в една от
73.
Следващата програма илюстрира използуването на оператора
sizeof за голямо разнообразие от типови спецификатори.
#include "IntStack.h"
main()
{
cout << "int :\t\t" << sizeof( int );
cout << "\nint* :\t\t" << sizeof( int* );
cout << "\nint& :\t\t" << sizeof( int& );
cout << "\nint[3] :\t" << sizeof( int[3] );
cout << "\n\n"; // ot separate output
cout << "Intstack :\t" << sizeof( IntStack );
cout << "\nIntstack* :\t" << sizeof( IntStack* );
cout << "\nIntstack& :\t" << sizeof( IntStack& );
cout << "\nIntstack[3] :\t" << sizeof(IntStack[3]);
Когато тази програма бъде компилирана и изпълнена на подходяща
машина, тя дава следния резултат:
int : 4
int* : 4
int& : 4
int[3] : 12
IntStack : 12
IntStack* : 4
IntStack& : 4
IntStack[3] : 36
2.7. Аритметичен оператор if
Аритметичният оператор if е един триместен (тернарен)
оператор, който има следния синтаксис:
expr1 ? expr2 : expr3;
expr1 винаги се изчислява. Ако стойността му е истина - т.е.
някаква ненулева стойност - се изчислява expr2; иначе - expr3.
Следната програма илюстрира как може да бъде използуван
аритметичния оператор if.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
74.
#include
main()
{
int i = 10, j = 20, k = 30;
cout << "\nThe Larger value of "
<< i << " and " << j << " is "
<< ( i > j ? i : j );
cout << "\nThe value of " << i << " is"
<< ( i % 2 ? " " : " not " )
<< "odd";
// the arithmetic if can be nested, bit
// too deep a nesting will be difficult to read
// max is set to the largest of 3 variables
int max = ( ( i>j )
? ( ( i>k ) ? i : k )
: ( ( j>k ) ? j : k ) );
cout << "\nThe larger value of "
<< i << ", " << j << " and " << k
<< " is " << max << "\n";
}
Когато тази програма се компилира и изпълни тя дава
следния резултат:
The larger value of 10 and 20 is 20
The value of 10 is not odd
The larger value of 10, 20 and 30 is 30
2.8. Оператори за работа с битове
Тези оператори разглеждат операндите си като подредена
съвкупност от битове. Всеки бит може да има стойност 0 (off)
или 1 (on). Операторите за работа с битове позволяват на
програмиста да проверява и инициализира индивидуални битове
или битови подмножества.
Операндите на тези оператори трябва да бъдат от тип
integer. Въпреки че те могат да бъдат със или без знак
препоръчва се използуването на операнди без знак. Как точно се
обработва знаковия бит при тези оператори зависи от
реализацията им; програми, които работят с дадена реализация
може да не работят с друга. По такъв начин използуванито на
операнд без знак подпомага осигуряването на мобилност на
програмата.
Първо ние ще обясним как работи всеки оператор. После
ще дадем пимери за използуване на всеки от операторите за
работа с битове. В раздел 6.4 (стр. 272) ще реализираме класа
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
75.
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
операция функция използуване
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
~ поразредно логическо
допълване до 1 ~expr1
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
<< изместване в ляво expr1 << expr2
>> изместване в дясно expr1 >> expr2
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
& поразредно логическо И expr1 & expr2
| поразредно логическо ИЛИ expr1 | expr2
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
Таблица 2.3. Оператори за работа с битове
BitVector.
Операторът ("~") обръща битовете на операнда си. Всеки
бит със стойност 1 става 0 и всеки нулев бит получава стойност
1.
unsigned char bits = 0227; 1 0 0 1 0 1 1 1
bits = ~bits; 0 1 1 0 1 0 0 0
Операторите ("<< , >>") изместват битовете на левия операнд с
някакъв брой позиции в ляво или в дясно.
unsigned char bits = 1; 0 0 0 0 0 0 0 1
bits = bits << 1; 0 0 0 0 0 0 1 0
bits = bits << 2; 0 0 0 0 1 0 0 0
bits = bits >> 3; 0 0 0 0 0 0 0 1
Излищните битове на операндите се отстраняват. Операторът за
изместване в ляво ("<<") вмъква битове със стойност 0 от
дясно. Операторът за изместване в дясно (">>") вмъква битове
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
76.
със стойност 0 от ляво. Ако операндът има знак то може да се
добавят копия на знаковия бит или нулеви битове; това зависи
от машината.
Операторът AND ("&") работи с два операнда. Резултатът
за всяка битова позиция е 1 ако двата операнда съдържат битове
със стойност 1; иначе резултатът е нулев бит. (Този оператор
не трябва да бъде бъркан с логическия оператор AND ("&&").
unsigned char result:
unsigned char b1 = 0145; 0 1 1 0 0 1 0 1
unsigned char b2 = 0257; 1 0 1 0 1 1 1 1
result = b1 & b2; 0 0 1 0 0 1 0 1
Операторът XOR (изключващо или) ("^") работи с два
операнда. Резултатът за всяка битова позиция е 1 ако един от
двата, но не и двата операнда съдържат битове със стойност 1;
иначе резултатът е нулев бит.
result = b1 ^ b2; 1 1 0 0 1 0 1 0
Операторът OR ("|") работи с два операнда. Резултатът
за всяка битова позиция е 1 ако един от двата или двата
операнда съдържат битове със стойност 1; иначе резултатът е
нулев бит.
result = b1 | b2; 1 1 1 0 1 1 1 1
Променлива, използувана като дискретна съвкупност от
битове се нарича битов вектор. Битовият вектор е едно
ефективно средство за съхраняване на да/не информация за набор
от елементи и условия.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
77.
Ето един пример. Нека учител има 30 студента в даден
клас. Всяка седмици на класа се дава изпит от типа
взет/невзет. Може да бъде използуван битов вектор за записване
на резултатите от всеки изпит.
unsigned int quiz1 = 0;
Учителят трябва да може да записва във всеки бит 0 или 1,
както и да проверява съдържанието му. Например, студентът с
номер 27 си е взел изпита и преминава. Учителят трябва да
запише 1 в съответния бит. Първата стъпка е да запише в 27-я
бит 1 като останалите битове запазят стойността си. Това може
да бъде направено чрез оператора за изместване в ляво ("<<") и
цялата константа 1.
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0
1 << 27 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Ако тази стойност се използува като операнд за OR заедно с
quiz1 всички битове освен 27-я ще останат непроменени. А 27-я
ще получи стойност 1:
quiz1 |= 1<<27;
Представете си, че учителят повтори изпитването и установи, че
студент 27 не се справя задоволително. Той трябва да може да
запише отново 0 в бит 27. Този път цялото число трябва да има
във всички битове стойност 1 с изключение на 27-я. Забележете,
че това число е инверсно на числото от предния пример. Така,
че може да бъде използуван оператора NOT:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
78.
1 1 1 1 0 1 1 1
1 1 1 1 1 1 1 1
~(1 << 27) 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
Ако тази стойност се използува като операнд за AND заедно с
quiz1 всички битове освен 27-я ще останат непроменени. А 27-я
ще получи стойност 0:
quiz1 &= ~(1 << 27);
Ето как учителят би могъл да провери стойността на различните
битове. Да разгледаме пак студент 27. Първата стъпка е да се
запише в 27-я бит на едно цяло число 1. Като се приложи
оператора AND над това число и quiz1 ще получим истина ако бит
27 на quiz е също 1; иначе ще бъде върната 0 (лъжа).
int hasPassed = quiz1 & (1 << 27);
Упражнение 2-4. Дадени са следните две дефиниции:
unsigned int ui1 = 3; ui2 = 7;
Какъв е резултата на изразите:
(a) ui1 & ui2 (c) ui1 | ui2
(b) ui1 && ui2 (d) ui1 || ui2
Упражнение 2-5. Какво означава присвояването на 0377
на променлива от тип unsigned char в термините на битовия й
шаблон? Начертайте картинката.
Упражнение 2-6. Как програмистът може да изолира
втория байт на променлива от тип int използувайки операторите
за работа с битове?
Упражнение 2-8. Съществува метод за получаване на
степените на двойката като се използува оператора за
изместване в ляво и константата 1. Генерирайте таблица от
първите 16 стойности.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
79.
2.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() != '\0' )
Намерението на програмиста е да присвои на ch следващия символ
и тогава да провери дали той не е '\0'. Обаче, фактически се
проверява дали следващият символ е '\0'. След това на ch се
присвоява стойност истина или лъжа като резултат от
проверката. Никога на ch не са присвоява следващия символ.
Може да бъде зададен друг ред на изпълнение на
операциите, като се обособят подизрази чрез използуване на
скоби. При изчисляване на смесени изрази първо се пресмятат
всички затворени в скоби подизрази. Всеки подизраз се замества
от резултата му; изчислението продължава. Най-вътрешните скоби
се изчисляват преди по-външните. Например,
4 * 5 + 7 * 2 ==> 34
4 * ( 5 + 7 * 2 ) ==> 76
4 * ( ( 5 + 7 ) * 2 ) ==> 96
Ето и по-горе споменатия смесен израз, в който са добавени
скоби съобразно намерението на програмиста:
while ( (ch = nextChar()) != '\0' )
Таблица 2.4. представя пълния набор от С++ опирации, подредени
според приоритета си. 17R трябва да се чете като "ниво на
приоритет 17, с асоциативно правило от дясно на ляво".
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
80.
Съответно, 7L трябва да се чете като "ниво на приоритет 7, с
асоциативно правило от ляво на дясно". Оператор с по-висок
приоритет има по-високо приоритетно ниво.
Упражнение 2-8. Използувайки таблица 2.4. определетте
реда на изчисление в следните смесени изрази:
(a) ! ptr == ptr->next
(b) ~ uc ^ 0377 & ui << 4
(c) ch = buf[ bp++ ] != '\0'
Упражнение 2-9. Трите израза по-горе се изчисляват по
начин, противоречащ на намеренията на програмиста. Поставете
скоби така, като считате, че програмистът би желал.
Упражнение 2-10. Защо се получава грешка от следния
кодов фрагмент? Как бихте могли да я откриете?
void doSomething();
main()
{
int i = doSomething(), 0;
}
2.10. Преобразуване на типове
На машинно ниво всички даннови типове се загубват в
последователността от битове. Информацията за типовете е
предварително описание от вида: "вземете х на брой битове и
ги интерпретирайте като използувате следния шаблон ..."
Преобразуването на един предварително дефиниран тип
към друг обикновено променя едно или две свойства на типа, но
не влияе на основния битов шаблон. Размерът може да бъде
увеличен или намален, а разбира се, и интерпретацията ще
бъде променена.
Някои от преобразуванията не са безопасни; обикновено
компилаторът предупреждава за тях. Преобразуването на
по-широкообхватен даннов тип към по-теснообхватен е определено
една небезопасна операция. Ето три примера за това:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
81.
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
Ниво Оператор Функция
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
17R :: глобален обхват (унарна)
17L :: класов обхват (бинарна)
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
16L' ->,. селектор на член
16L [] индекс на масив
16L () извикване на функция
16L () контруктор на тип
16L 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 , оператор запетая
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
Таблица 2.4. Приоритет на операциите и асоциативност
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
82.
long lval;
unsigned char uc;
int (3.14159);
(signed char) uc;
short (lval);
Следните два записа,
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 (стр.
282) обсъжда дефинирано от потребителя преобразуване на типа
клас.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
83.
Неявно перобразуване на типове
Неявното конвертиране на типовете е преборзуване,
което се изпълнява от компилатора без намесата на пограмиста.
Това неявно конвертиране се прилага най-общо котато се смесват
различни типове данни. То се прави съобразно набор от
предварително дефинирани правила, наречени стандартни
преобразувания.
Когато дадена стойност се присвоява на някакъв обект
тя се конвертира съобразно типа му. Изпращането на стойност
при извикване на функция предизвиква преобразуването на типа й
съобразно типа на аргументите на функцията. Например,
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
става след умножението.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
84.
Явно преобразуване
Може да се каже, че е малко разточително да се
изпълняват две операции за конвертиране в един смесен оператор
i = i + 3.14159;
Понеже типът на резултата е int изглежда по-разумно да се
свива операнда от тип double вместо да се разширява i до тип
double и след това сумата да се свива до int.
Една от причините за явното конвертиране е
отхвърлянето на стандартното преобразуване. Например,
i = i + int(3.14159);
Сега 3.14159 се конвертира към тип int със стойност 3. Тази
стойност се добавя към, и после се присвоява на i.
Едно предварително дефинирано стандартно конвертиране
позволява да бъде прсвояван указател от произволен даннов тип
на указател от тип void*. Указателят void* се използува винаги
когато конкретният тип на даден обект е неизвестен или ще се
променя при известни обстоятелства. Указателят void* понякота
се нарича обобщен указател поради способността му да адресира
обекти от произволен даннов тип.
Обаче, към данните, сочени от указател void* не можем
да се обръщаме директно. ( Няма налична информация за тип, от
която да се ръководи компилатора при интерпретирането на
сочената битова последователност). По-скоро указателят void*
трябва първо да бъде конвертиран към конкретен тип.
В С++, обаче, няма предварително дефинирано
преобразуване на указател void* към указател от конкретен тип
понеже това е потенциално опасно. Например, ако се опитате да
присвоите указател от тип void* на указател към какъвто и да е
обект ще получите грешка по време на компилация:
int i;
void *vp;
int *ip = &i;
double *dp;
vp = ip; // ok: explicit cast
dp =vp; // error: no standart convertion: unsafe
Втората причина за явното конвертиране е да бъде
заобиколено проверяването на типовете. Изобщо, С++ разрешава
всяка стойност да бъде явно конвертрана към произволен даннов
тип. При явното конвертиране, обаче, пограмистът носи
отговорност за безопастността на преобразуванетоо на типовете.
Например,
dp = (int*)vp; // ok: explicit cast
*dp = 3=14; // trouble if dp addresses i
Третата причина за явното конвертиране е да бъде
избегната ситуацията, когато е възможно повече от едно
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
85.
конвертиране. Ние ще разгледаме този случай по-задълбочено в
раздел 4.3 (стр. 161) при обсъждането на презаредимите имена
на функции.
Даден е следния набор от идентификатори:
char ch; unsigned char unChar;
short sh; unsigned short unShort;
int intVal; unsigned int unInt;
long longVal; float f1;
Упражнение 2-11. Определете кои от следните
присвоявания не са безопсни поради възможното стесняване:
(a) sh = intVal (e) longVal = unInt
(b) intVal = longVal (f) unInt = f1
(c) sh = unChar (g) intVal = unShort
Упражнение 2-12. Определете типа на следните изрази:
(a) 'a' - 3
(b) intVal * longVal - ch
(c) f1 + longVal / sh
(d) unInt + (unsigned int) longVal
(e) ch + unChar + longVal + unInt
2.11. Оператори
Операторите са най-малките изпълними единици в една
С++ програма. Те се разделят посредством точка и запетая;
най-простата форма на оператор е парзен или нулев оператор.
Празният оператор изглежда така:
; // null statement
Този оператор е полезен в случаите, когато синтаксисът на
езика изисква наличието на оператор, но не и логиката на
програмата. Това понякога се случва при операторите за цикъл
while и for. Например,
while ( *string++ = *inBuf++ )
; // null statement
Присъствието на ненужен празен оператор няма да
предизвика грешка по време на компилация. (На автора веднъж
се случи да използува компилатор на ALGOL68 в Колумбийския
университет, който отбелязваше всеки празен оператор като
фатална грешка. Представете си, че започвате от 3 ч. следобед
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
86.
да чакате 40-минутната компилация на програмата си, за да
получите странично канцилиране, следствие на точка и запетая).
int val;; // additional null statement
Това е съвкупност от два оператора: декларативен оператор int
val и празен оператор.
Декларация завършваща с точка и запетая е декларативен
оператор. Това е единствения оператор, който може да бъде
записван извън функция. Израз, след който има точка и запетая
е оператор-израз.
Съставни оператори и блокове
Някои синтактични конструкции на езика допускат
записването само на един оператор. Логикато на програмата,
обаче, може да изисква изпълнението на два или повече
оператора. В тези случаи може да се използува съставен
оператор. Например,
if ( account.balance - withdrawal < 0 )
{ // compound statement
issueNotice( account.number );
chargePenalty( account.number );
}
Съставният оператор представлява последователност от
оператори, затворена във фигурни скоби. Съставният оператор се
разглежда като неделима единица и може да се появява навсякъде
в програмата, където може да бъде поставен единичен оператор.
Не е необходимо да се записва точка и запетая след съставен
оператор.
Съставен оператор, който съдържа един или повече
декларативни оператори, се нарича блок. Блоковете се
разглеждат подробно в Раздел 3.10 (стр. 130) при обсъждането
на областите на действие.
2.12. Оператори за управление
Подразбиращият се начин за изпълнение на операторите в
една програма е последователен. Изпълнението на всяка С++
програма започва от първия оператор на main(). След това се
изпълнява всеки следващ оператор. Когато се изпълни последния
оператор изпълнението на програмата приключва.
Като изключим най-простите програми, последователното
изпълнение на програмата не съответствува на проблемите, които
трябва да бъдат решавани. В примерните програми, които вече
разгледахме, се запознахме с условния оператор if и
операторите за цикъл while и for. Следващият раздел преглежда
целият набор от С++ оператори.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
87.
2.13. Операторът if
Всеки оператор if проверява някакво условие. Винаги,
когато условието има стойност истина се изпълнява някакво
действие или набор от действия. Иначе тези действия се
игнорират.
Синтаксисът на оператора if има вида:
if ( expression )
statement;
Изразът expression трябва да бъде затворен в скоби. Ако той
има ненулева стойност се приема, че условието има стойност
истина и се изпълнява оператора statement.
Например, нека да опишем една член-функция, която да
намира минималната стойност, съдържаща се в обект от класа
IntArray. Също така трябва да поддържаме информация за броя на
появите на тази стойност в обекта. Логиката на функцията
изисква два условни оператора:
1. Ако текущата стойност е равна на минималната стойност
трябва броячът да бъде увеличен с единица.
2. Ако текущата стойност е по-малка от минималната стойност на
минималната се присвоява текущата и броячът става 1.
Стойностите на масива ще бъдат преглеждани като се използува
оператора for. За намирането на най-малкия елемент трябва да
бъде прегледан всеки елемент на масива.
На тази функция са необходими две променливи: една,
която да съдържа минималната стойност и втора - за брояч.
Размерът на масива определя максималния брой появи на
минималната стойност. Следователно данновият тип на брояча на
появите й трябва да има същия размер като този на size -
членът - данни на IntArray, който описва размерността на
масива.
int minVal = ? // what value ?
int occurs = ? // what value ?
minVal трябва да бъде инициализирана с една начална стойност,
спямо която да бъдат сравнявани елементите на масива. Един
подход при инициализирането на minVal е да се използува
най-голямото възможно цяло число. Инициализирането на occurs с
0 би работило във всички случаи. Това, обаче, не е най-добрата
стратегия.
Нека прегледаме възможните варианти при търсене на
минимална стойност.
- Ако масивът е записан във възходящ ред, то първият елемент е
минималната стойност на масива. В този случай е необходимо
само едно присвояване на стойност на minVal.
- Ако масивът е записан в низходящ ред, то последният елемент
е минималната стойност на масива. В този случай са
необходими n присвоявания на стойност на minVal.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
88.
- Масивът не е подреден и елементите са въведени в произволен
ред. Може да се каже, че минималната стойност ще бъде
намерена някъде в средата. При този среден случай вероятно
половината от елементите ще бъдат присвоени на minVal.
Във всички случаи ако minVal бъде инициализирана със
стойността на първия елемент на масива със сигурност ще бъде
спестено едно присвояване. Освен това ще е необходимо
сравняването само на n-1 елемента. Такава ще бъде и
стратегията на функцията.
Ще бъдат необходими два оператора if:
if minVal > ia[ i ] ... // new minVal
if minVal == ia[ i ] ... // another occurence of
// minVal
Една обща програмистка грешка при използуването на
оператора if е, че не се използува съставен оператор, когато
трябва да бъдат изпълнени няколко оператора при стойност
истина на условието. Това е една много трудна за откриване
грешка, тъй като текстът на програмата изглежда правилен.
Например,
if ( minVal > ia[ i ] )
minVal = ia[ i ];
occurs = 1; // not part of if statement
В портиворечие с намерението на пограмиста операторът
occurs = 1;
не се разглежда като част от оператора if, а се изпълнява
безусловно след изпълнението на if. Ето правилния запис:
if ( minVal > ia[ i ] )
{
minVal = ia[ i ];
occurs = 1;
}
Вторият оператор if изглежда така:
if ( minVal == ia[ i ] )
++occurs;
Забележете, че реда на операторите if е съществен. Във
функцията ни occurs винаги ще има стойност над 1 ако поставим
операторите в следния ред:
if ( minVal > ia[ i ] )
{
minVal = ia[ i ];
occurs = 1;
}
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
89.
// potential error iff minVal
// has iust been set to ia[i]
if ( minVal == ia[ i ] )
++occurs;
Изпълнението на двата оператора if за една и съща
стойност е не само потенциално опасно, но и ненужно. Един и
същ елемент не може да бъде едновременно по-малък от minVal и
равен на него. Ако едното условие има стойност истина, то
другото спокойно може да бъде игнорирано. За този вид условия
операторът if предлага и else клауза.
if-else операторът има следния синтаксис:
if ( expression )
statement-1;
else
statement-2;
Ако expresson има ненулева стойност, условието се приема за
истинно и се изпълнява statement-1; иначе се изпълнява
statement-2. Забележете, че ако statement-1 не е съставен
оператор той трябва за бъде завършен с точка и запетая.
Програмистът, запознат с Pascal или Ada, при които записът с
точка и запетая е неправилен, често пропускат да ги напишат.
Например,
if ( minVal == ia[ i ] )
++occurs; // terminating ';' required
else
if ( minVal > ia[ i ] )
{
minVal = ia[ i ];
occurs = 1;
}
В този пример операторът statement-2 е също оператор if. Ако
minVal е по-малко от елемента не се прави нищо.
В следващия пример винаги се изпълнява един от трите
оператора:
if ( minVal < ia[ i ] )
; // null statement
else
if ( minVal > ia[ i ] )
{
minVal = ia[ i ];
occurs = 1;
}
else // minVal == ia[ i ]
++occurs;
Операторът if-else внася известно двусмислие в текста на
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
90.
програмата, което се нарича проблем на объркващото else. Този
проблем се появява когато един оператор има повече if
отколкото else клаузи. Въпроса, който везниква е с кое if се
свързва допълнителната else клауза. Например,
if ( minVal <= ia[ i ]
if ( minVal == ia[ i ]
++occurs;
else
{
minVal = ia[ i ];
occurs = 1;
}
Начинът на записване на оператора показва, че програмистът
вярва, че else трябва да се свърже с външната if клауза. В
С++, обаче, двусмислието на записа с объркващото else се
избягва като се приема, че то се свързва с последния
незатворен if. В този случай операторът if-else ще бъде
изпълнен както следва:
if ( minVal <= ia[ i ]
{ // effect of dangling-else resolution
if ( minVal == ia[ i ]
++occurs;
else
{
minVal = ia[ i ];
occurs = 1;
}
}
Един метод за заобикаляне на подразбиращото се свързване на
клаузите if и else е пследният срещнат if да бъде поставен в
съставен оператор.
if ( minVal <= ia[ i ]
{ // override the default resolution
if ( minVal == ia[ i ]
++occurs;
}
else
{
minVal = ia[ i ];
occurs = 1;
}
Някой от стиловете за записване на текста на програмите
препоръчват винаги да се използуват фигурните скоби за
съставен оператор за да бъдат избегнати възможните обърквания
и грешки при по-късните изменения на текста.
Раздел 1.8 (стр. 51) въвежда класа IntArray. Ето
реализацията на член-функцията min() на IntArray:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
91.
#include "IntArray.h"
IntArray::min( int &occurs )
{
int minVal = ia[ 0 ];
occurs = 1;
for ( int i = 1; i < size; ++i )
{
if ( minVal == ia[ i ] )
++occurs;
else
if ( minVal > ia[ i ] )
{
minVal = ia[ i ];
occurs = 1;
}
}
return minVal;
}
За да върне както минималната стойност, така и броя на
появяванията й, min() използува един аргумент от тип указател
към integer. Аргументът от тип указател подава стойността за
запис на фактическия аргумент на функцията; аргумент, който не
е от тип указател подава стойност за четене към функцията.
Подаването-на-аргумент-чрез-указател позволява да му бъде
присвоявана стойност от вътрешността на функцията. Ето един
пимер за използуването му:
#include
#include "IntArray.h"
IntArray myArray;
main()
{
// initialize the array in descending order
for ( int i = myArray.getSize()-2; i >= 0; --i )
myArray[ i ] = i;
// insert a second copy of 0q the lowest value
myArray[ myArray=getSize()-1 ] = 0;
int number = 0;
int low = myArray.min( number );
cout << "\nlow: " << low
<< "number: " << number << "\n";
}
Когато тази програма бъде компилирана и изпълнена, се получава
следния резултат:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
92.
low: 0 number: 2
Сподели с приятели: