Яка різниця між
- параметр, переданий посиланням
- параметр, переданий за значенням?
Чи можете ви надати мені кілька прикладів, будь ласка?
Яка різниця між
Чи можете ви надати мені кілька прикладів, будь ласка?
Відповіді:
По-перше, відмінність "пропуск за величиною проти проходження посилання", визначена в теорії CS, тепер застаріла, оскільки спочатку методика, визначена як "пройти посиланням", з тих пір вийшла з ласки і рідко використовується зараз. 1
Нові мови 2, як правило, використовують різні (але схожі) пари методик для досягнення тих же ефектів (див. Нижче), що є основним джерелом плутанини.
Вторинним джерелом плутанини є той факт, що у "переході посилання" "посилання" має вужчий зміст, ніж загальний термін "посилання" (оскільки словосполучення передує цьому).
Тепер справжнім визначенням є:
Коли параметр передається за посиланням , абонент і абонент використовують одну і ту ж змінну для параметра. Якщо виклик модифікує змінну параметру, ефект буде видимий змінної абонента.
Коли параметр передається за значенням , абонент і абонент мають дві незалежні змінні з однаковим значенням. Якщо виклик модифікує змінну параметра, ефект не видно для абонента.
У цьому визначенні слід зазначити:
"Змінна" тут означає саму змінну абонента (локальну чи глобальну) - тобто, якщо я передаю локальну змінну за посиланням та призначу їй, я зміню саму змінну абонента, не наприклад, на що б вона не вказувала, якщо це покажчик .
Значення "посилання" у "пройти через посилання" . Відмінність від загального терміна "еталон" полягає в тому, що ця "посилання" є тимчасовою і неявною. Що в основному отримує виклик, це "змінна", яка якось "така ж", як і оригінальна. Наскільки конкретно досягається цей ефект, не має значення (наприклад, мова може також виявити деякі деталі реалізації - адреси, покажчики, перенаправлення - це все не має значення; якщо чистий ефект такий, це передається посилання).
Зараз у сучасних мовах змінні, як правило, є "типовими типами" (інша концепція, винайдена пізніше як "пройти посилання" та надихнута нею), тобто фактичні дані об'єкта зберігаються окремо десь (як правило, в купі), і тільки "посилання" на нього ніколи не зберігаються у змінних і передаються як параметри. 3
Передача такої посилання підпадає під пропускне значення, оскільки значення змінної технічно є саме посиланням, а не згаданим об'єктом. Однак чистий ефект на програму може бути таким самим, як прохідна вартість або прохідна посилання:
Як ви бачите, ця пара методів майже така сама, як і у визначенні, лише з рівнем непрямості: просто замініть "змінну" на "посилається об'єкт".
Не існує узгодженої назви для них, що призводить до викривлених пояснень на кшталт "виклик за значенням, де значення є посиланням". У 1975 році Барбара Ліськов запропонувала термін " поділ за допомогою об'єкта " (або іноді просто "поділ за дзвінками"), хоча він ніколи не впадав у дію. Більше того, жодна з цих фраз не проводить паралель з початковою парою. Недарма старі терміни в кінцевому підсумку використовувались у відсутності нічого кращого, що призводило до сум'яття. 4
ПРИМІТКА : Тривалий час у цій відповіді говорили:
Скажіть, я хочу поділитися з вами веб-сторінкою. Якщо я скажу вам URL, я проходжу посилання. Ви можете використовувати цю URL-адресу, щоб побачити ту саму веб-сторінку, яку я бачу. Якщо цю сторінку змінити, ми обидва бачимо зміни. Якщо ви видалите URL-адресу, все, що ви робите, - це руйнувати посилання на цю сторінку - ви не видаляєте саму фактичну сторінку.
Якщо я роздрукую сторінку і даю вам роздруківку, я проходжу цінність. Ваша сторінка - від’єднана копія оригіналу. Ви не побачите жодних наступних змін, і будь-які внесені вами зміни (наприклад, скреблінг у роздруківці) не з’являться на початковій сторінці. Якщо ви знищите роздруківку, ви фактично знищили свою копію об'єкта - але оригінальна веб-сторінка залишається недоторканою.
Це здебільшого правильно, за винятком вужчого значення "посилання" - воно є тимчасовим і неявним (не обов'язково, але явне та / або стійке - це додаткові функції, а не частина семантичного проходження посилань , як пояснено вище). Більш близька аналогія - це надання вам копії документа проти запрошення на роботу над оригіналом.
1 Якщо ви не програмуєте на Fortran або Visual Basic, це не поведінка за замовчуванням, і в більшості мов у сучасному використанні справжній дзвінок за посиланням навіть неможливий.
2 Неоднакова кількість людей старшого віку теж його підтримує
3 У кількох сучасних мовах всі типи є типовими. Цей підхід був започаткований мовним CLU в 1975 році і з тих пір був прийнятий багатьма іншими мовами, включаючи Python та Ruby. І багато інших мов використовують гібридний підхід, де деякі типи є "типовими типами", а інші - "еталонними типами" - серед них C #, Java та JavaScript.
4 Немає нічого поганого в тому, що переробляти підходящий старий термін як такий, але треба якось зрозуміти, яке значення вживається кожен раз. Якщо цього не робити, то саме це викликає плутанину.
Це спосіб передавати аргументи функціям. Перехід за посиланням означає, що параметр виклику функцій буде таким же, як і переданий аргумент абонента (не значення, а ідентифікація - сама змінна). Передати значення означає, що параметр виклику функцій буде копією переданого аргументу абонента. Значення буде однаковим, але ідентичність - змінна - інша. Таким чином, зміна параметра, виконаного викликаною функцією, в одному випадку змінює переданий аргумент, а в іншому випадку просто змінює значення параметра в викликаній функції (що є лише копією). Швидко поспішаючи:
ref
використовується у абонента та називається функцією). Джон Скит також має гарне пояснення цього тут .Коди
Оскільки моєю мовою є C ++, я буду використовувати її тут
// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
p = NULL;
}
// passes an integer
void call_by_value(int p) { // :2
p = 42;
}
// passes an integer by reference
void call_by_reference(int & p) { // :3
p = 42;
}
// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
*p = 10; // changes what p points to ("what p references" in java)
// only changes the value of the parameter, but *not* of
// the argument passed by the caller. thus it's pass-by-value:
p = NULL;
}
int main() {
int value = 10;
int * pointer = &value;
call_by_value(pointer); // :1
assert(pointer == &value); // pointer was copied
call_by_value(value); // :2
assert(value == 10); // value was copied
call_by_reference(value); // :3
assert(value == 42); // value was passed by reference
call_by_value_special(pointer); // :4
// pointer was copied but what pointer references was changed.
assert(value == 10 && pointer == &value);
}
І приклад на Java не зашкодить:
class Example {
int value = 0;
// similar to :4 case in the c++ example
static void accept_reference(Example e) { // :1
e.value++; // will change the referenced object
e = null; // will only change the parameter
}
// similar to the :2 case in the c++ example
static void accept_primitive(int v) { // :2
v++; // will only change the parameter
}
public static void main(String... args) {
int value = 0;
Example ref = new Example(); // reference
// note what we pass is the reference, not the object. we can't
// pass objects. The reference is copied (pass-by-value).
accept_reference(ref); // :1
assert ref != null && ref.value == 1;
// the primitive int variable is copied
accept_primitive(value); // :2
assert value == 0;
}
}
Вікіпедія
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference
Цей хлопець дуже нігті це:
Багато відповідей тут (і, зокрема, найбільш високообоснована відповідь) фактично невірні, оскільки неправильно розуміють, що насправді означає "дзвінок за посиланням". Ось моя спроба вирішити питання.
Простіше кажучи:
У метафоричному відношенні:
Зауважимо, що обидва ці поняття є абсолютно незалежними та ортогональними від поняття посилальних типів (що на Java - це всі типи, що є підтипами Object
, а у C # всіх class
типів) або від поняття типів вказівників, як у C (які семантично еквівалентні до "посилальних типів" Java, просто з різним синтаксисом).
Поняття типу посилання відповідає URL: воно є і самим інформаційним, і є посиланням ( покажчиком , якщо ви хочете) на іншу інформацію. Ви можете мати багато копій URL-адреси в різних місцях, і вони не змінюють веб-сайт, на який вони посилаються; якщо веб-сайт оновлюється, кожна копія URL-адреси все одно призведе до оновленої інформації. І навпаки, зміна URL-адреси в будь-якому одному місці не вплине на будь-яку іншу письмову копію URL-адреси.
Зауважте, що C ++ має поняття "посилання" (наприклад int&
), яке не схоже на типи посилань Java та C # 's, але є як "виклик за посиланням". "Посилання на типи" Java та C # ', а також усі типи в Python, схожі на те, що C і C ++ називають "типами вказівників" (наприклад int*
).
Гаразд, ось більш тривале та формальне пояснення.
Для початку я хочу виділити кілька важливих фрагментів термінології, щоб допомогти уточнити свою відповідь і гарантувати, що всі ми посилаємось на ті самі ідеї, коли вживаємо слова. (На практиці я вважаю, що переважна більшість плутанин щодо таких тем, як ці, є наслідком використання слів таким чином, щоб не повністю передати значення, яке було призначено.)
Для початку, ось приклад дещо на мові С-подібної декларації функції:
void foo(int param) { // line 1
param += 1;
}
Ось приклад виклику цієї функції:
void bar() {
int arg = 1; // line 2
foo(arg); // line 3
}
Користуючись цим прикладом, я хочу визначити деякі важливі біти термінології:
foo
- це функція, оголошена в рядку 1 (Java наполягає на тому, щоб зробити всі функції методів, але концепція однакова без втрати загальності; C і C ++ роблять різницю між декларацією та визначенням, про яку я тут не вступатиму)param
це формальний параметр для foo
, також оголошений у рядку 1arg
- змінна , конкретно локальна змінна функції bar
, оголошена та ініціалізована у рядку 2arg
також є аргументом для конкретного виклику з foo
в рядку 3Тут слід виділити два дуже важливих набори понять. Перше - значення проти змінної :
bar
функції вище, після рядка int arg = 1;
, вираз arg
має значення 1
.final
або C # 's readonly
) або глибоко незмінна (наприклад, за допомогою C ++ const
).Інша важлива пара понять, яку слід розрізнити, - це параметр проти аргументу :
У виклику за значенням формальні параметри функції - це змінні, які були створені для виклику функції та які ініціалізуються зі значеннями своїх аргументів.
Це працює точно так само, як ініціалізовані будь-які інші види змінних зі значеннями. Наприклад:
int arg = 1;
int another_variable = arg;
Тут arg
і another_variable
є абсолютно незалежні змінні - їх значення можуть змінюватися незалежно одна від одної. Однак у точці, де another_variable
оголошено, вона ініціалізується, щоб утримувати те саме значення, яке arg
має місце - яке є 1
.
Оскільки вони є незалежними змінними, зміни another_variable
не впливають на arg
:
int arg = 1;
int another_variable = arg;
another_variable = 2;
assert arg == 1; // true
assert another_variable == 2; // true
Це точно так само, як співвідношення між arg
і param
в нашому прикладі вище, яке я повторю тут для симетрії:
void foo(int param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
Це як би ми написали код таким чином:
// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here
Тобто визначальна характеристика того, що викликає значення, означає, що виклик ( foo
у даному випадку) отримує значення як аргументи, але має власні окремі змінні для цих значень від змінних абонента ( bar
у цьому випадку).
Повертаючись до моєї метафори вище, якщо я bar
і ти foo
, коли я тебе дзвоню, я передаю тобі аркуш паперу зі значенням, написаним на ньому. Ви називаєте цей аркуш паперу param
. Це значення є копією значення, яке я записав у своєму блокноті (мої локальні змінні), у змінну, яку я викликаю arg
.
(Вбік: залежно від апаратного забезпечення та операційної системи, існують різні умови виклику про те, як ви називаєте одну функцію від іншої. Конвенція про дзвінки - це як ми вирішуємо, чи записувати я значення на аркуші мого паперу, а потім передавати його вам або якщо у вас є аркуш паперу, на який я його пишу, або якщо я пишу на стіні перед нами обома. Це також цікава тема, але далеко виходить за рамки цієї вже тривалої відповіді.)
При виклику за посиланням формальні параметри функції - це просто нові імена для тих самих змінних, які абонент подає в якості аргументів.
Повертаючись до нашого вище прикладу, це рівнозначно:
// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here
Оскільки param
це лише інша назва arg
- тобто вони є тією ж змінною , зміни, на param
які відображаються arg
. Це основний спосіб, за яким виклик за посиланням відрізняється від дзвінка за значенням.
Дуже мало мов підтримують дзвінки за посиланням, але C ++ може робити це так:
void foo(int& param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
У цьому випадку param
не просто є таке ж значення, як arg
воно є насправді arg
(лише під іншим ім'ям), і тому bar
можна помітити, що arg
було збільшено.
Зауважте, що так не працює жоден Java, JavaScript, C, Objective-C, Python або майже будь-яка інша популярна сьогодні мова. Це означає, що ці мови не називаються посиланням, вони називаються за значенням.
Якщо ви маєте виклик за значенням , але фактичне значення - це тип посилання або тип вказівника , то саме "значення" не дуже цікаве (наприклад, у C це просто ціле число розміру, що залежить від платформи) - що таке цікаво те, на що вказує це значення .
Якщо те, на що вказує цей тип посилання (тобто вказівник), є незмінним, то можливий цікавий ефект: ви можете змінити вказане на значення, і абонент може спостерігати зміни у вказаному значенні, навіть якщо абонент не може спостерігати зміни до самого вказівника.
Щоб знову запозичити аналогію URL-адреси, той факт, що я дав вам копію URL-адреси на веб-сайт, не особливо цікавий, якщо нас обох цікавить веб-сайт, а не URL-адреса. Те, що ви писати над своєю копією URL-адреси, не впливає на мою копію URL-адреси - це нас не хвилює (а насправді для таких мов, як Java та Python, "URL", або значення типу посилання, може не можна змінювати взагалі, тільки те, на що вона вказує) може.
Барбара Лісков, коли винайшла мову програмування CLU (яка мала цю семантику), зрозуміла, що існуючі терміни "дзвінок за значенням" та "виклик за посиланням" не є особливо корисними для опису семантики цієї нової мови. Тому вона придумала новий термін: дзвінок по обміну об'єктами .
Під час обговорення мов, які технічно називають за значенням, але там, де поширеними типами користуються є типи посилань чи покажчиків (тобто майже кожен сучасний імперативний, об'єктно-орієнтований або багатопарадигмальний мова програмування), я вважаю, що це набагато менш заплутано просто уникайте говорити про дзвінок за значенням або за допомогою посилання . Дотримуйтесь дзвінка за допомогою спільного використання об'єкта (або просто дзвоніть за об'єктом ), і ніхто не буде плутати. :-)
The first is value versus variable.
The other important pair of concepts to distinguish is parameter versus argument:
Перш ніж зрозуміти два терміни, Ви ОБОВ'ЯЗКОВО зрозуміти наступне. Кожен об’єкт має 2 речі, які можуть його розрізнити.
Тож якщо ти скажеш employee.name = "John"
знайте, що є 2 речі про name
. Його значення , яке , "John"
а також його розташування в пам'яті , яка деякий шістнадцяткове число , може бути , як це: 0x7fd5d258dd00
.
Залежно від архітектури мови або типу (класу, структури тощо) вашого об'єкта, ви будете або передавати, "John"
або0x7fd5d258dd00
Проходження "John"
відоме як проходження за значенням. Проходження 0x7fd5d258dd00
відоме як проходження посиланням. Усі, хто вказує на це місце пам'яті, матимуть доступ до значення "John"
.
Для отримання додаткової інформації про це я рекомендую вам ознайомитися з перенаправленням покажчика, а також чому вибрати структуру (тип значення) над класом (тип посилання)
Ось приклад:
#include <iostream>
void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }
int main()
{
int x = 0;
by_val(x); std::cout << x << std::endl; // prints 0
by_ref(x); std::cout << x << std::endl; // prints 2
int y = 0;
by_ref(y); std::cout << y << std::endl; // prints 2
by_val(y); std::cout << y << std::endl; // prints 2
}
y
попереднім рядком вже встановлено 2. Чому воно повернеться до 0?
Найпростіший спосіб отримати це у файлі Excel. Скажімо, наприклад, що у вас є два числа, 5 і 2 у комірках A1 і B1 відповідно, і ви хочете знайти їх суму в третій комірці, скажімо, A2. Зробити це можна двома способами.
Або передавши свої значення клітині A2 , набравши = 5 + 2 в цю клітинку. У цьому випадку, якщо значення комірок A1 або B1 змінюються, сума в A2 залишається такою ж.
Або передавши "посилання" комірок A1 і B1 на комірку A2 , набравши = A1 + B1 . У цьому випадку, якщо значення комірок A1 або B1 змінюються, змінюється також сума в A2.
Передача за значенням надсилає КОПІЮВАННЯ даних, що зберігаються у вказаній змінній, передача посилань посилає пряме посилання на саму змінну. Отже, якщо ви передасте змінну за посиланням, а потім зміните змінну всередині блоку, в який ви її передали, початкова змінна буде змінена. Якщо ви просто перейдете за значенням, оригінальну змінну не вдасться змінити блоком, в який ви її передали, але ви отримаєте копію того, що вона містилася під час виклику.
Передати за значенням - функція копіює змінну і працює з копією (тому вона не змінює нічого в початковій змінній)
Передати посилання - функція використовує оригінальну змінну, якщо ви зміните змінну в іншій функції, вона змінюється і в вихідній змінній.
Приклад (скопіюйте та використовуйте / спробуйте це самі та подивіться):
#include <iostream>
using namespace std;
void funct1(int a){ //pass-by-value
a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}
int main()
{
int a = 5;
funct1(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
funct2(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7
return 0;
}
Тримайте це просто, заглядає. Стіни тексту можуть бути поганою звичкою.
Основна різниця між ними полягає в тому, що змінні типу типу зберігають значення, тому, визначаючи змінну типу значення у виклику методу, передає копію значення цієї змінної методу. Змінні типу посилання зберігають посилання на об'єкти, тому, визначаючи змінну типу посилання в якості аргументу, передає методу копію фактичної посилання, що посилається на об'єкт. Незважаючи на те, що сама посилання передається за значенням, метод все ще може використовувати посилання, яке вона отримує, щоб взаємодіяти з - і, можливо, змінювати - вихідний об'єкт. Аналогічно, при поверненні інформації з методу через оператор return, метод повертає копію значення, що зберігається у змінній типу значення, або копію посилання, що зберігається у змінній типу посилання. Коли повертається посилання, метод виклику може використовувати це посилання для взаємодії з посилається об'єктом. Тому,
У c #, щоб передати змінну за посиланням, щоб названий метод міг модифікувати змінну, C # надає ключові слова ref і out. Застосування ключового слова ref до оголошення параметра дозволяє передати змінну методу за посиланням - викликаний метод зможе змінити оригінальну змінну в абонента. Ключове слово ref використовується для змінних, які вже ініціалізовані в методі виклику. Зазвичай, коли виклик методу містить неініціалізовану змінну як аргумент, компілятор генерує помилку. Попередньо параметр із ключовим словом виводить вихідний параметр. Це вказує компілятору, що аргумент буде переданий в викликаний метод за посиланням і що викликаний метод призначить значення вихідній змінній у виклику. Якщо метод не присвоює значення вихідному параметру в кожному можливому шляху виконання, компілятор генерує помилку. Це також заважає компілятору генерувати повідомлення про помилку для неініціалізованої змінної, яке передається як аргумент методу. Метод може повернути своєму абоненту лише одне значення через оператор return, але може повернути багато значень, вказавши параметри декількох вихідних (ref та / або out).
дивіться c # обговорення та приклади тут тексту посилання
Приклади:
class Dog
{
public:
barkAt( const std::string& pOtherDog ); // const reference
barkAt( std::string pOtherDog ); // value
};
const &
як правило, найкраще. Ви не несете штрафу за будівництво та знищення. Якщо посилання не const, ваш інтерфейс передбачає, що він змінить передані дані.
Коротше кажучи, "Пройдене значення" - це ЩО воно є і передається за посиланням - ГДЕ ".
Якщо ваше значення VAR1 = "Щасливий хлопець!", Ви побачите лише "Щасливий хлопець!". Якщо VAR1 зміниться на "Happy Gal!", Ви цього не знаєте. Якщо це буде передано за посиланням, а VAR1 зміниться, ви.
Якщо ви не хочете змінювати значення вихідної змінної після передачі її у функцію, функція повинна бути побудована за допомогою параметра " передавати значення ".
Тоді функція матиме ТІЛЬКЕ значення, але не адресу переданої змінної. Без адреси змінної код всередині функції не може змінити значення змінної, як видно ззовні функції.
Але якщо ви хочете надати функції можливість змінювати значення змінної, як видно ззовні, вам потрібно скористатися пропуском за посиланням . Оскільки значення і адреса (посилання) передаються і доступні всередині функції.
передавати значення означає, як передавати значення функції за допомогою аргументів. мимобіжно за значенням ми копіюємо дані, що зберігаються у вказаній нами змінній, і це повільніше, ніж передавати за посиланням bcse t, коли дані копіюються. ми вносимо зміни в скопійовані дані, на вихідні дані це не впливає. Якщо ми передаємо посилання за адресою або за адресою, ми надсилаємо пряме посилання на саму змінну. або передача вказівника на змінну. це швидше, ніж менше часу витрачається
Ось приклад, який демонструє відмінності між прохідним значенням - значенням вказівника - посиланням :
void swap_by_value(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
}
void swap_by_pointer(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void swap_by_reference(int &a, int &b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(void){
int arg1 = 1, arg2 = 2;
swap_by_value(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 1 2
swap_by_pointer(&arg1, &arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
arg1 = 1; //reset values
arg2 = 2;
swap_by_reference(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
}
Метод «проходження посилання» має важливе обмеження . Якщо параметр оголошений як переданий посиланням (значить йому передує знак &), його відповідний фактичний параметр повинен бути змінною .
Фактичний параметр, що посилається на формальний параметр "переданий за значенням", може бути загальним виразом , тому дозволено використовувати не тільки змінну, але й буквальний результат або навіть результат виклику функції.
Функція не в змозі помістити значення в щось інше, ніж змінну. Він не може призначити нове значення буквальному чи змусити вираз змінити результат.
PS: Ви також можете перевірити відповідь Ділана Бітті у поточній темі, яка пояснює це простими словами.