Как передать ссылку в функцию 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;
Передача в функцию указателя или ссылки [дубликат]
Программы выведут одно и то же? Ведь и так, и так мы обращаемся к существующему элементу, а не создаем новый.
Отслеживать
задан 18 янв 2016 в 22:38
55 1 1 золотой знак 1 1 серебряный знак 5 5 бронзовых знаков
Если вам дан исчерпывающий ответ, отметьте его как верный (галка напротив выбранного ответа).
19 янв 2016 в 1:37
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
В принципе, технически разница между указателем и ссылкой лишь в том, что саму ссылку нельзя изменить (а указатель можно). [Вот тут бóльший список отличий.] Кроме этого, разница ещё синтаксическая: с ссылкой вы обращаетесь как будто это переменная, а с указателем нужно его правильно получать/разыменовывать:
void func(int *a) < *a = 5; >int main()
Других технических отличий нет, и результат будет одинаковый.
Но разница на самом деле не в синтаксисе, а в смысле, в семантике.
Указатель может означать всё, что угодно. Он в C и C++ означает строку, ассив, адрес переменной, передачу переменной по ссылке и ещё кучу всяких вещей. А смысл ссылки ровно один — это как бы альтернативное имя (alias) существующей переменной.
Поэтому для случаев наподобие того, который вы описали, в C++ уместно использовать ссылку, а не указатель. Хотя, как вы сами видите, с указателем тоже прекрасно работает.
И ещё: в чистом C ссылок нет, так что у вас нет другого варианта кроме указателей.
Отслеживать
ответ дан 18 янв 2016 в 22:43
207k 28 28 золотых знаков 293 293 серебряных знака 526 526 бронзовых знаков
Ответы на ваш вопрос даны выше, разницы в поведении никакой, но хотел бы уточнить несколько нюансов.
1. Пока у вас простые типы разницы никакой (кроме описаной выше в других ответах, например про NULL значение переменной в случае указателя). Но как только дело дойдет до классов появится разница. В случае передачи в функцию указателя на класс то, в зависимости от реализации и решаемых задач, будет иметь место разыменовывание указателя. Сложность этой процедуры будет зависеть от размера класса. Это справделиво в случе разыменовывания всего класса и не касается доступа к отдельным его членам. Например: int c = b->d; //дешево , а A b = *A; //дороже
2. вы можете перегрузить оператор & , а * нет.
3. Указатели не могут принимать временные значения, в то время как const & могут. Например: void f(const &A);. f(A()); //возможно
4. В целом работа с ссылками проще, поэтому ведут к меньшему числу ошибок
Золотое правило с++ программиста: используй ссылки где можешь, а указатели где должен.
Отслеживать
ответ дан 20 янв 2016 в 9:59
Denis Zaikin Denis Zaikin
441 4 4 серебряных знака 6 6 бронзовых знаков
+1 за возможную перегрузку
20 янв 2016 в 10:10
@VladD спасибо!
20 янв 2016 в 11:28
Если вы исправите опечатки и включите заголовок
#include void func(int *a) < *a = 5; ^^ ^ >int main()
#include void func(int &a) < a = 5; ^ >int main()
То вывод будет идентичен.
Когда используются указатели, то вы можете передать null-указатель. Например,
void func(int *a)
И функция может быть вызвана как
int x; func( &x ); func( nullptr );
Когда же используются ссылки, то ссылка должна указывать на существующий объект.
Как передать ссылку в функцию c
При рассмотрении передачи параметров в функцию указывалось, что параметры передаются в функцию по значению. То есть функция не изменяет значения передаваемых аргументов. Однако, используя в качестве параметров указатели, мы можем получить доступ к значению аргумента и изменить его.
Например, пусть у нас будет простейшая функция, которая увеличивает число на единицу:
#include void increment(int x) < x = x + 1; printf("increment function: %d \n", x); >int main(void)
Здесь переменная n передается в качестве аргумента для параметра x. Передача происходит по значению, поэтому любое изменение параметра x в функции increment никак не скажется на значении переменной n. Что мы можем увидеть, запустим программу:
increment function: 11 main function: 10
Теперь изменим функцию increment, использовав в качестве параметра указатель:
#include void increment(int *x) < *x = *x + 1; printf("increment function: %d \n", *x); >int main(void)
Теперь в функции increment разыменовываем указатель, получаем его значение и увеличиваем его на единицу.
*x = *x + 1;
Это изменяет значение, которое находится по адресу, хранимому в указателе x.
Поскольку теперь функция в качестве параметра принимает указатель, то при ее вызове необходимо передать адрес переменной: increment(&n); .
В итоге изменение параметра x также повлияет на переменную n:
increment function: 11 main function: 11
Еще один показательный пример применения указателей в параметрах — функция обмена значений:
#include void swap(int *a, int *b) < int temp = *a; *a = *b; *b=temp; >int main(void)
Функция swap() в качестве параметров принимает два указателя. Посредством переменной temp происходит обмен значениями.
При вызове функции swap в нее передаются адреса переменных x и y, и в итоге их значения будут изменены.
Константые параметры
Если необходимо запретить изменять значение параметра-указателя внутри функции, то можно определить такой параметра как константный:
#include // константный параметр int twice(const int *x) < //*x = *x * *x; // так нельзя, так как x - константный параметр int y = *x + *x; return y; >int main(void) < int n = 10; int m = twice(&n); printf("n = %d \n", n); // n = 10 printf("m = %d \n", m); // m = 20 return 0; >
Фактически такой константный параметр будет работать как указатель на константу — мы не сможем изменить его значение внутри функции.
Массивы в параметрах
Если функция принимает в качестве параметра массив, то фактически в эту функцию передается только адрес начала массива. То есть как и в случае с указателями нам доступен адрес, по которому мы можем менять значения. В отличие от параметров примитивных типов, которые передаются по значению.
Например, определим функцию для увеличения элементов массива в два раза:
#include void twice(int n, int p[]) < for(int i = 0; i < n; i++) < p[i]= p[i] * 2; >> int main(void) < int nums[] = ; // получаем количество элементов массива int length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); for(int i=0; i return 0; >
Функция twice в качестве параметров принимает массив и число его элементов и в цикле увеличивает их в два раза.
В функции main передаем массив в функцию twice и затем выводим его на консоль. В результате мы увидим, что массив nums был изменен:
2 4 6 8 10
Так как передача массива в функцию фактически представляет передачу адреса первого элемента, то массивы в параметрах мы можем заменить указателями:
#include void twice(int n, int *p) < for(int i=0; i> int main(void) < int nums[] = ; int length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); for(int i=0; i return 0; >
В итоге в данном случае не будет большой разницы, какой тип имеет параметр — массив или указатель.
Функции в языке C. Передача аргументов по значению и по ссылке
Язык C как и большинство других языков программирования позволяет создавать программы, состоящие из множества функций, а также из одного или нескольких файлов исходного кода. До сих пор мы видели только функцию main , которая является главной в программе на C , поскольку выполнение кода всегда начинается с нее. Однако ничего не мешает создавать другие функции, которые могут быть вызваны из main или любой другой функции. В этом уроке мы рассмотрим создание только однофайловых программ, содержащих более чем одну функцию.
При изучении работы функций важно понимать, что такое локальная и что такое глобальная переменные. В языке программирования 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; . Однако с параметрами функций так делать нельзя, для каждого параметра тип указывается отдельно: (int a, 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 epow(int *, int); int main() { int x = 34, y = 6; epow(&x, 3); epow(&y, 1); printf("%d %d\n", x, y); // 34000 60 } void epow(int *base, int pow) { while (pow > 0) { *base = *base * 10; pow--; } }
Функция epow ничего не возвращает, о чем говорит ключевое слово void . Принимает эта функция адрес, который присваивается локальной переменной-указателю, и целое число. В теле функции происходит изменение значения по адресу, содержащемуся в указателе. Поскольку это адрес то переменной x , то y из функции main , то epow() меняет их значение.
Когда epow() вызывается в main , то в качестве первого параметра мы должны передать адрес, а не значение. Поэтому, например, вызов epow(x, 3) привел бы к ошибке, а вызов epow(&x, 3) ‒ правильный, т. к. мы берем адрес переменной x и передаем его в функцию. При этом ничего не мешает объявить в main указатель и передавать именно его (в данном случае сама переменная p содержит адрес):
int x = 34, y = 6; int *p; p = &x; epow(p, 3); p = &y; epow(p, 1); printf("%d %d\n", x, y);
Кроме того, следует знать, что функция может возвращать адрес.
Важно понять механизм так называемой передачи аргументов по ссылке, т.к. это понимание пригодится при изучении массивов и строк. Использовать указатели при работе с простыми типами данных не стоит. Лучше возвращать из функции значение, чем менять локальные переменные одной функции с помощью кода другой. Функции должны быть достаточно автономными.
Напишите программу, в которой помимо функции main были бы еще две функции: в одной вычислялся факториал переданного числа, в другой ‒ находился n-ый элемент ряда Фибоначчи ( n ‒ параметр функции). Вызовите эти функции с разными аргументами.
Курс с решением задач:
pdf-версия