Смекни!
smekni.com

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

****************************************/

Удачно подобранные и хорошо написанные комментарии - существенная

часть программы. Написание хороших комментариев может быть столь же

сложным, сколь и написание самой программы.

Заметьте также, что если в функции используются исключительно

комментарии //, то любую часть этой функции можно закомментировать

с помощью комментариев /* */, и наоборот.

3.5 Упражнения

1. (*1) Перепишите следующий оператор for в виде эквивалентного

оператора while:

for (i=0; im

*p.m

*a[i]

7. (*2) Напишите функции: strlen(), которая возвращает длину

строки, strcpy(), которая копирует одну строку в другую, и

strcmp(), которая сравнивает две строки. Разберитесь, какие

должны быть типы параметров и типы возвращаемых значений, а

потом сравните их со стандартными версиями, которые описаны в

и в вашем руководстве.

8. (*1) Посмотрите, как ваш компилятор реагирует на ошибки:

a := b+1;

if (a = 3) // ...

if (a&077 == 0) // ...

Придумайте ошибки попроще, и посмотрите, как компилятор на них

реагирует.

9. (*2) Напишите функцию cat(), получающую два строковых

параметра и возвращающую строку, которая является

конкатенацией параметров. Используйте new, чтобы найти память

для результата. Напишите функцию rev(), которая получает

строку и переставляет в ней символы в обратном порядке. То

есть, после вызова rev(p) последний символ p становится

первым.

10. (*2) Что делает следующая программа?

- стр 107 -

void send(register* to, register* from, register count)

// Полезные комментарии несомненно уничтожены.

{

register n=(count+7)/8;

switch (count%8) {

case 0: do { *to++ = *from++;

case 7: do { *to++ = *from++;

case 6: do { *to++ = *from++;

case 5: do { *to++ = *from++;

case 4: do { *to++ = *from++;

case 3: do { *to++ = *from++;

case 2: do { *to++ = *from++;

case 1: do { *to++ = *from++;

while (--n>0);

}

}

Зачем кто-то мог написать нечто похожее?

11. (*2) Напишите функцию atoi(), которая получает строку,

содержащую цифры, и возвращает соответствующее int. Например,

atoi("123") - это 123. Модифицируйте atoi() так, чтобы помимо

обычной десятичной она обрабатывала еще восьмеричную и

шестнадцатиричную записи C++. Модифицируйте atoi() так, чтобы

обрабатывать запись символьной константы. Напишите функцию

itoa(), которая строит представление целого параметра в виде

строки.

12. (*2) Перепишите get_token() (#3.1.2), чтобы она за один раз

читала строку в буфер, а затем составляла лексемы, читая

символы из буфера.

13. (*2) Добавьте в настрольный калькулятор из #3.1 такие

функции, как sqrt(), log() и sin(). Подсказка: предопределите

имена и вызывайте функции с помощью вектора указателей на

функции. Не забывайте проверять параметры в вызове функции.

14. (*3) Дайте пользователю возможность определять функции в

настольном калькуляторе. Подсказка: определяйте функции как

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

пользователь. Такую последовательность можно хранить или как

символьную строку, или как список лексем. После этого, когда

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

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

должны придумать форму записи этого.

15. (*1.5) Преобразуйте настольный калькулятор так, чтобы вместо

статических переменных name_string и number_value

использовалась структура символа symbol:

struct symbol {

token_value tok;

union {

double number_value;

char* name_string;

};

};

16. (*2.5) Напишите программу, которая выбрасывает комментарии из

C++ программы. То есть, читает из cin, удаляет // и /* */

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

- стр 108 -

виде выходного текста (это могло бы быть другим, более сложным

упражнением). Не беспокойтесь о правильности программ.

Остерегайтесь // и /* и */ внутри комментариев, строк и

символьных констант.

17. (*2) Посмотрите какие-нибудь программы, чтобы понять принцип

различных стилей комментирования и выравнивания, которые

используются на практике.

* Глава 4 *

Функции и Файлы

Итерация свойственна человеку,

рекусия божественна.

- Л. Питер Дойч

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

компилируемых единиц (их принято называть просто файлами). В этой

главе описано, как раздельно откомпилированные функции могут

обращаться друг к другу, как такие функции могут совместно

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

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

программы. Функции обсуждаются довольно подробно. Сюда входят

передача параметров, параметры по умолчанию, перегрузка имен

функций, и, конечно же, описание и определение функций. В конце

описываются макросы.

4.1 Введение

Иметь всю программу в одном файле обычно невозможно, поскольку

коды стандартных библиотек и операционной системы находятся где-то

в другом месте. Кроме того, хранить весь текст пользовательской

программы в одном файле как правило непрактично и неудобно. Способ

организации программы в файлы может помочь читающему охватить всю

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

реализовать эту структуру. Поскольку единицей компиляции является

файл, то во всех случаях, когда в файл вносится изменение (сколь бы

мало оно ни было), весь файл нужно компилировать заново. Даже для

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

перекомпиляцию, можно значительно снизить с помощью разбиения

программы на файлы подходящих размеров.

Рассмотрим пример с калькулятором. Он был представлен в виде

одного исходного файла. Если вы его набили, то у вас наверняка были

небольшие трудности с расположением описаний в правильном порядке,

и пришлось использовать по меньшей мере одно "фальшивое" описание,

чтобы компилятор смог обработать взаимно рекурсивные функции

expr(), term() и prim(). В тексте уже отмечалось, что программа

состоит из четырех частей (лексического анализатора, программы

синтаксического разбора, таблицы имен и драйвера), но это никак не

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

был написан по-другому. Так это не делается; даже если в этой

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

программирования, эксплуатации и эффективности компиляции, автор

все равно разобьет эту программу в 200 строк на несколько файлов,

чтобы програмировать было приятнее.

Программа, состояшая из нескольких раздельно компилируемых

файлов, должна быть согласованной в смысле использования имен и

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

файла. В принципе, это может обеспечить и компоновщик*. Компоновщик

- это программа, стыкующая отдельно скомпилированные части вместе.

____________________

* или линкер. (прим. перев.)

- стр 110 -

Компоновщик часто (путая) называют загрузчиком. В UNIX'е

компоновщик называется ld. Однако компоновщики, имеющиеся в

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

согласованности.

Программист может скомпенсировать недостаток поддержки со стороны

компоновщика, предоставив дополнительную информацию о типах

(описания). После этого согласованность программы обеспечивается

проверкой согласованности описаний, которые находятся в отдельно

компилируемых частях. Средства, которые это обеспечивают, в вашей

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

компоновке**.

4.2 Компоновка

Если не указано иное, то имя, не являющееся локальным для функции

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

должно относиться к одному и тому же типу, значению, функции или

объекту. То есть, в программе может быть только один нелокальный

тип, значение, функция или объект с этим именем. Рассмотрим,

например, два файла:

// file1.c:

int a = 1;

int f() { /* что-то делает */ }

// file2.c:

extern int a;

int f();

void g() { a = f(); }

a и f(), используемые g() в файле file2.c,- те же, что определены в

файле 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;

____________________

** C разработан так, чтобы в большинстве случаев позволять

осуществлять неявную компоновку. Применение C, однако, возросло

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

линковку, сейчас составляют незначительное меньшинство. (прим.

автора)

- стр 111 -

Здесь три ошибки: a определено дважды (int a; является

определением, которое означает int a=0;), b описано дважды с

разными типами, а c описано дважды, но не определено. Эти виды

ошибок (ошибки компоновки) не могут быть обнаружены компилятором,

который за один раз видит только один файл. Компоновщик, однако, их

обнаруживает.

Следующая программа не является C++ программой (хотя C программой

является):

// file1.c:

int a;

int f() { return a; }

// file2.c:

int a;

int g() { return f(); }

Во-первых, file2.c не C++, потому что f() не была описана, и

поэтому компилятор будет недоволен. Во-вторых, (когда file2.c

фиксирован) программа не будет скомпонована, поскольку a определно

дважды.

Имя можно сделать локальным в файле, описав его static. Например:

// file1.c:

static int a = 6;

static int f() { /* ... */ }

// file2.c:

static int a = 7;

static int f() { /* ... */ }

Поскольку каждое a и f описано как static, получающаяся в

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

своя f().

Когда переменные и функции явно описаны как static, часть