<< Пред.           стр. 7 (из 121)           След. >>

Список литературы по разделу

 3el 1.0E-3E 2. 1.0L
 
 Слова true и false являются литералами типа bool.
 Представимые литеральные символьные константы записываются как символы в одинарных кавычках. Например:
 
 'a' '2' ',' ' ' (пробел)
 
 Специальные символы (табуляция, возврат каретки) записываются как escape-последовательности . Определены следующие такие последовательности (они начинаются с символа обратной косой черты):
 
 новая строка \n
 горизонтальная табуляция \t
 забой \b
 вертикальная табуляция \v
 возврат каретки \r
 прогон листа \f
 звонок \a
 обратная косая черта \\
 вопрос \?
 одиночная кавычка \'
 двойная кавычка \"
 
 escape-последовательность общего вида имеет форму \ooo, где ooo – от одной до трех восьмеричных цифр. Это число является кодом символа. Используя ASCII-код, мы можем написать следующие литералы:
 
 \7 (звонок) \14 (новая строка)
 \0 (null) \062 ('2')
 
 Символьный литерал может иметь префикс L (например, L'a'), что означает специальный тип wchar_t – двухбайтовый символьный тип, который применяется для хранения символов национальных алфавитов, если они не могут быть представлены обычным типом char, как, например, китайские или японские буквы.
 Строковый литерал – строка символов, заключенная в двойные кавычки. Такой литерал может занимать и несколько строк, в этом случае в конце строки ставится обратная косая черта. Специальные символы могут быть представлены своими escape-последовательностями. Вот примеры строковых литералов:
 
 "" (пустая строка)
 "a"
 "\nCC\toptions\tfile.[cC]\n"
 "a multi-line \
 string literal signals its \
 continuation with a backslash"
 
 Фактически строковый литерал представляет собой массив символьных констант, где по соглашению языков С и С++ последним элементом всегда является специальный символ с кодом 0 (\0).
 Литерал 'A' задает единственный символ А, а строковый литерал "А" – массив из двух элементов: 'А' и \0 (пустого символа).
 Раз существует тип wchar_t, существуют и литералы этого типа, обозначаемые, как и в случае с отдельными символами, префиксом L:
 L"a wide string literal"
 Строковый литерал типа wchar_t – это массив символов того же типа, завершенный нулем.
 Если в тесте программы идут подряд два или несколько строковых литералов (типа char или wchar_t), компилятор соединяет их в одну строку. Например, следующий текст
 "two" "some"
 породит массив из восьми символов – twosome и завершающий нулевой символ. Результат конкатенации строк разного типа не определен. Если написать:
 // this is not a good idea
 "two" L"some"
 то на каком-то компьютере результатом будет некоторая осмысленная строка, а на другом может оказаться нечто совсем иное. Программы, использующие особенности реализации того или иного компилятора или операционной системы, являются непереносимыми. Мы крайне не рекомендуем пользоваться такими конструкциями.
 Упражнение 3.1
 Объясните разницу в определениях следующих литералов:
 (a) 'a', L'a', "a", L"a"
 (b) 10, 10u, 10L, 10uL, 012, 0*C
 (c) 3.14, 3.14f, 3.14L
 Упражнение 3.2
 Какие ошибки допущены в приведенных ниже примерах?
 (a) "Who goes with F\144rgus?\014"
 (b) 3.14e1L
 (c) "two" L"some"
 (d) 1024f
 (e) 3.14UL
 (f) "multiple line
  comment"
 3.2. Переменные
 Представим себе, что мы решаем задачу возведения 2 в степень 10. Пишем:
 #include
 
 int main() {
  // a first solution
  cout << "2 raised to the power of 10: ";
  cout << 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
  cout << endl;
  return 0;
 }
 Задача решена, хотя нам и пришлось неоднократно проверять, действительно ли 10 раз повторяется литерал 2. Мы не ошиблись в написании этой длинной последовательности двоек, и программа выдала правильный результат – 1024.
 Но теперь нас попросили возвести 2 в 17 степень, а потом в 23. Чрезвычайно неудобно каждый раз модифицировать текст программы! И, что еще хуже, очень просто ошибиться, написав лишнюю двойку или пропустив ее... А что делать, если нужно напечатать таблицу степеней двойки от 0 до 15? 16 раз повторить две строки, имеющие общий вид:
 cout << "2 в степени X\t";
 cout << 2 * ... * 2;
 где Х последовательно увеличивается на 1, а вместо отточия подставляется нужное число литералов?
 Да, мы справились с задачей. Заказчик вряд ли будет вникать в детали, удовлетворившись полученным результатом. В реальной жизни такой подход достаточно часто срабатывает, более того, бывает оправдан: задача решена далеко не самым изящным способом, зато в желаемый срок. Искать более красивый и грамотный вариант может оказаться непрактичной тратой времени.
 В данном случае “метод грубой силы” дает правильный ответ, но как же неприятно и скучно решать задачу подобным образом! Мы точно знаем, какие шаги нужно сделать, но сами эти шаги просты и однообразны.
 Привлечение более сложных механизмов для той же задачи, как правило, значительно увеличивает время подготовительного этапа. Кроме того, чем более сложные механизмы применяются, тем больше вероятность ошибок. Но даже несмотря на неизбежные ошибки и неверные ходы, применение “высоких технологий” может принести выигрыш в скорости разработки, не говоря уже о том, что эти технологии значительно расширяют наши возможности. И – что интересно! – сам процесс решения может стать привлекательным.
 Вернемся к нашему примеру и попробуем “технологически усовершенствовать” его реализацию. Мы можем воспользоваться именованным объектом для хранения значения степени, в которую нужно возвести наше число. Кроме того, вместо повторяющейся последовательности литералов применим оператор цикла. Вот как это будет выглядеть:
 #include
 
 int main()
 {
 // objects of type int
  int value = 2;
  int pow = 10;
 
  cout << value << " в степени "
  << pow << ": \t";
 
  int res = 1;
 
  // оператор цикла:
  // повторить вычисление res
  // до тех пор пока cnt не станет больше pow
  for ( int cnt=1; cnt <= pow; ++cnt )
  res = res * value;
 
  cout << res << endl;
 }
 value, pow, res и cnt – это переменные, которые позволяют хранить, модифицировать и извлекать значения. Оператор цикла for повторяет строку вычисления результата pow раз.
 Несомненно, мы создали гораздо более гибкую программу. Однако это все еще не функция. Чтобы получить настоящую функцию, которую можно использовать в любой программе для вычисления степени числа, нужно выделить общую часть вычислений, а конкретные значения задать параметрами.
 int pow( int val, int exp )
 {
  for ( int res = 1; exp > 0; --exp )
  res = res * val;
  return res;
 }
 Теперь получить любую степень нужного числа не составит никакого труда. Вот как реализуется последняя наша задача – напечатать таблицу степеней двойки от 0 до 15:
 #include
 extern int pow(int,int);
 int main()
 {
  int val = 2;
  int exp = 15;
 
  cout << "Степени 2\n";
  for ( int cnt=0; cnt <= exp; ++cnt )
  cout << cnt << ": "
  << pow( val, cnt ) << endl;
 
  return 0;
 }
 Конечно, наша функция pow() все еще недостаточно обобщена и недостаточно надежна. Она не может оперировать вещественными числами, неправильно возводит числа в отрицательную степень – всегда возвращает 1. Результат возведения большого числа в большую степень может не поместиться в переменную типа int, и тогда будет возвращено некоторое случайное неправильное значение. Видите, как непросто, оказывается, писать функции, рассчитанные на широкое применение? Гораздо сложнее, чем реализовать конкретный алгоритм, направленный на решение конкретной задачи.
 3.2.1. Что такое переменная
 Переменная, или объект – это именованная область памяти, к которой мы имеем доступ из программы; туда можно помещать значения и затем извлекать их. Каждая переменная С++ имеет определенный тип, который характеризует размер и расположение этой области памяти, диапазон значений, которые она может хранить, и набор операций, применимых к этой переменной. Вот пример определения пяти объектов разных типов:
 int student_count;
 double salary;
 bool on_loan;
 strins street_address;
 char delimiter;
 Переменная, как и литерал, имеет определенный тип и хранит свое значение в некоторой области памяти. Адресуемость – вот чего не хватает литералу. С переменной ассоциируются две величины:
 собственно значение, или r-значение (от read value – значение для чтения), которое хранится в этой области памяти и присуще как переменной, так и литералу;
 значение адреса области памяти, ассоциированной с переменной, или l-значение (от location value – значение местоположения) – место, где хранится r-значение; присуще только объекту.
 В выражении
 ch = ch - '0';
 переменная ch находится и слева и справа от символа операции присваивания. Справа расположено значение для чтения (ch и символьный литерал '0'): ассоциированные с переменной данные считываются из соответствующей области памяти. Слева – значение местоположения: в область памяти, соотнесенную с переменной ch, помещается результат вычитания. В общем случае левый операнд операции присваивания должен быть l-значением. Мы не можем написать следующие выражения:
 // ошибки компиляции: значения слева не являются l-значениями
 
 // ошибка: литерал - не l-значение
 0 = 1;
 
 // ошибка: арифметическое выражение - не l-значение
 salary + salary * 0.10 = new_salary;
 Оператор определения переменной выделяет для нее память. Поскольку объект имеет только одну ассоциированную с ним область памяти, такой оператор может встретиться в программе только один раз. Если же переменная, определенная в одном исходном файле, должна быть использована в другом, появляются проблемы. Например:
 // файл module0.C
 // определяет объект fileName
 string fileName;
 // ... присвоить fileName значение
 
 // файл module1.C
 // использует объект fileName
 
 // увы, не компилируется:
 // fileName не определен в module1.C
 ifstream input_file( fileName );
 С++ требует, чтобы объект был известен до первого обращения к нему. Это вызвано необходимостью гарантировать правильность использования объекта в соответствии с его типом. В нашем примере модуль module1.C вызовет ошибку компиляции, поскольку переменная fileName не определена в нем. Чтобы избежать этой ошибки, мы должны сообщить компилятору об уже определенной переменной fileName. Это делается с помощью инструкции объявления переменной:
 // файл module1.C
 // использует объект fileName
 
 // fileName объявляется, то есть программа получает
 // информацию об этом объекте без вторичного его определения
 extern string fileName;
 
 ifstream input_file( fileName )
 Объявление переменной сообщает компилятору, что объект с данным именем, имеющий данный тип, определен где-то в программе. Память под переменную при ее объявлении не отводится. (Ключевое слово extern рассматривается в разделе 8.2.)
 Программа может содержать сколько угодно объявлений одной и той же переменной, но определить ее можно только один раз. Такие объявления удобно помещать в заголовочные файлы, включая их в те модули, которые этого требуют. Так мы можем хранить информацию об объектах в одном месте и обеспечить удобство ее модификации в случае надобности. (Более подробно о заголовочных файлах мы поговорим в разделе 8.2.)
 3.2.2. Имя переменной
 Имя переменной, или идентификатор, может состоять из латинских букв, цифр и символа подчеркивания. Прописные и строчные буквы в именах различаются. Язык С++ не ограничивает длину идентификатора, однако пользоваться слишком длинными именами типа gosh_this_is_an_impossibly_name_to_type неудобно.
 Некоторые слова являются ключевыми в С++ и не могут быть использованы в качестве идентификаторов; в таблице 3.1 приведен их полный список.
 
 Таблица 3.1. Ключевые слова C++
 
 asm auto bool break case
 catch char class const const_cast
 continue default delete do double
 dynamic_cast else enum explicit export
 extern false float for friend
 goto if inline int long
 mutable namespace new operator private
 protected public register reinterpret_cast return
 short signed sizeof static static_cast
 struct switch template this throw
 true try typedef typeid typename
 union unsigned using virtual void
 volatile wchar_t while
 
 Чтобы текст вашей программы был более понятным, мы рекомендуем придерживаться общепринятых соглашений об именах объектов:
 имя переменной обычно пишется строчными буквами, например index (для сравнения: Index – это имя типа, а INDEX – константа, определенная с помощью директивы препроцессора #define);
 идентификатор должен нести какой-либо смысл, поясняя назначение объекта в программе, например: birth_date или salary;
 если такое имя состоит из нескольких слов, как, например, birth_date, то принято либо разделять слова символом подчеркивания (birth_date), либо писать каждое следующее слово с большой буквы (birthDate). Замечено, что программисты, привыкшие к ОбъектноОриентированномуПодходу предпочитают выделять слова заглавными буквами, в то время как те_кто_много_писал_на_С используют символ подчеркивания. Какой из двух способов лучше – вопрос вкуса.
 3.2.3. Определение объекта
 В самом простом случае оператор определения объекта состоит из спецификатора типа и имени объекта и заканчивается точкой с запятой. Например:
 double salary;
 double wage;
 int month;
 int day;
 int year;
 unsigned long distance;
 В одном операторе можно определить несколько объектов одного типа. В этом случае их имена перечисляются через запятую:
 double salary, wage;
 int month,
  day, year;
 unsigned long distance;
 Простое определение переменной не задает ее начального значения. Если объект определен как глобальный, спецификация С++ гарантирует, что он будет инициализирован нулевым значением. Если же переменная локальная либо динамически размещаемая (с помощью оператора new), ее начальное значение не определено, то есть она может содержать некоторое случайное значение.
 Использование подобных переменных – очень распространенная ошибка, которую к тому же трудно обнаружить. Рекомендуется явно указывать начальное значение объекта, по крайней мере в тех случаях, когда неизвестно, может ли объект инициализировать сам себя. Механизм классов вводит понятие конструктора по умолчанию, который служит для присвоения значений по умолчанию. (Мы уже сказали об этом в разделе 2.3. Разговор о конструкторах по умолчанию будет продолжен немного позже, в разделах 3.11 и 3.15, где мы будем разбирать классы string и complex из стандартной библиотеки.)
 int main() {
  // неинициализированный локальный объект
  int ival;
 
  // объект типа string инициализирован
  // конструктором по умолчанию
  string project;
 
  // ...
 }
 Начальное значение может быть задано прямо в операторе определения переменной. В С++ допустимы две формы инициализации переменной – явная, с использованием оператора присваивания:
 int ival = 1024;
 string project = "Fantasia 2000";
 и неявная, с заданием начального значения в скобках:
 int ival( 1024 );
 string project( "Fantasia 2000" );
 Оба варианта эквивалентны и задают начальные значения для целой переменной ival как 1024 и для строки project как "Fantasia 2000".
 Явную инициализацию можно применять и при определении переменных списком:
 double salary = 9999.99, wage = salary + 0.01;
 int month = 08;
  day = 07, year = 1955;
 Переменная становится видимой (и допустимой в программе) сразу после ее определения, поэтому мы могли проинициализировать переменную wage суммой только что определенной переменной salary с некоторой константой. Таким образом, определение:
 // корректно, но бессмысленно
 int bizarre = bizarre;
 является синтаксически допустимым, хотя и бессмысленным.
 Встроенные типы данных имеют специальный синтаксис для задания нулевого значения:
 // ival получает значение 0, а dval - 0.0
 int ival = int();
 double dval = double();
 В следующем определении:
 // int() применяется к каждому из 10 элементов
 vector< int > ivec( 10 );
 к каждому из десяти элементов вектора применяется инициализация с помощью int(). (Мы уже говорили о классе vector в разделе 2.8. Более подробно об этом см. в разделе 3.10 и главе 6.)
 Переменная может быть инициализирована выражением любой сложности, включая вызовы функций. Например:
 #include
 #include
 
 double price = 109.99, discount = 0.16;
 double sale_price( price * discount );
 string pet( "wrinkles" );
 
 extern int get_value();
 int val = get_value();
 
 unsigned abs_val = abs( val );
 abs() – стандартная функция, возвращающая абсолютное значение параметра. get_value() – некоторая пользовательская функция, возвращающая целое значение.
 Упражнение 3.3
 Какие из приведенных ниже определений переменных содержат синтаксические ошибки?
 (a) int car = 1024, auto = 2048;
 (b) int ival = ival;
 (c) int ival( int() );
 (d) double salary = wage = 9999.99;
 (e) cin >> int input_value;
 Упражнение 3.4
 Объясните разницу между l-значением и r-значением. Приведите примеры.
 Упражнение 3.5
 Найдите отличия в использовании переменных name и student в первой и второй строчках каждого примера:
 (a) extern string name;
  string name( "exercise 3.5a" );
 
 (b) extern vector students;
  vector students;
 Упражнение 3.6
 Какие имена объектов недопустимы в С++? Измените их так, чтобы они стали синтаксически правильными:
 (a) int double = 3.14159; (b) vector< int > _;
 (c) string namespase; (d) string catch-22;
 (e) char 1_or_2 = '1'; (f) float Float = 3.14f;
 Упражнение 3.7
 В чем разница между следующими глобальными и локальными определениями переменных?
 string global_class;
 int global_int;
 
 int main() {
  int local_int;
  string local_class;
 
  // ...
 }
 3.3. Указатели
 Указатели и динамическое выделение памяти были вкратце представлены в разделе 2.2. Указатель – это объект, содержащий адрес другого объекта и позволяющий косвенно манипулировать этим объектом. Обычно указатели используются для работы с динамически созданными объектами, для построения связанных структур данных, таких, как связанные списки и иерархические деревья, и для передачи в функции больших объектов – массивов и объектов классов – в качестве параметров.
 Каждый указатель ассоциируется с некоторым типом данных, причем их внутреннее представление не зависит от внутреннего типа: и размер памяти, занимаемый объектом типа указатель, и диапазон значений у них одинаков. Разница состоит в том, как компилятор воспринимает адресуемый объект. Указатели на разные типы могут иметь одно и то же значение, но область памяти, где размещаются соответствующие типы, может быть различной:
 указатель на int, содержащий значение адреса 1000, направлен на область памяти 1000-1003 (в 32-битной системе);
 указатель на double, содержащий значение адреса 1000, направлен на область памяти 1000-1007 (в 32-битной системе).
 Вот несколько примеров:
 int *ip1, *ip2;
 complex *cp;
 string *pstring;
 vector *pvec;
 double *dp;
 Указатель обозначается звездочкой перед именем. В определении переменных списком звездочка должна стоять перед каждым указателем (см. выше: ip1 и ip2). В примере ниже lp – указатель на объект типа long, а lp2 – объект типа long:
 long *lp, lp2;
 В следующем случае fp интерпретируется как объект типа float, а fp2 – указатель на него:

<< Пред.           стр. 7 (из 121)           След. >>

Список литературы по разделу