Главная страница

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


Скачать 32.35 Mb.
НазваниеПрограмма на языке Си, так же как и на большинстве современных языков программирования, создается в два этапа
АнкорЭлектронный конспект по программированию часть 1.doc
Дата04.09.2018
Размер32.35 Mb.
Формат файлаdoc
Имя файлаЭлектронный конспект по программированию часть 1.doc
ТипПрограмма
#24059
страница9 из 12
1   ...   4   5   6   7   8   9   10   11   12

printf ("A[%d][%d]=", i, j); // подсказка для ввода

scanf ("%d", & A[i][j]); // ввод A[i][j]

}

// работа с матрицей

}

Заметьте, что при изменении порядка циклов (если поменять местами два оператора for) изменится и порядок ввода элементов в память.

Заполнение случайными числами

Выполняется также в двойном цикле аналогично одномерным массивам. В примере пока-

зано заполнение целой матрицы случайными числами в интервале [a,b] (для вещественных чисел формула изменится – см. одномерные массивы). Функция

int random ( int N ) { return rand() % N; }

возвращающая случайное целое число в интервале [0,N-1], была рассмотрена выше, когда

мы говорили о массивах (ее нужно добавить в программу).В этой и следующей программах мы будем считать, что объявлена целая матрица M на N,

где M и N — целые константы (объявленные через const), а также целые переменные i и j.

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

for ( j = 0; j < N; j ++ )

A[i][j] = random(b-a+1) + a;

Вывод на экран

При выводе матрицы ее элементы желательно расположить в привычном виде – по стро-

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

printf("Матрица A\n");

for ( i = 0; i < M; i ++ ) { // циклпострокам

for ( j = 0; j < N; j ++ ) // вывод одной строки (в цикле)

printf ( "%4d", A[i][j] ); // 4 символа на число

printf("\n"); // переход на другую строку

}

Работа с файлами

��Текстовые файлы

При вводе из текстового файла надо читать последовательно все элементы, обрабатывая

(так же, как и для линейных массивов) ошибки отсутствия или недостатка данных в файле.

#include

const int M = 5; // числострок

const int N = 4; // числостолбцов

main()

{

int i, j, A[M][N];

FILE *fp;

fp = fopen("input.dat", "r");

for ( i = 0; i < M; i ++ ) // циклпострокам

for ( j = 0; j < N; j ++ ) // цикл по столбцам

if ( 0 == fscanf(fp,"%d",&A[i][j]) ) // ввод A[i][j]

{

puts("Не хватает данных");

fclose ( fp ); // закрыть файл по ошибке

return 1; // выход по ошибке, код ошибки 1

}

fclose ( fp ); // закрыть файл норматьно

// работа с матрицей

}

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

Двоичные файлы

С двоичным файлом удобно работать тогда, когда данные записала (или будет читать)

другая программа и их не надо просматривать вручную. Основное преимущество этого способа— скорость чтения и записи, поскольку весь массив читается (или записывается) сразу единым блоком. При этом функциям fread и fwrite надо указать размер одного элемента массива и количество таких элементов, то есть M*N.

В программе, которая приведена ниже, матрица читается из двоичного файла, затем с ней

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

#include

const int M = 5; // числострок

const int N = 4; // числостолбцов

main()

{

int total, A[M][N];

FILE *fp;

fp = fopen("input.dat", "rb");

total = fread(A, sizeof(int), M*N, fp); // чтениематрицы

fclose ( fp );

if ( total != M*N ) // обработкаошибки

{

printf("Не хватает данных");

return 1; // выход по ошибке, код ошибки 1

}

// работа с матрицей

fp = fopen("output.dat", "wb"); // запись матрицы в файл

if ( M*N != fwrite(A, sizeof(int), M*N, fp) )

printf("Ошибка записи в файл");

fclose ( fp );

}

Для обработки ошибок используется тот факт, что функции fread и fwrite возвращают ко-

личество реально прочитанных (записанных) элементов, и если оно не равно заданному (M*N),то произошла ошибка.

Алгоритмы для работы с матрицами

Перебор элементов матрицы

В отличие от одномерных массивов, для перебора всех элементов матрицы надо исполь-

зовать двойной цикл. Ниже показано, как найти минимальный элемент в массиве и его индексы.Сначала считаем, что минимальным является элемент A[0][0] (хотя можно начать и с любого другого), а затем проходим все элементы, проверяя, нет ли где еще меньшего. Так же, как и для одномерного массива, запоминаются только индексы, а значение минимального элемента «вытаскивается» прямо из массива.

float A[M][N], i, j, row, col;

...

row = col = 0; // сначала считаем, что A[0][0] - минимальный

for ( i = 0; i < M; i ++ ) // просмотр всех строк

for ( j = 0; j < N; j ++ ) // просмотр всех столбцов

if ( A[i][j] < A[row][col] ) {

row = i; // запомнили новые индексы

col = j;

}

printf ("Минимальный элемент A[%d][%d]=%d",

row, col, A[row][col]);

Работа с отдельными элементами

Рассмотрим квадратную матрицу N на N. Выведем на экран обе ее диагонали (главную

диагональ и перпендикулярную ей). С главной диагональю все просто – в цикле выводим все элементы, у которых номера строки и столбца равны, то есть A[i][i] для всех i от 0 до N-1.

Вторую диагональ формируют такие элементы:

A[0][N-1], A[1][N-2], A[2][N-3], ..., A[N-1][0]

Обратим внимание, что каждый следующий элемент имеет номер строки на 1 больше, а номер столбца – на 1 меньше. Таким образом, сумма номеров строки и столбца постоянна и равна N-1. Тогда, зная номер строки i можно сразу сказать, что на второй диагонали стоит ее элемент A[i][N-1-i].

\

Перестановка строк и столбцов

Пусть надо переставить две строки с индексами i1 и i2. Это значит, что для каждого

столбца j надо поменять местами элементы A[i1][j] и A[i2][j] через временную переменную (она называется temp).

for ( j = 0; j < N; j ++ ) {

temp = A[i1][j];

A[i1][j] = A[i2][j];

A[i2][j] = temp;

}

Преобразование в одномерный массив

Иногда надо скопировать матрицу A размером M на N в одномерный массив B размером

M*N. Очевидно, что при копировании по строкам (сначала первая строка, затем вторая и т.д.)элемент первой строки A[0][j] надо скопировать в B[j], элементы второй строки A[1][j] в B[N+j] и т.д. Отсюда следует, что для любой строки i элемент A[i][j] копируется в B[i*N+j].теперь осталось только в двойном цикле перебрать все элементы матрицы.

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

for ( j = 0; j < N; j ++ )

B[i*N+j] = A[i][j];

Заметим, что если надо провести какую-то операцию со всеми или некоторыми (стоящими

подряд) элементами матрицы и для одномерного массива уже есть соответствующая процедура, ее можно использовать, учитывая, что имя строки массива (например, A[0]) является указателем на начальный элемент этой строки. При этом надо учитывать, что в памяти элементы матрицы расположены по строкам. Например, функция вычисления суммы элементов (см. массивы) может применяться для матрицы так:

s0 = Sum(A[0], N); // суммастроки 0

s14 = Sum(A[1], 4*N); // суммастрок 1-4

sAll = Sum(A[0], M*N); // сумма всех строк

Сортировка

Мы уже научились сортировать массивы целых и вещественных чисел, а теперь надо сор-

тировать строки. Можно придумать разные способы, но наиболее часто встречается алфавитная сортировка. При сортировке строк возникают две проблемы.

1. Определить, какая из двух строк «меньше», то есть какая из двух должна стоять выше.

2. Сортировка массивов чисел предусматривает перестановку элементов массива. В случае строк это связано с копированием больших объемов данных, что крайне невыгодно.Начнем с первой. Мы говорили, что есть функция сравнения строк strcmp, которая возвращает нуль, если строки равны, и не нуль, если они разные. Оказывается, эта функция возвращает «разность» этих строк, то есть разность кодов их первых отличающихся символов.Если функция вернула отрицательное число, то первая строка «меньше» второй и стоит по алфавиту раньше, если положительное – наоборот. Здесь надо учитывать, что сравнение идет по таблице кодов, и коды заглавных букв меньше, чем коды строчных (и для русских, и для английских).

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

памяти массив указателей на строки и сделаем так, чтобы i-ый указатель указывал на i-ую

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

void SortStrings ( char *s[], int n ) // *s[] – массивуказателей

{ // n – число строк

char *p;

int i, j;

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

for ( j = n-1; j > i; j -- )

if ( strcmp(s[j-1],s[j]) > 0 ) {

p = s[j]; s[j] = s[j-1]; // перестановка УКАЗАТЕЛЕЙ

s[j-1] = p;

}

}

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

main()

{

char s[20][80]; // массив из 20 строк

char *ps[20]; // массив из 20 указателей на строки

int i, count;

// здесь нужно ввести строки и записать

// в переменную count их количество

for ( i = 0; i < count; i ++ ) // расставитьуказатели

ps[i] = s[i];

SortStrings ( ps, count ); // сортировкауказателей

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

puts ( ps[i] ); // вывод через указатели

}

5. Управление памятью

Указатели

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

1. Выделить память с запасом, если известно, например, что этих чисел гарантированно не

более 1000.

2. Использовать динамическое выделение памяти – сначала определить, сколько чисел в

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

Наиболее грамотным решением является второй вариант (использование динамических массивов). Для этого надо сначала поближе познакомиться с указателями.

Указатель – это переменная, в которой хранится адрес другой переменной или участка памяти.Указатели являются одним из основных понятий языка Си. В такие переменные можно записывать адреса любых участков памяти, на чаще всего – адрес начального элемента динамического массива. Что же можно делать с указателями?


Итак, что надо знать про указатели:

• указатель – это переменная, в которой записан адрес другой переменной;

• при объявлении указателя надо указать тип переменных, на которых он будет указывать, а

перед именем поставить знак *;

• знак &перед именем переменной обозначает ее адрес;

• знак * перед указателем в рабочей части программы (не в объявлении) обозначает значение ячейки, на которую указывает указатель;

• нельзя записывать по указателю, который указывает непонятно куда – это вызывает сбой

программы, поскольку что-то стирается в памяти;

• для обозначения недействительного указателя используется константа NULL;

• при изменении значения указателя на n он в самом деле сдвигается к n-ому следующему числу данного типа, то есть для указателей на целые числа на n*sizeof(int) байт;

• указатель печатаются по формату %p.

Теперь вам должно быть понятно, что многие функции ввода типа scanf и fscanf в самом

деле принимают в параметрах адреса переменных, например

scanf ( "%d", &i);

Динамическое выделение памяти

Динамическими называются массивы, размер которых неизвестен на этапе написания

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

Следующая простейшая программа, которая использует динамический массив, вводит с

клавиатуры размер массива, все его элементы, а затем сортирует их и выводит на экран.

#include

main()

{

int N; // размер массива (заранее неизвестен)

int *A; // указатель для выделения памяти

printf ("Размер массива > "); // ввод размера массива

scanf ("%d", &N);

A = new int [N]; // выделениепамяти

if ( A == NULL ) { // если не удалось выделить

printf("Не удалось выделить память");

return 1; // выход по ошибке, код ошибки 1

}

for (i = 0; i < N; i ++ ) { // дальше так же, как для массива

printf ("\nA[%d] > ", i+1);

scanf ("%d", &A[i]);

}

// здесь сортировка и вывод на экран

delete A; // освободить память

}

Итак, мы хотим выделить в памяти место под новый массив целых чисел во время работы

программы. Мы уже знаем его размер, пусть он хранится в переменной N (это число обязательно должно быть больше нуля). Оператор выделения памяти new вернет нам адрес нового выделенного блока и для работы с массивов нам надо где-то его запомнить. Вы уже знаете, что есть специальный класс переменных для записи в них адресов памяти – указатели.

• динамические массивы используются тогда, когда на момент написания программы раз-

мер массива неизвестен

• для того, чтобы работать с динамическим массивом, надо объявить указатель соответствующего типа (в нем будет храниться адрес первого элемента массива);

int *A;

• когда требуемый размер массива стал известен, надо использовать оператор new языка

Си++, указав в скобках размер массива (в программе для краткости нет проверки на положительностьN) ;

A = newint[N];

нельзя использовать оператор new при отрицательном или нулевом N;

после выделения памяти надо проверить, успешно ли оно прошло; если память выделить не удалось, то значение указателя будет равно NULL, использование такого массива приведет к сбой программы;

• работа с динамическим массивом, на который указывает указательА, идет также, как и с обычным массивом размера N (помните, что язык Си не следит за выходом за границы

массива);

• после использования массива надо освободить выделенную память, вызвав оператор

delete A;

• после освобождения памяти значение указателя не изменяется, но использовать его уже

нельзя, потому что память освобождена;

• учитывая, что при добавлении числа к указателю он сдвигается на заданное число ячеек

ЗАДАННОГО ТИПА, то следующие записи равносильны и вычисляют адрес i-ого эле-

мента массива:

&A[i] и A+i

Отсюда следует, что A совпадает с &A[0], и поэтому имя массива можно использовать

как адрес его начального элемента;

Ошибки, связанные с выделением памяти

Самые тяжелые и трудно вылавливаемые ошибки в программах на языке Си связаны

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




\Выделение памяти для матрицы

Для выделения памяти под одномерный массив целых чисел нам потребовался указатель

на целые числа. Для матрицы надо выделить указатель на массив целых чисел, который объявляется как
1   ...   4   5   6   7   8   9   10   11   12


написать администратору сайта