- •7. Программирование алгоритма линейной структуры на языке Си 27
- •8. Понятие о препроцессоре языка Си 28
- •9. Операторы языка Си и приемы программирования 28
- •10. Массивы. Адресная арифметика языка Си 47
- •1.Правила записи программы на языке Си
- •2.Правила формального описания синтаксиса языка программирования
- •3.Идентификаторы языка Си
- •4.Понятие о типах данных.
- •4.1.Системы счисления. Представление данных в эвм.
- •4.2.Основные типы данных языка Си
- •4.3.Правила записи констант различных типов
- •4.4.Беззнаковый тип для целых данных
- •4.5.Символьные строки
- •5.Понятие функции
- •5.1.Стандартная функция printf
- •5.2.Стандартная функция scanf
- •6.Операции и выражения
- •6.1.Простейшие арифметические операции
- •6.2.Операция присваивания
- •6.3.Оператор-выражение
- •6.4.Использование в выражениях операндов разных типов
- •6.5.Операции преобразования типов
- •6.6.Стандартные математические функции
- •6.7.Простейшие функции, определяемые программистом
- •6.8.Дополнительные арифметические операции
- •6.9.Дополнительные операции присваивания
- •6.10.Битовые операции
- •6.11.Операции отношения
- •6.12.Логические операции
- •6.13.Операция определения размера данных
- •6.14.Приоритеты операций
- •7.Программирование алгоритма линейной структуры на языке Си
- •8.Понятие о препроцессоре языка Си
- •9.Операторы языка Си и приемы программирования
- •9.1.Оператор цикла while
- •9.2.Условный оператор и условная операция
- •1) Короткие операторы:
- •2) Группы коротких операторов:
- •3) Длинные группы операторов:
- •9.3.Запись алгоритмов с помощью диаграмм Несси - Шнейдермана (структограмм )
- •9.4.Н екоторые приемы программирования
- •9.5.Оператор прерывания цикла
- •9.6.Оператор продолжения цикла
- •9.7.Пример организации простейшего меню
- •9.8.Множественный выбор. Оператор переключения
- •9.9.Оператор цикла do-while.
- •9.10.Перечисления. Работа с клавиатурой ibm pc
- •9.11.Пример организации светового меню
- •10.Массивы. Адресная арифметика языка Си
- •10.1.Описание массива
- •10.2.Ввод-вывод массива
- •10.3.Инициализация массива
- •10.4.Программа вычисления длины строки символов
- •10.5.Двумерные массивы (массивы массивов)
- •10.6.Адресная арифметика языка Си
- •10.7.Указатели и одномерные массивы
- •10.8.Указатели и двумерные массивы
- •10.9.Указатели и функции
- •10.10.Оператор typedef
- •10.11.Дополнительные описания указателей для ibm pc
- •10.12.Непосредственная работа с экранной памятью
- •11.Дополнительные сведения о функциях
- •11.1.Области видимости и глобальные данные
- •11.2.Время жизни переменных и классы памяти языка Си
- •11.3.Передача аргументов в функцию
- •11.4.Возврат значений из функций
- •12.Работа с динамической памятью
- •12.1.Стандартные функции управления динамической памятью
- •12.2.Пример использования динамической памяти
- •12.3.Особенности работы с двумерными массивами
- •12.3.1.Пересчет индексов вручную
- •12.3.2.Массивы с постоянной длиной строки
- •12.3.3.Общий случай двумерного массива
- •12.4.Особенности работы с массивами большого размера
- •13.Модульное программирование в системе Turbo c
- •13.1.Обеспечение корректной стыковки модулей
- •13.2.Создание библиотек функций
- •14.Некоторые библиотечные функции языка Си
- •14.1.Функции консольного ввода/вывода (уникальны для tc)
- •14.2.Функции обработки строк.
- •14.3.Функции распознавания вида символа
- •14.4.Функции преобразования данных
- •15.Структуры языка c.
- •15.1.Описание структуры
- •1 Способ
- •2 Способ
- •15.2.Трактовка имени структуры.
- •15.2.1.Доступ к элементу структуры.
- •15.3.Инициализация структур.
- •15.4.Структуры и функции.
- •15.5.Поля бит в структурах.
- •16.Объединения.
- •17.Дополнительные сведения о препроцессоре языка c.
- •18.Условное выражение.
- •18.1.Приоритеты и направления операций.
- •19.Динамические данные.
- •19.1.Линейные списки.
- •19.2.Организация данных в виде стека.
- •19.3.Организация данных в виде очереди.
- •19.4.Организация данных в виде деревьев.
- •20.Библиотека ввода-вывода языка c.
- •20.1.Открытие потока.
- •20.2.Закрытие потока.
- •20.3.Предопределенные указатели потоков.
- •20.4.Функции ввода-вывода.
12.3.2.Массивы с постоянной длиной строки
Если у массива длина строки постоянная, то адресацию динамического двумерного массива может выполнить компилятор:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define MAXVAL 1000
#define STRLEN 6
void *Malloc ( size_t size );
void RandomMatr ( double (*Matr)[STRLEN], int n );
void OutMatr ( char *name,
double (*Matr)[STRLEN], int n );
void main( void )
{
size_t n = 5;
double (*A)[STRLEN];
/* Выделение памяти под матрицу */
A = (double (*)[STRLEN])
Malloc( n*sizeof(double[STRLEN]) );
/* Заполнение матрицы значениями и распечатка */
RandomMatr(A, n);
OutMatr("A", A, n);
/* освобождение памяти */
free(A);
}
void RandomMatr (double (*Matr)[STRLEN], int n)
{
int i, j;
for(i = 0; i < n; i++)
for(j = 0; j < STRLEN; j++)
Matr[i][j] = random(MAXVAL) + 1;
}
void OutMatr( char *name, double (*Matr)[STRLEN], int n )
{
int i, j;
printf("\nМатрица %s\n---------------\n", name);
for(i = 0; i < n; i++)
{
for(j = 0; j < STRLEN; 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[STRLEN], то есть строк двумерного массива, которые должны иметь фиксированную длину.
12.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() можно получить соответствующие размеры массива. Способ индексации не изменяется и она по-прежнему выполняется в стиле индексации двумерных массивов.
Такое же скрытое хранение размеров матрицы можно организовать и в случае создания массива в единственном блоке памяти, меньшим сегмента.
