Программирование >>  Динамические структуры данных 

1 ... 20 21 22 [ 23 ] 24 25 26 ... 38


for (j = 0; j < п - 1: j++) { if (a[i][j] a[i][j + 1] ) count++: else { if (count > maxcount) {

maxcount = count: line = i: } count = 0:

if (count > maxcount) { maxcount count: line i; }

return line:

2 3

Алгоритм работы функции прост: в каждой строке выполняется сравнение соседних элементов (оператор 2). Если они равны, мы находимся внутри серии, при этом увеличиваем ее текущую длину. Она накапливается в переменной count, которая обнуляется перед обработкой каждой строки (оператор 1). Если же элементы не равны, это означает либо окончание серии, либо просто одиночный элемент (оператор 3). В этом случае надо посмотреть, не является ли данная серия самой длинной из рассмотренных и, если да, то запомнить ее длину и номер строки, в которой она встретилась (оператор 4). Для подготовки к анализу следующих серий в этой же строке надо обнулить счетчик count. Аналогичная проверка после цикла просмотра строки (оператор 5) выполняется для серии, которая расположена в конце строки, поскольку в этом случае ветвь else выполняться не будет. Если в массиве нет ни одной серии одинаковых элементов, функция вернет значение, равное -1.

Задача 7.5. Передача структур в функцию

Написать программу дополнения бинарного файла, сформированного в задаче 6.3, вв(имыми с клавиатуры сведениями о сотрудниках.

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

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

Ниже приведен текст программы.

finclude <stdio.h> #include <string.h> #include <stdlib.h>

finclude <windows.h> 0

const int 1 name = 30;

struct Man {

char name[l name + 1]: int birth year: float pay:

Man read data():

int append2binfile(const Man &man. const char* filename); int print from bin(const char * filename):

int main(){ bool contin; chary n[2]; char filenameC] = dbase.bin ; do { contin - false;

if (append2binfile(read data(), filename) != 0) { putsC* Ошибка при записи в файл ): return 1: }

putsC Продолжить (y/n)? ): gets(y n):

if ((y n[0] - -y) II (y n[0] == Y ) ) contin = true: } while (contin): pri nt from bi n(fi1ename): return 0:

int append2binfile(const Man &man. const char* filename) { FILE *fout:

if ((fout fopen(filename, ab )) NULL ) return 1: int success = fwrite(&man, sizeof(man). 1, fout); fclose(fout):

if (success = 1) return 0; else return 2;

int print from bin(const char * filename) { int num: Man man: FILE *f;

if ((f = fopen(filename, rb )) -= NULL ) return 1:

fseekCf. 0. SEEKJND):

int n record = ftell(f) / sizeof (man):

while (true) {

puts( Введите номер записи или -1: ): scanf( i . &rum): if Сnum < 0 II num >= n record) break: fseek(f. num * sizeof(man). SEEK SET): fread(&man. sizeof(man). 1. f): CharToOem(man.name. man.name):

printf( *30sX5iX10.2f\n . man.name. man.birth year. man.pay);



iSabaUaus

return 0;

Man read data(){ Man man: char buf[80]: char name[l name + 1]: putsCBeeflHTe фамилию И.О. ): gets(name): if (strlen(name) < l name)

for (int i - strlen(name): i < l name: name[i] - : name[l name] - 0:

OemToChar(name, name): 2

strncpy (man. name. name. l name + 1): do {

putsCBeeflHTe год рождения ): gets(buf): } while ((man.birth year - atoi(buf)) - 0): 3

do {

puts( Введите оклад ): gets(buf): } while (!(man.pay - atof(buf))): 4

return man:

В функции ввода read data предусмотрено заполнение пробелами оставшейся части строковой переменной name, чтобы формат имени был идентичен формату ввода в текстовом файле.

ПРИМЕЧАНИЕ -----

Здесь так же, как и в задаче 6.1, необходимо учесть возможные различия в кодировке символов. Если вы работаете в среде Visual С++, а бинарный файл был создан из текстового файла, подготовленного в текстовом редакторе с кодировкой ANSI, то вам нужно раскомментировать операторы 0,1 и 2.

Обратите внимание на то, как в этой функции выполняется проверка правильности ввода числовой информации. Чтение выполняется в буферную строку, которая затем преобразуется с помощью функций atoi () и atof () в числа. Если функции возвращают О, преобразование выполнить не удалось (например, вместо цифр были введены буквы), и информация запрашивается повторно. Условие повторения циклов 3 и 4 записано в двух разных вариантах, чтобы вы сами могли оценить, какой из них вам более понятен (профессионалы предпочли бы второй, более лаконичный вариант).

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

Задача 7.6. Рекурсивные функции

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

Пришло время выполнить обещание, данное на третьем семинаре, - показать рекурсивную реализацию метода быстрой сортировки. Если вы не знаете, что такое рекурсивные функции, загляните в Учебник на с. 82. Говоря кратко, рекурсивной называется функция, в которой имеется обращение к ней самой. Освежите в памяти алгоритм быстрой сортировки, который мы рассматривали, решая задачу 3.3. Там использовалась процедура разделения , применяемая к фрагменту массива (изначально - ко всему массиву). На каждом шаге образовывались две половинки текущего фрагмента, и к ним снова нужно было применять процедуру разделения. То есть по своей сути алгоритм является рекурсивным, и если не здесь применять рекурсивные функции, то где?..

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

void qsort(float* array, int left, int right):

int main(){ const int n = 10: float arr[n]: int i. 1. r:

cout Введите элементы массива: : for (i = 0: i < n: i++) cin arr[i]:

1 = 0: r = n - 1: qscrt(arr. 1. r);

левая и правая границы начального фрагмента

for (i = 0: i < n: i++) cout arr[i] : return 0:

Cm. пояснения к задаче 6.1.

void qsort(float* array, int left, int right) { int i = left, j = right: float middle = array[(left + right) / 2]: float temp:



while (1 < j) { while (arrayCi] < middle) i++: while (middle < arrayCj]) j--: if (i <= j) {

temp = arrayCi]:

arrayCi] - arrayCj]:

arrayCj] = temp:

i++:

J--:

if (left < j) qsort(array. left, j): if (i < right) qsort(array, i. right):

2 3

Мы не будем подробно анализировать эту программу, поскольку алгоритм вам знаком по задаче 3.3. Отметим только, что процедура разделения реализована здесь в виде рекурсивно вызываемой функции qsort (), в теле которой есть два обращения к самой себе: в операторе 2 - для сортировки левой половинки текущего фрагмента, и в операторе 3 - для сортировки его правой половинки. Надеемся, что сравнение этой программы с предыдущей нерекурсивной версией (задача 3.3) вызовет у вас истинное эстетическое наслаждение*.

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

Многофайловые проекты

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

ПРИМЕЧАНИЕ ---

Описанные в приложениях интегрированные среды Visual С++ 6.0 и Borland С++ 3.1, как и подавляющее большинство других, поддерживают создание, компиляцию и сборку многофайловых проектов. С технологией их создания вы можете ознакомиться по приложениям, а здесь мы рассмотрим вопросы, связанные с распределением функций по модулям, разделением интерфейса и реализации и особенностями использования глобальных переменных. Теоретический материал по директивам препроцессора см. в Учебнике на с. 93-96.

Напомним, что пользуясь технологией нисходящего проектирования программ, мы разбиваем исходную задачу на подзадачи, затем при необходимости каждая из них также разбивается на подзадачи, и так далее, пока решение очередной подзадачи не окажется достаточно простым, то есть реализуемым в виде функции обозримого размера (как уже указывалось, наиболее предпочтительным считается размер не более одного-двух экранов текстового редактора). Исходные тексты совокупности функций для решения какой-либо подзадачи, как правило, размещаются в отдельном модуле (файле). Такой файл называют исход-ным (source file). Обычно он имеет расширение . с или . срр. Прототипы всех функций исходного файла выносят в отдельный так называемый заголовочный файл (headerfile), для него принято использовать расширение .h или .hpp. Таким образом, заголовочный файл ххх. h содержит интерфейспдя некоторого набора функций, а исходный файл ххх.срр содержит реализацию этого набора. Если некоторая функция из указанного набора вызывается из какого-то другого исходного модуля ууу.срр, то вы обязаны включить в этот модуль заголовочный файл XXX. h с помощью директивы #i ncl ude. Негласное правило стиля программирования на С++ требует включения этого же заголовочного файла (с помощью #incl ude) и в исходный файл ххх.срр.

Теперь о глобальных переменных. В многофайловом проекте возможны два вида глобальности . Если некоторая глобальная переменная glvarl объявлена в файле ххх.срр с модификатором static, то она видима от точки определения до конца этого файла, то есть область ее видимости ограничена файлом. Если же другая глобальная переменная gl var2 объявлена в файле ххх. срр без модификатора stati с, то она может быть видимой в пределах всего проекта. Правда, для того, чтобы она оказалась видимой в другом файле, необходимо иметь в этом файле ее объявление с модификатором extern (рекомендуется это объявление поместить в файл XXX. h).

Что и как следует размещать в заголовочном файле

в заголовочном файле принято размещать:

□ определения типов, задаваемых пользователем, констант, шаблонов;

□ объявления (прототипы) функций;

□ объявления внешних глобальных переменных (с модификатором extern);

□ пространства имен

Теперь обратим ваше внимание на проблему повторного включения заголовочных файлов. Проблема может возникнуть при иерархическом проектировании структур данных, когда в некоторый заголовочный файл ууу. h включается при помощи директивы #i ncl ude другой заголовочный файл ххх. h (например, для использования типов, определенных в этом файле). Впрочем, лучше рассмотреть эту проблему на конкретном примере.

Если этого не произошло, вернитесь к третьему семинару и выпшнтте все упражнения, вплоть до настоящего семинара, повторно.

Попробуйте не включить и посмотрите на реакцию ко.мпилятора. Пространства имен рассматриваются в Учебнике на с. 99.



1 ... 20 21 22 [ 23 ] 24 25 26 ... 38

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика