straustrup2 (852740), страница 29
Текст из файла (страница 29)
Это означает, что после вызова rev(p) последний символ p станет первым и т.д.10. (*2) Что делает следующая функция?void send(register* to, register* from, register count)// Псевдоустройство. Все комментарии сознательно удалены{register n=(count+7)/8;switch (count%8) {case 0: do { *to++ = *from++;case 7:*to++ = *from++;case 6:*to++ = *from++;case 5:*to++ = *from++;case 4:*to++ = *from++;case 3:*to++ = *from++;case 2:*to++ = *from++;case 1:*to++ = *from++;} while (--n>0);}}Каков может быть смысл этой функции?11. (*2) Напишите функцию atoi(), которая имеет параметр - строку цифр и возвращаетсоответствующее ей целое.
Например, atoi("123") равно 123. Измените функцию atoi() так, чтобыона могла переводить в число последовательность цифр не только в десятичной, но и ввосьмеричной и шестнадцатеричной записи, принятой в С++. Добавьте возможность переводасимвольных констант С++. Напишите функцию itoa() для перевода целого значения в строковоепредставление.12. (*2) Перепишите функцию get_token() ($$3.12) так, чтобы она читала целую строку в буфер, а затем95Бьерн Страуструп.Язык программирования С++выдавала лексемы, читая по символу из буфера.13.
(*2) Введите в программу калькулятора из $$3.1 такие функции, как sqrt(), log() и sin(). Подсказка:задайте предопределенные имена и вызывайте функции с помощью массива указателей на них. Незабывайте проверять параметры, передаваемые этим функциям.14. (*3) Введите в калькулятор возможность определять пользовательские функции. Подсказка:определите функцию как последовательность операторов, будто бы заданную самимпользователем. Эту последовательность можно хранить или как строку символов, или как списоклексем. Когда вызывается функция, надо выбирать и выполнять операции.
Если пользовательскиефункции могут иметь параметры, то придется придумать форму записи и для них.15. (*1.5) Переделайте программу калькулятора, используя структуру symbol вместо статическихпеременных name_string и number_value:struct symbol {token_value tok;union {double number_value;char* name_string;};};16.
(*2.5) Напишите программу, которая удаляет все комментарии из программы на С++. Это значит,надо читать символы из cin и удалять комментарии двух видов: // и /* */. Получившийся текстзапишите в cout. Не заботьтесь о красивом виде получившегося текста (это уже другая, болеесложная задача). Корректность программ неважна. Нужно учитывать возможность появлениясимволов //, /* и */ в комментариях, строках и символьных константах.17. (*2) Исследуйте различные программы и выясните, какие способы выделения текста пробелами икакие комментарии используются.96Бьерн Страуструп.Язык программирования С++ГЛАВА 4.Итерация присуща человеку, а рекурсия - богу.- Л. ДойчВсе нетривиальные программы состоят из нескольких раздельно транслируемых единиц, по традицииназываемых файлами.
В этой главе описано, как раздельно транслируемые функции могут вызыватьдруг друга, каким образом они могут иметь общие данные, и как добиться непротиворечивости типов,используемых в разных файлах программы. Подробно обсуждаются функции, в том числе: передачапараметров, перегрузка имени функции, стандартные значения параметров, указатели на функции и,естественно, описания и определения функций.
В конце главы обсуждаются макровозможности языка.4.1 ВведениеРоль файла в языке С++ сводится к тому, что он определяет файловую область видимости ($$R.3.2).Это область видимости глобальных функций (как статических, так и подстановок), а также глобальныхпеременных (как статических, так и со спецификацией const). Кроме того, файл является традиционнойединицей хранения в системе, а также единицей трансляции. Обычно системы хранят, транслируют ипредставляют пользователю программу на С++ как множество файлов, хотя существуют системы,устроенные иначе. В этой главе будет обсуждаться в основном традиционное использование файлов.Всю программу поместить в один файл, как правило, невозможно, поскольку программы стандартныхфункций и программы операционной системы нельзя включить в текстовом виде в программупользователя.
Вообще, помещать всю программу пользователя в один файл обычно неудобно инепрактично. Разбиения программы на файлы может облегчить понимание общей структуры программыи дает транслятору возможность поддерживать эту структуру. Если единицей трансляции являетсяфайл, то даже при небольшом изменении в нем следует его перетранслировать. Даже для программ неслишком большого размера время на перетрансляцию можно значительно сократить, если ее разбитьна файлы подходящего размера.Вернемся к примеру с калькулятором. Решение было дано в виде одного файла. Когда вы попытаетесьего транслировать, неизбежно возникнут некоторые проблемы с порядком описаний. По крайней мереодно "ненастоящее" описание придется добавить к тексту, чтобы транслятор мог разобраться виспользующих друг друга функциях expr(), term() и prim().
По тексту программы видно, что она состоитиз четырех частей: лексический анализатор (сканер), собственно анализатор, таблица имен и драйвер.Однако, этот факт никак не отражен в самой программе. На самом деле калькулятор не былзапрограммирован именно так. Так не следует писать программу. Даже если не учитывать всерекомендации по программированию, сопровождению и оптимизации для такой "зряшной" программы,все равно ее следует создавать из нескольких файлов хотя бы для удобства.Чтобы раздельная трансляция стала возможной, программист должен предусмотреть описания, изкоторых транслятор получит достаточно сведений о типах для трансляции файла, составляющеготолько часть программы.
Требование непротиворечивости использования всех имен и типов дляпрограммы, состоящей из нескольких раздельно транслируемых частей, так же справедливо, как и дляпрограммы, состоящей из одного файла. Это возможно только в том случае, когда описания,находящиеся в разных единицах трансляции, будут согласованы. В вашей системе программированияимеются средства, которые способны установить, выполняется ли это.
В частности, многиепротиворечия обнаруживает редактор связей. Редактор связей - это программа, которая связывает поименам раздельно транслируемые части программы. Иногда его по ошибке называют загрузчиком.4.2 СвязываниеЕсли явно не определено иначе, то имя, не являющееся локальным для некоторой функции или класса,должно обозначать один и тот же тип, значение, функцию или объект во всех единицах трансляцииданной программы. Иными словами, в программе может быть только один нелокальный тип, значение,функция или объект с данным именем. Рассмотрим для примера два файла:// file1.c97Бьерн Страуструп.Язык программирования С++int a = 1;int f() { /* какие-то операторы */ }// file2.cextern int a;int f();void g() { a = f(); }В функции g() используются те самые a и f(), которые определены в файле file1.c.
Служебное словоextern показывает, что описание a в файле file2.c является только описанием, но не определением.Если бы присутствовала инициализация a, то extern просто проигнорировалось бы, поскольку описаниес инициализацией всегда считается определением.
Любой объект в программе может определятьсятолько один раз. Описываться же он может неоднократно, но все описания должны быть согласованыпо типу. Например:// file1.c:int a = 1;int b = 1;extern int c;// file2.c:int a;extern double b;extern int c;Здесь содержится три ошибки: переменная a определена дважды ("int a;" - это определение,означающее "int a=0;"); b описано дважды, причем с разными типами; c описано дважды, нонеопределено.
Такие ошибки (ошибки связывания) транслятор, который обрабатывает файлы поотдельности, обнаружить не может, но большая их часть обнаруживается редактором связей.Следующая программа допустима в С, но не в С++:// file1.c:int a;int f() { return a; }// file2.c:int a;int g() { return f(); }Во-первых, ошибкой является вызов f() в file2.c, поскольку в этом файле f() не описана. Во-вторых,файлы программы не могут быть правильно связаны, поскольку a определено дважды.Если имя описано как static, оно становится локальном в этом файле. Например:// file1.c:static int a = 6;static int f() { /* ...
*/ }// file2.c:static int a = 7;static int f() { /* ... */ }Приведенная программа правильна, поскольку a и f определены как статические. В каждом файле свояпеременная a и функция f().Если переменные и функции в данной части программы описаны как static, то в этой части программыпроще разобраться, поскольку не нужно заглядывать в другие части. Описывать функции какстатические полезно еще и по той причине, что транслятору предоставляется возможность создатьболее простой вариант операции вызова функции.
Если имя объекта или функции локально в данномфайле, то говорят, что объект подлежит внутреннему связыванию. Обратно, если имя объекта илифункции нелокально в данном файле, то он подлежит внешнему связыванию.Обычно говорят, что имена типов, т.е. классов и перечислений, не подлежат связыванию. Имена98Бьерн Страуструп.Язык программирования С++глобальных классов и перечислений должны быть уникальными во всей программе и иметьединственное определение. Поэтому, если есть два даже идентичных определения одного класса, это все равно ошибка:// file1.c:struct S { int a; char b; };extern void f(S*);// file2.c:struct S { int a; char b; };void f(S* p) { /* ...
*/ }Но будьте осторожны: опознать идентичность двух описаний класса не в состоянии большинствосистем программирования С++. Такое дублирование может вызвать довольно тонкие ошибки (ведьклассы в разных файлах будут считаться различными).Глобальные функции-подстановки подлежат внутреннему связыванию, и то же по умолчаниюсправедливо для констант. Синонимы типов, т.е. имена typedef, локальны в своем файле, поэтомуописания в двух данных ниже файлах не противоречат друг другу:// file1.c:typedef int T;const int a = 7;inline T f(int i) { return i+a; }// file2.c:typedef void T;const int a = 8;inline T f(double d) { cout<<d; }Константа может получить внешнее связывание только с помощью явного описания:// file3.c:extern const int a;const int a = 77;// file4.c:extern const int a;void g() { cout<<a; }В этом примере g() напечатает 77.4.3 Заголовочные файлыТипы одного объекта или функции должны быть согласованы во всех их описаниях.