Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

struct metropol InputDat(void)




 {

 struct metropol t;

printf("\nНазвание - ");                   scanf("%s",&t.name);

printf("Численность населения - "); scanf("%d",&t.population);

printf("Территория - ");                 scanf("%f",&t.territory);

return t;

 }

При пользовании программой следует иметь в виду, что при вводе строки функцией scanf() по формату %s ввод осуществляется до первого пробела. Так что если по ошибке ввести название города с пробелом, например, Буэнос Айрес, то в переменную t.name запишется только первое слово Буэнос, а второе останется во входном потоке, будет вводиться оператором scanf("%d",&t.population); и вызовет ошибку ввода из-за несоответствия формату целого числа. Для устранения ошибки можно использовать функцию gets() с которой можно познакомиться самостоятельно по справочной системе. Заметим также, что если при вводе после спецификации формата указать пробел, например: scanf("%d ",&t.population);, то функция после ввода числа будет пропускать все вводимые пробельные символы и завершит свою работу только по первому непробельному символу. Внешне это проявится в том, что по нажатию клавиши Enter после ввода численности не выведется предложение вводить территорию. Оно появится, только когда мы, не дождавшись предложения, введем величину территории.

 

void main(void)

{ FILE *f=fopen("city.dat","a");

   while(1)

   {

   work=InputDat();

   fwrite(&work,sizeof(metropol),1,f);

   printf("Выход - Esc");

   if(getch()==27)break;

   }

   fclose(f);

} Конец примера из файла structst.cpp

В главной программе мы вводим данные о городе и дописываем их в файл, пока пользователь не нажмет Esc. Второй параметр функции открытия равен “a”, поэтому при первом пуске программа создаст файл city.dat, а при каждом следующем пуске будет дописывать в него данные

1.7.2. Способы проверки на конец файла

Пусть файл открыт для чтения функцией

 FILE *FileName = fopen(“C:\temp\abc.txt”, “r”).

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

Такую проверку можно сделать функцией feof(FileName) после выполнения операции чтения данных. При нормальном выполнении чтения функция возвратит ноль, но если чтение закончилось неудачей из-за того, что все данные уже прочитаны, функция возвратит ненулевую константу EOF. Обратите внимание на отличие от аналогичной функции Паскаля. Там, если указатель текущей позиции находится в конце файла, функция eof () возвращает признак «Конец файла» не требуя предварительного выполнения неудачной попытки чтения из позиции, находящейся за пределами файла.

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

Существуют и другие варианты определения конца файла. Так функция fgets() чтения из файла строк текста возвращает количество прочитанных символов. Возврат ею значения 0 укажет на конец файла. Проверку завершения ввода, основанную на предварительном определении длины файла мы рассмотрим позже.

Рассмотрим решение задачи поиска города с максимальным населением в файле, созданном предыдущей программой:

# include < stdio. h > Начало примера из файла structmx.cpp

#include <conio.h>

Struct metropol

{

 char name[20]

 int population;

 float territory;

} work, max;

Офорляем вывод ланных функцией, получающей структуру как параметр:

Void OutputDat(struct metropol t)

 {

printf("\n Название - %s",t.name);

printf("\nЧисленность населения - %d ",t.population);

printf("\n Территория - %f\n",t.territory);

getch();

 }

Void main(void)

{ FILE *f=fopen("city.dat","r");

   fread(&max,sizeof(metropol),1,f);

   work=max;

   while(feof(f)==0)

   { OutputDat (work);

     fread (& work, sizeof (metropol),1, f); В результате последнего, ошибочного чтения в содержимое переменной work не изменится и не повлияет на содержимое max. Следующая после этого проверка feof(f)==0 завершит цикл.

  if (work. population > max. population) max = work;

   }

   fclose(f);

   printf("Город с наибольшим населением");

  OutputDat(max);

  } Конец примера из файла structmx.cpp.

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

 

 

1.7.3. Прямой доступ к файлам

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

Для чтения нужного элемента без считывания всех предыдущих предназначена функция i nt fseek (<указатель на поток>,< смещение>, <начало отсчета>).

Функция перемещает указатель текущей позиции в потоке на нужный байт файла.

Смещение указывает, на сколько байтов следует переместить указатель относительно начала отсчета. Оно задается переменной или выражением типа long и может быть отрицательным, т.е. возможно перемещение по файлу в прямом и обратном направлениях. Начало отсчета смещения задается одной из предопределенных констант, размещенных в заголовочном файле stdio.h:

SEEK_SET (имеет значение 0) – начало файла;

SEEK_CUR (имеет значение 1) – текущая позиция;

SEEK_END (имеет значение 2) – конец файла.

Мы помним, что константа типа long записывается в виде последовательности десятичных цифр, вслед за которыми добавляется буква L или l.

Функция fseek () возвращает 0, если перемещение в потоке выполнено успешно, в противном случае возвращается ненулевое значение.

Приведем примеры перемещения указателя текущей позиции:

fseek (f, 0L, SEEK_SET); - перемещение в начало файла (в позицию номер 0);

fseek (f,0L,SEEK_END); - перемещение указателя в позицию, после последней.То, что такой вызов перемещает указатель не на последнюю позицию, а после нее удобно для добавления данных в файл – операция записи в файл не будет стирать последний байт. Крме того, если в файле N байтов, при установке указателя на последний байт функция ftell(f) возвратит значение N-1. Позиция после последнего байта даст значение N и количество городов в файле можно получить выражением fseek (f,0L,SEEK_END)/sizeof(metropol).

При использовании сложных типов данных, таких как структура

struct str

{

   .

   .

   .

}st;

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

При показанном ниже обращении к функции fseek () указатель текущей позиции в потоке будет перемещен на одну структуру назад относительно текущей позиции

fseek (f, -sizeof (str), SEEK_CUR);.

Используя функцию

long ftell (FILE *) получения значение указателя текущей позиции в потоке, можно определить длину файла, переместив указатель в конец файла и прочитав значение текущей позиции:

fseek(f, 0L, SEEK_END);

long DL = ftell (f);.

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

#include <stdio.h>

#include <conio.h>

long length,Numb;

 FILE *f=NULL;

Struct metropol

{

 char name[20];

Int population; 

float territory;

}work;

Void Outputfile()

 { struct metropol t;

       fseek(f, 0L, SEEK_SET);

       fread(&t,sizeof(metropol),1,f);

       while(feof(f)==0)

       {

       printf("\n Название %s",t.name);

       printf("\n Население - %d ",t.population);

       printf("\n Площадь - %f\n",t.territory);

       getch();

        fread(&t,sizeof(metropol),1,f);

       }

 }

 void main(void)

{ clrscr();

f=fopen("city.dat","r+");

fseek(f, 0L, SEEK_END);

length = ftell(f)/sizeof(metropol);

  while (1)

  {

       printf ("\ n ‚Введите номер записи ");

       scanf(" %ld",&Numb);

       if(Numb>length) printf("\n В файле только %ld записей ",length);

       else break;

  }

  Outputfile();

       fseek(f, (Numb-1)*sizeof(metropol), SEEK_SET);

       fread(&work,sizeof(metropol),1,f);

       printf("\n‚ Введите численность ");

       scanf(" %ld",&work.population);

       fseek(f, (Numb-1)*sizeof(metropol), SEEK_SET);

Здесь можно использовать fseek(f, -1L*sizeof(metropol), SEEK_CUR); Но в 16-битном режиме очень неприятная ошибка возникает, если указать -1*sizeof(metropol) где после единицы нет буквы L.

Второй параметр функции fseek() имеет тип long.

sizeof(metropol) возвращает значение типа size_t. В mem.h объявлено typedef unsigned. size_t, то есть, это то же, то unsigned.

Выражение -1*sizeof(metropol) Си++ вычисляет как надо – переводит результат умножения в дополнительный код.

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

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

fwrite (& work, sizeof (metropol),1, f); Если файл открыт только для чтения – надо проверять на наличие ошибок.(Я заметил ошибку, когда вызвал ftell() и увидел, что позиция не изменилась.)

Outputfile();

       fclose(f);

}

 

1.8. Операторы языка

1.8.1. Операторы цикла

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

В данном параграфе мы изучим еще два оператора цикла итерационного типа:

while ( <выражение> ) <оператор>;

do <оператор> while ( <выражение> );.

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

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

а) в скобках может быть записано любое выражение – не обязательно использующее операции сравнения (>, == и т.п.);

б) оба цикла выполняются до тех пор, пока выражение не станет равным нулю;

в) после do тоже записывается только один оператор (или блок).

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

1.Сначала массив a[1001] заполняется натуральными числами.

2. Потом в нем ищется первый невычеркнутый (не равный нулю) элемент. В первый раз это число 2.

3. Обнуляются все элементы массива, кратные найденному простому числу.

4. Действия повторяются, начиная с пункта 2, но поиск ненулевого элемента ведется начиная c позиции, в которой было найдено последнее простое число.

#include <conio.h>

int a[1001],i,b,j;

Void main (void)

{

for (i =1; i <1001;++ i) Заполнение массива натуральными числами.

 { В первом проходе i==0, (неважно ++i или i++), так как выражение ++i вычисляется после первого прохода

 a[i]=i; a[0] не заполнили - там и так ноль.

 }

for (i =1; j = i, i <501;++ i)

{ while (!(i =b=a[++j])); Пример пустого оператора в теле цикла – поиск ненулевого элемента обеспечит заголовок цикла.

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

while((j+=b)<1001) a[j]=0; Стираем элементы, кратные найденному простому b.

}

clrscr();

Вывод найденных простых чисел (если j=1, то первую единицу печатает отдельно: (1-1)%15==0).

for (i=1,j=0;i<1000;(i-j)%15==0 && a[i]?printf("\n"):0,i++)

a[i]?printf("%4d",a[i]):j++;

getch();

}

Два замечания:

В данном примере простые числа выводятся из массива после завершения поиска. Если выводить каждое число сразу, как только оно будет найдено, все простые числа, большие 501 останутся невыведенными на экран.

Если в операторе while (!(i=b=a[++j])); не заносить найденное простое число в параметр цикла (написать while (!(b=a[++j]))), поиск будет осуществляться по прежнему правильно, но программа будет многократно находить одно и то же простое число, и заново повторять цикл вычеркивания уже вычеркнутых чисел. Попробуйте самостоятельно организовать измерение времени и оценить, насколько увеличится время выполнения программы.

 

Для демонстрации применения оператора с постусловием выведем на экран в виде прямоугольной таблицы квадраты ста первых натуральных чисел

#include <stdio.h>

#include <conio.h>

int i;

Void main(void)

{

clrscr ();

i =0;

while (i <100)                  Используем оператор с предусловием

{

printf("%6d",i*i);

if(++i%10==0)printf("\n");

}

i =0;

do                                   Используем оператор с постусловием

{ В Паскале здесь можно было без begin end, а здесь только один оператор – нужны скобки

printf("%6d",i*i);

if (++ i %10==0) printf ("\ n ");

}

while (i <100);  Обратите внимание, что оба цикла выполняются, пока выражение в условии не равно нулю.

getch();

}

 

1.8.2. Операторы goto, break,continue

Внутрь составного оператора можно, но пропустим инициализацию переменных, объявленных после фигурной скобки


 

1.8.3. Оператор выбора варианта switch

Оператор выбора варианта (переключатель) позволяет выбирать один из нескольких, объединенных в блок {...}, вариантов продолжения вычислений. Вариант выбирается в зависимости от результата вычисления селектирующего выражения, заданного в скобках после слова switch. Каждый вариант начинается с метки варианта ­– записанной после слова case константы, задающей одно из возможных значений выражения.

switch (< целочисленное выражение> )

{ Здесь можно объявлять переменные

case <значение выражения>: а здесь нельзя, но если хотим, надо фигурными скобками объявить блок.

       < операторы (не один, а сколько угодно)>

default: в любом месте

       <операторы >

case <значение выражения>: Две метки указывают на один оператор.

case <значение выражения>:

       <операторы>

}

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

Пример: Вычисление результата арифметической операции. С клавиатуры вводится два числа и знак операции (например, 17/3), программа вводит их функцией scanf(), распознает заданную операцию оператором switch и производит соответствующие вычисления:

#include <stdio.h>

#include <conio.h>

 int a,b,c;

 char op;

Void main(void)

{

clrscr();

scanf(" %d %c %d",&a,&op,&b);

switch(op)

{ //int j=5; здесь выдает ошибку. Пишем просто int j –нет ошибки проверить

default: printf ("неверная операция\ n "); break;

case '+': c=a+b;                                          break;

case '-': c=a-b;                                           break;

case '*': c=a*b;                                          break;

case '/':

case ':': c=a/b;

}

printf("%d %c %d = %d",a,op,b, c);

getch();

}

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

В аналогичной программе на Паскале (см. п.) при вводе данных нельзя было после ввода первого числа вводить без пробела записать знак операции. Обратите внимание на то, что scanf() работает лучше read(), потому что признаком конца числа здесь считается не только пробельный символ, но и другие, отличные от цифр и десятичной точки.

В данном примере строка формата " %d %c %d " позволяет оператору scanf() разделить вводимые символы на первое число, знак операции и второе число. Если необходимо вычисление произвольного арифметического выражения, включающего операции разного приоритета и скобки, например ((2+4)/(7-2) +1)/2, приходится вводить такое выражение как строку и самостоятельно составлять программу, осуществляющую выделение из строки операндов, знаков операций и вычисление результата. Пример такой программы дан в следующем параграфе.

1.8.4. Рекурсивное вычисление арифметических выражений

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

<Надо сказать, что такое дерево, показать, что выражение, это древовидная структура.> 

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

Вычисление выражения выполняется вызовом рекурсивной функции

char Op=’+’,

int Result=Calc(0, &Op1,1)

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

Работа основной функции int Calc(int D1, char *Op1,int Prior)

заключается в выполнении заданных выражением операций при помощи многократного повторения рассмотренного в предыдущем пункте оператора switch. Функция получает значение первого операнда D1, знак операции *Op1 и приоритет операции.

Очередной операнд *D2 и следующую за ним операцию*Op2 выделяет из строки Str функция

 int Load(int *D2, char *Op2, int Prior);

. Функция Load() возвращает приоритет найденной операции (1 для сложения и вычитания 2 для умножения). Для операций, входящих в подвыражения, заключенные в круглые скобки, приоритет увеличивается на 4. Сами значения операнда и операции, возвращаются через указатели D2 и Op2. Если признак конца строки, задающей выражение, считать операцией приоритета ноль, возвращаемый приоритет можно использовать для окончания вычислений и описать алгоритм обработки выражения следующим псевдокодом:

int Calc(int D1, char *Op1,int Prior)

{int D2=0,;char Op2=0; int Res;

int Rep=Load(&D2,&Op2,Prior); //Чтение следующего операнда

 

<Выполнение операции Op1: Res = *D1 Op1 D2>

if(Rep) Res=Calc(Res,&Op2,Rep); 

// *Op1=Op2; Назначение этого оператора объясним ниже.

return Res;

}

Если выражение состоит из операций одного приоритета, например, 12-8-3, функция выполнит следующие действия:

1. при первом обращении:

получит D1=0 и операцию +;

вызовет Rep=Load(), которая возвратит D2=12, операцию Op2=’-‘ и приоритет >0;

выполнит сложение Res=0+12;

вызовет себя Calc(12,&Op2,Rep), передав накопленный результат 12 и операцию ‘-‘; 

2. при втором вызове:

       получит D1=12 и операцию -;

вызовет Rep=Load(), которая возвратит D2=8, операцию Op2=’+‘ и приоритет >0;

       выполнит вычитание Res=12-8;

       вызовет себя Calc(4,&Op2,Rep), передав результат 4 и операцию ‘-‘; 

3. при выполнении третьего вызова (в этот момент три раза функция

Calc() вызвана и не завершена):

                   функция получит D1=4 и операцию ‘-‘;

вызовет Load(), которая возвратит D2=3, операцию Op2=0 и приоритет 0;

                   выполнит вычитание Res=4-3;

                   поскольку приоритет равен 0, возвратит Res=1 без рекурсивного вызова; 

2. В результате завершения третьего вызова Res=Calc(Res,&Op2,Rep): 

         в переменную Res запишется единица; 

процесс попадает на строку return Res второго вызова, функция завершается;

1. В результате завершения второго вызова Res=Calc(Res,&Op2,Rep):

в переменную Res запишется единица; 

процесс попадает на строку return Res второго вызова, функция завершается и основная программа получает результат вычисления выражения.

 

При этом функция Load() используя позицию Pos, очередного обрабатываемого символа, может получить второй операнд int i и следующую за ним операцию char c одной строкой (сравните ее с написанной при изучении ассемблера процедурой упаковки):

while ((c=Str[Pos++])>47&&c<58)i=i*10+c-48;

Операция && задает точку последовательности, поэтому в данном случае есть гарантия, что к моменту проверки c<58 символ уже прочитан в подвыражении c=Str[Pos++].

Если приоритеты операций разные или порядок их выполнения изменяется скобками, как в выражении 10+12*3/4-2, то функция Calc останется работоспособной, если ей при первом сложении в качестве второго операнда передавать результат вычисления подвыражения 12*3/4. Для краткости назовем термом такое подвыражение, значение которого нужно получить, чтобы использовать в качестве второго операнда текущей операции. С учетом этого необходимо сделать следующие дополнения:

– функция Load() должна получать в качестве параметра приоритет текущей операции и, если операция справа от второго операнда (чтобы отличать от текущей, назовем ее правой операцией) имеет более высокий приоритет, делать вызов Calc() для предварительного вычисления приоритетного подвыражения;

– чтобы при этом внутреннем, из Load(), вызове функции Calc() она, вычислив терм, завершила работу, уменьшение приоритета правой операции по отношению к текущей, должно, как и конец строки, вызывать завершение работы Calc().  

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

int Load(int *D2, char *Op2, int Prior)

{int i=0; unsigned char c; int Prior2=1;

 while ((c=Str[Pos++])>47&&c<58)i=i*10+c-48; //Прочитали операнд и операцию.

 switch(c) //Вычислили приоритет правой операции.

 { case '+': case '-':Prior2=1;   break;

case '/': case '*':  Prior2=2; break;.

case 0: *Op2=c;*D2=i; return 0; // Последняя операция в выражении.

 }

if (Prior2>Prior) //Приоритет повышается, вычисляем новый терм вызовом Calc()

*D2=Calc(i,(char *)&c,Prior2); //Вычислили терм

< Получение правой для терма операции и оценка ее приоритета Prior2 >

*Op2=c; // Передача правой операции из функции Load()

if (!c) return 0; else return Prior2;

}

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

обязательно необходимо возвращать также и правую по отношению к терму операцию;

по этой операции можно определить ее приоритет, но можно возвращать его значение из Calc();

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

Чтобы выполнить хотя бы первое требование надо убрать комментарии из строки *Op1=Op2 в приведенной выше структуре функции Calc().

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

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

2. Провести предварительную обработку строки, в результате которой:

– операции будут кодироваться двумя байтами (первый – код операции, а второй, ее приоритет);

– из выражения будут удалены все скобки, а их влияние на порядок выполнения операций будет учтено кодом приоритета;

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

4. Чтобы не обрабатывать несколько записанных подряд скобок или взятый в скобки отдельный операнд как особые случаи, включить в строку во время предварительной обработки дополнительные операции сложения с нулем, например, вместо (10+(165))/2 записать (10+(0+165)+0)/2.

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

#include <stdio.h>

#include <conio.h>

char Str 0[80];

char Str [80]; Строка, задающая вычисляемое выражение.

static int Pos=0;       Текущая позиция в строке Str.

static int P =0;        Приоритет операции, определяемый уровнем вложения скобок.

int Load (int * D 2, char * Op 2, int Prior); Прототип функции чтения 2-го операнда.

int Calc (int D 1, char * Op 1, int Prior) Функция вычисления выражения.

{int D2=0;char Op2=0;

int Rep=Load(&D2,&Op2,Prior);

int Res;

switch(*Op1) {

case '-': Res=D1-D2;  break;

case '+': Res=D1+D2;  break;

case '*': Res=D1*D2;   break;

case '/': Res=D1/D2;    break;

}

if(Rep)Res=Calc(Res,&Op2,Rep); Внутренние вызовы Calc() изменят Op2.

*Op1=Op2; Передача правой от терма операции внешнему вызову.

return Res;

}

int Load(int *D2, char *Op2, int Prior)

{int i=0; unsigned char c; int ProirTerm,Prior2=1;

 while ((c=Str[Pos++])>47&&c<58)i=i*10+c-48;

switch(c)

 {// case '+': case '-':Prior2=1; break; //Удалить эту строку можно, но это нехорошо

case '/': case '*':  Prior2=2; break;

case '(': P = P +4; Prior 2=1; break; Скобка повышает приоритет на 4.

case ')':

       P = P -4; if (P <0) puts (" Error ()"); Скобка понижает приоритет на 4.

        * Op 2= c = Str [ Pos ++]; Чтение правой операции. Считаем, что)) не бывает.

       *D2=i;

return 0;                                        Последняя операция в терме.

 case 0: *Op2=c;*D2=i; return 0; Последняя операция в строке.   

 }

Prior 2= P + Prior 2; Общий приоритет операции с учетом скобки.

if (Prior 2> Prior)  Если открыли скобку, рассматриваем открытие

 { if(c=='(') c='+';                  скобки как 0+ (значение i при этом равно 0).

*D2=Calc(i,(char *)&c,Prior2);   Вычислили терм.

*Op2=c; Получили из Calc() правую операцию терма

if (c == '+'|| c == '-') PriorTerm = P +1;                    и определяем

if (c == '/'|| c == '*') PriorTerm = P +2;                            ее приоритет.

if(c== 0)            PriorTerm=P; PriorTerm заведомо ниже Prior2

if (PriorTerm > Prior) Приоритет правой операции терма по-прежнему выше левой.

{* D 2= Calc (* D 2,(char *)& c, PriorTerm); Терм пониженного приоритета.

*Op2=c;   Получили операцию справа от терма и уже не считаем, что ее

if(c!= 0)return PriorTerm; else return 0;    приоритет может быть выше левой.

} //Внутренний if (PriorTerm>Prior)

*Op2=c; //PriorTerm<=Prior

if(c!= 0)return PriorTerm;else return 0;

} //Внешний if (Prior2>Prior)

if (Prior2<Prior) // без этого ( 2+7*8 -3) - теряет двойку

{*Op2=c;*D2=i; return 0; } Это конец терма, надо выйти в предыдущий уровень.

*Op2=c;*D2=i; //if (Prior2<=Prior )

 if (!c) return 0; return Prior2;

}

Void main (void)

{

char cc =0;

while (cc!=27) Цикл ввода и вычисления выражений до нажатия клавиши <Esc>

{

gets (Str 0); Ввод исходной строки с выражением. и ее предварительная обработка:

for(int i=0,j=0;Str0[i];i++)

{ while (Str 0[ i ]==32|| Str 0[ i ]==9) i ++; Удаление из строки пробельных символов.

Str0[j++]=Str0[i];

}Str0[j]=0;

for (i =0, j =0; Str 0[ i ]; i ++) В ставка между двумя подряд закрывающими скобками))

{ Str [ j ++]= Str 0[ i ];         операции сложения с нулем)+0)

if (Str 0[ i ]==')'&& Str 0[ i +1]==')')  не изменяющей значения выражения.

       { Str [ j ++]='+'; Str [ j ++]='0';}

}Str[j]=0;

char Op1='+'; Pos=P=0;

 int Res=Calc(0,&Op1,1);

printf("%i \n",Res);

cc=getch();

}

}

 

Если в выражении встречаются две открытых скобки подряд

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

Самостоятельно – возвращать из Calc() структуру

результат, правая операция, приоритет правой операции

Res= Calc(/////).Res;

 

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...