faqs.org.ru

 Главная > Программирование > Языки C/C++/Builder >

Язык C в вопросах и ответах

Секция 4 из 6 - Предыдущая - Следующая
Все секции - 1 - 2 - 3 - 4 - 5 - 6

        (Часто для снижения ненужных затрат программисты предпочитают
        передавать функции указатель на структуру вместо самой структуры).
	Структуры часто	возвращаются функциями в ту область памяти, на
        которую указывает дополнительный поддерживаемый компилятором
        "скрытый" аргумент. Некоторые старые компиляторы используют для
        возврата структур фиксированную область памяти, хотя это делает
        невозможным рекурсивный вызов такой функции, что противоречит
        стандарту ANSI.

          Смотри: ANSI Разд.2.2.3 c. 13.

9.4	Эта программа работает правильно, но после завершения выдает дамп
	оперативной памяти. Почему?

		  struct list
			  {
			  char *item;
			  struct list *next;
			  }

		  /* Здесь функция main	*/

		  main(argc, argv)
		  ...

О:      Из-за пропущенной точки с запятой компилятор считает, что main
        возвращает структуру. (Связь структуры с функцией main трудно
        определить, мешает комментарий). Так как для возврата структур
        компилятор обычно использует в качестве скрытого параметра
        указатель, код, сгенерированный для main() пытается принять три
        аргумента, хотя передаются (в данном случае стартовым кодом С)
        только два.  См. также вопрос 17.21.

9.5	Почему нельзя сравнивать структуры?

О:      Не существует разумного способа сделать сравнение структур
        совместимым с низкоуровневой природой языка С. Побайтовое сравнение
        может быть неверным из-за случайных бит в неиспользуемых "дырках"
        (такое заполнение необходимо, чтобы сохранить выравнивание для
        последующих полей; см. вопросы 9.10 и 9.11). Почленное сравнение
        потребовало бы неприемлевого количества повторяющихся машинных
        инструкций в случае больших структур.

        Если необходимо сравнить две структуры, напишите для этого свою
	собственную  функцию. C++ позволит  создать оператор ==, чтобы
	связать	его с Вашей функцией.

          Смотри: K&R II Разд.6.2 c. 129; H&S Разд. 5.6.2 c. 103; ANSI
          Rationale разд. 3.3.9 c. 47.

9.7	Как читать/писать структуры из файла/в файл ?

О:	Писать структуры в файл	можно непосредственно с	помощью	fwrite:

	    fwrite((char *)&somestruct,	sizeof(somestruct), 1, fp);

	a cоответствующий вызов	fread прочитает	структуру из файла.
        Однако файлы, записанные таким образом будут _не_ особенно переносимы
        (см. вопросы 9.11 и 17.3). Заметьте также, что на многих системах
        нужно использовать в функции fopen флаг "b" .

9.7	Мне попалась программа,	в которой структура определяется так:

		  struct name
			  {
			  int namelen;
			  char name[1];
			  };

        затем идут хитрые манипуляции с памятью, чтобы массив name вел себя
        будто в нем несколько элементов. Такие манипуляции законны/мобильны?

О:      Такой прием популярен, хотя Деннис Ритчи назвал это "слишком
        фамильярным обращением с реализацией С". ANSI полагает, что выход за
        пределы объявленного размера члена структуры не полностью соответствует
        стандарту, хотя детальное обсуждение всех связанных с этим проблем
        не входит в задачу данных вопросов и ответов. Похоже, однако, что
        описанный прием будет одинаково хорошо принят всеми известными
        реализациями С. (Компиляторы, тщательно проверяющие границы массивов,
        могут выдать предупреждения).  Для страховки будет лучше объявить
        переменную очень большого размера чем очень малого. В нашем случае

		  ...
		  char name[MAXSIZE];
		  ...

	где MAXSIZE больше, чем	длина любого имени, которое будет сохранено
        в массиве name[]. (Есть мнение, что такая модификация будет
        соответствовать Стандарту).

          Смотри: ANSI Rationale Разд. 3.5.4.2 c. 54-5.

9.8	Как определить смещение	члена структуры	в байтах?

О:	Если это возможно, необходимо использовать макрос offsetof, который
	определен стандартом ANSI; см. <stddef.h>. Если	макрос отсутствует,
	предлагается такая (не на все 100% мобильная) его реализация

		#define	offsetof(type, mem) ((size_t) \
			((char *)&((type *) 0)->mem - (char *)((type *)	0)))

	Для некоторых компиляторов использование этого макроса может оказаться
	незаконным.
	О том, как использовать	offsetof(), смотри следующий вопрос.

          Смотри: ANSI Разд. 4.1.5, Rationale Разд. 3.5.4.2 c. 55.

9.9     Как осуществить доступ к членам структур по их именам во время
        выполнения программы?

О:	Создайте таблицу имен и	смещений, используя макрос offsetof().
	Смещение члена структуры b в структуре типа a равно

		  offsetb = offsetof(struct a, b)

	Если structp указывает на начало структуры, а b	- член структуры типа
        int, смещение которого получено выше, b может быть установлен
        косвенно с помощью

		  *(int	*)((char *)structp + offsetb) =	value;


9.10    Почему sizeof выдает больший размер структурного типа, чем я ожидал,
        как будто в конце структры лишние символы?

О:      Это происходит (возможны также внутренние "дыры" ; см.
        также вопрос 9.5), когда необходимо выравнивание при задании массива
        непрерывных структур.

9.11	Мой компилятор оставляет дыры в	структурах, что	приводит к потере
        памяти и препятствует "двоичному" вводу/выводу при работе с
        внешними файлами. Могу я отключить "дырообразование" или как-то
	контролировать выравнивание?

О:      В Вашем компиляторе, возможно, есть расширение, (например,
        #pragma), которое позволит это сделать, но стандартного способа не
        существует. См. также вопрос 17.3.

9.12	Можно ли задавать начальные значения объединений?

О:	Стандарт ANSI допускает	инициализацию первого члена объединения.
	Не существует стандартного способа инициализации других	членов.
	(и тем более нет такого	способа	для старых компиляторов, которые
        вообще не поддерживают какой-либо инициализации).

9.13    Как передать функциии структуру, у которой все члены - константы?

О:      Поскольку в языке С нет возможности создавать безымянные значения
        структурного типа, необходимо создать временную структуру.


	10. Декларации

10.1    Какой тип целочисленной переменной использовать?

О:	Если могут потребоваться большие числа,	(больше	32767 или меньше
        -32767), используйте тип long. Если нет, и важна экономия памяти
	(большие массивы или много структур), используйте short. Во всех
	остальных случаях используйте int. Если	важно точно определить
        момент переполнения и/или знак числа не имеет значения, используйте
        соответствующий тип unsigned. (Но будьте внимательны при совместном
        использовании типов signed и unsigned в выражениях). Похожие
        соображения применимы при выборе между float и double.

        Хотя тип char или unsigned char может использоваться как
        целочисленный тип наименьшего размера, от этого больше вреда,
        чем пользы из-за непредсказуемых перемен знака и возрастающего
        размера программы.

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

        Если необходимо объявить переменную _определенного_ размера,
        (единственной причиной тут может быть попытка удовлетворить внешним
        требованиям к организации памяти; см.,кроме того, вопрос 17.3),
        непременно изолируйте объявление соответствующим typedef.

10.2	Каким должен быть новый	64-битный тип на новых 64-битных машинах?

О:      Некоторые поставщики С компиляторов для 64-битных машин поддерживают
        тип long int длиной 64 бита. Другие же, опасаясь, что слишком многие
        уже написанные программы зависят от sizeof(int) == sizeof(long) == 32
        бита, вводят новый 64-битный тип long long (или __longlong).

	Программисты, желающие писать мобильные	программы, должны,
	следовательно, изолировать 64-битные типы с помощью средства typedef.
	Разработчики компиляторов, чувствующие необходимость ввести новый
	целочисленный тип большего размера, должны объявить его	как "имеющий
	по крайней мере	64 бит"	(это действительно новый тип, которого нет
	в традиционном С), а не	как "имеющий точно 64 бит".

10.3	У меня совсем не получается определение	связанного списка. Я пишу

		  typedef struct
			  {
			  char *item;
			  NODEPTR next;
			  } *NODEPTR;

	но компилятор выдает сообщение об ошибке. Может	структура в С
	содержать ссылку на себя?

О:	Структуры в С, конечно же, могут содержать указатели на	себя;
        обсуждение этого вопроса и пример в параграфе 6.5 K&R вполне
        проясняют этот вопрос. В приведенном тексте проблема состоит в том,
        что определение NODEPTR не закончено в том месте, где объявлется
        член структуры "next". Для исправления, снабдите сначала структуру
        тегом ("struct node"). Далее объявите "next" как "struct node
        *next;", и/или поместите декларацию typedef целиком до или целиком
        после объявления структуры.  Одно из возможных решений будет таким:

		  struct node
			  {
			  char *item;
			  struct node *next;
			  };

		  typedef struct node *NODEPTR;

        Есть по крайней мере три других одинаково правильных способа
        сделать то же самое.
	Сходная	проблема, которая решается примерно так	же, может возникнуть
        при попытке определить с помощью средства typedef пару cсылающихся
        друг на друга структур.

          Смотри: K&R I Разд. 6.5 c. 101; K&R II Разд. 6.5 c. 139; H&S
          Разд. 5.6.1 c. 102; ANSI Разд. 3.5.2.3 .

10.4    Как объявить массив из N указателей на функции, возвращающие
        указатели на функции возвращающие указатели на char?

О:      Есть по крайней мере три варианта ответа:

	  1.  char *(*(*a[N])())();

	  2.  Писать декларации	по шагам, используя typedef:

              typedef char *pc;   /* указатель на char */
              typedef pc fpc();   /* функция,возвращающая указатель на char */
              typedef fpc *pfpc;  /* указатель на.. см. выше */
              typedef pfpc fpfpc();   /* функция, возвращающая... */
              typedef fpfpc *pfpfpc;  /* указатель на... */
              pfpfpc a[N];            /* массив... */

	  3. Использовать программу cdecl, которая переводит с английского
	     на	С и наоборот.

		  cdecl> declare a as array of pointer to function returning
			   pointer to function returning pointer to char
		  char *(*(*a[])())()

        cdecl может также объяснить сложные декларации, помочь при явном
        приведении типов, и, для случая сложных деклараций, вроде только что
        разобранного, показать набор круглых скобок, в которые заключены
        аргументы. Версии cdecl можно найти в comp.sources.unix (см. вопрос
        17.12) и в K&R II.  Любая хорошая книга по С должна объяснять, как
        для понимания сложных деклараций, читать их "изнутри наружу",
        ("декларация напоминает использование").

          Смотри: K&R II Разд. 5.12 c. 122; H&S Разд. 5.10.1 c. 116.

10.5	Я моделирую Марковский процесс с конечным числом состояний, и у	меня
        есть набор функций для каждого состояния. Я хочу, чтобы смена
        состояний происходила путем возврата функцией указателя на функцию,
        соответветствующую следующему состоянию. Однако, я обнаружил
        ограничение в механизме деклараций языка С: нет возможности объявить
        функцию, возвращающую указатель на функцию, возвращающую указатель
        на функцию, возвращающую указатель на функцию...

О:	Да, непосредственно это	сделать	нельзя.	Пусть функция возвращает
	обобщенный указатель на	функцию, к которому перед вызовом функции
	будет применен оператор	приведения типа, или пусть она возвращает
	структуру, содержащую только указатель на функцию, возвращающую
	эту структуру.

10.6	Мой компилятор выдает сообщение	о неверной повторной декларации,
	хотя я только раз определил функцию и только раз вызвал.

О:	Подразумевается, что функции, вызываемые без декларации	в области
	видимости (или до такой	декларации), возвращают	значение типа int.
	Это приведет к противоречию, если впоследствии функция декларирована
	иначе. Если функция возвращает нецелое значение, она должна быть
        объявлена до того как будет вызвана.

          Смотри: K&R I Разд. 4.2 c. 70; K&R II Разд. 4.2 c. 72; ANSI
          Разд. 3.3.2.2 .

10.7    Как наилучшим образом декларировать и определить глобальные
        переменные?

О:	Прежде всего заметим, что хотя может быть много	_деклараций_ (и	во
        многих файлах) одной "глобальной" (строго говоря "внешней" )
        переменной, (или функции), должно быть всего одно _определение_.
        (Определение - это такая декларация, при которой действительно
        выделяется память для переменной, и присваивается, если нужно,
        начальное значение).  Лучше всего поместить определение в какой-то
        главный (для программы или ее части) .c файл, с внешней декларацией в
        головном файле .h, который при необходимости подключается с помощью
        #include. Файл, в котором находится определение переменной, также
        должен включать головной файл с внешней декларацией, чтобы компилятор
        мог проверить соответствие декларации и определения.

	Это правило обеспечивает высокую мобильность программ и	находится в
        согласии с требованиями стандарта ANSI C. Заметьте, что многие
        компиляторы и компоновщики в системе UNIX используют "общую модель",
        которая разрешает многократные определения без инициализации.
        Некоторые весьма странные компиляторы могут требовать явной
        инициализации, чтобы отличить определение от внешней декларации.

        С помощью препроцессорного трюка можно устроить так, что декларация
        будет сделана лишь однажды, в головном файле, и она c помощью #define
        "превратится" в определение точно при одном включении головного
        файла.

          Смотри: K&R I Разд. 4.5 c. 76-7; K&R II Разд. 4.4 c. 80-1;
          ANSI Разд. 3.1.2.2 (особенно Rationale), Разд. 3.7, 3.7.2,
          Разд. F.5.11; H&S Разд. 4.8 c. 79-80; CT&P Разд. 4.2 c. 54-56.


10.8	Что означает ключевое слово extern при декларации функции?

О:      слово extern при декларации функции может быть использовано из
        соображений хорошего стиля для указания на то, что определение
        функции, возможно, находится в другом файле. Формально между

		  extern int f();
	  и
		  int f();

	нет никакой разницы.

          Смотри: ANSI Разд. 3.1.2.2 .

10.9    Я, наконец, понял, как объвлять указатели на функции, но как их
	инициализировать?

О:	Используйте нечто такое

		  extern int func();
		  int (*fp)() =	func;

	Когда имя функции появляется в выражении, но функция не	вызывается
        (то есть, за именем функции не следует "(" ), оно "сворачивается",
        как и в случае массивов, в указатель (т.е. неявным образом записанный
        адрес).

        Явное объявление функции обычно необходимо, так как неявного
        объявления внешней функции в данном случае не происходит (опять-таки
        из-за того, что за именем функции не следует "(" ).

10.10	Я видел, что функции вызываются	с помощью указателей и просто как
	функции. В чем дело?

О:      По первоначальному замыслу создателя С указатель на функцию должен
        был "превратиться" в настоящую функцию с помощью оператора *
        и дополнительной пары круглых скобок для правильной интерпретации.

		  int r, func(), (*fp)() = func;
		  r = (*fp)();

        На это можно возразить, что функции всегда вызываются с помощью
        указателей, но что "настоящие" функции неявно превращаются в
        указатели (в выражениях, как это происходит при инициализациях) и это
        не приводит к каким-то проблемам. Этот довод, широко распространенный
        компилятором pcc и принятый стандартом ANSI, означает, что выражение

		  r = fp();

        работает одинаково правильно, независимо от того, что такое fp -
	функция	или указатель на нее. (Имя всегда используется однозначно;
	просто невозможно сделать что-то другое	с указателем на	функцию,
	за которым следует список аргументов, кроме как	вызвать	функцию).
	Явное задание *	безопасно и все	еще разрешено (и рекомендуется,
	если важна совместимость со старыми компиляторами).

          Смотри: ANSI Разд. 3.3.2.2 c. 41, Rationale c. 41.

10.11	Где может пригодиться ключевое слово auto?

О:	Нигде, оно вышло из употребления.


	11. Cтандартный	ввод/вывод.


11.1	Что плохого в таких строках:

		  char c;
		  while((c = getchar())	!= EOF)...

О:	Во-первых, переменная, которой присваивается возвращенное getchar
        значение, должна иметь тип int. getchar может вернуть все возможные
        значения для символов, в том числе EOF. Если значение, возвращенное
        getchar присваивается переменной типа char, возможно либо обычную
        литеру принять за EOF, либо EOF исказится (особенно если использовать
        тип unsigned char) так, что распознать его будет невозможно.

          Смотри: CT&P Разд.5.1 c. 70.

11.2	Как напечатать символ '%' в строке формата printf? Я попробовал
	\%, но из этого	ничего не вышло.

О:	Просто удвойте знак процента %%	.

          Смотри: K&R I Разд. 7.3 c. 147; K&R II Разд. 7.2 c. 154; ANSI
          Разд. 4.9.6.1 .

11.3	Почему не работает scanf("%d",i)?

О:      Для функции scanf необходимы адреса переменных, по которым будут
        записаны данные, нужно написать scanf("%d", &i);


11.4	Почему не работает

		  double d;
		  scanf("%f", &d);

О:	scanf использует спецификацию формата %lf для значений типа double
	и %f для значений типа float. (Обратите	внимание на несходство с
	printf,	где в соответствии с правилом расширения типов аргументов
	спецификация %f	используется как для float, так	и для double).

11.5    Почему фрагмент программы

		  while(!feof(infp)) {
			  fgets(buf, MAXLINE, infp);
			  fputs(buf, outfp);
		  }

        дважды копирует последнюю строку?

О:      Это Вам не Паскаль. Символ EOF появляется только _после_ попытки
        прочесть, когда функция ввода натыкается на конец файла.
        Чаще всего необходимо просто проверять значение, возвращаемое
        функцией ввода, (в нашем случае fgets); в использовании feof()
        обычно вообще нет необходимости.

11.6	Почему все против использования	gets()?

О:      Потому что нет возможности предотвратить переполнение буфера,
        куда читаются данные, ведь функции gets() нельзя сообщить его
        размер.  Смотрите вопрос 3.1, в котором приведен фрагмент программы,
        показывающий замену gets() на fgets().


11.7    Почему переменной errno присваивается значение ENOTTY после вызова
	printf()?

О:      Многие реализации стандартной библиотеки ввода/вывода несколько
        изменяют свое поведение, если стандартное устройство вывода -
        терминал. Чтобы определить тип устройства, выполняется операция,
        которая оканчивается неудачно (c сообщением ENOTTY), если устройство
        вывода - не терминал. Хотя вывод завершается успешно, errno все же
        содержит ENOTTY.

          Смотри: CT&P Разд. 5.4 c. 73.

11.8	Запросы	моей программы,	а также	промежуточные результаты не всегда
	отображаются на	экране,	особенно когда моя программа передает данные
	по каналу (pipe) другой	программе.

О:	Лучше всего явно использовать fflush(stdout), когда непременно
	нужно видеть то, что выдает программа. Несколько механизмов пытаются
	"в нужное время" осуществить fflush, но, похоже, все это правильно
        работает в том случае, когда stdout - это терминал. (см. вопрос
        11.7).

11.9    При чтении с клавиатуры функцией scanf возникает чувство, что
        программа зависает, пока я перевожу строку.

О:      Функция scanf была задумана для ввода в свободном формате,
        необходимость в котором возникает редко при чтении с клавиатуры.
        Что же касается ответа на вопрос, то символ "\n" в форматной строке
        вовсе не означает, что scanf будет ждать перевода строки. Это
        значит, что scanf будет читать и отбрасывать все встретившиеся подряд
        пробельные литеры (т.е. символы пробела, табуляции, новой строки,
        возврата каретки, вертикальной табуляции и новой страницы).
        Похожее затруднение случается, когда scanf "застревает", получив
        неожиданно для себя нечисловые данные. Из-за подобных проблем часто
        лучше читать всю строку с помощью fgets, а затем использовать sscanf
        или другие функции, работающие со строками, чтобы интерпретировать
        введенную строку по частям. Если используется sscanf, не забудьте
        проверить возвращаемое значение для уверенности в том, что число
        прочитанных переменных равно ожидаемому.

11.10   Я пытаюсь обновить содержимое файла, для чего использую fopen в
        режиме "r+", далее читаю строку, затем пишу модифицированную строку
        в файл, но у меня ничего не получается.

О:      Непременно вызовите fseek перед записью в файл. Это делается
	для возврата к началу строки, которую Вы хотите	переписать; кроме
	того, всегда необходимо	вызвать	fseek или fflush между чтением и
	записью	при чтении/записи в режимах "+". Помните также,	что литеры
	можно заменить лишь точно таким	же числом литер. См. также
	вопрос 17.4.

          Смотри: ANSI Разд. 4.9.5.3 c. 131.

11.11	Как мне	прочитать одну литеру, не дожидаясь нажатия RETURN?

О:	Смотри вопрос 16.1

11.12   Как мне отменить ожидаемый ввод, так, чтобы данные, введенные
        пользователем, не читались при следующем запросе? Поможет ли здесь
	fflush(stdin)?

О:	fflush определена только для вывода. Поскольку определение "flush"
        ("смывать") означает завершение записи символов из буфера (а не
        отбрасывание их), непрочитанные при вводе символы не будут уничтожены
        с помощью fflush.  Не существует стандартного способа игнорировать
        символы, еще не прочитанные из входного буфера stdio.  Не видно также,
        как это вообще можно сделать, поскольку непрочитанные символы могут
        накапливаться в других, зависящих от операциооной системы, буферах.

11.13   Как перенаправить stdin или stdout в файл?

О:	Используйте freopen.

11.14	Если я использовал freopen, то как вернуться назад к stdout (stdin)?

О:      Если необходимо переключаться между stdin (stdout) и файлом,
        наилучшее универсальное решение - не спешить использовать freopen.
        Попробуйте использовать указатель на файл, которому можно по желанию
        присвоить то или иное значение, оставляя значение stdout (stdin)
        нетронутым.

11.15	Как восстановить имя файла по указателю	на открытый файл?

О:	Это проблема, вообще говоря, неразрешима. В случае операционной
	системы	UNIX, например,	потребуется поиск по всему диску (который,
	возможно, потребует специального разрешения), и	этот поиск окончится
	неудачно, если указатель на файл был каналом (pipe) или	был связан
        с удаленным файлом. Кроме того, обманчивый ответ будет получен для
        файла со множественными связями.  Лучше всего самому запоминать имена
        при открытии файлов (возможно, используя специальные функции,
        вызываемые до и после fopen);

	12. Библиотечные функции.

12.1	Почему strncpy не всегда завершает строку-результат символом '\0'?

О:	strncpy	была задумана для обработки теперь уже устаревших структур
	данных - "строк" фиксированной длины, не обязательно завершающихся
        символом  '\0'.  И, надо сказать, strncpy не совсем удобно
        использовать в других случаях, поскольку часто придется добавлять
        символ '\0' вручную.

12.2	Я пытаюсь сортировать массив строк с помощью qsort, используя
	для сравнения strcmp, но у меня	ничего не получается.

О:	Когда Вы говорите о "массиве строк", то, видимо, имеете	в виду
	"массив	указателей на char". Аргументы функции сравнения, работающей
	в паре с qsort - это указатели на сравниваемые объекты,	в данном
	случае - указатели на указатели	на char. (Конечно, strcmp работает
	просто с указателями на	char).

        Аргументы процедуры сравнения описаны как "обобщенные указатели"
        const void * или char *. Они должны быть превращены в то, что они
        представляют на самом деле, т.е. (char **) и дальше нужно раскрыть
        ссылку с помощью * ; тогда strcmp получит именно то, что нужно для
        сравнения.  Напишите функцию сравнения примерно так:

	  int pstrcmp(p1, p2)	  /* сравнить строки, используя	указатели */
	  char *p1, *p2;	  /* const void	* для ANSI C */
	  {
              return strcmp(*(char **)p1, *(char **)p2);
	  }

        Имейте в виду, что в K&R II Разд. 5.11 обсуждается функция qsort,
	которая	отличается от стандартной.

12.3	Сейчас я пытаюсь сортировать массив структур с помощью qsort.
	Процедура сравнения, которую я использую, принимает в качестве
	аргументов указатели на	структуры, но компилятор выдает	сообщение
	о неверном типе	функции	сравнения. Как мне преобразовать аргументы
	функции, чтобы подавить	сообщения об ошибке?

О:	Преобразования должны быть сделаны внутри функци сравнения, которая
	должна быть объявлена как принимающая аргументы	типа "обобщенных
        указателей (const void * или char *) как это описано в вопросе 12.2.
	Функция	сравнения может	выглядеть так:

	   int mystructcmp(p1, p2)
	   char	*p1, *p2;	   /* const void * для ANSI C */
	   {
		   struct mystruct *sp1	= (struct mystruct *)p1;
		   struct mystruct *sp2	= (struct mystruct *)p2;

		   /* теперь сравнивайте sp1->что-угодно и  sp2-> ... */
	   }

        (С другой стороны, если сортируются указатели на структуры,
        необходима косвенная адресация, как в вопросе 12.2:

                    sp1 = *(struct mystruct **)p1 .)

12.4	Как преобразовать числа	в строки (операция, противоположная atoi)?
	Есть ли	функция	itoa?

О:	Просто используйте sprintf. (Необходимо	будет выделить память для
	результата, см.	вопросы	3.1 и 3.2. Беспокоиться, что sprintf -
	слишком	сильное	средство, которое может	привести к перерасходу
	памяти и увеличению времени выполнения,	нет оснований. На практике
	sprintf	работает хорошо).

          Смотри: K&R I Разд.3.6 c. 60; K&R II Разд.3.6 c. 64.

12.5    Как получить дату или время в С программе?

О:	Просто используйте функции time, ctime,	и/или localtime. (Эти
	функции	существуют многие годы,	они включены в стандарт	ANSI).
	Вот простой пример:

		  #include <stdio.h>
		  #include <time.h>

		  main()
		  {
			  time_t now = time((time_t *)NULL);
			  printf("It's %.24s.\n", ctime(&now));
			  return 0;
		  }

          Смотри: ANSI Разд. 4.12 .

12.6    Я знаю, что библиотечная функция localtime разбивает значение time_t
        по отдельным членам структуры tm, а функция ctime превращает time_t в
        строку символов.  А как проделать обратную операцию перевода
        структуры tm или строки символов в значение time_t?

О:      Стандарт ANSI определяет библиотеную функцию mktime, которая
        преобразует структуру tm в time_t. Если Ваш компилятор не
        поддерживает mktime, воспользуйтесь одной из общедоступных версий
        этой функции.

	Перевод	строки в значение time_t выполнить сложнее из-за большого
	количества форматов дат	и времени, которые должны быть распознаны.
        Некоторые компиляторы поддерживают функцию strptime; другая
        популярная функция - partime широко распространяется с пакетом RCS,
        но нет уверенности, что эти функции войдут в Стандарт.

          Смотри: K&R II Разд. B10 c. 256; H&S Разд. 20.4 c. 361; ANSI
          Разд. 4.12.2.3 .

12.7    Как прибавить n дней к дате? Как вычислить разность двух дат?

О:	Вошедшие в стандарт ANSI/ISO функции mktime и difftime могут помочь
	при решении обеих проблем. mktime() поддерживает ненормализованные
	даты, т.е. можно прямо взять заполненную структуру tm, увеличить или
	уменьшить член tm_mday,	затем вызвать mktime(),	чтобы нормализовать
	члены year, month, и day  (и преобразовать в значение time_t).
	difftime() вычисляет разность в	секундах между двумя величинами
	типа time_t. mktime() можно использовать для вычисления	значения
	time_t разности	двух дат. (Заметьте, однако, что все эти приемы
	возможны лишь для дат, которые могут быть представлены значением
        типа time_t; кроме того, из-за переходов на летнее и зимнее время
        продолжительность дня не точно равна 86400 сек.).
        Cм. также вопросы 12.6 и 17.28.

          Смотри: K&R II Разд. B10 c. 256; H&S Разд. 20.4, 20.5
          c. 361-362; ANSI Разд. 4.12.2.2, 4.12.2.3 .

12.8	Мне нужен генератор случайных чисел.

О:	В стандартной библиотеке С есть	функция	rand().	Реализация этой
        функции в Вашем компиляторе может не быть идеальной, но и создание
        лучшей функции может оказаться очень непростым.

          Смотри: ANSI Разд. 4.10.2.1 c. 154; Knuth Vol. 2 Chap. 3
          c. 1-177.

12.9	Как получить случайные целые числа в определенном диапазоне?

О:	Очевидный способ

		  rand() % N

        где N, конечно, интервал, довольно плох, ведь поведение младших
        бит во многих генераторах случайных чисел огорчает своей
        неслучайностью.  (См. вопрос 12.11).  Лучше попробуйте нечто вроде

               (int)((double)rand() / ((double)RAND_MAX + 1) * N)

        Если Вам не нравится употребление чисел с плавающей точкой,
        попробуйте

		  rand() / (RAND_MAX / N + 1)

	Оба метода требуют знания RAND_MAX (согласно ANSI, RAND_MAX определен
	в <stdlib.h>. Предполагается, что N много меньше RAND_MAX.

12.10   Каждый раз при запуске программы функция rand() выдает одну и ту же
	последовательность чисел.

О:      Можно вызвать srand() для случайной инициализации генератора
        случайных чисел. В качестве аргумента для srand() часто используется
        текущее время, или время, прошедшее до нажатия на клавишу (хотя едва
        ли существует мобильная процедура определения времен нажатия на
        клавиши; см.  вопрос 16.10).

          Смотри: ANSI Разд. 4.10.2.2 c. 154.

12.11   Мне необходима случайная величина, имеющая два значения true/false.
        Я использую rand() % 2, но получается неслучайная последовательность
	0,1,0,1,0....

О:      Некачественные генераторы случайных чисел (попавшие, к несчастью, в
        состав некоторых компиляторов) не очень то случайны, когда речь
        идет о младших битах. Попробуйте использовать старшие биты.
        См. вопрос 12.9.

12.12	Я пытаюсь перенести на	      О:  Эти подпрограммы в разной
        другую систему старую             степени устарели. Необходимо
	программу. Почему я		  использовать
	получаю	сообщения
	"undefined external"
	для

	  index?			  используйте strchr.
	  rindex?			  используйте strrchr.
	  bcopy?			  используйте memmove,
					  поменяв местами первый
					  и второй аргументы (см. также
					  вопрос 5.15).
	  bcmp?				  используйте memcmp.
	  bzero?			  используйте memset, со вторым
					  аргументом, равным 0.

12.13   Я все время получаю сообщения об ошибках - не определены библиотечные
        функции, но я включаю все необходимые головные файлы.

О:      Иногда (особенно для нестандартных функций) следует явно указывать,
        какие библиотеки нужны при компоновке программы.
        См. также вопрос 15.2.

12.14   Я по-прежнему получаю сообщения, что библиотечные функции не
        определены, хотя и использую ключ -l, чтобы явно указать библиотеки
	во время компоновки.

О:	Многие компоновщики делают один	проход по списку объектных файлов
	и библиотек, которые Вы	указали, извлекая из библиотек только те
	функции, удовлетворяющие ссылки, которые _к этому моменту_ оказались
	неопределенными. Следовательно,	порядок	относительно объектных файлов,
	в котором перечислены библиотеки, важен; обычно	просмотр библиотек
	нужно делать в самом конце. (Например, в операционной системе UNIX

Секция 4 из 6 - Предыдущая - Следующая

Вернуться в раздел "Языки C/C++/Builder" - Обсудить эту статью на Форуме
Главная - Поиск по сайту - О проекте - Форум - Обратная связь

© faqs.org.ru