Партнерка на США и Канаду по недвижимости, выплаты в крипто
- 30% recurring commission
- Выплаты в USDT
- Вывод каждую неделю
- Комиссия до 5 лет за каждого referral
ЯЗЫК СИ
Тема №1. Функции стандартного ввода–вывода, вычисление математических функций.
Задание: составить программу для проведения математических вычислений по индивидуальным вариантам заданий, которые представлены далее по тексту; все исходные данные, необходимые для вычислений, вводить с клавиатуры.
Программа должна после запуска на исполнение выводить информацию об авторе, назначении программы (приводится лабораторное задание полностью), перед запросом ввода данных с клавиатуры обязательно должно быть сообщение о типе и количестве вводимых данных.
Краткая теоретическая справка и рекомендации по выполнению
Кроме выполнения вычислений заданием предусмотрено изучение наиболее распространенных математических функций, даже если они не используются при выполнении задания.
Для каждой функции обратите внимание на тип обрабатываемых данных, например, результатом функции abs(–3.7) будет число 3.0, а не 3.7 как ожидалось, т. к. функция abs может работать только с целыми числами.
Перечень математических функций для изучения (в тригонометрических функциях углы измеряются в радианах):
abs (fabs) – абсолютная величина целого числа (числа с плавающей точкой),
acos – вычисление арккосинуса,
asin – вычисление арксинуса,
atan – вычисление арктангенса,
atan2 – вычисление арктангенса (вычисляет угол по значению катетов),
atof – преобразует строку в число с плавающей точкой,
cabs – вычисляет абсолютную величину комплексного числа,
ceil – округление до ближайшего целого в большую сторону,
cos – косинус,
cosh – гиперболический косинус,
exp – экспонента,
floor – округление до ближайшего целого в меньшую сторону,
fmod – остаток целочисленного деления двух чисел,
frexp – вычисляет значение мантиссы m и показателя степени n для преобразования числа с плавающей точкой к виду
,
hypot – вычисляет значение гипотенузы по значениям катетов,
ldexp – по значению мантиссы m и показателя степени n вычисляет число с плавающей точкой по формуле
,
log – вычисляет натуральный логарифм числа,
log10 – вычисляет десятичный логарифм числа,
modf – разбивает число с плавающей точкой на целую и дробную части,
poly – вычисляет полином вида
,
pow – вычисляет число в степени,
pow10 – вычисление числа 10 в степень,
sin – вычисление синуса угла,
sinh – вычисление гиперболического синуса,
sqrt – вычисление квадратного корня,
tan – вычисление тангенса угла,
tanh - вычисление гиперболического тангенса угла.
Пример выполнения задания по теме № 1
/* Блок директив препроцессору */
#include <math. h>
#include <stdio. h>
#define g 9.81
/* Объявление глобальной переменной */
float H;
/* Блок главной функции программы */
int main()
{
/* Объявление локальной переменной */
float V;
printf("\nЛабораторная работа N1 вариант N12");
printf("\n\nВыполнила студентка гр. Пд-11 ");
printf("\nВертикально вверх бросают камень со скоростью V.");
printf("\nОпределите максимальную высоту подъема камня.");
printf("\nВведите начальную скорость в м/c (от 0 до 100) V=");
scanf("%f",&V);
H=pow(V,2);
H=H/(2*g);
printf("\nМаксимальная высота подъема камня H=%5.2f м",H);
return 0;
}
Обратите внимание на то, что программа состоит из нескольких блоков, первый из которых включение (include) заголовочных файлов для обращения к библиотекам языка Си. В отличие от языка Паскаль ни одна библиотечная функция языка Си не может быть использована без указания места расположения ее прототипа. Кроме того, не забудьте, что язык Си различает регистр написания букв. Поэтому компилятор примет написание имени функции printf, но укажет, что printf – это неизвестный идентификатор.
Найдите описание функций, которые относятся к используемым в программе библиотекам: математической (math), библиотеке стандартного ввода-вывода (stdio). Более пристального внимания требуют функции форматного ввода-вывода (scanf и printf), особенно вопросы задания формата вводимых и выводимых данных, так как в Паскале выбор формата выводимых данных определял компилятор по типу переменной, а здесь выбор (как и многое другое) определяет программист. Не забудьте, что функция scanf требует адреса переменной.
Блок объявления главной функции, операторные скобки и локальные переменные требуют несколько более пристального внимания (опять из-за привычки к синтаксису языка Паскаль). Здесь чаще всего встречаются следующие ошибки:
- лишняя точка с запятой после объявления функции main(),
- объявление локальной переменной до фигурной скобки,
- забыты круглые скобки после идентификатора функции при ее объявлении или вызове на исполнение (напомним, что скобки обязательны, даже если функция не содержит параметров).
Контрольные вопросы по теме № 1
1. Что может быть в качестве параметров в функциях scanf, printf (по типам данных, но без файлового ввода/вывода)?
2. В чем отличие scanf и cscanf, printf и cprintf?
3. Как задать формат вывода чисел в функции printf?
4. Дайте общую характеристику библиотечных математических функций.
5. Как запустить программу на исполнение?
6. Как откомпилировать программу?
7. Где находится результат компиляции программы?
8. Как загрузить и сохранить текст программы?
9. Что означает директива препроцессору #include?
Тема №2. Ветвление программы (использование операторов ветвления и переключателя switch).
Задание: используя операторы ветвления (if и if … else), составить программу для вычисления составной (сложной) функции, имеющей различный вид на разных участках аргумента. С помощью переключателя (switch) указать на каком отрезке находится введенное с клавиатуры значение аргумента, и вывести значение функции в данной точке.
Все исходные данные, необходимые для проведения вычислений, вводить с клавиатуры. Начертить блок-схему алгоритма вычисления.
Пример выполнения лабораторного задания № 2
Пусть требуется вычислить составную функцию, заданную на 4-х отрезках изменения аргумента. В данном примере приведем программу в соответствии со всеми указанными ранее требованиями, в том числе подробным описанием задания и комментариями (внутри фигурных скобок) по тексту программы.
Обратите внимание на отступы вправо, принятые при записи программ на языке Cи.
Условный оператор специально используется в двух форматах: с применением и отсутствием альтернативной части (else).
#include <stdio. h>
#include <stdlib. h>
#include <math. h>
int main()
{ /* задание переменной перечисляемого типа */
enum {def, o1,o2,o3,o4} otr; /* номер отрезка, def – выбор по умолчанию */
float x, y;
printf(" Лабораторная работа N 2 вариант 27 \n");
printf("Тема: Ветвление программы \n");
printf(" Задание: Используя оператор if, составить программу \n");
printf("для вычисления составной функции вида: \n");
printf("1> sqr(x) при x от -2.0 до 1.0\n");
printf("2> sqrt(x) при x от 1.0 до 2.0\n");
printf("3> y=sqrt(x-2)+sqrt(2) при x от 2.0 до 3.0\n");
printf("4> y=1+sqrt(x-1) при x от 3.0 до 4.0\n");
printf("указать отрезок, где находится введенный с клавиатуры \n");
printf("аргумент и вывести значение функции в данной точке. \n");
printf(" Автор: , гр. Пд-11 \n");
printf("Введите x=");
scanf("%f",&x); printf("\n");
/* Выбор отрезка */
otr=def;
if ((x>=-2.0) && (x<1.0)) otr=o1;
else if ((x>=1.0) && (x<2.0)) otr=o2;
if ((x>=2.0) && (x<3.0)) otr=o3;
if ((x>=3.0) && (x<=4.0)) otr=o4;
/* Вычисление значения функции */
switch (otr)
{
case o1: y=pow(x,2); break;
case o2: y=sqrt(x); break;
case o3: y=sqrt(x-2)+sqrt(2); break;
case o4: y=1+sqrt(x-1); break;
default: printf("Число вне диапазона."); getch(); exit(1);
}
printf ("Промежуток %d: y= %3.2f; ",otr, y);
return 0;
}
Обратите внимание, что в примере использована еще одна библиотека stdlib. Часть функций этой библиотеки уже известна по математической библиотеке, а другие функции надо будет изучать по мере необходимости.
Новым является использование перечисляемого типа данных (enum). Объявление перечисления задает тип переменной перечисления и определяет список именованных констант, называемый списком перечисления. Значением каждого имени списка является некоторое целое число. Переменная типа перечисления может принимать значения одной из именованных констант списка. Именованные константы списка имеют тип int. Поэтому переменной перечисляемого типа можно присвоить любое значение типа int. Фатальной ошибки при этом не будет, только предупреждение (Assign int to enum) о том, что переменной перечисляемого типа присвоено целое значение. Допускаете ли вы такую вольность или нет, решайте сами, так как последствия могут быть неожиданными.
При использовании операторов ветвления программы (if, else), несколько существенных особенностей, отличающих синтаксис языков Паскаль и Си и дающих наибольшее число ошибок:
- в языке Си результатом логической операции служит целое число, любое целое число, не равное 0, интерпретируется как истина;
- поразрядные (битовые) логические операторы: & , | , ^ , ~ ;
- логические операторы: && , || , ! ;
- логическое выражение после слова if обязательно должно быть в круглых скобках;
- выражение перед альтернативной частью (else) должно заканчиваться точкой с запятой;
- оператор проверки на равенство обозначается двойным знаком «равно», а проверка на неравенство – «восклицательный знак равно».
Контрольные вопросы к лабораторной работе № 2
1. Как используются операторы логических операций (и, или, исключающее или) в условных выражениях?
2. Начертите таблицы истинности логических операций.
3. Укажите тип переменной оператора switch, по значению которой выполняется переключение.
4. Какое действие может быть выполнено в операторе switch по умолчанию?
5. Какой синтаксис записи метки в операторе switch?
6. Как использовать окно Watch?
7. Как выполнить программу по шагам (Step over и Trace into)?
8. Как средствами встроенного редактора производится копирование и вклейка фрагмента текста?
9. Каково назначение и использование оператора break?
Тема №3. Циклы, символьный (литерный) тип данных.
Задание: написать программу при помощи оператора цикла for (без применения массивов и прямой адресации курсора) для распечатки на экране монитора указанного в варианте набора символов. Начертить блок-схему к программе.
При выполнении заданий в первую очередь обратите внимание на то, что в языке Си литерный тип данных – это число (один байт). Поэтому с ним можно выполнять все операции, предусмотренные для работы с числами.
Второе – цикл for. Формат использования цикла for имеет следующий вид:
for ( [действие до цикла]; [предусловие цикла]; [последнее действие]).
Любое действие в прямоугольных скобках может отсутствовать. Предусловие (условие выполнения цикла) – это любое действие, результат которого будет интерпретироваться как int. Если результат равен нулю, то условие ложно, в противном случае истинно.
Третье, на что надо обратить внимание – цикл do…while будет повторяться, если условие в конструкции while – истинно.
Примеры выполнения задания по теме №3.
Распечатать на экране монитора таблицу символов, используя только циклы (без использования массивов и строк)
A | C | E | G | I |
I | A | C | E | G |
G | I | A | C | E |
E | G | I | A | C |
C | E | G | I | A |
Используя замеченные закономерности можно предложить следующий алгоритм решения:
§ надо организовать счет по строкам от первой до пятой (цикл по счетчику i), при каждом повторении этого цикла, т. е. внутри него будет выполняться три действия, объединенные в составном операторе:
I) распечатка строки символов (сначала A C E G I, а потом и других),
II) переход на следующую строку,
III) вычисление значения первого символа в следующей строке;
§ в каждой строке необходимо организовать счет (переменная j) по символам (с 1-ого до 5-ого); при каждом повторении цикла будет выполняться три действия (опять нужен составной оператор):
1) будет проверяться значение печатаемого символа (переменная C), и если окажется, что оно больше ‘I’, тогда переменной C присваивается значение ‘A’,
2) значение переменной C выводится на экран монитора,
3) переменной C присваивается новое значение (следующее за следующим, т. е. символы следуют через один).
Как при описании алгоритма, так и по тексту программы, действия внешнего цикла обозначены римскими цифрами, а внутреннего – арабскими. Решение записано в двух вариантах: подробном и сокращенном для демонстрации возможностей цикла for.
#include <conio. h>
#include <stdio. h>
int main() // ПОДРОБНЫЙ ВАРИАНТ ЗАПИСИ ПРОГРАММЫ
{
int i, j; /* Счетчики */
char c='A'; /* Переменная для хранения значения первого символа
в строке */
// Цикл по строкам
for (i=1; i<=5; i++)
{ /* Начало действий внутри цикла по строкам */
/**** I) Печать строки символов */
for (j=1; j<=5; j++)
{ /* Начало тела цикла печати строки */
if (c>'I') c='A'; /*** 1) Проверка значения символа */
printf ("%2c",c); /*** 2) Вывод символа на экран */
c++; c++; /*** 3) Новое значение символа */
} /* Конец тела цикла печати строки */
/**** II) Переход на следующую строку */
printf ("\n");
/**** III) Подготовка первого символа следующей строки */
c--;
c--;
} /* Конец действий внутри цикла по строкам */
getch();
return 0;
}
После подробно описанного алгоритма работы, особенностей, на которые можно было бы обратить внимание в первой программе, совсем немного. Это инициализация переменной C во время объявления и изменение значения литерной переменной C оператором декремента («минус-минус»), хотя С – переменная литерного типа.
Вторая программа (сокращенный вариант записи программы) также содержит особенности свойственные языку Си:
1) переменные i и j - литерного типа, но потом будут использованы как целочисленные;
2) в циклах for использован оператор запятая, позволяющий сделать программу более компактной.
Несмотря на перечисленные особенности, нетрудно заметить, что алгоритм решения задачи и в первом и во втором случае один и тот же.
#include <conio. h>
#include <stdio. h>
int main() // СОКРАЩЕННЫЙ ВАРИАНТ ЗАПИСИ ПРОГРАММЫ
{
char i, j, c;
for (i=1,c='A'; i<=5; printf ("\n"),c-=2,i++)
{
for (j=1; j<=5; c+=2,j++)
{
if (c>'I') c='A';
printf ("%2c",c);
}
}
return 0;
}
Вторая программа рисует ту же таблицу, но используется цикл while и другой алгоритм (см. пример для языка Паскаль):
#include <stdio. h>
#include <conio. h>
char First; /* Первый символ в строке */
char C; /* Текущий символ в строке */
int main()
{
First='K'; /* Первый символ в первой строке */
while (First>='C')
{
/* Печать первой части строки символов */
C=First;
while (C<='I')
{
printf("%2c",C); // Печать очередного символа
C+=2; // Вычисление следующего значения
}
/* Печать второй части строки символов */
C='A';
while (C<First)
{
printf("%2c",C); // Печать символа
C+=2; // Вычисление следующего значения
}
/* Переход на другую строку */
printf("\n");
/* Вычисление значения первого символа */
First-=2;
}
getch();
return 0;
}
Контрольные вопросы к теме № 3
1. Как использовать цикл for в языке Си?
2. Что означают операторы «++», «– –», «+=» и т. д.?
3. Назначение оператора break в языке Си?
4. К какому типу относится цикл for (с пред - или пост - условием)?
5. В чем отличие цикла for в языках Паскаль и Си?
6. Как использовать цикл while?
7. Как использовать цикл do…while?
8. Чем отличаются циклы с пред-и пост-условиями?
Тема №4. Функции в языке Си.
Задание: реализуйте задачу по теме №3 с использованием функций.
Пример решения задачи по теме №3 с использованием функций.
Для иллюстрации решения рассмотрим алгоритм, представленный для языка Паскаль. Сделаем «перевод» с языка Паскаля на язык Си, указав отличия.
Во-первых, нет необходимости процедуры определения предыдущего элемента перед предыдущим, так как эта операция в языке Си делается в одно действие.
Во-вторых, напомним, что в языке Си нет понятия «процедура», но есть функция, которая не возвращает никакого значения.
В-третьих, не ставьте знак «точка с запятой» после закрывающей круглой скобки в перечне параметров.
В-четвертых, для каждого параметра обязательно укажите тип, а параметры разделяйте запятыми.
Пятое – при вызове функции на исполнение, если даже у нее нет параметров, круглые скобки обязательны.
Шестое – локальные переменные пишутся внутри операторных скобок, определяющих тело функции.
#include <stdio. h>
#include <conio. h>
//Описание функции печати части строки
void Part(char First, char Last)
{ char C;
C = First;
Last --;
while (C <= Last)
{
printf("%2c",C);
C+=2;
}
}
//Основная часть программы
int main()
{
const int F1= 'K';
const int F2='A';
char First1 =F1, First2 =F2;
while (First1>='C')
{
Part(First1,F1); // Печать первой части строки
Part(First2,First1); // Печать второй части строки
printf("\n");
First1 -=2;
}
return 0;
}
}Контрольные вопросы по теме №4
1. Что такое глобальные переменные?
2. Для чего нужны параметры в функции?
3. Назначение функций?
4. Что такое локальные переменные?
5. Что такое параметры-переменные?
6. Что такое параметры-значения?
7. Что такое рекурсия?
8. Что такое условие выхода из рекурсии?
9. Что такое формальные параметры?
10. Что такое фактические параметры?
Тема №5. Обработка одномерных массивов.
Задание: заполните один или несколько одномерных массивов (по индивидуальному заданию). Распечатайте результат заполнения. Проведите преобразование (по индивидуальному заданию) и упорядочивание (сортировку) массивов. Распечатайте результирующие массивы. Сортировки проведите тремя методами. При использовании каждого из методов желательно использовать свой массив. Если количество массивов меньше трех, то один из массивов перед сортировкой надо скопировать в дополнительный массив и провести его сортировку.
Краткая теоретическая справка и рекомендации по выполнению
Для переменных типа массив в языке Си всегда в качестве индекса используется целое число. Это связано с тем, что переменная типа массив является указателем на начало этого одномерного массива. Аналогично в математике и физике определяется относительная система координат. А индекс – это смещение относительно начала отсчета в этой системе. Причем компилятор языка Си не отслеживает правильность использования индекса. По этой причине при неаккуратном программировании может возникнуть множество проблем, вплоть до «зависания» компьютера, если только операционная система не «отследит» обращение по некорректному адресу.
Самый безобидный вариант некорректного использования индексов приведен в следующем коротком фрагменте программы:
int x; int y[2]; int z;
void main()
{
x=1; z=4;
y[0]=2; y[1]=3; // Корректное обращение к элементам массива
y[-1]=5; y[2]=6; // Некорректное обращение к элементам массива
}
Здесь три глобальные переменные x, y, z расположены друг за другом. Так как они глобальные, то в такой же последовательности они будут располагаться в оперативной памяти. Поэтому присваивание элементу y[–1] значения 5, приведет к изменению переменной x, а изменение элемента y[2] изменит переменную z. На этом же примере легко можно убедиться, что при объявлении переменной типа массив, в квадратных скобках указывается общее количество элементов массива. Если количество элементов переменной типа массив было N, то первый элемент будет с индексом 0, а последний – с индексом (N–1).
Для одномерных массивов также распространена другая форма обращения к отдельному элементу. В записи *(y+1), в соответствии с порядком выполнения операций языка Си, сначала вычисляется адрес размещения переменной в оперативной памяти (y+1), а потом определяется сам элемент с помощью оператора «звездочка» (разыменование). При выполнении лабораторного задания необходимо хотя бы в одном месте использовать обращение к элементу массива через указатель.
Примеры функций для работы с одномерными массивами.
// 1. Инициализация генератора случайных чисел
void Randomize()
{
srand(time(0));
}
Функция инициализации генератора случайных чисел использует библиотечные функции srand, в которую в качестве параметра передается текущее время time(0). Прототип функции time находится в заголовочном файле <time.h>.
//2. Генератор случайного числа в диапазоне от 0 до range
int Random(int range)
{
return (rand() % range);
}
Функция генератора случайных чисел использует библиотечную функцию rand (прототип в файле stdlib.h). Функция rand вычисляет псевдослучайное число в диапазоне от 0 до 65535. Для задания числа в заданном диапазоне используется остаток от целочисленного деления генерируемого числа на диапазон range.
// 3. Проверка повторения случайного числа
// c - указатель начала массива, n - индекс нового элемента
int Test_Repetition(int *c, int n)
{ int x, j;
x = 0; // Считаем, что значение новое
// Цикл сравнения со всеми предыдущими
for (j=0; j<n; j++)
if (*(c+n)==*(c+j))
{
x = 1; // Элементы совпали
break;
}
return x;
}
Функция проверки повторения случайного числа используется только тогда, когда по условию задачи необходимо генерировать неповторяющиеся случайные числа. Эта функция простым перебором сравнивает новое число (c[n]) со всеми значениями массива.
// 4. Добавить новый элемент
// с - указатель начала массива, n - номер нового элемента
// range1, range2 – левая и правая границы диапазона
void New_Item(int *c, int n, int range1, int range2)
{ int x = 0; // Элементы массива разные
do
{
c[n] = Random(range2 - range1) + range1; // Новое значение
x =Test_Repetition(c, n); // Проверка на повторение
}
while (x==1); // Повторять, когда элементы совпали
}
В этой функции, если проверка на «неповторимость» элемента не требуется, достаточно использовать только строку присваивания в переменную c[n] результата работы функции Random.
// 5. Заполнение одномерного массива
// c - массив, n - количество элементов
void Filling (int *c, int n, int range1, int range2)
{ int i;
c[0]=Random (range2 - range1) + range1; // Элемент с индексом 0.
// Цикл заполнения массива
for (i=1;i<n; i++) // Цикл заполнения элементов значениями
New_Item(c, i, range1, range2);
}
Для реализации заполнения неповторяющимися числами, в функции заполнения массива приходится первое значение (с индексом 0) задавать отдельно, а не в цикле.
// 6. Распечатка одномерного массива
void Print(int *c, int n)
{ int i;
for (i=0; i<n; i++)
printf("%4d",c[i]);
puts(""); // Переход на новую строку
}
// 7. Поиск минимального значения
// с – указатель на начало одномерного массива,
// n - количество элементов массива,
// index – указатель на индекс минимума
int Min(int *c, int n, int *index)
{ int i, min;
min = c[0]; // Начальное значение минимума
(*index) = 0; // Начальное значение индекса минимума
for ( i=1; i<n; i++ )
if (c[i]<min)
{
min = c[i];
(*index) = i;
}
return min;
}
В функции поиска минимального значения в массиве обратите внимание на использование параметра-переменной index, которая передается указателем.
// 8. Создание дубликата массива в динамической памяти
int * Copy (int *c, int n)
{ int *m, i;
// Резервирование памяти под дубликат массива
m = (int*)malloc(sizeof(int)*n);
// Копирование массива
for ( i = 0; i<n; i++ ) m[i] = c[i];
// Функция возвращает адрес нового массива
return m;
}
Обратите внимание, что количество резервируемой памяти определяется типом данных одного элемента и количеством элементов в массиве ((sizeof(int)*n)). Так как библиотечная функция для резервирования памяти (malloc) возвращает нетипированный указатель, то требуется преобразование к типу. В данном примере – это преобразование к типу указателя на целое число (int *).
// 9. Обмен элементов местами в массиве
void Exchange (int *c, int n, int k)
{ int tmp; //Переменная для временного хранения данных
tmp = c[n];
c[n] = c[k];
c[k] = tmp;
}
// 10 .Сортировка методом прямого поиска
void SearchSort ( int *c, int n )
{ int i, min, indexMin; int *p;
for ( i=0; i<n-1; i++) // Определить массив с индексами от i до n
{ p = (c+i); // Задать адрес начала массива
min = Min (p, n-i, &indexMin); // Найти минимум в массиве
// Обменять местами минимальный элемент с первым
Exchange ( p, 0, indexMin ); }
}
Основная часть программы для обработки одномерных массивов может содержать заголовочные файлы, предварительное описание функций и главную часть программы. Предварительно описанные функции (в виде заголовков) могут быть представлены после главной функции.
#include <stdio. h>
#include <stdlib. h>
#include <locale. h>
#include <time. h>
#define N 15
void Randomize();
int Random(int range);
int Test_Repetition(int *c, int n);
void New_Item(int *c, int n, int range1, int range2);
void Filling (int *c, int n, int range1, int range2);
void Print(int *c, int n);
int Min(int *c, int n, int *index);
int * Copy (int *c, int n);
void Exchange (int *c, int n, int k);
void SearchSort ( int *c, int n );
// Главная функция
int main()
{
int m[N];
int min, IndexMin;
int *Duplicate;
Rus();
Randomize();
Title();
puts("\nЗаполнение массива");
Filling(m, N,-30,70);
puts("\nРаспечатка массива\n");
Print (m,N);
puts("\nМинимальное значение массива\n");
min = Min(m, N,&IndexMin);
printf("min = %d его индекс = %d \n", min, IndexMin);
puts("\nСоздание дубликата массива\n");
Duplicate = Copy(m,N);
puts("\nРаспечатка дубликата массива\n");
Print (Duplicate,N);
puts("\nСортировка дубликата методом прямого поиска");
SearchSort(Duplicate,N);
puts("\nРаспечатка дубликата массива после сортировки\n");
Print (Duplicate, N);
puts("\nУдаление дубликата");
free(Duplicate);
return 0;
}
Контрольные вопросы по теме № 5.
1. Какого типа могут быть индексы массивов?
2. Что такое размерность массивов?
3. Для чего нужен «барьер» в сортировке методом прямого включения?
4. Каков алгоритм поиска минимума или максимума в одномерном массиве?
5. Как найти индекс элемента в одномерном массиве по его значению?
6. Алгоритм поиска минимума или максимума в двумерном массиве.
7. Назначение функций random и randomize?
8. Сортировка элементов двумерного массива.
9. Как обратиться к элементу массива по указателю?
Тема №6. Строки и использование библиотечных функций для их обработки.
Задание: Введите с клавиатуры предложение, выделите из предложения отдельные слова в массив слов, проведите преобразование предложения и слов в соответствии с индивидуальным заданием, распечатайте результат обработки на экран монитора. Используйте как можно больше библиотечных функций для работы со строками.
Индивидуальные задания на лабораторную работу даны в конце описания к теме № 6. Длина вводимого предложения не более 80 символов (ширина экрана). При выполнении задания необходимо во всех случаях, где это можно, использовать функции для работы со строками. Все задания подразумевают необходимость применения не менее трех разных функций.
Краткая теоретическая справка и рекомендации по выполнению
Строка в языке Си представляет собой одномерный массив символов, последним элементом которой является символ конца строки – нуль (строка, завершающаяся нулем, то есть NULL terminated string).
Объявление переменной типа строка в языке Си возможно тремя способами, два из которых инициализируют строку во время объявления. Первый способ ничем не отличается от объявления массива символов (не забудьте добавить место для завершающего нуля):
char s[40+1];
Второй способ предусматривает присваивание строковой переменной начального значения (при этом длину строки компилятор может вычислить сам):
char s[] = "Пример инициализации строки";
Справа от знака присваивания записана строковая константа. В конце строки автоматически добавляется ноль ('\0'). Константы символьных строк помещаются в класс статической памяти.
В третьем способе явно не указывается, что используется массив. В левой части от знака присваивания указывается указатель на символ:
char *s="Второй вариант инициализации";
Переменная s будет указателем на то место в оперативной памяти, где располагается строковая константа. В такой форме записи кроется потенциальная ошибка, заключающаяся в том, что указатель на символ часто называют строкой. Представленная ниже запись – это только указатель на символ, так как для размещения строки место не предусмотрено:
char *s;
Для работы со строками есть набор функций. Для ввода со стандартного устройства ввода (клавиатуры) чаще всего используются библиотечные функциями из модуля стандартного ввода-вывода: scanf и gets.
Для ввода строки с помощью функции scanf, использует формат «%s», причем обратите внимание на то, что перед идентификатором строки не используется знак адреса «&», так как одномерный массив уже представлен указателем на его начало:
scanf("%s", s);
Функция gets() считывает символы до тех пор, пока не достигнет символа перехода на новую строку. Функция принимает все символы вплоть до символа перевода строки, но не включает его. К концу строки добавляется завершающий ноль ('\0'). Функция gets() помещает считанную с клавиатуры последовательность символов в параметр типа строка и возвращает указатель на эту строку (если операция завершилась успешно), или NULL (в случае ошибки). В приведенном ниже примере при успешном завершении операции, на экран будет выведено две одинаковые строки:
#include <stdio. h>
int main()
{ char s[50];
char *p;
p=gets(s);
printf(" \n Введена строка %s. ",s);
if (p) printf(" \n Введена строка %s. ",p);
return 0;
}
Попутно заметим, что функция gets часто используется для ввода любых данных с клавиатуры в виде строки с целью дальнейшего преобразования функцией sscanf к нужному формату или для предварительного анализа вводимых данных, например:
#include <string. h>
#include <stdio. h>
#include <conio. h>
int main()
{ char s[50]; int x, err;
do
{ printf(" \n Введите целое число -> ");
gets(s);
err=sscanf(s, "%d",&x);
if (err!=1) printf(" \n Ошибка ввода. ");
} while (err!=1);
printf("\n Введено целое число -> %d", x);
return 0;
}
Для вывода строк на стандартное устройство вывода (экран монитора) можно использовать две функции printf и puts. В функции printf в качестве формата передается "%s". Удобство использования этой функции заключается в том, что помимо строки можно сразу выводит данные других типов. Особенность функции puts заключается в том, что после вывода строки автоматически происходит переход на следующую строку.
Для преобразования строк в языке Си предусмотрена библиотека string. Каждая из функций имеет свой формат записи (прототип). Некоторые из функций представлены ниже. Более подробное описание функций смотрите в указанных библиографических источниках.
void *strcat(char *s1,const char *s2) – дополняет строку s1 строкой s2.
char *strncat(char *s1,const char *s2, size_t n) – дополняет строку s1 символами из строки s2, но не более чем n штук.
char *strcpy(char *s1,const char *s2) – копирует строку, указанную указателем s2, на место, указанное указателем s1; возвращает s1.
char *stpcpy(char *dest, const char *src) – копирует строку, указанную указателем src, на место, указанное указателем dest; не копирует символ конца строки;
char *strncpy(char *s1, const char *s2, size_t n) – – копирует строку, указанную указателем s2, на место, указанное указателем s1; возвращает s1; копируется не более чем n символов.
int strcmp(const char *s1,const char *s2) – сравнивает строки, указанные указателями s1 и s2; символы строк сравнивают с помощью значений их кодов; функция возвращает 0, если строки одинаковы; значение, которое меньше 0, если первая строка меньше второй; и значение, превышающее 0, если первая строка больше второй.
int strncmp(const char *s1,const char *s2, size_t n ) – сравнивает первые n символов или до первого пустого символа строки, указанные указателями s1 и s2.
int stricmp(const char *s1,const char *s2) – сравнивает строки, указанные указателями s1 и s2, игнорируя регистр символов (строчные или заглавные);
int strcmpi(const char *s1,const char *s2) – сравнивает строки, указанные указателями s1 и s2, игнорируя регистр символов (строчные или заглавные);
chat *strchr(const chat *s, int c) – ищет первое появление c (преобразованного в char) в строке, указанной указателем s; пустой символ является частью строки; возвращает указатель на первое появление или NULL, если ничего не найдено.
chat *strrchr(const chat *s, int c) – ищет последнее появление символа c в строке, то есть поиск идет с конца строки, заданной указателем s; пустой символ является частью строки; возвращает указатель на первое появление или NULL, если ничего не найдено.
char *strstr(const chat *s1, const char *s2) – возвращает указатель на положение первого появления последовательности символов из s2 в строке s1 (исключая завершающие пробелы); возвращает NULL, если совпадений не найдено.
chat *strtok(chat *s1,const chat *s2) – эта функция переформирует строку s1 в отдельные знаки; строка s2 содержит символы, которые используются в качестве разделителей. Функция вызывается последовательно. Для первого вызова s1 должен указывать на строку, которую необходимо разбить на знаки. Функция находит разделитель, который следует за символом, не являющимся разделителем, и заменяет, его пробелом. Она возвращает указатель на строку, содержащую первый знак. Если ни оного знака не найдено, она возвращает NULL. Чтобы найти следующий знак в строке, необходимо вызвать функцию опять, но первым аргументом поставить NULL. Каждый последовательный вызов возвращает указатель на следующий знак или на NULL, если больше знаков не найдено.
int strlen(const char *s) – возвращает число символов (исключая завершающие пробелы) в строке s.
void setmem(void *dest, unsigned length, char value) – присваивает значение value диапазону памяти, начиная с адреса dest;
size_t strspn(const char *s1, const char *s2) – находит начальный сегмент строки s1, которая состоит из символов строки s2; возвращает длину найденного сегмента.
size_t strсspn(const char *s1, const char *s2) – находит начальный сегмент строки s1, которая не состоит из символов строки s2; возвращает длину найденного сегмента.
char *strdup(const char s) – резервирует оперативную память и помещает на это место копию строки s; возвращает указатель на зарезервированную область памяти.
char *strlwr(char *s) – преобразует все символы строки к нижнему регистру (от a до z); возвращает указатель на строку s.
char *strupr(char *s) – преобразует все символы строки к верхнему регистру (от A до Z); возвращает указатель на строку s.
char *strpbrk(const char *s1, const char *s2) – сканирует строку s1 на присутствие любого символа строки s2; возвращает указатель на найденный символ или NULL, если совпадение не найдено.
char *strrev(char *s) – переписывает символы в строке в обратной последовательности; возвращает указатель на строку s.
char *strset(char *s, int ch) – все символы в строке делает равными ch; возвращает указатель на строку s.
size_t strxfrm(char *s1, char *s2, size_t n) – символы строки s2 заменяет на соответствующие символы строки s1, но не более чем n символов; возвращает количество замененных символов.
Кроме функций для работы со строками в библиотеке string есть ряд функций для преобразования одномерных массивов байтов памяти:
int memcmp (const void *s1, const void *s2, size_t n) – сравнивает два блока памяти, начинающиеся с адресов s1 и s2, но не более, чем n байтов; возвращает 0, если блоки равны, число меньшее 0, если первый блок меньше второго, число большее 0, если первый блок больше второго.
void *memcpy (void *dest, const void *src, size_t n) – копирует блок памяти src в блок памяти dest, но не более, чем n байтов; если блоки пересекаются, то результат не определен.
void *memccpy(void *dest, const void *src, int c, size_t n) – копирует блок памяти src в блок памяти dest; копирование останавливается по достижении количества в n байтов или после копирования символа c.
void *memmove(void *dest, const void *src, size_t n) – копирует блок памяти src в блок памяти dest, но не более, чем n байтов; копирование корректное даже если блоки памяти пересекаются.
void *memchr (const void *s, int c, size_t n) – в блоке памяти в n байтов ищет символ c, начиная с адреса s; если символ найден – возвращается указатель на найденный элемент, в противном случае NULL.
Пример выполнения лабораторного задания № 6
Индивидуальное задание: Слова исходного предложения расположить в обратном порядке. Расположение знаков препинания не менять.
Решение предусматривает выполнение нескольких действий:
1. Вывод информации о назначении программы (заголовок программы).
2. Ввод исходного предложения.
3. Выделение из предложения слов.
4. Выделение из предложения разделительных знаков.
5. Сборка нового предложения из слов и знаков поочередно.
6. Распечатка результата.
В соответствии с общим заданием на лабораторную работу каждое из перечисленных действий должна выполнять отдельная функция. Далее дадим краткие пояснения к каждой из функций. Но сначала опишем общую структуру программы. Программа может строиться так же, как и на языке Паскаль, – сначала объявление функций, а потом основная часть программы. Здесь используем другую форму: сначала будет предварительное объявление функций, потом основная функция, и только потом подробное описание предварительно записанных функций.
Напомним, что при объявлении функций записываются формальные параметры. Параметры могут быть переменными и значениями. Если параметр – это указатель, то это – параметр переменная, в противном случае – это значения. Параметр также будет значением, если он записан как константа.
Функция Title() – без параметров, так как только лишь выводит на экран набор строк.
Функция ввода предложения InpPredl (char predl[]) имеет один параметр – предложение, которое надо заполнить. В записи этого параметра есть сразу два момента, на которые надо обратить внимание: во-первых, в качестве параметра передается одномерный массив символов, то есть указатель (параметр переменная), во-вторых, в записи массива не указана размерность массива, а только открывающие и закрывающие квадратные скобки, так как достаточно передать только адрес начала строки.
Выделение из предложения слов и разделительных знаков реализуется одним алгоритмом, поэтому могут выполняться одной и той же функцией выделения Select(const char *predl, char m[][N],char *sel, int *n). Функция имеет четыре параметра:
1) predl – предложение, из которого идет выделение,
2) m – массив слов результата (под каждое слово предусмотрен размер в N символов),
3) sel – адрес символов, располагаемых между выделяемыми элементами,
4) указатель на переменную, в которой будет располагаться количество выделенных элементов, этот параметр используется как переменная, так как есть необходимость сохранить изменения.
Функция сборки нового предложения Constructor(char slova[][N], char razd[][N], int sl, int rz) – это действительно функция, так как возвращает указатель на первый символ полученного предложения. Назначение параметров понятно по названиям: массив слов, массив разделителей, количество слов, количество разделителей.
Вывод предложения функцией OutPredl(char *s1, char *s2) использует два параметра: исходное предложение и результат.
Функция strdup служит для создания в оперативной памяти дубликата передаваемой в качестве параметра строки. Функция strdup запрашивает у операционной системы оперативную память динамически, то есть в процессе работы программы, получает ее и возвращает указатель на занятую область. По завершении работы программы необходимо память опять отдать операционной системе. Это действие выполняется функцией free.
Главная функция последовательно использует (вызывает на исполнение) перечисленные функции одну за другой. Даже если функции используются в программе всего один раз, программа становится гораздо проще и понятнее. Перед вызовом некоторых функций делаются подготовительные операции, например, строчные и заглавные буквы объединяются в единый массив.
Теперь можете проанализировать весь текст программы:
#include <string. h>
#include <stdio. h>
#include <stdlib. h>
#include <locale. h>
#define N 20
// ПРЕДВАРИТЕЛЬНОЕ ОБЪЯВЛЕНИЕ ФУНКЦИЙ
// Заголовок программы
void Title();
// Ввод предложения
void InpPredl (char predl[]);
// Выделение из предложения
void Select(const char *predl, char m[][N],char *sel, int *n);
// Сборка предложения
char *Constructor(char slova[][N], char razd[][N], int sl, int rz);
// Вывод результата
void OutPredl(char *s1, char *s2);
inline void rus() // Русификация вывода в консольное окно
{
setlocale( LC_CTYPE, ".1251" );
setlocale( LC_MONETARY, ".1251" );
}
// ОСНОВНАЯ ФУНКЦИЯ
int main()
{
// ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ
char predl[80]; // Введенное предложение
char *res; // Результирующее предложение
char slova[N][N]; // Массив слов в предложении
char razd [N][N]; // Массив разделителей в предложении
int sl=-1, rz=-1; // Счетчики слов и разделителей
char buk[80]; // все буквы алфавита
char *bs="йцукенгшщзхъфывапролджэячсмитьбю";// Буквы строчные
char *bz ="ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ";/* заглавные */
char *r="`!;:,.?- _"; // Разделительные знаки
//ВЫЗОВ ФУНКЦИЙ НА ИСПОЛНЕНИЕ
rus();
// Заголовок программы
Title ();
// Ввод предложения
InpPredl (predl);
// Выделение из предложения слов
Select (predl, slova, r, &sl);
// Выделение из предложения разделителей
strcpy(buk,"");
strcat (buk, bs);
strcat (buk, bz);
Select (predl, razd, buk, &rz);
// Сборка предложения
res = Constructor(slova, razd, sl, rz);
// Вывод результата
OutPredl (predl, res);
free (res);
return 0;
}
// ОПИСАНИЕ ИСПОЛЬЗУЕМЫХ ФУНКЦИЙ
// Заголовок программы
void Title()
{
puts (" Лабораторное задание по теме № 6\n");
puts (" Во введенном предложении расположите слова");
puts (" в обратной последовательности.");
puts(" Порядок следования знаков препинания не менять.");
puts(" В предложении используются русские буквы и знаки. \n");
}
// Ввод предложения
void InpPredl(char *s)
{
puts("Введите предложение \n");
strcpy (s,"Верите ли вы, что задача решена?");
// gets (s);
}
// Вывод результата
void OutPredl (char *s1, char *s2)
{
puts ("Исходное предложение");
puts (s1);
puts (" Результат ");
puts (s2);
}
// Выделение из предложения
void Select(const char *predl, char m[][N], char *sel, int *n)
{
char *p, *s;
s=strdup (predl); // Сделать копию исходного предложения
// так как передается константа, strtok требует
// внесения изменений при своей работе
p = strtok (s, sel);
while (p)
{
(*n)++;
strcpy (m[*n],p);
p = strtok (NULL, sel);
}
free (s);
}
// Сборка предложения
char *Constructor(char slova[][N], char razd[][N], int sl, int rz)
{
int i, j;
char res[80];
char *s;
*res=0; // Начальное значение результата
i = sl;
j=-1;
while ((i>=0)||(j<=rz))
{
if ((i>=0)) strcat (res, slova[i]); // Добавление слова
i--;
j++;
if ((j<=rz)) strcat (res, razd[j]); // Добавление знаков
}
s = strdup (res);
return s;
}
Контрольные вопросы к теме № 6
1. Представление строк в языке Си.
2. Примеры инициализации строк в теле программы.
3. Библиотечные функции языка Си для обработки строк.
4. Библиотечные функции ввода-вывода строк.
Тема №7. Двумерные массивы. Файловый (бинарный) ввод-вывод.
Задание: Задание состоит из нескольких пунктов:
1. Заполнить двумерный массив (каждая строка массива заполняется в соответствии с заданием по теме №5).
2. Распечатать содержимое массива в виде прямоугольной матрицы.
3. Сохранить двумерный массив в бинарный файл.
4. Считать содержимое бинарного файла в другой двумерный массив.
5. Распечатать содержимое нового массива в виде прямоугольной матрицы.
6. Выполнить над каждой строкой нового двумерного массива действия, предусмотренные по теме №5.
7. Распечатать преобразованный двумерный массив.
Краткая теоретическая справка
Так же, как и во многих других языках программирования, двумерный массив – это одномерный массив одномерных массивов элементов любого типа. Как и для одномерного массива указывается количество элементов в массиве, например,
int x[5][7]; // Двумерный массив целых чисел (5 строк по 7 элементов)
Для использования одного элемента двумерного массива необходимо указать оба индекса (индекс строки и индекс столбца), например,
x[2][4] = 5; // Четвертому элементу второй строки присваивается 5
Особенным для языка Си является то, что можно получить указатель на одномерный массив, входящий в состав двумерного массива. Например, адрес начала второго массива будет выглядеть так:
x[2].
Тот же самый адрес второго массива можно представить в виде
(x+2).
Здесь x – адрес начала двумерного, а смещение на 2 относительно этого начала дает увеличение адреса на 14 байтов (размер одномерного массива равен 7, а после умножения на 2 и получаем число 14).
Используя оператор разыменования, по адресу массива можно получить обращение ко второй строке двумерного массива
*(x+2).
Ну а теперь, если вспомнить тему № 5, легко получить другой вариант записи присваивания четвертому элементу второй строки значения 5
*(*(x+2)+4) = 5// Четвертому элементу второй строки присваивается 5.
В этой форме записи присутствуют два оператора разыменования. Поэтому часто в отношении переменной x (двумерного массива) говорят, что это указатель на указатель, хотя это утверждение требует некоторых пояснений, которые будут даны ниже, при рассмотрении примеров обработки двумерных массивов.
Для использования всего того, что сделано в работе по теме №5, можно просто копировать функции из файла решения задачи по теме №5, а можно использовать другой прием. Алгоритм следующий:
1. Создадим новый проект, например, с именем 7.
2. Копируем в рабочий каталог проекта 7 файл с решением задачи по теме №5, но с именем 5.h.
3. Редактируем файл 5.h, оставляя только заголовки функций, например, получится следующее содержимое
// 1. Инициализация генератора случайных чисел
void Randomize();
//2. Генератор случайного числа в диапазоне от 0 до range
int Random(int range);
// 3. Проверка повторения случайного числа
// c - указатель начала массива, n - индекс нового элемента
int Test_Repetition(int *c, int n);
// 4. Добавить новый элемент
// с - указатель начала массива, n - номер нового элемента
// range1, range2 – левая и правая границы диапазона
void New_Item(int *c, int n, int range1, int range2);
// 5. Заполнение одномерного массива
// c - массив, n - количество элементов
void Filling (int *c, int n, int range1, int range2);
// 6. Распечатка одномерного массива
void Print(int *c, int n);
// 7. Поиск минимального значения
// с – указатель на начало одномерного массива,
// n - количество элементов массива,
// index – указатель на индекс минимума
int Min(int *c, int n, int *index);
// 8. Создание дубликата массива в динамической памяти
int * Copy (int *c, int n);
// 9. Обмен элементов местами в массиве
void Exchange (int *c, int n, int k);
// 10 .Сортировка методом прямого поиска
void SearchSort ( int *c, int n );
4. Копируем в рабочий каталог проекта 7 файл с решением задачи по теме №5, но с именем 5.c.
5. Редактируем файл 5.c и получаем
#include <stdio. h>
#include <stdlib. h>
#include <locale. h>
#include <time. h>
// 1. Инициализация генератора случайных чисел
void Randomize()
{
srand(time(0));
}
//2. Генератор случайного числа в диапазоне от 0 до range
int Random(int range)
{
return (rand() % range);
}
// 3. Проверка повторения случайного числа
// c - указатель начала массива, n - индекс нового элемента
int Test_Repetition(int *c, int n)
{ int x, j;
x = 0; // Считаем, что значение новое
// Цикл сравнения со всеми предыдущими
for (j=0; j<n; j++)
if (*(c+n)==*(c+j))
{
x = 1; // Элементы совпали
break;
}
return x;
}
// 4. Добавить новый элемент
// с - указатель начала массива, n - номер нового элемента
// range1, range2 – левая и правая границы диапазона
void New_Item(int *c, int n, int range1, int range2)
{ int x = 0; // Элементы массива разные
do
{
c[n] = Random(range2 - range1) + range1; // Новое значение
x =Test_Repetition(c, n); // Проверка на повторение
}
while (x==1); // Повторять, когда элементы совпали
}
// 5. Заполнение одномерного массива
// c - массив, n - количество элементов
void Filling (int *c, int n, int range1, int range2)
{ int i;
c[0]=Random (range2 - range1) + range1; // Элемент с индексом 0.
// Цикл заполнения массива
for (i=1;i<n; i++) // Цикл заполнения элементов значениями
New_Item(c, i, range1, range2);
}
// 6. Распечатка одномерного массива
void Print(int *c, int n)
{ int i;
for (i=0; i<n; i++)
printf("%4d",c[i]);
puts(""); // Переход на новую строку
}
// 7. Поиск минимального значения
// с – указатель на начало одномерного массива,
// n - количество элементов массива,
// index – указатель на индекс минимума
int Min(int *c, int n, int *index)
{ int i, min;
min = c[0]; // Начальное значение минимума
(*index) = 0; // Начальное значение индекса минимума
for ( i=1; i<n; i++ )
if (c[i]<min)
{
min = c[i];
(*index) = i;
}
return min;
}
// 8. Создание дубликата массива в динамической памяти
int * Copy (int *c, int n)
{ int *m, i;
// Резервирование памяти под дубликат массива
m = (int*)malloc(sizeof(int)*n);
// Копирование массива
for ( i = 0; i<n; i++ ) m[i] = c[i];
// Функция возвращает адрес нового массива
return m;
}
// 9. Обмен элементов местами в массиве
void Exchange (int *c, int n, int k)
{ int tmp; //Переменная для временного хранения данных
tmp = c[n];
c[n] = c[k];
c[k] = tmp;
}
// 10 .Сортировка методом прямого поиска
void SearchSort ( int *c, int n )
{ int i, min, indexMin; int *p;
for ( i=0; i<n-1; i++) // Определить массив с индексами от i до n
{ p = (c+i); // Задать адрес начала массива
min = Min (p, n-i, &indexMin); // Найти минимум в массиве
// Обменять местами минимальный элемент с первым
Exchange ( p, 0, indexMin ); }
}
6. Включаем файлы 5.h и 5.c в проект.
7. Копируем в рабочий каталог проекта 7 файл с решением задачи по теме №5, но с именем main.c. Редактируем файл к следующему виду (обратите внимание на стрку #include “5.h”)
#include <stdio. h>
#include <stdlib. h>
#include <locale. h>
#include "5.h"
#define N 15
inline void Rus() // Русификация вывода в консольное окно
{
setlocale( LC_CTYPE, ".1251" );
}
void Title()
{
puts("Лабораторная работа №5");
puts("Задание:");
puts("1. Заполнить массив неповторяющимися числами");
puts(" в диапазоне от -30 до +70.");
puts("2. Распечатать одномерный массив.");
puts("3. Найти минимальное значение в массиве и его индекс.");
puts("4. Сделать дубликат массива в динамической памяти.");
puts("5. Провести сортировку массива-дубликата методом");
puts(" прямого поиска");
puts("6. Распечатать результат сортировки.");
puts("7. Удалить дубликат из динамической памяти.\n");
}
// Главная функция
int main()
{
int m[N];
int min, IndexMin;
int *Duplicate;
Rus();
Randomize();
Title();
puts("\nЗаполнение массива");
Filling(m, N,-30,70);
puts("\nРаспечатка массива\n");
Print (m, N);
puts("\nМинимальное значение массива\n");
min = Min(m, N,&IndexMin);
printf("min = %d его индекс = %d \n", min, IndexMin);
puts("\nСоздание дубликата массива\n");
Duplicate = Copy(m, N);
puts("\nРаспечатка дубликата массива\n");
Print (Duplicate, N);
puts("\nСортировка дубликата методом прямого поиска");
SearchSort(Duplicate, N);
puts("\nРаспечатка дубликата массива после сортировки\n");
Print (Duplicate, N);
puts("\nУдаление дубликата");
free(Duplicate);
return 0;
}
8. Компилируем и запускаем эту программу на исполнение, чтобы убедиться, что все преобразования сделаны правильно.
Теперь в любой программе можем использовать функции обработки одномерных массивов, подключив в программу подготовленные файлы 5.h и 5.c, что и сделаем в следующих примерах.
Примеры функций для работы с двумерным массивом
Примеры функций рассмотрим в двух частях: в первой части будем использовать «традиционную» форму обращения к элементу двумерного массива (используя квадратные скобки), а во второй части – «комбинированную» (обращение через адреса и традиционно).
Часть первая («традиционная» форма использования обращения к элементу двумерного массива).
Двумерный массив декларируем в виде
int x[R][C];
где константами R и C обозначим количество строк и столбцов массива, соответственно.
// 1. Заполнение двумерного массива
// m – адрес начала двумерного массива,
// Nr - количество строк в двумерном массиве
void Filling_2 ( int m[][C], int Nr, int range1, int range2)
{ int i;
for ( i = 0; i<Nr; i++ ) //Для каждой строки массива…
{ //… заполняем одномерный массив
Filling(m[i], C, range1, range2);
}
}
Обратите внимание на то, что при передаче в качестве параметра массива m количество строк не указано. Но при этом обязательным будет указание размера одно строки ([C]). Другая особенность данного примера состоит в том, что при вызове на исполнение функции заполнения одномерного массива, в нее в качестве параметра передается одна строка двумерного массива (m[i]).
// 2. Распечатка двумерного массива
// m - адрес начала двумерного массива,
//Nr - окличество строк
void Print_2 ( int m[][C], int Nr)
{ int i;
for ( i = 0; i<Nr; i++ )
{
Print (m[i],С);
printf("\n");
}
}
Распечатка двумерного массива дополнительных комментариев не требует, так как все особенности описаны в комментариях к предыдущей функции.
// 3. Сохранение массива в файл
// m - адрес начала двумерного массива,
// Nr - окличество строк
// Name - имя файла
void Save( int m[][C], int Nr, char Name[])
{ FILE *out; int i;
// Открыть файл для записи.
out = fopen(Name,"wb");
// Проверить: файл открыт?
if (out!= NULL)
{ // Для каждой строки двуменого массива...
for (i=0; i<Nr; i++)
// ... записать строку массива в файл.
fwrite ( m[i], 1, C*sizeof(int), out );
// Закрыть файл.
fclose(out);
}
else puts("\nОшибка открытия файла для записи");
}
Для сохранения массива в файл потребуется файловая переменная out, являющаяся указателем на библиотечную структуру FILE.
Файл открывается для записи функцией fopen, в которую в качестве параметров передаются две строки: первая – имя файла, вторая – режим доступа к файлу. В строке режима доступа к файлу две буквы: w – означает запись (write), b – тип файла (бинарный, то есть двоичный – binary). Функция fopen возвращает указатель на структуру типа FILE, если открытие файла прошло успешно, или NULL, если произошла ошибка.
Запись в файл проводится функцией fwrite. Эта функция принимает четыре параметра: первый – нетипированный указатель на блок данных (в качестве фактического параметра может быть указатель любого типа, в данном примере – это адрес начала одномерного массива), второй параметр – количество блоков данных для записи, третий – размер одного блока данных (в примере вычисляется по размеру одномерного массива), четвертый – файловая переменная.
Обязательным действием по завершении записи в файл всех блоков данных служит закрытие файла функцией fclose, которая в качестве параметра принимает файловую переменную.
// 4. Загрузка массива из файла
// m - адрес начала двумерного массива,
// Nr - окличество строк
// Name - имя файла
void Load( int m[][C], int Nr, char Name[])
{ FILE *in; int i;
// Открыть файл для записи.
in = fopen(Name,"rb");
// Проверить: файл открыт?
if (in!=NULL)
{ // Для каждой строки двуменого массива...
for (i=0; i<Nr; i++)
// ... записать строку массива в файл.
fread(m[i], 1, C*sizeof(int), in);
// Закрыть файл.
fclose(in);
}
else puts("\nОшибка открытия файла для чтения");
}
При чтении данных из файла меняется режим доступа к файлу (rb) и используется функция чтения из файла fread, параметры которой полностью совпадают с параметрами функции fwrite.
Для проверки работоспособности приведенных функций была использована приведенная ниже программа. Не забудьте после главной функции добавить представленные ранее функции Filling_2, Print_2, Save и Load.
#include <stdio. h>
#include <stdlib. h>
#include <locale. h>
#include "5.h"
#define R 5
#define C 7
// Предварительное описание функций
void Filling_2(int m[][C], int Nr, int range1, int range2);
void Print_2(int m[][C], int Nr);
void Save( int m[][C], int Nr, char Name[]);
void Load( int m[][C], int Nr, char Name[]);
// Главная функция
int main()
{
int m[R][C], x[R][C];
Randomize();
Title();
puts("\nЗаполнение двумерного массива");
Filling_2(m, R,-30,70);
puts("\nРаспечатка двумерного массива\n");
Print_2 (m, R);
puts("\nСохранить содержимое массива в файл\n");
Save(m, R, "7.dat");
puts("\nЗагрузить данные из файла в новый массив\n");
Load(x, R, "7.dat");
puts("\nРаспечатка нового двумерного массива\n");
Print_2 (x, R);
return 0;
}
Обратите внимание на то, что функции Filling_2, Print_2, Save и Load могут быть использованы для двумерных массивов с произвольным количеством строк, но каждая строка должна быть строго определенной длины. В этом состоит основной недостаток «традиционной» формы обращения к двумерному массиву.
Часть вторая (обращение к двумерному массиву через указатели).
Двумерный массив декларируем как указатель на указатель
int **x;
Так массив задан через указатель, то необходимо его инициализировать, то есть зарезервировать место под массив в динамической области оперативной памяти.
// 1. Инициализация массива
// Nr - количество строк, Nc - количество столбцов
int ** Init(int Nr, int Nc)
{ int i; int **x;
// Задать массив указателей на строки
x = (int**)calloc ( Nr, sizeof(int*));
// Задать указатели на элементы в строке
for ( i = 0; i<Nr; i++)
x[i]=(int *)calloc(Nc, sizeof(int));
return x;
}
Инициализация проводится функцией calloc, в которой первым параметром указывается количество элементов, а вторым – размер одного элемента. Как видно из текста программы, сначала задается массив указателей на строки, а потом в каждый из указателей помещаются адреса элементов массива.
// 2. Заполнение массива
// Nr - количество строк, Nc - количество столбцов
void Fill(int **x, int Nr, int Nc)
{ int i, j; static int k=1;
for ( i=0; i<Nr; i++)
{
for ( j=0; j<Nc; j++)
x[i][j]=k++;
}
}
Заполнение массива проводим целыми числами, начиная с единицы. Обратите внимание на то, что для переменной k выбран статический класс памяти (инициализация значением 1 будет сделана только при первом вызове функции на исполнение), а также на то, что к элементам массива, на который указывает x, можно обращаться «традиционным» способом.
Ниже приведены функции для распечатки двумерного массива и работы с бинарным файлом. Распечатка проводится циклом в цикле, а сохранение и загрузка данных при работе с файлом – построчно.
// 3. Распечатка массива
// Nr - количество строк, Nc - количество столбцов
void Print (int **x, int Nr, int Nc)
{ int i, j;
printf("\n");
for ( i=0; i<Nr; i++)
{
for ( j=0; j<Nc; j++)
printf("%4d",x[i][j]);
printf("\n");
printf("\n");
}
printf("\n");
}
// 4. Сохранение массива в файл
// Nr - количество строк, Nc - количество столбцов
// Name - имя файла
void Save( int **x, int Nr, int Nc, char Name[])
{ FILE *out; int i;
out = fopen(Name,"wb");
if (out!= NULL)
{
for ( i=0; i<Nr; i++)
fwrite (x[i], 1, Nc*sizeof(int), out);
fclose(out);
}
else puts("\nОшибка открытия файла для записи");
}
// 5. Загрузка массива из файла
// Nr - количество строк, Nc - количество столбцов
// Name - имя файла
void Load( int **x, int Nr, int Nc, char Name[])
{ FILE *in; int i;
in = fopen (Name,"rb");
if (in!= NULL)
{
for ( i=0; i<Nr; i++)
fread ( x[i], 1, Nc*sizeof(int), in);
fclose(in);
}
else puts("\nОшибка открытия файла для чтения");
}
Проверка работоспособности функций проводилась приведенной ниже программой. Можете заметить, что функции применимы как для массива размером 4х5 (массивы x и x1), так и для массива размером 2х3 (массивы y и y1).
#include <stdio. h>
#include <stdlib. h>
#include <locale. h>
int ** Init(int Nr, int Nc);
void Fill(int **x, int Nr, int Nc);
void Print (int **x, int Nr, int Nc);
void Save( int **x, int Nr, int Nc, char Name[]);
void Load( int **x, int Nr, int Nc, char Name[]);
inline void Rus() // Русификация вывода в консольное окно
{ setlocale( LC_CTYPE, ".1251" ); }
int main()
{ int **x,**y,**x1,**y1;
Rus();
puts("\nИнициализация массива x\n");
x = Init (4, 5);
puts("\nЗаполнение массива x\n");
Fill(x,4,5);
puts("\nРаспечатка массива x\n");
Print(x,4,5);
puts("\nИнициализация массива y\n");
y = Init (2, 3);
puts("\nЗаполнение массива y\n");
Fill(y,2,3);
puts("\nРаспечатка массива y\n");
Print(y,2,3);
puts("Сохранение массива x в файл");
Save(x,4,5,"x. dat");
puts("\nИнициализация массива x1\n");
x1 = Init (4, 5);
puts("Загрузка массива x1 из файла");
Load(x1,4,5,"x. dat");
puts("\nРаспечатка массива x1\n");
Print(x1,4,5);
puts("Сохранение массива y в файл");
Save(y,2,3,"y. dat");
puts("\nИнициализация массива y1\n");
y1 = Init (2, 3);
puts("Загрузка массива y1 из файла");
Load(y1,2,3,"y. dat");
puts("\nРаспечатка массива y1\n");
Print(y1,2,3);
return 0;
}
Естественно, что при использовании приведенных функций нельзя путать размерность массивов. То есть, если массив x был инициализирован размером 4х5, то в нем нельзя использовать количество строк более 4 и столбцов более 5.
Чтобы было меньше ошибок можно ввести свой тип данных, содержащий в одной структуре не только указатель на указатель, например, такой
typedef struct {int **x; int Nr; int Nc;} TArray;
В этой записи слово typedef зарезервировано для обозначения того, что после его будет описание тип. Введенный тип данных представляет собой структуру (обозначено зарезервированным словом struct), а в фигурных скобках указано, что структура содержит три поля x – указатель на указатель на данные типа int, Nr и Nc – поля целого типа, для хранения информации о количестве строк и столбцов двумерного массива соответственно. TArray – имя типа данных.
Теперь, если задать переменную типа TArray, например,
TArray m;
то можно обратиться отдельно к каждому из полей (x, Nr, Nc) структуры типа TArray, записывая после имени переменной (в данном примере m) знак точка, а потом идентификатор поля (x, Nr, Nc), напрмер,
m. Nr = 4;
m. Nc = 5;
m. x = (int**) calloc ( m. Nr, sizeof(int*) );
Теперь функции для работы с двумерным массивом можно переписать к следующему виду:
// 1. Инициализация массива
// r - количество строк, c - количество столбцов
void Init ( TArray *m, int r, int c)
{ int i;
// Определить размеры массива
(*m).Nr = r; (*m).Nc = c;
// Задать массив указателей на строки
(*m).x = (int**)calloc ( r, sizeof(int*));
// Задать указатели на элементы в строке
for ( i = 0; i<r; i++)
(*m).x[i]=(int *)calloc ( c, sizeof(int));
}
Обратите внимание на то, что параметр m типа (TArray *) – это параметр –переменная (передается в функцию адресом). Поэтому при обращении к полям структуры используется разыменование, например, (*m).Nr = r. Аналогично используется параметр-переменная при заполнении массива и загрузке массива из файла. При распечатке и сохранении массива в файл можно использовать параметр-значение.
// 2. Заполнение массива
// Nr - количество строк, Nc - количество столбцов
void Fill ( TArray *m)
{ int i, j; static int k=1;
for ( i=0; i<(*m).Nr; i++)
{
for ( j=0; j<(*m).Nc; j++)
(*m).x[i][j] = k++;
}
}
// 3. Распечатка массива
// Nr - количество строк, Nc - количество столбцов
void Print (TArray m)
{ int i, j;
printf("\n");
for ( i=0; i<m. Nr; i++)
{
for ( j=0; j<m. Nc; j++)
printf("%4d",m. x[i][j]);
printf("\n");
printf("\n");
}
printf("\n");
}
// 4. Сохранение массива в файл
// Nr - количество строк, Nc - количество столбцов
// Name - имя файла
void Save( TArray m, char Name[])
{ FILE *out; int i;
out = fopen(Name,"wb");
if (out!= NULL)
{
for ( i=0; i<m. Nr; i++)
fwrite (m. x[i], 1, m. Nc*sizeof(int), out);
fclose(out);
}
else puts("\nОшибка открытия файла для записи");
}
// 5. Загрузка массива из файла
// Nr - количество строк, Nc - количество столбцов
// Name - имя файла
void Load( TArray *m, char Name[])
{ FILE *in; int i;
in = fopen (Name,"rb");
if (in!= NULL)
{
for ( i=0; i<(*m).Nr; i++)
fread ( (*m).x[i], 1, (*m).Nc*sizeof(int), in);
fclose(in);
}
else puts("\nОшибка открытия файла для чтения");
}
В главной части программы размер двумерного массива задается только при инициализации. Не забудьте также в функции Init, Fill и Load передавать в качестве фактического параметра адрес размещения переменной в оперативной памяти.
#include <stdio. h>
#include <stdlib. h>
#include <locale. h>
typedef struct {int **x; int Nr; int Nc;} TArray;
void Init(TArray *m, int r, int c);
void Fill(TArray *m);
void Print (TArray m);
void Save( TArray m, char Name[]);
void Load( TArray *m, char Name[]);
inline void Rus() // Русификация вывода в консольное окно
{ setlocale( LC_CTYPE, ".1251" ); }
int main()
{ TArray x, y, x1, y1;
Rus();
puts("\nИнициализация массива x\n");
Init (&x,4, 5);
puts("\nЗаполнение массива x\n");
Fill(&x);
puts("\nРаспечатка массива x\n");
Print(x);
puts("\nИнициализация массива y\n");
Init (&y,2, 3);
puts("\nЗаполнение массива y\n");
Fill(&y);
puts("\nРаспечатка массива y\n");
Print(y);
puts("Сохранение массива x в файл");
Save(x,"x. dat");
puts("\nИнициализация массива x1\n");
Init (&x1,4, 5);
puts("Загрузка массива x1 из файла");
Load(&x1,"x. dat");
puts("\nРаспечатка массива x1\n");
Print(x1);
puts("Сохранение массива y в файл");
Save(y,"y. dat");
puts("\nИнициализация массива y1\n");
Init (&y1,2, 3);
puts("Загрузка массива y1 из файла");
Load(&y1,"y. dat");
puts("\nРаспечатка массива y1\n");
Print(y1);
return 0;
}


