View on GitHub

Курс по C++

Базовые конспекты по программированию на C++ для групп 371ПС, 372ПС. В конспектах ОЧЕНЬ много неточностей, ошибок. Правки приветствуются =)

Процедуры, функции.

Тернарный оператор.

Если вам понадобилось написать очень простое условие, то бывает полезно воспользоваться тернарным оператором.

#include <iostream>
using namespace std;
 
int main()
{
    int a, b, max, min;
    cin >> a >> b;
    // используя тернарный оператор, определяем максимум 
    max = (a > b) ? a : b; // и сразу записываем его в переменную max
    
    if (a > b) {
        max = a;
    } else { 
    	max = b;
    }
    
    min = (a < b) ? a : b; // так же определяем и записываем min
    
    if (a < b) {
        min = a;
    } else { 
    	min = b;
    }
    
    (a == max) ? (cout << "The first number is bigger") : (cout << "The second number is bigger"); 
}

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

Что такое функция. Пример.

Примеры функций на Clojure:

 (def mem-fib
           (memoize
             (fn [n]
               (cond
                 (== 0 n) 1
                 (== 1 n) 1
                 :else (+ (mem-fib (- n 1)) (mem-fib (- n 2)))))))

Если все-таки отойти от таких извращений как функциональные языки программирования и вернуться в С++, то все станет не так страшно и ужасно…(нет)

Несколько правил для безболезненного написания функций (рекомендуем проверять все эти пункты каждый раз после написания функции):

Локальные и глобальные переменные.

Переменные в С++ бывают локальными и глобальными. В примере ниже переменная int global является глобальной, а переменная int local является локальной.

Мы очень настоятельно просим вас не использовать глобальные переменные. Объясняем, почему.

Каждые { } в С++ определяют свою область видимости переменной, поэтому, если вы создаете переменные с одинаковым названием, находящиеся в непересекающихся областях видимости, то у вас никогда не возникнет проблем с тем, что одна из переменных случайно “перекроется” другой, как это происходит ниже при создании внутри функции foo одноименной переменной, с глобальной переменной global.

Важно запомнить, что приоритете при обращении отдается переменной с наименьшей областью видимости!!!

#include <iostream> 
using namespace std;

int global = 0;

int foo(int a, int b) {
	int global = a + b;
    return global;
}

int main() {
    int local = boo(2, 5);
    cout << (global == local ? "EQUAL" : "DIFFERENT"); // DIFFERENT
}

Замечание. То же происходит, когда вы объявляете случайно одноименную переменную внутри уже определенного цикла, условия и тд.

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

В примерах выше были показаны функции с передачей различных параметров. Давайте разберемся.

int increment(int a)
{
    return ++a;
}

int main()
{
	int a = 0, b = 0;
    cout << a << increment(a) << a; //
    cout << b << increment(b) << b; // В чем разница?
}    

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

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

Вариант первый - установка значений аргумента “по умолчанию”.

void greet (string name = "user")
{
    cout << "Greetings, " + name; 
}
int main()
{
    greet();
    greet("Fellow gamer");
}

Отличия функций от процедур.

В примере с функциями был использован оператор return. Это ключевое слово останавливает выполнение функции и подставляет в место её вызова некоторое значение.

ВАЖНО: типа возвращаемого значения должен совпадать с “типом“ функции

int foo ()
{
    return "ATATA, нельзя так";
}

Если функция имеет тип void, то она называется процедурой.

int sum (int a, int b)
{
	return a + b;
    cout << "Эта строка никогда не выведется";
}

int main()
{
    cout << "Сюда будет подставлено значение функции: " << sum(2, 2);
}

Перегрузка

Вспомните вопрос про вызов функции с разным набором аргументов.

Вариант второй - перегрузить функцию.

void greet (string name = "user")
{
    cout << "Greetings, " + name; 
}

void greet ()
{
    cout << "Greetings, user"; 
}

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

void foo (int a);
void foo (short b);

int main ()
{
	foo (0); // Будет вызвана первая функция
}

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

Если таких преобразований нет, то С++ попробует преобразовать каждый числовой тип к необходимому.

Что произойдет, если компилятор найдет несколько совпадений?

void foo (unsigned int value);
void foo (float value);
 
int main()
{
    foo('b'); // Это char
    foo(0); // Это int
    foo(3.14159); // Это double
}