C++ C++ C# C# ASP.NET Security ASP.NET Security ASM ASM Скачать Скачать Поиск Поиск Хостинг Хостинг  
  Программа для работы с LPT портом...
Язык: .NET — ©Alexey...
  "ASP.NET Atlas" – AJAX в исполнении Micro...
Язык: .NET — ©legigor@mail.ru...
  "Невытесняющая" Многопоточность...
Язык: C/C++ — ©...
  Update World C++: Сборник GPL QT исходников
  Весь сайт целиком можно загрузить по ссылкам из раздела Скачать
Нетология

 Книга по C / Книги / Литература

Стандарные подпрограммы (функции Printf, Scanf)

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

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

                printf ("Интересное сообщение \n");

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

               printf ("Интересное сообщение "); prin tf(" \n "); 

   - результат будет точно таким же, как и в первом случае!

   Первым аргументом служит строка форматов, а вторым, если они есть, - выводимые объекты. Строка форматов может включать обычные символы, которые начинаются со знака %, за ним следует символ преобразования. Каждая спецификация преобразования соответствует одному из аргументов, которые следуют за форматной строкой.Буква d в спецификации преобразования указывает, что значение аргумента должно быть напечатано как десятичное целое число. Из других символов отметим : c - для вывода отдельного символа; s - для печати символьной строки; x и o - для вывода шестнадцатиричных и восьмиричных чисел соответственно; f - для вывода чисел с плавающей точкой. В следующем примере

                 printf(" %c = %d \n",g,g);

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

                 printf(" %c = %5d \n",g,g);


Пример простой программы на языке Си

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

#include
<stdio.h>
{
   int a,b,c;
   a=5; b=7;
   c=a+b;
   printf("Cумма = %d \n",c)
}

  Дадим некоторые пояснения. В языке Си любая пограмма, состоит из нескольких программных едениц и каждая из них - функция. Функции в Си подобны функциям или подпрограммам в Фортране или процедурам в Паскале, Имена функций выбираются произвольно (только латинскими буквами), но одно из них main, именно с нее начинается выполнение программы. Такая главная функция обычно обращается  к другим функциям, которые находятся в одном файле с головной программой или извлекают из библиотеки предварительно подготовленных функций.Функция main не имеет аргументов, поэтому список ее выглядит так: ( ) . Скобки { } обрамляют операоры, которые реализуют собственно алгоритм. Эти скобки аналогичны BEGIN - END в Паскале. 
             Строка int a,b,c; объявляет a,b,c переменными целого типа. Все используемые в программе переменные должны быть объявлены. Далее идут опрераторы присваивания к a значение 5, а к b - 7, с -   значение их суммы. Значения переменных типа int лежат в диапазоне [-32768; 32767]. Функция printf выводит на экран: СУММА = 12.

     Рассмотрим теперь функцию scanf предназначенную для форматного ввода данных. Функция scanf в качестве фактических параметров использует адреса переменных, а не их значения. Для этого перед соответствующим параметром ставят знак & - символ взятия адресса. Например, &XL означает     "адрес перменной XL", а не значение, которое переменная имеет в данный момент.

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

  Предыдущяя программа страдает одним недостатком: программа вычисления суммы годится только для одного конкретного случая, когда a=5, b=7. Улучшим ее, заменив соответствующие операторы присваивания вызовом функции scanf        (пример 1.2) :           

/* Ввод двух чисел, вычисление суммы и печать результата*/

#include
<stdio.h>
{
   int a,b,c;
   scanf(" %d %d",&a,&b);
   c=a+b;
   printf("Cумма = %d \n",c)
}

        Форматная строка предписывает функции scanf  ввести десятичное число, которое надо поместить в переменную a, затем через пробел ввести второе десятичное число, которое надо присвоить переменной b.Обратите внимание,  что программа начинается со строки коминтарием : /* .. */ , транслятор пропускает любые символы между /* и */  и их можно использовать для пояснений.


Основные типы данных, операции и выражения

Основные объекты, с которыми работает программа на языке Си - переменные и константы. Переменные - поименнованые величины, значения которых, в отличае от констант могут меняться а процессе выполнения программы. Все переменные должны быть описаны; в описаниях указываются их типы и, возможно, начальные значения. Константам могут быть присвоены имена- синонимы констант в программе.

         Имена переменных. Имена могут состоять из букв латинского алфавита, цифр и символа подчеркивания "_", который считается буквой. Строчные и прописные буквы различаются Beta, beta , BETA - разные имена. Число символов в имени не ограничено. В качестве имен переменных нельзя исползовать зарезервированные слова типа if, else, for, char, int и т.д. Все служебные слова должны быть набраны малыми буквами.

         Типы данных. В языке Си имеются два существенно различных типа данных: int- целый и float - вещественный(с плавающей точкой). Из них можно создавать еще два типа: char - символьный , double - вещественный с двойной точности. Из этих четырех базовых типов может быть получено много других типов. При необходимости программист может изобрести любой  желаемый тип данных.
              Целые   константы и константы с плавающей точкой записываются в общепринятой для языков программирования форме:

                      13, -941, 0, 76; 13.0, 13E+0, -1.76, 0.123e-2, 6.02E23

Плавающяя константа состоит из десятичной целой части, десятичной точки, десятичной дробной части и степени, которая состоит из буквы E или е, за которой следует десятичный порядок. Перед показателями может стоять знак (+ или -). Либо десятичная точка, либо показатель, но не оба одновременно, могут быть опущены либо целая, либо дробная части.
         В языке Си существуют правила записи восьмеричных и шестнадцате- ричных   чисел: если перед целым числом идет 0 (нуль). то это восьмеричная константа: 037, 0776; начальные 0X или 0x указывают на шестнадцатеричное число: 0x f37, 0X1FA.

      Символьная константа состоит из заключенного в одиночные ковычки символа, например ' * ' . Символы в языке Си фактически являются целочисленными значениями. Их числовое значение соответствует внутреннему (машинному) представлению символов и некотором принятом стандартном коде. Например, в коде ASCII, широко применяемом в мини- и микрокомпьютерах, значение символа ' A ' равно 65, ' b ' - 98, ' 2' - 50.
       Символы, не имеющие графического изображения, и некоторые спечиальные символы записываются следующим образом: \n - новая строка, \t - табуляция, \0 - нуль (пусто - конец строки), \\ - обратная  косая черта, \' апостроф, \b - возрат на шаг, \r - возрат коретки, \f - перевод страницы. Присвоение константам сиволических имен происходит с помощью процессорного утверждения   #define. Например, запись #define MAX 100 перед текстом основной программы определяет имя MAX, являющееся синонимом константы 100.

    Строковая константа - это последовательность нуля и более символов, заключенная в двойные кавычки, например "Интересное сообщение"  или " " - пустая строка. В конце каждой такой строки транслятор помещяет нулевой символ \0, что сильно облегчает программисту поиск конца строки. Технически строка символов представляет собой одномерный массив, каждый елемент которого - один символ.

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

                      int a, b; int low;  char c;  float x, dl;

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

                       int  p = 1; float eps = 1.0 e-5;

      Арифметические операции. Существуют арифметические операции: +, - , * , / и вычисление остатка от деления -  %. Есть унарная операция - (унарный минус). При делении целых чисел дробная часть отбрасывается. Порядок выполнения операциями совпадает с общепринятыми: операции + и- имеют одинаковый приоритет, причем он ниже приоретета операций /, * и % . Самый высокий приоритет унарный минус.


Операторы  IF - ELSE

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

    if ( выражение )
        оператор_1;
    else
        оператор_2;
   

где часть else может и отсутствовать. Сначала вычисляется "выражение" в скобках; если оно истинно то выполняется оператор_1. Если "выражение" ложно (равно нулю - NULL), то оператор_1 пропускается, а выполняется оператор_2. Если на месте условно выполняемых операторов должна располагаться группа из нескольких операторов языка, то они заключаются в фигурные скобки - { }. Часто "выражение" в скобках представляет условие, заданное с помощью операций отношений и логических операций. Операции отношения обозначаются в Си следующим образом:

            = = равно; ! =   не равно; <  меньше; >  больше;
            < = меньше или равно; > = больше или равно.

  Символ ! в языке Си обозначает логическое отрицание. Есть еще две логические операции: || означает или, а && - логическое И. Операции отношения имеют приоретет ниже арифметических операций, так что выражение вида   k > n%i вычисляется как k > (n%i). Приоритет && выше, чем у ||, но обе логические операции выполняются после операций отношения и арифметических. В сомнительных случаях лучше расставлять скобки.

       Для иллюстрации применения условного оператора рассмотрим программу определения большего из трех чисел. Первый if оператор представляет полную условную конструкцию, во втором случае else отсутствует. Обратите внимание, что точка с запятой, завершая оператор присваивания max=x, не нарушает единство if- оператора. Если else- ветвь пропускается во вложенных условиях, возможна неоднозначность их толкования. Во избежание двусмысленностей решают так: else  соответствует ближайшему if, не имеющего своего else.

             Пример 1.3.

#include <stdio.h>
main()                                                    /* главная функция*/
{
  int x, y, z, max ;                                   /* описание переменных*/
printf(" Введите три числа :\n "); 
scanf(" %d %d %d ", &x, &y, &z);  /*ввод трех чисел*/
if( x > y)                                                 /*операции сравнивания*/
     max=x;
else                
  max=y;
if ( z>max)
  max=z;
printf(" Максимальное из (%d, %d, %d)= %d \n",x, y, z, max);
  }

   Рассмотрим пример программы, в которой применяются несколько вложенных друг в друга условных операторов. В этой программе сторока            float A, B, X объявляет эти три переменные как величины вещественного типа. Форматная строка функции scanf предписывает ввести два вещественные числа, которые станут значениями переменных A и B соответственно.

          Пример 1.4

/*РЕШЕНИЕ УРАВНЕНИЯ AX=B*/
#include <stdio.h>
main()
{  

   float A,B,X;
   printf("ВВЕДИ А, В\n");
   scanf("%f %f",&A, &B);
   if(A!=0)
       printf("РЕШЕНИЕ:%f\n", B/A);
   else
   if(B==0)
     printf("X-ЛЮБОЕ ЧИСЛО\n");
   else
     printf("РЕШЕНИЙ НЕТ\n");
}

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

if(a_0)
   {
    printf("...");
    scanf("...")
    другие операторы ...
   }

        Пример 1.5   

Оператор WHILE

            В языке Си основной структурой, управляющей повторением, служит цикл с предусловием while (пока). Он имеет следующий формат

                              while (условие) оператор;

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

/* Програииа определяет поведение ракеты,
    стартующей на экваторе, в зависимости
    от ее начальной скорости*/
#include <stdio.h>
main()
  {
   float V;
   printf("ВВЕДИ V\n");
   scanf("%f",&V);
   if(V<7.9)
     printf("РАКЕТА УПАДЕТ НА ЗЕМЛЮ\n");
   if(V<11.2)
     printf("РАКЕТА СТАНЕТ СПУТНИКОМ ЗЕМЛИ\n ");
   if(V<16.4)
     printf("РАКЕТА СТАНЕТ СПУТНИКОМ СОЛНЦА\n");
   else
     printf("РАКЕТА ПОКИНЕТ СОЛНЕЧНУЮ СИСТЕМУ\n");
}
while(условие)
{
   оператор_1;
   оператор_2;
   ....
    оператор
}

      Для описания условий в операторе while используются операции условия такие же, как и в операторе if . Приведенная ниже программа подсчитывает сумму цифр введенного числа N. Цикл while последовательно выделяет и суммирует цифру исходного числа, начиная с последней; для для выделения применяется операция взятия остатка от деления - %. При делении целых чисел любая дробная чать отбрасывается, поэтому после операции N=N/10; исходное число уменьшается в 10 раз при каждом "обороте" цикла, пока, наконец, не станет равным нулю, после чего цикл завершается и на экран дисплея выдается значение переменной S, в котором содержится сумма цифр числа N.

         Пример 1.6

#include <stdio.h>
main()
  {
    int N,S,Z;
    S=0;
    printf("ВВЕДИ N\n");
    scanf("%d",&N)
    while(N!=0)
{
   Z=N%10
          N=N/10
          S=S+Z;
}
    printf("СУММА ЦИФР=%d\n",S);
  }

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

              Пример 1.7

/*РАЗЛОЖИТЬ ЧИСЛО НА МНОЖИТЕЛИ */
   #include <stdio.h>
   main()
{
int M,i=3;
printf("ВВЕДИ M\n");
       scanf("%d",&M);
       printf("%d=1",M);
       while(M%2==0)
{
printf("*%d",2);
  M=M/2;

       while(i <=M)
{
if(M%i==0)
{
printf("*%d",i);
M=M/i;
}
              else
    i=i+2
}
}

       Иногда структуры со вложенными друг в друга операторами повторения называюися циклами. Следующяя программа простая , >хотя и содержит вложенные циклы. Она выводит на экран заполненный символом * треугольник, высота которого равна N.
    Во внешнем цикле устанавливается очередная строка вывода (параметр i ), а во внутреннем (параметр j ) в очередную строку вводится ровно i символов " * "   Вызов функции printf("\n")   обеспечивает в нужный момент переход нат новую строку. Обратите внимание, что для вывода одного символа в форматной строке функции printf используется спецификация %c.

       Пример 1.8

#include <stdio.h>
main()
{
int i,j,N;
printf("ВВЕДИ N \n");
scanf("%d",&N);
i=1;
while(i <=N)
{
j=1;
        while(j <=i)
{
printf("%c",'*');
j=j+1;
}
       i=i+1;
       printf("\n");
}
}

         Рассмотрим еще один пример, в котором используется сложный цикл. Программа позволяет найти в заданном интервале все совершенные числа. Напомним, что натуральное число называютя совершенным, если оно равно сумме всех своих делителей, считая его самого. Известно, что все совершенные числа - четные и что первое совершенное чсило из натурального ряда чисел равно 6. Этим объясняется начальное значение параметра внешнего цикла. Так  как все натуральные числа имеют своим делителем единицу, полагаем начальное значение суммы делителей числа S=1. Во внутренннем цикле организуется перебор всех множителей текущего значения N. Из теории чисел известно, что такому испытанию имеет подвергать числа от 2 до N/2, либо даже до   корень из N. Это очень несовершенный алгоритм и если вы захотите его выполнить на ЭВМ, имейте ввиду, что программа работает слишком долго. Более эффективно алгоритм будет реализован попозже.

             Пример 1.9


Определение стандартных функций

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

        Определение функции состоит из двух частей: заголовка и тела. Заголовок определяет имя функции, ее тип и формальные параметры, тело определяет действия над данными, выполняемые функцией. Возращающее функцией значение передается в вызывающюю программу опрератором return   (выражение). Значение "выражения" и есть результат функции (возращаемого значения). Если в нашей программе функция физически следует за вызывающей ее функцией main, то надо в последней объявить функцию внешней с помощью описателя extern: extern int fun(); или еще проще int fun();. В противном случае при компиляции будет выдана ошибка. Всякая функция имеет вид:     

#include <stdio.h>
main()
{
int j,N,M,S;
printf("ВВЕДИ M\n");
scanf("%d",&M);
N=4;
while(N<=M)
{ S=1;j=2;
   while(j<=N/2)
       {
if(N%j==0) S=S+j;
j=j+1;
}
       if(N==S)
         printf("%d- СОВЕРШЕННОЕ ЧИСЛО\n",N);
         N=N+2;
}
}
[тип] имя([список формальных параметров])
      описания формальных параметров;
{
описания;
операторы;
}

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

  Рассмотрим пример программы возведения числа в степень. Для этого составим функцию power(t,n); В нашей реализации функция power предшествует головной программе, поэтому она предварительно не объявлена. Описание формальных параметров функции происходит в заголовке (int t,n;) Оператор int p=1;  в теле функции определяет переменную p целого типа и присваивает ей начальное значение равное 1. Выражение int p=1; в точности эквиалентно   последовательности операторов int p; p=1; Обращение к функции задает один из аргументов стнадартной функции printf в программе main. Выражение power(t,n) предписывает вызов функции. Когда программа main достигает этой точки, то все управление передается функции power. Операторы , содержащиеся в теле функции power фактически оперируютя данными. Когда достигается оператор return , осуществляется переход в ту точку программы main, из которой мы пришли в power. Значение, вычисляемое функцией power, передается в головную программу с помощью оператора return(p). В скобках в общем случае может быть любое значение.

                Прмер 2.0

/*Функция y=t**n*/
      int power(t,n);
      int t,n;
      {
int p=1;
         while(n!=0)
   {
if(n%2 !=0) p=p*t;
n=n/2;
t=t*t;
   }
return(p);
}
    /*Возведение в степень- головная программа*/
   #include <stdio.h>
   main()
{
     int a,n;
     printf("Введи a,n \n");
     scanf("%d %d",&a,&n);
     printf("%d в степени %d = %d",a,n
power(a,n));
}

      Рекурсия.

               В языке Си можно использовать рекурсивно, т.е. функция может вызывать сама себя. Простейший пример 2.1, где функция main вызывает сама себя остановить которую можно только с помощью CTRL+C  - ^С. При рекурсивном обращении к функции - создается новый экземпляр данных.

                Пример 2.1

#include <stdio.h>
main()
{
printf("проверка рекурсии \n");
main();
}

Следущий пример 2.2 использует рекурсию для вычисления k! . При первом вызове fact(i) , если не равно 1, функция порождает выражение i*fact(i-1); в свою очередь, вызов fact(i-1) производит (i-1)*fact(i-2), что вместе с ранее полученным результатом дает i*(i-1)*fact(i-2). Рекурсия закончистся, как только следующий вызов функции получит аргумент, равный еденице. Нетрудно сообразить, что в конечном счете мы получим требуемое произведение.

Прмер 2.2


КЛАССЫ ПАМЯТИ

   Все переменные в программе характеризуются не только типом, но и классом памяти. В языке Си существует четыре класса памяти: автоматический (automatic), регистровый(register), статический(static) и внешний(external).

Автоматические переменные в программе можно описать так:

                                         auto A; auto char c1; auto int x= 125;

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

        Пимер 2.3

#include <stdio.h>
int fact(k);
int k;
{
  if(k==1)
return(1);
  else
    return(k*fact(k-1));

}
main()
{
int i=1;
    printf("проверка рекурсии\n");
    while(i<8)
              {
printf("%d!= %d\n",i, fact(i));
i=i+1;
}
}
#include <stdio.h>
main()
{
int t;
     {
      int t=2;
{
         int t=3;
         printf("%d\n",t);
       }
       printf("%d\n",t);
}
   printf("%d\n",t);

}

           В этой программе перменная t описана в нескольких блоках, в каждом блоке она может принимать разные значения не зависимо от других. С ней могут выполнятся разные операции. В нашей програамме значение переменной   t выводится на дисплей. В нашем случае выведятся числа 2, 3, 746. Число 746 - так называемое число "мусор" оно такое так как ей не присваивали значение в первом блоке.

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

   Пример 2.4


#include <stdio.h>
int x=145;/*Описание внешней переменной*/
main()
{
extern int x,y;
   printf("x=%d y=%d \n",x,y);
}
int y=541; /*Описание внешней переменной*/

  Внешнии переменные могут определятся вне квкой-либо функции; при этом выделяется фактическая память. В любой другой функции, обращающейся к этим переменным, они должны описываться; делается явно с помощью описателя extern. Обычно поступают так, как показано на прмере 2.5. Все внешние переменные размещают в начале исходного модуля (вне всяких функций!), опуская дополнительные описания со словом extern внутри функций. Конечно, если внешняя переменная и функция, которая ее использует, размещены в разных файлах, описывать эту переменную в функции необходиммо. Но самым важным способом является описание каждой внешней перемнной с ключевого слова extern в любой функции, которая ее использует. А еще лучше избегать применения внешних переменных, так как они часто служат источником труднообнаруживаемых ошибок.

Пример 2.5

int x=3; /*описание внешней переменной */
/* увелечение x */
int plus1()
{
   x=x+1;
   printf("прибавляем единицу: x=%d\n",x);
}
/*уменьшение x */
int mainus1()
{
x=x-1;
printf("вычитаем единицу: x=%d\n",x);
}
main()
{
  printf("начальное значение x=%d\n",x);
  plus1();
  minus1();
  minus1();
  printf("конечное значение x=%d \n", x);
}

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

   static char c; static int a=1;

Рассмотрим пример 2.6, в котором переменная объявлена как статическая.

  Пример 2.6

*статические переменные*/
#include <stdio.h>
plus1()
{
static int x=0;
x=x+1;
printf("x=%d\n",x);
}
  main()
{
plus1();
plus1();
plus1();
}

    Начальное значение равное нулю присваивает переменной x только один раз. Затем в программе main, функция plus1() несколько раз запускается, так как при каждом запуске функции аргумент x не изменяется, а оставляет значение из предыдущей функции. Таким образом повторение функции plus1 обеспечивает увелечение переменной x на 1 при каждом запуске 1, 2, 3 ...

Регистровые переменные объявляются в программе с помощью ключевого слова register и по замыслу автора языка Си должны хранится в сверх быстрой памяти ЭВМ - регистрах. Используются аналогично автоматическим переменным. Целесообразность их применения для увелечения быстродействия программы представляется в большинстве случаев сомнительной.


Обработка символьных данных

       Язык Си лучше всего подходит для системной работы: написания компиляторов, интерпретаторов, опрерационных систем, редакторов текста и.т.п. В стнадартной библиотеке Си предусмотренымногие полезные функции, выполняющие простые действия с символьными данными. Рассмотрим из них - putchar и getchar выполняющие ввод и вывод символа соответственно и создадим на их основе ряд своих полезных функций.

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

              int getchar()

видите, у нее совсем нет аргументов и она возращает значение типа - это значение символа во внутреннем представлении его для данной ЭВМ(напрмер в ACSII). Таким образом после обращения

               c = getchar()

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

     Функция putchar за одно обращение к ней выдает один символ в стандарный выходной поток. Вызов этой функции имеет вид:

               putchar(c);

c- переменная символьного типа, котрой предварительно было присвоено некоторое значение. Рассмотрим примеры. Программа (пример 2.7) вывводит на экран все прописные латинские буквы. Мы уже напоминали что типы char и int взаимозаменяемы. Последовательное прибавление к очередному значению с обеспечивает выбор очередной буквы ввиду их лексической упорядоченности. 

              Пример 2.7

#include <stdio.h>
main()
{
char c;
c='A'
while(c<='Z')
{
putchar(c);
       c=c+1;
}
}

Прежде, чем перейти к рассмотрению примеров, обсудим, как машина должна определять конец входного потока символов, вводимых с клавиатуры терминала. Можно, конечно, выбрать некоторый символ как признак конца потока, но нужна гарантия, что он нигде не повторится. В использованной нами операционной системе для получения такого символа надо нажать CTRL+Z. В программе значение этого символа мы будем использовать через символическое имя EOF(end of file). Теперь мы можем написать программы копирования файла(прмер 2.8) . Пока не обнаружен конец входного потока, ЭВМ получает с клавиатуры символ (это делает функция getchar) и сразу же выодит его на экран дисплея с помощью функции putchar. Для завершения программы достаточно нажать CTRL+Z.

     Пример 2.8

/*ЭХО ПРОГРАММА*/
#include <stdio.h>
main()
{
char c;
c=getchar();
while(c!=EOF)
{
putchar(c);
c=getchar();
}
}

   Программу копирования можно написать и более компактно. В языке Си любое присваивание например, c=getchar(), можно использовать в любом выражении в качестве операнда; его значение - это просто значение, присваиваемое левой части. Учитывая сказанное, перепишем Эхо Программу (Пример 2.9) . Это компактная, элегантная программа принимает символ с клавиатуры и присваивает его переменной с, а затем сравнивает его с признаком конца файла. Пока этот признак не обнаружен, выполняется тело цикла и символ выдается на экран. В противном случае цикл, а вместе с ним и вся программа завершаются.

       Пример 2.9

*ЭХО ПРОГРАММА ВАР2.*/
#include stdio.h
main()
{
  char c;     while((c=getchar())!=EOF)putchar(c);
}

    Отметим, что включение присваиваний в проверки - силбное единство языка, способствующее созданию программ. Учтите, что скобки вокруг присваивания внутри условия необходимы: приоритет операции != выше приоритета операции присваивания. 
      Следующяя программа (пример 3.0) подсчитывает количество строк, слов и символов во введенном с клавиатуры тексте. Слово - любая последовательность символов, не содержащяя знаков табуляции ("\t"), пробелов и символов "\n". Будем предполагать также, что любая строка, в том числе и последняя, завершается символом перехода на новую строку "\n".   Обратите внимание на строку line= word=kc=0; в которой трем переменным приваивается значение нуля. Выражения, связанные логическими операциями &&(И) и || (ИЛИ) вычисляются слева на право. Как только истинность или ложность  станет известной, дальнейшие вычисления прекращяются  Таким образом в строке     if(s==' ' || s=='\n' || s=='\t')  
символ  s содержит пробел, то остальные две проверки уже не делаются. Отсюда следует выжный для практики вывод: первыми надо проверять наиболее часто встречающиеся символы.

  Пример 3.0

/*подсчет строк, символов, слов*/
#include <stdio.h>
main()
{
  int c,line,word,kx,inword=0;
  line=word=kc=0;
  while((c=getchar())!=EOF)
{
kc=kc+1;
       if(c=='\n') line=line+1;
       if(c=='' || c=='\n' || c=='\t') inword=0;
         else
              if(!inword)
                 {
inword=1;
word=word+1;
}
      }
printf("\n");
printf("строк - %d\n",line);
printf("символов - %d\n",kc);
}

Дополнительные операции языка

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

      +,  & ,  \ ,  ^ , |           
     ?: ,  --,  /,   ++,  >     
      >=, ++,  !=, << , <     
       <=, &&, !,  ||, %        
~, >>, -> , - , =, *

          Операции уменьшения и увеличения.

        В языке Си широко используются две нетрадиционные операции для увелечения и уменьшения значения переменной, обозначенные соответственно ++ и -- . Операция ++ прибавляет единицу к операнду, а -- вычитает.Эти операции могут быть использованны и перед, и после своего операнда. Они оказывают разные действия в выражениях: в записи ++n увеличение происходит до использования значения n, а в n++ увеличение идет уже после того, как используется значение n. Если считать, что значение n равно 5, то переменная m в выражении m=++n ,будет иметь значение 6, а в выражение m=n++ переменная m равна 5, в том и этом случае переменная n будет равняться 6. Первый пример в точности соответствует следующей последовательности операторов n=n+1; m=n; а второй последовательности m=n; n=n+1; Операция действует аналагично.Обе операции имеют самый низкий приоритет и выполняются после бинарных операций + и -. Рассмотрим пример 3.1 .

      Пример 3.1

/*определить двузначные целые числа,
которые делятся на сумму своих цифр*/
#include <stdio.h>
main()
{
int a,b,k,s,c;
    k=0;a=1;
   while(a<=9)
{
b=0;
     while(b<=9)
{
s=a+b;
c=a*10+b;
if(c%s==0)
{
  printf("%d",c);
k++;
}
b++;
}
a++; printf("\n");
}
   printf("всего: %d\n", k);

       Поразрядные логические операции предназначенные для работы с отдельными битами целого числа или символа.

& -  поразрядное И   
^ -   поразрядное исключающее ИЛИ
>> - сдвиг вправо 
| -     поразрядное ИЛИ 
<< - сдвиг в лево  
~ -    инверсия

               Поразрядная операция И (&) часто используется для выделения некотрой группы двоичных разрядов, напрмер n=n&0177  устанвливает в нуль все двоичные разряды числа n, кроме семи младших. Операция ИЛИ( | ) используется для установки отдельных разрядов в единицу. Напрмер.              m=m | 0xF0F; "включает" 11,10, 9,8,3,2,1 и  0 разряды числа m. Операции << и >> выполняют сдвиг операдна влево или вправо на заданное число разрядов. Напрмер, m<<3 сдигает значение m на 3 разряда влево, заполняя освобождающиеся младшие разряды нулями. Унарная операция ~ выполняет инверсию двоичных разрядов числа(символа), т.е. преобразует каждый единичный бит в нулевой и наоборот. Применение некоторых поразрядных операций покажем на примерах. В программе на прмере 3.2 исходное число 511 дано в шестнадцатиричной форме: m= 0X|FF; Напомним, что написанная перед константой число 0 указывает на восьмеричное число, а 0X на шестнадцатиричное. Для выода результата предусмотрена функция PRINT. Она для удобства анализа результатов печатает числа в шестнадцатиричном, восьмеричном и десятиричном форматах, используя для этого спецификацию   x, o ,d. Остальные действия в программе коментируются и дополнительных пояснений не надо.

    Пример 3.2

/*поразрядные логические операции*/
  #include <stdio.h >
  PRINT(n)
  int n;
   {
printf("%5x %5o %5d \n",n,n,n);
}
  main()
{
int m,n;
m=0X1F3; PRINT(m); /*16- ричное число*/
n=m&0177 PRINT(n); /* выделение 7 мл.бит*/
n=m|013; PRINT(n); /*установка 4 мл.бит*/
n=m>>4 ; PRINT(n); /*сдвиг вправо*/
m=n<<3 ; PRINT(m); /*сдвиг влево*/
}

     Следующяя программа (прмер 3.3), используя команду сдвига числа вправо на один бит (m=n>>1;) и выделения младшего разряда числа (m&01), подсчитывает кол-во единичных битов исходного числа и печатает результат.

    Пример  3.3

/* подсчет единиц */
#include <stdio.h>
    main()
{
int m,k;
k=0;
m=0xf0f;
while(m!=0)
{
if(m&01)k++;
m=m>>1;

}
printf("k=%d\n",k);
}

        Операции присваивания и выражения. Выражение вида i=i+b, где левая часть повторяется в правой части, могут быть заменены в сжатой форме: i+ =b. При этом используется операция присваивания вида +=, которая означает буквально "увеличить i на b". Для большинства бинарных операций допускается запись вида op=, где op - одна из операций: + - / % | ^ & <<>>. Если E1op=E2 эквивалентно E1=(E1)op(E2). Обратите внимание на скобки вокруг E2; присвание  x*=y+1 фактически означает x=x*(y+1), а не x=x*y+1. В качестве иллюстраций, поясняющей сказанное, рассмотрим программу (пример 3.4), в которой по заданному натуральному числу n строится число m, написанное теми же цифрами, что и n, но взятыми в обратном порядке. Правду говоря, запись m=m*10+z более прозрачна, чем непривычная последовательность m*=10; m+=z.

          Пример 3.4

#include stdio.h
  main()
{
int n,z,m=0;
printf("введи n n\");
scanf("%d",&n);
while(n!=0)
{
z=n%10;
n/=10;
m*=10;
m+=z
}
printf("m=%d\n",m);
}

        Условная операция. Фактически она представляет собой сокращенную форму оператора if - then - else, и в общем виде записывается так: выражение1? выражение2? выражение3?. Если "выражение1" не равно нулю, то результатом операции будет значение "выражение2", в противном случае - значение "выражение3" . Условная операция, называемая иногда тенарной, определяет обычное выражение. которое может. в частности, быть использовано в операторе присваивания.  Таким образом, вместо if(x > y) max=c; else max=y; достаточно написать: max=(x>y)& x:y. Скобки вокруг "выражения1" ставить не обязательно, так как приоритет операции :? очень низкий, ниже он только у присваивания. Условная операция позволяет писать долее короткие программы. Вот как выглядит в программе (пример 3.5) цикл для печати квадратов натуральныз чисел от 1 до m, по 6 чисел в строке; каждое число занимает 5 позиций и колонки отделяются одним пробелом, а каждая строка, включая последнюю, заканчивается символом перевода на новую строку "печатается" после каждого шестого элемента и после m-го. За любыми другими элементами выводится один пробел.

            Пример 3.5


МАССИВЫ

      Как известно, массив - это конечная совокупность данных одного типа. Можно говорить о массивах целых чисел, массивов символов и.т.д. Мы можем даже определить масссив, элементы которого - массивы( массив массивов), определяя, таким образм, многомерные массивы. Любой массив в программе должен быть описан: после имени массива добаляют квадратные скобки [], внутри которых обычно стоит число, показывающее количество элементов массива. Например, запись 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}};  

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

          Пример 3.6

/*квадраты натуральных чисел*/
main()
{
int m,i=1;scanf("%d",&m);
while(i_=m)
{
printf("5%d%c",i*i,(i%6==0 || i==m)? '\n':' ');
i++;
}
}
/*обращение массива*/
#include <stdio.h>
main()
{
int p,i=0;
static a[10]={10,11,12,13,14,
   15,16,17,18,19};
while(i<10/2)
{
p=a[i];
a[i]=a[9-i];
a[9-i]=p;
i++;
}
i=0;
while(i<10)
      printf(" %d",a[i++]);
}

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

         Пример 3.7

/*в массиве найти разность
   мин. и макс. элементов */
int fmax(x,n)
int x[],n;
{
int max, i=0; max=x[0];
while(i<n)
{
if(x[i]> max)
   max=x[i];
i++;
}
return(max);
}
#include <stdio.h>
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\n",max+min);
}

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

    Пример 3.8

/*макс одинаковых подряд*/
#include <stdio.h>
int a[]={5,6,6,6,4,3,3,3,3,3,8};
int n=10;
main()
{
int i,k,max;
i=k=max=1;
while(i<n)
{
if(a[i]==a[i-1])
k++;
else
{
if(k>max)max=k;
k=1;
}
i++;
}
printf("kmax=%d\n",(k>max)?k:max);
}

   Если, как в данном примере, размер массива пропущен, то транслятор определит его дляну, считая присваиваемые значения во время начальной инициализации. Условная операция (k>max)?k:max в операторе printf предусмотрена для того частного случая, когда весь массив состоит из одинаковых элементов. Приведем несколько примеров, в которых ведется обработка двумерных массивов. Но прежде одну полезную возможносить языка Си. Речь идет о препроцессорном утверждении #difine, позволяющем присваивать символические имена константам. В общем случае это утверждение записывают так: #define строка1 строка2 (точка с запятой не ставится).
   Прежде чем исходный текст программы будет передан компилятору, он обрабатывается препроцессором, котоый всюду в исходном тексте заменит вхождение "строка1" на "строка2". Например, строка #difine max 80, записанная в начале программы, обеспечит всюду замену указанного имени max на соответствующую константу. Замена имени связана не только числами, но и текстами. А теперь вернемся к примерам. В следующей программе (пример 3.9) строится единичная матрица a[m][m], размер которой определяется с помощью конструкции #difine m 5. Сам алгоритм вычисления элементов матрицы основан на возведении (i/j)*(j/i) равно единице тогда и только тогда. когда i равно j. В остальных случаях оно равно нулю.

  Пример 3.9

#define M 5
#include <stdio.h>
main()
{
int a[M][M];
int j,i=0;
while(i<M)
{
j=1;
while(j<M)
{
a[i][j]=(i/j)*(j/i);
printf("%d",a[i][j]);
j++;
}
i++;printf("\n");
}
}

      В программе (пример 4.0) определяется минимальный элемент кажой строки матрицы и выполняется обмен местами найденого и диагональю этой же строки. 
   Обращаем внимание на следующее обстоятельство. Если двумерный массив надо передать ыункции, то описание параметра в ней должно обязательно включать в себя размер строки массива, а размер столбцов несущественен. Так, массив из трех строк и пяти столбцов можно описать как int a[3][5]; либо int a[][5];

   Пример 4.0

/*обмен мин с диагональю*/
#include <stdio.h>
#define M 4
main()
{
static a[M][M]={
   { 3,4,1,5),
   {-1,6,7,0},
   { 1,8,7,-1},
   { 4,9,7,-1}};
int i, j, jmin, amin;
i=0;
while(i<M)
{
amin=a[i][0];
jmin=0;j=1;
while(j<m)
{
if(a[i][j]<amin)
{
amin=a[i][j];
jmin=j;
}
j++;
}
a[i][jmin]=a[i][i];
a[i][i]=amin;
i++;
}
i=0
while(i<M)
{
j=0;
while(j<M)
printf("%3d",a[i][j++]);
    printf("\n");
i++;
}
}

Массивы символов

      Любая символьная константа, например "ОЙ У ПОЛИ КРИНИЧКА", представляет собой массив символов. Во внутреннем представлении компилятор завершает такой массив символом "\0", так что любая программа может по нему легко обнаружить конец строки. Поэтому строка занимает в паямяти на один символ больше, чем записано между двойными кавычками. Нумерация элементов массива начинается с нуля. Надо помнить, что, например 'T' - это символ(буква), а ' T ' - это строка, состоящая из двух символов: 'T' и '\0'. Отсюда следует, что пустых строк не бывает. 
      Строка в языке Си - это разновидность константы и ее можно присваивать некоторой переменной, представляющей массив символов:

            char str[]="ТЕКСТ";

  Такая запись и короче и понятнее, чем общепринятая для начальной инициализации массивов:

         char str[]={'Т','Е','К','С','Т',};

  Если длина строки в квадратных скобках опущена, то она определяется автоматически, по количеству присваеваемых символов. В приведенном выше примере она равна шести. 
    Запишем программу (пример 4.1), в которой функция main формирует строку по указанному выше правилу и вызывает функцию length, которая в качестве аргумента получает начальный адрес этой строки и вычисляет ее длину(без учета завеершающего символа). Эта функция представляет собой самостоятельный интерес и будет использована нами а дальнейшем как библиотечная.

       Пример 4.1

/*длина сторки*/
length(s);
char s[];
{
int l=0;
while(s[l]!='\0')
i++;
return(i);
}
#include <stdio.h>
main()
{
static char str[]="ОЙ У ПОЛИ КРИНИЧКА";
printf("%d",length(str));
}

  Программа приведенная ниже (пример 4.2), выполняет сцепление двух строк. Собственно, сцепление выполняет функция concat(s,t), которая присоединят строку t к концу строки s. Объединяемые строки str1 и str2 объявлены как внешние, причем размер str1 достаточно большой, чтобы сохранить новую строку. Используя спецификацию формата %s, функция printf выводит всю строку сразу,  причем она "знает", что печать остановить при достижении завершающего символа "\0".

       Пример 4.2

/*сцепление строк*/
concat(s,t)
char s[],t[];
{
int i,j;
i=j=0;
/*поиск конца строки*/
while(s[i]!='\0')i++;
while((s[i++]=t[j++])!='\0');
/*копия t*/
#include <stdio.h>
char str1[45]="ОЙ ТАМ У ПОЛИ КРИНИЧКА";
char str2[]="ТАМ ХОРОША ВОДИЧКА";
main()
{
concat(str1,str2);
printf("%s",str);
}

         На пример 4.3 приведена программа с функцией revers(s), переставляющей символы строки s в обратном порядке. В качестве библиотечной исползуется  ранее рассматривавшаяся функция length. Функция revers меняет местами символы строки, симетричные относительно ее середины; если в строке нечетное число символов, то средний символ остается на месте.

           Пример 4.3

/*вращение строки*/
#include stdio.h
#include "length.c"
revers(s)
char s[];
{
int l,i=0;
l=length(s);
while(i_=l/2)
{
c=s[i]; s[i]=s[l-i-1];
s[l-i-1]=c;
i++;
}
}
main()
{
static char str[]= "ОЙ У ПОЛИ КРИНИЧКА";
revers(str);
printf("%s\n",str);
}

Организация циклов с помощью оператора for

      Оператора for является самой популярной структурой для организации циклов. В общем случае его можно представить так :

        for( выражение1; выражение2; выражение3) оператор;

В одной строке этот оператор определяет сразу три составляющие, отделяемые друг от друга точкой с запятой: а) начальное значение параметра цикла(выражение1), б) условие оканчания цикла (выражение2) , в) закон изменения параметра цикла(выражение3). Формально этот оператор эквиалентен последовательности операторов

выражение1;
while(выражение2)
{
  оператор;
  выражение3;
)

  В следующей программе (пример 4.4) оператор for служит для организации и печати последовательных чисел Фибоначчи, не превосходящих m. Проверка k<=m происходит перед каждым (в том числе и перед первым)   выполнением тела цикла. Тело цикла выполняется, если проверочное выражение истинно. Затем следует изменение параметра: k=k+1; этот оператор, кстати, формирует очередное число Фибоначчи, а оператор j=k-j; хранит предыдущее.

        Пример 4.4

/*числа Фибоначчи*/
#include <stdio.h>
main()
{
int m, k, j=1;
printf("введи m\n");
scanf("%d",&m);
for(k=1;k<=m;k=k+j)
{
     printf("%d",k);
    j=k-j;
}
}

    Как и в операторе while, тело цикла for может состоять из единственного оператора или из нескольких, заключенных в скобки. Следующяя программа (пример 4.5) предназначена для печати гистопрограммы длин читаемых слов (признак конца слова - пробел, запятая или "\n"). Оператор for управляет циклом, состоящим по сути из одного оператора if , так что скобки можно было бы опустить. Для построения гистопрограммы выбран символ, внутреннее представление которого в коде ASCII численно равно 220.
         В скобках после ключевого слова for можно размещать выражения. Однако выражение1 и выражение3 не обязаны присваивать начальное значение и изменять параметр цикла, а выражение2 не обязано быть проверкой условия. Но компилятор всегда интерпретирует выражение2 как истинну или ложь (ложь- двоичный нуль, истина - все остальное). Любое из трех может быть опущено, хотя точка с запятой должна остаться. Допускается даже такой вырожденный случай: for(;;);. Он означает бесконечный цикл, так как пропущенное выражение2 всегда трактуется как истина.

  Пример 4.5

#include <stdio.h>
main()
{
char str[24];
int i,n=0;
printf("введи строку:\n");
while((str[n++]=getchar())!='\n';
printf("%d\n",n);
for(i=0;i<n;i++)
{
if(str[i]!=' ' &&
str[i]!=',' && str[i]!= '\n')
printf("%c",220);
else
printf("\n");
}
}

   В Си есть еще операция ", " (запятая), которая чаще всего используется в операторе for. Пара выражений, разделенных запятой, вычисляются слева направо. В каждой из трех составляющих оператора for можно помещать несколько выражений, разделенных запятыми. Этот прием часто используют, например, для синхронного изменения двух индексов при обработке массивов. В качестве примера приведем функцию substr, которая позволяет из задонной строки s1 выделить подстроку s2 указанной длины L, начиная с k-го по порядку символа (нумерация символов начинается с нуля). Функция принимает также параметр m- общее кол-во символов в исходной строке, чтобы контролировать возможный выход за ее пределы(пример 4.6).

              Пример 4.6

/*выделение подстроки*/
substr(char s1, char s2, int l, int k, int m)
{
int j,i;
if(l+l>=m)k=m-l-1;
for(i=1,j=0;i<l+k;i++.j++)
s2[j]=s1[i];
}
#include <stdio.h>
main()
{
char str[80], str2[80];
int k,l,m=0;
printf("ведите строку");
while((str1[m++]=getchar())!='\n');
printf("\n");
/*m сохраняет свое значение*/
printf("введите l и k\n");
scanf("%d %d",&l, &k);
substr(str1,str2,l,k,m);
printf("%s\n",str2);
}

  Преимущество оператора for - компактность и объединение в одной строке трех операторов управления циклом - особенно заметно проявляется во вложенных циклах. Очередная прогрпмма(пример 4.6) предназначена для упорядочения целых чисел по возрастанию. Функция sor реализует один из наиболее простых алгоритмов сортировки - метод "пузырька".

Пример 4.6

/*СОРТИРОВКА */
#include <stdio.h>
main()
{
extern int sor();
int i,n;
static a[10]={10,1,9,2,8,3,7,4,6,5};
n=10;
sor(a,n);
for(i=0; i<n; i++)
  printf(" %d",a[i]);
}
/*________________*/
int sor(int x[], int n)
{
int i,j,z;
for(i=0;i<n-1;i++)
    for(j=i+1;j<n;j++)
if(x[i]>x[j])
{
z=x[i];
          x[i]=x[j];
          x[j]=z;
}
  return;
}

            Еще один пример со вложенными циклами демонстрирует программа (пример 4.7). Она использует одну полезную при обработке текстов функцию index. Функция index определяет , входит ли в некоторую строку s1 заданная подстрока s2 и выдает положение (индекс)  в строке s1 , начиная с которого строка s2 содержится в s1. Если s2 не входит в s1, то функция возращает '-1'. 

        Пример 4.7

/*индекс строки */
index(s1,s2)
char s1[],s2[];
{
int i,j,k;
for(i=0;s1[i]!='\0'; i++)
{
for(j=i,k=0;s2[k]!='\0'
&& s1[j]==s2[k]; j++,k++);
if(s2[k]=='\0')
return(i);
}
return(-1);
}
char str1[]="информатика"
chra str2[]="форма"
#include <stdio.h>
main()
{
printf("%d\n",index(str1,str2));
}

Организация циклов с помощью оператора do - while

      В общем виде этот оператор можно записать следующим образом:

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

        Выполняется "оператор", а затем вычисляется "выражение2". Если оно истинно, то снова выполняется "оператор" и.т.д. Если "выражение" становится ложным, циклический процесс заканчивается. Это так называемый цикл с постусловием: условие завершения цикла проверяется не в его начале. как это имеет место в операторах while и for, а в конце, уже после прохода по телу цика. Как следствие, тело цикла обязательно выполняется по крацней мере один раз. Этот тип цикла встречается нечасто, но иногда бывает полезен. Составим , например, функцию length(s), которая вычисляет длину строки s с учетом завершающегося нуля. Известно, что число символов в строке не меньше единицы, если учитывать этот нулеврй символ. Поэтому естественно предположить, что мы бедм проходить по телу цикла по крайней мере один раз. Значит, можно использовать цикл do - while(пример 4.8).

      Пример 4.8

/*длина строки*/
length(s)
char s[]
{
int i,l;
i=l=0;
do
i++;
while(s[l++]!='\0');
return(i);
}
#include stdio.h
main()
{
printf("%d\n",length(""));
printf("%d\n",length("мама"));
}

Операторы break и continue

      Часто при вознекновении некоторого события удобно иметь возможность досрочно завершить цикл. Используемый для этой цели оператор break (разрыв) вызывает немедленный выход из циклов, организуемых с помощью операторов for, while, do-while, а также прекращение оператора switch. Приведенная ниже программа обеспечивает поиск в заданном иассиве элемента, равного g (пример 4.9). В случае обнаружения такого элемента оператор break прекращает дальнейшее выполнение цикла. Так как параметр i сохраняет значение после выхода из цикла, то дальнейший анализ его значения (if(i==n)) позволяет судить об удачном (i<=n)  илинеудачном (i==n) поиске. В случае вложенных циклов оператор break немедленно прекращает выполнение самого внутреннего из объемлющих его циклов.

  Пример 4.9

/*линейный поиск*/
#include <stdio.h>
int a[]={1,2,3,33,5,6,0,8};
int n=8;
main()
{
int i,g=33;
for(i=0;i<n;i++)
if(a[i]==g)
break;
if(i==n)
printf("%d не найден\n",g);
else
printf("%d на %d месте \n"g,i);
}

  На примере 5.0 приведена программа, которая ведет подсчет числа различных элементов в массиве. Каждый очередной элемент a[i] сравнивается с последующими элементами массива. Если он не совпадает ни с одним из этих элементов, в счетчик k добавляется еденица. В противном случае внутренний цикл прерывается оператором break и начинается новая итерация внешнего цикла.

         Пример 5.0

/*число разных элементов*/
#include <stdio.h>
main()
{
static a[]={7,3,7,4,3,6};
int i,j,m,k=1;m=6;
for(i=0;i<m-1;i++)
{
  for(j=i+1;j<m;j++)
if(a[i]==a[j])
break;
if(j==m)
k++;
}
printf("%d разных элем. \n", k);
}

  Оператор continue тоже предназначен для прерывания циклического процесса, организуемого операторами for, while, do-while. Но в отличае от оператора break, он не прекращает дальнейшее выполнение цикла, а только немедленно переходит к следующей интерации того цикла, в теле которого он оказался. Он как бы имитирует безусловный переход на конечный оператор цикла, но не за ее пределы самого цикла. Программа на примере 5.1 использует оператор continue для пропуска отрицательных элементов массива, суммируя только положительные.

            Пример 5.1

#include <stdio.h>
main()
{
static int a[]={1,2,-3,4,-5,6};
int i,n,s;
n=6; s=0;
for(i=0; i<n; i++)
{
if(a[i]<=0)
continue; /*пропуск 0*/
      s+=a[i];
     }
printf("сумма = %d \n",s);
}

Переключатель

      Мы уже напоминали о том, что использовать для многозадачного ветвления в программе вложенные операторы if-then-else Если глубина вложенности этих операторов свыше трех, то конструкция теряет ясность. Более наглядным и понятным в данной ситуации выглядит оператор switch (переключатель), специально предназначенный для принятия одного из многих решений. Чаще всего этот оператор выглядит следующим образом:

switch(целое выражение)
{
case константа1: оператор1;
case константа2: оператор2;
...
...
...
case константаn: операторn;
default        : оператор;
}

При выполнении этого оператора вычисляется выражение, стоящее в скобках после ключевого слова switch, которое должно быть целым. Оно, в частности, может быть и символьным значением (в языке Си символьные значения автоматически расширяются до целых значений). Эта целая величина используется в качестве критерия для выбора одного из возможных вариантов. Ее значение сравнивается с константой операторов case. Вместо целой или литерной константы в операторе case может стоять дае некоторое константное выражение. Значения таких констант (выражений) должны быть различными в разных операторах case. При несовпадении выполняется переход к следующему case и сравнивается его константа. В случае совпадения "константы_i" выполняется "оператор_i", а также все последующие операторы case и default. Если не было ни одного совпадения и имеется оператор default, то выполняется стоящий за ним оператор. Если же оператора default не было, выполнение программы продолжится с оператора, следующего за структурой switch. Таким образом, при каждом выполнении оператора просматриваются все метки case. Рассмотрим следующую программу (пример 5.2)

          Пример 5.2

/*проверка switch*/
#include <stdio.h>
main()
{
int k=2;
switch(k)
{
  case 0; printf("выбор 0\n");
  case 1; printf("выбор 1\n");
  case 2; printf("выбор 2\n");
  case 3; printf("выбор 3\n");
  default: printf("default\n");
  }
}

  Как видите, происходит то, о чем мы говорили: выполняется альтернатива, соответствующая k=2 и все последующие операторы case, а также выриант default. Чтобы обеспечить выбор одного из многих вариантов (что и требуется чаще всего), используют обычно оператор braek, который вызывает немедленный выход из оператора switch (пример 5.3) Для этой цели можно применять и оператор return, а continue можно применять лишь в случае, когда сам оператор switch находится внутри цикла. Тогда continue вызывает немедленный переход к следующей интеракции, без рассмотрения оставшихся case.

        Пример 5.3

/*проверка switch*/
#include <stdio.h>
main()
{
int k=2;
switch(k)
{
  case 0; printf("выбор 0\n");
         break;
  case 1; printf("выбор 1\n");
   break;
  case 2; printf("выбор 2\n");
  break;
  case 3; printf("выбор 3\n");
         break;
  default: printf("default\n");
}
}

Переключатель

      Указатель - это переменная, значением которой является адресс другой переменной. Так как указатель может ссылаться на переменные разных типов, с указателем в языке Си связывается тип того объекта, на который он ссылается. Для описания указателей используется операция косвенной адресации *. Например, указатель целого типа uk описывается так : int *uk. Унарная операция &, примененная к некоторой переменной, показывает, что нам нужен адресс этой переменной, а не ее текущее значение. Если переменная uk объявлена как указатель, то оператор присваивания uk=&x означает: "взять адресс переменной x и присвоить его значение переменной-указателю uk".

  Унарная операция *. примененная к указателю, обеспечивает доступ к содержимому ячейки памяти, на которую ссылается указатель. Например, *uk можно описать словами как "то, что содержится по адресу, на который указывает uk". Указатели могут использоваться в выражениях. Если. например, переменная uk указывает на целое x, то *uk может во всех случаях использоваться вместо x; так, *uk+1 увеличивает x на единицу, а *uk=0 равносильно x=0. Два оператора присваивания uk=&x;y=*uk; выполняет то же самое, что и один оператор y=x. Польза от применения указателей в таких ситуациях, мягко выражаясь, невелика.

   Наиболее полно их преимущества при обработке массивов и, в частности, символьных строк. Указатели и массивы тесно связаны друг с другом. Прежде чем рассмотреть эту связь подробно, заметим, что если uk - некоторый указатель, то uk++ увеличивает его значение и он теперь указывает на следющий, соседний адресуемый объект. Значение uk используется в выражении, а затем увеличивается. Аналогично определяются операции uk--, ++uk, --uk. В общем случае указатель uk можно скаладывать с целым числом i. Оператор uk+=i передвигает ссылку на i элементов относительно текущего значения. Эти конструкции подчиняются правилам адресной арифметики.

  А теперь вернемся к массивам. Пусть имеется описание int a[5]; Оно определяет массив размером 5 элементов, т.е. пять последовательно расположенных ячеек памяти a[0], a[1], a[2], a[3], a[4]. Адресс i-го элемента массива равен сумме адреса начального елемента массива и смещения этого элемента на i единиц от начала массива. Это достигается индексированием: a[i]-i -й элемент массива. Но доступ к любому элементу массива может быть выполнен и с помощью указателей, причем, более эффективно. Если uk -указатель на целое, описанный как int *uk, то uk после выполнения операции uk=&a[0] содержит адресс a[0], а uk+i  указывает на i -й элемент массива. Таким образом, uk+i является адрессом a[i], а *(uk=1) - содержимым i- го элемента(операции * и & более приоритетны, чем арифметические операции). Так как имя массива в программе отождествляется с адресом его первого элемента, то выражение uk=&a[0] эквивалентно такому: uk=a. Поэтому значение a[i] можно записать как *(a+i). Применив к этим двум элементам операцию взятия адреса, получим, что &a[i] и a+i идеитичны.

         Раньше, в связи с использованием функции scanf, мы говорили, что применение указателей в качестве аргументов функции дает способ обхода защиты значений вызывающей функции от действий вызванной функции. На примере 5.4 приведен текст программы с функцией obmen(x,y), которая меняет местами значения двух целых величин. Так как x,y - адреса переменных a и b, то *x и *y обеспечивают косвенный доступ значениям a и b. К сказанному добавим, что использование указателей позволяет нам обходить ограничения языка Си, согласно которым функциям может возращать только одно значение.

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

  Пример 5.4

/*обмен a и b */
obmen(int *x,int *y)
{
int t;
t=*x;
*x=*y;
*y=t;
}
#include <stdio.h>
main()
{
int a,b;
a=3;b=7;
obmen(a,b);
printf("a=%d b=%d",a,b);
}

  В определении функции формальные параметры char s[] и char *s совершенно идеитичны. Операция s++ (пример 5.5) увеличение на единицу текущее значение указателя, первоначально указывающее на первый символ строки, а операция *s!='\0' сравнивает очередной символс признаком конца строки.

         Пример 5.5

/*длина строки*/
length(s)
char *s;
{
int i;
for(i=0; *s!='\0';s++)
i+++;
return(i);
}

  Кроме ранее расмотренных операций адресной арифметики, к указателям можно применить операции сравнения == и !=. Даже операции отношения Б <,>= и т.п. работают правильно, если указатели ссылаются на элементы одного и того же  массива. В последнем случае возможно даже вычитание ссылок: если u и s ссылаются на элементы одного массива, то u-s есть число элементов между u и s. Используем этот факт для составления еще одной версии функции length (пример 5.6). Cначала u указывает на первый символ строки (char *u =s). Затем в цикле по очереди проверяется каждый символ, пока в конце концов не будет обнаружен "\0". Разность u-s дает как раз длину строки.

        Пример 5.6

/*длина строки*/
length(s)
char *s;
{
char *u=s;
while(*u!='\0')
u++;
return(u-s);
}

Для илюстрации основных аспектов применения указателей в СИ рассмотрим функцию копирования строки s1 в строку s2. Сначала приведем версию, основанную на работе с массивами(пример 5.7). Для сравнения рядом помещена версия с использованием указателей(пример 5.8).

            Пример 5.7

/*копия строки*/
copy(s1,s2)
char s1[],s2[];
{
int i=0;
while((s2[i]=s1[i])!='\0')
i++;
}

   Пример 5.8

/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while((*s2=*s1)!='\0')
{
s2++;s1++;
}
}

   Здесь операция копирования помещена непосредственно в условие, определяющее момент цикла: while((*s2=*s1)!='\0'). Продвижение вдоль массивов вплоть до тех пор, пока не встретится "\0", обеспечивают операторы s2++ и s1++. Их, однако, тоже можно поместить в проверку (пример 5.9).

      Пример 5.9

/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while((*s2++=*s1++)!='\0');
}

Еще раз напомним, что унарные операции типа * и ++ выполняются справа налево. Значение *s++ cесть символ, на который указывает s до его увеличения. Так как значение "\0" есть нуль, а цикл while проверет, не нуль ли выражение в скобках, то это позволяет опустить явное сравнение с нулем(пример 6.0) . Так постепенно функция копирования становится все более компактной и ... все менее понятной. Но  в системном программировании предпостение чаще отдают именно компактным и, следовательно, более эффективным по быстродействиб программам.

        Пример 6.0

/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while(*s2++=*s1++);
}

   В языке Си, что некоторая литерная строка, выраженная как "строка" , фактически рассматривается как указатель на нулевой элемент массива " строка". Допускается, например, такая интересная запись:

            char *uk; uk="ИНФОРМАТИКА";

   Последний оператор присваивает указателю адрес нулевого элемента строки, т.е. символа "И". Возникает вопрос, где находится массив, содержащий символы "ИНФОРМАТИКА"? Мы его нигде не описывали. Ответ такой: эта строка - константа; она является частью функции, в которой встречается, точно также, как целая константа 4 или символьная константа "А" в операторах i=4; c="A";. Более детально пояснит сказанное программа на пример 6.1, которая печатает строку символов в обратном порядке.

       Пример 6.1

#include <stdio.h>
main()
{
char *uk1,*uk2;
uk1=uk2="ИНФОРМАТИКА";
while(*uk2!='\0')
  putchar(*uk2++);
putchar('\n');
while(--uk2 >= uk1)
putchar(*uk2);
putchar('\n');
}

В самом начале указателям uk1 и uk2 присваивается начальный адресс строки "ИНФОРМАТИКА". Затем строка посимвольно печатается и одновременно указатель uk2 смещается вдоль строки. В конце вывода uk2 указывает на последний символ исходной строки. Во втором цикле while все тот же указатель uk2 начинает изменяться в обратном направлении, уменьнаясь до тех пор, пока он не будет указывать на нулевой элемент массива, обеспечивая выдачу строки в обратном порядке.


Указатель

      Указатель - это переменная, значением которой является адресс другой переменной. Так как указатель может ссылаться на переменные разных типов, с указателем в языке Си связывается тип того объекта, на который он ссылается. Для описания указателей используется операция косвенной адресации *. Например, указатель целого типа uk описывается так : int *uk. Унарная операция &, примененная к некоторой переменной, показывает, что нам нужен адресс этой переменной, а не ее текущее значение. Если переменная uk объявлена как указатель, то оператор присваивания uk=&x означает: "взять адресс переменной x и присвоить его значение переменной-указателю uk".

  Унарная операция *. примененная к указателю, обеспечивает доступ к содержимому ячейки памяти, на которую ссылается указатель. Например, *uk можно описать словами как "то, что содержится по адресу, на который указывает uk". Указатели могут использоваться в выражениях. Если. например, переменная uk указывает на целое x, то *uk может во всех случаях использоваться вместо x; так, *uk+1 увеличивает x на единицу, а *uk=0 равносильно x=0. Два оператора присваивания uk=&x;y=*uk; выполняет то же самое, что и один оператор y=x. Польза от применения указателей в таких ситуациях, мягко выражаясь, невелика.

   Наиболее полно их преимущества при обработке массивов и, в частности, символьных строк. Указатели и массивы тесно связаны друг с другом. Прежде чем рассмотреть эту связь подробно, заметим, что если uk - некоторый указатель, то uk++ увеличивает его значение и он теперь указывает на следющий, соседний адресуемый объект. Значение uk используется в выражении, а затем увеличивается. Аналогично определяются операции uk--, ++uk, --uk. В общем случае указатель uk можно скаладывать с целым числом i. Оператор uk+=i передвигает ссылку на i элементов относительно текущего значения. Эти конструкции подчиняются правилам адресной арифметики.

  А теперь вернемся к массивам. Пусть имеется описание int a[5]; Оно определяет массив размером 5 элементов, т.е. пять последовательно расположенных ячеек памяти a[0], a[1], a[2], a[3], a[4]. Адресс i-го элемента массива равен сумме адреса начального елемента массива и смещения этого элемента на i единиц от начала массива. Это достигается индексированием: a[i]-i -й элемент массива. Но доступ к любому элементу массива может быть выполнен и с помощью указателей, причем, более эффективно. Если uk -указатель на целое, описанный как int *uk, то uk после выполнения операции uk=&a[0] содержит адресс a[0], а uk+i  указывает на i -й элемент массива. Таким образом, uk+i является адрессом a[i], а *(uk=1) - содержимым i- го элемента(операции * и & более приоритетны, чем арифметические операции). Так как имя массива в программе отождествляется с адресом его первого элемента, то выражение uk=&a[0] эквивалентно такому: uk=a. Поэтому значение a[i] можно записать как *(a+i). Применив к этим двум элементам операцию взятия адреса, получим, что &a[i] и a+i идеитичны.

         Раньше, в связи с использованием функции scanf, мы говорили, что применение указателей в качестве аргументов функции дает способ обхода защиты значений вызывающей функции от действий вызванной функции. На примере 5.4 приведен текст программы с функцией obmen(x,y), которая меняет местами значения двух целых величин. Так как x,y - адреса переменных a и b, то *x и *y обеспечивают косвенный доступ значениям a и b. К сказанному добавим, что использование указателей позволяет нам обходить ограничения языка Си, согласно которым функциям может возращать только одно значение.

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

  Пример 5.4

/*обмен a и b */
obmen(int *x,int *y)
{
int t;
t=*x;
*x=*y;
*y=t;
}
#include <stdio.h>
main()
{
int a,b;
a=3;b=7;
obmen(a,b);
printf("a=%d b=%d",a,b);
}

  В определении функции формальные параметры char s[] и char *s совершенно идеитичны. Операция s++ (пример 5.5) увеличение на единицу текущее значение указателя, первоначально указывающее на первый символ строки, а операция *s!='\0' сравнивает очередной символс признаком конца строки.

         Пример 5.5

/*длина строки*/
length(s)
char *s;
{
int i;
for(i=0; *s!='\0';s++)
i+++;
return(i);
}

  Кроме ранее расмотренных операций адресной арифметики, к указателям можно применить операции сравнения == и !=. Даже операции отношения Б <,>= и т.п. работают правильно, если указатели ссылаются на элементы одного и того же  массива. В последнем случае возможно даже вычитание ссылок: если u и s ссылаются на элементы одного массива, то u-s есть число элементов между u и s. Используем этот факт для составления еще одной версии функции length (пример 5.6). Cначала u указывает на первый символ строки (char *u =s). Затем в цикле по очереди проверяется каждый символ, пока в конце концов не будет обнаружен "\0". Разность u-s дает как раз длину строки.

        Пример 5.6

/*длина строки*/
length(s)
char *s;
{
char *u=s;
while(*u!='\0')
u++;
return(u-s);
}

Для илюстрации основных аспектов применения указателей в СИ рассмотрим функцию копирования строки s1 в строку s2. Сначала приведем версию, основанную на работе с массивами(пример 5.7). Для сравнения рядом помещена версия с использованием указателей(пример 5.8).

            Пример 5.7

/*копия строки*/
copy(s1,s2)
char s1[],s2[];
{
int i=0;
while((s2[i]=s1[i])!='\0')
i++;
}

   Пример 5.8

/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while((*s2=*s1)!='\0')
{
s2++;s1++;
}
}

   Здесь операция копирования помещена непосредственно в условие, определяющее момент цикла: while((*s2=*s1)!='\0'). Продвижение вдоль массивов вплоть до тех пор, пока не встретится "\0", обеспечивают операторы s2++ и s1++. Их, однако, тоже можно поместить в проверку (пример 5.9).

      Пример 5.9

/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while((*s2++=*s1++)!='\0');
}

Еще раз напомним, что унарные операции типа * и ++ выполняются справа налево. Значение *s++ cесть символ, на который указывает s до его увеличения. Так как значение "\0" есть нуль, а цикл while проверет, не нуль ли выражение в скобках, то это позволяет опустить явное сравнение с нулем(пример 6.0) . Так постепенно функция копирования становится все более компактной и ... все менее понятной. Но  в системном программировании предпостение чаще отдают именно компактным и, следовательно, более эффективным по быстродействиб программам.

        Пример 6.0

/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while(*s2++=*s1++);
}

   В языке Си, что некоторая литерная строка, выраженная как "строка" , фактически рассматривается как указатель на нулевой элемент массива " строка". Допускается, например, такая интересная запись:

            char *uk; uk="ИНФОРМАТИКА";

   Последний оператор присваивает указателю адрес нулевого элемента строки, т.е. символа "И". Возникает вопрос, где находится массив, содержащий символы "ИНФОРМАТИКА"? Мы его нигде не описывали. Ответ такой: эта строка - константа; она является частью функции, в которой встречается, точно также, как целая константа 4 или символьная константа "А" в операторах i=4; c="A";. Более детально пояснит сказанное программа на пример 6.1, которая печатает строку символов в обратном порядке.

       Пример 6.1

#include <stdio.h>
main()
{
char *uk1,*uk2;
uk1=uk2="ИНФОРМАТИКА";
while(*uk2!='\0')
  putchar(*uk2++);
putchar('\n');
while(--uk2 >= uk1)
putchar(*uk2);
putchar('\n');
}

В самом начале указателям uk1 и uk2 присваивается начальный адресс строки "ИНФОРМАТИКА". Затем строка посимвольно печатается и одновременно указатель uk2 смещается вдоль строки. В конце вывода uk2 указывает на последний символ исходной строки. Во втором цикле while все тот же указатель uk2 начинает изменяться в обратном направлении, уменьнаясь до тех пор, пока он не будет указывать на нулевой элемент массива, обеспечивая выдачу строки в обратном порядке.


СТРУКТУРЫ И УКАЗАТЕЛИ

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

                struct anketa *uk;

     говорит , что uk - указатель на структуру типа anketa. Обозначение относится к конкретному элементу структуры и означает выборку этого элемента, например: uk-> tab_nom. Поскольку uk есть указатель на структуру anketa, то к элементу tab_nom можно обращаться и так:

   (*uk).tab_nom,

если учесть, чтоуказатель установлен на начало массива структур. Имя массива, как обычно, эквивалентно адресу его начального элемента и при добавлении к указателю на структуру или вычитании из него целого числа размер структуры учитывается автоматически  Так, оператор uk=a; устанавливает указатель на первый экземпляр массива структур, а запись ++a; обеспечивает автоматический переход к следующему экземпляру. В выражении (*uk).fio скобки обязательны, так как приоритет операции выделения элемента " . " выше чем у "*".


Препроцессор языка Си

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

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

  Замена лексических единиц. Командная строка вида #define name text вызывает в оставшейся части программы замену всех вхождений идентификатора name на строку text. Например, определение #define p1 3.14159265 позволяет использовать в программе имя p1 вместо константы 3.14159265. Обратите внимание, что это определение не завершается точкой с запятой. Замещающий текст обычно представляет собою остаток строки. Длинное определение можно продолжить, если в конце продолжаемой строки поставить \. Внутри строк, заключенных в кавычки, подстановка не производится, так что, например, для определенного выше имени P1 в printf("P1"); подстановки не будет. Имена могут переопределяться и новые определения могут использовать ранее введенные определения.

   Так как препроцессор не является частью компилятора языка Си, а представляет относительно простой макрогенератор, имеется возможность переопределять различные синтаксические единицы языка-лексемы (т.е. идентификаторы, ключевые слова, константы, цепочки литер, знаки операций и знаки пунктуаций). В приведенной (на примере 6.2) программе, предназначенной для выявления всех пар целых чисел из интервала [-n,n], являющихся решениями уравнения 2*y-x*x =4 , используются привычные для таких языков как Алгол, Паскаль, операторные скобки begin-end вместо пары {}, ключевое слово then . Это стало возможным благодаря предварительно определенным лексическим заменам.

Пример 6.2

#include <stdio.h>
#define then
#define begin {
#define end }
main()
begin
int n,x,y,k=0;
printf("введи n\n");scanf("%d",&n);
for(x=-n;x<=n;x++)
for(y=-n;y<=n;y++)
  if(2*y-x*x==4)then
   begin
    k=k+1;
    printf(x=%d, y=%d\n",x,y);
   end
if(k==0) then
printf("корней нет\n ");
end

Приведенная выше технология замены лесксем относится к макросредствам языка. Строка #define  name text называется макроопределением, name называется макрошаблоном, а text - макрорасширением. Каждое появление имени name в теле программы называется макровызовом. Командная строка

            #define name (p1,p2, ..,pk) text

является макроопределением с аргументами. За именем name в круглых скобках (после name не должно быть пробела!) следует разделенные запятыми формальные параметры p1, p2 .., pk, также являющиеся идентификаторами. Каждый раз, когда в тексте программы встречается имя макроопределения с фактичкскими аргументами, они подставляются вместо формальных, так что заменяющий текст будет зависеть от вида макровызова. Определим в качестве примера такую макроподстановку:

  #define MAX(X,Y) ((X)>(Y)?(X):(Y))

Как и в определении функции, переменные X и Y в макроопределении являются формальными параметрами. После этого строка в программе:

       m=MAX(a+b, a-b);

будет заменена на строку

m=((a+b)>(a-b)?(a+b):(a-b);

Текст макроопределения берут в скобки для обеспечения большей надежности программы. Пренебрежение скобками может привести к серьезным ошибкам, что иллюстрирует следующий пример 6.3. делит число 16 на квадрат числа 2 и дает правильный результат. Во второй программе (пример 6.4), где скобки в макроопределении опущены, результат ошибочный, так как макроподстановка породила текст:

       printf("%d\n",16/2*2);

что конечно, не равносильно задуманному.

     Пример 6.3

  #include <stdio.h>
#define SQR(n) (n*n)
main()
{
printf("%d\n",16/SQR(2));
}

Пример 6.4

#include <stdio.h>
#define SQR(n) n*n
main()
{
printf("%d\n",16/SQR(2));
}

      Макроопределения иногда используются вместо определений функций, обычно из сображений эффективности. Но следует помнить, что препроцесор может лишь тупо и бездумно заменять одну строку на другую, не разбираясь, зачем это нужно. В отличае от параметра функции, параметр макроопределения вычисляется при каждом вхождении в макроопределение. Поэтому иакровызов MAX(i++, j++)  для приведенного выше макроопределения к увеличению i и j на 2.


Включение файлов

Командная строка для включения файлов выглядит следующим образом: #include "filename" и указывает препроцессору, что содержимое файла с именем filename надо вставить в том месте программы, где использованна командная строка. Эта возможность препроцессора позволяет следовать в Си идеям структурного программирования, согласно которым большая порграмма обычно расчленяется на логически завершенные части и каждая затем оформляется как самостоятельная функция.   После отладки каждая из них оформляется в виде отдельного файла и при необходимости включается в отлаживаемую программу командой #include. Часто в таких файлах содержатся макроопределения и после включения их в исходный модуль утверждением #include они становятся доступными для всех функций. Например, приведенная ниже (пример 6.5) полезные макроопределения, используемые в программе (пример 6.6), можно "замаскировать", поместив их в файл "makro.h", а в начале исходного файла с текстом программы поместить командную строку #include "makro.h".

             Пример 6.5

#include <stdio.h>
#define pr(int) printf("%d",int);
#define SKIP putchar('\n');
#define PRINT1(X1) pr(X!);SKIP
#define PRINT2(X1,X2) pr(X1);PRINT(X2);SKIP
#define PRINT3(X1,X2,X3) pr(X1);PRINT(X2,X3);SKIP

Команда включения может иметь другую форму: #include <filename>. В частности, все наши пограммы включали командную строку #include <stdio.h>, благодаря которой программы пользователя могут обращаться к функциям, обеспечивающим стандартный ввод-вывод (getchar, putchar, printf, scanf и др.)

              Пример 6.6

#include <stdio.h>
main()
{
int a,b,c;
a=5;b=7;c=a+b;
PRINT1(a);PRINT2(a,b);PRINT3(a,b,c);
}

Условная компиляция

Условные конструкции препроцессора позволяют компилировать или пропускать часть программы в зависимсти от выполнения некоторого условия. Условие может принимать одну из описываемых ниже форм.

#if константное_выражение

Проверяется значение выражения, составленного из констант и если оно не равно нулю, компилируется (включается) последующий текст.

#ifdef идентификатор

Последующий тест компилируется, если "идентификатор" уже был опредеоен для препроцессора в команде #define.

#ifndef идентификатор

Последующий текст компилируется, если "идентификатор" в данный момент не определен. Конструкция

#undef идентификатор

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

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

       Пример 6.7

#define SIZE 16
#include stdio.h
main()
{
char c='A';
#ifdef SIZE
  int x=123;
  printf("x=%d\n",x);
#else
  static char x[SIZE]="информатика";
  printf("x=%s\n",x);
#endif
  printf("%c\n",c);
}

Функции Fopen, fclose, getc, putc

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

   В программе нужно описывать ссылки на файлы и выглядит это, например, так: FILE *fu; Здесь fu означает указатель на FILE, а fopen выдает ссылку на этот файл. Функция fopen имеет следующий заголовок:

    FILE *fopen(char *fname, char type);

Обращение к fopen в программе делается так: fu=fopen(fname, type); Строка символов fname содержит имя файла, который надо открыть; type- тоже строкасимволов, заключенная в кавычки и указывающая, как бует использоваться файл: "r"-чтение, "w" - запись, "r+" - чтение с дозаписью, "a" -дозапись. Функция fopen возвращает указатель, с помощью которого мы в дальнейшем будем обращаться к этому файлу. Примеры:

FILE *uin, *uout;
uin=fopen("MAK1","r");
uout=fopen("MAK2","w");

   Файл с именем MAK1 открывается для чтения и далее идентифицируется как uin; файл MAK2 открывается для записи и связывается с идентификатором uout.




Letyshops [lifetime]