← Назад к вопросам

Как передать параметр в функцию?

1.0 Junior🔥 251 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы передачи параметров в функцию на C++

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

1. Передача по значению (Pass by Value)

Параметр копируется при вызове функции. Изменения внутри функции не влияют на оригинальный параметр.

void process_value(int x) {
    x = 100;  // Меняем копию, не оригинал
}

int main() {
    int a = 5;
    process_value(a);
    std::cout << a << std::endl;  // 5 (не изменилось)
}

Преимущества:

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

Недостатки:

  • Копирование затратно для больших объектов (vector, string)
  • Лишние операции памяти

Когда использовать:

  • Примитивные типы (int, float, bool)
  • Малые структуры (2-3 поля)

2. Передача по ссылке (Pass by Reference)

Функция работает с оригинальным объектом через ссылку. Изменения влияют на оригинал.

void modify(int& x) {
    x = 100;  // Меняем оригинал
}

int main() {
    int a = 5;
    modify(a);
    std::cout << a << std::endl;  // 100 (изменилось!)
}

Преимущества:

  • Нет копирования — эффективно для больших объектов
  • Можно изменять оригинальный объект
  • Удобно для передачи нескольких выходных параметров

Недостатки:

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

Когда использовать:

  • Когда нужно изменить параметр
  • Для больших объектов (vector, string) если изменение нужно
  • Для нескольких выходных значений

3. Передача по константной ссылке (Pass by const Reference)

Функция работает с оригинальным объектом, но не может его менять.

void print(const std::string& str) {
    std::cout << str << std::endl;
    // str = "new";  // Ошибка! str const
}

int main() {
    std::string name = "Alice";
    print(name);  // Без копирования!
}

Преимущества:

  • Нет копирования для больших объектов
  • Ясно, что функция не меняет параметр
  • Работает с временными объектами (temporaries)
  • Потокобезопаснее

Недостатки:

  • Менее гибко, чем неконстантная ссылка

Когда использовать:

  • По умолчанию для объектов! (vector, string, struct)
  • Когда нужно только читать параметр
  • Для функций, работающих с данными

4. Передача по указателю (Pass by Pointer)

Функция получает адрес объекта через указатель.

void modify(int* ptr) {
    if (ptr != nullptr) {
        *ptr = 100;  // Разыменовываем указатель
    }
}

int main() {
    int a = 5;
    modify(&a);  // Передаём адрес
    std::cout << a << std::endl;  // 100
}

Преимущества:

  • Можно проверить на null
  • Явно видно, что передаём адрес
  • Можно манипулировать памятью

Недостатки:

  • Сложнее синтаксис (*ptr vs ptr.field)
  • Опасность null pointer dereference
  • В современном C++ ссылки предпочтительнее

Когда использовать:

  • Когда указатель может быть null
  • При работе с C API
  • В современном C++ — редко (есть std::optional)

5. Передача по умолчанию (Move Semantics) — C++11

Для временных объектов (rvalue references) используется перемещение вместо копирования.

#include <utility>

class Vector {
public:
    Vector() { data = new int[100]; }
    
    // Конструктор перемещения
    Vector(Vector&& other) noexcept : data(other.data) {
        other.data = nullptr;  // Очищаем источник
    }
    
    Vector& operator=(Vector&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
    
private:
    int* data;
};

Vector create_vector() {
    Vector v;  // Создаём объект
    return v;  // Перемещается, не копируется (RVO)
}

int main() {
    Vector v = create_vector();  // Перемещение вместо копирования
}

Преимущества:

  • Максимальная производительность
  • Копирование дорогих объектов избегается
  • Автоматическое управление ресурсами

Когда использовать:

  • Для функций, возвращающих временные объекты
  • Для std::vector, std::string и контейнеров

6. Шаблон передачи параметров (Modern C++)

Для шаблонных функций используется perfect forwarding.

template<typename T>
void process(T&& param) {
    // T&& может быть и lvalue, и rvalue ссылкой
    // Это universal reference, не rvalue reference!
    
    do_something(std::forward<T>(param));  // Передаём как было
}

int main() {
    int x = 5;
    process(x);           // Lvalue reference
    process(10);          // Rvalue reference
    process(std::move(x)); // Rvalue reference
}

Сравнительная таблица

СпособКопированиеМожно менятьnull?Когда использовать
By ValueДаНетНетПримитивы, малые объекты
By ReferenceНетДаНетИзменение параметра
By Const ReferenceНетНетНетСТАНДАРТНО для объектов
By PointerНетДаДаОпциональные параметры
Move (&&)НетДаНетВременные объекты

Практические рекомендации

Для примитивных типов:

void foo(int x) { }  // By value — просто и быстро

Для объектов (стандартный выбор):

void foo(const std::string& name) { }  // Читаем
void foo(std::string& name) { }        // Изменяем
void foo(std::string&& name) { }       // Захватываем временный

Для контейнеров:

void process(const std::vector<int>& data) { }  // Читаем
void fill(std::vector<int>& data) { }           // Заполняем
void consume(std::vector<int>&& data) { }       // Захватываем

Для опциональных параметров:

// OLD (C++98):
void foo(int* ptr) { if (ptr) { } }  // Может быть nullptr

// NEW (C++17):
void foo(std::optional<int> value) { }  // Явно опциональный

Частые ошибки

Ошибка 1: Передача по значению больших объектов

// ПЛОХО!
void print_all(std::vector<int> data) {  // Копирует весь вектор!
    for (int x : data) std::cout << x << std::endl;
}

// ХОРОШО!
void print_all(const std::vector<int>& data) {  // Без копирования
    for (int x : data) std::cout << x << std::endl;
}

Ошибка 2: Возврат ссылки на локальную переменную

// ОПАСНО!
const int& get_number() {
    int x = 5;
    return x;  // x выходит из области видимости!
}

int main() {
    const int& ref = get_number();  // Undefined behavior!
}

Ошибка 3: Изменение const ссылки

void foo(const int& x) {
    // x = 10;  // Ошибка! x const
}

Итог

Правило для выбора способа передачи:

  1. Примитивы → по значению (int, float, bool)
  2. Объекты (read-only) → по const ссылке (const std::string&)
  3. Объекты (изменение) → по ссылке (std::string&)
  4. Временные объекты → по rvalue ссылке (std::string&&)
  5. Опциональные параметры → std::optional<T>

В современном C++17/20 ссылки предпочтительнее указателей. Они безопаснее и явнее показывают намерение.