Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Паппас К., Мюррей У. - Visual C++ 6. Руководство разработчика - 2000

.pdf
Скачиваний:
288
Добавлен:
13.08.2013
Размер:
4.96 Mб
Скачать

//переменная ivalue становится доступной до того,

//как будет инициализирована

void function_a(void); void function_b(void); int main()

ivalue++; // ссылка на объявленную выше переменную cout << ivalue << "\n"; // выводит значение 11 function_a();

return(0);

int ivalue =10; void function_a(void) ivalue++; cout << ivalue << "\n"; function b () ; //инициализация переменной ivalue

//ссылка на объявленную выше переменную //выводит значение 12

//------------------------------------------------

//Файл В

#include <iostream.h> extern int ivalue ; void function_b (void) f

ivalue++;

cout << ivalue << "\n";

//ссылка на переменную ivalue ,

//описанную в файле А

//выводит значение 13

Объявление переменных на внутреннем уровне

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

Спецификатор register указывает компилятору, что данную переменную необходимо сохранить

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

иупрощается программный код. Область видимости у регистровых переменных такая же, как и у автоматических. В случае отсутствия свободных регистров переменной присваивается класс auto и она сохраняется в памяти.

Стандарт ANSI С не позволяет запрашивать адрес переменной, сохраненной в регистре. Но это ограничение не распространяется на язык C++. Просто при обнаружении оператора взятия адреса (&), примененного к регистровой переменной, компилятор сохранит переменную в ячейке памяти и возвратит ее адрес.

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

Спецификатор extern на уровне блока используется для создания ссылки на переменную с таким же именем, объявленную на внешнем уровне в любом исходном файле программы. Если в блоке определяется переменная с тем же именем, но без спецификатора extern, то внешняя переменная становится недоступной в данном блоке. В следующем примере показаны все рассмотренные выше ситуации.

#include <iostream.h> int ivalue1 = 1;

void function_a (void) ; void main ( )

{// ссылка на переменную ivalue1, описанную выше extern int ivalue1;

// создание статической переменной, видимой только внутри функции main(),

//а также инициализация ее нулевым значением static int ivalue2;

81

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

//и сохранением в регистре процессора (если возможно) register int rvalue = 0;

//создание автоматической переменной с присвоением ей нулевого значения intint_value3 = 0;

//вывод значений 1, 0, 0, 0:

cout << ivalue1 << "\n" << rvalue << "\n"

<< ivalue2 << "\n" << int_value3 << "\n"; function_a () ; void function_a (void) {

//сохранение адреса глобальной переменной ivalue1 static int *pivalue1 = &ivalue1;

//создание новой, локальной переменной ivalue1;

//тем самым глобальная переменная ivalue1 становится недоступной int ivalue1 = 32;

//создание новой статической переменной ivalue2,

//видимой только внутри функции function_a () static int ivalue2 = 2; ivalue2 += 2;

//вывод значений 32, 4 и 1: cout << ivalue1 << "\n" << ivalue2 << "\n" << *pivalue1 << "\n";

cout << ivalue1 << "\n" << ivalue2 << "\n" << *pivalue1 << "\n";

}

Поскольку переменная ivalue1 переопределяется внутри функции function_a (), доступ к глобальной переменной ivalue1 блокируется. Тем не менее, с помощью указателя pivalue1 можно получить доступ к глобальной переменной, сославшись на нее по адресу.

Правила определения области видимости переменных

Чтобы определить, в какой части программы будет доступна та или иная переменная, нужно воспользоваться следующими правилами. Областью видимости переменной может быть программный блок, функция, файл и вся программа. Переменные, объявленные внутри блока или функции, доступны только в их пределах. Переменные, объявленные на уровне файла, т.е. вне любой функции, и имеющие спецификатор static, доступны во всем файле, начиная со строки объявления. Переменные, объявленные в одном из файлов программы на внешнем уровне без спецификатора static или со спецификатором extern, видимы в пределах всей программы.

Объявление функций

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

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

Функцию, объявленную как внешняя, можно использовать во всех файлах программы (если только в одном из них она не переопределена как статическая). Если при объявлении функции не указан класс памяти, то по умолчанию она считается внешней.

Операторы

В языках C/C++ есть ряд операторов, которых вы не встретите в других языках программирования. В их числе побитовые операторы, операторы инкрементирования и декрементирования, условный оператор, оператор запятая, а также операторы комбинированного присваивания.

Побитовые операторы

82

Побитовые операторы обращаются с переменными как с наборами битов, а не как с числами. Эти операторы используются в тех случаях, когда необходимо получить доступ к отдельным битам данных, например при выводе графических изображений на экран. Побитовые операторы могут выполнять действия только над целочисленными значениями. В отличие от логических операторов, с их помощью сравниваются нe два числа целиком, а отдельные их биты. Существует три основных побитовых оператора: И (&), ИЛИ (|) и исключающее ИЛИ (^). Сюда можно также отнести унарный эператор побитового отрицания (~), который инвертирует значения битов числа.

Побитовое И

Оператор & записывает в бит результата единицу только в том случае, если оба cравниваемых бита равны 1, как показано в следующей таблице:

Бит0 Бит1 Результат

0 0 0

0 1 0

1 0 0

1 1 1

Этот оператор часто используют для маскирования отдельных битов числа.

Побитовое ИЛИ

Оператор | записывает в бит результата единицу в том случае, если хотя бы один лз сравниваемых битов равен 1, как показано в следующей таблице:

Бит 0 Бит 1 Результат

0 0 0

0 1 1

1 0 1

1 1 1

Этот оператор часто используют для установки отдельных битов числа.

Побитовое исключающее ИЛИ

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

Бит 0 Бит 1 Результат

0 0 0

0 1 1

1 0 1

1 1 0

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

0xF1& 0x35

результат 0x31 (шестнадцатеричное)

 

 

 

0361

& 0065

результат 061 (восьмеричное)

 

 

11110011 & 00110101

результат 00110001 (двоичное)

 

 

 

0xF1 |

0x35

результат 0xF5 (шестнадцатеричное)

 

 

 

 

0361

|

0065

результат 0365 (восьмеричное)

 

 

 

 

83

 

 

 

11110011 | 00110101

результат 11110111

(двоичное)

 

 

 

 

0xF1 ^ 0x35

результат 0хС4 (шестнадцатеричное)

 

 

0361 ^ 0065

результат 0304 (восьмеричное)

 

 

 

11110011 ^ 00110101

результат 11000110

(двоичное)

 

 

~0xF1

результат 0xFF0E (шестнадцатеричное)

 

 

~0361

результат 0177416 (восьмеричное)

 

 

 

~11110011

результат 11111111

00001100 (двоичное)

 

 

 

В последнем примере результат состоит из двух байтов, так как в процессе операции побитового отрицания осуществляется повышение целочисленности операнда — его тип преобразуется к типу с большей размерностью. Если операнд беззнаковый, то результат получается вычитанием его значения из самого большого числа повышенного типа. Если операнд знаковый, то результат вычисляется посредством приведения операнда повышенного типа к беззнаковому типу, выполнения операции ~ и обратного приведения его к знаковому типу.

Операторы сдвига

Языки C/C++ содержат два оператора сдвига: сдвиг влево (<<) и сдвиг вправо (>>). Первый сдвигает битовое представление целочисленной переменной, указанной слева от оператора, влево на количество битов, указанное справа от оператора. При этом освобождающиеся младшие биты заполняются нулями, а соответствующее количество старших битов теряется.

Сдвиг беззнакового числа на одну позицию влево с заполнением младшего разряда нулем эквивалентен умножению числа на 2, как показано в следующем примере:

unsigned int valuel =.65; // младший байт: 0100 0001

valuel <<= 1;

//

младший байт: 1000

0010

cout << valuel;

//

будет выведено 130

 

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

unsigned int valuel = 10;//

младший байт: 0000 1010

valuel >>= 1;

// младший байт:

0000 0101

printf("%d",value1);

//

будет выведено

5

Инкрементирование и декрементирование

Операции увеличения или уменьшения значения переменной на 1 столь часто встречаются в программах, что разработчики языка С предусмотрели для этих целей специальные операторы инкрементирования (++) и декрементировант (--). Эти операторы можно применять только к переменным, но не константам.

Так, вместо следующей строки value1 + 1;

можно ввести строку value1++;

ИЛИ

++value1;

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

Операторы ++ и — находят широкое применение в цикле for:

sum = 0;

for(1=1; i <= 20; i++) sum = sum + i;

84

Цикл с декрементом будет выглядеть так:

sum = 0;

for(i = 20; 1 >= 1; i—) sum = sum + i;

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

Например, при постфиксном инкрементировании — i++ — сначала возвращается значение переменной, после чего оно увеличивается на единицу. С другой стороны, оператор префиксного инкрементирования — ++i — указывает, что сначала следует увеличить значение переменной, а затем возвратить его в качестве результата. Рассмотрим примеры. Предположим, имеются следующие переменные:

int i = 3, j, k = 0;

Теперь проанализируем, как изменятся значения переменных в следующих выражениях (предполагается, что они выполняются не последовательно, а по отдельности):

k

=

++i;

//

i = 4,

k=i++;

//

i = 4,

k =

=

--i;

//

i = 2,

k

i--;

//

i = 2,

i

=

j

= k—; //

i = 0,

k = 4 k = 3 k = 2

k

=

3

j

=

0, k = -1

Арифметические операторы

В языках C/C++ вы найдете все стандартные арифметические операторы, в частности операторы сложения (+), вычитания (-), умножения (*), деления (/) и деления по модулю (%). Первые четыре понятны и не требуют разъяснений. Возможно, имеет смысл остановится на операции деления по модулю:

int a=3,b=8,c=0,d;

2

d = b % a;

// результат:

d = a % b;

//

результат:

3

d = b % с;

//

результат:

сообщение об ошибке

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

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

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

valuel = 8 * (value2 = 5);

Вданном случае сначала переменной value2 будет присвоено значение 5, после чего это значение будет умножено на 8 и результат 40 будет записан в переменную value1.

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

valuel = value2 = value3 = 0;

Второй прием часто можно встретить в условных выражениях цикла while, как в следующем примере:

while ((с = getchar())

!= EOF)

{

 

.

.

85

.

}

Вначале переменной с присваивается значение, возвращаемое функцией getchar (}, после чего осуществляется проверка значения переменной на равенство константе eof. Цикл завершается при обнаружении конца файла. Использование круглых скобок необходимо из-за того, что оператор присваивания имеет меньший приоритет, чем подавляющее большинство других операторов, в частности оператор неравенства. Без круглых скобок данная строка будет воспринята следующим образом:

с = (getchar()

!= EOF)

То есть переменной с будет присваиваться значение 1 (true) всякий раз, когда функция getchar () возвращает значение, отличное от признака конца файла.

Комбинированные операторы присваивания

Набор операторов присваивания в языках C/C++ значительно богаче, чем в других языках программирования. Всевозможные комбинированные операторы присваивания позволяют предельно сжать программный код. В следующих строках показаны некоторые выражения присваивания, стандартные для большинства языков высокого уровня:

irow_index = irow_index + irow_increment; ddepth = ddepth - dl_fathom; fcalculate_tax = fcalculate_tax * 1.07; fyards = fya.rds / ifeet_convert;

Теперь посмотрим, как эти же выражения будут выглядеть в C/C++: irow_index += irow_increment;

ddepth -= dl_fathom; fcalculate_tax *= 1.07; fyards /= ifeet_convert;

Если вы внимательно рассмотрите эти примеры, то легко поймете синтаксис комбинированных операторов присваивания. Когда в левой и правой частях выражения встречается одна и та же переменная, вы можете удалить ссылку на нее из правой части, а соответствующий оператор объединить с оператором присваивания. Комбинированный оператор указывает, что над переменными из левой и правой частей выражения нужно выполнить операцию, указанную перед знаком равенства, а результат вновь присвоить переменной из левой части выражения. Все комбинированные операторы присваивания перечислены в нижней части табл. 5.4, которая приведена в конце главы.

Операторы сравнения и логические операторы

Операторы сравнения предназначены для проверки равенства или неравенства сравниваемых операндов. Все они возвращают true в случае установления истинности выражения и false в противном случае. Ниже показан список операторов сравнения, используемых в языках С и

C++:

Оператор Выполняемая проверка

==Равно (не путать с оператором присваивания)

! =

Не равно

>Больше

<Меньше

<=

Меньше или равно

 

 

>=

Больше или равно

Логические операторы И (&&),ИЛИ (| |) и НЕ (!)возвращают значение true или false в зависимости от логического отношения между их операндами. Так, оператор && возвращает true, когда истинны (не равны нулю) оба его аргумента. Оператор | | возвращает false только в том случае, если ложны (равны нулю) оба его аргумента. Оператор ! просто инвертирует значение своего операнда с false на true и наоборот.

Следующий пример программы на языке С демонстрирует применение перечисленных выше операторов сравнения и логических операторов.

/*

86

*oprs.c

*Эта программа на языке С демонстрирует применение

*операторов сравнения и логических операторов.

*/

#include <stdio.h> int main ()

{

float foperand1, foperand2;

printf("\nВведите значения переменных foperand1 и foperand2: "); scanf("%f%f", &foperand1, &foperand2);

printf("\n foperand1 > foperand2 =%d", (foperand1 > foperand2)); printf("\n foperand1 < foperand2 =%d", (foperand1 < foperand2)); printf("\n foperand1 => foperand2 =%d", (foperand1 >= foperand2)); printf("\n foperand1 <= foperand2 = %d",(foperand1 <= foperand2)); printf("\n foperand1 == foperand2 =%d", (foperand1 == foperand2)); printf("\n foperand1 != foperand2 =%d", (foperand1 != foperand2)); printf("\n foperand1 && foperand2 =%d", (foperand1 && foperand2)); return(0);

}

Иногда полученные результаты могут вас удивить. Следует помнить, особенно при сравнении значений типа float и double, что отличие даже в последней цифре после запятой будет воспринято как неравенство.

Ниже показана аналогичная программа на языке C++:

/*

*oprs.срр

*Эта программа на языке C++ демонстрирует применение

*операторов сравнения и логических операторов.. #include <iostream.h>

int main () {

float foperand1, foperand2;

cout << "\nВведите значения переменных foperand1 и foperand2: cin >> foperand1 >> foperand2;

cout << "foperand1 > foperand2 = " << (foperand1 > foperand2) << "\n"; cout << "foperand1 < foperand2 = " << (foperand1 < foperand2) << "\n";

cout << "foperand1 >= foperand2 = " << (foperand1 >= foperand2) << "\n";

cout << "foperand1 <= foperand2 = " << (foperand1 <= foperand2) << "\n";

cout << "foperand1 == foperand2 = " << (foperand1 == foperand2) << "\n";

cout << "foperand1 != foperand2 = " << (foperand1 != foperand2) << "\n";

cout << "foperand1 && foperand2 = " << (foperand1 S& foperand2) << "\n";

cout << "foperand1 | I Јoperand2 = " << (foperand1 I I foperand2) << "\n";

return(0);

}

Условный оператор

Оператор ?: часто применяется в различных условных конструкциях. Он имеет cледующий синтаксис:

Условие ? выражение1 : выражение2

Если условие равно true выполняется выражение1 в противном случае выражение2.

Оператор запятая

87

Позволяет последовательно выполнить два выражения записанных в одной строке. Синтаксис оператора следующий:

левое_выражение, правое_выражение

Приоритеты выполнения операторов

Последовательность выполнения различных действий в выражении определяется компилятором.Вследствие этого могут получаться неправильные результаты, если не был учтен порядок разбора выражения компилятором. Побочные эффекты также могут возникать при неправильном использовании простого и комбинированного операторов присваивания.Вот почему важно установление четких приоритетов в выполнении операторов и порядка вычисления операндов. К примеру, операция логического И (&&) всегда выполняется слева направо:

while ((с= getchar())

!= EOF)

&£, (с

!= '\n'))

Оператор & & предопределяет, что сначала будет выполнено выражение слева от него, т.е. переменной с будет присвоено значение, возвращаемое функцией getchar (), и только потом это значение проверяется на равенство символу новой строки.

В табл. 5.4 перечислены все операторы языков С и C++ в порядке уменьшения их приоритета (для компилятора MicrosoftVisual C++) и указывается направление вычисления операндов (ассоциативность): слева направо или справа налево.

Таблица 5.4. Операторы языков С и С++ (в порядке уменьшения приоритета)

Оператор

::

::

[]

()

()

.

->

++

--

new

delete

delete[]

++

--

*

&

+

-

!

~

sizeof

sizeof ()

typeid()

(тип данных)

const_cast

dynamic_cast

reinterpret_cast

static_cast

Операция

Ассоциативность

 

 

Расширение области видимости

 

 

Доступ к члену класса по имени класса

 

 

Доступ к элементу массива

Слева направо

 

 

Вызов функции

Слева направо

 

 

Приведение объекта к другому типу

 

 

Прямой доступ к члену класса (через объект)

Слева направо

 

 

Косвенный доступ к члену класса (через указатель

Слева направо

на объект)

 

 

 

Постфиксный инкремент

 

 

Постфиксный декремент

 

 

Динамическое создание объекта

 

 

Динамическое удаление объекта

 

 

Динамическое удаление массива

 

 

Префиксный инкремент

 

 

Префиксный декремент

 

 

Раскрытие указателя

 

 

Взятие адреса

 

 

Унарный плюс

 

 

Унарный минус

 

 

Логическое НЕ

 

 

Побитовое НЕ

 

 

Получение размерности выражения в байтах

 

 

Получение размерности типа данных в байтах

 

 

Получение информации о типе операнда

 

 

Приведение типа

Справа налево

 

 

Приведение типа

 

 

Приведение типа

 

 

Приведение типа

 

 

Приведение типа

 

 

88

. *

-> *

*

/

%

+

-

<<

>>

<

>

<=

>=

==

! =

&

^

|

&&

||

? :

=

*=

/=

%=

+=

-=

<<=

Прямой доступ к указателю на член класса (через

 

Слева направо

объект)

 

 

 

 

 

 

 

 

Косвенный доступ к указателю на член класса

 

Слева направо

(через указатель на объект)

 

 

 

 

 

 

 

 

Умножение

 

Слева направо

 

 

 

 

 

 

Деление

 

Слева направо

 

 

 

 

 

 

Деление по модулю

 

Слева направо

 

 

 

 

 

 

Сложение

 

Слева направо

 

 

 

 

 

 

Вычитание

 

Слева направо

 

 

 

 

 

 

Сдвиг влево

 

Слева направо

 

 

 

 

 

 

Сдвиг вправо

 

Слева направо

 

 

 

 

 

 

Меньше

 

Слева направо

 

 

 

 

 

 

Больше

 

Слева направо

 

 

 

 

 

 

Меньше или равно

 

Слева направо

 

 

 

 

 

 

Больше или равно

 

Слева направо

 

 

 

 

 

 

Равно

 

Слева направо

 

 

 

 

 

 

Не равно

 

Слева направо

 

 

 

 

 

 

Побитовое И

 

Слева направо

 

 

 

 

 

 

Побитовое исключающее ИЛИ

 

Слева направо

 

 

 

 

 

 

Побитовое ИЛИ

 

Слева направо

 

 

 

 

 

 

Логическое И

 

Слева направо

 

 

 

 

 

 

Логическое ИЛИ

 

Слева направо

 

 

 

 

 

 

Условное выражение

 

 

 

 

 

 

 

Простое присваивание

 

Справа налево

 

 

 

 

 

 

Присваивание с умножением

 

Справа налево

 

 

 

 

 

 

Присваивание с делением

 

Справа налево

 

 

 

 

 

 

Присваивание с делением по модулю

 

Справа налево

 

 

 

 

 

 

Присваивание со сложением

 

Справа налево

 

 

 

Присваивание с вычитанием

 

Справа налево

 

 

 

 

 

 

Присваивание со сдвигом влево

 

Справа налево

 

 

 

 

89

 

 

 

>>=

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

Справа налево

 

 

 

 

 

 

&=

Присваивание с побитовым И

Справа налево

 

 

 

 

 

 

| =

Присваивание с побитовым ИЛИ

Справа налево

 

 

 

 

 

 

^=

Присваивание с побитовым исключающим ИЛИ

Справа налево

 

 

 

 

 

 

,

Запятая

Слева направо

 

 

 

 

 

 

90

Соседние файлы в предмете Программирование на C++