for( nline=0; nline < lines ; nline++ ){
naster = 1 + 2 * nline;
/* лидирующие пробелы */
printn(' ', lines-1 - nline);
/* звездочки */
printn('*', naster);
/* перевод строки */
putchar( '\n' );
}
exit(0); /* завершение программы */
}
1.12. В чем состоит ошибка?
main(){ /* печать фразы 10 раз */
int i;
while(i < 10){
printf("%d-ый раз\n", i+1);
i++;
}
}
Ответ: автоматическая переменная i не была проинициализирована и содержит не 0, а
какое-то произвольное значение. Цикл может выполниться не 10, а любое число раз (в
том числе и 0 по случайности). Не забывайте инициализировать переменные, возьмите
описание с инициализацией за правило!
int i = 0;
Если бы переменная i была статической, она бы имела начальное значение 0.
В данном примере было бы еще лучше использовать цикл for, в котором все операции
над индексом цикла собраны в одном месте - в заголовке цикла:
for(i=0; i < 10; i++) printf(...);
А. Богатырев, 1992-95 - 7 - Си в UNIX
1.13. Вспомогательные переменные, не несущие смысловой нагрузки (вроде счетчика пов-
торений цикла, не используемого в самом теле цикла) принято по традиции обозначать
однобуквенными именами, вроде i, j. Более того, возможны даже такие курьезы:
main(){
int _ ;
for( _ = 0; _ < 10; _++) printf("%d\n", _ );
}
основанные на том, что подчерк в идентификаторах - полноправная буква.
1.14. Найдите 2 ошибки в программе:
main(){
int x = 12;
printf( "x=%d\n" );
int y;
y = 2 * x;
printf( "y=%d\n", y );
}
Комментарий: в теле функции все описания должны идти перед всеми выполняемыми опера-
торами (кроме операторов, входящих в состав описаний с инициализацией). Очень часто
после внесения правок в программу некоторые описания оказываются после выполняемых
операторов. Именно поэтому рекомендуется отделять строки описания переменных от
выполняемых операторов пустыми строками (в этой книге это часто не делается для эко-
номии места).
1.15. Найдите ошибку:
int n;
n = 12;
main(){
int y;
y = n+2;
printf( "%d\n", y );
}
Ответ: выполняемый оператор n=12 находится вне тела какой-либо функции. Следует
внести его в main() после описания переменной y, либо переписать объявление перед
main() в виде
int n = 12;
В последнем случае присваивание переменной n значения 12 выполнит компилятор еще во
время компиляции программы, а не сама программа при своем запуске. Точно так же про-
исходит со всеми статическими данными (описанными как static, либо расположенными вне
всех функций); причем если их начальное значение не указано явно - то подразумевается
0 ('\0', NULL, ""). Однако нулевые значения не хранятся в скомпилированном выполняе-
мом файле, а требуемая "чистая" память расписывается при старте программы.
1.16. По поводу описания переменной с инициализацией:
TYPE x = выражение;
является (почти) эквивалентом для
TYPE x; /* описание */
x = выражение; /* вычисление начального значения */
А. Богатырев, 1992-95 - 8 - Си в UNIX
Рассмотрим пример:
#include
extern double sqrt(); /* квадратный корень */
double x = 1.17;
double s12 = sqrt(12.0); /* #1 */
double y = x * 2.0; /* #2 */
FILE *fp = fopen("out.out", "w"); /* #3 */
main(){
double ss = sqrt(25.0) + x; /* #4 */
...
}
Строки с метками #1, #2 и #3 ошибочны. Почему?
Ответ: при инициализации статических данных (а s12, y и fp таковыми и являются,
так как описаны вне какой-либо функции) выражение должно содержать только константы,
поскольку оно вычисляется КОМПИЛЯТОРОМ. Поэтому ни использование значений переменных,
ни вызовы функций здесь недопустимы (но можно брать адреса от переменных).
В строке #4 мы инициализируем автоматическую переменную ss, т.е. она отводится
уже во время выполнения программы. Поэтому выражение для инициализации вычисляется
уже не компилятором, а самой программой, что дает нам право использовать переменные,
вызовы функций и.т.п., то есть выражения языка Си без ограничений.
1.17. Напишите программу, реализующую эхо-печать вводимых символов. Программа
должна завершать работу при получении признака EOF. В UNIX при вводе с клавиатуры
признак EOF обычно обозначается одновременным нажатием клавиш CTRL и D (CTRL чуть
раньше), что в дальнейшем будет обозначаться CTRL/D; а в MS DOS - клавиш CTRL/Z.
Используйте getchar() для ввода буквы и putchar() для вывода.
1.18. Напишите программу, подсчитывающую число символов поступающих со стандартного
ввода. Какие достоинства и недостатки у следующей реализации:
#include
main(){ double cnt = 0.0;
while (getchar() != EOF) ++cnt;
printf("%.0f\n", cnt );
}
Ответ: и достоинство и недостаток в том, что счетчик имеет тип double. Достоинство -
можно подсчитать очень большое число символов; недостаток - операции с double обычно
выполняются гораздо медленнее, чем с int и long (до десяти раз), программа будет
работать дольше. В повседневных задачах вам вряд ли понадобится иметь счетчик,
отличный от long cnt; (печатать его надо по формату "%ld").
1.19. Составьте программу перекодировки вводимых символов со стандартного ввода по
следующему правилу:
a -> b
b -> c
c -> d
...
z -> a
другой символ -> *
Коды строчных латинских букв расположены подряд по возрастанию.
1.20. Составьте программу перекодировки вводимых символов со стандартного ввода по
следующему правилу:
А. Богатырев, 1992-95 - 9 - Си в UNIX
B -> A
C -> B
...
Z -> Y
другой символ -> *
Коды прописных латинских букв также расположены по возрастанию.
1.21. Напишите программу, печатающую номер и код введенного символа в восьмеричном и
шестнадцатеричном виде. Заметьте, что если вы наберете на вводе строку символов и
нажмете клавишу ENTER, то программа напечатает вам на один символ больше, чем вы наб-
рали. Дело в том, что код клавиши ENTER, завершившей ввод строки - символ '\n' -
тоже попадает в вашу программу (на экране он отображается как перевод курсора в
начало следующей строки!).
1.22. Разберитесь, в чем состоит разница между символами '0' (цифра нуль) и '\0'
(нулевой байт). Напечатайте
printf( "%d %d %c\n", '\0', '0', '0' );
Поставьте опыт: что печатает программа?
main(){
int c = 060; /* код символа '0' */
printf( "%c %d %o\n", c, c, c);
}
Почему печатается 0 48 60? Теперь напишите вместо
int c = 060;
строчку
char c = '0';
1.23. Что напечатает программа?
#include
void main(){
printf("ab\0cd\nxyz");
putchar('\n');
}
Запомните, что '\0' служит признаком конца строки в памяти, а '\n' - в файле. Что в
строке "abcd\n" на конце неявно уже расположен нулевой байт:
'a','b','c','d','\n','\0'
Что строка "ab\0cd\nxyz" - это
'a','b','\0','c','d','\n','x','y',z','\0'
Что строка "abcd\0" - избыточна, поскольку будет иметь на конце два нулевых байта
(что не вредно, но зачем?). Что printf печатает строку до нулевого байта, а не до
закрывающей кавычки.
Программа эта напечатает ab и перевод строки.
Вопрос: чему равен sizeof("ab\0cd\nxyz")? Ответ: 10.
1.24. Напишите программу, печатающую целые числа от 0 до 100.
1.25. Напишите программу, печатающую квадраты и кубы целых чисел.
А. Богатырев, 1992-95 - 10 - Си в UNIX
1.26. Напишите программу, печатающую сумму квадратов первых n целых чисел.
1.27. Напишите программу, которая переводит секунды в дни, часы, минуты и секунды.
1.28. Напишите программу, переводящую скорость из километров в час в метры в секун-
дах.
1.29. Напишите программу, шифрующую текст файла путем замены значения символа (нап-
ример, значение символа C заменяется на C+1 или на ~C ).
1.30. Напишите программу, которая при введении с клавиатуры буквы печатает на терми-
нале ключевое слово, начинающееся с данной буквы. Например, при введении буквы 'b'
печатает "break".
1.31. Напишите программу, отгадывающую задуманное вами число в пределах от 1 до 200,
пользуясь подсказкой с клавиатуры "=" (равно), "<" (меньше) и ">" (больше). Для уга-
дывания числа используйте метод деления пополам.
1.32. Напишите программу, печатающую степени двойки
1, 2, 4, 8, ...
Заметьте, что, начиная с некоторого n, результат становится отрицательным из-за пере-
полнения целого.
1.33. Напишите подпрограмму вычисления квадратного корня с использованием метода
касательных (Ньютона):
x(0) = a
1 a
x(n+1) = - * ( ---- + x(n))
2 x(n)
Итерировать, пока не будет | x(n+1) - x(n) | < 0.001
Внимание! В данной задаче массив не нужен. Достаточно хранить текущее и предыду-
щее значения x и обновлять их после каждой итерации.
1.34. Напишите программу, распечатывающую простые числа до 1000.
1, 2, 3, 5, 7, 11, 13, 17, ...
А. Богатырев, 1992-95 - 11 - Си в UNIX
/*#!/bin/cc primes.c -o primes -lm
* Простые числа.
*/
#include
#include
int debug = 0;
/* Корень квадратный из числа по методу Ньютона */
#define eps 0.0001
double sqrt (x) double x;
{
double sq, sqold, EPS;
if (x < 0.0)
return -1.0;
if (x == 0.0)
return 0.0; /* может привести к делению на 0 */
EPS = x * eps;
sq = x;
sqold = x + 30.0; /* != sq */
while (fabs (sq * sq - x) >= EPS) {
/* fabs( sq - sqold )>= EPS */
sqold = sq;
sq = 0.5 * (sq + x / sq);
}
return sq;
}
/* таблица прoстых чисел */
int is_prime (t) register int t; {
register int i, up;
int not_div;
if (t == 2 || t == 3 || t == 5 || t == 7)
return 1; /* prime */
if (t % 2 == 0 || t == 1)
return 0; /* composite */
up = ceil (sqrt ((double) t)) + 1;
i = 3;
not_div = 1;
while (i <= up && not_div) {
if (t % i == 0) {
if (debug)
fprintf (stderr, "%d поделилось на %d\n",
t, i);
not_div = 0;
break;
}
i += 2; /*
* Нет смысла проверять четные,
* потому что если делится на 2*n,
* то делится и на 2,
* а этот случай уже обработан выше.
*/
}
return not_div;
}
А. Богатырев, 1992-95 - 12 - Си в UNIX
#define COL 6
int n;
main (argc, argv) char **argv;
{
int i,
j;
int n;
if( argc < 2 ){
fprintf( stderr, "Вызов: %s число [-]\n", argv[0] );