Wie übergebe ich Objekte an Funktionen in C ++?


249

Ich bin neu in der C ++ - Programmierung, habe aber Erfahrung in Java. Ich benötige Anleitungen zum Übergeben von Objekten an Funktionen in C ++.

Muss ich Zeiger, Referenzen oder Nicht-Zeiger- und Nicht-Referenzwerte übergeben? Ich erinnere mich, dass es in Java keine derartigen Probleme gibt, da wir nur die Variable übergeben, die auf die Objekte verweist.

Es wäre großartig, wenn Sie auch erklären könnten, wo jede dieser Optionen verwendet werden soll.


6
Aus welchem ​​Buch lernst du C ++?

17
Das Buch ist stark nicht empfohlen. Entscheiden Sie sich für C ++ Primer von Stan Lippman.
Prasoon Saurav

23
Nun, da ist dein Problem. Schildt ist im Grunde genommen cr * p - get Accelerated C ++ von Koenig & Moo.

9
Ich frage mich, wie niemand die C ++ - Programmiersprache von Bjarne Stroustrup erwähnt hat. Bjarne Stroustrup ist der Schöpfer von C ++. Ein wirklich gutes Buch, um C ++ zu lernen.
George

15
@ George: TC ++ PL ist nicht für Anfänger, aber es gilt als die Bibel für C ++.
XD

Antworten:


277

Faustregeln für C ++ 11:

Übergeben Sie den Wert , außer wenn

  1. Sie nicht das Eigentum an dem Objekt benötigen und eine einfache Alias tun werden, in dem Fall , dass Sie vorbei constBezug ,
  2. Sie müssen das Objekt mutieren. In diesem Fall verwenden Sie pass by a constnon-lvalre reference .
  3. Sie übergeben Objekte abgeleiteter Klassen als Basisklassen. In diesem Fall müssen Sie als Referenz übergeben . (Verwenden Sie die vorherigen Regeln, um zu bestimmen, ob als constReferenz übergeben werden soll oder nicht.)

Das Übergeben eines Zeigers wird praktisch nie empfohlen. Optionale Parameter werden am besten als std::optional( boost::optionalfür ältere Standardbibliotheken) ausgedrückt , und das Aliasing wird anhand der Referenz problemlos durchgeführt.

Die Bewegungssemantik von C ++ 11 macht das Übergeben und Zurückgeben nach Wert auch für komplexe Objekte viel attraktiver.


Faustregeln für C ++ 03:

Übergeben von Argumenten durch constVerweis , es sei denn,

  1. Sie sind in der Funktion und solche Änderungen geändert werden sollten außen reflektiert werden, in welchem Fall sie durch nicht passieren constReferenz
  2. sollte die Funktion ohne Argument aufrufbar sein, in dem Fall , dass Sie durch den Zeiger übergeben, so dass die Benutzer weitergeben können NULL/ 0/ nullptrstatt; Wenden Sie die vorherige Regel an, um zu bestimmen, ob Sie einen Zeiger auf ein constArgument übergeben sollen
  3. Es handelt sich um integrierte Typen, die per Kopie übergeben werden können
  4. Sie müssen innerhalb der Funktion geändert werden, und solche Änderungen sollten nicht außerhalb der Funktion widergespiegelt werden. In diesem Fall können Sie eine Kopie übergeben (eine Alternative wäre, gemäß den vorherigen Regeln zu übergeben und eine Kopie innerhalb der Funktion zu erstellen).

(Hier wird "Übergeben von Wert" als "Übergeben von Kopie" bezeichnet, da das Übergeben von Wert in C ++ 03 immer eine Kopie erstellt.)


Es gibt noch mehr, aber diese wenigen Anfängerregeln bringen Sie ziemlich weit.


17
+1 - Ich möchte auch darauf hinweisen, dass einige (z. B. Google) der Meinung sind, dass Objekte, die innerhalb der Funktion geändert werden, über einen Zeiger anstelle einer nicht konstanten Referenz übergeben werden sollten. Der Grund dafür ist, dass es offensichtlicher ist, wenn die Adresse eines Objekts an eine Funktion übergeben wird, dass diese Funktion sie ändern kann. Beispiel: Bei Referenzen lautet der Aufruf foo (bar); ob die Referenz const ist oder nicht, mit einem Zeiger ist es foo (& bar); und es ist offensichtlicher, dass foo ein veränderliches Objekt übergeben wird.
RC.

19
@RC sagt Ihnen immer noch nicht, ob der Zeiger const ist oder nicht. Die Richtlinien von Google haben in den verschiedenen C ++ - Online-Communities viel Aufsehen erregt - zu Recht, IMHO.

14
Während in anderen Kontexten Google möglicherweise eine Vorreiterrolle einnimmt, ist der Styleguide in C ++ nicht wirklich gut.
David Rodríguez - Dribeas

4
@ArunSaha: Als reiner Styleguide hat Stroustrup einen Guide , der für ein Luft- und Raumfahrtunternehmen entwickelt wurde. Ich habe den Google-Leitfaden durchgesehen und ihn aus mehreren Gründen nicht gemocht. Sutter & Alexandrescu C ++ Coding Standards ist ein großartiges Buch zum Lesen, und Sie können einige gute Ratschläge erhalten, aber es ist nicht wirklich ein Styleguide . Ich weiß nicht , von automatisierten Checker für Stil , mit Ausnahme des Menschen und der gesunde Menschenverstand.
David Rodríguez - Dribeas

3
@anon Sie erhalten jedoch die Garantie, dass ein Argument, das nicht über einen Zeiger übergeben wird, NICHT geändert wird. Das ist meiner Meinung nach sehr wertvoll. Andernfalls müssen Sie beim Verfolgen der Vorgänge mit einer Variablen in einer Funktion die Header-Dateien aller übergebenen Funktionen untersuchen, um festzustellen, ob sie geändert wurden. Auf diese Weise müssen Sie nur die betrachten, die über den Zeiger übergeben wurden.
smehmood

107

Es gibt einige Unterschiede bei den Aufrufkonventionen in C ++ und Java. In C ++ gibt es technisch gesehen nur zwei Konventionen: Pass-by-Value und Pass-by-Reference, wobei einige Literaturstellen eine dritte Pass-by-Pointer-Konvention enthalten (dh tatsächlich Pass-by-Value eines Zeigertyps). Darüber hinaus können Sie dem Typ des Arguments Konstanz hinzufügen und so die Semantik verbessern.

Als Referenz übergeben

Das Übergeben als Referenz bedeutet, dass die Funktion Ihre Objektinstanz konzeptionell empfängt und keine Kopie davon. Die Referenz ist konzeptionell ein Alias ​​für das Objekt, das im aufrufenden Kontext verwendet wurde, und kann nicht null sein. Alle innerhalb der Funktion ausgeführten Operationen gelten für das Objekt außerhalb der Funktion. Diese Konvention ist in Java oder C nicht verfügbar.

Wert übergeben (und Zeiger übergeben)

Der Compiler generiert eine Kopie des Objekts im aufrufenden Kontext und verwendet diese Kopie innerhalb der Funktion. Alle innerhalb der Funktion ausgeführten Operationen werden für die Kopie ausgeführt, nicht für das externe Element. Dies ist die Konvention für primitive Typen in Java.

Eine spezielle Version davon übergibt einen Zeiger (Adresse des Objekts) an eine Funktion. Die Funktion empfängt den Zeiger, und alle Operationen, die auf den Zeiger selbst angewendet werden, werden auf die Kopie (Zeiger) angewendet. Andererseits werden Operationen, die auf den dereferenzierten Zeiger angewendet werden, auf die Objektinstanz an diesem Speicherort angewendet, also auf die Funktion kann Nebenwirkungen haben. Durch die Verwendung der Wertübergabe eines Zeigers auf das Objekt kann die interne Funktion externe Werte wie bei der Referenzübergabe ändern und optionale Werte zulassen (Übergabe eines Nullzeigers).

Dies ist die in C verwendete Konvention, wenn eine Funktion eine externe Variable ändern muss, und die in Java verwendete Referenzkonvention mit Referenztypen: Die Referenz wird kopiert, das referenzierte Objekt ist jedoch dasselbe: Änderungen an der Referenz / dem Zeiger sind außerhalb nicht sichtbar die Funktion, aber Änderungen am spitzen Speicher sind.

Hinzufügen von const zur Gleichung

In C ++ können Sie Objekten Konstanz zuweisen, wenn Sie Variablen, Zeiger und Referenzen auf verschiedenen Ebenen definieren. Sie können eine Variable als konstant deklarieren, einen Verweis auf eine konstante Instanz deklarieren und alle Zeiger auf konstante Objekte, konstante Zeiger auf veränderbare Objekte und konstante Zeiger auf konstante Elemente definieren. Umgekehrt können Sie in Java nur eine Konstantenebene (endgültiges Schlüsselwort) definieren: die der Variablen (Instanz für primitive Typen, Referenz für Referenztypen), aber Sie können keine Referenz auf ein unveränderliches Element definieren (es sei denn, die Klasse selbst ist dies unveränderlich).

Dies wird häufig in C ++ - Aufrufkonventionen verwendet. Wenn die Objekte klein sind, können Sie das Objekt als Wert übergeben. Der Compiler generiert eine Kopie, aber diese Kopie ist keine teure Operation. Wenn die Funktion das Objekt bei keinem anderen Typ ändert, können Sie eine Referenz an eine konstante Instanz (normalerweise als konstante Referenz bezeichnet) des Typs übergeben. Dadurch wird das Objekt nicht kopiert, sondern an die Funktion übergeben. Gleichzeitig garantiert der Compiler, dass das Objekt innerhalb der Funktion nicht verändert wird.

Faustregeln

Dies sind einige Grundregeln, die befolgt werden müssen:

  • Bevorzugen Sie die Übergabe von Werten für primitive Typen
  • Bevorzugen Sie die Referenzübergabe mit Verweisen auf die Konstante für andere Typen
  • Wenn die Funktion das Argument ändern muss, verwenden Sie die Referenzübergabe
  • Wenn das Argument optional ist, verwenden Sie den Pass-by-Pointer (zur Konstante, wenn der optionale Wert nicht geändert werden soll).

Es gibt andere kleine Abweichungen von diesen Regeln, von denen die erste die Behandlung des Eigentums an einem Objekt betrifft. Wenn ein Objekt dynamisch mit new zugewiesen wird, muss es mit delete (oder den [] -Versionen davon) freigegeben werden. Das Objekt oder die Funktion, die für die Zerstörung des Objekts verantwortlich ist, gilt als Eigentümer der Ressource. Wenn ein dynamisch zugewiesenes Objekt in einem Codeteil erstellt wird, der Besitz jedoch auf ein anderes Element übertragen wird, erfolgt dies normalerweise mit der Semantik des Pass-by-Pointers oder wenn möglich mit intelligenten Zeigern.

Randnotiz

Es ist wichtig, auf die Bedeutung des Unterschieds zwischen C ++ - und Java-Referenzen zu bestehen. In C ++ sind Referenzen konzeptionell die Instanz des Objekts und kein Accessor dafür. Das einfachste Beispiel ist die Implementierung einer Swap-Funktion:

// 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 ); 
}

Die obige Swap-Funktion ändert beide Argumente durch die Verwendung von Referenzen. Der nächstgelegene Code in 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
   }
}

Die Java-Version des Codes ändert die Kopien der Referenzen intern, die tatsächlichen Objekte jedoch nicht extern. Java-Referenzen sind C-Zeiger ohne Zeigerarithmetik, die als Wert an Funktionen übergeben werden.


4
@ David-Rodriguez-Dribeas Ich mag die Faustregeln Abschnitt, speziell "
Vorzugspass

Meiner Meinung nach ist dies eine viel bessere Antwort auf die Frage.
unrealsoul007

22

Es sind mehrere Fälle zu berücksichtigen.

Parameter geändert (Parameter "out" und "in / out")

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

In diesem Fall geht es hauptsächlich um Stil: Möchten Sie, dass der Code wie call (obj) oder call (& obj) aussieht ? Es gibt jedoch zwei Punkte, an denen der Unterschied von Bedeutung ist: den optionalen Fall unten, und Sie möchten eine Referenz verwenden, wenn Sie Operatoren überladen.

... und optional

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

Parameter nicht geändert

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

Dies ist der interessante Fall. Als Faustregel gilt, dass "billig zu kopierende" Typen als Wert übergeben werden - dies sind im Allgemeinen kleine Typen (aber nicht immer) -, während andere als const ref übergeben werden. Wenn Sie jedoch unabhängig von Ihrer Funktion eine Kopie erstellen müssen, sollten Sie den Wert übergeben . (Ja, dies enthüllt einige Implementierungsdetails. C'est le C ++. )

... und optional

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

Hier gibt es den geringsten Unterschied zwischen allen Situationen. Wählen Sie also die Situation, die Ihnen das Leben am einfachsten macht.

Const by Value ist ein Implementierungsdetail

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

Diese Deklarationen haben tatsächlich genau die gleiche Funktion! Bei der Übergabe von Werten ist const lediglich ein Implementierungsdetail. Versuch es:

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 Ich wusste nicht, constdass es sich bei der Übergabe von Werten um eine Implementierung handelt.
Balki

20

Wert übergeben:

void func (vector v)

Übergeben Sie Variablen als Wert, wenn die Funktion vollständig von der Umgebung isoliert werden muss, dh um zu verhindern, dass die Funktion die ursprüngliche Variable ändert, und um zu verhindern, dass andere Threads ihren Wert ändern, während die Funktion ausgeführt wird.

Der Nachteil sind die CPU-Zyklen und der zusätzliche Speicher, der zum Kopieren des Objekts aufgewendet wird.

Pass by const Referenz:

void func (const vector& v);

Dieses Formular emuliert das Verhalten beim Übergeben von Werten, während der Kopieraufwand entfernt wird. Die Funktion erhält Lesezugriff auf das ursprüngliche Objekt, kann jedoch dessen Wert nicht ändern.

Der Nachteil ist die Thread-Sicherheit: Jede Änderung, die von einem anderen Thread am Originalobjekt vorgenommen wird, wird in der Funktion angezeigt, während sie noch ausgeführt wird.

Übergeben Sie eine nicht konstante Referenz:

void func (vector& v)

Verwenden Sie diese Option, wenn die Funktion einen Wert in die Variable zurückschreiben muss, der letztendlich vom Aufrufer verwendet wird.

Genau wie der const-Referenzfall ist dies nicht threadsicher.

Übergeben Sie den const-Zeiger:

void func (const vector* vp);

Funktionell identisch mit übergeben durch const-Referenz, mit Ausnahme der unterschiedlichen Syntax sowie der Tatsache, dass die aufrufende Funktion den NULL-Zeiger übergeben kann, um anzuzeigen, dass keine gültigen Daten übergeben werden müssen.

Nicht threadsicher.

Übergeben Sie einen nicht konstanten Zeiger:

void func (vector* vp);

Ähnlich wie bei einer nicht konstanten Referenz. Der Aufrufer setzt die Variable normalerweise auf NULL, wenn die Funktion keinen Wert zurückschreiben soll. Diese Konvention wird in vielen glibc-APIs verwendet. Beispiel:

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

Genau wie alle, die als Referenz / Zeiger übergeben werden, nicht threadsicher.


0

Da niemand erwähnt, dass ich es hinzufüge, wird beim Übergeben eines Objekts an eine Funktion in c ++ der Standardkopierkonstruktor des Objekts aufgerufen, wenn Sie keinen haben, der einen Klon des Objekts erstellt und ihn dann an die Methode übergibt Wenn Sie die Objektwerte ändern, die sich auf die Kopie des Objekts anstelle des ursprünglichen Objekts auswirken, ist dies das Problem in c ++. Wenn Sie also alle Klassenattribute als Zeiger festlegen, kopieren die Kopierkonstruktoren die Adressen des Zeigerattribute Wenn also die Methode das Objekt aufruft, das die in den Zeigerattributadressen gespeicherten Werte bearbeitet, werden die Änderungen auch im ursprünglichen Objekt wiedergegeben, das als Parameter übergeben wird. Dies kann sich also wie bei Java verhalten, aber vergessen Sie nicht, dass Ihre gesamte Klasse Attribute müssen Zeiger sein, außerdem sollten Sie die Werte von Zeigern ändern.wird mit Code-Erklärung viel klarer sein.

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
}

Dies ist jedoch keine gute Idee, da Sie am Ende viel Code mit Zeigern schreiben werden, die für Speicherlecks anfällig sind und nicht vergessen, Destruktoren aufzurufen. Um dies zu vermeiden, verfügt c ++ über Kopierkonstruktoren, in denen Sie neuen Speicher erstellen, wenn die Objekte, die Zeiger enthalten, an Funktionsargumente übergeben werden, die die Manipulation anderer Objektdaten stoppen. Java übergibt den Wert und der Wert ist die Referenz, sodass keine Kopierkonstruktoren erforderlich sind.


-1

Es gibt drei Methoden, um ein Objekt als Parameter an eine Funktion zu übergeben:

  1. Als Referenz übergeben
  2. Wert übergeben
  3. Konstante im Parameter hinzufügen

Gehen Sie das folgende Beispiel durch:

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;
}

Ausgabe:

Angenommen, ich bin in someFunc.
Der Wert des Zeigers ist -17891602.
Der Wert der Variablen ist 4


Es gibt nur 2 Methoden (die ersten 2, die Sie erwähnt haben). Keine Ahnung, was Sie mit "Konstante im Parameter übergeben" gemeint haben
MM

-1

Im Folgenden finden Sie die Möglichkeiten, Argumente / Parameter an C ++ zu übergeben.

1. nach Wert.

// passing parameters by value . . .

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

2. durch Bezugnahme.

// 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. nach Objekt.

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
"Objekt passieren" ist keine Sache. Es gibt nur Wertübergabe und Referenzübergabe. Ihr "Fall 3" zeigt tatsächlich einen Fall von Wertübergabe und einen Fall von Referenzübergabe.
MM
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.