Тотальное программирование "от класса к классу"
Строгое следование технологии ООП предполагает, что любая функция в программе представляет собой метод для объекта некоторого класса. Это не означает, что нужно вводить в программу какие попало классы ради того, чтобы написать необходимые для работы функции. Наоборот, класс должен формироваться в программе естественным образом, как только в ней возникает необходимость описания новых физических предметов или абстрактных понятий (объектов программирования). С другой стороны, каждый новый шаг в разработке алгоритма также должен представлять собой разработку нового класса на основе уже существующих. В конце концов вся программа в таком виде представляет собой объект некоторого класса с единственным методом run (выполнить). Именно этот переход (а не понятия класса и объекта, как таковые) создает психологический барьер перед программистом, осваивающим технологию ООП.
Программирование "от класса к классу" включает в себя ряд новых понятий. Прежде всего, это - НАСЛЕДОВАНИЕ . Новый, или производный класс может быть определен на основе уже имеющегося, или базового. При этом новый класс сохраняет все свойства старого: данные объекта базового класса включаются в данные объекта производного, а методы базового класса могут быть вызваны для объекта производного класса, причем они будут выполняться над данными включенного в него объекта базового класса. Иначе говоря, новый класс наследует как данные старого класса, так и методы их обработки.
Вторым по значимости понятием является ПОЛИМОРФИЗМ . Он основывается на возможности включения в данные объекта также и информации о методах их обработки (в виде указателей на функции). Принципиально важно, что такой объект становится "самодостаточным". Будучи доступным в некоторой точке программы, даже при отсутствии полной информации о его типе, он всегда может корректно вызвать свойственные ему методы. Полиморфной называется функция, определенная в нескольких производных классах и имеющая в них общее имя. Точнее сказать, что полиморфная функция, это группа функций, которая выступает под одним и тем же именем, но в разных классах. Полиморфная функция обладает тем свойством, что при отсутствии полной информации о том, объект какого из производных классов в данный момент обрабатывается, она тем не менее корректно вызывается в том виде, который соответствует именно объекту этого класса (Здесь уместен образный термин " многоликая функция" ). Практический смысл полиморфизма заключается в том, что программист может сделать регулярным процесс обработки несовместимых объектов различных типов при наличии у них такого полиморфного метода (в Си++ -виртуальной функции).
Трансляция и ее фазы
Собственно трансляция начинается с лексического анализа программы. ЛЕКСИКА языка программирования -это правила "правописания слов" программы, таких как идентификатры, константы, служебные слова, комментарии. Лексический анализ разбивает текст программы на указанные элементы. Особенность любой лексики -ее элементы представляют собой регулярные линейные проследовательности символов. Например, ИДЕНТИФИКАТОР -это произвольная последовательность букв, цифр и символа "_", начинающаяся с буквы или "_".
СИНТАКСИС языка программирования -это правила составления предложений языка из отдельных слов. Такими предложениями являются операции, операторы, определения функций и переменных. Особенностью синтаксиса является принцип вложенности (рекурсивность) правил построения предложений. Это значит, что элемент синтаксиса языка в своем определении прямо или косвенно в одной из его частей содержит сам себя. Например, в определении оператора цикла телом цикла является оператор, частным случаем которого является все тот же оператор цикла.
СЕМАНТИКА языка программирования -это смысл, который закладывается в каждую конструкцию языка. Семантический анализ -это проверка смысловой правильности конструкции. Например, если мы в выражении используем переменную, то она должна быть определена ранее по тексту программы, а из этого определения может быть получен ее тип. Исходя из типа переменной, можно говорит о допустимости операции с данной переменной.
ГЕНЕРАЦИЯ КОДА -это преобразование элементарных действий, полученных в результате лексического, синтаксического и семантического анализа программы, в некоторое внутреннее представление. Это могут быть коды команд, адреса и содержимое памяти данных, либо текст программы на языке Ассемблера, либо стандартизованный промежуточный код (например, P-код). В процессе генерации кода производится и его оптимизация.
Трансляция и компоновка
.
---------------------------------------------------------------¬
¦File Edit Search Run Compile Debug Project Options Window Help¦
L-----------------¦---¦-----------------------------------------
----------------+-¬ ¦
¦Run Ctrl+F9 ---- Команда "Make" и выполнение программы
¦... ¦ ¦
L------------------ ¦
--------------+---------¬
--------¦Compile Alt-F9 ¦
¦-------¦Make F9 ¦
¦¦------¦Link ¦
¦¦¦-----¦Build all ¦
¦¦¦¦ +-----------------------+
¦¦¦¦ ¦Rеmove messages --- Очистить окно сообщений
¦¦¦¦ L------------------------ транслятора (message)
¦¦¦L--- Безусловная трансляция и компоновка файла текущего
¦¦¦ окна или проекта
¦¦L---- Компоновка файла текущего окна или проекта
¦L----- Трансляция и компоновка файла текущего окна или
¦ проекта (каждый файл транслируется только при
¦ условии внесения изменений в текст программы,
¦ в том числе в файлы, включенные директивой include)
L------ Трансляция файла текущего окна
.
---------------------------------------------------------------¬
¦File Edit Search Run Compile Debug Project Options Window Help¦
L----------¦----------------------------------------------------
-----------+-----------¬
¦... ¦ -- Переход к строке программы, вызвавшей
¦Previous error Alt+F7 --- предыдущее сообщение (message)
¦Next error Alt+F8 ---- Переход к строке программы, вызвавшей
¦... ¦ следующее сообщение
L-----------------------
Указатель как элемент архитектуры компьютера
Возможен также случай, когда машинное слово содержит адрес другого машинного слова. Тогда доступ к данным во втором машинном слове через первое называется КОСВЕННОЙ АДРЕСАЦИЕЙ. Команды косвенной адресации имеются в любом компьютере и являются основой любого регулярного процесса обработки данных.
В языках программирования имя переменной обычно ассоциируется с адресом области памяти, в которой транслятор размещает ее в процессе трансляции программы. Поэтому все операции над переменными базовых типов данных преобразуются в команды с прямой адресацией к соответствующим словам памяти.
УКАЗАТЕЛЬ -- переменная, содержимым которой является адрес другой переменной.
Соответственно, основная операция для указателя - это косвенное обращение по нему к той переменной, адрес которой он содержит. В дальнейшем будем пользоваться такими терминами:
-указатель, который содержит адрес переменной, ССЫЛАЕТСЯ на эту переменную или НАЗНАЧЕН на нее;
-переменная, адрес которой содержится в указателе, называется УКАЗУЕМОЙ переменной.
В Си имеется специальная операция - "*" , которую называют КОСВЕННЫМ ОБРАЩЕНИЕМ ПО УКАЗАТЕЛЮ и которая является аналогом косвенной адресации. Кроме того, имеется операция "&" , которая дает адрес переменной, перед именем которой она поставлена. Все это позволяет повторить изображенный выше пример с косвенной адресацией с использованием средств языка Си:
Если же эту "картинку" перевести на определения и операции языка, то получим следующее:
int a,x; // Обычные целые переменнные
int *p; // Переменная - указатель на другую
// целую переменную
a = 2000;
p = &a; // Указатель содержит адрес переменной a
x = x + *p; // При косвенном обращении по указателю p
// берется значение указуемой переменной a
Указатель как средство доступа к данным
В языках программирования указатель можно рассматривать прежде всего как универсальное средство доступа к данным. Дело в том, что сложные программы хранят в памяти и обрабатывают одновременно значительное количество данных (переменных). Размерность их также может быть весьма большой. Передавать их из одной части программы в другую можно двумя способами :
- создавать в каждой точке программы (например, на входе функции) копию тех данных, которые необходимо обрабатывать ;
- передавать информацию о том, где в памяти расположены данные. Такая информация, естественно, является более компактной, чем сами данные, и ее условно можно назвать указателем. Таким образом, получаем " дилетантское" определение указателя :
В дальнейшем при работе с указателями и со структурами данных, их содержащими, мы будем постоянно использовать графическое представление указателя :
Указатель на функцию как формальный параметр
Указатель на функцию может быть передан в качестве формального параметра. Это типичный случай реализации алгоритма, в котором некоторый внутренний шаг задан в виде действия общего вида. Этот шаг осуществляется путем получения указателя на необходимую функцию и ее вызова. В качестве примера приведем функцию вычисления определенного интеграла для произвольной подынтегральной функции:
//------------------------------------------------------bk56-01.cpp
//------Численное интегрирование произвольной функции
double INTEG(double a, double b, int n, double(*pf)(double))
// a,b - границы интегрирования
// n - число точек
// pf - подынтегральная функция
{
double s,h,x;
for (s=0., x=a, h = (b-a)/n; x <=b; x+=h)
s += (*pf)(x) * h;
return(s);
}
extern double sin(double);
void main() { cout << INTEG(0.,1.,40,sin)); }
Указатель на структуру -формальный параметр
В "классическом" Си формальными параметрами могут быть базовые типы данных и указатели. Передача параметров в функцию по значению связано с их копированием, поэтому транслятор должен уметь это сделать. А поскольку массивы и структуры нельзя копировать целиком, а только поэлементно, то в формальных параметрах могут быть только указатели на них (о формальных параметрах -массивах -см.4.1). Предыдущий пример с использованием указателя на структуру будет выглядеть следующим образом:
void proc(man *p)
{
p->name[m]... // Для работы с элементами
// структуры через указатель
cout << p->name; // на нее используется
// операция "->"
cout << p->yy << p->mm << p->dd;
} // Фактические параметры -
void main() { // указатели на структурированные
int i; // переменные, полученные
proc(&A); // с помощью операции "&"
for (i=0; i< 10; i++) proc(&X[i]);
}
В данном случае формальный параметр p считается указателем на отдельную структурированную переменную. Функцию можно несколько изменить, если считать, что указатель ссылается на массив структурированных переменных. Для этого его придется сопроводить еще и размерностью массива, а доступ к элементам структур через этот указатель будет выглядеть несколько иначе:
void proc(man *p, int n)
{ // Здесь p[i].name эквивалентно
int i; // (*(p+i)).name - элемент name
for (i=0; i<n; i++) // в i-й структурированной пере-
{ // меннной от текущего положения
p[i].name[m]... // указателя
cout << p[i].name; //
cout << p[i].yy << p[i].mm << p[i].dd;
} // Фактические параметры -
// указатели на структурированныe
proc(&A,1); // переменные (операция "&") и
proc(B,10); // массивы (идентификатор массива)
}
Указатель на структуру -результат функции
Указатель на структурированную переменную может быть также и результатом функции. В этом случае в операторе return должен быть указатель (адрес) некоторой структурированной переменной, которая будет доступна через него после завершения функции. Чисто технически это не составляет проблем. Но попутно возникает вопрос: а откуда берется эта переменная. Для ответа на него перечислим возможные " источники" :
-переменная не может быть локальной (автоматической) переменной функции, поскольку они уничтожается после выхода из функции;
-функция может возвращать указатель на любую общедоступную глобальную (внешнюю) переменную;
-функция может возвращать указатель на переменную, которая в свою очередь получена функцией через указатель на переменную или массив (то есть передавать переменную " со входа на выход" );
-функция может возвращать указатель на динамическую переменную, создаваемую в процессе работы функции.
В данном примере функция выбирает из массива структурированных переменных и возвращает указатель на переменную, у которой элемент name содержит пустую строку :
man *find(man *p, int n)
{
int i;
for (i=0; i<n; i++)
if (p[i].name[0]=='\0')
return(&p[i]); // или return(p+i);
return(NULL);
}
Указатель - результат функции
Функция в качестве результата может возвращать указатель. В этом случае она обычно "выбирает" некоторую переменную из имеющихся или же "создает" ее (см.5.1). В следующем простом примере функция возвращает указатель на минимальный элемент массива, переданного в виде формального параметра:
// Результат функции - указатель на целое
int *min(int A[], int n)
{
int *pmin, i; // Рабочий указатель, содержащий результат
for (i=1, pmin=A; i<n; i++)
if (A[i] < *pmin) pmin = &A[i];
return(pmin); // В операторе return - значение указателя
}
void main() // Записать 0 на место
{ *min(B,10) = 0; } // минимального элемента массива
Прежде всего обратим внимание на синтаксис. Заголовок функции написан таким образом, как будто имя функции является указателем на int . Этим способом и обозначается, что ее результат -указатель. Оператор return возвращает значение переменной-указателя pmin , то есть адрес. Вообще в нем может стоять любое выражение, значение которого является указателем, например:
return &A[k];
return pmin+i;
return A+K;
Указатель - результат функции может ссылаться не только на отдельную переменную, но и на массив. В этом смысле он не отличается ничем от других указателей.
Указатель типа void*
Наличие указателя определенного типа предполагает известную организацию памяти, на которую он ссылается. Но в некоторых случаях фрагмент программы " не должен знать" или просто не имеет достаточной информации о структуре данных в этой области. Тогда указатель должен пониматься как адрес памяти как таковой, с неопределенной организацией и неизвестной размерностью указуемой переменной. Такой указатель можно присваивать, передавать в качестве параметра и результата функции, но операции косвенного обращения и адресной арифметики с ним недопустимы.
Именно такими свойствами обладает указатель типа void* -указатель на пустой тип void . Наличие его в данном месте программы говорит о том, что она не имеет достаточных оснований для работы с адресуемой областью памяти. Наиболее часто тип void* является формальным параметром или результатом функции. Приведем несколько примеров:
extern void *malloc(int);
int *p;
p = ( int*)malloc(sizeof(int)*20);
p[i] = i;
Функция malloc возвращает адрес зарезервированной области динамической памяти в виде указателя void*. Это означает, что функцией выделяется память как таковая, безотносительно к размещаемым в ней переменным. Вызывающая функция неявно преобразует тип указателя void* в требуемый тип int* для работы с этой областью как с массивом целых переменных.
extern int fread(void *, int, int, FILE *);
int A[20];
fread( (void*)A, sizeof(int), 20, fd);
Как видно из примеров, преобразование типа указателя void* к любому другому типу указателя соответствует " смене точки зрения" программы на адресуемую память от " данные вообще" к " конкретные данные" и наоборот.
Указатели и массивы
Нетрудно заметить, что указатель в Си имеет много общего с массивом. Наоборот, труднее сформулировать, чем они отличаются друг от друга. Действительно, разница лежит не в принципе работы с указуемыми переменными, а в способе назначения указателя и массива на ту память, с которой они работают. Образно говоря, указателю соответствует массив, "не привязанный" к конкретной памяти, а массиву соответствует указатель, постоянно назначенный на выделенную транслятором область памяти. Рассмотрим сходства и различия в использовании указателей и массивов на следующем примере.
.
int A[20]; int *p;
При определении массива происходит резервирование памяти заданного размера, в дальнейшем имя массива интерпретируется как ее начальный адрес. При определении указателя с ним не связывается никакая область памяти.
.
A; или &A[0];
Идентификатор массива без скобок интерпретируется как начальный адрес массива, или указатель на нулевой элемент.
.
p = A; p = &A[0];
Перед началом работы с указателем его необходимо назначить на некоторую область памяти, в данном случае -на начало массива.
.
p = &A[5];
p = A + 5;
Указатель может быть назначен также на любую область памяти, в данном случае -на 5-й элемент массива;
.
A[i] *(p + i)
&A[i] p + i
A + i &p[i]
*(A + i) p[i]
Работа с областью памяти как с обычным массивом, так и через указатель полностью идентична вплоть до синтаксиса: транслятор интерпретирует конструкцию p[i] как *(p+i) для любого указателя и имени массива.
.
p++;
p +=i;
m = *p++;
Указатель может перемещаться по памяти относительно своего текущего положения.
Таким образом, различие между указателем и массивом аналогично различию между переменной и константой. Указатель -это ссылочная переменная, а имя массива -ссылочная константа, привязанная к конкретному адресу памяти.
Указатели и многомерные массивы
Для двумерных и многомерных массивов в Си существуют особенные взаимоотношения с указателями. Для их понимания необходимо напомнить те соглашения, которые заложены в языке для многомерных массивов:
-двумерный массив всегда реализован как одномерный массив с количеством элементов, соответствующих первому индексу, причем каждый элемент представляект собой массив элементов базового типа с количеством, соответствующим второму индексу. Например,
char A[20][80];
определяет массив из 20 массивов по 80 символов в каждом и никак иначе. Массив, таким образом, располагается в памяти по строкам;
-идентификатор массива без скобок интерпретируется как адрес нулевого элемента нулевой строки, или указатель на базовый тип данных. В нашем примере идентификатору A будет соответствовать выражение &A[0][0] с типом char*;
-по аналогии имя двумерного массива с единственным индексом интерпретируется как начальный адрес соответствующего внутреннего одномерного массива. A[i] понимается как &A[i][0], то есть начальный адрес i-го массива символов.
Сказанное справедливо и для многомерных массивов: многомерный массив представляет собой массив элементов первого индекса, каждый из который представляет собой массив элементов второго индекса и т.д..
extern void gets(char*);
char A[20][80];
int i;
for (i=0; i< 20; i++) // А[i] - указатель на i-ю строку
gets(A[i]); // в двумерном массиве символов
Как известно, обычный указатель работает с линейной последовательностью элементов. В этом случае при присваивании начального адреса двумерного массива обычному указателю его двумерная структура " теряется" . В следующем примере указатель используется для работы с двумерным массивом с учетом его реального размещения в памяти:
char *q,A[20][80];
q = A;
*(q + i*80 + j) // или
q[i*80 + j]
Для работы с многомерными массивами вводятся особые указатели -указатели на массивы. Они представляют собой обычные указатели, адресуемым элементом которых является не базовый тип, а массив элементов этого типа:
char (*p)[][80];
char (*q)[5][80];
Заметим, что круглые скобки имеют здесь принципиальное значение. В контексте определения p является переменной, при косвенном обращении к которой получается двумерный массив символов, то есть p является указателем на двумерный массив. При отсутствии скобок имел бы место двумерный массив указателей на строки. Кроме того, если не используются операции вида p++ или p+=n , связанные с размерностью указуемого массива, то наличия первого индекса не требуется.
p = q = A; // назначить p и q на двумерный массив
(*p)[i][j]... // j-ый символ в i-ой строке
// в массиве по указателю p
p+=2; // переместить p на 2 массива
// по 5 строк по 80 символов
Указатели и управление памятью в Си
Насколько глубоко программист владеет техникой работы указателями - является показателем его понимания специфики Си. Такая рядовая операция как преобразование типа указателя меняет точку зрения программиста на память - в Си к ней не привязываются "намертво" структуры данных, программист может работать с ней на более низком уровне, как это принято на ассемблере. Специфика этой работы лучше всего иллюстрируется на задачах, в которых данные представлены в виде последовательности переменного формата, в которой значения предыдущих переменных определяют типы и значения последующих (типичный случай - упаковка данных).
Указатели как формальные параметры
В Си предусмотрен единый способ передачи параметров в функцию -так называемая ПЕРЕДАЧА ПО ЗНАЧЕНИЮ. Формальные параметры представляют собой аналог собственных локальных переменных функции, которым присваиваются значения фактических параметров. Формальные параметры, представляя собой копии, могут как угодно изменяться -это не затрагивает соответствующих фактических параметров. Если же требуется обратное, то формальный и фактический параметр должны быть явно переданы в виде указателей на ту переменную, изменения которой должны производиться в функции:
void inc(int *p)
{ (*pi)++; } // аналог вызова:
// pi = &a
void main()
{ int a;
inc(&a); } // *(pi)++ эквивалентно a++
В данном примере формальный параметр является явным указателем, а фактический - явным адресом переменной. В Си++ аналогичный способ передачи параметров поддерживается транслятором с помощью неявных, скрытых от программиста указателей, и называется передачей параметров по ссылке (см.7.2). В Си тоже имеется одно такое исключение: формальный параметр - массив передается в виде указателя на его начало. Посмотрим, как в этом случае поступает транслятор:
int sum(int A[],int n) // Исходная программа
{ int s,i;
for (i=s=0; i<n; i++) s+= A[i];
return s; }
int sum(int *p, int n) // Эквивалент с указателем
{ int s,i;
for (i=s=0; i<n; i++) s+= p[i];
return s; }
int x,B[10]={1,4,3,6,3,7,2,5,23,6};
void main()
{ x = sum(B,10); } // аналог вызова: p = B, n = 10
Заметим, что в вызове фигурирует идентификатор массива. Он интерпретируется как начальный адрес массива (указатель на его нулевой элемент). Поэтому типы формального и фактического параметров совпадают. Совпадают также оба варианта функций вплоть до генерируемого кода.
Рассмотрим третий вариант той же функции:
int sum(int *p, int n)
{ int s;
for (s=0; n >=0; n--) s += *p++;
return s; }
В нем операции p[i] и i++ над элементами массива заменены эквивалентной операцией *p++. Это значит, что при просмотре массива операция индексирования с линейно изменяющимся индексом может быть замена аналогичным линейным перемещением указателя.
Указатели, массивы, память
Указатель - это инструмент, использование которого отличает системного программиста от прикладного. Специфика Си заключается именно в той свободе, которую дает имеющаяся в нем интерпретация указателя - любой указатель может ссылаться на неограниченный массив, то есть на память программы. Здесь необходимо подробно проанализировать различия и сходство массивов и указателей, рассмотреть "классику" - работу со строками через указатели.
Указатели на функции
Указатель на функцию - отдельно стоящий специфический тип данных, который реализует принцип позднего связывания, то есть позволяет вынести определение переменной части алгоритма на период выполнения программы. Это крамольную мысль, положенную в основу виртуальных функций, DLL, итераторов, здесь и пытаемся донести. В качестве примера используются итераторы - функции, работающие со структурами данных с произвольными элементами, в которых функции обработки хранимых элементов (например, сравнение) вынесены за пределы основного алгоритма.
Указатели на элементы структуры
Если структура имеет несколько элементов одного типа, то для нее может быть создан "внутренний" указатель, который принимает значение внутреннего адреса (смещения) элемента относительно выбранной структуры. Формирование и использование такого указателя ясно из примера:
struct dat
{
int day,month,year;
void Getdat();
void Putdat();
void Nextdat();
};
void main() {
int dat::*p; // Указатель на элемент типа int в структуре dat
p = &dat::month; // Значение p - смещение (адрес) элемента month
// в структуре типа dat
dat x,*px = &x;
x.*p = 5; // Обращение по внутреннему
px->*p = 5; // указателю эквивалентно
// x.month = 5;
} // px->month =5;
Аналогичный внутренний указатель может быть создан для функций-элементов, принадлежащих одной структуре, при этом функции должны быть идентичными по результатам и параметрам:
void (dat::*fun)(); // Указатель на функцию- элемент структуры dat
fun = & dat::Putdat(); // Значение fun - указатель на элемент-функцию
// Putdat в dat
(x.*fun)(); // Вызовы функции-элемента по
(px->*fun)(); // указателю fun эквивалентны
// x.Putdat(); px->Putdat();
Умения и навыки
После изучения дисциплины студент должен приобрести умения и навыки:
· анализа существующих и разработки собственных программ с использованием стандартных фра г ментов алгоритмов;
· подготовки, трансляции и отладки программ в среде Borland C;
· разработки простых программ на Си и использованием базовых типов данных и массивов;
· использования технологии пошагового модульного проектирования программ;
· проектирвоания программ обработки символов и строк;
· решения задач, связанных с приближенными вычислениями с использованием итерационных циклов.
После изучения дисциплины студент должен приобрести умения и навыки:
· разработки модульных программ, использующих сложные иерархические типы данных и переме н ные ;
· использования указателей, структурированных переменных в разрабатываемых программах ;
· распознавания типа переменной по ее контексту ;
· разработки функций, имеющих формальные параметры и результат различных типов ;
· использования динамической памяти при обработке данных заранее неизвестного объема и ра з мерности ;
· разработки программ, использующих данные в произвольном формате ;
· создания алгоритмов обработки отдельных битов и полей машинного слова ;
· разработки программ, использующих массивы указателей и списки для хранения, упорядочения и поиска данных.
После изучения дисциплины студент должен приобрести умения и навыки:
· использования древовидных структур данных в задачах поиска и хранения информации ;
· использования указателей на функцию при проектировании алгоритмов, использующих динамич е ское связывание ;
· проектирования программ, использующих двоичные файлы для размещения различных структур данных с полной и поэлементной их загрузкой в память ;
· разработки резидентных программ и программ, работающих по прерыванию.
После изучения дисциплины студент должен приобрести умения и навыки:
· разработка отдельных классов для внедрения в Си++ собственных типов данных ;
· разработки программы в виде системы классов и взаимодействующих объектов ;
· использования стандартных объектно-ориентированных библиотек для разработки собственных приложений ;
· разработки классов структур данных и шаблонов структур данных, а также использования анал о гичных стандартных средств .
Упаковка данных
Возможность упаковки данных (архивирование) основана на том, что наиболее часто встречающиеся значения могут быть закодированы более короткими последовательностями битов, в то время как редко встречающиеся -длинными. В сумме это и дает эффект сокращения требуемого объема памяти. Самые простые архиваторы заранее "знают" о наиболее часто встречающихся значениях, более сложные -находят их сами. Например, при упаковке текста можно исходить из того, что наиболее вероятны последовательности букв одного регистра и одного алфавита (например, строчные латинские). В следующем примере для представления символа используется 5 битов (32 значения). Значениями 0..25 кодируются сами символы, объединенные в группы (строчные латинские буквы, прописные латинские, цифры и пр.), значениями 26..31 - группы символов. При следовании в тексте подряд двух символов из разных групп в последовательность 5-битовых кодов перед вторым символом добавляется его номер группы. 5-битовые коды упаковываются в массив байтов (8 бит).
//------------------------------------------------------bk48-01.cpp
//------Упаковка символов 5-битным кодом
unsigned char PACK[1000]; // Массив упакованных данных
int np; // Текущий байт
int nb; // Текущий бит
// Добавить бит в последовательность в массиве
void PutBit(int byte)
{
if (nb==0) PACK[np]=0; // Запись нового байта
PACK[np] |= (byte & 1) << nb;
nb = (nb+1) % 8; // Следующий бит
if (nb==0) np++; // Переход в следующий байт
}
// Добавить 5-битный код, начиная с младшего бита
void PutBit_5(int code)
{
int n;
for (n=0; n< 5; n++)
{ PutBit(code); code >>=1; }
}
#define ULAT 26 // Определение кодов групп
#define LLAT 27
#define DIGIT 28
int group; // Текущая группа символов
void PutChar(char c) // Упаковать и добавить символ
{
int code;
int newgroup; // Группа нового символа
if (c >='A' && c<='Z')
{ newgroup = ULAT; code = c - 'A'; }
else
if (c >='a' && c <='z')
{ newgroup = LLAT; code = c - 'a'; }
else
if (c >='0' && c <='9')
{ newgroup = DIGIT; code = c - '0'; }
else
if (c == ' ')
{ newgroup = DIGIT; code = 10; }
if (c == '\0')
{ newgroup = DIGIT; code = 11; }
if (newgroup != group)
{ group = newgroup; PutBit_5(group); }
PutBit_5(Code);
}
void PutString(char s[])
{ int n;
nb = np = n = 0;
do PutChar(s[n]); while(s[n++] !='\0');
}
Упаковка последовательности нулей
В качестве примера рассмортим программу, которая упаковывает массив положительных вещественных чисел, " сворачивая" последовательности подряд идущих нулевых элементов. Формат упакованной последовательности следующий :
-последовательность ненулевых элементов кодируется целым счетчиком (типа int), за которым следуют сами элементы ;
-последовательность нулевых элементов кодируется отрицательным значением целого счетчика ;
-нулевое значение целого счетчика обозначает конец последовательности ;
-пример неупакованной и упакованной последовательностей : 2.2, 3.3, 4.4, 5.5, 0.0, 0.0, 0.0, 1.1, 2.2, 0.0, 0.0, 4.4 и 4, 2.2, 3.3, 4.4, 5.5, -3, 2, 1.1, 2.2, -2, 1, 4.4, 0
В процессе упаковки требуется подсчитывать количество подряд идущих нулей. При этом в выходной последовательности запоминается место расположения последнего счетчика - также в виде указателя. Смена счетчика происходит, если текущий и предыдущий элементы относятся к разным последовательностям (комбинации " нулевой - ненулевой" и наоборот).
//------------------------------------------------------bk44-01.cpp
void pack(int *p, double v[], int n)
{
int *pcnt=p++; // Указатель на последний счетчик
*pcnt=0;
for (int i=0; i<n; i++)
{ // Смена счетчика
if (i!=0 && (v[i]==0 && v[i-1]!=0) ||
v[i]!=0 && v[i-1]==0))
{ pcnt=p++; *pcnt=0; }
if (v[i]==0)
(*pcnt)--; // -1 к счетчику нулевых
else
{
(*pcnt)++ ; // +1 к счетчику ненулевых
*((double*)p)++ = v[i];
}
}
*p++ = 0;
}
Функция распаковки не в пример проще :
//------------------------------------------------------bk44-02.cpp
int unpack(int *p, double v[])
{ int i=0,cnt;
while ((cnt = *p++)!=0) // Пока нет нулевого счетчика
{
if (cnt< 0) // Последовательность нулей
while(cnt++!=0)
v[i++]=0;
else // Ненулевые элементы
while(cnt--!=0) // извлечь с преобразованием
v[i++]=*((double*)p)++;
} // типа указателя
}
Упорядоченные строки
При работе со строками часто возникает необходимость их сравнения в алфавитном порядке. Простейший способ состоит в сравнении кодов символов, что при наличии последовательного кодирования латинских букв и цифр дает гарантию их алфавитного упорядочения (цифры, прописные латинские, строчные латинские).
//------------------------------------------------------bk34-07.cpp
// Функция сравнивает две строки по значениям
// кодов (гарантированный алфавитный порядок для
// латинских букв). Результат сравнения:
// 0 - строки одинаковы,
// -1 - первая строка "меньше" по алфавиту, либо
// строки равны, но первая короче
// 1 - то же самое, но для второй строки
int Compare1(unsigned char s1[],unsigned char s2[])
{
int n;
for (n=0; s1[n]!='\0' && s2[n]!='\0'; n++)
if (s1[n] != s2[n]) break;
if (s1[n] == s2[n]) return 0;
if (s1[n] < s2[n]) return -1 ;
return 1 ;
}
Обратите внимание на то, что массивы символов указаны как беззнаковые. В противном случае коды с весом более 0x80 (символы кириллицы) будут иметь отрицательные значения и располагаться в алфавите "раньше" латинских, имеющих положительные значения кодов (кроме того, еще и в обратном порядке, от Я к А). Чтобы устранить этот и другие недостатки, необходимо установить свой порядок следования символов в алфавите и использовать порядковый номер символа в последовательности (индекс в массиве) в качестве его "веса":
//------------------------------------------------------bk34-08.cpp
//---- Сравнение строк с заданными "весами" символов
char ORD[] = "АаБбВвГгДдЕе1234567890";
int Carry(char c)
{
int n;
if (c=='\0') return(0);
for (n=0; ORD[n]!='\0'; n++)
if (ORD[n]==c) break;
return n+1;
}
int Compare2(char s1[],char s2[])
{
int n;
char c1,c2;
for (n=0; (c1=Carry(s1[n]))!='\0' &&(c2=Carry(s2[n]))!='\0'; n++)
if (c1 != c2) break;
if (c1 == c2) return 0;
if (c1 < c2) return -1;
return 1;
}
УПРАВЛЕНИЕ
В тетрисе задействованы следующие клавиши:
-A - сдвиг влево;
-D - сдвиг вправо;
-W - разворот;
-S - уронить фигуру;
- Esc - Выход из игры;
Условная операция
Условная операция позволяет встроить в любое выражение некоторое подобие условного оператора.
int a;
double b;
c = x + a > b ? a : b;
// Условие ? Выражение для "истина" : Выражение для "ложь"
Операция использует три операнда и два знака операции (?:) . Первым операндом является условие. Если оно истинно, то результатом становится значение второго операнда, если ложно -то третьего. В данном примере вычисляется максимальное значение переменных a,b . Тип результата операции определяется по правилам неявного преобразования типов для второго и третьего операндов. Он будет всегда один и тот же, независимо от выполнения условия. В данном случае -всегда double , так как переменная a будет приведена к этому типу.
Условные операторы
Единственный условный оператор имеет две разновидности:
.
if (выражение) оператор_1 else оператор_2
и if (выражение) оператор_1
Заметим, что выражение может иметь любой целый результат и интерпретируется в соответствии с принятыми в Си соглашениями о логических значениях: 0 -" ложь" , не 0 -" истина" . Круглые скобки являются частью синтаксиса (потому что отсутствует другой ограничитель выражения, например ключевое слово then). Действует он как и во всех языках программирования: если значение выражения есть " истина" , то выполняется первый оператор, если " ложь" -второй (после else ).
В стремлении к совершенству
Нет предела совершенству структур данных. В принципе возможно иерархическое соединение массивов указателей, списков, деревьев. В результате получаются такие вещи как списки с массивами указателей, двухуровневые массивы указателей и тому подобное. Все, что было сказано выше, вплотную подводит к пониманию того, как работает база данных, остается ввести только соответствующую терминологию. Что здесь и делается. Кроме того рассматривается такой оригинальный метод поиска как вычисление адреса записи (хэширование).
Виртуальные функции - как элемент " отложенного" проектирования
Рассмотрим в качестве примера фрагмент класса двоичного файла, в котором обработка ошибок открытия файла вынесена за пределы класса - в производный класс.
// Класс двоичных файлов с " отложенной" функцией обработки ошибок
#include <fstream.h>
typedef int BOOL;
typedef long FPTR; // Тип - указатель в файле
class BinFile : public fstream
{
public:
BOOL Open(char *); // Открыть существующий
virtual int OnError(char *s)
{return NULL}; // Обработка ошибок открытия
// по умолчанию - отказ от дальнейших
}; // попыток открыть файл
BOOL BinFile::Open(char * s)
{
char ss[80];
strcpy(ss,s);
while (1)
{
open(ss,ios::in | ios::out | ios::binary);
if (good()) return 1;
if (!OnError(ss)) // Виртуальная функция в производном классе
return 0; // ищет другое подходящее имя файла
}
return 1;
}
Виртуальная функция в производном классе должна выполнить конкретный диалог, содержание которого в базовом классе не раскрывается. В качестве результата она должна загрузить строку в массив - имя нового файла. Если " пользователь" производного класса предполагает продолжать диалог, он может это сделать например, так
class MyFile : public BinFile
{
public:
virtual int OnError(char *s)
{
cout << " не могу открыть файл " << s << endl;
cout << " введите еще (CR-отказ):" ;
cin >> s;
if (s[0]==0) return 0;
return 1;
}
};
Вложенные прерывания
До сих пор мы рассматривали однократное прерывание. В простейшей схеме его обработки возможность повторного прерывания, пока не произошел выход из предыдущего, исключена полностью :
-при входе в прерывание в процессоре устанавливается состояние запрещения прерывания, которое при отсутствии других действий будет сохраняться вплоть до завершения выхода из прерывания ;
-контроллер прерывания после выдачи номера вектора прерывания блокируется до тех пор, пока не будет выполнена команда outportb(0x20,0x20), разрешающую ему продолжить процесс обслуживания последующих запросов на прерывания;
Очевидно, что если в функции обработки прерываний разрешить процессору вход в прерывание функцией enable() , а также разрешить контроллеру обслуживать запросы, то вход в следующее прерывание может произойти раньше выхода из текущего, то есть произойдет вложенное прерывание. Очевидно также, что вложенное прерывание, как и вложенный вызов функции при наличии механизма стека и обеспечении прозрачности являются вполне допустимыми. Соответственно и вложенные прерывания от различных источников не таят в себе никакой опасности и служат обычно для расстановки приоритетов обработки прерываний различной " срочности" . Другое дело, что может произойти вложенное прерывание от того же самого источника, которое будет рекурсивным. Здесь уже возникают тонкости, связанные с возможностью " бесконечной" рекурсии при снижении скорости обслуживания прерываний и с особенностями самой рекурсии. Обычно уровень вложенности прерываний от одного источника контролируется и ограничивается в таких случаях програмно.
Вопросы без ответов
Определить значения переменных после выполнения действий.
//------------------------------------------------------bk13-01.cpp
//------------------------------------------------- 1
int i1 = 0xFFFF; i1 ++;
//------------------------------------------------- 2
unsigned u1,u2,u; u1 = 5; u2 = -1; u=0; if (u1 > u2) u++;
//------------------------------------------------- 3
int i1 = 0x01FF; char c; c = i1; i1 = c;
//------------------------------------------------- 4
int i1 = 0x01FF;
unsigned char c; c = i1; i1 = c;
//------------------------------------------------- 5
double d1,d2,d3;
d1 = 2.56; d2 = (int)d1 + 1.5;
d3 = (int)(d1 + 1.5);
//------------------------------------------------- 6
double d1 = 2.56; int i; i = (d1 - (int)d1) * 10;
//------------------------------------------------- 7
int i1=20000,i2=20000,s ; // sizeof(int) равен 2
long s1,s2;
s1 = i1 + i2; s2 = (long)i1 + i2;
if (s1 == s2) s=0; else s=1;
//------------------------------------------------- 8
i=0; if (i++) i++;
//------------------------------------------------- 9
i=0; if (++i) i++;
//------------------------------------------------ 10
m = a > b ? a : b;
//------------------------------------------------ 11
m = (a * b) > 0;
//------------------------------------------------ 12
m = a > 0 ? a : -a;
Определить значения переменных после выполнения операторов (по умолчанию все переменные - int) .
//------------------------------------------------------bk14-01.cpp
for (i=0; i< 20; I++);
//------------------------------------------------- 2
for (i=0,j=20; i<j; i++, j--);
//------------------------------------------------- 3
for (n=16,i=0; n!=1; i++, n/=2);
//------------------------------------------------- 4
for (i=0; 0; i++);
//------------------------------------------------- 5
for (i=0, d=0; i< 10; i++, d =!d);
//------------------------------------------------- 6
for (i=0; i>=0; i++);
//------------------------------------------------- 7
i=5; if (i) i++;
//------------------------------------------------- 8
for (i=1,s=1; i<=5; i++) s=s*i;
//------------------------------------------------- 9
for (i=1,s=0; i<=5; i++) s=s+i;
//------------------------------------------------ 10
for (i=0; 1; i++) { if (i==20) break; }
//------------------------------------------------ 11
for (i=0, n=0; i< 10; i++)
{ if (i > 5) continue;
n++;
}
//------------------------------------------------ 12
a = 5; b = 3; c = 1;
switch ((a > b) * 2 + (b > c))
{
case 0: n = c; break;
case 1: n = b; break;
case 2: n = c > a ? c : a; break;
case 3: n = a; break;
}
Содержательно сформулировать результат выполнения фрагмента программы примерно в таком виде: " Последовательный просмотр элементов массива и поиск нулевого" .
//------------------------------------------------------bk15-02.cpp
int d,s,i,A[20]; char c;
//------------------------------------------------- 1
for (s=0,i=0; i< 20; i++) s+=A[i];
//------------------------------------------------- 2
for (d=A[0], i=1; i< 20; i++) if (d > A[i]) d=A[i];
//------------------------------------------------- 3
for (i=0; i< 20 && A[i] !=0; i++);
if (i!=20) A[i]=10000;
//------------------------------------------------- 4
for (d=0, i=0; i>=0; i==19 ? d=!d : 1 , d ? i-- : i++)
{ A[i]; }
//------------------------------------------------- 5
for (i=0, s=0; i< 20; i++)
{ if (A[i]==0) continue; s++; }
//------------------------------------------------- 6
for (i=0; i< 20; i++)
if (A[i]==0) { A[i]=10000; break; }
//------------------------------------------------- 7
for (i=s=0; i< 20; A[i] >=0 ? s++ : s--, i++);
//------------------------------------------------- 8
for (i=s=0; i< 20; i++) s+= A[i] > 0;
//------------------------------------------------- 9
for (s=A[19], i=19; i!=0; i--) A[i]=A[i-1];
A[0]=s;
//------------------------------------------------ 10
d = -1;
switch (c)
{
case '9': d++;
case '8': d++;
// ...
case '0': d++;
};
Определите " смысл" приведенных ниже фрагментов программ и сформулируйте его в виде одной фразы, например : " программа находит максимальное значение из элементов массива A" .
//------------------------------------------------------bk23-01.cpp
// Дальше некуда
//--------------------------------------------------------1
cin >> a >> b;
c=a; a=b; b=c;
//--------------------------------------------------------2
c= A[i]; A[i]=A[i+1]; A[i+1]=c;
//--------------------------------------------------------3
cin >> a >> b;
if (a < b) c = b; else c = a;
//--------------------------------------------------------4
cin >> a >> b;
c = a; if (a > b) c = b;
//--------------------------------------------------------5
if (a < 0) a = -a;
//--------------------------------------------------------6
if (a > 0 ) a = a + 1; else a = a - 1;
//--------------------------------------------------------7
for (i=1; i< 10; i++) ...
// Переменная - минимум/максимум
//---------------------------------------------------------8
for (i=0,s=0; i< 10; i++)
if (A[i]>s) s=A[i];
//---------------------------------------------------------9
for (i=1,k=0; i< 10; i++)
if (A[i]>A[k]) k=i;
//--------------------------------------------------------- 10
for (i=0,k=-1; i< 10; i++)
{ if (A[i]< 0) continue;
if (k==-1) k=i;
else
if (A[i]<A[k]) k=i;
}
//--------------------------------------------------------- 11
for (i=0,k=-1; i< 10; i++)
{ if (A[i]< 0) continue;
if (k==-1 || A[i]<A[k]) k=i;
}
// Переменная - счетчик
//---------------------------------------------------------1 2
for (i=0,s=0; i< 10; i++)
if (A[i]> 0) s++;
//--------------------------------------------------------- 13
for (i=1,s=0; i< 10; i++)
if (A[i]> 0 && A[i-1]< 0) s++;
//---------------------------------------------------------14
Содержательно сформулировать результат выполнения функции, примерно в таком виде: "Функция находит в массиве минимальный элемент и возвращает в качестве результата его индекс". Для этого необходимо формальную запись алгоритма перевести в словесное описание, а затем попытаться сформулировать результат. Если это не получается, то же самое можно попытаться проделать по шагам, либо по нисходящей, либо по восходящей цепочке конструкций языка: перевести конструкцию в словесное описание, сформулировать результат или алгоритм ее выполнения, затем то же самое сделать с учетом вложенной или объемлющей конструкции. То есть использовать ту же технологию структурного проектирования с точностью до наоборот.
//------------------------------------------------------bk32-04.cpp
int F1(int c[], int n)
{ int s,i;
for (s=0, i=0; i<n; i++) s +=c[i]; return s; }
//------------------------------------------------ 2
int F2(int c[], int n)
{ int m,i,k;
for (m=c[0],i=1,k=0; i<n; i++)
if (c[i] > m) { m=c[i]; k=i;}
return k; }
//------------------------------------------------ 3
int F3(int c[], int n)
{ int i,j;
for (i=0; i<n-1; i++)
for (j=i+1; j<n; j++)
if (c[i]==c[j]) return i;
return -1; }
//------------------------------------------------ 4
int F4(int n)
{ int k,m;
for (k=0, m=1; m <= n; k++, m = m * 2);
return k-1; }
//------------------------------------------------ 5
void F5(int c[], int n)
{ int i,j,k;
for (i=0,j=n-1; i < j; i++,j--)
{ k = c[i]; c[i] = c[j]; c[j] = k; }
}
//------------------------------------------------ 6
int F6(int c[], int n)
{ int i,j,k1,k2;
for (i=0; i<n; i++)
{
for (j=k1=k2=0; j<n; j++)
if (c[i] != c[j])
{ if (c[i] < c[j]) k1++; else k2++; }
if (k1 == k2) return i;
}
return -1; }
//------------------------------------------------ 7
int F7(int c[], int n)
{ int i,j,m,s;
for (s=0, i=0; i < n-1; i++)
{
for (j=i+1, m=0; j<n; j++)
if (c[i]==c[j]) m++;
if (m > s) s = m;
}
return s; }
//------------------------------------------------ 8
int F8(int c[], int n)
{ int i,j,k,m;
for (i=k=m=0; i < n-1; i++)
if (c[i] < c[i+1])
k++;
else
{
if (k > m) m=k;
k=0;
}
return m; }
//------------------------------------------------ 9
int F9(int n)
{ int i,k, c[6];
for (i=0; n !=0; i++, n /=10) c[i]=n % 10;
for( k=0; k < i; k++) n = n *10 + c[k];
return n; }
//------------------------------------------------ 10
int F10(int n)
{ int m,k;
for (m=0; n!=0; n /=10)
if ((k = n % 10) > m) m = k;
return m; }
Определить общий вид степенного ряда, вычисляемого в данной функции .
//------------------------------------------------------bk33-05.cpp
double sum(double x,double eps)
{
double s,sn; int n;
for (s=0, sn = 1, n=1; fabs(sn) > eps; n++)
{ s += sn;
sn= - sn * x / n;
}
return s; }
//-----------------------------------------------2
for (s=0, sn = x, n=1; fabs(sn) > eps; n++)
{ s += sn;
sn= - sn * x / n;
}
//-----------------------------------------------3
for (s=0, sn = x; n=1; fabs(sn) > eps; n+=2)
{ s += sn;
sn= sn * x * x / (n*(n+1) );
}
//-----------------------------------------------4
for (s=0, sn = x, n=1; fabs(sn) > eps; n+=2)
{ s += sn;
sn= sn * x / (n *(n+1) );
}
//-----------------------------------------------5
for (s=0, sn = x, n=1; fabs(sn) > eps; n++)
{ s += sn;
sn= sn * x * (2*n) / (2*n-1);
}
//-----------------------------------------------6
for (s=0, sn = x, n=1; fabs(sn) > eps; n+=2)
{ s += sn;
sn= sn * x *x * n / (n+1);
}
//-----------------------------------------------7
for (s=0, sn = x, n=1; fabs(sn) > eps; n++)
{ s += sn;
sn= sn * x * x * (2*n-1) / (2*n+1);
}
//-----------------------------------------------8
for (s=0, sn = x, n=2; fabs(sn) > eps; n+=2)
{ s += sn;
sn= sn * x *x * (n -1) / (n+1);
}
//-----------------------------------------------9
for (s=0, sn = 1, n=1; fabs(sn) > eps; n++)
{ s += sn;
int nn = 2*n-2; if (nn==0) nn=1;
sn= sn * x * x * nn / (2*n);
}
//-----------------------------------------------10
for (s=0, sn = 1, n=1; fabs(sn) > eps; n+=2)
{ s += sn;
int nn = n-1; if (nn==0) nn=1;
sn= sn * x *x * nn / (n+1);
}
//------------------------------------------------------bk34-13.cpp
void F1(char c[])
{ int i,j;
for (i=0; c[i] !='\0'; i++);
for (j=0,i--; i>j; i--,j++)
{ char s; s=c[i]; c[i]=c[j]; c[j]=s; }
}
//------------------------------------------------- 2
int F2(char s)
{ if (s >='0' && s <='9') return(s - '0');
else return(-1); }
//------------------------------------------------- 3
void F3(char c[])
{ int i;
for (i=0; c[i] !='\0'; i++)
if (c[i] >='a' && c[i] <='z')
c[i] += 'A' - 'a';
}
//------------------------------------------------- 4
int F4(char c[])
{ int i,old,nw;
for (i=0, old=0, nw=0; c[i] !='\0'; i++)
{
if (c[i]==' ') old = 0;
else { if (old==0) nw++; old=1; }
if (c[i]== '\0') break;
}
return nw; }
//------------------------------------------------- 5
void F5(char c[])
{ int i,j;
for (i=0, j=0; c[i] !='\0'; i++)
if (c[i] !=' ') c[j++] = c[i];
c[j] = '\0';
}
//------------------------------------------------- 6
void F6(char c[], int nn)
{ int k,mm;
for (mm=nn, k=1; mm !=0; mm /=10 ,k++);
for (c[k--]='\0'; k>=0; k--)
{ c[k]= nn % 10 + '0'; nn /=10; }
}
//------------------------------------------------- 7
int F7(char c[])
{ int i,s;
for (i=0; c[i] !='\0'; i++)
if (c[i] >='0' && c[i]<='7') break;
for (s=0; c[i] >='0' && c[i] <='7'; i++)
s = s * 8 + c[i] - '0';
return s; }
//------------------------------------------------- 8
int F8(char c[])
{ int n,k,ns;
for (n=0,ns=0; c[n] !='\0'; n++)
{
for (k=0; n-k !=0 && c[n+k] !='\0'; k++)
if (c[n-k] != c[n+k]) break;
if (k >=3) ns++;
}
return ns; }
//------------------------------------------------- 9
int F9(char c1[],char c2[])
{ int i,j;
for (i=0; c1[i] !='\0'; i++)
{
for (j=0; c2[j] !='\0'; j++)
if (c1[i+j] != c2[j]) break;
if (c2[j] =='\0') return i;
}
return -1;}
//------------------------------------------------ 10
char F10(char c[])
{ char m,z; int n,s,i;
for (s=0,m='A'; m <='Z'; m++)
{
for (n=0, i=0; c[i] !='\0'; i++)
if (c[i]==m) n++;
if (n > s) { z=m; s=n; }
}
return z; }
//------------------------------------------------ 11
void F11(char c[], double x)
{ int i;
for (c[0]='.',i=1; i< 6; i++)
{
x *= 10.; c[i]=(int)x + '0'; x -= (int)x;
}
c[i]='\0'; }
//------------------------------------------------ 12
int F12(char c[])
{
for (int i=0; c[i]!=0; i++)
{
if (c[i]==' ') continue;
for (int j=i+1; c[j]==c[i]; j++);
for (; c[j]!=0; j++)
{
for (int k=0; i+k<j && c[i+k]==c[j+k]; k++);
if (k>=4) return i;
}
}
}
По тексту программы определите алгоритм сортировки.
//------------------------------------------------------bk35-05.cpp
void F1(int in[],int n)
{ int i,j,k,c;
for (i=1; i<n; i++)
{ for (k=i; k !=0; k--)
{ if (in[k] > in[k-1]) break;
c=in[k]; in[k]=in[k-1]; in[k-1]=c;
}
} }
//------------------------------------------------- 2
void F2(int in[],int out[],int n)
{ int i,j ,cnt;
for (i=0; i< n; i++)
{
for ( cnt=0,j=0; j<n; j++)
if (in[j] > in[i]) cnt++;
else
if (in[j]==in[i] && j>i) cnt++;
out[cnt]=in[i];
} }
//------------------------------------------------- 3
void F3(int in[],int n)
{ int a,b,dd,i,lasta,lastb,swap;
for (a=lasta=0, b=lastb=n, dd=1; a < b;
dd = !dd, a=lasta, b=lastb)
{
if (dd)
{
for (i=a,lastb=a; i<b; i++)
if (in[i] > in[i+1])
{ lastb = i;
swap = in[i]; in[i]=in[i+1]; in[i+1]=swap;
}
}
else
{
for (i=b,lasta=b; i>a; i--)
if (in[i-1] > in[i])
{ lasta = i;
swap = in[i]; in[i]=in[i-1]; in[i-1]=swap;
} } } }
//------------------------------------------------- 4
int find(int out[],int n, int val);
// Двоичный или линейный поиск расположения значения val
// в массиве out[n]
void F4(in,n)
int in[],n;
{
int i,j,k;
for (i=1; i<n; i++)
{ int c; c = in[i]; k = find(in,i,c);
for (j=i; j!=k; j--) in[j] = in[j-1];
in[k] = c; }
}
//------------------------------------------------- 5
void F5(int in[], int n)
{ int i,j,c,k;
for (i=0; i < n-1; i++)
{
for (j=i+1,c=in[i],k=i; j<n; j++)
if (in[j] > c) { c = in[j]; k=j; }
in[k] = in[i]; in[i] = c;
} }
//------------------------------------------------- 6
#define MAXINT 0x7FFF
void F6(int in[], int n, int v0[], int v1[])
{
int m,i,max,i0,i1;
for (i=0, max=0; i<n; i++)
if (in[i] > max) max=in[i];
for (m=1; m <=max; m <<=1);
for (m >>=1; m !=0; m >>=1)
{
for (i0=i1=0; i0+i1 < n; )
if ((in[i0+i1] & m) ==0)
v0[i0] = in[i0+i1], i0++;
Дайте определение нестандартным операциям со стеком и очередью, представленным функциями.
//------------------------------------------------------bk36-04.cpp
int sp, LIFO[100];
int lst,fst,FIFO[100];
//------------------------------------------------- 1
void F1()
{ int c; if (sp < 1) return;
c = LIFO[sp]; LIFO[sp]=LIFO[sp-1]; LIFO[sp-1]=c;
}
//------------------------------------------------- 2
int F2(int n)
{ int v,i;
if (sp < n) return (0);
v = LIFO[sp-n];
for (i=sp-n; i<sp; i++) LIFO[i]=LIFO[i+1];
sp--; return (v);}
//------------------------------------------------- 3
void F3(){ LIFO[sp+1]=LIFO[sp]; sp++; }
//------------------------------------------------- 4
int F4(int n)
{ int v,i1,i2;
i1 = (fst+n) %100;
v = FIFO[i1];
for (; i1!=lst; i1 = i2)
{
i2 = (i1 + 1) % 100;
FIFO[i1]=FIFO[i2];
}
lst = --lst % 100;
return(v);}
//------------------------------------------------- 5
void F5()
{ int n;
if (fst==lst) return;
n = (lst-1) %100;
FIFO[lst]=FIFO[n];
lst = ++lst % 100;}
//------------------------------------------------------bk41-02.cpp
void F1(int *p1, int *p2)
{ int c;
c = *p1; *p1 = *p2; *p2 = c;
}
//------------------------------------------------------- 2
void F2(int *p, int *q, int n)
{
for (*q = *p++; n > 0; n--, p++)
if (*p > *q) *q = *p;
}
//------------------------------------------------------- 3
int *F3(int *p, int n)
{ int *q;
for (q = p; n > 0; n--, p++)
if (*p > *q) q = p;
return q;
}
//--- Указатели на строки (char*) ----------------------- 4
void F4(char *p)
{ char *q;
for (q=p; *q !='\0'; q++);
for (q--; p < q; p++, q--)
{ char c; c = *p; *p = *q; *q = c; }
}
//------------------------------------------------------- 5
int F5(char *p)
{ int n;
for (n=0; *p !='\0'; p++, n++);
return n;
}
//------------------------------------------------------- 6
char *F6(char *p,char *q)
{ char *s1,*s2;
for (; *p!='\0'; p++)
{
for (s1=p, s2=q; *s2!='\0' && *s1==*s2; s1++,s2++);
if (*s2 == '\0') return p;
}
return NULL;
}
//------------------------------------------------------- 7
void F7(char *p, char *q)
{
for (; *p !='\0'; p++);
for (; *q !='\0'; *p++ = *q++);
*q = '\0';
}
//------------------------------------------------------- 8
int F8(char *p)
{ int n;
if (*p=='\0') return (0);
if (*p !=' ') n=1; else n=0;
for (p++; *p !='\0'; p++)
if (p[0] !=' ' && p[-1]==' ') n++;
return n; }
//------------------------------------------------------- 9
void F9(char *p)
{ char *q; int n;
for (n=0, q=p; *p !='\0'; p++)
{
if (*p !=' ')
{ n=0; *q++ = *p; }
else
{ n++; if (n==1) *q++ = *p; }
}
}
//------------------------------------------------------ 10
void F10(char *p)
{ char *q; int cm;
for (q=p,cm=0; *p !='\0'; p++)
{
if (p[0]=='*' && p[1]=='/') { cm--, p++; continue; }
if (p[0]=='/' && p[1]=='*') { cm++, p++; continue; }
if (cm==0) *q++ = *p;
}
*q=0;
}
Определить значения переменных после выполнения действий .
//------------------------------------------------------bk42-01.cpp
union x // sizeof(int) = 2
{ // sizeof(long)= 4
char c[4];
int n[2];
unsigned char u[4];
long l;
}
UNI;
struct man
{
char name[20];
int dd,mm,yy;
char *zodiak;
}
A= { "Иванов",1,10,1969,"Весы" }, B[10], *p;
//------------------------------------------------------- 1
void F1() {
char c; int i;
for (i=0; i< 10; i++)
B[i].zodiak = "abcdefghij" + i;
c = B[1].zodiak[2]; }
//------------------------------------------------------- 2
void F2() {
char c; int i,j;
for (i=0; i< 10; i++)
{
for (j=0; j< 10; j++)
B[i].name[j] = 'a' + i + j;
B[i].name[j]='\0';
}
c = B[1].zodiak[2]; }
//------------------------------------------------------ 3
void F3() {
int i,n;
for (i=0; i< 10; i++)
B[i].dd = i;
for (p=B, n=5; n!=0; n--, p++)
n += p->dd; }
//------------------------------------------------------ 4
void F4() {
char c; int i;
for (i=0; i< 10; i++)
B[i].zodiak = A.zodiak + i % 4;
c = B[5].zodiak[2]; }
//------------------------------------------------------ 5
void F5() {
int i,n; char *p;
for (i=0; i< 10; i++)
B[i].zodiak = "abcdefghij" + i;
for (n=0, p=B[6].zodiak; *p !='\0'; p++, n++); }
//----------------------------------------------------- 6
void F6() {
long s; int i;
for (i=0; i< 4; i++) UNI.c[i]='0'+i;
s = UNI.l; }
//----------------------------------------------------- 7
void F7() {
char z;
UNI.l = 0x00003130;
z = UNI.c[1]; }
//----------------------------------------------------- 8
void F8() {
long s; char z;
UNI.l = 0x0000FFFF;
z = UNI.c[1]; UNI.c[1]=UNI.c[2]; UNI.c[2]=z;
s = UNI.l; }
//----------------------------------------------------- 9
void F9() {
long s;
UNI.l = 0x0001FFFF;
UNI.n[0] >>=2;
s = UNI.l; }
//---------------------------------------------------- 10
void F10() {
long s;
UNI.l = 0x0001FFFF;
UNI.c[1] <<=2;
s = UNI.l; }
//---------------------------------------------------- 11
void F11() {
long s;
UNI.l = 0x0001FFFF;
UNI.u[1] >>=2;
s = UNI.l; }
//---------------------------------------------------- 12
void F12() {
long s; int i,m, cr;
UNI.l = 0x00010000;
for (i=0; i< 4; i++) UNI.c[i] = ~UNI.c[i];
for (cr=1, i=0; i< 4; i++)
{
m = UNI.c[i] + cr;
UNI.c[i] = m;
cr = (m & 0x100) !=0;
}
s = UNI.l; }
Определите значения переменных после выполнения действий .
//------------------------------------------------------bk43-01.cpp
#include <stdio.h>
struct man1
{
char name[20];
int dd,mm,yy;
char *zodiak;
struct man1 *next;
}
A1= {"Петров",1,10,1969,"Весы",NULL },
B1= {"Сидоров",8,9,1958,"Дева",&A1 },
*p1 = &B1;
void F1() {
char c1,c2,c3,c4;
c1 = A1.name[2]; c2 = B1.zodiak[3];
c3 = p1->name[3]; c4 = p1->next->zodiak[1];
}
//------------------------------------------------------- 2
struct man2
{
char name[20];
char *zodiak;
struct man2 *next;
} C2[3] = {
{"Петров","Весы",NULL },
{"Сидоров","Дева",&C2[0] },
{"Иванов","Козерог",&C2[1] }
};
void F2() {
char c1,c2,c3,c4;
c1 = C2[0].name[2];
c2 = C2[1].zodiak[3];
c3 = C2[2].next->name[3];
c4 = C2[2].next->next->zodiak[1];
}
//------------------------------------------------------- 3
struct tree3
{
int vv;
struct tree3 *l,*r;
}
A3 = { 1,NULL,NULL },
B3 = { 2,NULL,NULL },
C3 = { 3, &A3, &B3 },
D3 = { 4, &C3, NULL },
*p3 = &D3;
void F3() {
int i1,i2,i3,i4;
i1 =A3.vv; i2 = D3.l->vv;
i3 =p3->l->r->vv; i4 = p3->vv;
}
//------------------------------------------------------- 4
struct tree4
{
int vv;
struct tree4 *l,*r;
}
F[4] =
{{ 1,NULL,NULL },
{ 2,NULL,NULL },
{ 3, &F[0], &F[1] },
{ 4, &F[2], NULL }};
void F4() {
int i1,i2,i3,i4;
i1 = F[0].vv; i2 = F[3].l->vv;
i3 = F[3].l->r->vv; i4 = F[2].r->vv;
}
//------------------------------------------------------- 5
struct list5
{
int vv;
struct list5 *pred,*next;
};
extern struct list5 C5,B5,A5;
struct list5 A5 = { 1, &C5, &B5 },
B5 = { 2, &A5, &C5 },
C5 = { 3, &B5, &A5 },
*p5 = &A5;
void F5() {
int i1,i2,i3,i4;
i1 = A5.next->vv; i2 = p5->next->next->vv;
Определить способ размещения последовательности переменных в общей области памяти, которая читается или заполняется функцией (формат структуры данных).
//------------------------------------------------------bk44-03.cpp
struct man {char name[20]; int dd,mm,yy; char *addr; };
char *F1(char *p, char *nm, char *ad)
{ man *q =(man*)p;
strcpy(q->name,nm);
strcpy((char*)q+1,ad);
q->addr = (char*)q+1;
for (p=(char*)q+1; *p!=0; p++);
p++;
return p;}
//------------------------------------------------------ 2
struct man1 {char name[20]; int dd,mm,yy; char addr[]; };
char *F2(char *p, char *nm, char *ad)
{ man1 *q =(man1*)p;
strcpy(q->name,nm);
strcpy(q->addr,ad);
for (p=q->addr; *p!=0; p++);
p++;
return p;}
//------------------------------------------------------ 3
int *F3(int *q, char *p[])
{ int i,j;
char *s;
for (i=0; p[i]!=NULL; i++);
*q = i;
for (s = (char*)(q+1), i=0; p[i]!=NULL; i++)
{
for (j=0; p[i][j]!='\0'; j++) *s++ = p[i][j];
*s++ = '\0';
}
return (int*)s;}
//------------------------------------------------------- 4
double F4(int *p)
{ double *q,s; int m;
for (q=(double*)(p+1), m=*p, s=0.; m>=0; m--) s+= *q++;
return s;}
//------------------------------------------------------- 5
char *F5(char *s, char *p[])
{ int i,j;
for (i=0; p[i]!=NULL; i++)
{
for (j=0; p[i][j]!='\0'; j++) *s++ = p[i][j];
*s++ = '\0';
}
*s = '\0';
return s;}
//------------------------------------------------------- 6
union x {int *pi; long *pl; double *pd;};
double F6(int *p)
{ union x ptr;
double dd=0;
for (ptr.pi=p; *ptr.pi !=0; )
switch (*ptr.pi++)
{
case 1: dd += *ptr.pi++; break;
case 2: dd += *ptr.pl++; break;
case 3: dd += *ptr.pd++; break;
}
return dd;}
//------------------------------------------------------- 7
unsigned char *F7(unsigned char *s, char *p)
{ int n;
for (n=0; p[n] != '\0'; n++);
*((int*)s)++ = n;
for (; *p != '\0'; *s++ = *p++);
s++;
return s;}
//------------------------------------------------------- 8
int *F8(int *p, int n, double v[])
Для функции с переменным количеством параметров определите их последовательность (формат) и приведите пример вызова.
//------------------------------------------------------bk45-03.cpp
void F1(char *p,...)
{ char **q;
for (q = &p; *q !=NULL; q++) cout << *q; }
//--------------------------------------------------------2
void F2(int *p,...)
{ int **q, i, d;
for (i=1, q = &p, d=*p; q[i]!=NULL; i++)
*q[i-1] = *q[i];
*q[i-1] = d;}
//--------------------------------------------------------3
int *F3(int *p,...)
{ int **q, i, *s;
for (i=1, q = &p, s=p; q[i]!=NULL; i++)
if (*q[i] > *s) s = q[i];
return s;
}
//--------------------------------------------------------4
int F4(int p[], int a1,...)
{ int *q, i;
for (i=0, q = &a1; q[i] > 0; i++)
p[i] = q[i];
return i;}
//--------------------------------------------------------5
union x { int *pi; long *pl; double *pd; };
void F5(int p,...)
{ union x ptr;
for (ptr.pi = &p; *ptr.pi != 0; )
{
switch(*ptr.pi++)
{
case 1: cout << *ptr.pi++; break;
case 2: cout << *ptr.pl++; break;
case 3: cout << *ptr.pd++; break;
}}}
//--------------------------------------------------------6
char **F6(char *p,...)
{ char **q,**s;
int i,n;
for (n=0, q = &p; q[n] !=NULL; n++);
s = new char*[n+1];
for (i=0, q = &p; q[i] !=NULL; i++) s[i]=q[i];
s[n]=NULL;
return s;}
//--------------------------------------------------------7
char *F7(char *p,...)
{ char **q; int i,n;
for (i=0, n=0, q = &p; q[i] !=NULL; i++)
if (strlen(q[i]) > strlen(q[n])) n=i;
return q[n];
}
//--------------------------------------------------------8
int F8(int a1,...)
{ int *q, i, s;
for (s=0, i=0, q = &a1; *q > 0; q++)
s+= *q;
return s;}
//--------------------------------------------------------9
union xx { int *pi; long *pl; double *pd; };
double F9(int p,...)
{ union xx ptr;
double dd=0;
for (ptr.pi = &p; *ptr.pi != 0; )
//------------------------------------------------------bk47-04.cpp
char *F1(char *s)
{ char *p,*q; int n;
for (n=0; s[n] !='\0'; n++);
p = new char[n+1];
for (q=p; n >=0; n--) *q++ = *s++;
return p;
}
//------------------------------------------------------- 2
int *F2()
{ int n,i,*p;
cin >> n;
p=new int[n+1];
for (p[0]=n, i=0; i<n; i++)
cin >> p[i+1];
return p; }
//------------------------------------------------------- 3
int *F3()
{ int n,i,*p;
cin >> n;
p=new int[n+1];
for (i=0; i<n; i++)
{ cin >> p[i]; if (p[i]&# 60 0) break; }
p[i]=-1;
return p; }
//------------------------------------------------------- 4
char *F4(char *p, char *q)
{ int n1, n2;
for (n1=0; p[n1]!=0; n1++);
for (n2=0; p[n2]!=0; n2++);
char *s,*v;
s=v=new char[n1+n2+1];
while(*p!=0) *s++ = *p++;
while(*q!=0) *s++ = *q++;
*s=0;
return v; }
//------------------------------------------------------- 5
double *F5(int n, double v[])
{
double *p=new double[n+1];
p[0]=n;
for (int i=0; i<n; i++) p[i+1]=v[i];
return p;
}
//------------------------------------------------------- 6
int *F6()
{ int *p, n=10,i;
p=new int[n];
for (i=0;;i++)
{
if (i==n) { n=n*2; p=(int*)realloc(p,sizeof(int)*n); }
cin >> p[i];
if (p[i]==0) break;
}
return p;}
//------------------------------------------------------- 7
void *F7(void *p, int n)
{ char *pp, *qq, *ss;
qq = ss = new char [n];
for (pp= (char*)p; n!=0; n--) *pp++ = *qq++;
return ss;}
//------------------------------------------------------- 8
int *F8(int n)
{
int s,i,m,k,*p;
s = 10; p = new int[s];
for (i=2, m=0; i<n; i++)
{
for (k=0; k<m; k++)
if (i % p[k]==0) break;
if (k==m)
{ p[m++] = i;
if (m==s)
{
s=s*2;
p= (int*)realloc( (void*)p,sizeof(int)*s);
}}}
return p; }
В первой половине заданий необходимо определить значения переменных после выполнения поразрядных операций. Учтите, что заданные маски соответствуют восьмеричным или шестнадцатеричным цифрам.
//------------------------------------------------------bk48-06.cpp
int i,j; long l; char c; double d;
// Примечание: - sizeof(int) равен 2
// - sizeof(long) равен 4
// - "~" - операция побитовой инверсии
//------------------------------------------------ 1
i = 0x5678;
l = (i & ~0x00F0) | 0x0010;
c = (l >> 4) & 0xF + '0';
j = (i & 0xFF0F) | (~i & 0x00F0);
//------------------------------------------------ 2
i = 1; j = 2; c = 3;
l = (j > i) + (j==c) << 1 + (i !=c) << 2;
//------------------------------------------------ 3
for (l=1,i=0; l > 0; l<<=1, i++);
//------------------------------------------------ 4
for (l=1,i=0; l !=0; l<<=1, i++);
//------------------------------------------------ 5
i = 1; j = 3; c = 2;
l = i + (j << 4) + (c << 8 );
c = i << 8;
j = j << j;
//------------------------------------------------ 6
int F6(long n)
{ int i,s;
for (i=0,s=0; i < sizeof(long) * 8; i++)
{ if (n & 1) s++; n >>=1; }
return s; }
//------------------------------------------------ 7
long F7(long n)
{ int i; long s;
for (i=s=0; i < sizeof(long) * 8; i++)
{ s <<=1; s |= n & 1; n >>=1; }
return s; }
//------------------------------------------------ 8
long F8(long n, int m1, int m2)
{ long s,x; int i;
for (i=0,x=1,s=n; i < sizeof(long)*8; i++)
{ if (i >=m1 && i <=m2) s |= x; x <<=1; }
return s; }
//------------------------------------------------ 9
int F9(char c[])
{ int i,s;
for (i=0; c[i] !='\0'; i++)
if (c[i] >='0' && c[i] <='7') break;
for (s=0; c[i] >='0' && c[i] <='7'; i++)
{ s <<=3; s |= c[i] & 0x7; }
return s; }
//------------------------------------------------ 10
void F10(char c[],long n)
{ int i;
i=sizeof(long)*8/3 +1;
for (c[i--]='\0'; i>=0; i--)
{ c[i] = n & 0x7 + '0'; n >>=3; }
}
//------------------------------------------------ 11
// Операция "^" - ИСКЛЮЧАЮЩЕЕ ИЛИ
int F11(long n)
{ int i,m,k;
for (i=m=k=0; i < sizeof(long) * 8; i++, n >>= 1)
if ((n & 1) ^ m)
{ k++; m =!m; }
return k; }
//------------------------------------------------ 12
int F12(long n)
{ int i,m,k;
for (i=m=k=0; i < sizeof(long) * 8; i++, n >>= 1)
if (n & 1)
k++;
else
{ if (k > m) m=k; k=0; }
return m; }
//------------------------------------------------------bk52-05.cpp
int F1(double *p[])
{ int n;
for (n=0; p[n]!=NULL; n++);
return(n); }
//------------------------------------------------------- 2
void F2(double *p[])
{ int i,k;
do {
k=0;
for (i=1; p[i]!=NULL; i++)
if (*p[i-1] > *p[i])
{ double *dd;
dd=p[i]; p[i]=p[i-1]; p[i-1]=dd; k++;
}
} while k;}
//------------------------------------------------------ 3
void F3(double *p[], double *q)
{ int i,n;
for (i=0; p[i]!=0; i++)
if (*p[i] > *q) break;
for (n=i; p[n]!=NULL; n++);
for (; n >=i; n--)
p[n+1] = p[n];
p[i] = q;}
//------------------------------------------------------ 4
int F4(double **p[])
{ int k,i,j;
for (k=i=0; p[i]!=NULL; i++)
for (j=0; p[i][j] !=NULL; j++, k++);
return k;}
//------------------------------------------------------ 5
char **F5(char a[][80], int n)
{ int i; double **p;
p = new char* [n+1];
for (i=0; i<n; i++) p[i]=a[i];
p[n]=NULL;
return p;}
//------------------------------------------------------ 6
// strlen(char *) - длина строки
char *F6(char *p[])
{ int i,sz,l,k;
for (i=sz=k=0; p[i]!=NULL; i++)
if ((l=strlen(p[i])) >sz) { sz=l; k=i; }
return(p[k]); }
//------------------------------------------------------ 7
char **F7(char c[])
{ char **p; int i,n, cnt;
p = new char*[20];
for (i=n=cnt=0; c[n]!=0; n++)
{
if (c[n]==' ')
{ c[n]='\0'; cnt=0; }
else
{ cnt++;
if (cnt==1) p[i++]=&c[n];
if (i==19) break;
}
}
p[i]=NULL; return(p);}
//------------------------------------------------------ 8
char *F8(char *p[], int m)
{ int n; char *q;
for (n=0; p[n]!=NULL; n++);
if (m >=n) return (NULL);
q = p[m];
for (n=m; p[n]!=NULL; n++) p[n]=p[n+1];
return q;}
//------------------------------------------------------ 9
// strcmp(char*,char*) - сравнение строк
int F9(char *p[], char *str)
{ int h,l,m;
for (h=0; p[h]!=NULL; h++);
for (h--,l=0; h >= l;)
{
m = (l+h) / 2;
switch(strcmp(p[m],str))
{
case 0: return(m);
case -1: l = m+1; break;
case 1: h = m-1; break;
//------------------------------------------------------bk53-07.cpp
struct list
{
int val;
list *next,*pred;
};
//------------------------------------------------------- 1
int F1(list *p)
{ int n;
for (n=0; p!=NULL; p=p->next, n++);
return(n);
}
//------------------------------------------------------- 2
list *F2(list * p h, int v)
{ struct list *q;
q = new list;
q->val = v; q->next = p h; p h = q;
return ph;
}
//------------------------------------------------------- 3
list *F3(list *p, int n)
{
for (; n!=0 && p!=NULL; n--, p=p->next);
return p;
}
//------------------------------------------------------- 4
list *F4(list *p h, int v)
{ list *q ,*p;
q = new list;
q->val = v; q->next = NULL;
if (ph == NULL) return q;
for ( p=ph ; p ->next !=NULL; p = p->next);
p ->next = q;
return ph;
}
//------------------------------------------------------- 5
list *F5(list *p h, int n)
{ list *q ,*pr,*p;
for ( p=ph,pr=NULL; n!=0 && p!=NULL; n--, pr=p, p =p->next);
if (p==NULL) return ph;
if (pr==NULL) { q=ph; ph=ph->next; }
else { q=p; pr->next=p->next; }
delete q;
return ph;
}
//------------------------------------------------------- 6
int F6(list *p)
{ int n; list *q;
if (p==NULL) return(0);
for (q = p, p = p->next, n=1; p !=q; p=p->next, n++);
return n;
}
//------------------------------------------------------- 7
list *F7(list *p, int v)
{ list *q;
q = new list;
q->val = v; q->next = q->pred = q;
if (p == NULL) p = q;
else
{
q->next = p; q->pred = p->pred;
p->pred->next = q; p->pred = q; p=q;
}
return p;
}
//------------------------------------------------------- 8
list *F8(list *ph)
{ list *q, *tmp, **pp;
tmp = NULL;
while (ph !=NULL)
{
q = ph; ph = ph->next;
for (pp = &tmp; *pp !=NULL && (*pp)->val < q->val;
pp = &(*pp)->next);
//------------------------------------------------------bk54-06.cpp
long F(int n)
{ if (n==1) return (1);
return (n * F(n-1)); }
//------------------------------------------------------ 2
struct xxx { int val; xxx *next; };
void F( xxx **p, int v)
{ xxx *q;
if (*p !=NULL)
F( &((*p)->next) ,v );
else
{ q = malloc(sizeof( xxx));
q->val = v; q->next = NULL; *p = q;
}
}
//------------------------------------------------------ 3
double F(double *pk, double x, int n)
{
if (n==0) return(*pk);
return( *pk + x *F(pk+1,x,n-1));
}
void z3()
{
double B[] ={ 5.,0.7,4.,3. } ,X=1., Y; // Пример вызова
Y = F(B,X,4); }
//------------------------------------------------------ 4
void sort(int in[], int a, int b)
{ int i,j,mode;
if (a==b) return;
for (i=a, j=b, mode=1; i != j; mode > 0 ? i++ : j--)
if (in[i] > in[j])
{ int c;
c = in[i]; in[i] = in[j]; in[j]=c; mode = -mode;
}
sort(in,a,i-1); sort(in,i+1,b);
}
//------------------------------------------------------ 5
char *F(char *p, char *s)
{
if ( *s =='\0') return p;
*p++ = *s;
p=F(p, s+1);
*p++ = *s;
return p;
}
void z6()
{ char *q, S[80];
F(S, "abcd"); // Пример вызова
}
//------------------------------------------------------ 6
void F(char **p, char *s)
{
if ( *s =='\0') return;
*(*p)++ = *s;
F(p, s+1);
*(*p)++ = *s;
}
void z7()
{ char *q, S[80];
q = S; F(&q,"abcd"); // Пример вызова
}
//------------------------------------------------------ 7
struct xxx { int val; xxx *next; };
int F( xxx *p)
{ int zzz;
if (p ==NULL) return 0;
zzz = F(p->next));
if (zzz > p->val) return zzz;
return p->val;
}
//------------------------------------------------------ 8
struct xxx { int val; xxx *p[10]; };
int F( xxx *p)
{ int zzz,i, rrr;
if (p==NULL) return 0;
for (i=0, zzz = p->val; i< 20; i++)
if ((rrr=F(p->p[i])) > zzz) zzz = rrr;
return zzz;
}
//------------------------------------------------------ 9
struct xxx { int val; xxx *p[10]; };
int F( xxx *p)
{ int zzz,i, rrr;
if (p==NULL) return 0;
for (i=0, zzz = 0; i< 20; i++)
if ((rrr=F(p->p[i])) > zzz) zzz = rrr;
return zzz+1;
}
//------------------------------------------------------ 10
void F(int p[], int nn)
{ int i;
if (nn==1) { p[0]=0; return; }
for (i=2; nn % i !=0; i++);
p[0]=i;
F(p+1,nn / i);
}
//------------------------------------------------------bk55-11.cpp
struct xxx { int v; xxx *p[4]; };
int F(xxx *q)
{
int i,n,m;
if (q==NULL) return 0;
for (n=F1(q->p[0]),i=1; i< 4; i++)
if ((m=F(q->p[i])) >n) n=m;
return n+1;
}
//-------------------------------------------------------2
struct xxx { int v; xxx *l,*r; };
int F( xxx *p)
{
if (p==NULL) return(0);
return (1 + F(p->r) + F(p->l));
}
//-------------------------------------------------------- 3
struct xxx { int v; xxx *p[4]; };
int F(xxx *q)
{
int i,n,m;
if (q==NULL) return 0;
for (n=q->v,i=0; i< 4; i++)
if ((m=F(q->p[i])) >n) n=m;
return n;
}
//-------------------------------------------------------- 4
void F(int a[], int n, int v)
{
if (a[n] ==-1) { a[n]=v; return; }
if (a[n]==v) return;
if (a[n] >v)
F(a,2*n,v);
else
F(a,2*n+1,v);
}
void z3() {
int B[256],i;
for (i=0; i< 256; i++) B[i]=-1;
F(B,1,5); F(B,1,3); } // Пример вызова
//------------------------------------------------------- 5
struct xxx { int v; xxx *p[4]; };
int F(xxx *q)
{
int i,n,m;
if (q==NULL) return 0;
for (n=q->v,i=0; i< 4; i++)
n+=F(q->p[i]);
return n;
}
//------------------------------------------------------- 6
struct xxx { int k; int v[10]; xxx *l,*r; };
int F(xxx *q)
{
int i,n,m;
if (q==NULL) return 0;
for (n=0,i=0; i<k; i++)
n+=q->v[i]);
return n+F(q->l)+F(q->r);
}
//------------------------------------------------------- 7
struct xxx { int v; xxx *p[4]; };
int F(xxx *q)
{
int i,n,m;
if (q==NULL) return 0;
for (n=1,i=0; i< 4; i++)
n+=F(q->p[i]);
return n;
}
//-------------------------------------------------------- 8
struct xxx { int v; xxx *l,*r; };
int F( xxx *p)
{
if (p==NULL) return(0);
int nr=F(p->r)+1;
int nl=F(p->l)+1;
return nr>nl ? nr : nl;
}
//------------------------------------------------------- 9
struct xxx { int v; xxx *p[4]; };
int F(xxx *q)
{
int i,n,m;
if (q==NULL) return -1;
if (q->v >=0) return q->v;
for (n=1,i=0; i< 4; i++)
if ((m=F(q->p[i] )) !=-1) return m;;
return -1;
}
Примеры 1- 5 содержат итерационные циклы, используемые в приближенных вычислениях. Содержательно определить смысл алгоритма и назначение указателя на функцию. В остальных примерах определить результат.
//------------------------------------------------------bk56-04.cpp
double F1(double a, double b, double (*pf)(double))
{ double m;
if ((*pf)(a) * (*pf)(b) > 0 )return(a);
while ( b-a > 0.0001 )
{
m = (b + a)/2;
if ((*pf)(a) * (*pf)(m) < 0) b=m; else a=m;
}
return a;
}
//------------------------------------------------------- 2
double F2(double x, double s0, double (*pf)(double,int))
{ double s; int n;
for (n=1, s=0.0; fabs(s0) > 0.0001; n++)
{ s += s0; s0 = s0 * (pf)(x,n); }
return s; }
double ff(double x, int n) { return( x/n); }
void main()
{ double x,y; y = F2(x, 1, ff); }
//------------------------------------------------------- 3
double F(double a, double b, double (*pf)(double))
{ double dd;
for (dd = 0.0001; b-a > dd;)
if ((*pf)(a) > (*pf)(b)) b -=dd; else a +=dd;
return a;
}
//------------------------------------------------------- 4
double F4(double x, double (*pf)(double))
{ double x1;
do {
x1 = x; x = (*pf)(x1) + x1;
if (fabs(x) > fabs(x1)) return(0.0);
}
while (fabs(x1-x) > 0.0001);
return x;
}
//------------------------------------------------------- 5
double F5(double a, double b, int n, double(*pf)(double))
{ double s,h,x;
for (s=0., x=a, h = (b-a)/n; x <=b; x+=h)
s += (*pf)(x) * h;
return s;}
extern double sin(double);
void main() { cout << F5(0.,1.,40,sin)); }
//------------------------------------------------------- 6
void ( *P(void(*ff)(void)))(void) { return ff; }
void foo(void){ cout << "I'm foo" << endl; }
void main(){(*P(foo))();}
//------------------------------------------------------- 7
int ( *P(int(*ff)(int)))(int) { return ff; }
int inc(int n){ return n+1; }
void main(){ cout << (*P(inc))( 5);}
//------------------------------------------------------- 8
typedef void (*PF)(void);
PF P(PF ff) { return ff; }
void foo(void){ cout << "I'm foo" << endl; }
void main(){(*P(foo))();}
//------------------------------------------------------- 9
typedef int (*PF)(int);
PF P(PF ff) { return ff; }
int inc(int n){ return n+1; }
void main(){ cout << (*P(inc))( 7);}
//------------------------------------------------------ 10
double P(double(*ff [])(double) ,int n, double x )
{ return (*ff [n])(x); }
double(*FF [])(double) ={sin,cos,tan};
void main(){ cout << P( FF,1,0.5);}
//------------------------------------------------------ 11
typedef double(*PF)(double) ;
double P( PF ff [],int n, double x )
{ return (*ff [n])(x); }
PF FF []={sin,cos,tan};
void main(){ cout << P( FF,2,1.5);}
Определить вид итератора и структуру данных, с которой он работает
//------------------------------------------------------bk57-04.cpp
struct xxx { void *data; xxx *next; };
void F( xxx **p, void (*pf)(void*))
{
xxx *q;
for (; *p != NULL; p++)
for (q = *p; q != NULL; q = q->next)
(*pf)(q->data);
}
//-------------------------------------------------------- 2
struct xxx { void *data; xxx *next; };
struct sxxx { xxx *ph; sxxx *next; };
void F( sxxx *p, void (*pf)(void*))
{
xxx *q;
for (; p != NULL; p = p->next)
for ( q = p->ph; q!=NULL; q=q->next)
(*pf)(q->data);
}
//-------------------------------------------------------- 3
struct mxxx { void **data; mxxx *next; };
void F( mxxx *p, void (*pf)(void*))
{
void **q;
for (; p != NULL; p = p->next)
for (q = p->data; *q != NULL; q++)
(*pf)(*q);
}
//-------------------------------------------------------- 4
void F(void ***p, void (*pf)(void*))
{
void **q;
for (; *p != NULL; p++)
for (q = *p; *q != NULL; q++)
(*pf)(*q);
}
//------------------------------------------------------- 5
void F(void *p, int sz, int n, void (*pf)(void*))
{
char *q;
for (q = p; n > 0; n--, q+=sz) ( *pf)(q);
}
//-------------------------------------------------------- 6
struct xxx { void *data; xxx **link; };
void F( xxx *p, void (*pf)(void*))
{
xxx **q;
if (p==NULL) return;
(*pf)(p->data);
for (q = p->link; *q != NULL; q++)
F(*q,pf);
}
//-------------------------------------------------------- 7
struct xxx { void **data; xxx *r, *l; };
void F( xxx *p, void (*pf)(void*))
{
void **q;
if (p==NULL) return;
F(p->r, pf);
for (q = p->data; *q != NULL; q++)
(*pf)(*q);
F(p->l, pf);
}
//------------------------------------------------------- 8
struct xxx { void *data; xxx *next; };
struct zzz { xxx *ph; zzz *r, *l; };
void F( zzz *p, void (*pf)(void*))
{
xxx *q;
if (p==NULL) return;
F(p->r, pf);
for (q = p->ph; q != NULL; q = q->next)
(*pf)(q->data);
Определить структуру данных в двоичном файле произвольного доступа
//------------------------------------------------------bk59-13.cpp
struct man { int dd,mm,yy; };
man *F(int n, FILE *fd)
{ man *p;
p = new man;
fseek (fd, (long)sizeof( man)*n, SEEK_SET);
fread (p, sizeof( man),1,fd);
return(p);
}
//------------------------------------------------------ 2
void *F(FILE *fd)
{ int n;
void *p;
fread(&n,sizeof(int),1,fd);
if (n==0) return(NULL);
p = ( void*) new char[n];
fread(p,n,1,fd);
return(p);
}
//------------------------------------------------------ 3
double *F(FILE *fd)
{ int n;
double *p;
fread(&n,sizeof(int),1,fd);
if (n==0) return(NULL);
p = new double[ n+1];
fread(p,sizeof(double),n,fd);
p[n]=0.0;
return(p);
}
//------------------------------------------------------ 4
#define FNULL -1L
struct xxx { long fnext; . . . };
xxx *F(int n,FILE *fd)
{ xxx *p;
long p0;
p = new xxx;
fseek(fd,0L,SEEK_SET);
fread(&p0,sizeof(long),1,fd);
for (; p0!=FNULL && n!=0; n--, p0 = p->fnext)
{
fseek(fd,p0,SEEK_SET);
fread(p,sizeof( xxx),1,fd);
}
return(p);
}
//------------------------------------------------------ 5
struct man { int dd,mm,yy; };
man *F(int n, FILE *fd)
{ man *p;
long fp;
p = new man;
fseek(fd, sizeof(long)*n,SEEK_SET);
fread(&fp,sizeof(long),1,fd);
fseek(fd,fp,SEEK_SET);
fread(p,sizeof( man),1,fd);
return(p);
}
//------------------------------------------------------ 6
void *F(int n, FILE *fd)
{ int sz;
void *p;
fseek(fd,0L,SEEK_SET);
fread(&sz,sizeof(int),1,fd);
p = ( void*) new char[sz];
fseek (fd, (long)sz * n +sizeof(int), SEEK_SET);
fread (p, sz,1,fd);
return(p);
}
//------------------------------------------------------ 7
void *F(int n, FILE *fd)
{ int sz;
void *p;
long p0;
fseek(fd,0L,SEEK_SET);
fread(&sz,sizeof(int),1,fd);
fread(&p0,sizeof(long),1,fd);
p = (void*)new char[sz];
fseek (fd, p0 + sizeof(long)*n, SEEK_SET);
fread (&p0, sizeof(long),1,fd);
fseek(fd, p0, SEEK_SET);
//------------------------------------------------------bk72-03.cpp
double &F1(double s[], int n)
{ int i,k; double d;
for (i=1, k=0, d=s[0]; i<n; i++)
if (s[i] > d) { k=i; d=s[i]; }
return(s[k]);
}
//---------------------------------------------------- 2
void F2(char *&p, char *s)
{ p = new char[strlen(s)+1];
strcpy(p,s);
}
//---------------------------------------------------- 3
struct tree
{ int val;
tree *pr,*pl;
};
void F3(tree *&p, int v)
{
if (p==NULL)
{ p = new tree;
p->pl = p->pr=NULL;
p->val = v; return;
}
if (p->val = v) return;
if (p->val > v) F3(p->pl,v); else F3(p->pr,v);
}
//------------------------------------------------------
// Определить значения переменных после выполнения действий
//----------------------------------------------------- 4
int INC4(int &x)
{ x++; return(x+1); }
void main()
{ int x,y,z; x = 5; y = INC4(x);
z = INC4(INC4(x)); }
//----------------------------------------------------- 5
int &INC5(int &x)
{ x++; return(x); }
void main()
{ int x,y,z;
x = 5; y = INC5(x); z = INC5(INC5(x)); }
//----------------------------------------------------- 6
int *INC6(int &x)
{ x++; return(&x); }
void main()
{ int x,y,z; x = 5; y = *INC6(x);
z = *INC6(*INC6(x)); }
//----------------------------------------------------- 7
int INC7(int x)
{ x++; return(x+1); }
void main()
{ int x,y,z; x = 5; y = INC7(x);
z = INC7(INC7(x)); }
//----------------------------------------------------- 8
struct integer
{ int val;
friend integer INC8(integer);
integer INC9();
integer &INC10();
integer *INC11();
};
integer INC8(integer src)
{ src.val++; return(src); }
void main()
{ integer x = {5} ,y = {0}, z = {0};
y = INC8(x); z = INC8(INC8(x)); }
//----------------------------------------------------- 9
integer integer::INC9()
{ integer t = *this;
t.val++;
return(t);
}
void main()
{ integer x = {5}, y = {0}, z = {0};
y = x.INC9(); z = x.INC9().INC9(); }
//---------------------------------------------------- 10
integer &integer::INC10()
{ val++;
return(*this);
}
void main()
{ integer x = {5}, y = {0}, z= {0};
y = x.INC10(); z = x.INC10().INC10(); }
//---------------------------------------------------- 11
integer *integer::INC11()
{ val++;
return(this);
}
void main()
{ integer x = {5}, y ={0}, z ={0};
y = *x.INC11(); z = *(x.INC11()->INC11()); }
Определите значения элементов данных объектов после выполнения переопределенных операций .
//------------------------------------------------------bk73-14.cpp
#include <stdio.h>
#include <string.h>
class integ1
{
int val;
public: integ1 operator+(integ1);
integ1 operator+(int);
integ1 (int v0) { val = v0; }
};
integ1 integ1::operator+(integ1 two)
{ integ1 res = *this;
res.val += two.val;
return(res);
}
integ1 integ1::operator+(int two)
{ integ1 res = *this;
res.val += two;
return(res);
}
void main()
{integ1 x(5),y(0), z(0); y = x + 5; z = x + y; }
//------------------------------------------------------- 2
class integ2
{
int val;
public: integ2 &operator+(integ2 &);
integ2 &operator+(int &);
integ2 (int v0) { val = v0; }
};
integ2 &integ2::operator+(integ2 &two)
{
val += two.val;
return(*this);
}
integ2 &integ2::operator+(int &two)
{
val += two;
return(*this);
}
void main()
{ integ2 x(5),y(0), z(0); y = x + 5; z = x + y; }
//------------------------------------------------------- 3
class integ3
{
int val;
public:
integ3 &operator+(integ3 &);
integ3 operator+(int &);
integ3 (int v0) { val = v0; }
};
integ3 &integ3::operator+(integ3 &two)
{
two.val +=val;
return(*this);
}
integ3 integ3::operator+(int &two)
{ integ3 tmp = *this;
tmp.val += two;
return(tmp);
}
void main ()
{integ3 x(5),y(0), z(0); y = x + 5; z = x + y; }
//---------------------------------------------------------
class string
{
char *s;
public: string(char *);
string(string &);
string() { s=NULL; }
char operator[](int);
char &operator()(int);
string &operator=(int);
operator int ();
string &operator+(char);
string &operator+(char *);
string &operator+(string&);
};
string::string(string &src)
{ s = new char[strlen(src.s)+1]; strcpy(s,src.s); }
string::string(char *src)
{ s = new char [strlen(src)+1]; strcpy(s,src); }
Определить значения переменных после выполнения действий .
//------------------------------------------------------bk75-01.cpp
#include <string.h>
class a
{ int x;
public: a() { x = 0; }
a(int n) { x = n; }
int get() { return(x); }
};
class b : public a
{
public:
int get() { return (a::get()+1); }
b(int n) : a(n+1) {}
};
void z1()
{ a a1(10); b b1(12);
int x = a1.get(); int y = b1.get();
}
//------------------------------------------------------- 2
class aa
{ int x;
public:
aa() { x = 0; }
aa(int n) { x = n; }
int inc() { return(++x); }
};
class bb: public aa
{
public: int inc() { int nn = aa::inc(); return (nn-1); }
bb(int n) : aa(n+1) {}
};
void z2()
{ aa a1(10); bb b1(12);
int x = a1.inc(); int y = b1.inc() + a1.inc();
}
//------------------------------------------------------- 3
class aaa
{ int x;
public: aaa() { x = 0; }
aaa(int n) { x = n; }
int inc() { return(++x); }
};
class bbb : public aaa
{
public: int inc() { int nn = aaa::inc(); return (nn-1); }
bbb(int n) : aaa(n+1) {}
};
void z3()
{ aaa a1(10); bbb b1(12);
aaa *pa = &b1; bbb *pb = &b1;
int x = a1.inc();
int y = b1.inc() + pa->inc();
int z = pb->inc();
}
//------------------------------------------------------ 4
class ax
{ int x;
public:
virtual int out() { return(x); }
ax(int n) { x = n; }
};
class bx : public ax
{
public: int out() { return (ax::out()+1); }
bx(int n) : ax(n) { }
};
class cx : public ax
{
public: cx(int n) : ax(n) { }
};
void z4()
{ ax A1(5);
bx B1(6);
cx C1(10);
ax *p[] = { &A1, &B1, &C1 };
int r1 = p[0]->out() + p[1]->out() + p[2]->out();
int r2 = A1.out() + B1.out() + C1.out();
}
//------------------------------------------------------ 5
class ay
{
public: virtual int put()=0;
ay() {};
};
class integer : public ay
{ int val;
public: int put() { return(val); }
integer(int n) { val=n; }
};
class string : public ay
{
char *str;
public: int put() { return(strlen(str)); }
string(char *s) { str = s; }
Вставляемые (inline) функции
Если функция (обычная или функция-элемент структуры или класса) объявлены inline-функциями, то при вызове таких функций транслятор выполняет подстановку по тексту программы тела функции с соответствующей заменой формальных параметров на фактические. Функция-элемент также считается inline по умолчанию, если ее тело находится непосредственно в определении структуры (или класса), например:
struct dat
{
int day,month,year;
void Setdat(char *p) // Функция inline по умолчанию
{ ... } // Тело функции
};
Введение элементов многозадачности в MS DOS
Операционная система MS DOS является однозадачной, то есть принципиально не содержит механизмов переключения задач (процессов), структур данных, локализующих состояние задачи и пр.. Поэтому все традиционные средства управления процессами в MS DOS создаются прикладными программами. Такие программы имеют много общих принципов реализации и могут быть написаны на Си или ассемблере :
- резидентная программа (иначе TSR-программа) запускается как обычная EXE-программа и выполняет в функции main() следующие действия:
- определяется размер программы в параграфах как сумма размеров сегментов данных и команд (справедливо для Small-модели памяти программы на Си);
- сохраняет вектора аппаратных и программных прерываний, по которым она будет в дальнейшем активизироваться (клавиатура, таймер, последовательный порт, прерывание 21h DOS и др.);
- устанавливает на эти вектора адреса собственных функций обработки прерывания;
- инициализирует собственные данные программы;
- выполняет функцию keep() - выйти в DOS с сохранением занятой памяти;
- программы обработки прерываний должны иметь вызов функции обработки прерывания по сохраненному вектору прерывания, то есть вызывать по цепочке предыдущий обработчик прерывания;
- программы обработки прерываний не должны содержать функций, прямо или косвенно обращающихся к DOS или BIOS, поэтому все средства диалога (экран, клавиатура) должны быть реализованы "вручную".
Ввод целого числа
В преобразовании используется тот факт, что при добавлении к числу очередной цифры справа старое значение увеличивается в 10 раз и к нему добавляется значение новой цифры, например:
Число: '123' '1234'
Значение: 123 1234 = 123 * 10 + 4
n = n * 10 + c[i] - '0';
где s[i]-'0' -двоичное значение цифры, представленной символом c[i].
//------------------------------------------------------bk34-05.cpp
//------Преобразование внешней формы во внутреннюю в 10СС
int StringToInt(char c[])
{
int n,i;
for (i=0; !(c[i]>='0' && c[i]<='9'); i++)
if (c[i]=='\0') return(0); // Поиск первой цифры
for (n=0; c[i]>='0' && c[i]<='9'; i++)
// Накопление целого
n = n * 10 + c[i] - '0'; // "цифра за цифрой"
return n;
}
Ввод-вывод целых чисел
Функции и объекты стандартных потоков ввода /вывода могут, в частности, вводить и выводить целые числа, представленные в десятичной, восьмеричной и шестнадцатеричной системах счисления. При этом происходят некоторые преобразования, связанные с различной формой представления целого в памяти (внутренняя) и устройствах символьного ввода-вывода (внешняя форма представления).
Так, при выводе на экран или вводе с клавиатуры целое число представлено строкой символов в виде последовательности цифр числа (в нашем примере "1052"). В памяти это же число представлено целой переменной (машинным словом), которое хранит двоичный эквивалент десятичного числа 1052 (в шестнадцатеричной системе -0x041C). Преобразования при вводе и выводе целых чисел заключаются в переходе от символа-цифры к значению целой переменной, соответствующему этой цифре, и наоборот:
char c; int n;
n = c - '0';
c = n + '0';
int,long) или вещественной переменной.
По своей природе внутренняя форма представления числа является двоичной. Однако для внешнего наблюдателя это вообще не существенно. Он может считать ее какой угодно, хоть десятичной. Дело в том, что в целом важно, что компьютер производит в этой форме корректные вычисления.
ВНЕШНЯЯ ФОРМА ПРЕДСТАВЛЕНИ ЧИСЛА -- представление числа в виде строки символов цифр, соответствующих числу в заданной системе счисления.
Таким образом, о системе счисления имеет смысл говорить только во внешней форме представления числа.
Ввод-вывод данных по прерыванию
Наиболее часто прерывание используется для организации обмена данными с относительно медленными устройствами, чтобы программа " не теряла зря времени" при ожидании готовности устройства к вводу-выводу очередной порции информации. В таком случае возникает уже физический параллелизм между выполняемой программой и операцией ввода-вывода, производимой устройством. В качестве примера рассмотрим, как выглядит процесс посимвольного вывода по прерыванию строки в гипотетическое устройство последовательной передачи. Взаимодействие основной программы и обработчика прерываний также осуществляется через программное прерывание.
#define USERVEC 0x60 // Номер вектора
// программного прерывания
#define IOVEC ... // Номер вектора прерывания
// устройства
int cnt; // Текущая длина строки
char far *p=NULL; // Указатель на строку
// в основной программе
// NULL - устройство свободно
void interrupt
USERINT(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
// ds:dx - начальный адрес буфера,
// cx - размер буфера в байтах
// Программное прерывание запускает процесс вывода
// строки из буфера посимвольно по прерыванию
{
if (p!=NULL) {ax=0; return; } // Устройство занято
p = MK_FP(ds,dx); // Сформировать длинный
// указатель из регистров ds,dx
cnt=cx;
setvect(INT,IOVEC); // Установить вектор прерывания
// Разрешить прерывание от устройства
// в контроллере прерываний
// по соответствующему IRQ
// и в контроллере устройства ...
ax=1; // Выйти из программного прерывания
} // с _AX=1
void interrupt INT(...) // Прерывание от устройства по
{ char *c; // готовности к выводу символа
if (cnt==0) // Вывод закончен
{ p=NULL;
// Запретить прерывание от устройства в контроллере
// прерываний по соответствующему IRQ
// и в контроллере устройства ...
}
else
{ c=*p++;
cnt--; // Получить очередной байт и вывести
outportb(c,...);
}}
Вычисление степенного полинома
При вычислении значения степенного полинома необходимы целые степени аргумента x:
.
n n-1
y = An * x + An-1 * x + ... + A1 * x + A0
Исключить вычисление степеней в явном виде можно, преобразовав полином к виду, соответствующему итерационному процессу:
.
y =(((...(((An*x + An-1)*x + An-2)*x +...+ A1)*x+A0
шаг 1 шаг 2 шаг 3 ... шаг n
//------------------------------------------------------bk33-03.cpp
//-----Степенной полином
double poly(double A[], double x, int n)
{
int i; double y;
for (y=A[n], i=n-1; i>=0; i--) y = y * x + A[i];
return(y);
}
Вычисление суммы степенного ряда
При вычислении сумм рядов, слагаемые которых включают в себя степени аргумента и факториалы, может быть также использован итерационный цикл. В этом цикле значение очередного слагаемого ряда вычисляется умножением предыдущего слагаемого на некоторый коэффициент, что позволяет избавиться от повторного вычисления степеней и факториалов. Сам коэффициент вычисляется таким образом :
.
Y = S0(x) + S1(x) + S2(x) +...+ Sn-1(x) + Sn(x) + ...
k(x,n) = Sn / Sn-1
Так, например для ряда, вычисляющего sin(x), коэффициент и функция его вычисления имеют вид:
.
3 5 7 n 2n+1
sin(x)=x - x/3! + x/5! - x/7! +...+ (-1) x / (2n+1)!
.
S0 S1 S2 S3 ... Sn
.
2
k(x,n) = Sn / Sn-1 = - x / (2n (2n+1))
Соответствующая программа выгладит так:
//------------------------------------------------------bk33-04.cpp
//--- Вычисление значения функции sin через степенной ряд
#include <iostream.h>
#include <math.h>
double sum(double x,double eps)
{
double s,sn; // Сумма и текущее слагаемое ряда
int n;
for (s=0., sn = x, n=1; fabs(sn) > eps; n++)
{
s += sn;
sn = - sn * x * x / (2. * n * (2. * n + 1));
}
return s;
}
// Вычисление степенного ряда для x в диапазоне от 0.1 до 1 с шагом 0.1
void main()
{
double x;
for (x=0.1; x <= 1.; x += 0.1)
cout << "x=" << x << " sum=" << sum(x,0.0001) << " sin=" << sin(x) << endl;
}
Выделение вложенных фрагментов
Следующий пример включает в себя практически все перечисленные выше приемы работы со строкой : поиск символов с запоминанием их позиций, исключение фрагментов, преобразование числа. Требуется переписать из входной строки вложенные друг в друга фрагменты последовательно друг за другом, оставляя при исключении фрагмента на его месте уникальный номер.
.Пример : a{b{c}b}a{d{e{g}e}d}a => {c}{b1b}{g}{e3e}{d4d}a2a5a
Предварительное решение задачи начнем с разработки функции, которая обязательно потребуется: поиск открывающейся скобки для самого внутреннего вложенного фрагмента. Для этого можно использовать переменную-счетчик, которая увеличивает свое значение на 1 на каждую из открывающихся скобок и уменьшает на 1 на каждую из закрывающихся. При этом фиксируется максимальное значение счетчика и позиция элемента, где это происходит.
//------------------------------------------------------bk34-09.cpp
// функция возвращает индекс скобки " {" для самой внутренней пары {}
int find(char c[])
{
int i; // Индекс в строке
int k; // Счетчик вложенности
int max; // Максимум вложенности
int b; // Индекс максимальной " {"
for (i=0, max=0, b=-1; c[i]!=0; i++)
{
if (c[i]== } ) k--;
if (c[i]== { )
{
k++;
if (k>max) { max=k; b=i; }
}
}
if (k!=0) return 0; // Защита " от дурака" , нет парных скобок
return b;
}
Другой вариант функции ищет первую внутреннюю пару скобок. Запоминается позиция открывающейся скобки, при обраружении закрывающейся скобки возвращается индекс последней открывающейся. Заметим, что его также сожно использовать, просто последовательность извлечения фрагментов будет другая.
//------------------------------------------------------bk34-10.cpp
// функция возвращает индекс скобки " {" для первой внутренней пары {}
int find(char c[])
{
int i; // Индекс в строке
int b; // Индекс максимальной " {"
for (i=0, b=-1; c[i]!=0; i++)
{
if (c[i]== } ) return b;
if (c[i]== { ) b=i;
}
return b;
}
Идея основного алгоритма заключается в последовательной нумерации " выкусываемых" из входной строки фрагментов, при этом на место каждого помещается его номер значение счетчика во внешней форме представления.
//------------------------------------------------------bk34-11.cpp
void copy(char c1[], char c2[])
{
int i=0; // Индекс в выходной строке
int k; // Индекс найденного фрагмента
int n; // Запоминание начала фрагмента
int m; // Счетчик фрагментов
for (m=1; (k=find(c1))!=-1; m++) // Пока есть фрагменты
{
for (n=k; c1[k]!= } ; k++, i++) c2[i]=c1[k];
c2[i++] = c1[k++]; // Переписать фрагмент
if (m/10!=0) c1[n++] = m/10 + 0 ; // На его место две цифры
c1[n++] = m%10 + 0 ; // номера во внешней форме
for (;c1[k]!=0; k++, n++) c1[n]=c1[k];
c1[n]=0; // сдвинуть " хвост" к началу
}
for (k=0; c1[k]!=0; k++, i++) c2[i]=c1[k];
c2[i]=0; // перенести остаток входной строки
}
Практический совет избегать сложные вычисления над индексами. Лучше всего для каждого фрагмента строки заводить свой индекс и перемещать их независимо друг от друга в нужные моменты. Что, например, сделано выше при " уплотнении" строки.
Выполнение и отладка
.
---------------------------------------------------------------¬
¦File Edit Search Run Compile Debug Project Options Window Help¦
L------------------¦--------------------------------------------
------------------+-----¬
¦Run Ctrl+F9 -- Команда "Make" и выполнение программы
¦Programm reset Ctrl+F2 -- Сброс и установка начального состояния
¦Goto cursor F4 ----¬ программы при отладке
¦Trace into F7 ---¬L- Выполнять программу до строки,
¦Step over F8 --¬¦ отмеченной курсором
¦Arguments -¬¦L-- Выполнить одну строку программы
L------------------------¦¦ с трассировкой вызываемой функции
¦L--- Выполнить одну строку программы
¦ без трассировки вызываемой функции
L---- Задать аргументы командной строки
программы при отладке
---------------------------------------------------------------¬
¦File Edit Search Run Compile Debug Project Options Window Help¦
L---------------------------------¦-----------------------------
-+-------------------------¬
--------------------------------- Inspect Alt+F4 ¦
¦-------------------------------- Evaluate/modify Ctrl+F4 ¦
¦¦ Последовательность (стек )---- Call stack Ctrl+F3 ¦
¦¦ вызовов функций ¦Watches --¬
¦¦ Установить/отменить точку----- Toggle breakpoint Ctrl+F8 ¦ ¦
¦¦ останова в текущей строке --- Breakpoints ¦ ¦
¦¦ Просмотр/редактирование ---- L--------------------------- ¦
¦¦ списка точек останова ¦
¦¦ Точки просмотра (вычисляемые выражения в окне Watch) ¦
¦¦ ----------------------+-¬
¦¦ Добавить точку просмотра ------------ Add watch Ctrl+F7 ¦
¦¦ Удалить текущую точку просмотра ----- Delete watch ¦
¦¦ Редактировать текущую точку --------- Edit watch ¦
¦¦ Удалить все точки просмотра --------- Remove all watches ¦
¦¦ L------------------------
¦L- Вычисление и модификация значения адресного выражения
¦ в отдельном окне
L-- Просмотр и модификация (инспектирование) значения текущей
переменной под курсором (Аlt+F4) или явно указанной
переменной или адресного выражения (меню). Значение
переменной или выражения отображается в отдельном окне,
окно закрывается по Esc. Alt+M - модификация выбранного
поля. Для указателя отображается и модифицируется указу-
емый тип данных, для массива - элементы массива.
Вывод двоичного числа
В преобразовании используется тот факт, что значение младшей цифры целого числа n равно остатку от деления его на 10, вторая цифра -остатку от деления на 10 частного n/10 и т.д.. Тогда в основе алгоритма лежит итерационный цикл, в котором на каждом шаге получается значение очередной цифры справа как остаток от деления числа на 10, а само число уменьшается в 10 раз. Поскольку цифры получаются в обратном порядке их размещения в строке, массив символов также необходимо заполнять в обратном порядке. При этом надо либо вычислить заранее количество цифр, либо заполнять лишние позиции слева нулями или пробелами.
//------------------------------------------------------bk34-06.cpp
//------Преобразование внутренней формы во внешнюю в 10СС
void IntToString(char c[], int n)
{ int nn,k;
for (nn=n, k=1; nn!=0; k++, nn/=10); // Подсчет количества
c[k] = '\0'; // цифр числа
for (k--; k >=0; k--, n /= 10) // Получение цифр числа
c[k] = n % 10 + '0'; // в обратном порядке
}
Вызов функцииФункции с переменным количеством параметров
Ранее (см. 3.6) мы уже рассматривали, как используется стек для вызова функций, передачи параметров и хранения локальных (автоматических) переменных. Фактические параметры записываются в стек перед вызовом функции, начиная с последнего в списке. Поскольку аппаратный стек расположен "вверх дном" и "растет" от старших адресов к младшим, то этим обеспечивается прямой порядок размещения их в памяти. Формальные параметры представляют собой "ожидаемые" смещения в стеке, по которым должны после вызова находиться соответствующие фактические параметры. Таким образом, сам механизм вызова функции соответствие параметров устанавливает только "по договоренности" между вызывающей и вызываемой функциями, а транслятор при использовании прототипа проверяет эти соглашения.
Поскольку фактические параметры записываются в стек и извлекаются из него вызывающей функцией, а используются вызываемой по смещениям в стеке, то имеет место независимость формальных и фактических параметров. В частности, если в списке есть лишние параметры, то вызов будет корректен, а вызываемая функция просто их "не заметит". Отсюда один шаг к использованию переменного количества фактических параметров: достаточно установить некоторый указатель на область памяти (область стека), занимаемую "лишними" фактическими параметрами, и тогда с помощью его и операций адресной арифметики функция может просмотреть их последовательность.
Текущее количество фактических параметров, передаваемых при вызове, может быть указано:
-отдельным параметром -счетчиком;
-параметром ограничителем, значение которого отмечает конец списка параметров;
-форматной строкой, в которой перечислены спецификации параметров (например, функция printf).
Если уж быть совсем точным, то список фактических параметров должен представлять собой последовательность переменного формата, в которой значение текущей переменной может определять количество и типы последующих переменных списка. Способы работы с такими последовательностями с использованием указателей и преобразованием их типа рассмотрены в п.4.4.
Пример функции, работающей с параметром -счетчиком.
int sum(int n,...) // n - счетчик параметров
{ int s,*p; // Указатель на область параметров
p = &n+1; // назначается на область памяти
for (s=0; n > 0; n--) // вслед за счетчиком n
s += *p++;
return(s); }
void main(){ cout << sum(5,0,4,2,56,7) + sum(2,6,46); }
Пример функции, работающей с параметром -ограничителем.
int sum(int a,...)
{ int s,*p; // Указатель на область параметров
p = &a; // назначается на первый параметр
for (s=0; *p >= 0; ) // из переменного списка
s += *p++; // Ограничитель - отрицательное
return(s); // значение
}
void main() { cout << sum(0,4,2,56,7,-1) + sum(6,46,-1) ;}
Если в списке предполагается наличие параметров различных типов, то типы их должны быть переданы в функцию отдельной спецификацией (подобно форматной строке функции printf). В этом случае область фактических параметров представляет собой память, в которой последовательность типов переменных заранее не определена (см п.4.4). С такой памятью можно работать только используя явное преобразование типа указателя к типу той переменной, которая в данный момент под ним находится. Операция *p++ по отношению к каждому типу указателя будет выполнять чтение из памяти переменной соответствующего типа с переходом к началу следующей:
//------------------------------------------------------bk45-01.cpp
#include <stdio.h>
int printf(char *s,...)
{
int ii;
double dd;
char * p,*ss;
p = &s+1; // Указатель на начало списка параметров
while (*s != '\0') // Просмотр форматной строки
{
if (*s != '%')
putchar(*s++); // Копирование форматной строки
else
{ s++; // Спецификация параметра вида "%d"
switch(*s++)
{ // Извлечение параметра и переход к следующему
case 'c': puchar(*p++);
break;
case 'd': ii = *((int*)p)++;
break; // Преобразование и вывод целого
case 'f': dd = *((double*)pd)++;
break; // Преобразование и вывод плавающего
case 's': ss = *((char**)ps)++;
puts(ss); // Вывод строки по указателю
break;
}}}}
Этот механизм может быть скрыт от программиста соответствующими макрокомандами.
//------------------------------------------------------bk45-02.cpp
#include <stdarg.h>
int printf(char *s,...)
{
va_list PNT; // Аналог указателя на область стека
int ii;
double dd;
char *ss;
va_start(PNT,s+1); // Назначить указатель на начало
while (*s != '\0') // Просмотр форматной строки
{
if (*s != '%')
putchar(*s++); // Копирование форматной строки
else
{ s++; // Спецификация параметра вида "%d"
switch(*s++)
{ // Извлечение параметра по указателю
// и переход к следующему
case 'd': ii = va_arg(PNT,int);
break; // Преобразование и вывод целого
case 'f': dd = va_arg(PNT,double);
// Преобразование и вывод плавающего
break;
case 's': ss = va_arg(PNT, char*);
puts(ss); // Вывод строки по указателю
break;
}
}}
va_end(PNT);
}
Макрокоманда va_list определяет переменную -указатель на область параметров в стеке, va_start назначает его на область памяти, расположенную за последним явно указанным параметром, va_arg извлекает из памяти переменную указанного типа и продвигает указатель на ее размерность, va_end выполняет завершающие действия. Использование макрокоманд предпочтительнее, так как они учитывают все нюансы механизма передачи параметров в данной версии транслятора (например, выравнивание адреса параметра к границе машинного слова ).
Вызов оболочки
c:\bc\bin\bc - вызов оболочки
c:\bc\bin\bc a.c - вызов оболочки для файла a.c
c:\bc\bin\bc b.prj - вызов оболочки для проекта b.prj
Взаимодействие прерывающего процесса и основной программы
Заметим, что функция обработки прерывания и основная (прерываемая) программа в приведенных выше терминах представляют собой процессы, которые протекают асинхронно, то есть независимо друг от друга. Условность этого случая состоит в том, что эти процессы неравноправны, но все равно на них распространяются все проблемы, связанные с взаимодействием таких процессов между собой, иначе говоря, СИНХРОНИЗАЦИЕЙ. Самый простой способ синхронизации заключается в использовании общих глобальных переменных, которые изменяются одним из процессов и проверяются другим.
З нания
После изучения дисциплины студент должен знать:
· основные понятия теории информации;
· формы представления числовой и символьной информации;
· способы определения переменных базовых типов данных и массивов, синтаксис основных опер а ций, операторов и функций в Си.
· структуры данных - последовательность, стек, очередь и способы их представления в массиве;
· назначение (" смысл" ) переменных в стандартных фрагментах программ, основные принципы ан а лиза программ путем разбиения на стандартные фрагменты;
· основы технологии структурного программирования;
· алгоритмы последовательного и двоичного поиска, виды сортировок, оценки трудоемкости алг о ритмов сортировки и поиска.
После изучения дисциплины студент должен знать:
· определение и свойства типа данных, способ контекстного определения типов данных в Си ;
· свойства и принципы работы с производными типами данных - указателями, структурами, масс и вами, функциями.
· принципы определения сложных типов данных и работы с переменными соответствующих т и пов, принципы организации модульных программ, работающих со сложными типами данных ;
· основы модульного проектирования программ на Си : свойства переменных и функций - время жизни и область действия, назначение и структуру заголовочных файлов, объектных модулей и файла проекта ;
· принципы управления памятью программы, заложенные в Си, способы работы с данными пер е менного формата ;
· способы динамического управления памятью - динамическими переменными и массивами.
· определение, виды и свойства структур данных, операции над ними, способы их формирования ;
· назначение, способы формирования и основные алгоритмы работы с массивами указателей и сп и сками ;
· особенности рекурсивных алгоритмов и их проектирования, назначение и смысл формальных и фактических параметров, локальных и глобальных переменных, принципы использования рекурсии в поисковых задачах.
После изучения дисциплины студент должен знать:
· виды структур данных, их сравнительные характеристики, основные алгоритмы работы с ними ;
· алгоритмы работы с деревьями, свойства двоичных деревьев ;
· принципы использования указателей на функцию для позднего связывания алгоритмов ;
· принципы организации структур данных в двоичных файлах ;
· основную терминологию и понятия баз данных и механизмы из программной реализации ;
· свойства процессов, определение и свойства прерывания, понятие реентерабельности, особенн о сти программирования асинхронных квази-параллельных процессов, принципы их синхрониз а ции.
После изучения дисциплины студент должен знать:
· принципиальные отличия Си++ от Си ;
· концептуальные основы объектно-ориентированного программирования, понятия класса, объекта, метода, закрытости, доступа, конструирования объектов ;
· синтаксис определения классов, методов и объектов, особенности работы с объектами, содерж а щими динамические данные и связанные ресурсы, основные виды конвейерной обработки объе к тов в операциях - по значению, по ссылке, по указателю ;
· принципы переопределения операций, особенности переопределения отдельных операций ;
· понятия наследования и полиморфизма, сущность " развития" класса от базового к производному, принципы проектирования программы " от класса к классу" , основные варианты использования виртуальной функции ;
· принципы организации объектно-ориентированной библиотеки и работы с ней ;
· способы организации объектов в программе, принцип событийного программирования с испол ь зованием объектов.
Задачи, задачи и еще раз задачи
Далее рассматривается несколько разделов, составляющий "классику" обучения программированию. Арифметические (вычислительные) задачи, приближенные вычисления, обработка текста, сортировка и поиск. Скучно, но ничего не поделаешь.
Написать функцию поиска парных фрагментов
//------------------------------------------------------prg1pr-03.cpp
int find(char c[])
{
for (int i=0; c[i]!=0; i++)
for (int j=i+1; c[j]!=0; j++)
{
for (int k=0; i+k<j && c[i+k]==c[j+k]; k++);
if (k>=4) return i;
}
return -1;
}
Написать программу поиска самой внутренней вложенной пары скобок
Идея. Использовать счетчик, который увеличивает свое значение на 1 на каждую из открывающихся скобок и уменьшает на 1 на каждую из закрывающихся. При этом фиксируется максимальное значение счетчика и позиция элемента, где это происходит.
//------------------------------------------------------prg1pr-01.cpp
// функция возвращает индекс скобки " { " для самой внутренней пары {}
int find(char c[])
{
int i; // Индекс в строке
int k; // Счетчик вложенности
int max; // Максимум вложенности
int b; // Индекс максимальной " {"
for (i=0, max=0, b=-1; c[i]!=0; i++)
{
if (c[i]== } ) k--;
if (c[i]== { )
{
k++;
if (k>max) { max=k; b=i; }
}
}
if (k!=0) return 0; // Защита " от дурака" , нет парных скобок
return b;
}
Другой вариант. Функция ищет первую внутреннюю пару скобок. Запоминается позиция открывающейся скобки, при обраружении закрывающейся скобки возвращается индекс последней открывающейся.
//------------------------------------------------------prg1pr-02.cpp
// функция возвращает индекс скобки " { " для первой внутренней пары {}
int find(char c[])
{
int i; // Индекс в строке
int b; // Индекс максимальной " {"
for (i=0, b=-1; c[i]!=0; i++)
{
if (c[i]== } ) return b;
if (c[i]== { ) b=i;
}
return b;
}
Написать программу слияния двух упорядоченных массивов в третий
Идея. Если имеется две упорядоченные последовательности, то из них можно составить общую упорядоченную последовательность путем выбора минимального двух очередных элементов массивов. После переноса выбранного элемента он "убирается" из входной последовательности. Заметим, что при слиянии один массив может закончится раньше, чем другой, тогда слияние продолжается из одного оставшегося.
Варианты "убирания" выбранного элемента:
- сдвигать элементы в массиве влево, записывая в конец "очень большое число", которое играет роль "затычки";
- использовать индексы текущего элемента.
Последовательное проектирование программы .
1. Заголовок функции.
void sleave(int in1[], int in2[], int out[], int n)
{
_ 2. слить массивы in1[] и in2[] размерности n
_ в массив out[] размерности 2*n
}
2. Последовательно сливать по одному элементу из массивов в выходной массив - Шаг 3 - выбор цикла for (){}.
3. Переменная цикла-индекс очередного элемента в выходном массиве out .
for (int i=0; i< 2*n; i++)
{
_ 4. Выбрать очередной элемент, исключить и "слить" в out[i]
}
4. Используем вариант извлечения элементов "со сдвигом". В этом случае сравниваются элементы in1[0] и in2[0]. Выбирается и исключается элемент в зависимости от сравнения - выбираем условную конструкцию .
if (in1[0] < in2[0])
_Выбрать in1[0], исключить и перенести в out[i]
else
_Выбрать in2[0], исключить и перенести в out[i]
5. Поскольку одно и то же действие выполняется над двумя разными массивами, то его можно оформить в виде функции (отдельного модуля), которая будет иметь вид :
int get(int in[], int m)
{
_Извлечь из массива in[] размерности m
_первый элемент (in[0]), сдвинуть содержимое
_влево, записать в конец "очень большое число"
_и возвратить в виде результата извлеченный элемент
}
6. С вызовом еще не написанной функции фрагмент будет иметь вид :
if (in1[0] < in2[0])
out[i]=get(in1,n);
else
out[i]=get(in2,n);
7. Тело функции-простая последовательность действий :
int get(int in[], int m)
{
_1. Запомнить in[0]
_2. Сдвинуть содержимое in[] влево
_3. Записать в конец "очень большое число"
_4. Возвратить в виде результата извлеченный элемент
}
8. Окончательный вид функции:
int get(int in[], int m)
{
int v=in[0];
for (int i=1; i<m; i++) in[i-1]=in[i];
in[m-1]=MAXINT;
return v;
}
9. Окончательный вид функции слияния :
void sleave(int in1[], int in2[], int out[], int n)
{
for (int i=0; i< 2*n; i++)
if (in1[0] < in2[0])
out[i]=get(in1,n);
else
out[i]=get(in2,n);
}
Задания к лабораторным работам
1. Найти в массиве и вывести значение наиболее часто встречающегося элемента.
2. Найти в массиве элемент, наиболее близкий к среднему арифметическому суммы его элементов.
3. Разделить массив на две части, поместив в первую элементы, большие среднего арифметического их суммы, а во вторую - меньшие (части не сортировать).
4. Сформировать массив простых чисел, не больших заданного.
5. Сформировать массив простых множителей заданного числа.
6. Найти наименьшее общее кратное всех элементов массива (то есть числа, которое делится на все элементы).
7. Найти наибольший общий делитель всех элементов массива (на который они все делятся без остатка).
8. Интервал между минимальным и максимальным значениями элементов массива разбить пополам и относительно этого значения разбить массив на две части (части не сортировать).
9. Задан массив, определить значение k, при котором сумма |A(1)+A(2)+…A(k)-A(k+1)+…+A(N)| минимальна (то есть минимален модуль разности сумм элементов в правой и левой части, на которые массив делится этим k).
10. Заданы два упорядоченных по возрастанию массива. Составить из их значений третий, также упорядоченный по возрастанию (слияние).
11. Известно, что 1 января 1999 г. пятница. Для любой заданной даты программа должна выводить день недели.
12. Известно, что 1 января 1999 г. пятница. Программа должна найти все " черные вторники" и " черные пятницы" 1999 года (то есть 13 числа).
13. Найти в массиве наибольшее число подряд идущих одинаковых элементов (например {1,5,3,l 6,6,6,6,6,3,4,4,5,5,5} = 5).
14. Составить алгоритм решения ребуса РАДАР=(Р+А+Д)^4 (различные буквы означают различные цифры, старшая - не 0 ).
15. Составить алгоритм решения ребуса МУХА+МУХА+МУХА=СЛОН (различные буквы означают различные цифры, старшая - не 0 ).
16. Составить алгоритм решения ребуса ДРУГ-ГУРД=2727 (различные буквы означают различные цифры, старшая - не 0 ).
17. Составить алгоритм решения ребуса КОТ+КОТ=ТОК (различные буквы означают различные цифры, старшая - не 0 ).
Для заданного варианта написать функцию вычисления суммы ряда. для диапазона значений 0.1 .. 0.9 и шага 0.1 изменения аргумента вычислить значения суммы ряда и контрольной функции, к которой он сходится, с точностью до 4 знаков после запятой.
Для вариантов 6-8 использовать значение sin и cos на предыдущем шаге для вычисления значений на текущем шаге по формулам:
sin(nx) = sin((n-1)x) cos(x) + cos((n-1)x) sin(x)
cos(nx) = cos((n-1)x) cos)x) - sin((n-1)x) sin(x)
В вариантах 3, Bn - числа Бернулли, использовать в виде следующего массива значений для n = 1..11 .
1 1 1 1 5 691 7 3617 43867 174611 854513
-, --, --, --, --, ----, -, ----, -----, -------, ------
6 30 42 30 66 2730 6 510 798 330 138]
__________________________________________________________________________
Вариант Ряд Функция
__________________________________________________________________________
2 n 2n
1 1 - x / 2! + ... + (-1) x / (2n)! cos(x)
__________________________________________________________________________
3 2n-1
2 z=((x-1)/(x+1)) (2/1)z + (2/3)z +...+ (2/2n-1)z ln(x)
__________________________________________________________________________
1 3 2 5 2n 2n 2n-1
3 x + -- x + --- x +...+2 (2 -1) Bn x / (2n)! tg(x)
3 15 ( ряд с n=1 )
__________________________________________________________________________
1 3 1 3 5...(2n-1) 2n+1
4 x + ---x +...+ ----------------- x arcsin(x)
2 3 2 4 6...2n(2n+1)
__________________________________________________________________________
2 n x
5 1+ x ln3 + (x ln3)/2! +...+(x ln 3)/n! 3
__________________________________________________________________________
1 1 1 2 n 1 1 3...(2n-3) n 0.5
6 1+ - x + --- x -...+ (-1) ------------- x (1 + x)
2 2 4 2 4 6...2n
__________________________________________________________________________
n
7 sin(x) - sin(2x) / 2+..+(-1)sin(nx) / n x / 2
__________________________________________________________________________
2 n 2 2 2
8 -cos(x) + cos(2x)/2 +..+(-1) cos(nx)/n 0.25(x - PI /3)
__________________________________________________________________________
1. Выполнить сортировку символов в строке. Порядок возрастания "весов" символов задать таблицей вида c h ar ORD[] = "АаБбВвГгДдЕе1234567890"; Символы, не попавшие в таблицу, размещаются в конце отсортированной строки.
2. В строке, содержащей последовательность слов, найти конец предложения, обозначенный символом "точка". В следующем слове первую строчную бувку заменить на прописную.
3. В строке найти все числа в десятичной системе счисления, сформировать новую строку, в которой заменить их соответствующим представлением в шестнадцатеричной системе.
4. Заменить в строке принятое в Си обозначение символа с заданным кодом (например, \101) на сам символ (в данном случае - A).
5. Переписать в выходную строку слова из входной строки в порядке возрастания их длины.
6. Преобразовать строку, содержащую выражение на Си с операциями (=,==,!=,a+=,a-=), в строку, содержащую эти же операции с синтаксисом языка Паскаль (:=,=,#,a=a+,a=a-).
7. Удалить из строки комментарии вида "/* ... */". Игнорировать вложенные комментарии.
8. Заменить в строке символьные константы вида 'А' на соответствующие шестнадцатеричные (т.е. 'А' на 0x41).
9. Заменить в строке последовательности одинаковых символов (не пробелов) на десятичное число, состоящее из двух десятичных цифр и соответствующее их количеству (т.е. " abcdaaaaa xyznnnnnnn " на " abcd5a xyz7n " ).
10. Найти в строке два одинаковых фрагмента (не включающих в себя пробелы) длиной более 5 символов и возвратить индекс начала первого из них (т.е. для " aaaaaabcdefgxxxxxxbcdefgwwwww" вернуть n=6 - индекс начала " bcdefg" ) .
11. Оставить в строке фрагменты, симметричные центрального символа, длиной более 5 символов (например, " dcbabcd" ), остальные символы заменить на пробелы.
12. Найти во входной строке самую внутреннюю пару скобок {...} и переписать в выходную строку содержащиеся между ними символы. Во входной строке фрагмент удаляется.
13. Заменить в строке все целые числа соответствующим повторением следующего за ними символа (например " abc5xacb15y" - " abcxxxxxacbyyyyyyyyyyyyyyy ") .
14. "Перевернуть" в строке все слова. (Например : "Жили были дед и баба" - "илиЖ илиб дед и абаб").
15. Функция переписывает строку. Если она находит в строке число, то вместо него она переписывает в выходную строку соответствующее по счету слово из входной строки. (например, " aaa bb1bb cc2cc" - " aaa bbaaabb ccbb1bbcc") .
Алгоритм сортировки реализовать в виде функции, возвращающей в качестве результата характеристику трудоемкости алгоритма (например, количество сравнений ). Если имеется базовый алгоритм сортировки (для Шелла - " пузырек" , для Шейкер - " пузырек" , для вставки с двоичным поиском - погружение), то аналогично оформить базовый алгоритм и произвести сравнение эффективности.
1. Сортировка вставками, место помещения очередного элемента в отсортированную часть производить с помощью двоичного поиска. Двоичный поиск оформить в виде отдельной функции.
2. Сортировка Шелла. Частичную сортировку с заданным шагом, начиная с заданного элемента оформить в виде функции. Алгоритм частичной сортировки - вставка погружением.
3. Сортировка разделением. Способ разделения : вычислить среднее арифметическое всех элементов массива и относительно этого значения разбить массив на две части (с использованием вспомогательных массивов).
4. Шейкер-сортировка. Процесс движения в прямом и обратном направлении реализовать в виде одного цикла, используя параметр - направление движения (+1/-1) и меняя местами нижнюю и верхнюю границы просмотра.
5. "Быстрая" сортировка с итерационным циклом вычисления медианы. Для заданного интервала массива, в котором производится разделение, ищется медиана обычным способом. Затем выбирается та часть интервала между границей и медианой, в которой находится середина исходного интервала, и процесс повторяется.
6. Сортировка циклическим слиянием с использованием одного выходного и двух входных массивов. Для упрощения алгоритма и разграничения сливаемых групп в последовательности в качестве разделителя добавить " очень большое значение" ( MAXINT) .
7. Сортировка разделением. Способ разделения : интервал между минимальным и максимальным значениями элементов массива разбить пополам и относительно этого значения разбить массив на две части (с использованием вспомогательных массивов).
8. Простое однократное слияние. Разделить массив на n частей и отсортировать их произвольным методом.
Вариант задания реализовать в виде функции, использующей для работы со строкой только указатели и операции вида *p++, p++ и т.д.. Если функция возвращает строку или ее фрагмент, то это также необходимо сделать через указатель.
1. Функция находит в строке заданную подстроку и возвращает указатель на нее. С использованием функции найти все вхождения подстроки в строке.
2. Функция находит минимальный элемент массива и возвращает указатель на него. С использованием этой функции реализовать сортировку выбором.
3. Шейкер-сортировка с использованием указателей на правую и левую границы отсортированного массива и сравнения указателей (см.3.7).
4. Функция находит в строке пары одинаковых фрагментов и возвращает указатель на первый. С помощью функции найти все пары одинаковых фрагментов.
5. Функция находит в строке пары инвертированных фрагментов (например "123apr" и "rpa321") и возвращает указатель на первый. С помощью функции найти все пары.
6. Функция производит двоичный поиск места размещения нового элемента в упорядоченном массиве и возвращает указатель на место включения нового элемента. С помощью функции реализовать сортировку вставками.
7. Функция находит в строке десятичные константы и заменяет их на шестнадцатеричные с тем же значением, например "aaaaa258xxx" на "aaaaa0x102xxx".
8. Функция находит в строке символьные константы и заменяет их на десятичные коды, например "aaa'6'xxx" на "aaa54xxx".
9. Функция находит в строке самое длинное слово и возвращает указатель на него. С ее помощью реализовать размещение слов в выходной строке в порядке убывания их длины.
10. Функция находит в строке самое первое (по алфавиту) слово. С ее помощью реализовать размещение слов в выходной строке в алфавитном порядке.
11. Функция находит в строке симметричный фрагмент вида " abcdcba" длиной 7 и более символов (не содержащий пробелов) и возвращает указатель на его начало и длину. С использованием функции " вычеркнуть" все симметричные фрагменты из строки.
Определить структурированный тип, определить набор функций для работы с массивом структур. В структурированной переменной предусмотреть способ отметки ее как не содержащей данных (т.е. "пустой"). Функции должны работать с массивом структур или с отдельной структурой через указатели, а также при необходимости возвращать указатель на структуру. В перечень функций входят:
- " очистка" структурированных переменных ;
- поиск свободной структурированной переменной ;
- ввод элементов (полей) структуры с клавиатуры;
- вывод элементов (полей) структуры с клавиатуры;
- поиск в массиве структуры и минимальным значением заданного поля;
- сортировка массива структур в порядке возрастания заданного поля (при сортировке можно использовать тот факт, что в Си++ разрешается присваивание структурированных переменных);
- поиск в массиве структур элемента с заданным значением поля или с наиболее близким к нему по значению.
- удаление заданного элемента;
- изменение (редактирование) заданного элемента.
- вычисление с проверкой и использованием всех элементов массива по заданному условию и формуле (например, общая сумма на всех счетах) - дается индивидуально.
Перечень полей структурированной переменной:
1. Фамилия И.О., дата рождения, адрес.
2. Фамилия И.О., номер счета, сумма на счете, дата последнего изменения.
3. Номер страницы, номер строки, текст изменения строки, дата изменения.
4. Название экзамена, дата экзамена, фамилия преподавателя, количество оценок, оценки.
5. Фамилия И.О., номер зачетной книжки, факультет, группа,
6. Фамилия И.О., номер читательского билета, название книги, срок возврата.
7. Наименование товара, цена, количество, процент торговой надбавки.
8. Номер рейса, пункт назначения, время вылета, дата вылета, стоимость билета.
9. Фамилия И.О., количество оценок, оценки, средний балл.
10. Фамилия И.О., дата поступления, дата отчисления.
11. Регистрационный номер автомобиля, марка, пробег.
12. Фамилия И.О., количество переговоров (для каждого - дата и продолжительность).
13. Номер телефона, дата разговора, продолжительность, код города.
14. Номер поезда, пункт назначения, дни следования, время прибытия, время стоянки.
15. Название кинофильма, сеанс, стоимость билета, количество зрителей.
Разработать две функции, одна из которых вводит с клавиатуры набор данных в произвольной последовательности и размещает в памяти в переменном формате. Другая функция читает эти данные и выводит на экран. Программа запрашивает и размещает в памяти несколько наборов данных при помощи первой функции, а затем читает их и выводит на экран при помощи второй. Размещение данных производить в статическом массиве байтов фиксированной размерности с контролем его переполнения.
1. Прямоугольная матрица вещественных чисел, предваренная двумя целыми переменными - размерностью матрицы.
2. Последовательность тестовых строк. Каждая строка ограничена символом \0, последовательность - двумя символами \0.
3. Последовательность строк символов. Каждая строка предваряется байтом - счетчиком символов. Ограничение последовательности - счетчик со значением 0.
4. Упакованный массив целых переменных. Байт-счетчик, имеющий положительное значение n, предваряет последовательность из n различных целых переменных, байт-счетчик, имеющий отрицательное значение -n, обозначает n подряд идущих одинаковых значений целой переменной. Пример:
-исходная последовательность: 2 3 3 3 5 2 4 4 4 4 4 8 -6 8
-упакованная последовательность: (1) 2 (-3) 3 (2) 5 2 (-5) 4 (3) 8 -6 8
5. Упакованная строка, содержащая символьное представление целых чисел. Все символы строки, кроме цифр, помещаются в последовательность в исходном виде. Последовательность цифр преобразуется в целую переменную, которая записывается в упакованную строку, предваренная символом \0. Конец строки - два подряд идущих символа \0. Пример:
-исходная строка: "aa2456bbbb6665"
-упакованная строка: 'a' 'a' '\0' 2456 'b' 'b' 'b' 'b' '\0' 6665 '\0' '\0'
6. Произвольная последовательность переменных типа char, int и long. Перед каждой переменной размещается байт, определяющий ее тип (0-char, 1-int, 2-long). Последовательность вводится в виде целых переменных типа long, которые затем " укорачиваются" до минимальной размерности без потери значащих цифр.
Разработать функцию с переменным количеством параметров. Для извлечения параметров из списка использовать технологию программирования областей памяти переменного формата, описанную в 4.4.
1. Целая переменная - счетчик, затем последовательность вещественных переменных. Функция возвращает сумму переменных.
2. Последовательность целых положительных переменных, ограниченная переменной со значением -1. Функция возвращает максимальную из них.
3. Последовательность переменных различных типов. Перед каждой переменной находится целая переменная - идентификатор типа : 1-целое, 2-длинное целое, 3-вещественнное, 0-конец последовательности. Функция возвращает сумму значений параметров.
4. Первый параметр - строка, в которой каждый символ " *" обозначает место включения строки, являющейся очередным параметром. Функция выводит на экран полученный текст.
5. Первый параметр - форматная строка, в которой каждая цифра обозначает тип очередного параметра : 1-целое, 2-длинное целое, 3-вещественнное. Функция возвращает сумму значений параметров.
6. Каждый параметр - строка, последний параметр - NULL. Функция возвращает строку в динамической памяти, содержащую объединение строк-параметров.
7. Последовательность вещественных положительных переменных, ограниченная переменной со значением -1. Функция возвращает динамический массив, содержащий значения этих переменных.
8. Последовательность указателей на вещественные переменные, ограниченная NULL.. Функция возвращает динамический массив указателей на эти переменные.
9. Последовательность вещественных массивов. Сначала идет целый параметр - размерность массива (int), затем непосредственно последовательность значений типа double. Значение целого параметра - 0 обозначает конец последовательности. Функция возвращает сумму всех элементов.
10. Последовательность вещественных массивов. Сначала идет целый параметр - размерность массива (int), затем указатель на массив значений типа double (имя массива ). . Значение целого параметра - 0 обозначает конец последовательности.
1. Программа умножения целых переменных произвольной длины с использованием операций сложения и сдвига (проверить на переменных типа long).
2. Программа деления целых чисел произвольной длины с использованием операций вычитания и проверки на знак результата (проверить на переменных типа long).
3. Программа умножения чисел произвольной длины, представленных непосредственно строками цифр:
char a1[]="364543453";
char s[20];
add (a1,"345353",s);
4. Программа сложения и вычитания чисел произвольной длины, представленных непосредственно строками цифр:
char a1[]="364543453";
char s[20];
add (a1,"345353",s);
5. Кодирование и декодирование строки символов, содержащих цифры, в последовательность битов. Десятичная цифра кодируется 4 битами - одной шестнадцатеричной цифрой. Цифра F обозначает, что за ней следует байт (2 цифры) с кодом символа, отличного от цифры. Разработать функции кодирования и декодирования с определением процента уплотнения.
6. Число произвольной длины представлено в двоично-десятичной системе. Десятичная цифра кодируется 4 битами - одной шестнадцатеричной цифрой. Цифра F обозначает конец последовательности цифр. Разработать функции сложения и вычитания чисел в такой форме представления.
7. Число произвольной длины представлено в двоично-десятичной системе. Десятичная цифра кодируется 4 битами - одной шестнадцатеричной цифрой. Цифра F обозначает конец последовательности цифр. Разработать функцию умножения чисел в такой форме представления.
8. Последовательность целых переменных различной размерности типов char, int, long кодируется следующим образом : перед каждым числом размещаются 2 бита, определяющие размерность числа - 00 - конец последовательности, 01 - char, 10 - int, 11 - long. Разработать функции упаковки и распаковки массива переменных типа long с учетом количества значащих битов и с определением коэффициента уплотнения. Пример : 01 xxxxxxxx 10 xxxxxxxxxxxxxxxx 01xxxxxxxx 00
9. Последовательность целых переменных различной размерности кодируется следующим образом : перед каждым числом размещаются 5 битов, определяющие количество битов в следующем за ним целом числе. 00000 - конец последовательности.
Задание рассчитано на выполнение в течение 2-3 лабораторных занятий и предполагает разработку функций поддержки индексного файла с заданной структурой, заполнение файла данных, построение индекса и выполнение операций поиска, добавления, удаления и обновления записи.
1. Построение двухуровнего индекса. Выполнение операций над записями файла с коррекцией таблиц верхнего и нижнего уровней. Сохранение индекса нижнего уровня в файле. Построение индекса верхнего уровня при открытии индекса.
2. Индексный файл с областью переполнения. Выполнение операций с коррекцией области переполнения и основной области. При превышении размера области переполнения 10% от размера основной области производится операция повторного построения основной области.
3. Индексный файл с индексируемым полем переменной длины. Значение поля хранится в отдельном файле записей переменной длины, а запись файла данных содержит ссылку на него.
4. Файл содержит записи переменной длины текстовые строки, организованные в виде дерева: каждая запись содержит ссылки (адреса в файле) двух других записей. Реализовать операции двоичного поиска и включения записи в файл без чтения в память всего файла. Обеспечить сохранение сбалансированности дерева.
5. Индексный файл представляет собой индексное дерево, которое сохраняется в файле записей фиксированной длины "естественным" образом. Записи индексного файла содержат номера записей файла данных. Индекс считывается в память полностью, для записей первых m уровней (задается при загрузке) обеспечивается считывание и связывание записей файла данных для ускорения поиска по индексу. Обеспечить сохранение сбалансированности дерева.
6. Ключом в файле записей фиксированной длины является поле name[30], содержащее строку ограниченного размера. Реализовать функции добавления, удаления и поиска записей по ключу с использованием метода расстановки ключей (хеширования). Для исключения коллизий использовать область переполнения с организацией списков записей. Предложить функцию расстановки и проверить ее эффективность.
7. Ключом в файле записей является целая переменная. Используется хеширование на основе функции середины квадрата. Размер таблицы изменяется: 16,32,64 и так далее записей. При отсутствии в области переполнения свободного места создается новый файл вдвое большего размера и в него переписываются записи из старого файла с использованием новой функции размещения (количество разрядов в "середине квадрата" увеличивается на 1).
8. Предложить и реализовать способ организации индекса для файла данных, содержащих координаты точек на плоскости.
Варианты 1-6. Используя программу - заготовку menu0.cpp, содержащую функцию вывода меню
int MENU(int x0, int y0 , char *s , char *m [])
// Возвращает номер выбранной строки меню
// x0,y0 - координаты окна меню
// s0 - надпись на рамке меню
// m - массив указателей на строки меню
реализовать функции редактирования файла. Для вывода на экран редактируемого текста использовать функцию MENU с массивом указателей на строки редактируемого текста. Строки редактируемого текста разместить в динамической памяти. В программе предусмотреть меню основных операции, в которое включить - просмотр текста, добавление строки к тексту и действие над текстом из варианта задания :
1. Удаление, вставка, перемещение строки.
2. Отметка начала и конца блока, удаление, перемещение блока строк.
3. Редактирование выбранной строки: стирание и вставка символов.
4. Поиск по образцу в выделенном блоке и замена на другой образец.
5. Отметка начала и конца блока, копирование блока строк, запись блока в отдельный файл.
6. Чтение файла и включение текста после текущей строки.
7. Функция получает строку текста и возвращает динамический массив указателей на слова. Каждое слово копируется в отдельный массив в динамической памяти.
8. Функция получает массив указателей на строки и возвращает строку в динамической памяти, содержащую объединенный текст из входных строк.
9. Функция получает массив вещественных чисел. Размерность массива - формальный параметр. Функция создает динамический массив указателей на переменные в этом массиве и выполняет сортировку указателей (без перемещения указуемых переменных).
10. Функция получает на вход и возвращает в качестве результата динамический массив указателей на упорядоченные по алфавиту строки и включает в нее копию заданной строки с сохранением упорядоченности. Если после включения размерность массива становится кратной N (k=N,2N,4N....), то создается новый массив указателей, размерность которого в два раза больше, В него переписываются указатели из старого, а старый разрушается.
1. Включение элементов в двусвязный циклический список с сохранением упорядоченности.
2. Включение элементов в односвязный список с сохранением упорядоченности.
3. Включение элементов в двусвязный список с сохранением упорядоченности.
4. Сортировка односвязного циклического списка путем исключения первого элемента и включения в новый список с сохранением его упорядоченности.
5. Сортировка односвязного списка путем исключения элемента с минимальным значением и включения его в начало нового списка.
6. Сортировка двусвязного циклического списка путем перестановки соседних элементов.
7. Элемент односвязного списка содержит указатель на строку в динамической памяти. Написать функции просмотра списка и включения очередной строки с сохранением упорядоченности по длине строки и по алфавиту.
8. Элемент односвязного списка содержит массив из 4 целых переменных. Массив может быть заполнен частично. Все значения целых переменных хранятся в порядке возрастания. Написать функцию включения значения в элемент списка с сохранением упорядоченности. При переполнении массива создается новый элемент списка и в него включается половина значений из переполненного.
9. Элемент двусвязного циклического списка содержит указатель на строку в динамической памяти. Написать функции просмотра списка и включения очередной строки с сохранением упорядоченности по длине строки и по алфавиту.
10. Элемент двусвязного циклического списка содержит массив из 4 целых переменных. Массив может быть заполнен частично. Все значения целых переменных хранятся в порядке возрастания. Написать функцию включения значения в элемент списка с сохранением упорядоченности. При переполнении массива создается новый элемент списка и в него включается половина значений из переполненного.
11. Элемент односвязного списка содержит указатель на строку. Вставить строку в конец списка. В список помещается копия входной строки в динамической памяти.
12. Элемент односвязного списка содержит указатель на строку. Строки упорядочены по возрастанию. Вставить строку в список с сохранением упорядоченности. В список помещается копия входной строки в динамической памяти.
13. Элемент односвязного списка содержит указатель на строку. Отсортировать список путем исключения максимального элемента и включения в начало нового списка.
14. Элемент двусвязного циклического списка содержит указатель на строку. Строки упорядочены по возрастанию. Вставить строку в список с сохранением упорядоченности. В список помещается копия входной строки в динамической памяти.
15. Элемент односвязного списка содержит массив указателей на строки. Строки читаются из текстового файла функцией fgets и указатели на них помещаются в структуру данных. Элементы списка и сами строки должны создаваться в динамической памяти в процессе чтения файла. В исходном состоянии структура данных - пуста.
1. Используя фрагмент программы просмотра каталога, приведенный ниже, написать рекурсивную функцию поиска файлов с одинаковыми именами во всех подкаталогах диска. (Структуру данных, содержащую имена найденных файлов можно реализовать в виде глобального односвязного списка).
#include <stdio.h>
#include <dir.h>
#define FA_DIREC 0x10
void showdir(char *dir)
{ struct ffblk DIR; int done; char irname[40];
strcpy(dirname,dir);
strcat(dirname,"*.*");
done=findfirst(dirname,&DIR,FA_DIREC);
while(! done)
{ if (DIR.ff_attrib & FA_DIREC)
{
if (DIR.ff_name[0] != '.')
printf("Подкаталог %s\n",DIR.ff_name);
}
else
printf("Файл %s%s\n",dir,DIR.ff_name);
done=findnext(&DIR);
} }
void main() showdir("E:\\a\\b\\"); }
2. Реализовать рекурсивный алгоритм построения цепочки из имеющегося набора костей домино.
3. Рекурсивная программа обхода дерева и изображения его вершин на экране. Для равномерного размещения вершин н экране программа должна "знать" для каждой вершины интервал позиций экрана, который выделен для данного поддерева и количество вершин в нем. Само дерево можно задать статически (инициализация).
4. Расстояния между городами заданы матрицей (Если между городами i,j есть прямой путь с расстоянием N, то элементы матрицы A(i,j) и A(j,i) содержат значение N, иначе 0 ). Написать программу поиска минимального пути для произвольной пары городов.
5. Расстояния между городами заданы матрицей (Если между городами i,j есть прямой путь с расстоянием N, то элементы матрицы A(i,j) и A(j,i) содержат значение N, иначе 0 ). Написать программу поиска минимального пути обхода всех городов без посещения дважды одного и того же города (задача коммивояжера).
6. Задача о восьми ферзях. Разместить на шахматной доске восемь ферзей так, чтобы они не находились " под боем" .
7. Разместить на шахматной доске максимальное количество коней так, чтобы они не находились друг у друга " под боем" .
Программа должна содержать функцию обхода дерева с выводом его содержимого, функцию добавления вершины дерева (ввод), а также указанную в варианте функцию.
1. Вершина дерева содержит указатель на строку. Строки в дереве не упорядочены. Функция включает вершину в дерево с новой строкой в ближайшее свободное к корню дерева место. (То есть дерево должно иметь ветви, отличающиеся не более чем на 1).
2. Вершина двоичного дерева содержит массив целых и два указателя на правое и левое поддерево. Массив целых в каждом элементе упорядочен, дерево в целом также упорядочено. Функция включает в дерево целую переменную с сохранением упорядоченности.
3. Вершина двоичного дерева содержит указатель на строку и указатели на правое и левое поддерево. Строки в дереве упорядочены по возрастанию. Написать функций включения строки и получения указателя на строку по заданному номеру, который она имеет в упорядоченной последовательности обхода дерева.
4. Элемент дерева содержит либо данные (строка ограниченной длины), либо указатели на правое и левое поддеревья. Строки в дереве упорядочены. Написать функцию включения новой строки. (Обратить внимание на то, что элемент с указателями не содержит данных, и при включении новой вершины вершину с данными следует заменить на вершину с указателями).
5. Вершина дерева содержит целое число и массив указателей на поддеревья. Целые в дереве не упорядочены. Функция включает вершину в дерево с новой целой переменной в ближайшее свободное к корню дерева место. (То есть дерево должно иметь ветви, отличающиеся не более чем на 1).
6. Вершина дерева содержит два целых числа и три указателя на поддеревья. Данные в дереве упорядочены. Написать функцию включения нового значения в дерево с сохранением упорядоченности.
7. Вершина дерева содержит указатель на строку и N указателей на потомков. Функция помещает строки в дерево так, что строки с меньшей длиной располагаются ближе к корню. Если новая строка " проходит" через вершину, в которой находится более длинная строка, то новая занимает место старой, а алгоритм включения продолжается для старой строки.
Для заданной в варианте структуры данных, каждый элемент которой содержит указатели на элементы произвольного типа void*, написать итератор. Проверить его работу на примере вызова итератора для структуры данных с соответствующими элементами и конкретной функцией.
1. Односвязный список, элемент которого содержит указатель типа void* на элемент данных. Функция включения последним и итератор сортировки методом вставок: исключается первый и включается в новый список с порядке возрастания. Проверить на примере элементов данных - строк и функции сравнения strcmp.
2. Дерево, каждая вершина которого содержит указатель на элемент данных void* и не более 4 указателей на поддеревья. Итератор поиска первого подходящего firstthat и функция включения в поддерево с минимальной длиной ветви. Проверить на примере элементов данных - строк и функции проверки на длину строки - не менее 10 символов.
3. Динамический массив указателей типа void*, содержащий указатели на упорядоченные элементы данных. Итераторы включения с сохранением упорядоченности и foreach. Предусмотреть увеличение размерности динамического массива при включении данных. Проверить на примерах элементов данных типов int и float (2 проверки).
4. Двусвязный циклический список, элемент которого содержит указатель типа void* на элемент данных. Итераторы foreach и включения с сохранением упорядоченности. Проверить на примере элементов данных структурированного типа, содержащих фамилию, год рождения и номер группы и функций сравнения по году рождения и по фамилии.
5. Двоичное дерево, каждая вершина которого содержит указатель типа void*. Итераторы foreach , двоичного поиска и включения с сохранением упорядоченности. Проверить на примере элементов данных структурированного типа, содержащих фамилию, год рождения и номер группы и функций сравнения по году рождения и по фамилии.
6. Динамический массив указателей типа void* на неупорядоченные элементы данных. Итератор поиска минимального элемента. Проверить на примере элементов данных структурированного типа, содержащих фамилию, год рождения и номер группы и функций сравнения по году рождения и по фамилии.
Разработать заданные функции для иерархической (двухуровневой) структуры данных.
1. Список - элемент содержит статический массив указателей на упорядоченные строки. Включение с сохранением упорядоченности. Если после включения строки массив заполняется полностью, то создается еще один элемент списка с массивом указателей, в который переписывается половина указателей из старого.
2. Список, каждый элемент которого содержит динамический массив указателей на строки, упорядоченные по длине. Размерность массива в каждом следующем элементе в 2 раза больше, чем в предыдущем. Строка включается в структуру данных с сохранением упорядоченности. Если строка включается в заполненный массив, то последний указатель перемещается в следующий элемент и так далее).
3. Двухуровневый массив указателей на упорядоченные строки. Массив верхнего уровня - статический, массивы нижнего уровня - динамические. Включение строки с сохранением упорядоченности. Если после включения строки массив заполняется полностью, то создается еще один массив указателей, в который переписывается половина указателей из старого.
4. Двухуровневый массив указателей на строки, упорядоченные по длине. Размерность каждого следующего массива нижнего уровня в 2 раза больше предыдущего. Строка включается в структуру данных с сохранением упорядоченности. Если строка включается в заполненный массив, то последний указатель перемещается в следующий элемент и так далее).
5. Список - элемент содержит статический массив указателей на строки. Включение новой строки - последней. Сортировка выбором - в старой структуре данных выбирается минимальная строка и включается последней в новую структуру данных.
6. Двухуровневый массив указателей на строки. Массив верхнего уровня - статический, массивы нижнего уровня - динамические. Включение новой строки - последней. Сортировка выбором - в старой структуре данных выбирается минимальная строка и включается последней в новую структуру данных.
7. Дерево, вершина которого содержит статический массив указателей на строки и N указателей на потомков.
Указанные варианты заданий реализовать с использованием позиционирования указателя в текстовом файле и массива указателей, без загрузки самого текстового файла в память.
1. Сортировка строк файла по длине и по алфавиту и вывод результата в отдельный файл.
2. Программа-интерпретатор текста. Текстовый файл разбит на именованные модули. Каждый модуль может иметь вызовы других текстовых модулей. Требуется вывести текст модуля main с включением текстов других модулей в порядке вызова:
.
#aaa
{
Произвольные строки модуля текста ааа
}
#ппп
{
Произвольные строки текста
#aaa // Вызов модуля текста с именем aaa
Произвольные строки текста
}
#main
Основной текст с вызовами других модулей
3. Программа - редактор текста с командами удаления, копирования, и перестановки строк, с прокруткой текста в обоих направлениях (исходный файл при редактировании не меняется).
4. Программа - интерпретатор текста, включающего фрагменты следующего вида :
.
#repeat 5
Произвольный текст
#end
При просмотре файла программа выводит его текст, текст фрагментов "#repeat - #end" выводится указанное количество раз. Фрагменты могут быть вложенными.
5. Программа просмотра блочной структуры Си-программы с командами вывода текущего блока, входа в n-ый по счету вложенный блок и выхода в блок верхнего уровня.
6. Программа построчного сравнения двух файлов с выводом групп строк, вставленных или удаленных из второго файла относительно первого.
7. Программа просмотра текстового файла по предложениям. Предложением считается любая последовательность слов, ограниченная точкой, после которой идет большая буква или конец строки. Программа выводит на экран любой блок с n-го по m-ое предложение.
8.Программа просмотра текстового файла по абзацам. Абзацем считается любая последовательность строк, ограниченная пустой строкой. Программа выводит на экран любой абзац по номеру.
9. Программа составляет словарь терминов. Каждый термин - слово, записанное большими (строчными) буквами.
1. Файл записей переменной длины перед каждой записью содержит целое, определяющее длину этой записи. Написать функции ввода и вывода записи в такой файл. Функция ввода (чтения) должна возвращать размер очередной прочитанной записи. Использовать функции для работы с двумя файлами - строк и динамических массивов целых чисел.
2. Программа создает в файле массив указателей фиксированной размерности на строки текста. Размерность массива находится в начале файла, сами строки также хранятся в файле в виде записей переменной длины. Написать функции чтения/записи строки из файла по заданному номеру.
3. Программа переписывает дерево с ограниченным количеством потомков из памяти в файл записей фиксированной длины, заменяя указатели на вершины номерами записей в файле. Затем выполняет обратную операцию.
4. Дерево представлено в файле записей фиксированной длины естественным образом: если вершина дерева в файле находится в записи под номером N, то ее потомки - под номерами 2N и 2N+1. Корень дерева - запись с номером 1. Написать функции включения в дерево с сохранением упорядоченности и обхода дерева (вывод упорядоченных записей). (Необходимо учесть, что несуществующие потомки должны быть записями специального вида (например, пустой строкой)).
5. Упорядоченные по возрастанию строки хранятся в файле в виде массива указателей (см.вар.2). Написать функции включения строки в файл и вывода упорядоченной последовательности строк (просмотр файла).
6. Для произвольного текстового файла программа составляет файл записей фиксированной длины, содержащий файловые указатели на строки (см. л/р 6, вар.3). Используя массив указателей на строки из этого файла программа производит логическое удаление, перестановку и сортировку строк, не меняя самого текстового файла.
7. Выполнить задание 3 применительно к графу, представленному списковой структурой.
8. Составить файл записей фиксированной длины, в котором группы записей связаны в односвязные списки (например, списочный состав студентов различных групп).
Разработать резидентную программу в DOS. Программа должна обеспечивать включение и отключение (если необходимо, с восстановлением содержимого экрана) заданного процесса по комбинации " горячих клавиш" ALT/?. Процесс, связанный с изменением содержимого экрана, должен начинаться при отсутствии ввода с клавиатуры в течение заданного интервала и прекращаться при начале ввода. Изменение следующего слова или буквы должно производиться только после завершения аналогичного процесса над предыдущим словом или буквой.
1. Подсчет интенсивности работы с клавиатурой и вывод сообщения "не торопись" при превышении интенсивности ввода выше заданной. Строка отображается 2 секунды и исчезает.
2. Осыпание букв и цифр на экране (последовательное перемещение каждой буквы и цифры по всем строкам сверху вниз).
3. Вывод окна с сохранением содержимого, перемещение окна по всем направлениям, (с использованием " горячих клавиш" ) и уничтожение окна с восстановлением содержимого экрана.
4. Программа запоминает последние n (100-200) интервалов нажатия клавиш и по " горячей клавише" выводит строку с математическим ожиданием и дисперсией времени нажания. Через 3 секунды строка стирается.
5. Поиск в видеопамяти идентификаторов (цепочка букв) и перестановка первых и последних букв. Измененные слова запоминаются и в дальнейшем уже не меняются.
6. Бегающий по экрану символ, отражающийся от границ экрана и от всех символов - не пробелов.
7. Поиск в видеопамяти идентификаторов (цепочка букв) и замена строчных букв на прописные и наоборот.
8. " Буквоед" - символ, бегающий по экрану по диагонали. При попадании на букву или цифру " выедает" все слово, заменяя его на пробелы.
9. " Анти-комментатор" . При обнаружении комментариев ( //..... или /*.....*/) удаляет их. При удалении комментария вида /*...*/ сдвигает текст вверх-влево (либо заливает определенным цветом).
10. Автоматическая подсветка блока. Программа обнаруживает на экране последователь-ности, заключенные в фигурные скобки ( {...} ) и меняет цвет фона внутри каждого блока в зависимости от уровня вложенности.
Заголовочные файлы
#include <iostream.h> // Стандартные потоки ввода-вывода:
// ios,istream,ostream,iostream
#include <fstream.h> // Файловые потоки ввода-вывода:
// ifstream,ofstream,fstream
#include <iomanip.h> // Манипуляторы
#include <strstream.h> // Резидентные потоки ввода-вывода:
// istrstream,ostrstream,strstream
Заголовочные файлы и библиотеки
В Си имеется общая технология, которая касается как организации модульных программ, так и библиотек. Любой модуль, который претендует на дальнейшее использование через обращение к собственным внешним переменным и вызов собственных внешних функций, должен иметь некоторое описание своего интерфейса. Оно заключается в составлении заголовочного файла (файла с расширением -".h" ), который используется другими модулями. Заголовочный файл должен включать в себя:
-определение используемых типов данных в формальных параметрах и результатах функций с использованием оператора typedef ;
-объявления внешних переменных и функций модуля, к которым возможно обращение.
#include <alloc.h> - заголовочный файл из системного каталога
#include "myhead.h" - заголовочный файл из текущего(явно указанного) каталога
Процесс подготовки библиотеки включает в себя следующие шаги:
-создание заголовочного файла, содержащего определения используемых типов данных и объявления внешних функций и переменных библиотеки;
-создание модуля, включающего определение функций и переменных библиотеки и трансляция его в объектный модуль;
-включение объектного модуля в библиотеку.
ЗаписьKлючИндекс
С точки зрения внешнего пользователя файл данных состоит из записей. В простейшем случае это записи фиксированной длины, которым с Си соответствует понятие структуры. Элементы структуры в файле называются полями записи. Файл представляет собой множество таких записей, имеющих некоторый физический (реальный) порядок расположения в файле и физические (реальные) адреса. С точки зрения внешнего пользователя файл выглядит как таблица, столбцам которой соответствуют поля записей.
Заметим, что такая картина соответствует логической организации файла, то есть представлению его в программе. В действительности способ размещения записей в файле может быть различным. Иногда в записи можно выделить поле, которое однозначно идентифицирует запись в файле: то есть в файле принципиально не может быть двух записей с одинаковым значением этого поля, которое в таком случае называется ключевым или просто ключом . В некоторых случаях ключом может являться группа из нескольких полей, то есть запись идентифицируется совокупностью значений, записанных в этой группе полей.
Житейский смысл алгоритмов и их фрагментов
Можно сказать, что одну и ту же вещь человек видит по-разному в зависимости от того, проникает он в суть явления или скользит по поверхности. В последнем случае он видит только то, что есть, сумму отдельных элементов, а не качественное поведение предмета в целом. В первом случае человек знает цель всего происходящего, поэтому и воспринимает явление в совокупности. Иначе говоря, прошлый опыт раскрашивает восприятие той или иной вещи совершенно другими красками. Сказанное имеет непосредственное отношение к программированию. Любая конструкция языка имеет для программиста смысл больший, нежели просто перечень имеющихся в ней операций или операторов. Смысл ее заключается в результате производимых действий. С этой точки зрения результат любой программы является теоремой, а ее доказательством - процесс ее написания. Доказать, что данная конструкция языка делает то, что она действительно делает, можно разными способами. Можно убедиться на конкретном примере (что не всегда доказательно), можно использовать житейскую или более-менее формальную логику умозаключений (что наиболее естественно и просто), можно применять формальные методы (что, на самом деле, ценно только с теоретической точки зрения), можно пользоваться аналогиями из других сфер деятельности. Последнее на самом деле - самое убедительное, поскольку логика программирования отражает логику формально-логических отношений в окружающем мире.
Возьмем простой пример. Определим, что делает программа со значениями переменных a,b .
a=5; b=6;
c=a;
a=b;
b=c;
-на примере конкретных значений видно, что переменные меняются своими значениями, но практика показывает, что не все это замечают ;
-формально-логическое объяснение : если переменная а переписывается сначала в c , а потом в b , а перед этим b переписывается в a - то они " меняются местами" ;
-житейская аналогия - поменять содержимое двух стаканов, не смешивая, можно только с использованием третьего (в данном случае - c ) .
Как бы там ни было, результат работы этого фрагмента - обмен значениями двух переменных с использованием третьей, является " медицинским фактом" , как сказал бы О.Бендер.