Программное обеспечение. Основные этапы решения задач на ЭВМ. Жизненный цикл программного средства



бет2/5
Дата28.06.2016
өлшемі475.5 Kb.
#162860
түріПрограмма
1   2   3   4   5

Цикл с постусловием. Цикл с постусловием — цикл, в котором условие проверяется после выполнения тела цикла. Отсюда следует, что тело всегда выполняется хотя бы один раз. В языке Си — do…while.
На языке Си:
do { <тело цикла> } while(<условие>)


В трактовке условия цикла с постусловием в разных языках есть различия. В Си и его потомках <условие> - условие продолжения (цикл завершается, когда условие ложно, такие циклы иногда называют «цикл пока»)

Цикл cо счётчиком. Цикл со счётчиком — цикл, в котором некоторая переменная изменяет своё значение от заданного начального значения до конечного значения с некоторым шагом, и для каждого значения этой переменной тело цикла выполняется один раз. В большинстве процедурных языков программирования реализуется оператором for, в котором указывается счётчик (так называемая «переменная цикла»), требуемое количество проходов (или граничное значение счётчика) и, возможно, шаг, с которым изменяется счётчик.


Цикл со счётчиком всегда можно записать как условный цикл, перед началом которого счётчику присваивается начальное значение, а условием выхода является достижение счётчиком конечного значения; к телу цикла при этом добавляется оператор изменения счётчика на заданный шаг. Однако специальные операторы цикла со счётчиком могут эффективнее транслироваться, так как формализованный вид такого цикла позволяет использовать специальные процессорные команды организации циклов.

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

for (i = 0; i < 10; ++i) { ... тело цикла }

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

i = 0; while (i < 10) { ... тело цикла ++i; }

То есть в конструкции for сначала пишется произвольное предложение инициализации цикла, затем — условие продолжения и, наконец, выполняемая после каждого тела цикла некоторая операция (это не обязательно должно быть изменение счётчика; это может быть правка указателя или какая-нибудь совершенно посторонняя операция). Для языков такого вида вышеописанная проблема решается очень просто: переменная-счётчик ведёт себя совершенно предсказуемо и по завершении цикла сохраняет своё последнее значение.

Вложенные циклы

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

Полное число исполнений тела внутреннего цикла не превышает произведения числа итераций внутреннего и всех внешних циклов. Например взяв три вложенных друг в друга цикла, каждый по 10 итераций, получим 10 исполнений тела для внешнего цикла, 100 для цикла второго уровня и 1000 в самом внутреннем цикле.

Одна из проблем, связанных с вложенными циклами — организация досрочного выхода из них. Во многих языках программирования есть оператор досрочного завершения цикла (break в Си, exit в Турбо Паскале, last в Perl и т. п.), но он, как правило, обеспечивает выход только из цикла того уровня, откуда вызван. Вызов его из вложенного цикла приведёт к завершению только этого внутреннего цикла, объемлющий же цикл продолжит выполняться. Проблема может показаться надуманной, но она действительно иногда возникает при программировании сложной обработки данных, когда алгоритм требует немедленного прерывания в определённых условиях, наличие которых можно проверить только в глубоко вложенном цикле.

Решений проблемы выхода из вложенных циклов несколько.


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

  • Альтернатива — использовать штатные средства завершения циклов, в случае необходимости устанавливая специальные флаги, требующие немедленного завершения обработки. Недостаток — усложнение кода, снижение производительности без каких-либо преимуществ, кроме теоретической «правильности» из-за отказа от goto.

  • Размещение вложенного цикла в процедуре. Идея состоит в том, чтобы всё действие, которое может потребоваться прервать досрочно, оформить в виде отдельной процедуры, и для досрочного завершения использовать оператор выхода из процедуры (если такой есть в языке программирования). В языке Си, например, можно построить функцию с вложенным циклом, а выход из неё организовать с помощью оператора return. Недостаток — выделение фрагмента кода в процедуру не всегда логически обосновано, и не все языки имеют штатные средства досрочного завершения процедур.

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



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

Функция - это самостоятельная единица программы, созданная для решения конкретной задачи. Функция в языке С играет ту же роль, что и подпрограммы или процедуры в других языках. Функциями удобно пользоваться, например, если необходимо обработать один и тот же код программы. Как и переменные, функции надо объявлять (declare). Функцию необходимо объявить до её использования. Запомните это простое правило - сначала объяви, а потом используй. 

Каждая функция языка С имеет имя и список аргументов (формальных параметров). Функции могут возвращать значение. Это значение может быть использовано далее в программе. Так как  функция может вернуть какое-нибудь значение, то обязательно нужно указать тип данных возвращаемого значения. Если тип не указан, то по умолчанию предполагается, что функция возвращает целое значение (типа int). После имени функции принято ставить круглые скобки (это касается вызова функции её объявления и описания). В этих скобках перечисляются параметры функции, если они есть. Если у функции нет параметров, то при объявлении и при описании функции вместо <список параметров> надо поставить void - пусто.


Основная форма описания (definition) функции имеет вид:
тип <имя функции>(список параметров)
{        тело функции }

Объявление (прототип) функции имеет вид:

тип <имя функции>(список параметров);

Обратите внимание на то, что при описании функции после заголовка функции
тип <имя функции>(список параметров) 
точка с запятой не ставиться, а при объявлении функции точка с запятой ставиться.

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

<имя функции>(параметры);

или


<переменная>=<имя функции>(параметры);

При вызове функции так же ставиться точка с запятой.


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

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


#include
void main(void)                    // Точка входа в программу
{
      void function1(void);      // Объявление функции
      function1();              //  Вызов функции
}
/* Описание функции  */
void function1(void)           // Заголовок функции
{                                      // Начало тела функции      
      printf("Вызвали функцию\n");
}                                      //  Конец тела функции
В теле функции main() мы объявили функцию function1(), затем её вызвали.

Обратите внимание ещё на то, что тип возвращаемого значения у нашей функции void (пусто). Это значит, что функция не будет возвращать никакого значения.



Функции с параметрами.

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


Формальные и фактические параметры

Формальные параметры - это параметры которые мы объявляем в заголовке функции при описании.

Фактические параметры - это параметры которые мы подставляем при вызове функции.

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



    1. Регулярный тип (массивы). Описание массивов. Ввод и вывод элементов массива. Нахождение максимального (минимального) элемента массива.

Как известно, массив - это конечная совокупность данных одного типа. Можно говорить о массивах целых чисел, массивов символов и.т.д. Мы можем даже определить масссив, элементы которого - массивы( массив массивов), определяя, таким образм, многомерные массивы. Любой массив в программе должен быть описан: после имени массива добаляют квадратные скобки [], внутри которых обычно стоит число, показывающее количество элементов массива. Например, запись int x[10]; определяет x как массив из 10 целых чисел. В случае многомерных массивов показывают столько пар скобок , какова размерность массива, а число внутри скобок показывает размер массива по данному измерению. Например, описание двумерного массива выглядит так: int a[2][5];. Такое описание можно трактовать как матрицу из 2 строк и 5 столбцов. Для обрщения к некоторому элементу массива указывают его имя и индекс, заключенный в квадратные скобки(для многомерног массива - несколько индексов , заключенные в отдельные квадратные скобки): a[1][3], x[i] a[0][k+2]. Индексы массива в Си всегда начинаются с 0, а не с 1, т.е. описание int x[5]; порождает элементы x[0], x[1], x[2], x[3], x[4], x[5]. Индекс может быть не только целой константой или целой переменной, но и любым выражением целого типа. Переменная с индексами в программе используется наравне с простой переменной (например, в операторе присваивания, в функциях ввода- вывода). Начальные значения массивам в языке Си могут быть присвоены при компиляции только в том случае, если они объявлены с классом памяти extern или static, например:

         static int a[6]={5,0,4,-17,49,1};   


  
обеспечивает присвоения a[0]=5; a[1]=0; a[2]=4 ... a[5]=1. Как видите, для начального присвоения значений некоторому массиву надо в описании поместить справа от знака = список инициирующих значений, заключенные в фигурные скобки и разделенные запятыми. Двумерный массив можно инициировать так:

     static int matr[2][5] = {{3,4,0,1,2},{6,5,1,4,9}};  

Матрица хранится в памяти построчно, т.е. самый правый индекс в наборе индексов массива меняется наиболее быстро.
       Следующяя программа позволяет в целочисленном массиве найти разность максимального и минимального элемента . Обратите внимание, что функция fmax при первом обращении к ней дает максимальный элемент массива, а при повторном вызове -  минимальный, так как предварительно мы изменили знаки элементов на противоположные. Это изменение знаков учитывается при вызове функции printf. В языке Си отсутствует возможность динамически распределять память под массивы: надо при описании массива задать точно его размер. Но если тот же массив описывается еще раз в другой программе, размеры можно не указывать;достаточно после имени сохранить пару квадратных скобок, например int x[]. Если при вызове функции в качестве аргумента ей передается имя массива, то, в отличае от простых переменных, берется фактически адрес начала этого массива. Поэтому записи fmax(a, 10) и fmax(&a[0], 10) равносильны.

int fmax(x,n)


int x[],n;
{ int max, i=0; max=x[0];
while(i{ if(x[i]> max)
   max=x[i];
i++; }
return(max); }
#include
main()
{ static int a[10]={1,-2,3,-4,5,-6,7,-8,9,-13};
max=fmax(a,10);
i=0;
while(i<10)
{ a[i]=-a[i];
i++; } main=fmax(a,10); printf("макс = %d мин=%d\n",max,min); }

    1. Обработка матриц. Поиск заданного элемента в матрице.


Матрица – это одномерные и многомерные массивы, вощем смотри предыдущий вопрос про массивы

Задана матрица А(n,m). Для выполнения действий над элементами матрицы в соответствии с данными, приведенными в таблице написать программу на языке Pascal.

Количество строк n=20
Количество столбцов m=10
Результат – одномерный массив

Найти минимальный элемент в каждом столбце матрицы отдельно.

#include "stdio.h"

#include

#include
using namespace std;
#define n 20

#define m 10

int a[n][m];

int minA[m];

int i,j,min_;
int _tmain(int argc, _TCHAR* argv[])

{

cout << "Vvedite elementy massiva:";



for (i=0;i

for (j=0;j

a[i][j]=(float)rand()*m*n;

for (j=0;j

{

min_ = a[0][j];



for (i=1;i

if (a[i][j]

minA[j]=min_;

}

cout<<"Minimal elementy stolbtsov:";



for (j=0;j

cout<

getchar();

return 0;

}


    1. Работа с динамическими переменными. Динамические массивы.

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

Используя структурные типы, указатели и динамические переменные, можно создавать разнообразные динамические структуры памяти — списки, деревья, графы и т.д. (Особенности указателей в языках Си/Си++ позволяют, вообще говоря, строить динамические структуры памяти на основе статически объявленных переменных или на смеси статических и динамических переменных.) Идея организации всех динамических структур одна и та же. Определяется некоторый структурный тип T, одно или несколько полей которого объявлены указателями на тот же или некоторый другой структурный тип. В программе объявляется переменная var типа T (или переменная типа указателя на T в случае полностью динамического создания структуры). Имя этой переменной при выполнении программы используется как имя "корня" динамической структуры. При выполнении программы по мере построения динамической структуры запрашиваются динамические переменные соответствующих типов и связываются ссылками, начиная с переменной var (или первой динамической переменной, указатель на которую содержится в переменной var). Понятно, что этот подход позволяет создать динамическую структуру с любой топологией.

Наиболее простой динамической структурой является однонаправленный список (рисунок 1.1). Для создания списка определяется структурный тип T, у которого имеется одно поле next, объявленное как указатель на T. Другие поля структуры содержат информацию, характеризующую элемент списка. При образовании первого элемента ("корня") списка в поле next заносится пустой указатель (nil или NULL). При дальнейшем построении списка это значение будет присутствовать в последнем элементе списка

Рисунок 14.1. Однонаправленный список

Над списком, построенном в такой манере, можно выполнять операции поиска элемента, удаления элемента и занесение нового элемента в начало, конец или середину списка. Понятно, что все эти операции будут выполняться путем манипуляций над содержимым поля next существующих элементов списка. Для оптимизации операций над списком иногда создают вспомогательную переменную-структуру (заголовок списка), состоящую из двух полей — указателей на первый и последний элементы списка (рисунок 1.2). Для этих же целей создают двунаправленные списки, элементы которых, помимо поля next, включают поле previous, содержащее указатель на предыдущий элемент списка (рисунок 1.3)



Рисунок 14.2. Заголовок списка



Рисунок 14.3. Двунаправленный список



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

void* malloc(size_t r) - возвращает указатель на место в памяти, отведенное для объекта размера r ; если память недоступна – возвращает NULL;

void* calloc(size_t n,size_t r) - возвращает указатель на место в памяти, отведенное для массива из n объектов размера r ; если память недоступна - возвращает NULL;

void* realloc(void* p,size_t r) - изменяет размер объекта, на который указывает p , на r , возвращает новый указатель или NULL;

void free(void* p) - освобождает память, распределенную функциями calloc , malloc или realloc .

В С++ введены две унарные операции - new и delete - для динамического распределения памяти, свобождающие программиста от необходи мости явно использовать библиотечные функции malloc, calloc и free. Операции new имя типа или new имя типа [ инициализатор ] позволяют выделить и сделать доступным свободный участок памяти и возвращают адрес выделенного места. Если память недоступна - возвращают NULL. В выделенный участок заносится значение, определяемое инициализатором, который не является обязательным элементом и представляет собой выражение в круглых скобках. Например:

point=new int(15); h=new double(3.1415); string=new char[80];

Последний оператор отводит место в свободной памяти под массив из 80 элементов типа char . Указатель string содержит теперь адрес нулевого элемента массива. Для явного освобождения выделенного операцией new фрагмента памяти используется операция delete указатель , где указатель адресует освобождаемый участок памяти. Например, операторами

delete point; delete string;

освобождаются участки памяти, связанные с указателями point и string.

Си:

// размер масссива

#define SIZE 100

// объявляем

int *array;

// выделяем память

array = (int*)malloc( SIZE*sizeof(int) );

if (array == NULL){ // не хватило памяти

}

// обращаемся по индексам



array[10] = 10;

*(array+5) = 20;

// освобождаем память

free(array);

#undef SIZE


C++:

const int size = 100;

int *array = new int[size]; // выделяем память

array[10] = 20;

10[array] = 33; // :) - никогда так не делайте

delete[] array; // освобождаем память

array = NULL; // на всякий случай




1.11. Файловый ввод-вывод. Работа с текстовыми и двоичными файлами.

Язык программирования Си поддерживает множество функций стандартных библиотек для файлового ввода и вывода. Эти функции составляют основу заголовочного файла стандартной библиотеки языка Си <stdio.h>.

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

Потоковая модель файлового ввода-вывода была популяризирована во многом благодаря операционной системе Unix, написанной на языке Си. Большая функциональность современных операционных систем унаследовала потоки от Unix, а многие языки семейства языков программирования Си унаследовали интерфейс файлового ввода-вывода языка Си с небольшими отличиями (например, PHP). Стандартная библиотека C++ отражает потоковую концепцию в своем синтаксисе.

Файл открывается при помощи fopen, которая возвращает информацию потока ввода-вывода, прикрепленного к указанному файлу или другому устройству, с которого идет чтение (или в который идет запись). В случае неудачи функция возвращает нулевой указатель.

Функция freopen закрывает текущий файл, связанный с потоком fp, и переназначает этот поток в файл, определяемый path-именем. Эта функция обычно применяется для переадресации предоткрытых потоков stdin, stdout, stderr, stdaux, stdprn в файлы, определяемые пользователем. Новый файл, связанный с потоком, открывается в режиме mode.

Они определяются как:

FILE *fopen(const char *path, const char *mode);

FILE *freopen(const char *path, const char *mode, FILE *fp);

Функция fopen по сути представляет собой "обертку" более высокого уровня системного вызова open операционной системы Unix. Аналогично, fclose является оберткой системного вызова Unix close, а сама структура FILE языка Си зачастую обращается к соответствующему файловому дескриптору Unix. В POSIX-окружении[ Portable Operating System Interface for Unix — Переносимый интерфейс операционных систем Unix — набор стандартов, описывающих интерфейсы между операционной системой и прикладной программой.] функция fopen может использоваться для инициализации структуры FILE файловым дескриптором. Тем не менее, файловые дескрипторы как исключительно Unix-концепция не представлены в стандарте языка Си.



Параметр mode (режим) для fopen и freopen должен быть строковый и начинаться с одной из следующих последовательностей:

режим

описание

начинает с..

r

rb




открывает для чтения

начала

w

wb




открывает для записи (создает файл в случае его отсутствия). Удаляет содержимое и перезаписывает файл.

начала

a

ab




открывает для добавления (создает файл в случае его отсутствия)

конца

r+

rb+

r+b

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

начала

w+

wb+

w+b

открывает для чтения и записи. Удаляет содержимое и перезаписывает файл.

начала

a+

ab+

a+b

открывает для чтения и записи (добавляет в случае существования файла)

конца

Значение "b" зарезервировано для двоичного режима С. Стандарт языка Си описывает два вида файлов — текстовые и двоичные — хотя операционная система не требует их различать. Текстовый файл - файл, содержащий текст, разбитый на строки при помощи некоторого разделяющего символа окончания строки или последовательности (в Unix - одиночный символ перевода строки; в Microsoft Windows за символом перевода строки следует знак возврата каретки). При считывании байтов из текстового файла, символы конца строки обычно связываются (заменяются) с переводом строки для упрощения обработки. При записи текстового файла одиночный символ перевода строки перед записью связывается (заменяется) с специфичной для ОС последовательностью символов конца строки. Двоичный файл - файл, из которого байты считываются и выводятся в "сыром" виде без какого-либо связывания (подстановки).

При открытом файле в режиме обновления ( '+' в качестве второго или третьего символа аргумента обозначения режима) и ввод и вывод могут выполняться в одном потоке. Тем не менее, запись не может следовать за чтением без промежуточного вызова fflush или функции позиционирования в файле (fseek, fsetpos или rewind), а чтение не может следовать за записью без промежуточного вызова функции позиционирования в файле.

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

Закрытие файла осуществляется с помощью функции fclose . Функция fclose принимает один аргумент: указатель на структуру FILE потока для закрытия.

int fclose(FILE *fp);

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



Достарыңызбен бөлісу:
1   2   3   4   5




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет