Параметры функции в C++ для начинающих

Отдача аргументов в функцию и приём этих аргументов в параметры крайне важная тема. Эта статья, как и многие статьи моего сайта, охватывает только часть большой темы.
Многие новички, изучающие языки программирования, тяжело переходят на работу с функциями. Язык С++ имеет свои особенности, которые могут мешать осознанию новичками происходящего в процессе написания кем-то кода. Часть этих особенностей касается параметров функций.
Вам сразу нужно научиться различать термины параметры функции и аргументы функции. Различать эти термины несложно.
  • Аргументы и параметры могут называть не тем способом, которым я называю в своей статье. Это не ошибка, просто разные авторы пишут по-разному.
Я следую логичности и называю параметрами то, что указывается в списке формальных параметров функции. Это изображается на анимированной картинке. Вам важно понять, что есть формальный параметр и актуальный параметр: формальный — это тот, который принимает значение, а актуальный — это тот, который уходит в функцию. Стрелки на картинке идут от актуальных параметров к формальным.

анимированная картинка, показывающая, что такое параметры функции и что такое аргументы функции

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

Только не перепутайте параметры и аргументы. Различие параметров и аргументов — ключ к пониманию этой статьи в целом.
Параметр, к которому приписывается ссылочный тип, работает таким образом, как если бы он был непосредственно ушедшим в функцию аргументом. Эта особенность ссылочных типов очень широко применяется. С виду может казаться, что параметр с заявленным ссылочным типом ничем не отличается от параметра с заявленным указательным типом, но на самом деле отличие есть: в случае ссылочного типа работа с аргументом действительно происходит напрямую, а в случае с указательным типом работа происходит с новосделанной указательной переменной, которая указывает на тот адрес, на котором живёт ушедший в функцию аргумент.

Показываемый пример на самом деле ведёт к непрогнозируемому поведению программы. Попробуйте поменять вызовы foo1 и foo2 местами (внутри main), программа скорее всего сработает ужасно, полезут какие-нибудь ошибки. Дело в том, что для вывода на экран используется аргумент-указатель, состояние которого не было изменено созданным внутри функции клоном указательного аргумента, память выделилась для клона, но не для ушедшего вовнутрь функции аргумента, а потом delete используется для оригинального аргумента, к которому не было применено new. Тут вам и утечка памяти и неправильное использование delete для указателя (delete применяется только в паре с new). Исключение — нулевой указатель, к нему можно применять delete. Если область памяти была выделена операцией new, то можно на эту область памяти направить любой подходящий по типу указатель и применить к этой области delete. Но в коде существует две указательные переменные и аргумент, ушедший в функцию, ничем не перенаправляется на участок памяти, выделенный посредством клона. А потом к аргументу, который указывает на участок памяти, который не выделялся с помощью new используется delete, т. е. происходит попытка разрушить память, которую рушить нельзя.
  • В С++ можно создавать ссылку на указатель, но нельзя создавать указатель на ссылку.
В списке параметров, где используется параметр с ссылочным типом, на самом деле используется ссылка на указательную переменную, указывающую на тип int (читайте подобные синтаксические изыски справа-налево, и вам будет легче их понимать). Ссылочная переменная напрямую влияет на аргумент, ушедший в функцию, поэтому выделение памяти операцией new внутри функции foo2 повлияло на указательную переменную x, уходящую в функцию как аргумент, и выделение памяти для параметра value оказалось выделением памяти непосредственно для x.

В списке параметров, где используется параметр с указательным типом, создаётся новая указательная переменная, при этом без выделения для неё памяти. Она указывает на тот же участок памяти, на который указывает указательная переменная, являющаяся аргументом, ушедшим в эту функцию. Но после того, как память для указательной переменной-параметра выделяется, две указательные переменные начинают указывать на разные участки памяти. Указательная переменная — она сама по себе сущность, две аналогичные указательные переменные легко могут указывать на один и тот же адрес памяти, ведь ничто не препятствует такому положению дел, но каждая из указательных переменных живёт своей жизнью, и выделение памяти одной указательной переменной не обозначает, что и другой память будет выделена. Ведь если вы перенаправляете один указатель, то другие указатели за ним не пойдут:

Это основное отличие параметра, являющегося указательной переменной, от параметра, являющегося ссылочной переменной на указательный тип. В случае с ссылками в параметрах функции, работа с аргументами, уходящими в функцию происходит непосредственно напрямую.
Когда в функции объявляется параметр нессылочного характера (будь то обычный параметр или параметр-указатель), внутри функции создаётся новая переменная, которая и принимает на себя основные обязанности. При этом все изменения, происходящие над этой новой переменной, имеют значение только для работы внутри самой функции.
  • Когда параметр функции не ссылочен, внутри функции создаётся новая переменная, которая является локальной для функции копией пришедшего в функцию аргумента.
  • Когда параметр функции представляет собой ссылку, то никаких новых переменных внутри функции не создаётся, и работа с аргументом, пришедшим в функцию, происходит напрямую.
Тут есть маленький нюанс: несмотря на то, что внутри функции не создаётся клона аргумента, ссылка всё-таки создаётся. Но ссылка не является объектом, это только название, т. е. посредством параметра функции аргумент называется ещё одним именем, внутри функции аргумент, ушедший в функцию зовётся названным именем, а вне функции аргумент зовётся как назван. Такой вот нюанс есть.
Вопрос, должный появляться у очень многих новичков может звучать приблизительно так: "А когда нужно использовать параметры с ссылочным типом и когда с обычным типом?". Ответ на этот вопрос достаточно прост: при работе с большими объектами данных лучше использовать ссылочный тип параметров, а при работе с маленькими объектами лучше использовать обычный тип для параметров. Понять, что такое большой объект и маленький объект новичкам понять бывает сложно. Типы состоят из простых типов: int, double, char и составных типов: массивы, структуры, классы. Большие объекты, это объекты, которые строятся из нескольких типов. Например, массив строится из множества ячеек одного типа, это большой объект. В классе или структуре для создания объекта совмещают несколько нужных типов, объекты таких классов или структур — это большие объекты. Можете продолжать аналогию, суть в том, что если для описания какой-то сущности нужно использовать много объединённых типов, то такой тип стоит считать большим, а если используется примитивный тип данных, просто сам по себе, то этот тип считается маленьким.
  • Использование ссылочного типа для параметра функции помогает избегать накладных расходов на выполнение операций копирования в больших объектах.
Если вы возьмёте, например, строку, то она по сути массив, а массив — это сущность, состоящая из множества однотипных элементов, т. е. строка — это большой объект. В случае создания клона строки каждый символ оригинальной строки будет копироваться в созданный клон, а это отнимает время. Поэтому логичнее использовать параметр функции ссылочного характера для приёма аргумента строки, чем использовать параметр нессылочного характера. Из-за ссылочного характера параметра клон строки создан не будет, и операций по копированию символов просто не будет нужно выполнять компилятору, просто имя параметра свяжется с оригинальной строкой и это всё.
Меня достаточно долго интересовал вопрос: Почему во многих книгах в функциях параметры описываются как константные ссылки, и почему разные опытные программисты ругаются на новичков, забывающих дописать квалификатор const к параметру функции? Наиболее часто встречаемый довод: "Вы так убережёте себя от своих же ошибок".
Сначала мне такие константные ссылки очень сильно мешали понимать описываемые коды, да и никак мой мозг не хотел дать мне знать, от каких же это моих же ошибок должно меня оберегать.
Иногда не нужно, чтобы работа функции повлияла на внутреннее состояние объекта, тогда нужно параметр сделать константным, а чтобы программа не тратила время на копирование состояния уходящего в функцию аргумента, нужно в функции заявить, что параметр должен быть ссылкой. При возникновении двух этих потребностей одновременно — используют параметр ссылочного характера, который защищают квалификатором const:

В последнем примере неразумно использовать параметр ссылочного характера, но чтобы вам было проще, я решил использовать именно этот пример. Мы пишем много функций, которые не предназначены для изменений, в таких случаях нужно обязательно добавлять к параметрам, которые мы не намерены изменять, квалификатор const. Если бы мы сразу учли, что коли функция сравнивает две сущности и ничего изменять не должна в принципе, то добавили бы const, и этот код просто бы не стал компилироваться, таким образом мы уберегли бы себя задолго до возможного возникновения похожей ситуации.
  • ЗАПОМНИТЕ! const — ваш друг!

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

19 комментариев на «“Параметры функции в C++ для начинающих”»

  1. Anonymous:

    "Первый вариант можно привести ко второму или третьему, если например, объявить глобальную переменную (int z), после этого внутри функции присвоить в нее полученную x, а после вызова функции из тела программы написать value=x"Наверно, надо написать value=z, так как через глобальную переменную z в данном случае передаётся выходная информация функции.

  2. bazikmaloj:

    а как написать функцию по вычисление целых чисел умножение! в языке С#

  3. Аноним:

    Все разжовано, и досупно. огромное спасибо. Где можно найти сборник ваших статей?

    Автор сайта отвечает:
    Пожалуйста)
    Про сборник статей не понял чего-то. Если это вопрос про содержание списка тем сайта, то главная страница. Сайт ci-plus-plus-snachala.ru

  4. Аноним:

    автор спасибо большое за статью, но учите орфографию и пишите чуть короче. успехов )

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

  5. Аноним:

    Здравствуйте, помогите, пожалуйста с заданием, никак не могу вникнуть в суть -_-
    Написать программу, вычисляющую сумму всех положительных чисел, введённых с клавиатуры, и номер первого положительного числа.
    Например, при вводе с клавиатуры чисел: -1, -2, -3, 0, 0, 0, 1, 2, 3 сумма равна 6, номер первого положительного равен 7.
    Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия.
    Функция inout: 1. объявляет переменную int i; 2. передаёт переменную i по указателю функции func; 3. выводит на экран результат работы.
    Функция func: 1. получает указатель на переменную i; 2. запрашивает и i получает неопределённое количество чисел, вводящихся с клавиатуры; 3. переменной присваивается номер первого положительного элемента; 4. вычисляет сумму положительных элементов и возвращает вычисленную сумму.

    Автор сайта отвечает:

  6. Wao-Bao:

    Е-мое!!! Можно же просто и толково объяснить. После кучи статей наконец понял механизм передачи параметров.

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

    (как прототип :)))))))    Спасибо !

  7. Harrix:

    В функции srav допущена ошибка. Там при сравнении используется один знак равно, а не два, как нужно.

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

  8. Shurkec:

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

    Эту функцию вызываю так (ввод/вывод значений массива я опустил):

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

     

    • Массив из функции вернуть нельзя.
      При передаче массива в функцию, имя массива неявно приводится к указателю на первый элемент этого массива. Так как получается эффект получения параметра по указателю, то копии массива не делается. Поэтому все изменения внутри функции действуют и на него. Варианты решения: использовать обьект класса или структуры, использовать вектор, использовать обьект array(c++11), или руками делать внутри функции новый массив и копироаать в него данные для следующей обработки.

  9. Vitek Beluy:

    а можно теперь как-то сделать к этому счетчик количества вызова функций?

    • Можно
      Глобальной переменной
      Статической переменной
      Передачей параметра по ссылке
      Передачей указателя на массив
      Сохранением в файл
      Другими способами…

  10. Валерий:

    меня интересует иное. Как функция знает соответствие входных переменных  внутренним переменным в её теле.

    • Мы сами ей говорим в момент её вызова, что мы в ней извне использовать станем. У неё два варианта: использовать извне что-то напрямую или использовать извне не напрямую. Это мы говорим сами, говорим, когда внутри функции выбираем, как будем использовать параметры (по ссылке или нет). В общем, сама она знает ровно столько, сколько мы ей сказали знать.

      Не очень понял вопрос, но надеюсь ответ получился понятным.
      1. Место вызова функции.
      2. Параметры функции.

  11. Николай:

    Скажите пожалуйста, что с ней не так???

    Почему компилятор выдает мне это?

    C:\Users\USER\Desktop\Hashtag_Zvukooperator\exam\main.c:8:38: error: expected ‘;’, ‘,’ or ‘)’ before ‘&’ token
    void func(int arr[], int num, double &sp, double &so)

    • Без понятия. Если компилятор C++, должно работать.

      • Дима:

        Какой из способов передачи параметров расходует больше памяти?

        • Если в функцию отдаётся маленький объект (double, int, char…), то одинаково быстро.
          Если в функцию отдаётся большой объект (класс, структура), то по указателю и ссылке одинаково быстрее, чем по значению.

    • Дмитрий:

      Николай, компилятор сообщает вам: «Ожидаю ‘;’, ‘,’ или ‘)’ до ‘&'». А теперь внимательно посмотрите текст своей программы: и func, и pow, и main, и scanf, и printf выделены коричневым цветом. Из чего я делаю вывод: ваша функция func конфликтует с библиотечной func, в которой ВСЕГО ДВА параметра, а язык, почему-то, не позволяет выполнить перегрузку (переопределение) функции (хотя, насколько мне известно, в C++ такая возможность реализована). Советую вам сменить имя функции на другое. У меня в Arduino IDE текст вашей программы компилируется без ошибок!
      Творческих вам успехов!
      С уважением, Дмитрий.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/kardos-lopes-bruno/llvm-infrastruktura-dlya-razrabotki-kompilyatorov-22784657/?lfrom=15589587

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

Яндекс.Метрика