Как передать значение в функцию c
Перейти к содержимому

Как передать значение в функцию c

  • автор:

Как передать значение в функцию c

В языке С++ данные в подпрограмму можно передавать тремя способами: по значению, по адресу и по ссылке. В языке С допустимы только два способа: по значению и по адресу.

Передача данных по значению

Этот способ передачи данных в подпрограмму является основным и действует по-умолчанию. Фактический параметр вычисляется в вызывающей функции и его значение передаётся на место формального параметра в вызываемой функции. На этом связь между фактическим и формальным параметрами прекращается.

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

Пример 1 . Вычислить сумму ряда с заданной точностью ε =10 -5 :

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

Возможный вариант реализации программы:

using namespace std;

double fsum(double x, double eps);

double x, s, eps = 1.0e-5;

double fsum(double x, double eps)

double s = x, p = x, i, t = x * x;

for(i = 3; fabs(p) > eps; i += 2)

p = -p * t / (i * (i — 1));

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

Пример 2 . Даны два числа, хранящиеся в переменных a и b . Используя подпрограмму, выполнить обмен содержимого ячеек этих переменных.

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

Возможный вариант реализации программы:

using namespace std;

void Obmen(double x, double y);

double a = 2.5, b = 3.1;

void Obmen(double x, double y)

Результаты выполнения программы:

Do Obmen: a=2.5 b=3.1

Function Obmen start:

Function Obmen end:

Posle Obmen: a=2.5 b=3.1

Вывод на экран значений переменных показывает, что данные в функцию переданы правильно, перестановка в функции произведена, но это ни как не отразилось на значениях исходных переменных a и b после выхода из функции Obmen() .

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

Передача данных по адресу

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

В случае передачи данных по адресу фактический параметр может быть только переменной (константа или выражение не имеют адреса!).

Вернёмся к предыдущему примеру.

Пример 3 . Даны два числа, хранящиеся в переменных a и b . Используя подпрограмму, выполнить обмен содержимого ячеек этих переменных.

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

Возможный вариант реализации программы:

using namespace std;

void Obmen(double *x, double *y);

double a = 2.5, b = 3.1;

void Obmen(double *x, double *y)

Результаты выполнения программы:

Do Obmen: a=2.5 b=3.1

Function Obmen start:

Function Obmen end:

Posle Obmen: a=3.1 b=2.5

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

Как это работает? Рассмотрим данный вопрос подробнее, используя пример с обменом данных. Для наглядности приведём рисунок:

В вызывающей функции (в нашем случае — в main() ) вычисляются адреса объектов, передаваемых по адресу ( у нас — адреса переменных a и b . Пусть это будут числа 1000 и 1008 ), и затем эти адреса копируются в ячейки памяти — указатели, память под которые выделена в функции Obmen() (это x и y ). Зная адрес переменной, например, адрес переменной a , который теперь хранится в указателе x , можно, пользуясь операцией разыменование, не только прочитать, но и изменить значение исходной переменной.

Ни какой реальной передачи данных (в смысле копирования) из подпрограммы Obmen() назад в main() не делается. Мы на самом деле через указатели работаем с исходными объектами! Поэтому после выхода из функции Obmen() имеем изменённые переменные a и b (если быть точнее, переменные изменятся ещё до выхода из функции,то есть в момент перестановки в самой функции Obmen() ).

Передача данных по ссылке

Это ещё один из способов вернуть результат работы функции через список параметров. Напомним, что применяется только для С++. В языке С такого варианта нет.

При передаче данных по ссылке в функцию, куда передаются данные, создаются синонимы исходных объектов. Поэтому работа в подпрограмме ведётся именно с исходными объектами. Если в подпрограмме ссылочная переменная изменит значение, то это сразу отразится на исходной переменной.

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

Вернёмся снова к примеру обмена, только данные передадим по ссылке.

Пример 4 . Даны два числа, хранящиеся в переменных a и b . Используя подпрограмму, выполнить обмен содержимого ячеек этих переменных.

Возможный вариант реализации программы:

using namespace std;

double a = 2.5, b = 3.1;

Передача параметров в функцию

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

Пусть у нас имеется какая-то структура и функция, которая работает с этой структурой.

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

#include #include typedef struct point_tag < float x; float y; >point_t; void foo(point_t point) < point.x = 100; >int main() < point_t p = < 10.0, 20.0 >; printf("p.x = %.3f\n", p.x); foo(p); printf("p.x = %.3f", p.x); _getch(); return 0; >

В этом коде мы передаём в функцию foo копию переменной p. Значение кладётся на стек при вызове функции, и внутри функции мы можем с ним работать. Но изменяем мы только локальную копию. В main никакие изменения не видны. Проиллюстрируем это

Передача копии структуры

Пусть переменная p имеет адрес AB00. По этому адресу в памяти компьютера располагается экземпляр структуры со значением x = 10. Мы передаём это значение в функцию foo, и локальная копия имеет адрес CD00. Потом мы изменяем значение внутри foo, и оно становится 100, но по тому же адресу CD00, а исходное значение не изменилось.

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

#include #include typedef struct point_tag < float x; float y; >point_t; void foo(point_t *point) < point->x = 100; > int main() < point_t p = < 10.0, 20.0 >; printf("p.x = %.3f\n", p.x); foo(&p); printf("p.x = %.3f", p.x); _getch(); return 0; >

Передача указателя на структуру

Пусть переменная p имеет адрес AB00. Передача по указателю – это тоже передача значения, просто в данном случае мы передаём адрес переменной. Внутри функции переменная point хранится по адресу CD00. Хранит она адрес переменной p, равный AB00. Далее мы изменяем значение содержимого, на которое ссылается переменная point. Поэтому изменение видно внутри main.

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

void foo(const point_t *point) < point->x = 100; //compilation error >

Более сложный пример, когда мы хотим изменить не содержимое объекта, а непосредственно сам объект. Типичная ошибка делать вот так

#include #include typedef struct point_tag < float x; >point_t; void foo(point_t *point) < point = malloc(sizeof(point_t)); point->x = 100; > int main() < point_t *p = malloc(sizeof(point_t)); p->x = 10; printf("p.x = %.3f\n", p->x); foo(p); printf("p.x = %.3f", p->x); _getch(); free(p); return 0; >

В этом случае изменения объекта не произойдёт, плюс, мы получим утечку памяти. Давайте разберёмся.

Модификация структуры не приводит к изменению этой структуры

Пусть переменная p хранится по адресу AB00, а память под структуру была выделена по адресу AB04. p хранит этот адрес. В foo передаётся AB04 (содержимое p). Внутри foo мы локальной переменной point, которая хранится по адресу CD00, присваиваем новое значение СВ04 – адрес, который вернула нам функция malloc. Как видно, внутри main ничего не поменялось. Кроме того, после выхода из функции foo будет удалена переменная point, которая хранит адрес структуры на куче, а сама память не будет очищена.

Для модификации объекта нужно передавать указатель , и в данном случае, для модификации указателя, нужно передать указатель на указатель.

#include #include typedef struct point_tag < float x; >point_t; void foo(point_t **point) < (*point) = malloc(sizeof(point_t)); (*point)->x = 100; > int main() < point_t *p = malloc(sizeof(point_t)); p->x = 10; printf("p.x = %.3f\n", p->x); foo(&p); printf("p.x = %.3f", p->x); _getch(); return 0; >

Указатель на указатель позволяет модифицировать указатель

Как обычно, p имеет адрес CD00 и хранит адрес CD04, по которому хранится структура. Мы передаём адрес переменной p – это AB00. Внутри foo теперь мы меняем содержимое по адресу AB00, и оно становится равным CD04. Иначе говоря, мы имеем две переменные, которые хранят один и тот же адрес на куче – это p внутри main, и point внутри foo. После выхода из функции локальная переменная point будет удалена, а значение, выделенное на куче, по адресу CD04 не пострадает.

Но заметьте, теперь у нас осталась «висячая» структура на куче по адресу AB04, на неё никто не ссылается и никто её не удаляет!

Утечка памяти при изменении указателя

Внимательно следите за такими вещами.

#include #include typedef struct point_tag < float x; >point_t; void foo(point_t **point) < free(*point); (*point) = malloc(sizeof(point_t)); (*point)->x = 100; > int main() < point_t *p = malloc(sizeof(point_t)); p->x = 10; printf("p.x = %.3f\n", p->x); foo(&p); printf("p.x = %.3f", p->x); _getch(); free(p); return 0; >

Краткая памятка по использованию разных типов аргументов, на примере структуры point_t

Таб. 1 Памятка по передаче параметров

foo(point_t) Только чтение
foo(point_t*) Модификация значения
foo(const point_t*) Только чтение, но при передаче большого объекта расходуется меньше ресурсов
foo(point_t **) Модификация содержимого и самого указателя

ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

email

Всё ещё не понятно? – пиши вопросы на ящик

Как передать значение в функцию c

Аргументы, которые представляют переменные или константы, могут передаваться в функцию по значению (by value) и по ссылке (by reference).

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

#include void square(int); // прототип функции int main() < int n ; std::cout void square(int m) < m = m * m; // изменяем значение параметра std::cout Before square: n = 4 In square: m = 16 After square: n = 4

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

Передача параметров по ссылке

При передаче параметров по ссылке передается ссылка на объект, через которую мы можем манипулировать самим объектов, а не просто его значением. Так, перепишем предыдущий пример, используя передачу по ссылке:

#include void square(int&); // прототип функции int main() < int n ; std::cout void square(int& m) < m = m * m; // изменяем значение параметра std::cout по ссылке. Ссылочный параметр связывается непосредственно с объектом, поэтому через ссылку можно менять сам объект. То есть здесь при вызове функции параметр m в функции square будет представлять тот же объект, что и переменная n

И если мы скомпилируем и запустим программу, то результат будет иным:

Before square: n = 4 In square: m = 16 After square: n = 16

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

От передачи аргументов по ссылке следует отличать передачу ссылок в качестве аргументов:

#include void square(int); // прототип функции int main() < int n = 4; int &nRef = n; // ссылка на переменную n std::cout void square(int m) < m = m * m; // изменяем значение параметра std::cout Before square: n = 4 In square: m = 16 After square: n = 4

Передача параметров по значению больше подходит для передачи в функцию небольших объектов, значения которых копируются в определенные участки памяти, которые потом использует функция.

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

Преобразования типов

Передача параметров по значению и по ссылке отличаются еще одним важным моментом. С++ может автоматически преобразовывать значения одних типов в другие, в том числе если подобные преобразования сопровождаются потерей точности (например, преобразование от типа double к типу int). Но при передаче параметров по ссылке неявные автоматические преобразования типов исключены. Так, рассмотрим пример:

#include void printVal(int); void printRef(int&); int main() < double value; printVal(value); // 3 printRef(value); // ! Ошибка > void printVal(int n) < std::cout void printRef(int& n)

Здесь определены две практически идентичные функции. Только функция printVal получает параметр по значению, а функция printRef - по ссылке. При вызове в обе функции передается число типа double . Но параметр обоих функций представляет тип int . И если при передаче по значению переданное число double успешно преобразуется в int (пусть и с потерей точности), то при передаче по ссылке мы столкнемся с ошибкой на этапе компиляции. Это еще одна причина, почему нередко рекомендуется передавать значения по ссылки - исключается вероятность предвиденных и иногда нежелательных преобразований типов.

Функции. Передача аргументов по значению и по ссылке

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

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

Структурная организация файла программы на языке C, содержащего несколько функций, может выглядеть немного по-разному. Так как выполнение начинается с main() , то ей должны быть известны спецификации (имена, количество и тип параметров, тип возвращаемого значения) всех функций, которые из нее вызываются. Отсюда следует, что объявляться функции должны до того, как будут вызваны. А вот определение функции уже может следовать и до и после main() . Рассмотрим такую программу:

#include // объявление функции float median (int a, int b); int main () { int num1 = 18, num2 = 35; float result; printf("%10.1f\n", median(num1, num2)); result = median(121, 346); printf("%10.1f\n", result); printf("%10.1f\n", median(1032, 1896)); } // определение функции float median (int n1, int n2) { float m; m = (float) (n1 + n2) / 2; return m; }

В данном случае в начале программы объявляется функция median() . Объявляются тип возвращаемого ею значения ( float ), количество и типы параметров ( int a, int b ). Обратите внимание, когда объявляются переменные, то их можно группировать: int a, b; . Однако с параметрами функций так делать нельзя, для каждого параметра тип указывается отдельно: (inta, int b) .

Далее идет функция main() , а после нее — определение median() . Имена переменных-параметров в объявлении функции никакой роли не играют (их вообще можно опустить, например, float median (int, int); ). Поэтому когда функция определяется, то имена параметров могут быть другими, однако тип и количество должны строго совпадать с объявлением.

Функция median() возвращает число типа float . Оператор return возвращает результат выполнения переданного ему выражения; после return функция завершает свое выполнение, даже если далее тело функции имеет продолжение. Функция median() вычисляет среднее значение от двух целых чисел. В выражении (float) (n1 + n2) / 2 сначала вычисляется сумма двух целых чисел, результат преобразуется в вещественное число и только после этого делится на 2. Иначе мы бы делили целое на целое и получили целое (в таком случае дробная часть просто усекается).

В теле main() функция median() вызывается три раза. Результат выполнения функции не обязательно должен быть присвоен переменной.

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

#include float median (int n1, int n2) { float m; m = (float) (n1 + n2) / 2; return m; } int main () { int num1 = 18, num2 = 35; float result; printf("%10.1f\n", median(num1, num2)); result = median(121, 346); printf("%10.1f\n", result); printf("%10.1f\n", median(1032, 1896)); }

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

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

Статические переменные

В языке программирования C существуют так называемые статические переменные. Они могут быть как глобальными, так и локальными. Перед именем статической переменной пишется ключевое слово static .

Внешние статические переменные, в отличие от обычных глобальных переменных, нельзя использовать из других файлов в случае программы, состоящей не из одного файла. Они глобальны только для функций того файла, в котором объявлены. Это своего рода сокрытие данных, по принципу "не выставлять наружу ничего лишнего, чтобы 'что-нибудь' нечаянно не могло 'испортить' данные".

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

#include int hello(); int main() { printf(" - %d-й вызов\n", hello()); printf(" - %d-й вызов\n", hello()); printf(" - %d-й вызов\n", hello()); } int hello () { static count = 1; printf("Hello world!"); return count++; }
Hello world! - 1-й вызов Hello world! - 2-й вызов Hello world! - 3-й вызов

В этом примере в функции hello() производится подсчет ее вызовов.

Передача аргументов по ссылке

В первом примере этого урока мы передавали в функцию аргументы по значению. Это значит, что когда функция вызывается, ей передаются в качестве фактических параметров (аргументов) не указанные переменные, а копии значений этих переменных. Сами переменные к этим копиям уже никакого отношения не имеют. В вызываемой функции эти значения присваиваются переменным-параметрам, которые, как известно, локальны. Отсюда следует, что изменение переданных значений никакого влияния на переменные, переданные в функцию при вызове, не оказывают. В примере выше даже если бы в функции median() менялись значения переменных n1 и n2, то никакого влияния сей факт на переменные num1 и num2 не оказал.

Однако можно организовать изменение локальной переменной одной функции с помощью другой функции. Сделать это можно, передав в функцию адрес переменной или указатель на нее. На самом деле в этом случае также передается копия значения. Но какого значения?! Это адрес на область памяти. На один и тот же участок памяти может существовать множество ссылок, и с помощью каждой из них можно поменять находящееся там значение. Рассмотрим пример:

#include void multi (int *px, int y); int main () { int x = 34, y = 6; multi(&x, 367); multi(&y, 91); printf("%d %d\n", x, y); } void multi (int *base, int pow) { while (pow >= 10) { *base = *base * 10; pow = pow / 10; } }

Функция multi() ничего не возвращает, что подчеркнуто с помощью ключевого слова void . Принимает эта функция адрес, который присваивается локальной переменной-указателю, и целое число. В теле функции происходит изменение значения по адресу, содержащемуся в указателе. Но по сути это адрес переменной x из фукнции main() , а значит меняется и ее значение.

Когда multi() вызывается в main() , то в качестве первого параметра мы должны передать адрес, а не значение. Поэтому, например, вызов multi(x, 786) привел бы к ошибке, а вызов multi(&x, 786) — правильный, т.к. мы берем адрес переменной x и передаем его в функцию. При этом ничего не мешает объявить в main() указатель и передавать именно его (в данном случае сама переменная p содержит адрес):

int x = 34, y = 6; int *p; p = &x; multi(p, 367); p = &y; multi(p, 367); printf("%d %d\n", x, y);

Кроме того, следует знать, что функция может возвращать адрес.

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

  1. Перепишите код первого примера этого урока так, чтобы в нем использовался указатель; а код примера с функцией multi() , наоборот, избавьте от указателей.
  2. Напишите программу, в которой помимо функции main() были бы еще две функции: в одной вычислялся факториал переданного числа, в другой — находился n-ый элемент ряда Фибоначчи (n — параметр функции). Вызовите эти функции с разными аргументами.
  3. Придумайте и напишите программу, в которой из функции main() вызывается другая функция, а из последней вызывается еще одна функция.

Курс с решением части задач:
pdf-версия

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

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