Як передавати об’єкти функціям в C ++?


249

Я новачок у програмуванні на C ++, але маю досвід роботи в Java. Мені потрібні вказівки щодо передачі об'єктів функціям на C ++.

Чи потрібно мені передавати вказівники, посилання чи не-вказівні та неопосилання? Пам’ятаю, у Java таких питань немає, оскільки ми передаємо лише змінну, яка містить посилання на об’єкти.

Було б чудово, якби ви могли також пояснити, де використовувати кожен із цих варіантів.


6
З якої книги ви навчаєтесь C ++?

17
Ця книга категорично не рекомендується. Перейдіть на C ++ Primer від Стен Ліппман.
Prasoon Saurav

23
Ну, є ваша проблема. Шилдт в основному cr * p - отримуйте прискорений C ++ від Koenig & Moo.

9
Цікаво, як ніхто не згадував мову програмування C ++ від Bjarne Stroustrup. Bjarne Stroustrup - творець C ++. Дійсно хороша книга для вивчення C ++.
Джордж

15
@George: TC ++ PL не призначений для початківців, але вважається Біблією для C ++. XD
Прасоон Саурав

Відповіді:


277

Правила великого пальця для C ++ 11:

Перейти за значенням , крім випадків, коли

  1. вам не потрібне право власності на об’єкт, і простий псевдонім зробить, і в цьому випадку ви проходите повзconst посиланням ,
  2. ви повинні вимкнути об'єкт, і в цьому випадку використовувати пропуск неconst нецінні посилання ,
  3. ви передаєте об'єкти похідних класів як базові класи, і в цьому випадку вам потрібно передати посилання . (Скористайтеся попередніми правилами, щоб визначити, проходити constпосилання чи ні.)

Проходити по вказівнику практично ніколи не рекомендується. Необов’язкові параметри найкраще виражати як astd::optional ( boost::optionalдля старих ліній версій), а псевдонім виконувати добре за допомогою посилання.

Семантика руху C ++ 11 робить набагато привабливішими проходження та повернення за значенням навіть для складних об'єктів.


Правила великого пальця для C ++ 03:

Передайте аргументи за constпосиланням , крім випадків, коли

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

(тут "pass by value" називається "pass by copy", оскільки передача за значенням завжди створює копію в C ++ 03)


Тут є ще багато, але ці кілька правил для початківців дадуть вам досить далеко.


17
+1 - Я також зазначу, що деякі (тобто Google) вважають, що об'єкти, які будуть змінені в межах функції, повинні передаватися через вказівник замість non-const посилання. Причина полягає в тому, що коли адреса об'єкта передається функції, більш очевидно, що зазначена функція може її змінити. Приклад: із посиланнями, виклик foo (bar); чи посилання const чи ні, вказівником є ​​foo (& bar); і більш очевидно, що foo передається змінним об'єктом.
RC.

19
@RC все ще не говорить вам, чи вказівник const чи ні. Вказівки Google привели до великої кількості проблем у різних Інтернет-спільнотах C ++ - це виправдано, IMHO.

14
Хоча в інших контекстах Google може бути лідируючим, у C ++ їх посібник зі стилів насправді не настільки гарний.
Девід Родрігес - дрибес

4
@ArunSaha: Як посібник із чистого стилю, у Stroustrup є посібник , розроблений для аерокосмічної компанії. Я переглянув путівник google і мені це не сподобалося з кількох причин. Стандарти кодування Sutter & Alexandrescu C ++ - це чудова книга, яку можна прочитати, і ви можете отримати досить багато корисних порад, але насправді це не посібник із стилів . Я не знаю жодної автоматизованої шашки для стилю , окрім людей та здорового глузду.
Девід Родрігес - дрибес

3
@anon Однак ви отримуєте гарантію, що якщо аргумент не передається через покажчик, він НЕ буде змінений. Це досить цінний IMHO, інакше, намагаючись простежити, що відбувається зі змінною у функції, вам доведеться вивчити файли заголовків усіх переданих функцій, щоб визначити, чи змінилась вона. Таким чином, вам залишається лише дивитися на ті, які були передані за допомогою вказівника.
smehmood

107

Існують деякі відмінності у викликах конвенцій у C ++ та Java. В C ++ технічно говорять лише дві конвенції: прохідна вартість і пропускна посилання, з деякою літературою, включаючи третю конвенцію прохідного вказівника (що є фактично прохідним значенням типу вказівника). Крім того, ви можете додати доцільність до типу аргументу, посилюючи семантику.

Перейти за посиланням

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

Передача за значенням (і прохідний вказівник)

Компілятор генерує копію об'єкта в контексті виклику і використовує цю копію всередині функції. Усі операції, що виконуються всередині функції, виконуються над копією, а не зовнішнім елементом. Це умова для примітивних типів на Java.

Спеціальна його версія - це передача покажчика (адреси-об’єкта) у функцію. Функція отримує вказівник, і всі операції, застосовані до самого вказівника, застосовуються до копії (вказівника), з іншого боку, операції, застосовані до вказівника відміни, застосовуватимуться до об'єкта об'єкта в цьому місці пам'яті, тому функція може мати побічні ефекти. Ефект від використання прохідного значення вказівника на об’єкт дозволить внутрішній функції змінювати зовнішні значення, як при передачі посилання, а також дозволить отримати необов'язкові значення (передайте нульовий покажчик).

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

Додавання рівняння до рівняння

У C ++ ви можете призначити константу ness об'єктам при визначенні змінних, покажчиків та посилань на різних рівнях. Ви можете оголосити змінну постійною, ви можете оголосити посилання на постійний екземпляр, а також можна визначити всі вказівники на постійні об'єкти, постійні вказівники на об'єкти, що змінюються, і постійні вказівники на постійні елементи. І навпаки, у Java ви можете визначити лише один рівень константи-ness (остаточне ключове слово): рівень змінної (екземпляр для примітивних типів, посилання на типи посилань), але ви не можете визначити посилання на незмінний елемент (якщо тільки сам клас не непорушний).

Це широко використовується у конвенціях про виклики C ++. Коли об’єктів мало, ви можете передати об'єкт за значенням. Компілятор створить копію, але ця копія не є дорогою операцією. Для будь-якого іншого типу, якщо функція не змінить об'єкт, ви можете передати посилання на постійний екземпляр (зазвичай називається постійним посиланням) типу. Це не скопіює об’єкт, а передасть його у функцію. Але в той же час компілятор гарантує, що об'єкт не буде змінений всередині функції.

Емпіричні правила

Ось кілька основних правил, яких слід дотримуватися:

  • Віддайте перевагу прохідному значенню для примітивних типів
  • Віддайте перевагу проходженню з посиланнями на постійні для інших типів
  • Якщо функції потрібно змінити аргумент, використовуйте пропускний посилання
  • Якщо аргумент необов’язковий, використовуйте прохідний вказівник (до постійного, якщо необов'язкове значення не слід змінювати)

Існують і інші невеликі відхилення від цих правил, перше з яких - обробка права власності на об'єкт. Коли об'єкт динамічно виділяється новим, він повинен бути розміщений з видаленням (або [] його версіями). Об'єкт або функція, що відповідає за знищення об'єкта, вважається власником ресурсу. Коли динамічно виділений об'єкт створюється в фрагменті коду, але право власності передається іншому елементу, зазвичай це робиться за допомогою семантики прохідних покажчиків або, якщо можливо, за допомогою розумних покажчиків.

Бічна примітка

Важливо наполягати на важливості різниці між посиланнями C ++ та Java. У посиланнях C ++ концептуально є екземпляр об'єкта, а не його доступ. Найпростіший приклад - реалізація функції swap:

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

Вищенаведена функція swap змінює обидва аргументи за допомогою використання посилань. Найближчий код на Java:

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

Версія Java-коду змінює копії посилань всередині країни, але не змінює фактичні об'єкти зовні. Посилання Java - це покажчики C без арифметики вказівника, які передаються за значенням у функції.


4
@ david-rodriguez-dribeas Мені подобаються правила розділу великого пальця, спеціально "Віддавайте перевагу
переважним

На мою думку, це набагато краща відповідь на питання.
undealsoul007

22

Можна розглянути кілька випадків.

Параметр змінено (параметри "вихід" та "вхід / вихід")

void modifies(T &param);
// vs
void modifies(T *param);

Цей випадок здебільшого стосується стилю: чи хочете, щоб код виглядав як call (obj) або call (& obj) ? Однак є два моменти, в яких різниця має значення: необов'язковий випадок, наведений нижче, і ви хочете використовувати посилання під час перевантаження операторів.

... і необов’язково

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

Параметр не змінений

void uses(T const &param);
// vs
void uses(T param);

Це цікавий випадок. Основне правило: "дешево копіювати", типи передаються за значенням - це, як правило, невеликі типи (але не завжди), а інші передаються через const ref. Однак якщо вам потрібно зробити копію в межах своєї функції незалежно, вам слід перейти за значенням . (Так, це дає дещо детальну інформацію про реалізацію. C'est le C ++. )

... і необов’язково

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

Тут є найменша різниця між усіма ситуаціями, тому вибирайте те, що полегшує ваше життя.

Вартість за вартістю - це деталь реалізації

void f(T);
void f(T const);

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

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types

3
+1 Я не знав про constте, що це реалізація, коли проходить повз значення.
балки

20

Перейти за вартістю:

void func (vector v)

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

Недоліком є ​​цикли процесора та додаткова пам'ять, витрачена на копіювання об'єкта.

Перейти через посилання const:

void func (const vector& v);

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

Мінусом є безпека потоку: будь-яка зміна оригінального об'єкта іншим потоком з’явиться всередині функції, поки вона все ще виконується.

Пройти повне посилання:

void func (vector& v)

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

Як і у випадку, що стосується еталону const, це не є безпечним для потоків.

Пройти повз вказівник const:

void func (const vector* vp);

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

Не безпечно для ниток.

Пройти повз неконст-покажчик:

void func (vector* vp);

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

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

Так само, як і всі прохідні посилання / покажчик, не є безпечними для потоків.


0

Оскільки ніхто не згадав, що я додаю на нього, Коли ви передаєте об'єкт функції у c ++, викликується конструктор копіювання за замовчуванням об'єкта, якщо у вас немає такого, який створює клон об'єкта, а потім передає його методу, так коли ви змінюєте значення об'єкта, які відображатимуться на копії об'єкта замість оригінального об'єкта, це проблема в c ++, тож якщо ви робите всі атрибути класу покажчиками, то конструктори копіювання копіюватимуть адреси атрибути покажчика, тому коли метод викликає об'єкт, який маніпулює значеннями, збереженими в адресах атрибутів вказівника, зміни відображаються також у вихідному об'єкті, який передається як параметр, тому це може поводитись так само як Java, але не забувайте, що весь ваш клас атрибути повинні бути вказівниками, також ви повинні змінити значення вказівників,буде зрозуміло з поясненням коду.

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

Але це не дуже гарна ідея, оскільки ви закінчите писати багато коду, пов’язаного з покажчиками, які схильні до витоку пам'яті і не забудьте викликати деструктори. І щоб уникнути цього c ++ мати конструктори копій, де ви будете створювати нову пам'ять, коли об’єкти, що містять покажчики, передаються функціям аргументів, які перестануть маніпулювати даними інших об'єктів, Java переходить за значенням і значенням є посиланням, тому не вимагає конструкторів копіювання.


-1

Існує три способи передачі об'єкта функції як параметр:

  1. Перейти за посиланням
  2. пройти за значенням
  3. додавання константи в параметр

Перейдіть наступний приклад:

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

Вихід:

Скажіть, я в someFunc
Значення вказівника -17891602
Значення змінної дорівнює 4


Існує лише 2 методи (перші 2 ви згадали). Поняття не маю на увазі, що ви мали на увазі під "пропусканням константи в параметрі"
ММ

-1

Нижче наведено способи передачі аргументів / параметрів для функціонування в C ++.

1. за значенням.

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2. шляхом посилання.

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3. за об’єктом.

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}

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