только из самого внутреннего цикла (на один уровень).
1.53. К какому if-у относится else?
if(...) ... if(...) ... else ...
Ответ: ко второму (к ближайшему предшествующему, для которого нет другого else).
Вообще же лучше явно расставлять скобки (для ясности):
if(...){ ... if(...) ... else ... }
if(...){ ... if(...) ... } else ...
1.54. Макроопределение, чье тело представляет собой последовательность операторов в
{...} скобках (блок), может вызвать проблемы при использовании его в условном опера-
торе if с else-частью:
#define MACRO { x=1; y=2; }
if(z) MACRO;
else .......;
Мы получим после макрорасширения
if(z) { x=1; y=2; } /* конец if-а */ ;
else .......; /* else ни к чему не относится */
то есть синтаксически ошибочный фрагмент, так как должно быть либо
if(...) один_оператор;
else .....
либо
if(...){ последовательность; ...; операторов; }
else .....
где точка-с-запятой после } не нужна. С этим явлением борются, оформляя блок {...} в
виде do{...}while(0)
#define MACRO do{ x=1; y=2; }while(0)
Тело такого "цикла" выполняется единственный раз, при этом мы получаем правильный
текст:
А. Богатырев, 1992-95 - 25 - Си в UNIX
if(z) do{ x=1; y=2; }while(0);
else .......;
1.55. В чем ошибка (для знающих язык "Паскаль")?
int x = 12;
if( x < 20 and x > 10 ) printf( "O'K\n");
else if( x > 100 or x < 0 ) printf( "Bad x\n");
else printf( "x=%d\n", x);
Напишите
#define and &&
#define or ||
1.56. Почему программа зацикливается? Мы хотим подсчитать число пробелов и табуля-
ций в начале строки:
int i = 0;
char *s = " 3 spaces";
while(*s == ' ' || *s++ == '\t')
printf( "Пробел %d\n", ++i);
Ответ: логические операции || и && выполняются слева направо; как только какое-то
условие в || оказывается истинным (а в && ложным) - дальнейшие условия просто не
вычисляются. В нашем случае условие *s==' ' сразу же верно, и операция s++ из второго
условия не выполняется! Мы должны были написать хотя бы так:
while(*s == ' ' || *s == '\t'){
printf( "Пробел %d\n", ++i); s++;
}
С другой стороны, это свойство || и && черезвычайно полезно, например:
if( x != 0.0 && y/x < 1.0 ) ... ;
Если бы мы не вставили проверку на 0, мы могли бы получить деление на 0. В данном же
случае при x==0 деление просто не будет вычисляться. Вот еще пример:
int a[5], i;
for(i=0; i < 5 && a[i] != 0; ++i) ...;
Если i выйдет за границу массива, то сравнение a[i] с нулем уже не будет вычисляться,
т.е. попытки прочесть элемент не входящий в массив не произойдет.
Это свойство && позволяет писать довольно неочевидные конструкции, вроде
if((cond) && f());
что оказывается эквивалентным
if( cond ) f();
Вообще же
if(C1 && C2 && C3) DO;
эквивалентно
if(C1) if(C2) if(C3) DO;
и для "или"
А. Богатырев, 1992-95 - 26 - Си в UNIX
if(C1 || C2 || C3) DO;
эквивалентно
if(C1) goto ok;
else if(C2) goto ok;
else if(C3){ ok: DO; }
Вот еще пример, пользующийся этим свойством ||
#include
main(argc, argv) int argc; char *argv[];
{ FILE *fp;
if(argc < 2 || (fp=fopen(argv[1], "r")) == NULL){
fprintf(stderr, "Плохое имя файла\n");
exit(1); /* завершить программу */
}
...
}
Если argc==1, то argv[1] не определено, однако в этом случае попытки открыть файл с
именем argv[1] просто не будет предпринято!
Ниже приведен еще один содержательный пример, представляющий собой одну из воз-
можных схем написания "двуязычных" программ, т.е. выдающих сообщения на одном из двух
языков по вашему желанию. Проверяется переменная окружения MSG (или LANG):
ЯЗЫК:
1) "MSG=engl" английский
2) MSG нет в окружении английский
3) "MSG=rus" русский
Про окружение и функцию getenv() смотри в главе "Взаимодействие с UNIX", про strchr()
- в главе "Массивы и строки".
#include
int _ediag = 0; /* язык диагностик: 1-русский */
extern char *getenv(), *strchr();
#define ediag(e,r) (_ediag?(r):(e))
main(){ char *s;
_ediag = ((s=getenv("MSG")) != NULL &&
strchr("rRрР", *s) != NULL);
printf(ediag("%d:english\n", "%d:русский\n"), _ediag);
}
Если переменная MSG не определена, то s==NULL и функция strchr(s,...) не вызывается
(ее первый фргумент не должен быть NULL-ом). Здесь ее можно было бы упрощенно заме-
нить на *s=='r'; тогда если s равно NULL, то обращение *s было бы незаконно (обраще-
ние по указателю NULL дает непредсказуемые результаты и, скорее всего, вызовет крах
программы).
1.57. Иногда логическое условие можно сделать более понятным, используя правила де-
Моргана:
a && b = ! ( !a || !b )
a || b = ! ( !a && !b )
а также учитывая, что
! !a = a
! (a == b) = (a != b)
Например:
А. Богатырев, 1992-95 - 27 - Си в UNIX
if( c != 'a' && c != 'b' && c != 'c' )...;
превращается в
if( !(c == 'a' || c == 'b' || c == 'c')) ...;
1.58. Пример, в котором используются побочные эффекты вычисления выражений. Обычно
значение выражения присваивается некоторой переменной, но это не необходимо. Поэтому
можно использовать свойства вычисления && и || в выражениях (хотя это не есть самый
понятный способ написания программ, скорее некоторый род извращения). Ограничение тут
таково: все части выражения должны возвращать значения.
#include
extern int errno; /* код системной ошибки */
FILE *fp;
int openFile(){
errno = 0;
fp = fopen("/etc/inittab", "r");
printf("fp=%x\n", fp);
return(fp == NULL ? 0 : 1);
}
int closeFile(){
printf("closeFile\n");
if(fp) fclose(fp);
return 0;
}
int die(int code){
printf("exit(%d)\n", code);
exit(code);
return 0;
}
void main(){
char buf[2048];
if( !openFile()) die(errno); closeFile();
openFile() || die(errno); closeFile();
/* если файл открылся, то die() не вычисляется */
openFile() ? 0 : die(errno); closeFile();
if(openFile()) closeFile();
openFile() && closeFile();
/* вычислить closeFile() только если openFile() удалось */
openFile() && (printf("%s", fgets(buf, sizeof buf, fp)), closeFile());
}
В последней строке использован оператор "запятая": (a,b,c) возвращает значение выра-
жения c.
1.59. Напишите функцию, вычисляющую сумму массива заданных чисел.
1.60. Напишите функцию, вычисляющую среднее значение массива заданных чисел.
1.61. Что будет напечатано в результате работы следующего цикла?
for ( i = 36; i > 0; i /= 2 )
printf ( "%d%s", i,
i==1 ? ".\n":", ");
А. Богатырев, 1992-95 - 28 - Си в UNIX
Ответ: 36, 18, 9, 4, 2, 1.
1.62. Найдите ошибки в следующей программе:
main {
int i, j, k(10);
for ( i = 0, i <= 10, i++ ){
k[i] = 2 * i + 3;
for ( j = 0, j <= i, j++ )
printf ("%i\n", k[j]);
}
}
Обратите внимание на формат %i, существует ли такой формат? Есть ли это тот формат,
по которому следует печатать значения типа int?
1.63. Напишите программу, которая распечатывает элементы массива. Напишите прог-
рамму, которая распечатывает элементы массива по 5 чисел в строке.
1.64. Составьте программу считывания строк символов из стандартного ввода и печати
номера введенной строки, адреса строки в памяти ЭВМ, значения строки, длины строки.
1.65. Стилистическое замечание: в операторе return возвращаемое выражение не обяза-
тельно должно быть в ()-скобках. Дело в том, что return - не функция, а оператор.
return выражение;
return (выражение);
Однако если вы вызываете функцию (например, exit) - то аргументы должны быть в круг-
лых скобках: exit(1); но не exit 1;
1.66. Избегайте ситуации, когда функция в разных ветвях вычисления то возвращает
некоторое значение, то не возвращает ничего:
int func (int x) {
if( x > 10 ) return x*2;
if( x == 10 ) return (10);
/* а здесь - неявный return; без значения */
}
при x < 10 функция вернет непредсказуемое значение! Многие компиляторы распознают
такие ситуации и выдают предупреждение.
1.67. Напишите программу, запрашивающую ваше имя и "приветствующую" вас. Напишите
функцию чтения строки. Используйте getchar() и printf().
Ответ:
#include /* standard input/output */
main(){
char buffer[81]; int i;
printf( "Введите ваше имя:" );
while((i = getstr( buffer, sizeof buffer )) != EOF){
printf( "Здравствуй, %s\n", buffer );
printf( "Введите ваше имя:" );
}
}
getstr( s, maxlen )
char *s; /* куда поместить строку */
int maxlen; /* длина буфера:
А. Богатырев, 1992-95 - 29 - Си в UNIX
макс. длина строки = maxlen-1 */
{ int c; /* не char! (почему ?) */
register int i = 0;
maxlen--; /* резервируем байт под конечный '\0' */
while(i < maxlen && (c = getchar()) != '\n'
&& c != EOF )
s[i++] = c;
/* обратите внимание, что сам символ '\n'
* в строку не попадет */
s[i] = '\0'; /* признак конца строки */
return (i == 0 && c == EOF) ? EOF : i;
/* вернем длину строки */
}
Вот еще один вариант функции чтения строки: в нашем примере ее следует вызывать как
fgetstr(buffer,sizeof(buffer),stdin);
Это подправленный вариант стандартной функции fgets (в ней строки @1 и @2 обменяны
местами).
char *fgetstr(char *s, int maxlen, register FILE *fp){
register c; register char *cs = s;
while(--maxlen > 0 && (c = getc(fp)) != EOF){
if(c == '\n') break; /* @1 */
*cs++ = c; /* @2 */
}
if(c == EOF && cs == s) return NULL;
/* Заметьте, что при EOF строка s не меняется! */
*cs = '\0'; return s;
}
Исследуйте поведение этих функций, когда входная строка слишком длинная (длиннее max-
len). Замечание: вместо нашей "рукописной" функции getstr() мы могли бы использовать
стандартную библиотечную функцию gets(buffer).
1.68. Объясните, почему d стало отрицательным и почему %X печатает больше F, чем в
исходном числе? Пример выполнялся на 32-х битной машине.
main(){
unsigned short u = 65535; /* 16 бит: 0xFFFF */
short d = u; /* 15 бит + знаковый бит */
printf( "%X %d\n", d, d); /* FFFFFFFF -1 */
}
Указание: рассмотрите двоичное представление чисел (смотри приложение). Какие приве-
дения типов здесь происходят?
1.69. Почему 128 превратилось в отрицательное число?
main()
{
/*signed*/ char c = 128; /* биты: 10000000 */
unsigned char uc = 128;
int d = c; /* используется 32-х битный int */
printf( "%d %d %x\n", c, d, d );
/* -128 -128 ffffff80 */
d = uc;
printf( "%d %d %x\n", uc, d, d );
/* 128 128 80 */