Последние новости

YoungCoder теперь и на Stepikе. Записывайтесь: https://vk.cc/75rISy

Чтобы записаться на курс, необходимо зарегистрироваться на Степике: https://vk.cc/75rIC4

Это моя личная ссылка-приглашение на Stepik для вас. Регистрируясь по этой ссылке, записываясь на курсы и решая задачи, Вы помогаете автору данного сайта принять участие в конкурсе платформы Stepik! Подробности конкурса здесь: https://vk.cc/75rKuS

понедельник, 9 июля 2012 г.

Занятие 10. Функции определяемые пользователем.Уроки программирования для чайников. Язык Си.

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

Прочитайте улучшенную версию этого урока "Простые пользовательские функции".

В новой версии:

  • Ещё более доступное объяснение
  • Дополнительные материалы
  • 10 задач на программирование с автоматической проверкой решения

В домашнем задании к уроку №5 я просил вас, написать программу которая решает уравнение ах=b в целых числах относительно х. Что многие успешно сделали. Разберем теперь другую сторону вопроса. Пусть нам надо проверить одну такую программу. Вот её код.

Листинг 10.1

#include <stdio.h>
int main(void)
{
int a,b;
scanf("%d %d",&a,&b);

if((a==0)&&(b!=0)) printf("no solution\n");
else {
      if((a==0)&&(b==0)) printf("many solution\n");
      else {
            if (b%a!=0) printf("no solution\n");
            else
                  printf("%d\n",b/a);
            }
      }
}

return(0);
}

Теперь подумаем как вообще должно решаться такое уравнение и подберем к нему набор тестов.
ах=b следовательно х=b/a. Так как решение в целых числах, то при делении b на a не должно получаться остатка. 
Получаем несколько вариантов решения.
1) а=0 и b=0  х любое число.т.е.  программа должна вывести many solution.
2) a=0 и b!=0 решений нет. Программа должна ответить no solution
3) b делится на а без остатка, то это и b/a решение.
4) b делится на а с остатком. Программа должна вывести no solution

Теперь подберем проверочные значения для каждого случая.
1. а=0, b=0
2. a=0, b=3
3. a=3, b=9
4. a=3, b=7

Теперь нам надо проверить нашу программу на всех этих данных и если в ответе получается 
1.many solution
2. no solution
3.3
4.no solution
то программа написана верно.

Разберемся как можно сделать это используя те знания, которые у нас уже есть за 9 уроков.

1 способ.
Мы можем запустить нашу программу несколько раз и подставить в неё эти данные, но это слишком долго. Отметаем этот способ.

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

Листинг 10.2
#include <stdio.h>
int main(void)
{

      for(int i=0;i<4;i++) {
            int a,b;
            scanf("%d %d",&a,&b);

            if((a==0)&&(b!=0)) printf("no solution\n");
            else {
                  if((a==0)&&(b==0)) printf("many solution\n");
                  else {
                        if (b%a!=0) printf("no solution\n");
                        else
                             printf("%d\n",b/a);
                        }
                  }
            }
      }
return(0);
}


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

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

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

4 способ.
Хорошо было бы написать так программу, чтобы мы туда один раз скопировали текст своей программы, запустили её и она нам сразу выдала бы, правильно работает программа или нет. 

Вот здесь-то нам и придут на помощь подпрограммы. Подпрограмма это отдельная программа которая может выполняться внутри основной программы. 
Подпрограммы в различных языках программирования делят на два вида. Функции и процедуры. Различие между двумя этими названиями лишь в том, что 
функция это подпрограмма, которая после своего выполнения возвращает в программу какое-то значение, а  процедура это подпрограмма, которая после своего выполнения не возвращает в программу ничего. 
Функция main, которая есть в каждой нашей программе является именно функцией, так как после того как она выполнится она возвращает в программу которое её вызвала, а в данном случае операционная система, некоторое значение. 
В некоторых языках программирования функции и процедуры разграничены синтаксисом языка. Например, в языке программирования Pascal. В языке Си, процедура это любая функция которая возвращает значение типа void.  Поэтому, здесь и далее я  буду использовать название функция как для функций, так и для процедур.

Как задать функцию.
Написать свою функцию очень просто, и вы уже много раз это делали. Любая функция состоит из двух частей: заголовка и тела функции. Покажем это на примере функции main. 
ЗАГОЛОВОК ФУНКЦИИ.
Заголовок функции состоит из трех частей. Сначала нужно указать тип значения которое возвращает функция. В нашем случае это int. То есть когда функция main закончит свою работу она должна вернуть в программу которая её вызвала какое-то целое значение. Если нам не нужно чтобы программа возвращала какое-то значение, то пишем тип void. 
Если бы функция main не должна была бы ничего возвращать, то её заголовок выглядел бы так. 
Листинг 10.3
void main(void)

После типа возвращаемого значения идет имя функции. В нашем случае это имя main. Но могло быть и какое-нибудь другое.

Следом за именем функции в скобках, пишут типы и количество аргументов (параметров) функции. Т.е. значения которые передаются в функцию. В нашем случае там написано void, это значит то функция не принимает никаких аргументов.
Несколько примеров заголовков функций.

Листинг 10.4
int zadanie_1(void) //функция с именем zadanie_1, которая не принимает ни одного параметра, и после окончания работы возвращает значение типа int
void func(int,int)  //функция с именем func, которая принимает два параметра каждый из которых целого типа, и после окончания работы не возвращает ничего.
float f_3(int,int,float ) //функция с именем f_3, которая принимает три аргумента, причем первые два типа int, а третий типа float, и возвращает значение типа float.


ТЕЛО ФУНКЦИИ.
После заголовка функции в фигурных скобках пишется тело функции. 
Здесь описано как должна эта функции работать. Если функция должна возвращать какое-то значение то в теле функции обязательно должна быть инструкция return. В нашем примере, так как функция main должна возвращать значение типа int, после того как программа вывела на экран Hello World, написана строка return (0); Т.е функция возвращает 0, в программу которая её вызвала.


Задавать и описывать функции мы уже научились. Теперь оформим нашу программу, из листинга 10.1 в виде функции, принимающей 2 значение типа int, и которая ничего не возвращает в результате своей работы

Листинг 10.5
void fun1(int a, int b)
{
if((a==0)&&(b!=0)) printf("no solution\n");
else {
      if((a==0)&&(b==0)) printf("many solution\n");
      else {
            if (b%a!=0) printf("no solution\n");
            else { 
                  printf("%d\n",b/a);
            }
      }
}

}


Как можете видеть в заголовке мы определили функцию c именем fun1 которая принимает два аргумента целого типа, причем мы сразу дали им имена a и b. Теперь внутри функции мы можем их использовать как обычные переменные с именами a и b. Параметры которые описаны в заголовке функции называются формальными параметрами.

Теперь когда мы разобрались с объявлением функции, надо научится её использовать. Любая функция прежде чем быть использованной, должна быть объявлена. Функции объявляются после подключения библиотек, перед объявление функции main.
Напишем программу, в которой объявим нашу функцию и используем её.

Листинг 10.6
#include <stdio.h>

void fun1(int a, int b){
      if((a==0)&&(b!=0)) printf("no solution\n");
      else {
            if((a==0)&&(b==0)) printf("many solution\n");
            else {
                  if (b%a!=0) printf("no solution\n");
            else {
                  printf("%d\n",b/a);
            }
            }
      }
}

int main (void) {
      int x,y;
      scanf("%d %d",&x,&y);
      fun1(x,y);
      fun1(3,9);

return(0);
}

Разберем код нашей программы и заодно посмотрим как она работает.
В первой строке подключаем stdio.h.
Далее объявляем функцию c именем fun1 которая принимает два аргумента целого типа, причем мы сразу дали им имена a и b. Потом в фигурных скобках описано тело функции. Т.е как должна работать наша функция.  Так как функции имеет тип возвращаемого значения void, значит она ничего не возвращает, поэтому нет оператора return в нашей функции.
После тела функции fun1, объявляем функцию main. Объявляем две переменные типа int. Считываем что в них значения которые введет пользователь. Далее вызываем функцию fun1 и передаем ей в качестве аргументов переменные х и у. После этого опять вызываем функцию, но уже передаем ей в качестве аргументов числа 3 и 9.

Результат работы этой программы, при вводе данных 2 и 5 представлен на рисунке ниже.

Разберемся как работает вызов функции. Начнем с более легкого, что происходит когда мы пишем 
fun1(3,9);
После того как программа встречает эту строчку она начинает искать в выше функции main объявление функции с именем fun1 которая принимает два параметра. Параметры которые передаются в функцию в программе, называются фактическими параметрами. Если она не найдет такую функцию, то программа не скомпилируется, ну и следовательно не выполнится. Если такая функция есть, то программа передает управление в эту функцию.
Первым делом она создает две переменные целого типа с именами a и b. Которые указаны в заголовке. И присваивает им значения a=3, b=9.Далее выполняется тело функции. После того, как функция закончит работу, управление снова передается в основную программу. А переменные а и b удаляются. И выполняется строчка следующая за вызовом функции. 

ВАЖНО!!!
Обратите внимание, что после того, как функция закончила свою работу, переменные a и b удаляются. Они доступны только внутри функции в которой они объявлены. 

Теперь разберем случай с передачу в функцию переменных. 
fun1(x,y);
После того как программа встречает эту строчку она начинает искать в выше функции main объявление функции с именем fun1 которая принимает два параметра. Если она не найдет такую функцию, то программа не скомпилируется, ну и следовательно не выполнится. Если такая функция есть, то программа передает управление в эту функцию.
Первым делом она создает две переменные целого типа с именами a и b. Которые указаны в заголовке. В эти значения копируются значения переменных, которые были переданы в функцию в качестве параметров. Далее выполняется тело функции. После того, как функция закончит работу, управление снова передается в основную программу. А переменные а и b удаляются. И выполняется строчка следующая за вызовом функции

ВАЖНО!!!
Обратите внимание, что в функцию передаются не сами переменные х и у, а только копии их значений. Т.е. любые действия производимые потом внутри функции на них никак не влияют.

Еще один важный момент. Область видимости переменных. Как уже говорилось выше переменные a и b доступны только внутри функции, в которой они объявлены. Их область видимости тело функции fun1. За пределами этой функции эти переменные не существуют. 
Из этого следует интересный факт. Например, если мы перепишем код нашей программы вот так.
Листинг 10.7
#include <stdio.h>

void fun1(int a, int b){
      if((a==0)&&(b!=0)) printf("no solution\n");
      else {
            if((a==0)&&(b==0)) printf("many solution\n");
            else {
                  if (b%a!=0) printf("no solution\n");
            else {
                  printf("%d\n",b/a);
            }
            }
      }
}

int main (void) {
      int a,b;
      scanf("%d %d",&a,&b);
      fun1(a,b);

return(0);
}


Переменные a и b которые объявлены в основной программе, и переменные a и b которые объявлены в функции fun1 это совершенно разные переменные. И ничего общего между собой не имеют.
Вот небольшой пример.Подумайте что выведет на экран эта программа а потом проверьте себя выполнив её на компьютере.
Листинг 10.8
#include <stdio.h>

void fun1(int a){
      a=8;
}

int main (void) {
      int a;
      a=3;
      fun1(a);
      printf("%d\n",a);

return(0);
}


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

Листинг 10.9
#include <stdio.h>

void fun1(int,int);

int main (void) {
      int x,y;
      scanf("%d %d",&x,&y);
      fun1(x,y);
      fun1(3,9);

return(0);
}

void fun1(int a, int b){
      if((a==0)&&(b!=0)) printf("no solution\n");
      else {
            if((a==0)&&(b==0)) printf("many solution\n");
            else {
                  if (b%a!=0) printf("no solution\n");
            else {
                  printf("%d\n",b/a);
            }
            }
      }
}

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

Теперь когда мы стали почти что профессионалами в написании функций вернемся опять к нашему исходному примеру. Мы дошли до того, что оформили решение в виде функции на вход которой подается значения а и b. Листинг 10.9.

Теперь перепишем нашу функцию, так, чтобы она возвращала результат своей работы в виде целого числа. Условимся, что 
30001 это ответ many solution.
30002 это ответ no solution.

Цифры выбраны не случайно. Область решения у нас от -30000 до 30000, значит эти числа использовать нельзя. иначе реальный ответ может совпасть с одним из них. Например если many solution обозначить как 3. То тогда при а=3 b=9 ответ функция вернет 3, но мы условились это считать за many solution. И не понятно будет какой ответ.

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

Листинг10.10
#include <stdio.h>

int fun1(int,int);

int main (void) {
     
        printf("%d\n", fun1(0,0));
        printf("%d\n", fun1(0,3));
        printf("%d\n", fun1(3,9));
        printf("%d\n", fun1(3,7));

return(0);
}

int fun1(int a, int b){
      if((a==0)&&(b!=0)) return (30002); //printf("no solution\n");
      else {
            if((a==0)&&(b==0))return (30001); //printf("many solution\n");
            else {
                  if (b%a!=0)return (30002); //printf("no solution\n");
            else {
                  return (b/a); //printf("%d\n",b/a);
            }
            }
      }
}

Так как функция возвращает значение типа int, её можно использовать везде где допустимо использование типа int. В нашем примере мы записали её в оператор printf. Хотя могли бы сначала присвоить её значение какой-нибудь переменной, а потом выводить в printf её значение. 
Например так,
Листинг 10.11
#include <stdio.h>

int fun1(int,int);

int main (void) {
      int x;
        x=fun1(0,0);
        printf("%d\n",x);
        x=fun1(0,3);
        printf("%d\n",x);
        x=fun1(3,9);
        printf("%d\n",x);
        x=fun1(3,7);
        printf("%d\n",x);

return(0);
}

int fun1(int a, int b){
      if((a==0)&&(b!=0)) return (30002); //printf("no solution\n");
      else {
            if((a==0)&&(b==0))return (30001); //printf("many solution\n");
            else {
                  if (b%a!=0)return (30002); //printf("no solution\n");
            else {
                  return (b/a); //printf("%d\n",b/a);
            }
            }
      }
}


Кстати, как вы заметили в нашей функции несколько раз встречается оператор return. Это необязательно. Можно было сначала присваивать значение какой-то одной переменной, а потом выводить его в самом конце функции. Вообще он предназначен для немедленного прекращения выполнения функции. Как оператор break в циклах. И так его можно использовать в функциях которые не возвращают никаких значений.
А так же с помощью него можно возвращать значение которое должна возвращать функция. Так он используется в нашем примере.

Функции могут вызывать друг друга, главное чтобы они были определены перед их использованием. 
Усовершенствуем нашу программу, и сделаем так, чтобы она выводила правильно ли работает программа или нет.А для этого опишем еще одну функцию с именем prov.

Листинг 10.12
void prov(void){
      if ((fun1(0,0)== 30001)&&(fun1(0,3)==30002)&&(fun1(3,9)==3)&&(fun1(3,7)==30002))
            printf("pravilno\n");
      else
            printf("nepravilno\n");
}


И добавим её в нашу программу.

Листинг 10.13
#include <stdio.h>

int fun1(int,int);
void prov(void);

int main (void) {
     prov();
return(0);
}

void prov(void){
      if ((fun1(0,0)== 30001)&&(fun1(0,3)==30002)&&(fun1(3,9)==3)&&(fun1(3,7)==30002))
            printf("pravilno\n");
      else
            printf("nepravilno\n");
}

int fun1(int a, int b){
      if((a==0)&&(b!=0)) return (30002); //printf("no solution\n");
      else {
            if((a==0)&&(b==0))return (30001); //printf("many solution\n");
            else {
                  if (b%a!=0)return (30002); //printf("no solution\n");
            else {
                  return (b/a); //printf("%d\n",b/a);
            }
            }
      }
}


Если вы запустите эту программу то, она выведет pravilno. Значит наша программа которую мы проверяли работает правильно. Теперь, для проверки чужой программы, нам останется скопировать часть её кода в функцию fun1 и немного подредактировать его. 

На этом урок закончим. А то он и так поучился очень-очень большим.Разбирайтесь. 

Резюме урока:
  • узнали что такое подпрограммы и чем отличаются функции от процедур
  • научились объявлять и описывать свои функции
  • узнали что такое фактические и формальные параметры
  • получили представление об области видимости переменных

Задание для практической работы.

Отдельного задания не будет. С этого дня оформляйте решения ваших заданий в виде отдельных функций.


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

14 комментариев :

  1. А продолжение уроков будут? или это конец?

    ОтветитьУдалить
  2. Хотим еще уроков

    ОтветитьУдалить
    Ответы
    1. Хотите? Значит будут! В воскресенье новый урок.

      Удалить
  3. Добрый день! Не понял листинг 10.11. Добавил в функцию main
    x=fun1(1,30002);
    printf("%d\n",x);
    Вышло 30002. Что это? Ответ или то, что нет решения.

    ОтветитьУдалить
    Ответы
    1. В данном случае, конечно же решение. Но только вы не учли, тот факт, что входные данные у нас ограничены в задании. И только лишь потому, мы выбрали числа 30001и 30002 в качестве возвращаемых значений. =))

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

      Удалить
    2. Спасибо! Видимо я упустил условие задачи. Сразу начал искать погрешности. Извините)))

      Удалить
  4. можно ли писать функцую внутри другой функции?

    ОтветитьУдалить
    Ответы
    1. Да, функции могут использовать внутри себя другие функции. Главное чтобы, используемая внутри функция была заранее объявлена. =)))

      Удалить
  5. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    В УРОКЕ НЕ ОПИСАНО РЕАЛИЗАЦИЯ ФУНКЦИЙ (НЕ ПРОЦЕДУР), И КАК ВОЗВРАЩАТЬ ЗНАЧЕНИЕ ПОСЛЕ ВЫПОЛНЕНИЯ ФУНКЦИИ""

    ОтветитьУдалить
    Ответы
    1. Ознакомься более внимательней.Скомпилируй пару простеньких программ через функции...
      У меня в ВУЗе вот такое первое ознакомление с функциями после одного занятия по изучению указателей

      #include"stdio.h"
      #include"stdlib.h"
      int* mem(int);
      void vvod(int*,int);
      void show(int*,int);
      int sum(int*,int);
      void main()
      {
      int rm,rez;
      int *mas;
      printf("Vvod raznera massiva\n");
      scanf("%d",&rm);
      mas=mem(rm);
      printf("Vvod dannih v massiv\n");
      vvod(mas,rm);
      printf("Ishodnii massiv\n");
      show(mas,rm);
      rez=sum(mas,rm);
      printf("\nRezultat rez=%d\n",rez);
      }
      int* mem(int n)
      {
      int *mm;
      mm=(int*) calloc (n,sizeof(int));
      return mm;
      }
      void vvod(int *m,int n)
      {
      for(int i=0;i0)
      rez+=*(m+i);
      return rez;
      }

      Описанное автором вполне понятно...

      Удалить
    2. Вы невнимательны. Перечитайте еще раз, здесь есть то, о чем вы говорите. =)

      Удалить
  6. Я написал программу, которая обходит математическую догму деления на ноль. Пользователь ДОЛЖЕН ввести 2 ЗНАЧЕНИЯ, НО ПРОГРАММА СЧИТАЕТ, ЧТО 3, хотя третье ни на что не влияет. Подскажите, где ошибка, заранее спасибо
    #include

    void nolik (int a, int b)
    { if (b==0) {printf ("znamenatel otsutstvuet, poetomy %d", a);}
    else { printf ("%d",a/b);}
    }
    int main ()
    { int x, y;
    scanf ("%d ", &x);
    scanf ("%d ", &y);
    nolik (x, y);
    return (0);

    ОтветитьУдалить
    Ответы
    1. Меня и самого это волнует scanf всегда требует ввести первый раз на одно значение больше чем нужно, не считываемым является всегда последнее значение. По видимому это как то связано с устарелостью функции

      Удалить
    2. В scanf пробел убери после %d и всее)

      Удалить

Примечание. Отправлять комментарии могут только участники этого блога.