Основы программирования

Изучение основных правил формального описания синтаксиса языка программирования. Ознакомление с требованиями к байту перед вызовом функции. Исследование процесса организации данных в виде стека. Рассмотрение и характеристика примеров работы с файлами.

Рубрика Программирование, компьютеры и кибернетика
Вид курс лекций
Язык русский
Дата добавления 12.10.2016
Размер файла 156,4 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

После выполнения следующего примера:

#include <stdio.h>

int i=1;

void PrintI(void)

{

printf( "i = %d\n", i );

}

void main(void)

{

int i=10;

printf( "i = %d\n", i );

PrintI();

{

int i=100;

printf( "i = %d\n", i );

PrintI();

}

printf( "i = %d\n", i );

PrintI();

}

будет напечатано

i = 10

i = 1

i = 100

i = 1

i = 10

i = 1

Это произошло потому, что локальные определения имени i перекрывают объявления более высокого уровня, создают и инициализируют новые локальные переменные с именем i. После выхода из очередного блока его локальные переменные уничтожаются и открывается доступ к соответствующей переменной охватывающего блока. Функция PrintI() всегда имеет дело с переменной i, описанной на файловом (глобальном) уровне, потому, что локальной переменной i в функции PrintI() нет. Все три переменные, описанные в примере, соответствуют разным ячейкам памяти, несмотря на одинаковые имена.

Глобальные переменные могут использоваться всеми функциями программы. Их использование сильно затрудняет анализ программы и поиск ошибок. Действительно, поскольку все функции имеют доступ к глобальным данным, никогда по вызову функции нельзя определить, меняет ли она значение глобальной переменной или нет. Поэтому, необходимо иметь специальное обоснование целесообразности использования каждой глобальной переменной.

Никогда не следует делать глобальными вспомогательные переменные функции, например переменные циклов. В следующей программе использование глобальной переменной цикла приводит к ошибке, которую трудно обнаружить:

#include <stdio.h>

int i;

int Sum(int A[], int n)

{

int s = 0; /* Сумма одномерного массива */

for(i=0; i<n; i++) s += A[i];

return s;

}

void main(void)

{

int B[3][2] = { { 1, 1 },

{ 2, 2 },

{ 3, 3 } };

int s = 0; /* Сумма двумерного массива */

for(i=0; i<3; i++) s += Sum(B[i], 2);

printf("s = %d\n", s);

}

Никаких ошибок компиляции в программе нет. Если рассматривать все ее функции по отдельности, то ошибка тоже не видна. Но программа дает неверный результат: s = 2. Это происходит потому, что цикл головной программы выполняется всего один раз, так как после возврата из функции Sum(), значение глобальной переменной i будет равно 3. Если описать переменные циклов i внутри каждой функции, то программа станет выдавать правильную сумму элементов двумерного массива: s = 12.

Глобальные данные определяются и им выделяется память в конкретном исходном файле программы на языке Си, который образует модуль. Все функции модуля видят глобальные данные своего модуля и имеют к ним доступ. Функции других модулей тоже могут иметь доступ к глобальным данным первого модуля, однако, они не видят эти данные. Для обеспечения видимости используется специальное описание данных, не выделяющее память для них (то есть описание, а не определение). Это описание должно полностью повторять определение данных, по которому выделяется память, но перед ним должно стоять ключевое слово extern. Например, описание extern double Speed;

говорит о том, что в каком-то из модулей программы выделена память под переменную Speed типа double и функции данного модуля могут ее использовать.

Подобное описание одного и того же имени может повторяться сколько угодно раз при условии, что все последующие описания будут в точности совпадать с первым. Смысл этого описания очень похож на смысл прототипа (описания) для функции. Действительно, это описание является инструкцией компилятору по поводу использования имени глобального данного, находящегося в каком-то другом файле. Как и прототипы функций, описания глобальных данных следует помещать в заголовочный файл программы и включать этот файл в каждый модуль программы с помощью директивы препроцессора #include.

Во многих случаях целесообразно ограничить использование глобальных данных рамками одного модуля, то есть запретить функциям другого модуля доступ к ним. Это можно сделать используя для глобальных данных спецификатор static. Например, после описания static int Count;

к глобальной переменной Count будут иметь доступ только функции того модуля, в котором собственно была выделена память под переменную Count.

Спецификатор static можно использовать и совместно с функциями, тогда их использование тоже будет ограничено одним модулем. Например, при описании static double cotan(double x);

функция cotan будет доступна только в том модуле (файле), где она определена, то есть, где находится ее тело.

10.2 Время жизни переменных и классы памяти языка Си

Время жизни переменных программы определяется классом памяти. В языке Си принято различать статические (static), автоматические (auto) и динамические данные.

Статические переменные создаются и инициализируются в момент запуска программы в сегменте статических данных и существуют до ее останова, не меняя своего местоположения в памяти ЭВМ.

Все глобальные переменные по умолчанию статические и не могут быть иными, спецификатор static для глобальных переменных используется для ограничения доступа, а не для изменения класса памяти (см. предыдущий раздел).

Локальную переменную функции или блока тоже можно сделать статической, используя ключевое слово static. В следующем примере используется локальная статическая переменная функции для того, чтобы отличить первый вызов функции от последующих:

#include <stdio.h>

void fun(void)

{

static int first = 1;

if( first )

{

printf("Это первый вызов функции.\n");

first = 0;

}

else

printf("Это не первый вызов функции.\n");

}

void main(void)

{

fun(); fun(); fun();

}

Статическая переменная first функции fun() создается и инициализируется при запуске программы, до выполнения функции main(). Поэтому, при первом вызове функции fun() значение first равно 1, при последующих вызовах fun() переменная first будет сохранять свое значение, измененное функцией fun() на 0. Подобным же образом можно организовать подсчет числа обращений к функции.

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

Все локальные данные и формальные параметры функций автоматические по умолчанию. Никаких специальных описаний для этого не требуется.

Если в предыдущем примере убрать ключевое слово static из описания переменной first, то функция fun() при каждом вызове будет вести себя как и при первом. Это происходит потому, что переменная first стала автоматической, создается и инициализируется при каждом вызове функции fun().

Глобальные данные не могут быть автоматическими, потому что нет блока, которому они принадлежат.

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

10.3 Передача аргументов в функцию

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

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

Рассмотренный алгоритм вызова функции гарантирует сохранение значений фактических параметров независимо от того, что делала функция с соответствующими формальными параметрами. Рассмотрим функцию Sum(), вычисляющую значение суммы элементов массива:

#include <stdio.h>

double Sum(double A[], int nA)

{

double s = 0;

while(nA) s += A[--nA];

return s;

}

void main (void)

{

double B[] = { 1, 2, 3, 4, 5 };

int nB = sizeof(B)/sizeof(B[0]);

printf("Сумма = %lf\n", Sum(B,nB));

printf("nB = %d\n", nB);

}

Функция при своей работе изменяет значение формального параметра nA, в котором передается размер массива. Однако, если запустить программу, то можно увидеть, что значение соответствующего фактического параметра nB осталось неизменным, ввиду того, что функция работает с копиями параметров.

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

double Sum(double A[], int nA)

{

double s = 0, *Aend = A + nA;

while( A < Aend ) s += *(A++);

return s;

}

Соответствующий фактический параметр - константный адрес массива B в вызывающей функции не меняет своего значения, то есть никаких противоречий в предыдущей функции нет.

Вообще говоря, для формальных параметров-массивов описания в виде A[] и *A совершенно идентичны и обозначают локальную копию адреса соответствующего типа. Какое из этих описаний использовать, зависит от смысла параметра. Если это массив, то более наглядно использовать описание вида A.

10.4 Возврат значений из функций

Возврат результата работы функции в вызывающую программу в виде единственного значения можно осуществить с помощью оператора return. При этом результат возвратится как значение самой функции и должен иметь соответствующий тип.

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

Если необходимо изменить содержимое массива, его адрес нужно передать в функцию и обычным способом, с помощью операции индексации изменить нужные элементы массива. В следующем примере функция FillArray() заполняет массив указанным значением:

void FillArray(double A[], int nA, double val)

{

int i;

for (i=0; i<nA; i++) A[i] = val;

}

void main (void)

{

double B[100];

FillArray(B, 40, 35.4);

/* ... */

FillArray(&B[60], 20, 15.4);

/* ... */

}

Первый вызов FillArray() заполняет 40 первых элементов массива B значением 35.4, второй вызов заполняет 20 элементов массива B, начиная с элемента B[60], значением 15.4. При возврате из функции массив будет изменен, т. к. занесение значения val происходит непосредственно по нужному адресу.

Эту же функцию можно использовать для заполнения строк двумерного массива:

void main (void)

{

double a[10][20];

int n = sizeof(a) / sizeof(a[0]);

int m = sizeof(a[0]) / sizeof(a[0][0]);

int i;

/* ... */

for(i=0; i<n; i++ )

FillArray(a[i], m, 14.6);

/* ... */

}

В примере следует обратить внимание на соответствие типов передаваемых параметров и на способ вычисления числа строк и числа столбцов двумерного массива.

Возврат из функции нескольких значений, которые не являются элементами массива, можно организовать, используя указатели. В следующем примере функция Decart() осуществляет перевод пары полярных координат в декартовые:

void Decart(double *px, double *py, double r, double f)

{

(*px) = r * cos(f);

(*py) = r * sin(f);

}

При обращении к данной функции для параметров px и py нужно передавать адреса:

void main(void)

{

double x, y, r=5, f=0.5;

/* ... */

Decart( &x, &y, r, f );

/* ... */

}

В данном примере при вызове функции создаются локальные копии адресов переменных x и y, а внутри функции происходит обращение к переменным x и y через их адреса (как и в случае массивов), поэтому значения x и y после вызова функции будут изменены.

11. Работа с динамической памятью

11.1 Стандартные функции управления динамической памятью

Данные, которые создаются, инициализируются и уничтожаются по требованию программиста называются динамическими. Для управления такими данными используются специальные стандартные функции, прототипы которых описаны в заголовочном файле <malloc.h> (для некоторых компиляторов <alloc.h>).

Для запроса динамической памяти служит функция malloc(), которая имеет следующий прототип: void * malloc(size_t size);

Функция malloc() выделяет область динамической памяти, размером size байт, и возвращает адрес этой области памяти.

Параметр size, имеет тип size_t, который описан в файле <malloc.h> с помощью оператора typedef и используется для описания размеров, счетчиков и т.д. Обычно тип size_t соответствует типу unsigned int.

В том случае, когда функция malloc() не может удовлетворить запрос на память, она возвращает значение NULL, то есть значение не существующего указателя. Константа NULL описана в заголовочном файле <malloc.h>. Значение NULL возвращается и в том случае, когда значение параметра size нулевое.

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

После того, как выполнена вся работа с выделенной областью памяти, ее следует освободить с помощью функции free(), имеющей следующий прототип: void free(void *block); где block - указатель на область памяти, значение которого ранее было возвращено какой-либо функцией выделения памяти.

Если при вызове функции free() значение указателя block не соответствует адресу, возвращенному функцией выделения памяти, то результат выполнения функции free() непредсказуем, а область динамической памяти может быть вообще разрушена.

Не допускается также освобождать уже освобожденный блок памяти.

Значение параметра block равное NULL не вызывает никаких действий со стороны функции free();

Рассмотрим типичную последовательность действий при работе с динамической памятью:

double *A; int n;

...

n = 200;

...

A = (double *) malloc( n * sizeof(double) );

...

/* Работа с массивом A */

...

free(A);

В рассмотренном фрагменте программы выделяется память для хранения n элементов типа double. В целях совместимости никогда не следует явно задавать размер элемента данных. Нужно пользоваться операцией sizeof(). Возвращаемое функцией malloc() значение преобразуется к типу указателя на double.

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

В некоторых случаях бывает полезной функция calloc(), которая не только выделяет память, но и заполняет область выделенной памяти нулевыми значениями. Она имеет следующий прототип:

void * calloc(size_t nitems, size_t size);

Функция выделяет непрерывный блок памяти для nitems элементов данных размером size байт каждый и заполняет этот блок нулевыми значениями. В остальном работа ее аналогична работе функции malloc().

Функция realloc() служит для изменения размера ранее выделенного блока памяти: void *realloc(void *block, size_t size);

Здесь block - адрес ранее выделенного блока памяти, size - новый размер блока в байтах. Функция возвращает значение нового указателя на блок памяти, которое может и не совпадать со старым.

Функция гарантирует сохранность данных в блоке, разумеется, сохранность не более size байт. В остальном работа функции совпадает с работой ранее рассмотренных функций выделения памяти.

Все рассмотренные функции могут выделять память размером не более одного сегмента, то есть не более 64K в 16-ти разрядных моделях и не более 4G в 32-х разрядных моделях памяти.

При работе с динамической памятью следует иметь в виду, что в каждом выделенном блоке несколько байт отводится на служебную информацию. Так в 16-ти разрядной Large модели память выделяется блоками по размеру кратными 16 байтам, и в каждом блоке 4 байта служебные.

К сожалению, стандартные средства работы с динамической памятью не предусматривают "сборку мусора", то есть автоматическое перемещение выделенных блоков в динамической памяти так, чтобы между ними не было неиспользуемых промежутков. Поэтому от программиста требуется повышенное внимание к стратегии выделения и освобождения динамической памяти в своих программах. Иначе может получиться так, что требуемый блок памяти невозможно выделить, хотя суммарный объем неиспользуемой памяти допускает это.

Функция coreleft() возвращает значение оставшейся в динамической области памяти в байтах. Функция может иметь следующие прототипы в зависимости от моделей памяти:

unsigned coreleft(void); /* Маленьких модели */

unsigned long coreleft(void); /* Большие модели */

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

11.2 Пример использования динамической памяти

В следующем примере динамическая память используется для хранения элементов массива заранее неопределенного размера, ввод которого прекращается при появлении во входном потоке числа больше или равного 1e300. При этом, вначале под массив выделяется сегмент памяти, максимального размера, затем размер блока памяти устанавливается соответствующим фактическому размеру массива.

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <malloc.h>

#include <limits.h>

void * Malloc( size_t size )

{

void *p = malloc(size);

if( !p )

{ printf("Недостаточно памяти!\n"); exit(1); }

return p;

}

void * Realloc ( void *block, size_t size )

{

void *p = realloc(block, size);

if( !p ) { printf("Недостаточно памяти!\n"); exit(1); }

return p;

}

void main(void)

{

double *A, temp;

unsigned i, n, maxN, goodIO;

A = (double *) Malloc( maxN = UINT_MAX );

maxN /= sizeof(double);

for(goodIO = n = 0; n < maxN; n++)

{

printf("A[%d] = ", n); scanf("%lf", &temp);

if(temp >= 1e300) { goodIO = 1; break; }

A[n] = temp;

}

if(goodIO)

{

A = (double *) Realloc(A, n * sizeof(double));

/* Обработка массива. Для примера - печать. */

for(i = 0; i < n; i++)

{

printf("%10.3lf ", A[i]);

if( (i + 6) % 5 == 0 ) printf("\n");

if( (i + 121) % 120 == 0 ) { getch(); clrscr(); }

}

printf("\n");

}

free(A);

}

Максимальный размер сегмента в байтах всегда равен величине наибольшего беззнакового целого числа, значение которого определяет константа UINT_MAX из заголовочного файла <limits.h>.

В программе используются вспомогательные функции Malloc() и Realloc() для обеспечения контроля выделения памяти. В них функция exit() с прототипом в файле <stdlib.h> используется для прерывания работы программы.

11.3 Особенности работы с двумерными массивами

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

Пересчет индексов вручную

В следующем примере двумерный массив представляется в виде одномерного, а местоположения каждого элемента двумерного массива в одномерном определяется суммой номера столбца и произведения номера строки на длину строки. Способ индексации одинаков как в вызывающей функции, так и в вызываемых.

#include <stdio.h>

#include <stdlib.h>

#include <malloc.h>

#define MAXVAL 1000

void *Malloc ( size_t size );

void RandomMatr ( double *Matr, int n, int l );

void OutMatr ( char *name,

double *Matr, int n, int m );

void main( void )

{

size_t n = 5, m = 6;

double *A;

/* Выделение памяти под матрицу */

A = (double *) Malloc( n*m*sizeof(double) );

/* Заполнение матрицы значениями и распечатка */

RandomMatr(A, n, m);

OutMatr("A", A, n, m);

/* освобождение памяти */

free(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*m+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*m+j]);

printf("\n");

}

}

void * Malloc( size_t size )

{

void *p = malloc(size);

if( !p )

{ printf("Недостаточно памяти!\n"); exit(1); }

return p;

}

Функция rand() с прототипом из <stdlib.h> возвращает псевдослучайное число в диапазоне от 0 до MAXVAL-1.

Массивы с постоянной длиной строки

Если у массива длина строки постоянная, то адресацию динамического двумерного массива может выполнить компилятор:

#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], то есть строк двумерного массива, которые должны иметь фиксированную длину.

Общий случай двумерного массива

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

#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.

Доступ к элементу структуры.

Для этого используется операция ".".

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 будет преобразовано в целый тип, затем будет использоваться. На преобразование тратится время и память. Рекомендуется использовать для беззнаковых типов данных и в крайних случаях.


Подобные документы

  • Цели и задачи дисциплины "Технология программирования". Программные средства ПК. Состав системы программирования и элементы языка. Введение в систему программирования и операторы языка Си. Организация работы с файлами. Особенности программирования на С++.

    методичка [126,3 K], добавлен 07.12.2011

  • Понятие математического программирования. Класс как тип структуры, позволяющий включать в описание типа не только элементы данных, но и функции. Рассмотрение основных особенности языка программирования C++. Характеристика среды MS Visual Studio 2008.

    контрольная работа [318,0 K], добавлен 13.01.2013

  • Особенности способов описания языков программирования. Язык программирования как способ записи программ на ЭВМ в понятной для компьютера форме. Характеристика языка Паскаль, анализ стандартных его функций. Анализ примеров записи арифметических выражений.

    курсовая работа [292,0 K], добавлен 18.03.2013

  • История создания языка Java. Основные принципы объектно-ориентированного программирования. Структура, особенности синтаксиса и примеры прикладных возможностей использования языка Java, его преимущества. Перспективы работы программистом на языке Java.

    курсовая работа [795,9 K], добавлен 14.12.2012

  • Изучение способов организации консольного ввода/вывода данных в режиме черного экрана. Ознакомление со стандартными типами данных (целый, вещественный, символьный, логический) и методами описания переменных, использующихся на языке программирования С++.

    презентация [2,2 M], добавлен 17.04.2010

  • Изучение теоретических основ разработки программы и правил выбора языка программирования. Рассмотрение основных задач по созданию сайта автоклуба. Основы разработки базы данных, создания web-дизайна, текстового наполнения сайта и его публикации.

    курсовая работа [687,9 K], добавлен 07.04.2014

  • Изучение общей структуры языка программирования Delphi: главные и дополнительные составные части среды программирования. Синтаксис и семантика языка программирования Delphi: алфавит языка, элементарные конструкции, переменные, константы и операторы.

    курсовая работа [738,1 K], добавлен 17.05.2010

  • Рассмотрение и ознакомление с одним из наиболее используемых языков программирования - С++. Его применение в процессе работы со строковыми типами данных и символами. Исследование кодов написания программ в режиме разработки консольного приложения.

    курсовая работа [6,1 M], добавлен 20.01.2016

  • Ознакомление с ситуацией распространения на рынке языков программирования. Определение плюсов и минусов Pascal, C++, VBA. Сравнение и анализ синтаксиса программ на основе одной задачи. Выявление лучшего языка для освоения первоначальных навыков.

    курсовая работа [1022,0 K], добавлен 13.10.2014

  • Исследование базовых концепций программирования приложений под операционную систему Windows. Изучение истории создания универсального языка программирования Си. Разработка графического пользовательского интерфейса. Обзор правил игры и алгоритма работы.

    курсовая работа [58,2 K], добавлен 09.11.2012

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.