Skip to main content
0 votes

Доброго времени суток и добро пожаловать на седьмой по счету урок по основам программирования на С++. К этому моменту мы уже изучили структуру программы, типы данных, ввод-вывод информации, различные алгоритмические структуры, массивы, структуры, и всех этих знаний нам вполне хватит для написания достаточно объемной для начала (не обязательно сложной, но объемной) программы. А чем больше программа, тем сложнее ее сопровождать (то есть искать и исправлять ошибки, выявленные в результате эксплуатации). Тут появляется вопрос – как можно изменить программу (или как ее изначально писать), чтобы она была понятнее в дальнейшем и проще для сопровождения и дополнения? Что есть в любой программе, которую мы можем написать на данный момент? Правильно, функция main. И очень хорошая практика при больших программах использовать свои функции для решения определенных задач. Именно об этом сегодня пойдет речь.
Вообще что же такое функция. Роберт Лафоре дает такое определение

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

Давайте сначала разберем функцию main, которую мы видели в каждой нашей программе.

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. return 0;
  4. }

В начале указан тип возвращаемого функцией значения. Функция main возвращает некий код в операционную систему по завершении своей работы. Код 0, в частности, говорит о нормальном завершении работы программы, без неожиданностей.
Дальше идет имя функции. Ничего особого. Подчиняется правилам именования переменных, которые мы рассмотрели раньше. Только функция main является зарезервированным словом, и она обязательно должна присутствовать в программе.
В круглых скобках указываются параметры, передаваемые в функцию. Указание параметров происходит так же как и объявление переменных: сначала тип, потом имя, которое будет видно внутри этой функции (тут мы вспоминаем наш разговор об областях видимости из прошлого урока). Если параметров несколько, они указываются через запятую. В данном случае argc указывает на количество переданных аргументов командной строки, а argv собственно массив этих аргументов.
В фигурных скобках указывается тело функции. Тут указываются все операторы, выполняющиеся при вызове функции. Также в теле функции можно вызывать другие функции, описанные выше.
Return возвращает результат работы функции. В данном случае это код о нормальном завершении работы приложения (0). В других случаях это может быть, скажем, переменная (return result;) или символ (return ‘a’;) и так далее. Но бывают ведь такие случаи, когда нам ничего не надо возвращать. Как быть? Тогда не нужно указывать return, но нужно вместо типа возвращаемого функцией значения указать void. Тип void говорит, что функция ничего не вернет (если говорить совсем уж академичным языком, то тип void – пустое множество возвращаемых значений).
Внимательный читатель обратил внимание на то, что я говорю о функции main, а в программе вместо нее указано имя _tmain. Свою первую программу на С++ я написал в блокноте и всегда использовал функцию void main () {}, а с функцией _tmain столкнулся только при работе со студией. В принципе эти функции одинаковы, но умные люди пишут, что main заменен на _tmain для поддержки юникода при передаче аргументов. Чтобы показать, что для Windows-пользователей при неглубоком рассмотрении разницы нет никакой, сегодня будем работать с функцией main.
Сейчас мы попробуем написать свою простейшую функцию.

  1. #include "stdafx.h"
  2. #include <iostream>
  3.  
  4. using namespace std;
  5.  
  6. void firstFunc()
  7. {
  8. cout << "Hello, Function!" << endl;
  9. getchar();
  10. }
  11.  
  12. void main()
  13. {
  14. firstFunc();
  15. }

Как мы видим, весь код, который что-то делает, вынесен из функции main в функцию firstFunc. В функции main присутствует лишь вызов функции firstFunc. Без этого вызова наша функция никогда не выполнит своего предназначения.

first function.PNG

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

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <string>
  4.  
  5. using namespace std;
  6.  
  7. string gimmeName()
  8. {
  9. string name;
  10. cout << "What's your name?" << endl;
  11. cin >> name;
  12. return name;
  13. }
  14.  
  15. void firstFunc()
  16. {
  17. string yourName = gimmeName();
  18. cout << "Hello, " << yourName << endl;
  19. getchar();
  20. }
  21.  
  22. void main()
  23. {
  24. firstFunc();
  25. }

В примере выше мы добавили функцию, которая спрашивает у пользователя его имя. Ничего особенного: создали строковую переменную, которая будет хранить имя, введенное пользователем. Далее это имя возвращается в функцию, в которой произошел вызов функции gimmeName.
В функции firstFunc мы создаем строковую переменную, которая получает имя у функции gimmeName и хранит его, дальше выводим приветственное сообщение и все.
В функции main мы лишь вызываем функцию firstFunc. Результат работы программы представлен ниже.

hi bill.PNG

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

  1. void firstFunc(string yourName)
  2. {
  3. cout << "Hello, " << yourName << endl;
  4. getchar();
  5. }
  6.  
  7. void main()
  8. {
  9. firstFunc("Bill");
  10. string name = "Alex";
  11. firstFunc(name);
  12. }

Теперь наша функция firstFunc требует, чтобы мы при ее вызове передали ей какую-нибудь строку. Переменная yourName является локальной для функции firstFunc и не видна для остальных функций. Передавать в качестве параметра можно как конкретное значение (firstFunc(“Bill”);), так и переменную или константу (firstFunc(name);). Результат работы программы представлен ниже.

hello boys.PNG

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

  1. string firstFunc(string yourName)
  2. {
  3. yourName += ". You're welcome!";
  4. return yourName;
  5. }
  6.  
  7. void main()
  8. {
  9. string name;
  10. cout << "What's your name?" << endl;
  11. cin >> name;
  12. cout << firstFunc(name) << endl;
  13. getchar();
  14. }

Давайте разберем, что же там такое происходит. Функция firstFunc принимает в качестве параметра некоторую строку, добавляет сообщение «You're welcome!» и возвращает измененную строку назад в функцию, откуда была вызвана. Обратите внимание, в функции main при выводе можно функцию, возвращающую значение, указывать в выходном потоке (cout). Результаты работы программы приведены ниже.

welcome bill.PNG

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

  1. void nothing (char, int); // create a prototype
  2.  
  3. void firstFunc()
  4. {
  5. nothing('*', 10);
  6. getchar();
  7. }
  8.  
  9. void nothing(char symb, int n) // description
  10. {
  11. if (n > 0)
  12. {
  13. for (int i = 0; i < n; i++)
  14. {
  15. cout << symb << " ";
  16. }
  17. }
  18. }
  19.  
  20. void main()
  21. {
  22. firstFunc();
  23. }

В первой строке мы создаем прототип функции: тип возвращаемого значения, имя функции и типы принимаемых параметров (можно также указать имена аргументов, то есть не просто char, a char symb). Без этого прототипа мы не можем вызывать функцию nothing в функции firstFunc. Если мы хотим работать без прототипа, необходимо перенести объявление функции nothing наверх.
Простые переменные как параметры функции мы разобрали достаточно хорошо. Но мы помним, что есть такая штука как структура. Как же работать с ней?

  1. struct car
  2. {
  3. string color;
  4. int horses;
  5. };
  6.  
  7. car initAll (car bmw)
  8. {
  9. bmw.color = "red";
  10. bmw.horses = 340;
  11. return bmw;
  12. }
  13.  
  14. void main()
  15. {
  16. car myCar;
  17. myCar = initAll(myCar);
  18. cout << myCar.color << " " << myCar.horses << endl;
  19. getchar();
  20. }

Имеем структуру car, функцию initAll, инициализирующую поля структурной переменной. В функции main создаем структурную переменную myCar, и инициализируем ее посредством вызова функции initAll. Функция может работать с глобальными и своими локальными переменными. Глобальных переменных у нас нет, зато есть локальная структурная переменная bmw, которая обладает полями структуры car, соответственно, мы указываем имя локальной нашей переменной для работы с ней, и через точку поле, которое необходимо нам в данный момент времени. Возвращаем (так же как и принимаем в качестве параметра) локальную переменную без указания полей. Результат работы программы приведен ниже.

struct.PNG

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

  1. void printNumbers (int numbers[5])
  2. {
  3. for (int i = 0; i < 5; i++)
  4. {
  5. cout << numbers[i] << endl;
  6. }
  7. }
  8.  
  9. void main()
  10. {
  11. int num[] = { 100, 2, 0, -14, 8 };
  12. printNumbers(num);
  13. getchar();
  14. }

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

mass as parameter 1.PNG

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

  1. void printNumbers (int numbers[])
  2. {
  3. for (int i = 0; i < 5; i++)
  4. {
  5. numbers[i] *= 10;
  6. cout << numbers[i] << endl;
  7. }
  8. }
  9.  
  10. void main()
  11. {
  12. int num[] = { 100, 2, 0, -14, 8 };
  13. printNumbers(num);
  14. getchar();
  15. }

Но в этом случае мы все же указываем длину нашего массива (в цикле внутри функции). Лучше вместе с безразмерным массивом передавать в функцию также и размерность этого массива. Делается это следующим образом:

  1. void printNumbers (int numbers[], int col)
  2. {
  3. for (int i = 0; i < col; i++)
  4. {
  5. numbers[i] *= 10;
  6. cout << numbers[i] << endl;
  7. }
  8. }
  9.  
  10. void main()
  11. {
  12. const int len = 5;
  13. int num[] = { 100, 2, 0, -14, 8 };
  14. int num2[] = { 100, 50, 25, 0, -25, -50, -100 };
  15. printNumbers(num, len);
  16. cout << endl;
  17. printNumbers(num2, 7);
  18. getchar();
  19. }

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

mass as parameter 2.PNG

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