Смекни!
smekni.com

Правила правой руки 17 Замечания для программистов на c 17 Глава 1 (стр. 10 из 43)

Имена параметров функции считаются описанными в самом внешнем

блоке функции, поэтому

f(int x)

{

int x; // ошибка

}

содержит ошибку, так как x определено дважды в одной и той же

области видимости.

2.1.2 Объекты и Адреса (Lvalue)

Можно назначать и использовать переменные, не имеющие имен, и

можно осуществлять присваивание выражениям странного вида

(например, *p[a+10]=7). Следовательно, есть потребность в имени

"нечто в памяти". Вот соответствующая цитата из справочного

руководства по C++:"Объект есть область памяти; lvalue есть

выражение, ссылающееся на объект"(#с.5). Слово "lvalue"

первоначально было придумано для значения "нечто, что может стоять

в левой части присваивания". Однако не всякий адрес можно

использовать в левой части присваивания; бывают адреса, ссылающиеся

на константу (см. #2.4).

2.1.3 Время Жизни

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

встречается его описание, и уничтожается, когда его имя выходит из

области видимости, Объекты с глобальными именами создаются и

инициализируются один раз (только) и "живут" до завершения

программы. Объекты, определенные описанием с ключевым словом

static, ведут себя так же. Например*:

____________________

* Команда #include была выброшена из примеров в этой

главе для экономии места. Она необходима в примерах, производящих

ввывод, чтобы они были полными. (прим. автора)

- стр 51 -

int a = 1;

void f()

{

int b = 1; // инициализируется при каждом вызове f()

static int c = 1; // инициализируется только один раз

cout << " a = " << a++

<< " b = " << b++

<< " c = " << c++ << "&bsol;n";

}

main()

{

while (a < 4) f();

}

производит вывод

a = 1 b = 1 c = 1

a = 2 b = 1 c = 2

a = 3 b = 1 c = 3

Не инициализированная явно статическая (static) переменная неявно

инициализируется нулем.

С помощью операций new и delete программист может также создавать

объекты, время жизни которых управляется непосредственно; см.

#3.2.4.

2.2 Имена

Имя (идентификатор) состоит из последовательности букв и цифр.

Первый символ должен быть буквой. Символ подчерка _ считается

буквой. C++ не налагает ограничений на число символов в имени, но

некоторые части реализации находятся вне ведения автора компилятора

(в частности, загрузчик), и они, к сожалению, такие ограничения

налагают. Некоторые среды выполнения также делают необходимым

расширить или ограничить набор символов, допустимых в

идентификаторе; расширения (например, при допущении в именах

символа $) порождают непереносимые программы. В качестве имени не

могут использоваться ключевые слова C++ (см. #с.2.3). Примеры имен:

hello this_is_a_most_unusially_long_name

DEFINED foO bAr u_name HorseSense

var0 var1 CLASS _class ___

Примеры последовательностей символов, которые не могут

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

012 a fool $sys class 3var

pay.due foo~bar .name if

Буквы в верхнем и нижнем регистрах считаются различными, поэтому

Count и count - различные имена, но вводить имена, лишь

незначительно отличающиеся друг от друга, нежелательно. Имена,

начинающиеся с подчерка, по традиции используются для специальных

- стр 52 -

средств среды выполнения, поэтому использовать такие имена в

прикладных программах нежелательно.

Во время чтения программы компилятор всегда ищет наиболее длинную

строку, составляющую имя, поэтому var10 - это одно имя, а не имя

var, за которым следует число 10; и elseif - одно имя, а не

ключевое слово else, после которого стоит ключевое слово if.

2.3 Типы

Каждое имя (идентификатор) в C++ программе имеет ассоциированный

с ним тип. Этот тип определяет, какие операции можно применять к

имени (то есть к объекту, на который оно ссылается), и как эти

операции интерпретируются. Например:

int error number;

float real(complex* p);

Поскольку error_number описано как int, его можно присваивать,

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

real может вызываться с адресом complex в качестве параметра. Можно

взять адрес любого из них. Некоторые имена, вроде int и complex,

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

спецификации другого имени. Единственные отличные от этого действия

над именем типа - это sizeof (для определения количества памяти,

которая требуется для хранения объекта типа) и new (для размещения

объекта типа в свободной памяти). Например:

main()

{

int* p = new int;

cout << "sizeof(int) = " << sizeof(int) "&bsol;n";

}

Имя типа можно также использовать для задания явного

преобразования одного типа в другой, например:

float f;

char* p;

//...

long ll = long(p); // преобразует p в long

int i = int(f); // преобразует f в int

2.3.1 Основные Типы

В C++ есть набор основных типов, которые соответствуют наиболее

общим основным единицам памяти компьютера и наиболее общим основным

способам их использования:

char

short int

int

long int

- стр 53 -

для представления целых различных размеров,

float

double

для представления чисел с плавающей точкой,

unsigned char

unsigned short int

unsigned int

unsigned long int

для представления беззнаковых целых, логических значений, битовых

массивов и т.п. Для большей компактности записи можно опускать int

в комбинациях из нескольких слов, что не меняет смысла; так, long

означает long int, и unsigned означает unsigned int. В общем, когда

в описании опущен тип, он предполагается int. Например:

const a = 1;

static x;

все определяют объект типа int.

Целый тип char наиболее удобен для хранения и обработки символов

на данном компьютере; обычно это 8-битовый байт. Размеры объектов

C++ выражаются в единицах размера char, поэтому по определению

sizeof(char)==1. В зависимости от аппаратного обеспечения char

является знаковым или беззнаковым целым. Тип unsigned char,

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

более переносимые программы, но из-за применения его вместо просто

char могут возникать значительные потери в эффективности.

Причина того, что предоставляется более чем один целый тип, более

чем один беззнаковый тип и более чем один тип с плавающей точкой, в

том, чтобы дать возможность программисту воспользоваться

характерными особенностями аппаратного обеспечения. На многих

машинах между различными разновидностями основных типов существуют

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

памяти и временах вычислений. Зная машину обычно легко, например,

выбрать подходящий тип для конкретной переменной. Написать

действительно переносимую программу нижнего уровня сложнее. Вот

все, что гарантируется относительно размеров основных типов:

1==sizeof(char)<=sizeof(short)<= sizeof(int)<=sizeof(long)

sizeof(float)<=sizeof(double)

Однако обычно разумно предполагать, что в char могут храниться

целые числа в диапазоне 0...127 (в нем всегда могут храниться

символы машинного набора символов), что short и int имеют не менее

16 бит, что int имеет размер, соответствующий целой арифметике, и

что long имеет по меньшей мере 24 бита. Предполагать что-либо

помимо этого рискованно, и даже эти эмпирические правила применимы

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

некоторых машин можно найти в #с.2.6.

Беззнаковые (unsigned) целые типы идеально подходят для

применений, в которых память рассматривается как массив битов.

Использование unsigned вместо int с тем, чтобы получить еще один

бит для представления положительных целых, почти никогда не

оказывается хорошей идеей. Попытки гарантировать то, что некоторые

значения положительны, посредством описания переменных как

- стр 54 -

unsigned, обычно срываются из-за правил неявного преобразования.

Например:

unsigned surprise = -1;

допустимо (но компилятор обязательно сделает предупреждение).

2.3.2 Неявное Преобразование Типа

Основные типы можно свободно сочетать в присваиваниях и

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

чтобы информация не терялась. Точные правила можно найти в #с.6.6.

Существуют случаи, в которых информация может теряться или

искажаться. Присваивание значения одного типа переменной другого

типа, представление которого содержит меньшее число бит, неизбежно

является источником неприятностей. Допустим, например, что

следующая часть программы выполняется на машине с двоичным

дополнительным предсталением целых и 8-битовыми символами:

int i1 = 256+255;

char ch = i1 // ch == 255

int i2 = ch; // i2 == ?

В присваивании ch=i1 теряется один бит (самый значимый!), и ch

будет содержать двоичный код "все-единицы" (т.е. 8 единиц); при

присваивании i2 это никак не может превратиться в 511! Но каким же

может быть значение i2? На DEC VAX, где char знаковые, ответ будет

-1; на AT&T 3B-20, где char беззнаковые, ответ будет 255. В C++ нет

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

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

вообще очень сложно, поэтому программист должен быть внимателен.

2.3.3 Производные Типы

Другие типы модно выводить из основных типов (и типов,

определенных пользователем) посредством операций описания:

* указатель

& ссылка

[] вектор

() функция

и механизма определения структур. Например:

int* a;

float v[10];

char* p[20]; // вектор из 20 указателей на символ

void f(int);

struct str { short length; char* p; };

Правила построения типов с помощью этих операций подробно

объясняются в #с.8.3-4. Основная идея состоит в том, что описание

производного типа отражает его использование. Например:

- стр 55 -

int v[10]; // описывает вектор

i = v[3]; // использует элемент вектора

int* p; // описывает указатель

i = *p; // использует указываемый объект

Вся сложность понимания записи производных типов проистекает из

того, что операции * и & префиксные, а операции [] () постфиксные,