Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции 3-9 full.doc
Скачиваний:
19
Добавлен:
22.12.2018
Размер:
979.97 Кб
Скачать

6. Сложные типы данных

6.1 Массивы

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

Упорядоченность проявляется в том, что доступ к каждому элементу массива осуществляется посредством его индекса (номера) в массиве. Индекс представляет собой одно или несколько целых чисел, в зависимости от размерности массива. В принципе в языке С поддерживается произвольная размерность массивов, но действует ограничение: размер всего массива не должен превышать 231-1 байт (для 32-ух разрядных x86 процессоров в ОС Windows). Это ограничение связано с архитектурой вычислительной машины и может отличаться в других семействах процессоров и операционных систем.

Объявление массивов на языке С имеет следующий синтаксис:

тип имя[предел №1][предел №2]...[предел №N];

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

int a[10];

double b[5][20];

char c[5][5][10];

В первой строке объявлен целочисленный одномерный массив a из десяти элементов. Во второй строке объявлен вещественный двумерный массив b, в первой размерности предел 5, а во второй - 20, всего 100 элементов. Данный массив можно условно представить как пять одномерных вещественных массивов по 20 элементов в каждом из них. В третьей строке объявлен трехмерный символьный массив c с пределами 5, 5 и 10 в первой, второй и третьей размерностях соответственно (всего 250 элементов). По аналогии с предыдущим представлением, данный массив можно представить как пять двумерных символьных массивов с размерностями 5 и 10.

ПРИМЕЧАНИЕ: Объявление массива в языке С является обычным оператором объявления, поэтому в одном операторе допускается объявлять несколько массивов и обычных переменных, например:

double x[5][10], y[10][10];

int a[10], i,j;

Обращение к элементам массива в языке С осуществляется путем указания имени массива и, следом за ним, индексов элемента в парных квадратных скобках. Очень важно усвоить, что индексация в языке C, в отличие от некоторых других языков программирования, начинается с нуля, а не с единицы. Таким образом, в массиве a обращение к первому элементу будет иметь вид: a[0], а к последнему - a[9]. Соответственно в массиве b: b[0][0] и b[4][19]. Элемент массива в языке С при построении выражений может выступать и как RValue, и как LValue.

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

6.1.1 одномерный

Объявление одномерного массива (далее просто массив) имеет следующий синтаксис:

тип имя[размер];

В качестве размера массива может указываться любое положительное целочисленное значение. В стандарте С89 значение могло являться только константой. В стандарте С99 было введено понятие динамического массива. Под динамическим массивом здесь имеется в виду массив, при создании которого в качестве размера указывается значение некоторого выражения, в которое входят переменные, объявленные и инициализированные ранее (выражение должно иметь положительный целочисленный результат). Например:

int n;

printf(“Введите размер массива: ”);

scanf(“%d”,&n);

double x[n];

В данном примере в последней строчке создается вещественный массив x, в качестве размера которого указывается переменная n, значение которой вводится пользователем.

ПРИМЕЧАНИЕ: Память под динамический массив выделяется в стеке, поэтому данный механизм применим только для относительно малых массивов. При создании динамических массивов большого размера рекомендуется использовать функции для работы с динамической памятью (будут рассмотрены в следующей главе).

При объявлении массивов допускается производить инициализацию элементов массива. Синтаксис такого объявления:

тип имя[размер] = {значение №1, ... значение №N};

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

int a[5] = {1,2,3,4,5}, b[5] = {1,2};

double x[10] = {0.0};

В первой строчке создаются два целочисленных массива по пять элементов в каждом. Элементы массива a инициализированы значениями 1 2 3 4 5 соответственно, а элементы массива b - 1 2 0 0 0. Во второй строке объявлен вещественный массив x из десяти элементов, инициализированных нулями.

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

int a[5] = {1,2,,4,5};

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

int a[] = {1,2,3,4,5};

char b[] = {’a’,’b’,’c’};

В первой строке объявлен целочисленный массив a с пятью инициализирующими значениями: размер массива - пять элементов. Во второй строке объявлен символьный массив b с тремя инициализирующими значениями: размер массива - три элемента.

ПРИМЕЧАНИЕ: В языке С инициализировать динамические массивы нельзя.

Объявление константных массивов (значения их элементов изменить нельзя) начинается с ключевого слова const, за которым следует объявление массива с инициализацией. Примеры:

const int array[] = {1,2,3,4,5};

const double vector[5] = {1.0,2.0,3.0};

Обращение к элементу массива осуществляется путем указания имени массива, а после имени в квадратных скобках индекса элемента:

имя[индекс]

Как уже отмечалось, индексация в языке С начинается с нуля, поэтому для массива размером, например, десять элементов правильными будут индексы от нуля до девяти включительно. Каждый отдельный элемент массива может рассматриваться как простая переменная и, соответственно, выступать в выражениях в качестве RValue или LValue значений.

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

int a[10];

for(int i=0;i<10;i++) scanf(“%d”,&a[i]);

...

for(int i=0;i<10;i++) printf(“%d\t”,a[i]);

Присвоение массива массиву также осуществляется поэлементно. Например, необходимо присвоить вещественный массив x вещественному массиву y. Фрагмент программы:

double x[15], y[15];

...

for(int i=0;i<15;i++) y[i] = x[i];

...

ПРИМЕЧАНИЕ: В языке С во время выполнения программы не производится контроль за допустимыми значениями индексов элементов. Поэтому, если индекс элемента выходит за рамки массива, то в программе возможно появление ошибок. Ошибки могут быть: простыми (например «случайное» изменение переменных) и критическими (выход за пределы пространства памяти, отведенной для программы). Например, в результате выполнения следующего фрагмента программы (индексная переменная i выходит за пределы допустимых значений) будет выведено сообщение о некорректном обращении к памяти:

int a[10];

for(int i=0;i<=10;i++) a[i] = i;

Предотвращение таких ситуаций в языке С в основном возложено на плечи программистов.

6.1.1 двумерный

Объявление двумерного массива (далее матрица) имеет следующий синтаксис:

тип имя[размер №1][размер №2];

Размеры матрицы указываются в отдельных парных квадратных скобках после имени и могут быть любыми положительными целочисленными значениями. На практике принято значение первой размерности называть строками, а второй - столбцами. Как и в случае одномерного массива, в стандарте С89 регламентируется, что размеры матрицы должны быть целочисленными константами. Стандарт С99 допускает объявление динамических матриц, путем использования выражений при указании размеров матрицы, если в это выражение входят значения определенных ранее переменных (выражение должно иметь положительный целочисленный результат). Например:

int n,m;

printf(“Введите размеры матрицы: ”);

scanf(“%d %d”,&n,&m);

double a[n][m];

В данном примере в последней строчке создается вещественная матрица a, в качестве размеров которой указываются переменные n и m, значения которых вводятся пользователем.

ПРИМЕЧАНИЕ: Как и для одномерных динамических массивов, для матриц память в таком случае выделяется в стеке, поэтому данный механизм применим только для относительно малых матриц.

При объявлении матриц допускается производить инициализацию значений элементов матрицы:

тип имя[размер №1][размер №2] = {

{значение № 11, ... значение № 1N},

...

{значение № M1, ... значение № MN}

};

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

int a[2][4] = { //Объявлена матрица

{1,2,3,4}, // 1 2 3 4

{5,6} // 5 6 0 0

};

double b[3][5] = { //Объявлена матрица

{1.0, 2.0, 3.0, 4.0, 5.0}, // 1 2 3 4 5

{6.0, 7.0} // 6 7 0 0 0

}; // 0 0 0 0 0

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

int a[3][5] = {{1,2,3,4,5},,{6,7,8,9,0}};

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

double b[][4] = {{1,2,3,4},{5,6,7,8}};

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

ПРИМЕЧАНИЕ: В языке С инициализировать динамические матрицы, как и массивы, нельзя.

Объявление константных матриц (значения их элементов изменить нельзя) начинается с ключевого слова const, за которым следует объявление матрицы с инициализацией. Примеры:

const int matrix[][5] = {

{1,2,3,4,5},

{6,7,8,9}

};

Обращение к элементу матрицы осуществляется путем указания имени матрицы, а после имени в отдельных парных квадратных скобках индексы элемента (строка и столбец):

имя[строка][столбец]

Как уже отмечалось, индексация в языке С начинается с нуля, поэтому для матрицы размером, например, пять строки и десять столбцов правильными будут индексы строк от нуля до четырех, а столбцов - от нуля до девяти включительно. Каждый отдельный элемент матрицы может рассматриваться как простая переменная и, соответственно, выступать в выражениях в качестве RValue или LValue значений.

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

double a[5][10];

for(int i=0;i<5;i++) for(int j=0;j<10;j++)

scanf(“%lf”,&a[i][j]);

...

for(int i=0;i<5;i++){

for(int j=0;j<10;j++)

printf(“%8.2lf\t”,a[i][j]);

printf(“\n”);

}

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

Присвоение матрицы матрице также осуществляется поэлементно. Например, необходимо присвоить целочисленную матрицу x целочисленной матрице y. Фрагмент программы:

int x[5][10], y[5][10];

...

for(int i=0;i<5;i++) for(int j=0;j<10;j++)

y[i][j] = x[i][j];

...

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

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

int a[2][2][2]={

{{1,2},{3,4}},

{{5,6},{7,8}}

};

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

6.2 Строки

Строка - это последовательность ASCII или UNICODE символов. Строки в большинстве языков программирования высокого уровня рассматриваются как отдельный тип, входящий в систему базовых типов языка. Так как язык С по своему происхождению является языком системного программирования, то строковый тип данных в нем как таковой отсутствует, а в качестве строк используются обычные массивы символов.

ПРИМЕЧАНИЕ: Изучение строк в языке С осуществляется исключительно только на примере ASCII строк. Работа со строками из двухбайтных символов рекомендуются для самостоятельного изучения.

6.2.1 Формат строки

Исторически сложилось два представления формата строк:

  • формат ANSI;

  • строки с завершающим нулем (используется в языке С).

Формат ANSI устанавливает, что значением первой позиции в строке является ее длина, а затем следуют сами символы строки. Например, представление строки “Моя строка!” будет следующим:

11

‘М’

‘о’

‘я’

‘ ’

‘с’

‘т’

‘р’

‘о’

‘к’

‘а’

‘!’

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

‘М’

‘о’

‘я’

‘ ’

‘с’

‘т’

‘р’

‘о’

‘к’

‘а’

‘!’

0

Как уже отмечалось, строки в языке С реализуются посредством массивов символов. Поэтому объявление ASCII строки на языке С имеет следующий синтаксис:

char имя[длина];

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

char str[21];

Инициализация строки осуществляется при ее объявлении, используя следующий синтаксис:

char str[длина] = строковый литерал;

Строковый литерал - строка ASCII символов заключенных в двойные кавычки.

Примеры объявления строк с инициализацией:

char str1[20] = “Введите значение: ”,

str2[20] = “”;

Строка str1 объявлена с инициализирующей строкой “Введите значение: ”, а строка str2 - с пустой строкой. Длина инициализирующей строки должна быть строго меньше длины объявляемой строки (для учета символа завершающего нуля).

Объявление константных строковых переменных начинается с ключевого слова const, за которым следует объявление строки с инициализацией. Пример:

const char message[] = “Сообщение об ошибке!”;

Так как строки на языке С являются массивами символов, то к любому символу строки можно обратиться по его индексу. Для этого используется синтаксис обращения к элементу массива, поэтому первый символ в строке имеет индекс ноль. Например, в следующем фрагменте программы в строке str осуществляется замена всех символов a на символы A и наоборот.

for(int i=0;str[i]!=0;i++){

if(str[i] == ‘a’) str[i] = ‘A’;

else if(str[i] == ‘A’) str[i] = ‘a’;

}

В программе используется цикл for с переменной-счетчиком i. Цикл выполняется со значения переменной-счетчика ноль: первого символа строки. Выход из цикла осуществляется при достижении конца строки (i-ый символ строки равен нулю). В теле цикла в первом операторе if выполняется сравнение значения текущего символа строки с символом a, и, если равен, то осуществляется его замена на символ A. В противном случае во втором операторе if осуществляется сравнение с символом A, и, при положительном исходе, осуществляется замена на символ a.

Объявление массивов строк в языке С также возможно. Для этого используются двумерные массивы символов, что имеет следующий синтаксис:

char имя[количество][длина];

Первым размером матрицы указывается количество строк в массиве, а вторым - максимальная (с учетом завершающего нуля) длина каждой строки. Например, объявление массива из пяти строк максимальной длиной 30 значащих символов будет иметь вид:

char strs[5][31];

При объявлении массивов строк можно производить инициализацию:

char имя[количество][длина] =

{строковый литерал №1, ... строковый литерал №N};

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

char days[12][10] =

{”Январь”, ”Февраль”, ”Март”, ”Апрель”, ”Май”, ”Июнь”,

”Июль”, ”Август”, ”Сентябрь”, ”Октябрь”, ”Ноябрь”,

”Декабрь”};

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

char days[][12] =

{”Понедельник”, ”Вторник”, ”Среда”, ”Четверг”,

”Пятница”, ”Суббота”, ”Воскресенье”};

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

6.2.2 Функции для работы со строками

Все библиотечные функции, предназначенные для работы со строками, можно разделить на три группы:

  • ввод и вывод строк;

  • преобразование строк;

  • обработка строк.

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

char str[31] = “”;

printf(“Введите строку: ”);

scanf(“%s”,str);

printf(“Вы ввели: %s”,str);

В первой строке осуществляется объявление строковой переменной str максимальной длиной 30 символов и инициализацией пустой строкой. Далее выводится приглашение к вводу строки и, затем, осуществляется непосредственно ввод. Следует обратить внимание на то, что в функции scanf имя переменой str указано без знака амперсанда. Такое допустимо только при вводе строк, так как имя строковой переменной является именем массива, а имя массива в языке С является указателем на сам массив (указатели будут рассматриваться в следующее главе). Следовательно, имя строки, по сути, является указателем на эту строку и знак амперсанда можно не указывать. В последней строке осуществляется вывод введенного значения.

Недостатком функции scanf при вводе строковых данных является то, что символами разделителями данной функции являются: перевод строки, табуляция и пробел. Поэтому, используя данную функцию невозможно ввести строку, содержащую несколько слов, разделенных пробелами или табуляциями. Например, если в предыдущей программе пользователь введет строку “Сообщение из нескольких слов”, то на экране будет выведено только “Сообщение”. Поэтому для ввода и вывода строк в библиотеке stdio.h содержатся специализированные функции gets и puts.

ПРИМЕЧАНИЕ: При работе со строками в языке С активно используются указатели. Если возникает необходимость, рекомендуется сначала обратиться к следующей главе.

Функция gets предназначена для ввода строк и имеет следующий заголовок:

char * gets(char *buffer);

Единственным параметром функции является строковая переменная, в которую будут вводиться данные. Функция возвращает значение параметра buffer, если строка была введена, или значение NULL (ноль), если был достигнут конец потока ввода. Данная функция осуществляет ввод строки, пока не встретится символ перевода строки.

ПРИМЕЧАНИЕ: Ввод и вывод в языке С осуществляется посредством потоков. Понятие потоков и все, что с ними связано, будет рассматриваться в главе 7.

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

Функция puts предназначена для вывода строк и имеет следующий заголовок:

int puts(const char *string);

Единственным параметром функции является строковая переменная, значение которой необходимо вывести. Функция возвращает целочисленное значение: положительное число или ноль в случае успеха, и значение EOF (-1) в случае ошибки. Данная функция автоматически выводит в поток символ перевода строки.

Простейшая программа: ввод и вывод строки с использованием функций gets и puts будет иметь вид:

char str[100] = “”;

printf(“Введите строку: ”); gets(str);

printf(“Вы ввели: ”); puts(str);

Помимо функций ввода и вывода в потоки в библиотеке stdio.h присутствуют функции форматированного ввода и вывода в строки. Функция форматированного ввода из строки имеет следующий заголовок:

int sscanf(const char * restrict buffer,

const char * restrict string,[address] ...);

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

Функции форматированного вывода в строку имеют следующие заголовки:

int sprintf(char * restrict buffer,

const char * restrict format, [argument] ...);

int snprintf(char * restrict buffer, size_t maxsize,

const char * restrict format, [argument] ...);

В параметре buffer передается строка, в которую будет осуществляться вывод. В функции snprintf вторым параметром maxlen указывается максимальное число символов, которое может быть записано в результирующую строку (остальные символы просто отбрасываются). Параметры format и argument аналогичны одноименным параметрам функции printf. Возвращаемое значение и поведение функций также аналогично функции printf. Функция snprintf была введена только в стандарте C99.

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

int summa = 0;

while(1){

char str[15];

printf(“Введите число или пустую строку: ”);

gets(str);

if(str[0]==0) break;

int n;

sscanf(str,”%d”,&n);

summa += n;

}

printf(“Сумма чисел: %d\n”,summa);

В первой строке осуществляется объявление и инициализация целочисленной переменной summa, в которой будет накапливаться сумма вводимых чисел. Далее описывается бесконечный цикл while. В теле цикла объявляется строка str, выводится приглашение к вводу данных и осуществляется ввод строки. Затем в операторе if осуществляется проверка, является ли введенное значение пустой строкой. Если да, то осуществляется выход из цикла посредством оператора break. Проверка осуществляется исходя из того, что первый символ пустой строки есть завершающий ноль. Если введена не пустая строка, то объявляется переменная n, в которую посредством функции sscanf записывается введенное значение (фактически здесь осуществляется преобразование строки в число) и вычисляется сумма. После цикла осуществляется вывод суммы.

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

double atof(const char *string);

int atoi(const char *string);

long int atol(const char *string);

long long int atoll(const char *string);

В качестве параметра во всех функциях выступает строка string, которая содержит текстовое представление числа в десятичной системе исчисления. Функции возвращают результат в виде числа определенного типа (вещественное - atof, целое - atoi, длинное целое - atol, длинное длинное целое - atoll). В случае переполнения результат выполнения функций не определен. В случае некорректного представления числа функции возвращают нулевые значения соответствующего формата. Корректное представление вещественного числа в текстовой строке должно удовлетворять формату:

[пробел][{+|-}][цифры][.цифры][{d|D|e|E}[{+|-}]цифры]

После символов D, d, E, e указывается порядок числа. Корректное представление целого числа в текстовой строке должно удовлетворять формату:

[пробел] [{+|-}] цифры

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

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

float strtof(const char * restrict string,

char ** restrict endptr);

double strtod(const char * restrict string,

char ** restrict endptr);

long double strtold(const char * restrict string,

char ** restrict endptr);

Приведенные функции осуществляют преобразование строк, содержащих вещественные числа, в вещественные значения типа float, double и long double соответственно. В первом параметре функций передается строка string, содержащая текстовое представление вещественного числа. Если второй параметр endptr не NULL, то через него осуществляется возврат указателя на не преобразованное окончание строки. Функции возвращают значение ноль, если строка не может быть интерпретирована как вещественное число. При переполнении функции возвращают значения +/-HUGE_VALF / HUGE_VAL / HUGE_VALL соответственно.

ПРИМЕЧАНИЕ: Для передачи второго параметра и возврата через него некоторого значения используется принцип передачи параметров по ссылке. Описание данного принципа будет дано в главе 6.

Аналогичные функции присутствуют и для преобразования строк в целочисленные значения:

long int strtol(const char * restrict string,

char ** restrict endptr, int base);

unsigned long strtoul(const char * restrict string,

char ** restrict endptr, int base);

long long int strtoll(const char * restrict string,

char ** restrict endptr, int base);

unsigned long long strtoull(

const char * restrict string,

char ** restrict endptr, int base);

Приведенные функции осуществляют преобразование строк, содержащих целые числа, в целочисленные значения типа long int, unsigned long, long long int и unsigned long long int соответственно. В первом параметре функций передается строка string, содержащая текстовое представление целого числа. Если второй параметр endptr не NULL, то через него осуществляется возврат указателя на не преобразованное окончание строки. В третьем параметре base передается основание системы исчисления, в котором записано число. Функции возвращают значение ноль, если строка не может быть интерпретирована как целое число. При переполнении функции возвращают значения +/-LONG_MAX / LLONG_MAX или ULONG_MAX / ULLONG_MAX соответственно.

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

В библиотеке string.h содержаться функции для различных действий над строками. В данном руководстве будут рассмотрены только основные из них, входящие в стандарт языка.

Функция вычисления длины строки:

size_t strlen(const char *string);

В качестве параметра передается строка string. Функция возвращает численное значение длины строки (тип unsigned int). Пример:

char str[] = “1234”;

int n = strlen(str); //n == 4

Функции копирования строк:

char * strcpy(char * restrict dst,

const char * restrict src);

char * strncpy(char * restrict dst,

const char * restrict src, size_t num);

Первым параметром dst является строковая переменная, в которую необходимо скопировать строковое значение (назначение), а вторым параметром - строковая переменная значение которой будет копироваться (источник). Третий параметр num функции strncpy указывает, какое число символов от начала строки необходимо скопировать. Если длина строки источника больше значения параметра num, то в строку назначение копируется только первых num символов строки источника, в противном случае - вся строка. Функции возвращают фактическое значение параметра dst. Пример:

char str[] = “abcdefg”, dst1[10] =“”, dst2[10] = “”;

strcpy(dst1,str); //dst1 == “abcdefg”

strncpy(dst2,str,5); //dst2 == “abcde”

Функции сравнения строк:

int strcmp(const char *string1, const char *string2);

int strncmp(const char *string1, const char *string2,

size_t num);

Первым string1 и вторым string2 параметрами функции являются строки, значения которых необходимо сравнить. Третьим параметром num функции strncmp указывается, какое число символов от начала строк необходимо сравнивать. Функции осуществляют сравнение строк по алфавитному порядку и возвращают:

  • положительное значение - если string1 больше string2;

  • отрицательное значение - если string1 меньше string2;

  • нулевое значение - если string1 совпадает с string2.

Пример:

char str1[] = “Компьютер”, str2[] = “Компьютерный”;

int n1 = strcmp(str1,str2); //n1 < 0

int n2 = strncmp(str1,str2,9); //n2 == 0

Функции объединения (конкатенации) строк:

char * strcat(char * restrict dst,

const char * restrict src);

char * strncat(char * restrict dst,

const char * restrict src, size_t num);

Первыми двумя параметрами функций являются строки: dst - назначение, src - источник. В третьем параметре num функции strncat указывается, какое число первых символов строки src следует добавить к строке dst. Функции объединения строк не создают новую строковую переменную, поэтому при написании программ необходимо проверять возможное переполнение строки dst. Если длина строки источника больше значения параметра num, то к строке назначение прибавляется только первых num символов строки источника, в противном случае - вся строка. Функции возвращают фактическое значение параметра dst. Пример:

char str1[20] = “серверный ”, str2[20] = “Главный ”,

str3[] = “зал”;

strncat(str2,str1,6); //str2 == “Главный сервер”

strcat(str1,str3); //str1 == “серверный зал”;

Функции поиска символа в строке:

char * strchr(const char *string, int c);

char * strrchr(const char *string, int c);

В первом параметре string указывается строка, в которой осуществляется поиск. Во втором параметре c указывается символ, который необходимо найти в строке. Функции возвращают указатель на найденный символ или значение NULL, если символ не был найден. Функция strchr начинает поиск символа с начала строки, а функция strrchr - с конца строки. Пример:

char str[] = “Строка для поиска”;

char *str1 = strchr(str,’к’); //str1 == “ка для поиска”

char *str2 = strrchr(str,’к’);//str2 == “ка”

Функция поиска строки в строке:

char * strstr(const char *str, const char *substr);

В первом параметре str указывается строка, в которой осуществляется поиск. Во втором параметре substr указывается строка, которую необходимо найти. Функция возвращает указатель на первое вхождение строки substr в строку str. Пример:

char str[] = “Строка для поиска”;

char *str1 = strstr(str,“для”); //str1 == “для поиска”

Функция поиска первого символа в строке из заданного набора символов:

size_t strcspn(const char *str, const char *charset);

В первом параметре str указывается строка, в которой осуществляется поиск символа. Во втором параметре charset указывается строка, содержащая набор символов, поиск которых осуществляется. Функция возвращает индекс первого найденного символа из набора charset в строке str. Символ завершения строки также входит в искомые значения. Пример:

char str[] = “Компьютер”;

char ch = str[strcspn(str,”трм”)]; //ch == ‘м’

Функции поиска первого символа в строке не принадлежащему заданному набору символов:

size_t strspn(const char *str, const char *charset);

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

char str[] = “Компьютер”;

char ch = str[strspn(str,”Кмьоп”)]; //ch == ‘ю’

Функции поиска первого символа в строке из заданного набора символов:

char * strpbrk(const char *str, const char *charset);

В первом параметре str указывается строка, в которой осуществляется поиск символа. Во втором параметре charset указывается строка, содержащая набор символов, поиск которых осуществляется. Функция возвращает указатель на первый найденный символ из набора charset в строке str. Символ завершения строки не входит в искомые значения. Функция возвращает значение NULL, если символ не найден. Пример:

char str[] = “Компьютер”;

char *ptr = strpbrk(str,”трм”);

char ch = *ptr; //ch == ‘м’

Функция поиска следующего литерала в строке:

char * strtok(char * restrict string,

const char * restrict charset);

В первом параметре string указывается строка, в которой осуществляется поиск следующего литерала. Во втором параметре указывается набор возможных символов разделителей литералов. Для кратного поиска литералов в одной строке, строка указывается только при первом вызове функции, в последующих вызовах в первом параметре передается значение NULL. Функция возвращает указатель на следующий в строке литерал, или значение NULL, если литерал не найден. Например, необходимо подсчитать количество слов в строке. Слова разделяются пробелами или табуляциями. Строка содержится в переменной str. Фрагмент программы:

char *ptr = strtok(str,” \t”);

unsigned num = 0;

while(ptr != NULL){

num++;

ptr = strtok(NULL,” \t”);

}

В первой строке объявляется указатель ptr и устанавливается на первый литерал в строке. Далее объявляется и инициализируется переменная num, для подсчета слов в строке. Затем в цикле пока значение указателя не равно NULL выполняется:

  • переменная счетчик слов num увеличивается на единицу;

  • указатель ptr устанавливается на следующий литерал в строке.

После завершения цикла в переменной num содержится число слов в строке.

6.3 Перечисления

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

Объявление перечисления имеет следующий синтаксис:

enum [имя типа] {элемент №1, элемент №2,... элемент №N}

[список переменных];

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

идентификатор [ = значение]

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

Пример объявления перечисления:

enum WEEK {MONDAY = 1, TUESDAY, WEDNESDAY, THURSDAY,

FRIDAY, SATURDAY, SUNDAY} day = MONDAY;

В данном перечислении имена элементов представляют названия дней недели. Понедельнику соответствует целочисленное значение один, вторнику - два и т.д. После объявления перечисления объявлена переменная day данного типа. При последующих объявлениях переменных перечислимого типа используется следующий синтаксис:

enum тип имя [= значение];

Указывать ключевое слово enum обязательно. Например:

enum WEEK newday = FRIDAY;

ПРИМЕЧАНИЕ: Если при объявлении перечисления имя типа не указывать, то создать переменные данного перечисления можно только непосредственно в объявлении.

Чтобы избежать необходимости каждый раз при объявлении переменных перечислимого типа указывать ключевое слово enum, нужно объявить перечисление используя оператор typedef. Синтаксис такого объявления имеет вид:

typedef enum {элементы} имя типа;

Например:

typedef enum {MONDAY = 1, TUESDAY, WEDNESDAY,

THURSDAY, FRIDAY, SATURDAY, SUNDAY} WEEK;

После этого в программе можно объявлять переменные в обычном виде:

WEEK day = MONDAY, newday = FRIDAY;

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

printf(“Введите номер дня недели:”);

scanf(“%d”,&day);

switch(day){

case MONDAY: {printf(“Понедельник\n”); break;}

case TUESDAY: {printf(“Вторник\n”); break;}

case WEDNESDAY: {printf(“Среда\n”); break;}

case THURSDAY: {printf(“Четверг\n”); break;}

case FRIDAY: {printf(“Пятница\n”); break;}

case SATURDAY: {printf(“Суббота\n”); break;}

case SUNDAY: {printf(“Воскресенье\n”); break;}

}

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

6.4 Структуры

Структура - это сложный тип данных представляющий собой упорядоченное в памяти множество элементов различного типа. Каждый элемент в структуре имеет свое имя и называется полем. Элементы в структуре располагаются последовательно. Размер структуры определяется суммой размеров всех элементов.

Объявление структуры имеет вид:

struct [имя типа] {

поле №1;

поле №2;

...

поле №N;

} [список переменных];

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

ПРИМЕЧАНИЕ: Объявление полей структуры возможно только без инициализации. Если несколько полей следующих друг за другом в описании структуры имеют один и тот же тип, то для их описания можно использовать синтаксис объявления нескольких переменных одного и того же типа. Типом поля может быть любой тип (как системный, так и пользовательский), описанный ранее.

Примеры структур:

Структура, содержащая информацию о точке в двумерном пространстве (координаты):

struct Point{double x,y;};

Структура, содержащая информацию об окружности (координаты центра и радиус):

struct Circle{

double x, y, radius;

};

Структура, содержащая информацию о студенте (фамилия, имя, отчество, номер зачетной книжки, средний балл):

struct Student{

char surname[15], name[15], patronymic[15];

unsigned number;

double rate;

};

Структура, содержащая информацию о группе студентов (название группы, количество студентов, список студентов (максимально 30)):

struct Group{

char name[10];

unsigned number;

struct Student list[30];

};

Объявление переменной определенной структуры осуществляется после описания данной структуры в следующем виде:

struct тип имя №1[= значение №1][,...];

Объявление начинается с ключевого слова struct, за которым указывается имя типа структуры, описанной ранее. Затем указывается имя переменной и, необязательно, инициализирующее значение. Далее через запятую указываются имена и инициализирующие значения переменных данной структуры. После последнего объявления ставится точка с запятой. Инициализирующее значение указывается в следующем виде: в фигурных скобках приводится список инициализирующих значений полей структуры в том порядке, в котором они указаны при объявлении самой структуры.

Например:

struct Point pnt[3] = {{0,0},{1,0},{0,1}};

struct Circle c1 = {10.0,10.0,5.0},

c2 = {0.0,0.0,25.0};

struct Student st

= {“Иванов”,”Иван”,”Иванович”,959623,7.5};

struct Group gr ={

“97-BC”, 3, {

{“Иванов”, ”Иван”, ”Иванович”, 979601,8.0},

{“Петров”, ”Петр”, ”Петрович”, 979602,6.5},

{“Сидоров”,”Сидор”,”Сидорович”,979603,9.0}

}

};

Как уже показано в примерах структур Student и Group поля структур могут быть сложными типами данных, в том числе и вложенными структурами. На практике обычно используются не более двух вложений структур.

От обязательно использования ключевого слова struct можно отказаться, если описывать структуру, используя оператор объявления типа typedef в следующем виде:

typedef struct [первичное имя типа] {...} имя типа;

ПРИМЕЧАНИЕ: Первичное имя типа, указываемое перед перечнем полей структуры является необязательным и указывается редко. Как правило, первичное имя типа имеет тот же идентификатор, что и основное имя типа, но начинается со знака подчеркивания.

Например, структура, содержащая информацию о книге (ФИО автора, название книги, год издания):

typedef struct {

char author[20], title[50];

unsigned year;

} BOOK;

Объявление переменной данного типа:

BOOK book = {“А. Дюма”,”Три мушкетера”, 1986};

Обращение к полям структуры осуществляется в следующем виде:

имя_переменной.имя_поля

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

Например:

Вычисление длины окружности, заданной переменной cir типа Circle:

double length = 2.0*3.1415*cir.radius;

Ввод информации о студенте в переменную st типа Student:

scanf(“%s %s %s %u %lf”, &st.surname, &st.name,

&st.patronymic, &st.number, &st.rate);

Вывод на экран списка группы, заданной в переменой gr типа Group:

printf(“Группа: %s\n”,gr.name);

for(unsigned i=0;i<gr.number;i++)

printf(“%2u: %15s %15s %15s %6u %.1lf\n”,

i+1, gr.list[i].surname, gr.list[i].name,

gr.list[i].patronymic, gr.list[i].number,

gr.list[i].rate);

Для определения размера переменной структурного типа в байтах используется оператор определения типа sizeof. Например:

unsigned size = sizeof(struct Student); //size == 57

6.5 Объединения

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

Объявление объединения имеет вид:

union [имя типа] {

поле №1;

поле №2;

...

поле №N;

} [список переменных];

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

Объявление переменных объединения имеет тот же синтаксис, что и для объявления переменных структур. Отличие состоит в том, что инициализировать значение объединения можно только значением первого поля. Например:

union VALUE{

unsigned num;

double val;

char str[20];

};

union VALUE val = {0};

Так же как и со структурами, для объединения можно создать свой тип данных, используя оператор typedef:

typedef union [первичное имя типа] {...} имя типа;

Обращение к полям объединения имеет тот же синтаксис, что и обращение к полям структуры. При обращении к полю объединения данная конструкция может рассматриваться и как LValue и как Rvalue значения.

Определение размера памяти, занимаемого значением типа объединение, осуществляется оператором sizeof. Например:

unsigned size = sizeof(union VALUE); //size == 20

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

typedef enum {

INT_8 = 0, //символ

UINT_8, //незнаковый символ

INT_16, //короткое целое

UINT_16, //короткое незнаковое целое

INT_32, //целое

UINT_32, //незнаковое целое

INT_64, //длинное целое

UINT_64, //длинное незнаковое целое

FLOAT_32, //одинарное вещественное

FLOAT_64, //двойное вещественное

FLOAT_80 //длинное вещественное

} TYPE;

typedef union{

char int8; //символ

unsigned char uint8; //незнаковый символ

short int16; //короткое целое

unsigned short uint16; //короткое незнаковое целое

int int32; //целое

unsigned uint32; //незнаковое целое

long long int64; //длинное целое

unsigned long long uint64;//длинное незнаковое целое

float float32; //одинарное вещественное

double float64; //двойное вещественное

long double float80; //длинное вещественное

} VALUE;

typedef struct {

TYPE type;

VALUE value;

} VARIANT;

В приведенном примере сначала в перечислении TYPE задаются возможные типы хранимых значений. Далее описывается объединение VALUE, в котором содержатся поля всех перечисленных типов. И в завершении описывается структура VARIANT, которая содержит два элемента: тип хранимого значения type (перечисление TYPE) и само значение value (объединение VALUE). Таким образом, переменная типа VARIANT позволяет хранить значение любого типа из системы базовых типов языка С. Размер данной переменной составляет 14 байт: 4 байта поле type, 10 байт поле value (10 байт - размер самого большого поля типа long double). Если при реализации типа VARIANT не использовать объединение, а все поля описывать внутри структуры, то размер переменной данного типа будет составлять 52 байта, что значительно больше.

ПРИМЕЧАНИЕ: Тип VARIANT широко используется в программировании на основе технологии COM (Component Object Model) для передачи данных различного типа между компонентами.

Работа с переменной типа VARIANT осуществляется в два действия:

  • обращение к полю type (тип TYPE) для установления типа хранимого значения;

  • обращение к элементу объединения VALUE (поле value) установленного типа.

Например, запись в переменную val типа VARIANT целого числа 150 будет иметь вид:

val.type = INT_32;

val.value.int32 = 150;

Более сложный пример, вывести на экран значение переменной val типа VARIANT:

switch(val.type){

case INT_8: printf("%d",val.value.int8); break;

case UINT_8: printf("%u",val.value.uint8); break;

case INT_16: printf("%hd",val.value.int16); break;

case UINT_16: printf("%hu",val.value.uint16); break;

case INT_32: printf("%d",val.value.int32); break;

case UINT_32: printf("%u",val.value.uint32); break;

case INT_64: printf("%lld",val.value.int64); break;

case UINT_64: printf("%llu",val.value.uint64); break;

case FLOAT_32: printf("%f",val.value.float32); break;

case FLOAT_64: printf("%lf",val.value.float64);break;

case FLOAT_80: printf("%Lf",val.value.float80);break;

}

В приведенном примере используется оператор switch для определения типа хранимого значения. В каждом операторе case осуществляется вывод значения в соответствии с его типом.

6.6 Битовые поля

Битовое поле - последовательность бит длиной до 32 бит. В языке С битовое поле может быть только элементом структуры или объединения. С точки зрения значения битовое поле рассматривается как целочисленная величина соответствующего размера.

Описание битового поля имеет вид:

[unsigned | int] имя:размер;

Объявление битового поля начинается с типа: целое (int) или незнаковое целое (unsigned). Другие типы запрещены. Далее указывается имя поля (идентификатор уникальный в пределах данной структуры) и через знак двоеточия указывается размер (или ширина) битового поля (число от 1 до 32).

В зависимости от того, какой тип данных был указан (int или unsigned) битовое поле будет знаковым или незнаковым. Например, объявим следующую структуру с двумя битовыми полями:

typedef struct{ typedef struct{

int a:4; int a:24;

unsigned b:4; unsigned b:24;

} BITFIELD1; } BITFIELD2;

Диапазон значений принимаемых полем a структуры BITFIELD1 - [-8,7], а поля b - [0,15]. Диапазон значений принимаемых полем a структуры BITFIELD2 - [-8388608, 8388607], а поля b - [0, 16777216].

Следует учитывать, что сумма длин всех битовых полей следующих в описании структуры друг за другом округляется до числа кратного четырем. Например, размер приведенной структуры BITFIELD1 равняется не одному, а четырем байтам. Размер структуры BITFIELD2 - 8 байтам.

На практике битовые поля в основном применяются для эффективного хранения информации. Рассмотрим, например, структуру содержащую информацию о человеке:

  • фамилия, имя, отчество (строки по 15 символов);

  • дата рождения (в формате дд.мм.гггг);

  • пол (М | Ж);

  • номер мобильного телефона (семизначное число);

  • номер домашнего телефона (шестизначное число);

  • почтовый индекс города (шестизначное число);

  • названия города и улицы (строки по 15 символов);

  • номера дома и квартиры (целые числа).

Приведем две реализации данной структуры: слева - без использования битовых полей, справа - с использованием.

typedef struct{ typedef struct{

char surname[15], char surname[15],

name[15], name[15],

patronymic[15]; patronymic[15];

char sex; unsigned sex:1;

unsigned char day, month; unsigned day:5, month:4;

unsigned short year; unsigned year:14;

unsigned char mobile[7], unsigned mobile:24,

home_tel[6]; home_tel:20;

unsigned short house, unsigned house:10,

flat; flat:10;

unsigned char index[6]; unsigned index:20;

char town[15], street[15]; char town[15], street[15];

} MAN1; } MAN2;

Поля surname, name и patronymic - фамилия, имя и отчество соответственно. Поля town и street - названия города и улицы соответственно. Поле sex - пол человека, для его кодирования достаточно одного бита (например, 0 - мужской, 1 - женский). Поле day - число месяца (от 1 до 31), для его кодирования достаточно 5 бит. Поле month - месяц (от 1 до 12), для его кодирования достаточно 4 бита. Поле year - год (от 1 до 9999), для его кодирования достаточно 14 бит. Поле mobile - номер мобильного телефона (число от 1 до 9999999), для его кодирования достаточно 24 бит. Поле home_tel - номер домашнего телефона (число от 1 до 999999), для его кодирования достаточно 20 бит. Поле house - номер дома (число от 1 до разумного предела, например 1000), для его кодирования достаточно 10 бит. Поле flat - номер квартиры (число от 1 до разумного предела, например 1000), для его кодирования достаточно 10 бит. Таким образом, размер структуры MAN1 (без использования полей бит) составляет 104 байта, а структуры MAN2 (с использованием полей бит) - 96 байт.

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

6.7 Примеры

Задача 4.1: Дан вещественный массив размера N. Размер массива и значения его элементов вводятся пользователем. Найти сумму всех локальных минимумов массива. Локальный минимум - элемент массива меньший по значению двух соседних элементов данного массива (исключая крайние элементы).

Программа для задачи

#include <stdio.h> //Библиотека стандартного ввода и вывода

int main(int argc, char *argv[])

{

int N; //Объявление переменной для размера массива

//Приглашение к вводу размера массива

printf("Введите количество элементов: ");

//Ввод количества элементов в массиве

scanf("%d",&N);

//Если введенное число меньше чем допустимое значение

if(N<3) {printf("Слишком мало элементов!\n"); return 0;}

//Приглашение к вводу элементов массива

printf("Введите массив: ");

//Объявление динамического массива

double arr[N];

//Ввод элементов массива в цикле

for(int i=0;i<N;i++) scanf("%lf",&arr[i]);

//Объявление и инициализация переменной для хранения

//суммы локальных минимумов

double summa = 0.0;

//Цикл по всем элементам массива, исключая первый

//и последний элементы

for(int i=1;i<N-1;i++)

//Если текущий элемент - локальный минимум,

if((arr[i]<arr[i-1])&&(arr[i]<arr[i+1]))

summa += arr[i]; // то суммирование

//Вывод на экран полученной суммы

printf("Сумма локальных минимумов: %.3lf\n",summa);

return 0;

}

Задача 4.2: Дан целочисленный массив размера N. Размер массива и его элементы вводятся пользователем. Упорядочить все элементы, расположенные до максимального элемента массива по возрастанию, а после максимального элемента по убыванию.

Программа для задачи

#include <stdio.h> //Библиотека стандартного ввода вывода

int main(int argc, char *argv[])

{

int N; //Объявление переменной для размера массива

//Приглашение к вводу размера массива

printf("Введите количество элементов: ");

//Ввод количества элементов массива

scanf("%d",&N);

//Если введенное число меньше чем допустимое значение

if(N<3) {printf("Слишком мало элементов!\n"); return 0;}

//Приглашение к вводу массива

printf("Введите массив: ");

//Объявление динамического массива

int arr[N];

//Ввод элементов массива в цикле

for(int i=0;i<N;i++) scanf("%d",&arr[i]);

//Переменная для позиции максимального элемента массива

int pos = 0;

//Цикл поиска максимального элемента в массиве

for(int i=1;i<N;i++)

//Если текущий элемент больше найденного максимума,

if(arr[i]>arr[pos])

pos = i; //то запоминаем позицию нового максимума

//Если есть два и более элементов массива, расположенных

//до максимального элемента

if(pos > 1){

//Сортировка пузырьковым алгоритмом по возрастанию

int flag = 1;//Флаг сортировки

while(flag){ //В цикле

flag = 0; //Сброс флага сортировки

//Цикл от первого элемента до максимального элемента

for(int i=0;i<pos-1;i++)

//Если необходима перестановка элементов

if(arr[i]>arr[i+1]){

//Обмен значений через дополнительную переменную

int r = arr[i]; arr[i] = arr[i+1]; arr[i+1] = r;

//Установка флага сортировки (необходима еще

//одна итерация)

flag = 1;

}

}

}

//Если есть два и более элементов массива, расположенных

//после максимального элемента

if(pos < N-2){

//Сортировка пузырьковым алгоритмом по убыванию

int flag = 1;//Флаг сортировки

while(flag){ //В цикле

flag = 0; //Сброс флага сортировки

//Цикл от первого элемента после максимального

//элемента до предпоследнего элемента в массиве

for(int i=pos+1;i<N-1;i++)

//Если необходима перестановка элементов

if(arr[i]<arr[i+1]){

//Обмен значений через дополнительную переменную

int r = arr[i]; arr[i] = arr[i+1]; arr[i+1] = r;

//Установка флага сортировки (необходима еще

//одна итерация)

flag = 1;

}

}

}

//Вывод полученного массива

printf("Результат: ");

for(int i=0;i<N;i++) printf("%d ",arr[i]);

printf("\n");

return 0;

}

Задача 4.3: Дана целочисленная матрица размера NЧM. Размер матрицы и элементы вводятся пользователем. Поменять местами строки матрицы, содержащие максимальный и минимальный элементы матрицы. Если максимум и минимум находятся в одной строке, то строку обнулить. Полученную матрицу вывести на экран.

Программа для задачи

#include <stdio.h> //Библиотека стандартного ввода/вывода

int main(int argc, char *argv[])

{

int N,M; //Переменные для хранения размера матрицы

//Приглашение к вводу размеров матрицы

printf("Введите размеры матрицы: ");

//Ввод размеров матрицы

scanf("%d %d",&N,&M);

//Если размеры введены некорректно, то выход из программы

if((N<2)||(M<2)){

printf("Размеры введены некорректно!\n");

return 0;

}

//Приглашение к вводу матрицы

printf("Введите матрицу:\n");

//Создание динамической матрицы

int matrix[N][M];

//Ввод матрицы в двух вложенных циклах

for(int i=0;i<N;i++) for(int j=0;j<M;j++)

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

//Объявление и инициализация переменных для хранения

//значений максимума и минимума матрицы

int max = matrix[0][0], min = matrix[0][0];

//Объявление и инициализация переменных для хранения

//номеров строк содержащих максимум и минимум матрицы

int imax = 0, imin = 0;

//Просмотр всей матрицы в двух вложенных циклах

for(int i=0;i<N;i++) for(int j=0;j<M;j++){

//Если текущий элемент матрицы больше найденного ранее

//максимума, то записываем новый максимум и его строку

if(max < matrix[i][j]) {max = matrix[i][j]; imax = i;}

//Если текущий элемент матрицы меньше найденного ранее

//минимума, то записываем новый минимум и его строку

if(min > matrix[i][j]) {min = matrix[i][j]; imin = i;}

}

//Если номер строки минимума не равен номеру строки

//максимума, то обмениваем значения строк

if(imin != imax){

//Обмен значений двух строк в цикле по всем столбцам

//через дополнительную переменную

for(int j=0;j<M;j++){

int tmp = matrix[imin][j];

matrix[imin][j] = matrix[imax][j];

matrix[imax][j] = tmp;

}

}else{ //В противном случае

//Обнуление всех элементов строки

for(int j=0;j<M;j++) matrix[imin][j] = 0;

}

//Форматированный вывод результата

printf("Результат:\n");

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

//Вывод строки матрицы

for(int j=0;j<M;j++) printf("%4d ",matrix[i][j]);

//Переход на новую строку

printf("\n");

}

return 0;

}

Задача 4.4: Дана строка (максимум 15 символов), содержащая целое незнаковое число в троичной системе исчисления. Перевести число в семеричную систему исчисленияеревести число в семиричную число в троичной системе исчисления... Полученный результат вывести на экран.

Программа для задачи

#include <stdio.h> //Библиотека ввода и вывода

int main(int argc, char *argv[])

{

char str[16]; //Объявление строки

printf("Введите число: ");//Приглашение к вводу строки

gets(str); //Ввод строки

//Переменная для хранения числа и индексная переменная

unsigned num = 0, i;

/* ----------------------------------------------------

В цикле осуществляется преобразование строки в число по

основанию три

---------------------------------------------------- */

//Цикл с первого до последнего символа строки

for(i=0;str[i]!=0;i++){

//Суммирование с числом текущей цифры строки

num += str[i] - 48;

//Умножение на основание системы исчисления

if(str[i+1] != 0) num *= 3;

}

/* ----------------------------------------------------

В цикле осуществляется преобразование в числа в строку

по основанию семь

---------------------------------------------------- */

//Цикл пока число больше ноля

for(i=0;num>0;i++){

//Запись в строку разряда числа

str[i] = num%7 + 48;

//Деление числа на основание системы исчисления

num /= 7;

}

//Запись завершающего нуля в строку, в переменной i

//содержится длина строки

str[i] = 0;

/* ----------------------------------------------------

В цикле осуществляется обращение строки, так как при

преобразовании числа в строку была получена обратная

строка

---------------------------------------------------- */

//Цикл от начала строки до ее середины

for(int j=0;j<i/2;j++){

char ch = str[j]; //Обратное отражение строки через

str[j] = str[i-j-1]; //дополнительную переменную

str[i-j-1] = ch;

}

printf(“Результат: ”);

puts(str); //Вывод результата на экран

return 0;

}

Задача 4.5: Дана строка (максимально 100 символов), содержащая слова, разделенные одним или несколькими пробелами, или знаками табуляции. Заменить все знаки табуляции знаком пробела, удалить двойные пробелы из строки. При реализации программы функции из библиотеки string.h не использовать.

Программа для задачи

#include <stdio.h> //Библиотека ввода и вывода

int main(int argc, char *argv[])

{

char str[101]; //Объявление строки на 100 символов

printf("Введите строку: "); //Приглашение к вводу строки

gets(str); //Ввод строки

for(int i=0;str[i]!=0;i++) //Цикл по всем символам строки

//Если текущий символ - знак табуляции, то заменяем его

if(str[i] == '\t') str[i] = ' '; //на знак пробела

/* ------------------------------------------------------

В следующем цикле осуществляется удаление всех двойных

пробелов. Для этого строка «копируется» сама в себя,

пропуская все лишние пробелы. Переменная j используется

для подсчета символов в новой строке.

------------------------------------------------------ */

int j = 1;

for(int i=1;str[i] != 0;i++){ //Цикл по всей строке

//Если найден двойной пробел, то продолжаем цикл

if((str[i] == ' ') && (str[i-1]==' ')) continue;

//«Копирование» строки саму в себя с пропуском лишних

str[j++] = str[i]; //пробелов

}

str[j] = 0; //Записываем завершающий ноль в конец строки

printf(“Результат: ”);

puts(str); //Выводим полученную строку на экран

return 0;

}

Задача 4.6: Дана строка (максимально 100 символов), содержащая слова, разделенные одним или несколькими пробелами, или знаками табуляции. Заменить все знаки табуляции знаком пробела, удалить двойные пробелы из строки. При реализации программы использовать функции из библиотеки string.h.

Программа для задачи

#include <stdio.h> //Библиотека ввода и вывода

#include <string.h> //Библиотека строковых функций

int main(int argc, char *argv[])

{

char str[101]; //Объявление строки на 100 символов

printf("Введите строку: "); //Приглашение к вводу строки

gets(str); //Ввод строки

/* ------------------------------------------------------

В бесконечном цикле do - while осуществляется поиск

символов табуляции в строке и замена их символами пробела

------------------------------------------------------ */

do{

int ind = strcspn(str,"\t");//Поиск символа табуляции

if(str[ind] == 0) break; //Если не найден, то выход

str[ind] = ' '; //Замена символа табуляции пробелом

}while(1);

/* -----------------------------------------------------

В бесконечном цикле do - while осуществляется поиск

двойных пробелов в строке и замена их одним пробелом

----------------------------------------------------- */

do{

char *ptr = strstr(str," ");//Поиск двойного пробела

if(!ptr) break; //Если не найден, то выход

strcpy(ptr,ptr+1); //Удаление двойного пробела, методом

//копирования строки самой в себя с подавлением одного

}while(1); // символа

/* -----------------------------------------------------

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

их арифметика, описываемые в следующей главе

----------------------------------------------------- */

printf(“Результат: ”); puts(str); //Вывод результата

return 0;

}

Задача 4.7: Дана строка (максимальная длина 100 символов), содержащая слова, разделенные пробелами или знаками табуляции. Число слов в строке не превышает 20, а длина каждого слова не более 10 символов. Упорядочить слова в строке по алфавиту.

Программа для задачи

#include <stdio.h> //Библиотека ввода и вывода

#include <string.h> //Библиотека строковых функций

int main(int argc, char *argv[])

{

char str[101]; //Объявление строки на 100 символов

//Приглашение и ввод строки

printf("Введите строку: "); gets(str);

char array[20][11]; //Массив строк для слов в строке

int count = 0; //Переменная для подсчета слов в строке

//Выделение первого слова в строке

char *ptr = strtok(str,"\t ");

//В цикле по всем словам в строке

for(count=0; ptr!=NULL; count++){

//Копирование слова из строки в массив слов

strcpy(array[count],ptr);

//Поиск следующего слова

ptr = strtok(NULL,"\t ");

}

int flag = 1; //Флаг сортировки

while(flag){ //Внешний цикл сортировки методом пузырька

flag = 0; //Сброс флага сортировки

for(int i = 0;i<count-1;i++) //Цикл по всем словам

//Если два соседних слова необходимо поменять местами

if(strcmp(array[i],array[i+1])>0){

char buffer[11]; //Дополнительная переменная

strcpy(buffer,array[i]); //Обмен значениями двух

strcpy(array[i],array[i+1]);//строк через дополни-

strcpy(array[i+1],buffer); //тельную переменную

flag = 1; //Установка флага сортировки

}

}

strcpy(str,"");//Запись пустой строки в исходную строку

for(int i=0;i<count;i++){//Цикл по всем словам

//Присоединение очередного слова к строке

strcat(str,array[i]);

strcat(str," "); //Запись разделяющего слова пробела

}

printf(“Результат: ”); puts(str); //Вывод результата

return 0;

}

Задача 4.8: Дан массив строк (максимально 25 строк, каждая строка не более 80 символов). Строки вводятся пользователем, признак завершения ввода - ввод пустой строки. Упорядочить строки по длине или по алфавиту (по выбору пользователя).

Программа для задачи

#include <stdio.h> //Библиотека ввода и вывода

#include <string.h> //Библиотека строковых функций

int main(int argc, char *argv[])

{

//Объявление массива из 25 строк длиной до 80 символов

char strs[25][81];

unsigned count = 0; //Число фактически введенных строк

printf("Вводите строки:\n"); //Приглашение к вводу строк

for(int i=0;i<25;i++,count++){//Цикл ввода 25 строк

gets(strs[i]); //Ввод одной i-ой строки

//Если введена пустая строка, то выход из цикла

if(strcmp(strs[i],"") == 0) break;

}

//Тип сортировки (0 - по алфавиту, 1 - по длине)

int type = 0;

//Приглашение к вводу типа сортировки

printf(" 0 - сортировать по алфавиту, \

\n !0 - сортировать по длине: ");

scanf("%d",&type); //Ввод типа сортировки

int flag = 1; //Флаг сортировки

while(flag){ //Внешний цикл сортировки пузырьком

flag = 0; //Сброс флага сортировки

for(int i = 0;i<count-1;i++) //Цикл по массиву строк

//Если сортировка по длине и необходима

//перестановка элементов strs[i] и strs[i+1]

if((type&&(strlen(strs[i]) > strlen(strs[i+1])))||

//Если сортировка по алфавиту и необходима

//перестановка элементов strs[i] и strs[i+1]

(!type&&(strcmp(strs[i],strs[i+1])>0))){

char buffer[81]; //Обмен значений двух

strcpy(buffer,strs[i]); //элементов через

strcpy(strs[i],strs[i+1]); //дополнительную

strcpy(strs[i+1],buffer); //переменную

flag = 1; //Установка флага сортировки

}

}

printf("Результат:\n"); //Вывод результата на экран

for(int i=0;i<count;i++) puts(strs[i]);

return 0;

}

Задача 4.9: Дан список групп студентов. Каждая группа характеризуется: название группы (строка 10 символов), количество студентов в группе (целое число), список студентов (максимально 30 элементов). Каждый элемент списка студентов содержит следующую информацию о студенте: ФИО (строки по 15 символов), номер зачетной книжки (целое шестизначное число), средний балл студента (вещественное число). Вывести список групп студентов со списком студентов в каждой группе, упорядоченным по среднему баллу, и указанием среднего балла каждой группы. Информация о каждом студенте вводится и выводится построчно в формате: ФИО [номер зачетной книжки] средний балл.

Программа для задачи

#include <stdio.h>

int main(int argc, char *argv[])

{

typedef struct{ //Описание структуры СТУДЕНТ

char fio[3][16]; //ФИО студента

unsigned number; //Номер зачетной книжки

double rate; //Средний балл

} STUDENT;

typedef struct{ //Описание структуры ГРУППА

char name[11]; //Имя группы

unsigned num; //Количество студентов в группе

STUDENT list[30];//Список студентов

} GROUP;

unsigned num = 0; //Переменная содержащая число групп

//Приглашение к вводу и ввод числа групп

printf("Введите число групп: "); scanf("%u",&num);

GROUP groups[num]; //Создание списка групп

/* --------------- Блок ввода данных ---------------- */

for(unsigned i=0;i<num;i++){ //Цикл ввода списка групп

//Приглашение к вводу i-ой группы

printf("Введите информацию о группе %u\n",i+1);

//Приглашение и ввод имени группы

printf("Имя: "); scanf("%s",&groups[i].name);

//Приглашение и ввод количества студентов в группе

printf("Число студентов: ");

scanf("%u",&groups[i].num);

//Приглашение к вводу списка студентов

printf("Введите список студентов: \n");

//Цикл ввода списка студентов

for(unsigned j=0;j<groups[i].num;j++){

printf("%2u: ",j+1); //Вывод номера в списке

//Ввод одного элемента согласно формату

scanf("%s %s %s [%u] %lf",

&groups[i].list[j].fio[0],

&groups[i].list[j].fio[1],

&groups[i].list[j].fio[2],

&groups[i].list[j].number,

&groups[i].list[j].rate);

}

//Разделитель для ввода следующей группы

puts("-----------------------------------------");

}

/* -------------- Блок обработки данных -------------- */

for(unsigned i=0;i<num;i++){ //Цикл по списку групп

int flag = 1; //Объявление флага сортировки

while(flag){ //Внешний цикл сортировки пузырьком

flag = 0; //Сброс флага сортировки

//Цикл по списку студентов

for(unsigned j=0;j<groups[i].num-1;j++)

//Если два элемента необходимо поменять местами

if(groups[i].list[j].rate <

groups[i].list[j+1].rate){

//Обмен значениями через дополнительную

STUDENT st = groups[i].list[j]; // переменную

groups[i].list[j] = groups[i].list[j+1];

groups[i].list[j+1] = st;

flag = 1; //Установка флага сортировки

}

}

}

/* --------------- Блок вывода данных ---------------- */

puts("Результат:");

for(unsigned i=0;i<num;i++){ //Цикл по списку групп

//Вывод имени и числа студентов в группе

printf("Группа %s (%u студентов)\n",

groups[i].name,groups[i].num);

//Переменная для вычисления среднего балла по группе

double mid = 0.0;

//Цикл по списку студентов в группе

for(unsigned j=0;j<groups[i].num;j++){

//Вывод одного элемента согласно формату

printf("%2u: %15s %15s %15s [%6u] %.1lf\n",j+1,

groups[i].list[j].fio[0], groups[i].list[j].fio[1],

groups[i].list[j].fio[2], groups[i].list[j].number,

groups[i].list[j].rate);

//Суммирование для вычисления среднего балла

mid += groups[i].list[j].rate;

}

//Вычисление среднего балла

mid /= groups[i].num;

//Вывод среднего балла

printf("Средний балл: %lf\n",mid);

//Разделитель для вывода следующей группы

puts("---------------------------------------------");

}

return 0;

}

Задача 4.10: Дан список учащихся школ и ВУЗов. Каждый элемент списка содержит следующую информацию: ФИО учащегося (строка 45 символов), пол (М | Ж), дата рождения (в формате дд.мм.гггг). Если это школьник, то содержится следующая информация: номер школы (целое число), номер класса (целое число), буква класса (символ). Если это студент, то содержится следующая информация: ВУЗ (строка 10 символов), группа (строка 10 символов). Вывести список учащихся по типам: сначала школьники, а затем студенты. Внутри списков учащихся упорядочивать по ФИО. Информация о школьниках вводится и выводится построчно в следующем формате: ФИО пол дата рождения, школа класс. Информация о студентах вводится и выводится построчно в следующем формате: ФИО пол дата рождения, ВУЗ группа.

Программа для задачи

#include <stdio.h> //Библиотека стандартного ввода и вывода

#include <string.h>//Библиотека строковых функций

int main(int argc, char *argv[])

{

/* ---------------------------------------------------

Объявление перечисления для типов учащихся:

PUPIL (значение 0) - школьник,

STUDENT (значение 1) - студент.

--------------------------------------------------- */

typedef enum{PUPIL = 0, STUDENT} TYPE;

//Объявление структуры информации о школьнике

typedef struct{

unsigned char school, form;//Номера школы и класса

char letter; //Буква класса

} P_INFO;

//Объявление структуры информации о студенте

typedef struct{

char uni[11], group[11];//названия ВУЗа и группы

} S_INFO;

//Объявление структуры информации об учащемся

typedef struct{

char fio[46]; //ФИО учащегося

//Пол и дата рождения (используя битовые поля)

unsigned pol:1,dd:5,mm:4,yy:14;

TYPE type; //Тип учащегося: школьник или студент

//Поле информации об учащемся в зависимости от его типа

//(реализовано с использованием объединений)

union {P_INFO p_info; S_INFO s_info;} data;

} MAN;

//Переменная для хранения числа записей

unsigned num = 0;

//Приглашение и ввод числа записей

printf("Введите количество записей: "); scanf("%u",&num);

//Проверка корректности ввода (выход если меньше двух)

if(num < 2) {

printf("Слишком мало элементов! \n");

return 0;

}

//Объявление списка учащихся

MAN list[num];

/* --------------- Блок ввода записей ---------------- */

for(unsigned i=0;i<num;i++){ //Цикл по всему списку

/* ---------------------------------------------------

Дополнительные переменные для промежуточного хранения:

str - буфер ввода

fio - ФИО учащегося

pol - пол учащегося

data - дата рождения

---------------------------------------------------- */

char str[100],fio[3][16],pol;

unsigned data[3];

//Очистка потока ввода и ввод данных (см. главу 9)

fflush(stdin); gets(str);

//Разбор первой (общей) части строки: ФИО пол дата

sscanf(str,"%s %s %s %c %u.%u.%u",

&fio[0],&fio[1],&fio[2],

&pol,&data[0],&data[1],&data[2]);

//Запись даты рождения в поля структуры элемента

list[i].dd = data[0];

list[i].mm = data[1];

list[i].yy = data[2];

//Запись ФИО учащегося в поле fio структуры элемента

strcpy(list[i].fio,""); //Инициализация пустой строкой

for(int j=0;j<3;j++){ //Сложение строк fio[0],

strcat(list[i].fio,fio[j]);//fio[1] и fio[2] с

strcat(list[i].fio," "); //разделяющими пробелами

}

//Запись пола (0- мужской, 1- женский) в поле структуры

list[i].pol= (pol=='М')?0:1;

//Определение позиции запятой с введенной строке

char *ptr = strchr(str,',');

//Читаем остаток строки как информацию о школьнике

int res = sscanf(ptr+1,"%u %u%c",

&list[i].data.p_info.school,

&list[i].data.p_info.form,

&list[i].data.p_info.letter);

//Если введена запись о школьнике, в поле type

//устанавливаем соответствующее значение PUPIL

if(res == 3) list[i].type = PUPIL;

//Если введена запись о студенте, в поле type

//устанавливаем соответствующее значение STUDENT

//и считываем остаток строки как информацию о студенте

else{

list[i].type = STUDENT;

sscanf(ptr+1,"%s %s",

&list[i].data.s_info.uni,

&list[i].data.s_info.group);

}

}

/* -------------- Блок обработки списка -------------- */

int flag = 1; //Флаг сортировки

while(flag){ //Внешний цикл сортировки пузырьком

flag = 0; //Сброс флага сортировки

for(unsigned i=0;i<num-1;i++) //Цикл по всему списку

//Проверка условия на перестановку элементов: если

//это студент и школьник, то необходима перестановка

if((list[i].type>list[i+1].type)||

//если это два студента или два школьника

//и второй из них стоит по алфавиту раньше первого

//то необходима перестановка

((list[i].type==list[i+1].type)&&

(strcmp(list[i].fio,list[i+1].fio)>0))){

MAN tmp = list[i]; //Обмен значений через

list[i] = list[i+1]; //дополнительную переменную

list[i+1] = tmp;

flag = 1; //Установка флага сортировки

}

}

/* --------------- Блок вывода записей --------------- */

printf("Результат:\n");

//Цикл по всему списку

for(unsigned i=0;i<num;i++){

//Вывод общей информации: ФИО пол дата рождения

printf("%-45s %c %2u.%2u.%4u, ",

list[i].fio, //ФИО учащегося

((list[i].pol)?'Ж':'М'), //пол учащегося

list[i].dd,list[i].mm,list[i].yy);//дата рождения

if(list[i].type == PUPIL) //Если это школьник, то

//выводим информацию о школьнике

printf("%d %d%c\n",

list[i].data.p_info.school, //номер школы

list[i].data.p_info.form, //номер класса

list[i].data.p_info.letter); //буква класса

else //Иначе это студент, поэтому

//выводим информацию о студенте

printf("%s %s\n",

list[i].data.s_info.uni, //ВУЗ

list[i].data.s_info.group);//группа

}

return 0;

}