3.12. Итак, снова напомним, что русские буквы char, а не unsigned char дают отрица-
тельные индексы в массиве.
char c = 'г';
int x[256];
...x[c]... /* индекс < 0 */
...x['г']...
Поэтому байтовые индексы должны быть либо unsigned char, либо & 0xFF. Как в следую-
щем примере:
/* Программа преобразования символов в файле: транслитерация
tr abcd prst заменяет строки
xxxxdbcaxxxx -> xxxxtrspxxxx
По мотивам книги М.Дансмура и Г.Дейвиса.
*/
#include <stdio.h>
#define ASCII 256 /* число букв в алфавите ASCII */
/* BUFSIZ определено в stdio.h */
char mt[ ASCII ]; /* таблица перекодировки */
/* начальная разметка таблицы */
void mtinit(){
register int i;
for( i=0; i < ASCII; i++ )
mt[i] = (char) i;
}
int main(int argc, char *argv[])
{
register char *tin, *tout; /* unsigned char */
char buffer[ BUFSIZ ];
if( argc != 3 ){
fprintf( stderr, "Вызов: %s что наЧто\n", argv[0] );
return(1);
}
tin = argv[1]; tout = argv[2];
if( strlen(tin) != strlen(tout)){
fprintf( stderr, "строки разной длины\n" );
return(2);
}
mtinit();
do{
mt[ (*tin++) & 0xFF ] = *tout++;
/* *tin - имеет тип char.
* & 0xFF подавляет расширение знака
*/
} while( *tin );
& 0xFF ];
fputs( buffer, stdout );
}
return(0);
}
3.13.
int main(int ac, char *av[]){
char c = 'г';
if('a' <= c && c < 256)
printf("Это одна буква.\n");
return 0;
}
Увы, эта программа не печатает НИЧЕГО. Просто потому, что signed char в сравнении (в
операторе if) приводится к типу int. А как целое число - русская буква отрицательна.
Снова решением является либо использование везде (c & 0xFF), либо объявление
unsigned char c. В частности, этот пример показывает, что НЕЛЬЗЯ просто так сравни-
вать две переменные типа char. Нужно принимать предохранительные меры по подавлению
расширения знака:
if((ch1 & 0xFF) < (ch2 & 0xFF))...;
Для unsigned char такой проблемы не будет.
3.14. Почему неверно:
#include <stdio.h>
main(){
char c;
while((c = getchar()) != EOF)
putchar(c);
}
Потому что c описано как char, в то время как EOF - значение типа intsigned char со зна-
чением знакового целого EOF, c будет приведено тоже к знаковому целому - расширением
знака. 0xFF превратится в (-1), что означает, что поступил символ EOF служит ПРАВИЛЬНОЕ объявление int c.
3.15. Изучите поведение программы
#define TYPE char
void f(TYPE c){
if(c == 'й') printf("Это буква й\n");
printf("c=%c c=\\%03o c=%03d c=0x%0X\n", c, c, c, c);
}
int main(){
f('г'); f('й');
f('z'); f('Z');
return 0;
}
когда TYPE определено как char, unsigned char, int. Объясните поведение. Выдачи в
этих трех случаях таковы (int == 32 бита):
c=г c=\37777777707 c=-57 c=0xFFFFFFC7
Это буква й
c=й c=\37777777712 c=-54 c=0xFFFFFFCA
c=z c=\172 c=122 c=0x7A
c=Z c=\132 c=090 c=0x5A
c=г c=\307 c=199 c=0xC7
c=й c=\312 c=202 c=0xCA
c=z c=\172 c=122 c=0x7A
c=Z c=\132 c=090 c=0x5A
и снова как 1 случай.
Рассмотрите альтернативу
if(c == (unsigned char) 'й') printf("Это буква й\n");
где предполагается, что знак у русских букв и у c НЕ расширяется. В данном случае
фраза 'Это буква й' не печатается ни с типом char, ни с типом int, поскольку в срав-
нении c приводится к типу signed int расширением знакового бита (который равен 1).
Слева получается отрицательное число!
В таких случаях вновь следует писать
if((unsigned char)c == (unsigned char)'й') printf("Это буква й\n");
3.16. Обычно возникают проблемы при написании функций с переменным числом аргумен-
тов. В языке Си эта проблема решается использованием макросов va_args<varargs.h> и <stdarg.h>. Первый был продемонстрирован в первой главе на примере
функции poly(). Для иллюстрации второго приведем пример функции трассировки, записы-
вающей собщение в файл:
#include <stdio.h>
#include <stdarg.h>
void trace(char *fmt, ...) {
va_list args;
static FILE *fp = NULL;
if(fp == NULL){
if((fp = fopen("TRACE", "w")) == NULL) return;
}
va_start(args, fmt);
/* второй аргумент: арг-т после которого
* в заголовке функции идет ... */
vfprintf(fp, fmt, args); /* библиотечная ф-ция */
fflush(fp); /* вытолкнуть сообщение в файл */
va_end(args);
}
main(){ trace( "%s\n", "Go home.");
trace( "%d %d\n", 12, 34);
}
Символ `...' (троеточие) в заголовке функции обозначает переменный (возможно пустой)
список аргументов. Он должен быть самым последним, следуя за всеми обязательными
аргументами функции.
Макрос va_arg(args,type), извлекающий из переменного списка аргументов `...'
очередное значение типа type, одинаков в обоех моделях. Функция vfprintf может быть
написана через функцию vsprintf (в действительности обе функции - стандартные):
int vfprintf(FILE *fp, const char *fmt, va_list args){
/*static*/ char buffer[1024]; int res;
res = vsprintf(buffer, fmt, args);
fputs(buffer, fp); return res;
}
Функция vsprintf(str,fmt,args); аналогична функции sprintf(str,fmt,...) - записывает
преобразованную по формату строку в байтовый массив str, но используется в контексте,
подобном приведенному. В конец сформированной строки sprintf записывает '\0'.
3.17. Напишите функцию printf, понимающую форматы %c (буква), %d (целое), %o (вось-
меричное), %x (шестнадцатеричное), %b (двоичное), %r (римское), %s (строка), %ld
(длинное целое). Ответ смотри в приложении.
3.18. выглядеть на разных машинах, поэтому их оформ-
ляют в виде так называемых "условно компилируемых" частей:
#ifdef XX
... вариант1
#else
... вариант2
#endif
Эта директива препроцессора ведет себя следующим образом: если макрос с именем XX был
определен
#define XX
то в программу подставляется вариант1, если же нет - вариант2. Оператор #else не обя-
зателен - при его отсутствии вариант2 пуст. Существует также оператор #ifndef, кото-
рый подставляет вариант1 если макрос XX не определен. Есть еще и оператор #elif -
else if:
#ifdef макро1
...
#elif макро2
...
#else
...
#endif
Определить макрос можно не только при помощи #define, но и при помощи ключа компиля-
тора, так
cc -DXX file.c ...
соответствует включению в начало файла file.c директивы
#define XX
А для программы
main(){
#ifdef XX
printf( "XX = %d\n", XX);
#else
printf( "XX undefined\n");
#endif
}
ключ
cc -D"XX=2" file.c ...
эквивалентен заданию директивы
#define XX 2
Что будет, если совсем не задать ключ -Dcc -Dvoid=int ...
cc -Dstrchr=index ...
В некоторых системах компилятор автоматически определяет специальные макросы: так
компиляторы в UNIX неявно подставляют один из ключей (или несколько сразу):
-DM_UNIX
-DM_XENIX
-Dunix
-DM_SYSV
-D__SVR4
-DUSG
... бывают и другие
Это позволяет программе "узнать", что ее компилируют для системы UNIX. Более под-
робно про это написано в документации по команде cc.
3.19. Оператор #ifdef применяется в include-файлах, чтобы исключить повторное вклю-
чение одного и того же файла. Пусть файлы aa.h и bb.h содержат
aa.h bb.h
#include "cc.h" #include "cc.h"
typedef unsigned long ulong; typedef int cnt_t;
А файлы cc.h и 00.c содержат
cc.h 00.c
... #include "aa.h"
struct II { int x, y; }; #include "bb.h"
... main(){ ... }
В этом случае текст файла cc.h будет вставлен в 00.c дважды: из aa.h и из bb.h. При
компиляции 00.c
/* файл cc.h */
#ifndef _CC_H
# define _CC_H /* определяется при первом включении */
...
struct II { int x, y; };
...
#endif /* _CC_H */
Второе и последующие включения такого файла будут подставлять пустое место, что и
требуется. Для файла <sys/types.h> было бы использовано макроопределение
_SYS_TYPES_H.
3.20. Любой макрос можно отменить, написав директиву
#undef имяМакро
Пример:
#include <stdio.h>
#undef M_UNIX
#undef M_SYSV
main() {
putchar('!');
#undef putchar
#define putchar(c) printf( "Буква '%c'\n", c);
putchar('?');
#if defined(M_UNIX) || defined(M_SYSV)
/* или просто #if M_UNIX */
printf("Это UNIX\n");
#else
printf("Это не UNIX\n");
#endif /* UNIX */
}
Обычно #undef используется именно для переопределения макроса, как putchar в этом
примере (дело в том, что putchar - это макрос из <stdio.h>).
Директива #if, использованная нами, является расширением оператора #ifdef и
подставляет текст если выполнено указанное условие:
#if defined(MACRO) /* равно #ifdef(MACRO) */
#if !defined(MACRO) /* равно #ifndef(MACRO) */
#if VALUE > 15 /* если целая константа
#define VALUE 25
больше 15 (==, !=, <=, ...) */
#if COND1 || COND2 /* если верно любое из условий */
#if COND1 && COND2 /* если верны оба условия */
Директива #if допускает использование в качестве аргумента довольно сложных выраже-
ний, вроде
#if !defined(M1) && (defined(M2) || defined(M3))
3.21. Условная компиляция может использоваться для трассировки программ:
#ifdef DEBUG
# define DEBUGF(body) \
{ \
body; \
}
#else
# define DEBUGF(body)
#endif
int f(int x){ return x*x; }
int main(int ac, char *av[]){
int x = 21;
DEBUGF(x = f(x); printf("%s equals to %d\n", "x", x));
printf("x=%d\n", x);
}
При компиляции
cc -DDEBUG file.c
в выходном потоке программы будет присутствовать отладочная выдача. При компиляции
без -DDEBUG этой выдачи не будет.
3.22. В языке C++ (развитие языка Си) слова class, delete, friend, new, operator,
overload, template, public, private, protected, this, virtual являются зарезервиро-
ванными (ключевыми). Это может вызвать небольшую проблему при переносе текста прог-
раммы на Си в систему программирования C++, например:
#include <termio.h>
...
int fd_tty = 2; /* stderr */
struct termio old, new;
ioctl (fd_tty, TCGETA, &old);
new = old;
new.c_lflag |= ECHO | ICANON;
ioctl (fd_tty, TCSETAW, &new);
...
Строки, содержащие имя переменной (или функции) new, окажутся неправильными в C++define:
#define new new_modes
... старый текст ...
#undef new
При переносе программы на Си в C++ следует также учесть, что в C++ для каждой функции
должен быть задан прототип, прежде чем эта функция будет использована (Си позволяет
опускать прототипы для многих функций, особенно возвращающих значения типов int или
void).
[Назад] [Содержание] [Вперед]
| Главная |