Вказівник проти посилання


256

Що було б кращою практикою при наданні функції оригінальної змінної для роботи:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

або:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: Чи є підстави обрати один одного?


1
Посилання є звичайно цінними, але я родом із С, де вказівники є скрізь. Слід спочатку володіти вказівниками, щоб зрозуміти значення посилань.
Джей Д

Як це вписується у таку мету, як референсна прозорість функціонального програмування? Що робити, якщо ви завжди хочете, щоб функції повертали нові об'єкти і ніколи не змінювали стан, особливо не змінні, передані функції. Чи існує спосіб використання цієї концепції з покажчиками та посиланнями такою мовою, як C ++. (Зверніть увагу, я припускаю, що у когось вже є мета референтної прозорості. Мені не цікаво говорити про те, чи є це хорошою метою.)
ely

Віддайте перевагу посиланням. Покажчики користувачів, коли у вас немає вибору.
Ферруччо

Відповіді:


285

Моє правило:

Використовуйте покажчики, якщо ви хочете робити з ними арифметику вказівника (наприклад, збільшуючи адресу вказівника, щоб перейти через масив) або якщо вам коли-небудь доведеться передати NULL-покажчик.

Використовуйте посилання інакше.


10
Відмінна точка щодо покажчика NULL. Якщо у вас є параметр вказівника, ви повинні або чітко перевірити, що це не NULL, або шукати всі звичаї функції, щоб бути впевненим, що це ніколи не NULL. Ці зусилля не потрібні для довідок.
Річард Корден

26
Поясніть, що ви маєте на увазі під арифметикою. Новий користувач може не зрозуміти, що ви хочете відрегулювати те, на що вказує вказівник.
Мартін Йорк

7
Мартін, по арифметиці я маю на увазі, що ви передаєте вказівник на структуру, але знаєте, що це не проста структура, а її масив. У цьому випадку ви можете або індексувати його за допомогою [], або робити арифметику, використовуючи ++ / - на покажчику. Ось у двох словах різниця.
Нілс Піпенбрінк

2
Мартіне, ти можеш це зробити лише за допомогою покажчиків безпосередньо. Не з посиланнями. Впевнені, що ви можете взяти покажчик на посилання і зробити те ж саме на практиці, але якщо ви це зробите, ви закінчите дуже брудним кодом ..
Nils Pipenbrinck,

1
А як щодо поліморфізму (наприклад Base* b = new Derived())? Це здається випадком, який не вдається обробити без покажчиків.
Кріс Редфорд

72

Я дійсно думаю, що ви отримаєте користь від встановлення наступних функцій виклику кодування:

  1. Як і у всіх інших місцях, завжди будь- constправильним.

    • Примітка. Це означає, серед іншого, що лише значень, що мають значення (див. Пункт 3) та значення, передані за значенням (див. Пункт 4), можуть бракувати constспецифікатора.
  2. Передавайте значення лише за вказівником, якщо значення 0 / NULL є дійсним введенням у поточному контексті.

    • Обґрунтування 1: Як абонент , ви бачите, що все, що ви переходите, повинно бути у справному стані.

    • Обґрунтування 2: Як закликано , ви знаєте, що все, що відбувається, знаходиться у використанні. Отже, для цього значення не потрібно проводити перевірку NULL або обробку помилок.

    • Обґрунтування 3: Обґрунтування 1 і 2 будуть виконані . Завжди ловіть помилки під час компіляції, якщо можете.

  3. Якщо аргумент функції не має значення, то передайте його за посиланням.

    • Обґрунтування: Ми не хочемо порушувати пункт 2 ...
  4. Виберіть "пропустити за значенням" над "пройти через посилання на const" лише у тому випадку, якщо значення є ПОД ( Звичайна стара структура даних ) або досить невелика (в пам'яті) або іншим способом досить дешевим (часовим) для копіювання.

    • Обгрунтування: уникайте зайвих копій.
    • Зауважте: досить малі та досить дешеві не є абсолютними вимірами.

У ньому відсутнє керівництво, коли: ... "коли використовувати const &" ... Настанова 2 повинна бути написана "для [в] значень, проходити лише вказівником, якщо NULL є дійсним. В іншому випадку використовуйте посилання const (або для" невеликі "об'єкти, копія) або посилання, якщо це значення [out]. Я відстежую цю публікацію, щоб потенційно додати +1."
paercebal

Пункт 1 охоплює описаний вами випадок.
Йоганн Герелл

Трохи важко передати вихідний параметр за посиланням, якщо він не може бути сконструйований за замовчуванням. Це досить поширене в моєму коді - вся причина, щоб функція створювала цей об'єкт, полягає в тому, що це нетривіально.
MSalters

@MSalters: Якщо ви збираєтесь виділити пам'ять всередині функції (на яку я думаю, що ви маєте на увазі), то чому б просто не повернути покажчик на виділену пам'ять?
Клейст

@Kleist: Від імені @MSalters є багато можливих причин. Одне полягає в тому, що, можливо, ви вже виділили пам'ять для заповнення, як попередній розмір std::vector<>.
Йоганн Герелл

24

Це врешті-решт стає суб'єктивним. Поки що обговорення корисне, але я не думаю, що на це є правильна чи вирішальна відповідь. Багато буде залежати від настанов стилю та ваших потреб у той час.

Хоча існують деякі різні можливості (чи може щось бути NULL) з покажчиком, найбільша практична різниця для вихідного параметра - це суто синтаксис. Наприклад, посібник зі стилів Google C ++ ( https://google.github.io/styleguide/cppguide.html#Reference_Arguments ), наприклад, позначає лише покажчики на вихідні параметри та дозволяє використовувати лише посилання, які є const. Міркування є читабельним: щось із синтаксисом значення не повинно мати смислове значення покажчика. Я не припускаю, що це обов'язково правильно чи неправильно, але я думаю, що тут справа в тому, що це справа стилю, а не коректності.


Що означає, що посилання мають синтаксис значення, але не мають смислового значення вказівника?
Ерік Ендрю Льюїс

Схоже, ви передаєте копію, оскільки частина "пройти за посиланням" видно лише з визначення функції funciton (синтаксис значення), але ви не копіюєте передане вами значення, ви по суті передаєте вказівник під капот, що дозволяє функція зміни вашого значення.
phant0m

Не слід забувати, що посібник зі стилів Google C ++ сильно збентежений.
Deduplicator

7

Вам слід передати вказівник, якщо ви збираєтесь змінити значення змінної. Навіть незважаючи на те, що технічно передача посилання або вказівника однакова, передача покажчика у вашому випадку використання є більш читаною, оскільки "рекламує" той факт, що значення буде змінено функцією.


2
Якщо ви дотримуєтесь інструкцій Йогана Герелла, посилання, що не містить const, також рекламує змінну змінну, тому вказівник не має такої переваги.
Олександр Кондрацький

4
@AlexanderKondratskiy: ви пропустили точку ... ви не можете бачити відразу на місці виклику , чи приймає викликається функцію у вигляді параметр Я constабо ненаправленої constпосилання, але ви можете побачити , якщо параметр переданих ала &xvs. xі використання ця конвекція для кодування того, чи може параметр бути змінений. (Отож, є випадки, коли вам захочеться пройти constвказівник, тому скликання - лише натяк. Сперечається підозра на те, що щось може бути змінено, коли це не буде, менш небезпечно, ніж думати, що не буде, коли це буде ....)
Тоні Делрой

5

Якщо у вас є параметр, де вам може знадобитися вказати відсутність значення, звичайна практика робити параметр значенням вказівника і передавати в NULL.

Кращим рішенням у більшості випадків (з точки зору безпеки) є використання boost :: необов'язково . Це дозволяє передавати необов'язкові значення за посиланням, а також як повернене значення.

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}


4

Покажчики

  • Вказівник - це змінна, що містить адресу пам'яті.
  • Декларація вказівника складається з базового типу, * та імені змінної.
  • Вказівник може вказувати на будь-яку кількість змінних протягом життя
  • Вказівнику, який в даний момент не вказує на дійсне місце пам'яті, надається значення null (Що дорівнює нулю)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • & - унарний оператор, який повертає адресу пам'яті свого операнда.

  • Оператор перенаправлення (*) використовується для доступу до значення, збереженого у змінній, на яку вказує вказівник.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

Довідково

  • Посилання (&) - це як псевдонім існуючої змінної.

  • Посилання (&) - це як постійний вказівник, який автоматично відмічається.

  • Зазвичай використовується для списків аргументів функцій та повернених значень функції.

  • Посилання має бути ініціалізовано під час його створення.

  • Після того, як посилання ініціалізується на об'єкт, його не можна змінити для посилання на інший об'єкт.

  • Ви не можете мати NULL посилань.

  • Посилання на const може посилатися на const int. Це робиться з тимчасовою змінною зі значенням const

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

введіть тут опис зображення

введіть тут опис зображення


3

Посилання - це неявний покажчик. В основному ви можете змінити значення, на яке посилаються точки, але ви не можете змінити посилання, щоб вказувати на щось інше. Отже, мої 2 центи - це те, що якщо ви хочете змінити значення параметра, передайте його як опорне, але якщо вам потрібно змінити параметр, щоб вказати на інший об'єкт, передайте його за допомогою вказівника.


3

Розгляньте ключове слово C #. Компілятор вимагає від виклику методу застосувати ключове слово out до будь-яких аргументів, хоча він уже знає, чи є вони. Це покликане підвищити читабельність. Хоча з сучасними IDE я схильний думати, що це робота для синтаксичного (або семантичного) виділення.


друкарські помилки: семантичний, а не симантичний; +1 Я згоден щодо можливості виділення, а не виписування (C #), або & (у випадку C, жодних посилань)
peenut

2

Пройти повз посилання const, якщо немає причин, який ви бажаєте змінити / зберегти вміст, який ви передаєте.

Це буде найбільш ефективним методом у більшості випадків.

Переконайтеся, що ви використовуєте const для кожного параметра, який ви не бажаєте змінювати, оскільки це не тільки захищає вас від того, щоб зробити щось нерозумно у функції, але й добре вказує іншим користувачам, що ця функція робить для переданих значень. Це включає створення const вказівника, коли ви хочете змінити лише те, на що вказували ...


2

Покажчики:

  • Можна призначити nullptr(або NULL).
  • На сайті виклику ви повинні використовувати, &якщо ваш тип не є самим покажчиком, явно ви змінюєте ваш об'єкт.
  • Покажчики можуть бути відскоковими.

Список літератури:

  • Не може бути нульовим.
  • Після прив’язки не можна змінити.
  • Абонентам не потрібно явно використовувати &. Це вважається часом поганим, тому що ви повинні перейти до реалізації функції, щоб побачити, чи змінено ваш параметр.

Невеликий момент для тих, хто не знає: nullptr або NULL - це просто 0. stackoverflow.com/questions/462165/…
Сергей Федоров

2
nullptr не те саме, що 0. Спробуйте int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Йохан Лундберг

0

Посилання схожа на покажчик, за винятком того, що вам не потрібно використовувати префікс ∗ для доступу до значення, на яке посилається посилання. Також не може бути посилання на посилання на інший об'єкт після його ініціалізації.

Посилання особливо корисні для вказівки аргументів функції.

для отримання додаткової інформації див. "Екскурсія на C ++" від "Bjarne Stroustrup" (2014) Сторінки 11-12

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.