Партнерка на США и Канаду по недвижимости, выплаты в крипто
- 30% recurring commission
- Выплаты в USDT
- Вывод каждую неделю
- Комиссия до 5 лет за каждого referral
return p;
}
В этом примере все обращения к элементам двумерного массива аналогичны случаю массива с постоянными границами. Следует обратить внимание на то, что динамическая память выделяется для одномерного массива из элементов типа double[STRLEN], то есть строк двумерного массива, которые должны иметь фиксированную длину.
11.3.3. Общий случай двумерного массива
Следующий вариант представления динамического двумерного массива позволяет использовать привычную индексацию двумерного массива и передавать массив в функцию, но требует специальной функции-конструктора для инициализации этого массива и функции-деструктора для освобождения памяти от массива. Массив представляется в виде одномерного вектора указателей на строки двумерного массива. Каждой строке выделяется соответствующий блок памяти в конструкторе:
#include <stdio. h>
#include <stdlib. h>
#include <malloc. h>
#define MAXVAL 1000
void *Malloc ( size_t size );
double **MakeMatr ( size_t n, size_t m );
void DelMatr ( double *Matr[] );
void RandomMatr ( double *Matr[], int n, int m );
void OutMatr ( char *name,
double *Matr[], int n, int m );
void main( void )
{
int n = 5, m = 6;
double **A;
/* Выделение памяти под матрицу */
A = MakeMatr(n, m);
/* Заполнение матрицы значениями и распечатка */
RandomMatr(A, n, m);
OutMatr("A", A, n, m);
/* освобождение памяти */
DelMatr(A);
}
void RandomMatr (double *Matr[], int n, int m)
{
int i, j;
for(i = 0; i < n; i++)
for(j = 0; j < m; j++)
Matr[i][j] = random(MAXVAL) + 1;
}
void OutMatr( char *name, double *Matr[], int n, int m )
{
int i, j;
printf("\nМатрица %s\n----\n", name);
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
printf("%8.1lf ", Matr[i][j]);
printf("\n");
}
}
void *Malloc( size_t size )
{
void *p = malloc(size);
if( !p )
{ printf("Недостаточно памяти!\n"); exit(1); }
return p;
}
/* Конструктор матрицы */
double **MakeMatr( size_t n, size_t m )
{
double **Matr; size_t i;
Matr = (double**) Malloc( (n + 1) * sizeof(double *) );
for(i = 0; i < n; i++)
Matr[i] = (double *) Malloc( m * sizeof(double) );
Matr[n] = NULL;
return Matr;
}
/* Деструктор матрицы */
void DelMatr( double *Matr[] )
{
size_t i;
for(i = 0; Matr[i]; i++) free(Matr[i]);
free(Matr);
}
Вначале в функции-конструкторе MakeMatr() выделяется вектор памяти размером n+1 элементов для хранения указателей на double. Затем для каждой из n строк массива выделяется память и адрес ее записывается в ранее выделенный вектор указателей. В последний элемент вектора заносится величина NULL, которая в деструкторе будет сигнализировать о конце вектора. Иначе в деструктор пришлось бы передавать дополнительный параметр n.
Рассмотренная схема выделения памяти не имеет практических ограничений даже для 16-разрядной сегментной модели памяти. Нужно лишь чтобы размер строки и размер вектора указателей не превышал сегмента.
Если массив целиком укладывается в сегмент, то для работы с ним можно предложить следующую схему с меньшими накладными расходами на выделение памяти:
/* Конструктор матрицы */
double **MakeMatr( size_t n, size_t m )
{
double **Matr; size_t i;
Matr = (double**) Malloc( n * sizeof(double *)
+ n * m * sizeof(double) );
for(i = 0; i < n; i++)
Matr[i] = (double *)(Matr + n) + i * m;
return Matr;
}
/* Деструктор матрицы */
void DelMatr( double *Matr[] )
{
free(Matr);
}
При такой организации матрицы память выделяется одним блоком, в котором находится и вектор указателей и сами элементы двумерного массива. Работа с этими матрицами ведется точно также как и с предыдущими.
При некоторых ухищрениях в выделенную область памяти можно поместить и информацию о размерах матрицы, жестко связав размеры с матрицей и таким образом избежать множества математических ошибок, связанных с использованием матриц с несогласованными размерами:
#include <stdio. h>
#include <stdlib. h>
#include <malloc. h>
#define MAXVAL 1000
void *Malloc ( size_t size );
double **MakeMatr ( size_t n, size_t m );
void DelMatr ( double *Matr[] );
size_t GetN ( double *Matr[] );
size_t GetM ( double *Matr[] );
void RandomMatr ( double *Matr[] );
void OutMatr ( char *name, double *Matr[] );
void main( void )
{
int n = 5, m = 6;
double **A;
/* Выделение памяти под матрицу */
A = MakeMatr(n, m);
/* Заполнение матрицы значениями и распечатка */
RandomMatr( A );
OutMatr("A", A );
/* освобождение памяти */
DelMatr(A);
}
void RandomMatr ( double *Matr[] )
{
int i, j, n = GetN(Matr), m = GetM(Matr);
for(i = 0; i < n; i++)
for(j = 0; j < m; j++)
Matr[i][j] = random(MAXVAL) + 1;
}
void OutMatr( char *name, double *Matr[] )
{
int i, j, n = GetN(Matr), m = GetM(Matr);
printf("\nМатрица %s\n----\n", name);
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
printf("%8.1lf ", Matr[i][j]);
printf("\n");
}
}
void * Malloc( size_t size )
{
void *p = malloc(size);
if( !p )
{ printf("Недостаточно памяти!\n"); exit(1); }
return p;
}
/* Конструктор матрицы */
double **MakeMatr( size_t n, size_t m )
{
double **Matr; size_t i;
Matr = (double**) Malloc( 2 * sizeof(size_t)
+ n * sizeof(double *) );
(size_t *)Matr += 2;
for(i=0; i<n; i++)
Matr[i] = (double *) Malloc( m * sizeof(double) );
Matr[n] = NULL;
*( (size_t *)Matr - 2 ) = n;
*( (size_t *)Matr - 1 ) = m;
return Matr;
}
size_t GetN( double *Matr[] )
{
return *( (size_t *)Matr - 2 );
}
size_t GetM( double *Matr[] )
{
return *( (size_t *)Matr - 1 );
}
/* Деструктор матрицы */
void DelMatr( double *Matr[] )
{
size_t i, n = GetN(Matr);
for(i = 0; i < n; i++) free(Matr[i]);
free( (size_t *)Matr - 2 );
}
В конструкторе теперь выделяется память для хранения n указателей на double и для двух величин типа size_t, которые служат для хранения размеров матрицы. Выделять дополнительный элемент для занесения NULL теперь нет необходимости, так как теперь с помощью функций GetN() и GetM() можно получить соответствующие размеры массива. Способ индексации не изменяется и она по-прежнему выполняется в стиле индексации двумерных массивов.
Такое же скрытое хранение размеров матрицы можно организовать и в случае создания массива в единственном блоке памяти, меньшим сегмента.
11.4. Особенности работы с массивами большого размера
Массивами большого размера будем называть такие массивы, которые не помещаются в сегменте памяти.
Для больших двумерных массивов вариантом решения проблемы может служить предложенная в предыдущем разделе схема массива в виде вектора указателей на строки.
Если эта схема неприемлема или если массив одномерный, то можно воспользоваться специальным атрибутом указателя huge, который имеется у всех компиляторов, ориентированных на IBM PC.
Следует иметь в виду, что использование модели Large или указателей типа far недостаточно для корректной работы с большими массивами. Это происходит потому, что, во-первых, при выполнении действий над far указателями их сегментная часть не меняется, во-вторых, описанные выше функции выделения памяти не могут выделить память больше одного сегмента.
При работе с массивами большого размера, соответствующий указатель должен описываться с ключевым словом huge, даже в модели памяти Huge (указатели по умолчанию - far), например:
double huge *A;
при этом обеспечивается автоматическая нормализация указателя при переходе от сегмента к сегменту. Естественно, операция нормализации может отнимать довольно значительное время.
Для работы с большими блоками памяти используются специальные функции с префиксом far.
Функция выделения памяти:
void far *farmalloc(unsigned long size);
Выделение памяти с обнулением:
void far *farcalloc(unsigned long nitems,
unsigned long size);
Изменение размера ранее выделенного блока памяти:
void far *farrealloc(void far *block,
unsigned long nbytes);
Освобождение блока памяти:
void farfree(void far *block);
Получение информации о верхнем свободном блоке памяти:
unsigned long farcoreleft(void);
Работа вышеперечисленных функций совпадает с функциями рассмотренными ранее и не имеющими префикса far. В любых моделях памяти они оперируют четырехбайтовыми указателями и могут выделять блок памяти больше максимального размера сегмента. Но если требуется корректная индексация, то соответствующий указатель должен быть обязательно huge.
Следующая программа иллюстрирует использование массива размером большим максимального размера сегмента:
#include <stdio. h>
#include <stdlib. h>
#include <conio. h>
#include <malloc. h>
void far FarMalloc( unsigned long size )
{
void far *p = farmalloc(size);
if( !p )
{ printf("Недостаточно памяти!\n"); exit(1); }
return p;
}
void main(void)
{
double huge *A;
unsigned long i, maxN;
/* Выделение максимального блока памяти */
A = (double huge *) FarMalloc( maxN = farcoreleft() );
maxN /= sizeof(double);
printf("Размер массива: %lu\n", maxN);
getch();
/* Заполняем массив */
for(i = 0; i < maxN; i++) A[i] = i;
/* Печатаем часть массива.*/
for(i = 0; i < 1000; i++)
{
printf("%10.3lf ", A[i]);
if( (i + 6) % 5 == 0 ) printf("\n");
if( (i + 121) % 120 == 0 ) { getch(); clrscr(); }
}
printf("\n");
/* Освобождение памяти */
farfree(A);
}
Если в этой программе поменять атрибут huge на far, то вся адресация будет выполняться по модулю равному размеру сегмента и результат будет неверным.
12. Модульное программирование в системе Turbo C
Большинство современных программных продуктов невозможно разработать и отладить, размещая все функции проекта в одном файле. Ранние версии компиляторов даже имели ограничение на размер файла исходного текста в 64 килобайта. Поэтому, компиляторы всех фирм без исключения имеют средства модульного программирования, позволяющие разрабатывать, тестировать и отлаживать проект по частям, разбивая его на модули.
Модуль представляет собой наименьшую неделимую часть программы. Модуль только целиком может быть добавлен к программе и только целиком из нее удален, даже в тех случаях, когда он содержит функции, к которым нет обращений.
Каждый файл исходного текста программы на языке СИ после компиляции образует отдельный модуль.
Программа собирается из отдельных модулей, находящихся на диске, и библиотечных модулей с помощью специальной программы, называемой компоновщиком или редактором связей. Компоновщик можно вызвать явно из командной строки MS DOS, или неявно из интегрированной среды разработки программ.
Имена всех модулей и библиотек функций, используемых в проекте, заносятся в файл проекта с помощью средств конкретной системы программирования и, в дальнейшем, среда разработки программ автоматически отслеживает все изменения в исходных файлах проекта, автоматически компилирую и собирая новые версии программы.
12.1. Обеспечение корректной стыковки модулей
Основной проблемой модульного программирования является обеспечение корректной стыковки отдельных модулей. Язык СИ имеет все необходимые для этого средства. Однако, ни один компилятор языка СИ не в состоянии отследить ошибки стыковки модулей, если программист не создал всех необходимых для этого условий. Рассмотрим основные правила организации модулей при программировании на языке СИ.
Каждый модуль программы должен описываться двумя файлами. Первый файл должен содержать текст функций модуля, то есть реализацию модуля и обычно имеет расширение *.c. Второй файл содержит описание интерфейса модуля, то есть прототипы функций модуля и описания глобальных переменных модуля с помощью оператора extern. Второй файл обычно имеет расширение *.h и называется заголовочным файлом.
В некоторых случаях может оказаться целесообразным наличие большого количества маленьких модулей, например, с разного рода сервисными библиотечными функциями. В этом случае допускается использовать один заголовочный файл для описания интерфейсов нескольких модулей.
Во всех случаях надо следить за тем, чтобы прототип любой функции имелся лишь в одном экземпляре и находился в заголовочном файле.
Файл с исходным текстом функций модуля (реализацией модуля) должен содержать директиву препроцессора #include подключающую заголовочный файл, описывающий интерфейс данного модуля. Это необходимо для того, чтобы компилятор был в состоянии проверить соответствие интерфейса модуля его реализации.
При компиляции проекта, состоящего из нескольких модулей, в обязательном порядке должно быть включена возможность выдачи предупреждения об использовании функций без прототипа. Это необходимо для того, чтобы заставить программиста вставить в каждый файл исходного текста, в котором используются функции некоторого модуля, директиву препроцессора #include подключающую заголовочный файл, описывающий интерфейс используемых функций. Это необходимо для того, чтобы компилятор был в состоянии проверить корректность использования функций модуля.
12.2. Создание библиотек функций
При разработке программных проектов, состоящих из большого числа модулей, целесообразно пользоваться библиотеками модулей. Каждая такая библиотека обычно имеет расширение *.lib и может быть включена в проект с помощью средств поддержки проекта.
Особенностью использования библиотек является тот факт, что компоновщик будет обращаться к библиотеке и выбирать оттуда необходимые модули только по мере необходимости, то есть, если имеются ссылки к функциям библиотечного модуля.
Следует помнить, что модуль будет выбран из библиотеки и подключен к программе всегда целиком, даже если на самом деле программе требуется всего лишь одна функция.
Для создания библиотек функций используются программы, называемые библиотекарями. В системе программирования фирмы Borland библиотекарь имеет имя tlib и может быть вызван из командной строки следующим образом:
"tlib " имя_библ ["/C"] ["/E"] команды [, файл_огл]
где имя_библиотеки - имя файла с создаваемой или модифицируемой библиотекой;
команды - последовательность команд модифицирующих библиотеку;
файл_огл - имя файла, в который будет помещено оглавление библиотеки;
/C - ключ, при наличии которого библиотекарь различает прописные и строчные буквы в именах внешних функций;
/E - клич, при наличии которого библиотекарь создает расширенный словарь.
Каждая команда модификации библиотеки имеет следующую форму:
("+" | "-" | "*" | "-+" | "-*") имя_модуля
где имя_модуля - имя скомпилированного модуля без расширения.
Символ "+" добавляет новый модуль в библиотеку, символ "-" удаляет модуль из библиотеки, символ "*" извлекает модуль из библиотеки без его удаления, символы "-+" или "+-" заменяют модуль в библиотеке, символы "-*" или "*-" извлекают и удаляют модуль из библиотеки.
Например, следующий вызов библиотекаря
tlib graph +line +draw +point, graph
создаст в текущем каталоге новую библиотеку с именем graph. lib, в которую будут добавлены модули line. obj, draw. obj и point. obj. Кроме того будет создан файл с оглавлением библиотеки, имеющий имя graph. lst.
13. Некоторые библиотечные функции языка Си
В этом разделе дается краткая информация о часто используемых функциях языка СИ.
13.1. Функции консольного ввода/вывода (уникальны для TC)
Рассматриваемые ниже функции уникальны для компиляторов фирмы Borland. Их прототипы находятся в файле <conio. h>.
Вертикальная позиция курсора в текущем текстовом окне (начиная с 1)
int wherey(void);
Горизонтальная текущая позиция курсора в текущем текстовом окне (начиная с 1)
int wherex(void);
Позиционирование курсора в текстовом окне
void gotoxy(int x, int y);
Установка активного текстового окна
void window(int left, int top, int right, int bottom);
Верхний левый угол экрана имеет координаты (1,1). Теперь для ввода доступно только это окно. Внешне это никак не проявляется. Действует на последующие операторы вывода текстовой информации.
Стирание текущего текстового окна
void clrscr(void);
Стереть до конца строки в текущем текстовом окне
void clreol(void);
Удалить строку в текущем текстовом окне
void delline(void);
Вставить пустую строку в текстовое окно в позицию курсора
void insline(void);
Строки ниже позиции курсора смещаются вниз, а последняя строка теряется.
Выбрать новый цвет фона символа в текстовом режиме
void textbackground(int newcolor);
Выбрать новый цвет символа в текстовом режиме
void textcolor(int newcolor);
Установить атрибут символа для текстовых функций вывода
void textattr(int newattr);
Скопировать текст с текстового экрана в память
int gettext(int left, int top, int right, int bottom,
void *destin);
Параметр destin должен указывать на реально существующий буфер памяти, имеющий размер достаточный для размещения фрагмента текста с атрибутами каждого символа. Функция возвращает не ноль при успешном копировании.
Скопировать текст из памяти на текстовый экран
int puttext(int left, int top, int right, int bottom,
void *source);
Функция выполняет операцию, обратную к предыдущей. Возвращает не ноль в случае успеха.
Копирует текст на экране с одной прямоугольной области в другую
int movetext(int left, int top, int right, int bottom,
int destleft, int desttop);
Возвращает не ноль в случае успеха.
Получить символ с консоли без эха
int getch(void);
Получить символ с консоли с эхом на экране
int getche(void);
Вывести символ в текстовое окно
int putch(int ch);
Возвратить символ назад в буфер клавиатуры
int ungetch(int ch);
Возвращает код символа ch в случае успеха или EOF при ошибке.
Прочитать строку с консоли
char *cgets(char *str);
Байт str[0] перед вызовом функции должен содержать максимальную длину строки, допустимую для ввода. После возврата байт str[1] содержит число фактически прочитанных символов. Сама строка начинается с байта str[2]. Функция возвращает адрес прочитанной строки &str[2].
Вывести строку в текстовое окно
int cputs(const char *str);
Возвращает последний выведенный символ.
Вводит данные с консоли с преобразованию по формату
int cscanf(char *format [, address, ...]);
Возвращает число успешно прочитанных полей данных. При попытке прочитать символ конца файла возвращает значение EOF.
Выводит данные в текстовое окно с преобразованием по формату
int cprintf(const char *format[, argumet,...]);
Возвращает число выведенных байт информации. В отличие от функции printf() использует установки цвета. Не производит автоматического добавления символа '\r' к '\n'.
13.2. Функции обработки строк.
Определение длины строки
int strlen(char *str);
Символ '\0' в длину строки не входит. Не путать с длиной массива, в котором размещается строка.
Слияние двух строк
char *strcat(char *dest, char *src);
К строке, на которую указывает dest приписываются все символы строки src. Буфер, в котором размещается строка dest должен быть такого размера, чтобы вместить результирующую строку.
Функция возвращает адрес строки dest.
Слияние строки dest с частью строки src
char *strneat(char *dest, char src, int n);
К строке, на которую указывает dest приписываются n символов строки src. Буфер, в котором размещается строка dest должен быть такого размера, чтобы вместить результирующую строку.
Функция возвращает адрес строки dest.
Функция сравнения двух строк в алфавитном порядке
int strcmp(char *s1, char *s2);
Функция возвращает значение больше нуля, если строка s1 больше s2 в смысле алфавитного порядка, меньше нуля, если строка s1 меньше s2, и равное нулю, если строки равны.
Функция сравнения части строк
int strncmp(char *s1, char *s2, int n);
Работает также как strcmp(), но сравнивает только n символов строк.
Функция копирования строки
char *strcpy(char *dest, char src);
Строка, на которую указывает src, копируется в буфер, на который указывает dest. Этот буфер должен быть такого размера, чтобы вместить копируемую строку. Функция возвращает адрес строки dest.
Функция копирования части строки
char *strncpy(char *dest, char src, int n);
Часть строки, на которую указывает src, размером n символов копируется в буфер, на который указывает dest. Этот буфер должен быть такого размера, чтобы вместить копируемую строку. Функция возвращает адрес строки dest.
Поиск символа в строке
char *strchr(char *str, char c);
Функция осуществляет поиск символа c с начала строки, на которую указывает str, и возвращает адрес найденного символа. Если символ не найден возвращает NULL.
Поиск символа с конца строки
char *strrchr(char *s, char c);
Функция осуществляет поиск символа c с конца строки, на которую указывает str, и возвращает адрес найденного символа. Если символ не найден возвращает NULL.
Форматный вывод в строку
int sprintf(char *str, char *format, ...);
Функция работает подобно printf(), но вывод вместо консоли осуществляет в буфер, на который указывает str. Его размер должен быть достаточным для того, чтобы вместить всю выводимую информацию. Функция возвращает число выведенных байт.
Форматный ввод из строки
int sscanf(char *str, char *format, ...);
Функция работает подобно scanf(), но ввод вместо клавиатуры осуществляет из буфера, на который указывает str. Функция возвращает число успешно прочитанных полей данных.
Используя последнюю функцию можно разработать программу для ввода массива данных, заканчивающегося ключевым словом. Фрагмент такой программы приведен ниже
#include <string. h>
#include <stdio. h>
void main(void)
{
int a[1000]; char buf[81]; int n, goodIO;
for( goodIO = n = 0; n < 1000; n++ )
{
printf("a[%d]=", n); scanf("%s", buf);
if( strcmp(buf, "end") == 0) { goodIO = 1; break; }
sscanf (buf, "%d", &a[n]);
}
if( goodIO )
{
/* ... обработка */
}
}
13.3. Функции распознавания вида символа
Строго говоря, это не функции, а макроопределения, описанные в заголовочном файле <ctype. h>:
isalnum(c) истина если c буква или цифра;
isalpha(c) истина если c буква;
isdigit(c) истина если c цифра;
iscntrl(c) истина если c символ удаления или обычный уп-
равляющий символ;
isprint(c) истина если c печатный символ;
islower(c) истина если c буква нижнего регистра;
isupper(c) истина если c буква верхнего регистра;
ispunct(c) истина если c знак пунктуации;
isspace(c) истина если c пробел, знак табуляции, возврат
каретки, символ перевода строки, вертикальной
табуляции, перевода страницы;
isxdigit(c) истина если c шестнадцатеричная цифра;
_toupper(c) преобразует c из диапазона [a-z] к символам
[A-Z];
_tolower(c) преобразует c из диапазона [A-Z] к символам
[a-z];
_toascii(c) преобразует c больший, чем 127 к диапазону
0-127 путем очистки всех битов, кроме 7 млад-
ших.
13.4. Функции преобразования данных
Прототипы функций преобразования данных находятся в файле <stdlib. h>.
Преобразование строки символов в целое число:
int atoi(const char *s);
long atol(const char *s);
Возвращает преобразованное значение входной строки. Если строка не может быть преобразована возвращает ноль.
Преобразование строки символов в вещественное число:
double atof(const char *s);
Возвращает преобразованное значение входной строки. Если строка не может быть преобразована возвращает ноль.
Преобразование строки символов в длинное целое число с указанием системы счисления:
long strtol(const char *s, char **endptr, int radix);
unsigned long strtoul(const char *s, char **endptr,
int radix);
Возвращает преобразованное значение входной строки. Если строка не может быть преобразована возвращает ноль. Указатель *endptr устанавливается на первый символ строки не отвечающий синтаксису целого числа языка СИ.
Преобразование строки символов в вещественное число:
double strtod(const char *s, char **endptr);
Возвращает преобразованное значение входной строки. Если строка не может быть преобразована возвращает ноль. Указатель *endptr устанавливается на первый символ строки не отвечающий синтаксису вещественного числа языка СИ.
Преобразование целого числа в строку символов с указанием системы счисления:
char *ltoa(long value, char *string, int radix);
char *itoa(int value, char *string, int radix);
char *ultoa(unsigned long value, char *string,
int radix);
Возвращает указатель на выходную строку.
Преобразование вещественного числа в строку символов:
char *ectv(double value, int ndig, int *dec, int *sign);
char *fctv(double value, int ndig, int *dec, int *sign);
Функции возвращают указатель на статический буфер памяти с выходной строкой, содержащей только цифры числа. Буфер обновляется при каждом вызове функции. Для функции ectv() ndig является числом цифр в выходной строке, для fctv() - числом цифр в выходной строке после десятичной точки. Параметр dec показывает положение десятичной точки в выходной строке, которая явно не присутствует. Параметр sign принимает отличное от нуля значение для отрицательных чисел.
Преобразование вещественного числа в строку:
char *gctv(double value, int ndec, char *buf);
Функции возвращают указатель на буфер buf памяти с выходной строкой, содержащей готовое к печати символьное представление числа из ndec цифр в формате F Фортрана, если возможно. В противном случае число будет представлено в формате e функции printf().
14. Структуры языка C.
Под структурой понимают совокупность данных разного типа, лежащих в непрерывной области памяти и объединенных общим именем.
Отличие от массивов - элементы структуры разного типа.
Необходимость: часто реальный объект характеризуется величинами разного типа.
Пример: товар на складе
название char name[21];
цена float price;
количество int number;
Все три переменных неразрывно связаны с каким-то товаром.
14.1. Описание структуры
1 способ
struct { char name[21];
float price;
int number;} goods;
Выделяется 27 байт для переменной goods;
2 способ
struct _GOODS { char name[21];
float price;
int number;};
Оператор не выделяет память, а устанавливает тип структуры. Для выделения памяти надо выдать оператор:
struct _GOODS goods1, goods2;
Выделяет память для goods1 и goods2, каждой по 27 байт.
Можно смепшать два способа:
struct _GOODS { char name[21];
float price;
int number;} goods;
Устанавливает тип структуры и выделяет память для goods.
3 способ. Используется оператор описания типа typedef:
typedef double real;
обычное описание
Если при описании имени стоит слово typedef, то описание не выделяет память, а создает новый тип данных - real, который можно применять также как и любое другое описание типа:
real a, b;
Еще пример:
typedef char string[40];
новый тип string
string a, b, c; - описание трех переменных, каждая из
которых является массивом из 40 символов.
В случае структуры имеем:
typedef struct { char name[21];
float price;
int number;} GOODS;
Описание типа
GOODS goods1, goods2; - выделение памяти для переменных
goods1 и goods2.
14.2. Трактовка имени структуры.
Имя структуры обозначает значение всей области памяти, которую она занимает. Поэтому для структур одного и того же типа допускается операция присваивания:
goods2 = goods1;
При этом вся область памяти goods1 копируется в область памяти goods2.
14.2.1. Доступ к элементу структуры.
Для этого используется операция ".".
goods1.name - образовалось составное имя. Тип составного имени такой же как тип соответствующего элемента структуры.
С составным именем можно выполнять любые действия, разрешенные для типа элемента.
goods2.price = 20*goods1.price;
scanf("%s", goods1.name);
goods1.name[3];
Из структур можно составить массив:
GOODS ab[50];
Тогда ab - адрес массива;
ab[2] - значение структуры;
ab[2].price - значение элемента структуры.
Структура может входить в другую структуру:
typedef struct { GOODS goods; int fl;} GF;
GF a - описание;
a. good. name
Никаких ограничений на уровень вложенности структур нет.
14.3. Инициализация структур.
Статические структуры могут быть проинициализированы подобно массивам:
static GOODS a = { "Телепвизор", 14000.0, 20};
Необходимо строго следитьза соответствием порядка констант порядку элементов структуры.
14.4. Структуры и функции.
Структура целиком может быть передана функции как параметр. Кроме того, структура может быть полностью возвращена как значение функции.
Пример:
typedef struct { double r, f;} POLAR;
typedef struct { double x, y;} DECART;
DECART ptod(POLAR pcoord)
{
DECART dcoord;
dcoord. x = pcoord. r*cos(pcoord. f);
dcoord. y = pcoord. r*sin(pcoord. f);
return dcoord;
}
void main(void)
{
DECART a; POLAR b = { 15.2, 0.18};
a = ptod(b);
.
.
.
}
Для больших структур такой способ передачи параметров и возврата значений неэффективен, так как компилятору приходится создавать копии параметров и результатов работы функции в специальной области памяти.
Значиительно эффективнее передавать адреса параметров:
void prot (DECART*dc, POLAR*pc)
{
(*dc).x = (*pc).r*cos((*pc).f);
(*dc).y = (*pc).r*cos((*pc).f);
}
(*dc) в скобках потому, что "." имеет более высший приоритет. Головная программа при этом выглядит так:
void main(void)
{
DECART a; POLAR b = { 15.2, 0.18};
ptod(&a, &b);
.
.
.
}
Запись вида (*dc).x громоздка и плохо понятна. Поэтому разработчики языка C предусмотрели более понятную эквивалентную запись:
(*dc).x эквивалентно dc->x.
Используя ее:
void ptod(DECART *dc, POLAR *pc)
{
dc->x = pc->r*cos(pc->f);
dc->y = pc->r*sin(pc->f);
}
14.5. Поля бит в структурах.
Для целых чисел допускается использовать область памяти меньше байта:
struct {
int c1:4; -8<c1<7
int c2:12 -2 <c2<2 -1
} ab;
ab. c1 будет преобразовано в целый тип, затем будет использоваться. На преобразование тратится время и память. Рекомендуется использовать для беззнаковых типов данных и в крайних случаях.
/* Пример использования структур */
#include <stdio. h>
#include <string. h>
#include <math. h>
typedef struct {
char name[21];
int number;
float price;
} GOODS;
void in_goods ( GOODS gs[], int *n );
int in_goods1 ( GOODS *g );
void out_goods ( GOODS gs[], int n );
void sort_goods ( GOODS gs[], int n);
void main( void )
{
int n; GOODS goods[100];
in_goods ( goods, &n );
sort_goods ( goods, n );
out_goods ( goods, n );
{ float f=0; sin(f); }
}
void in_goods( GOODS gs[], int *n)
{
printf("Введите характеристики товаров в виде:\n" \
"наименование количество цена\n" \
"-----окончание ввода \"end\"-------\n");
*n=0;
while( in_goods1(&gs[*n]) ) (*n)++;
}
int in_goods1( GOODS *g )
{
scanf( "%s", g->name );
if ( strcmp(g->name, "end")==0 ) return 0;
scanf( "%d%f", &g->number, &g->price );
return 1;
}
void out_goods( GOODS gs[], int n )
{
int i;
printf("*-*\n");
printf("| Наименование | Кол-во | Цена |\n");
printf("|----|||\n");
for( i=0; i<n; i++)
printf( "| %20s | %6d | %10.2f |\n",
gs[i].name, gs[i].number, gs[i].price );
printf("**\n");
}
void sort_goods( GOODS gs[], int n )
{
int i, j, GOODS r;
for (i=0; i<n-1; i++)
for(j=i+1; j<n; j++)
if( gs[i].price )
{ r=gs[j]; gs[j]=gs[i]; gs[i]=r; }
}
15. Объединения.
Объединяются ключевым словом union. Способы описания такие же, как и в случае структур, только вместо слова struct используется слово union.
union {int a; long b;} pr;
Для переменной pr выделяется память, достаточная для хранения самого длинного элемента объединения, т. е. в нашем примере - 4 байта.
Если использовать pr. a, то выделенная память будет использоваться как int, в случае pr. b как long. Однако, участок памяти один и тот же. Поэтому одновременно существовать pr. a и pr. b не могут. Ответственность за некорректное использование памяти лежит на программисте. Объединения используются для экономии памяти:
union {int bc[100]; double kk[20];} cc;
На все отводится 200 байт.
int bc[100] 200 байт
______________________________
|------|
double kk[20] (160 байт)
Одновременно работать с массивом cc. bc и cc. kk нельзя.
Объединения нельзя инициализировать.
16. Дополнительные сведения о препроцессоре языка C.
Препроцессор обрабатывает текст программы перед компиляцией.
1. Включение файлов (рассмотрено ранее):
#include <conio. h> - файл из специального каталога;
#include "d:\\user\\ff. h" - файл ищется по правилам MS DOS.
2. Текстовые подстановки (рассматривалось ранее):
#define N 21
#define ABC (a*b*c+\
d*sin(x))
\ - переход на следующую строку.
3. Создание макросов.
#define SQR(x) ((x)*(x))
В результирующую строку подставляется фактическая строка x. Например, если в тексте программы встречается SQR(y), то после макрораскрутки получим ((y)*(y)). Скобки нужны для того, чтобы не получилось недоразумений, например
#define SQR(x) x*x
SQR(y+2); превратится в
y+2*y+2;
Конечно, это не то, что хотелось.
4. Отмена ранее созданного имени:
#undef SQR.
5. Условная компиляция:
#if константное выражение
.
. строки программы
.
#else
.
. строки программы
.
#endif
Если константное выражение истинно, то в программу будут включены строки из первого блока, иначе из второго.
Пример:
#define DEBUG 1
.
.
.
#if DEBUG
printf("%d", x);
#endif
Можно проверить наличие или отсутствие какого-либо имени:
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 |


