Начало Решаване на проблеми



страница6/19
Дата20.01.2017
Размер4.54 Mb.
#13105
ТипГлава
1   2   3   4   5   6   7   8   9   ...   19
Глава 2: Изрази и оператори

Типовете данни бяха разгледани в глава 1. Ние описахме

предварително дефинираните типове данни, методите за

дефиниране на нови типове данни, както и как да създаваме

даннови обекти. В тази глава се разглеждат предварително

дефинираните операции, които могат да бъдат използувани за

обработване на данните. Тези операции включват набор от

оператори, които ги организират и ръководят изпълнението на

програмата. В глава 3 ще бъдат обсъдени функциите и механизма

за дефиниране на потребителски набор от операции.

2.1. Какво представлява изразът?
Всеки израз обединява една или повече операции.

Обектите, над които се прилагат операциите се наричат

операнди. Операциите се записват чрез опетори. Например, В С++

проверката за равенство се осъществява от оператора "==".

Операторите, които имат един операнд, се наричат

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

Операндите на бинарните оператори се делят на леви и десни.

Някои оператори предстаавят както унарни, така и бинарни

операции. Например,
*ptr
е един унарен указателен оператор. Той връща стойността,

съхранена в адреса на обекта ptr. Обаче,


var1 * var2
представя бинарния оператор умножение. Той изчислява

произведението на операндите си var1 и var2.

За изчисляването на един израз се изпълнява една или

повече операции за да бъде даден резултат. Изключвайки случая,

когато е указано обратното, резултатът, връщан от израза е

стойност за четене (rvalue). Типът на резултата на един израз

се определя от типа на операндите му. Когато операндите имат

различен тип се извършва преобразуване на типовете съобразно

предварително дефиниран набор от правила. Раздел 2.10 (стр.

80) раазглежда подробно преобразуването на типовете.

Когато в един израз се срещат две или повече операции,

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

64.
той се нарича съставен. Редът за изпълнение на операциите се

определя според приоритета и асоциативността им. (Това ще бъде

разяснено след като разгледаме предварително дефинирания набор

от операции).

Най-простата форма на израз се получава от единична

литерална константа или променлива. Т.е. "операнд" без

операция. Резултатът е стойността за четене на операнда.

Например,


3.14159

"melancholia"

upperBound
Резултатът на 3.14159 е 3.14159. Типът му е double. Резултатът

на "melancholia" e адреса в паметта на първия елемент на низа.

Типът му е char*. Резултатът на upperBound е нейната стойност

за четене (rvalue). Типът му се определя от дефиницията на

променливата.

Следващите раздели разглеждат предварително

дефинираните в С++ оператори, представени в удобен за

изучаването им ред.

2.2. Аритметични операции
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

Операция Функция Използуване

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

* умножение expr * expr

/ деление expr / expr

% деление по модул expr % expr

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

+ събиране expr + expr

- изваждане expr - expr

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ


Таблица 2.1 Аритметични операции
Делението на цели числа дава цяло число. Ако частното има

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


21 / 6;

21 / 7;
дават като резултат 3.

Оператарът за деление по модул ("%") изчислява

остатъка от делението на две стойности. Той може да се

използува само с операнди от цял тип. Левият операнд на

операторът деление по модул е делимото. Делителят е десният

операнд на операцията. Двата операнда трябва да са от цял тип.

Следват няколко примера на правилни и неправилни изрази,

използуващи оператора за деление по модул:

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

65.
3.14 % 3 // error: floating point operand

21 % 6 // ok: result is 3

21 % 7 // ok: result is 0
int i;

double f;


i % 2 // ok: non-zero result indicates i is odd

i % f // ok: floating point operand


В някои случаи изчисляването на аритметичен израз ще

върне неправилен или недефиниран резултат. Това се отнася за

аритметични изрази. Може да се дължи на самото естество на

математическата операция - деление на нула, например, - или

да зависи от компютъра - препълване. Например, в променлива от

тип unsigned char могат да се записват стойности от 0 до 255.

При следната операция за умножение на променливата от тип

unsigned char се дава стойност 256.


unsigned char uc = 32;

int i = 8;

uc = i * uc; // overflow
За да се представи 256 са необходими 9 бита.

Присвояването на 256 на uc предизвиква препълване на паметта,

отделена за този тип данни. Истинската стойност, запизана на

мястото на uc, е недефинирана и ще бъде различна за различните

машини.

2.3. Операции за равенство и отношение, логически



операции
В следствие на изпълнението на тези операции се

получава стойност истина или лъжа. Условието, което има

стойност истина, връща 1, а това с лъжа - 0.

Логическата операция AND ("&&") връща стойност истина

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

Логическата операция OR ("||") връща стойност истина когато

един от двата й операнда имат стойност истина. Операндите се

изчисляват от ляво на дясно. Изчислението се прекратява когато

стойността на израза може да бъде определена. В изразите:
expr1 && expr2

expr1 || expr2


expr2 не се изчислява съответно:
- при логическата операция AND, ако expr1 има стойност

лъжа;


- при логическата операция OR, ако expr1 има стойност

истина.
Ползата от такова изчисление на операцията AND се

вижда, когато expr1 задава такова условие в израза, което ако

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

66.
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

операция функция използуване

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

! логическо не !expr1

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

< по-малко expr1 < expr2

<= по-малко или равно expr1 <= expr2

> по-голямо expr1 > expr2

>= по-голямо или равно expr1 >= expr2

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

== равенство expr1 == expr2

!= различно expr1 != expr2

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

&& логическо и expr1 && expr2

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

|| логическо или expr1 || expr2

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
Таблица 2.2. Операции за равенство и отношение,

логически операции

има стойност лъжа, може да направи пресмятането на expr2

опасно. Например,


while ( ptr != 0 &&

ptr -> value < upperBound &&

notFound( ia[ ptr -> value ] ))
Указател със стойност 0 не адресира никакъв обект. Прилагането

на оператора за избор на елемент към указател със стойност 0

причинява винаги неприятности. Прървият операнд на израза с

AND предотвратява тази възможност. Излизането на индекса извън

границите на масива е също така неприятно. Вторият операнд е

предвиден да предотвратява тази възможност. Къгато първите два

операнда върнат стойност истина третият операнд може спокойно

да бъде изчислен.

Логическият оператор NOT ("!") получава стойност

истина когато неговият операнд има стойност 0; иначе има

стойност лъжа. Например:
int found = 0;

while ( !found )

{

found = lookup( *ptr++ );



if ( ptr == endptr ) // at end

return 0;

}
Изразът
( !found )
има стойност истина докато found има стойност 0.

Използуването на оператора NOT се свързва с въпроса за

стила на програмиране. Например, значението на израза

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

67.
( !found )
е ясно: не е намерен. Така ясно ли е, обаче, какво означава

следното условие?


!strcmp( string1, string2 )
strcmp() е една вградена функция в С-библиотека, която

сравнява два аргумента от тип низ за равенство. Ако върнатата

стойност е 0, то двата низа са равни. Тогава изразът
!strcmp( string1, string2 )
означава седното: ако string1 не е равен на string2. В този

случай използуването на оператора NOT би могло да доведе до

неясноста на условието.

2.4. Оператор за присвояване


Левият операнд на оператора за присвояване ("=")

трябва да бъде стойност за запис (lvalue). Резултатът от

изпълнението на този оператор е записването на нова стойност в

паметта, отделена на операнда. Например, нека са дадени

следните три дефиниции:
int i;

int *ip;


int ia[ 4 ];
за които са валидни следните оператори:
ip * &i;

i = ia[ 0 ] + 1;

ia[ *ip ] = 1024;

*ip = i * 2 + ia[ i ];


Резултатът на оператора за присвояване е стойността

на израза от дясната му страна. Типът на резултата е типа на

левия операнд.

Предвидено е операторите за присвояване да могат да

бъдат обединявани като всеки от операндите да бъде присвояван

на същия тип данни. Например,


main()

{

int i, j;



i = j = 0; // ok: each assigned 0

// ...


}
на i и j се дава стойност 0. Редът за пресмятане е от ляво на

дясно.


Обединяването на операторите за присвояване позволява

един по-компактен запис, като е при написания отново

конструктор на IntArray, например ( вж. Раздел 1.8 (стр. 52)

за неговата оригинална реализация):

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

68.
IntArray::IntArray( int sz )

{

ia = new int[ size = sz ];



// ...

}
Използуването на този запис зависи от стила на

програмиста. Бъдете внимателни, обаче, защото тази компактност

би могла да доведе до известна неяснота.

Смесеният оператор за присвояване също предлага един

начин за компактно записване. Например,


int arraySum( int ia[], int sz )

{

int sum = 0;



for ( int i = 0; i < sz; ++i )

usm += ia[ i ];

return sum;

}
Обобщената синтактична форма на смесения оператор за

присвояване има вида:
a op= b;
където op може да бъде един от следните десет оператора: +=,

-=, *=, /=, %=, <<=, >>=, &=, ^= и |=. Всеки смесен оператор е

еквивалентен на следното:
a = a op b;
Дългият запис на оператора за събиране, например, има вида:
sum = sum + ia[ i ];
Упражнение 2-1. Следният запис е правилен. Защо? Как

бихте могли да го промените?


main()

{

int i = j = k = 0;



}
Упражнение 2-2. Следният запис също е правилен. Защо?

Как бихте могли да го промените?


main()

{

int i, j;



int *ip;
i = j = 0;

}

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

69.
2.5. Оператори за увеличаване и намаляване с 1
Операторите за увеличаване ("++") и намаляване ("--")

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

изваждане на 1 от променлива. За двата оператора може да се

мисли като за оператори за присвояване; операндите трябва да

бъдат стойности за запис (lvalue). Всеки оператор има както

префксна, така и постфиксна форма. Например,


main()

{

int c;



++c; // prefix increment

c++; // postfix increment

}
Нека да илюстрираме използуването на пост- и

префиксната форма на операторите като дефинираме стеков клас

IntStack, който поддържа цели стойности. За IntStack са

дефинирани две операции:


1. push(int v), която поставя стойността на v на върха

на стека.

2. pop(), която връща стойността, записана на върха на

стека.


Освен това възможни са два особени случая:
1. препълване: опит за запис в стека, когато е пълен.
2. изпразване: опит за четене от стека, когато той е

празен.
IntStack ще бъде представен чрез един масив от цели

числа. Следователно, необходими са следните членове

(елементи):


int size;

int *ia;


int top;
top винаги ще съдържа индекса на елемента на върха на стека.

Това означава, че за празен стек top има стойност -1. Стекът е

пълен когато top има стойност size-1.

Написването на функциите isEmpty() и isFull(), които

връщат стойности истина или лъжа е елементарно:
typedef int Boolean;

extern const int BOS; // Bottom of Stack


Boolean IntStack::isEmpty() { return (top == BOS);}

Boolean IntStack::isFull() { return (top == size-1);}


Реализацията на push() илюстрира префиксната форма на

оператора за увеличение с 1. Да припомним, че top сочи текущия

връх на стека. Новият елемент трябва да бъде поставен в

елемент с едно по-голям от текущата стойност на top:

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

70.
void IntStack::push( int v )

{ // add v to the top of stack

if ( isFull() )

grow(); // enlarge stack


ia[ ++top ] = v;

}
Префиксната форма на ++ увеличава стойността на top преди тази

стойност да бъде използувана като индекс в ia. Това е

компактния запис на следните два оператора:


top = top + 1;

ia[ top ] = v;


grow() увеличава размера на стека с някакъв

предварително зададен брой елементи. Раздел 4.1 (стр. 137)

съдържа реализацията на grow().

Реализирането на pop() илюстрира постфиксната форма на

оператора за намаляване с 1. Припомняме отново, че top сочи

текущия връх на стека. След като бъде извлечен елемент от

стека, top трябва да бъде намален с 1.
int IntStack::pop()

{ // return the topmost element of stack

if( isEmpty() )

// report error and exit()

;
return ia[ top-- ];

}
Постфиксната форма на -- намалява стойността на top след като

тази стойност е била използувана като индекс на ia. Това е

компактен запис на следната двойка оператори:


ia[ top ];

top = top - 1;


След намаляването на top се изпълнява оператора return.

Това, което остава за цялостното дефиниране на

IntStack, е управлението на стека му. Понеже вътрешното

представяне на нашия стеков клас е същото както и на

дефинирания по-рано клас IntArray, ние ще използуваме

реализацията на IntArray като базова за IntStack (вж. Раздел

1.8 (стр. 51) за дефиницията на класа IntArray):

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

71.
#include "IntArray.h"

typedef int Boolean;

const int BOS = -1; // Bottom of Stack
class IntStack : private IntArray

public:


IntStack( int sz = ARRAY_SIZE )

: IntArray( sz ), top( BOS ) {}

Boolean isEmpty();

Boolean isFull();

void push( int );

int pop();

void grow() {}

protected:

int top;

}
При описанието на IntArrayRC, IntArray беше деклариран

като базов клас public. При описанието на IntStack, IntArray е

деклариран като базов клас private. Първата разлика между

базовите класове private и public е, че на базов клас private

не могат да бъдат присвоявани обекти от произлезлия клас.

Например,

extern swap( IntArray&, int, int );

IntArray ia;

IntArrayRC ia1;

IntStack is;
ia = ia1; // ok: public base class

ia = is; // error: private base class


swap( ia1, 4, 7 ); // ok: public base class

swap( is, 4, 7 ); // error: private base class


Втората разлика между базовите класове private и

public е, че наследените членове на базовия клас private се

разглеждат като собствени членове на произлезлия клас. Това

означава, че интерфейсът public на класа IntArray не е

достъпен през класовите обекти на IntStack. Например,
int bottom = is[0]; // error: operator[] private
Наследствеността при класовете може да бъде

използувана по два основни начина:


1. Като метод за създаване на подтип на съществуващ клас,

както е при дефинирането на IntArrayRC, произлязъл от класа

IntArray, описан в глава 1.
2. Като метод за повторно използуване на дадена реализация с

цел създаване на нов тип клас, както е при дефинирането на

IntStack, произлязъъл от класа IntArray.

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

72.
IntStack не е подтип на IntArray. Той не разделя

операциите, които могат да бъдат прилагани към обектите на

класа IntArray. Чрез определянето на IntArray като базов клас

private на IntStack не се допуска случайното прилагане на

операциите на IntArray върху обекти на класа IntStack. Освен

това, ако един обект от класа IntStack случайно бъде присвоен

на обект от класа IntArray целостта на стека може сериозно да

бъде повредена. Предотвратяването на такива инциденти е

втората причина за дефинирането на IntArray като базов клас

private на IntStack. Един базов клас private не може да бъде

присвояван на класов обект от произлезлия му клас.

Ето един пример за обекти от клас IntStack:


#include

#include "IntStack.h"


IntStack myStaack;
const DEPTH = 7;

main()


{

for ( int i = 0; i < DEPTH; ++i )

myStack.push(i);
for ( i = 0; i < DEPTH; ++i )

cout << myStack.pop() << "\t";


cout << "\n";

}
Когато тази програма бъде компилирана и изпълнена, тя връща

следния резултат:
6 5 4 3 2 1 0
Упражнение 2-3. Как мислите, защо С++ не е наречен

++С?

2.6. Операторът sizeof
Операторът sizeof връща размера в байтове на израз или

типов спецификатор. Той може да бъде използуван в една от

следните две форми:
sizeof (type-specifier);

sizeof expr;


Ето пример за използуването на двете форми:
int ia[] = { 0, 1, 2 };

const sz = sizeof ( ia ) / sizeof ( int );

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

73.
Следващата програма илюстрира използуването на оператора

sizeof за голямо разнообразие от типови спецификатори.


#include

#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


Каталог: files -> tu files
tu files -> Увод в компютърната графика
tu files -> Xii. Защита и безопасност на ос
tu files -> Електрически апарати
tu files -> Средства за описание на синтаксиса
tu files -> Stratofortress
tu files -> Писане на скриптове за bash шел : версия 2
tu files -> 6Технологии на компютърната графика 1Модели на изображението
tu files -> Z=f(x), където x- входни данни; z
tu files -> Body name библиотека global Matrix imports (достъп по име) … var m[N, N] := … end decl., proc … resource f final code imports node, Matrix end name var x: node node; if x … Matrix m[3,4] :=: … end


Сподели с приятели:
1   2   3   4   5   6   7   8   9   ...   19




©obuch.info 2024
отнасят до администрацията

    Начална страница