C++ CSS HTML Java JavaScript MySQL Oracle PERL PHP SQL Unix VBScript XHTML XML Сети
3. Мобильность и машинная зависимость программ. Проблемы с русскими буквами.
 
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).

[Назад] [Содержание] [Вперед]

Главная