Come passare oggetti alle funzioni in C ++?

Sono nuovo alla programmazione in C ++, ma ho esperienza in Java. Ho bisogno di una guida su come passare oggetti alle funzioni in C ++.

Devo passare puntatori, riferimenti o valori non puntatore e non di riferimento? Ricordo che in Java non ci sono tali problemi poiché passiamo solo la variabile che contiene riferimenti agli oggetti.

Sarebbe bello se potessi anche spiegare dove usare ciascuna di queste opzioni.

Regole pratiche per C ++ 11:

Passa per valore , tranne quando

  1. non hai bisogno della proprietà dell’object e un semplice alias lo farà, nel qual caso passi per riferimento const ,
  2. è necessario mutare l’object, nel qual caso, utilizzare il pass by un riferimento di lvalue non const ,
  3. si passano oggetti di classi derivate come classi base, nel qual caso è necessario passare per riferimento . (Usa le regole precedenti per determinare se passare per riferimento const o no.)

Passare da un puntatore non è praticamente mai consigliato. I parametri opzionali sono meglio espressi come boost::optional , e l’aliasing è fatto bene per riferimento.

La semantica del movimento di C ++ 11 rende il passaggio e il ritorno di valore molto più attraente anche per oggetti complessi.


Regole pratiche per C ++ 03:

Passare argomenti con riferimento const , tranne quando

  1. devono essere cambiati all’interno della funzione e tali cambiamenti dovrebbero essere riflessi all’esterno, nel qual caso si passa per riferimento non const
  2. la funzione dovrebbe essere chiamabile senza argomenti, nel qual caso si passa il puntatore, in modo che gli utenti possano passare NULL / 0 / nullptr ; applica la regola precedente per determinare se è necessario passare da un puntatore a un argomento const
  3. sono dei tipi built-in, che possono essere passati per copia
  4. devono essere cambiati all’interno della funzione e tali cambiamenti non devono essere riflessi all’esterno, nel qual caso puoi passare per copia (un’alternativa sarebbe passare secondo le regole precedenti e fare una copia all’interno della funzione)

(qui, “passa per valore” si chiama “passa per copia”, perché il passaggio per valore crea sempre una copia in C ++ 03)


C’è di più in questo, ma queste poche regole per principianti ti porteranno molto lontano.

Ci sono alcune differenze nel chiamare le convenzioni in C ++ e Java. In C ++ ci sono tecnicamente solo due convenzioni: pass-by-value e pass-by-reference, con alcune pubblicazioni che includono una terza convenzione pass-by-pointer (che in realtà è un valore pass-by di un tipo di puntatore). Oltre a questo, puoi aggiungere constity al tipo di argomento, migliorando la semantica.

Passare per riferimento

Passare per riferimento significa che la funzione riceverà concettualmente l’istanza dell’object e non una sua copia. Il riferimento è concettualmente un alias dell’object utilizzato nel contesto chiamante e non può essere nullo. Tutte le operazioni eseguite all’interno della funzione si applicano all’object all’esterno della funzione. Questa convenzione non è disponibile in Java o C.

Passa per valore (e pass-per-puntatore)

Il compilatore genererà una copia dell’object nel contesto chiamante e utilizzerà quella copia all’interno della funzione. Tutte le operazioni eseguite all’interno della funzione vengono eseguite sulla copia, non sull’elemento esterno. Questa è la convenzione per i tipi primitivi in ​​Java.

Una versione speciale di esso sta passando un puntatore (indirizzo-dell’object) in una funzione. La funzione riceve il puntatore e tutte le operazioni applicate al puntatore stesso vengono applicate alla copia (puntatore), d’altra parte, le operazioni applicate al puntatore dereferenziato si applicano all’istanza dell’object in quella posizione di memoria, quindi la funzione può avere effetti collaterali. L’effetto dell’utilizzo del pass-by-value di un puntatore all’object consentirà alla funzione interna di modificare i valori esterni, come nel caso del riferimento pass-by e consentirà anche valori opzionali (passare un puntatore nullo).

Questa è la convenzione utilizzata in C quando una funzione deve modificare una variabile esterna e la convenzione utilizzata in Java con i tipi di riferimento: il riferimento viene copiato, ma l’object di riferimento è lo stesso: le modifiche al riferimento / puntatore non sono visibili all’esterno la funzione, ma le modifiche alla memoria puntata sono.

Aggiungere const all’equazione

In C ++ è ansible assegnare una costante agli oggetti quando si definiscono variabili, puntatori e riferimenti a diversi livelli. È ansible dichiarare una variabile come costante, è ansible dichiarare un riferimento a un’istanza costante ed è ansible definire tutti i puntatori agli oggetti costanti, puntatori costanti agli oggetti mutabili e puntatori costanti agli elementi costanti. Viceversa in Java è ansible definire un solo livello di costante (keyword finale): quello della variabile (istanza per i tipi primitivi, riferimento per i tipi di riferimento), ma non è ansible definire un riferimento a un elemento immutabile (a meno che la class stessa non sia immutabile).

Questo è ampiamente utilizzato nelle convenzioni di chiamata C ++. Quando gli oggetti sono piccoli, puoi passare l’object in base al valore. Il compilatore genererà una copia, ma quella copia non è un’operazione costosa. Per qualsiasi altro tipo, se la funzione non modifica l’object, è ansible passare un riferimento a un’istanza costante (di solito denominata riferimento costante) del tipo. Questo non copierà l’object, ma lo passerà alla funzione. Ma allo stesso tempo il compilatore garantirà che l’object non venga modificato all’interno della funzione.

Regole empiriche

Ecco alcune regole base da seguire:

  • Preferisci il pass-by-value per i tipi primitivi
  • Preferisci il pass-by-reference con riferimenti a costanti per altri tipi
  • Se la funzione deve modificare l’argomento utilizzare pass-per-riferimento
  • Se l’argomento è facoltativo, utilizzare pass-by-pointer (a costante se il valore facoltativo non deve essere modificato)

Ci sono altre piccole deviazioni da queste regole, la prima delle quali è la gestione della proprietà di un object. Quando un object viene assegnato dynamicmente con new, deve essere deallocato con delete (o con le sue versioni []). L’object o la funzione responsabile della distruzione dell’object è considerato il proprietario della risorsa. Quando un object allocato dynamicmente viene creato in un pezzo di codice, ma la proprietà viene trasferita a un elemento diverso, di solito viene eseguita con semantica di pass-by-pointer o, se ansible, con puntatori intelligenti.

Nota a margine

È importante insistere sull’importanza della differenza tra i riferimenti C ++ e Java. In C ++ i riferimenti sono concettualmente l’istanza dell’object, non un accettore ad esso. L’esempio più semplice è l’implementazione di una funzione di scambio:

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

La funzione di scambio sopra cambia entrambi i suoi argomenti attraverso l’uso di riferimenti. Il codice più vicino 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 } } 

La versione Java del codice modificherà le copie dei riferimenti internamente, ma non modificherà gli oggetti reali esternamente. I riferimenti Java sono puntatori C senza aritmetica puntatore che vengono passati per valore in funzioni.

Ci sono diversi casi da considerare.

Parametro modificato (parametri “out” e “in / out”)

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

Questo caso riguarda principalmente lo stile: vuoi che il codice assomigli a call (obj) o call (& obj) ? Tuttavia, ci sono due punti in cui la differenza è importante: il caso opzionale, di seguito, e si desidera utilizzare un riferimento quando si sovraccaricano gli operatori.

… e facoltativo

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

Parametro non modificato

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

Questo è il caso interessante. La regola generale è che i tipi “economici per copiare” vengono passati per valore – questi sono in genere piccoli tipi (ma non sempre) – mentre altri vengono passati da const ref. Tuttavia, se è necessario eseguire una copia all’interno della propria funzione, è necessario passare per valore . (Sì, questo espone un po ‘di dettagli di implementazione. C’est le C ++. )

… e facoltativo

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

C’è qui la minima differenza tra tutte le situazioni, quindi scegli quella che ti facilita la vita.

Const by value è un dettaglio di implementazione

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

Queste dichiarazioni sono in realtà la stessa identica funzione! Quando si passa per valore, const è puramente un dettaglio di implementazione. Provalo:

 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 

Passa per valore:

 void func (vector v) 

Passa le variabili in base al valore quando la funzione richiede il completo isolamento dall’ambiente, ovvero per impedire alla funzione di modificare la variabile originale e impedire che altri thread modifichino il suo valore mentre la funzione viene eseguita.

Lo svantaggio sono i cicli della CPU e la memoria aggiuntiva spesa per copiare l’object.

Passa con riferimento const:

 void func (const vector & v); 

Questo modulo emula il comportamento del pass-by-value mentre rimuove il sovraccarico di copia. La funzione ottiene l’accesso in lettura all’object originale, ma non può modificarne il valore.

Il lato negativo è la sicurezza del thread: qualsiasi modifica apportata all’object originale da un altro thread verrà visualizzata all’interno della funzione mentre è ancora in esecuzione.

Passa con riferimento non const:

 void func (vector & v) 

Usalo quando la funzione deve riportare un valore alla variabile, che alla fine verrà utilizzata dal chiamante.

Proprio come il caso di riferimento const, questo non è thread-safe.

Passa con il puntatore const:

 void func (const vector * vp); 

Funzionalmente come passare per riferimento costante eccetto per la diversa syntax, oltre al fatto che la funzione di chiamata può passare il puntatore NULL per indicare che non ha dati validi da passare.

Non thread sicuro.

Passa con un puntatore non const:

 void func (vector * vp); 

Simile al riferimento non const. Generalmente, il chiamante imposta la variabile su NULL quando la funzione non deve restituire un valore. Questa convenzione è presente in molte API di glibc. Esempio:

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

Proprio come tutti passano per riferimento / puntatore, non thread-safe.

Poiché nessuno ha menzionato che sto aggiungendo su di esso, quando si passa un object a una funzione in c ++ viene chiamato il costruttore di copia predefinito dell’object se non si ha uno che crea un clone dell’object e quindi lo passa al metodo, quindi quando modifichi i valori dell’object che rifletteranno sulla copia dell’object invece dell’object originale, questo è il problema in c ++, quindi se fai tutti gli attributi di class come puntatori, i costruttori di copia copieranno gli indirizzi del attributi del puntatore, quindi quando le invocazioni del metodo sull’object che manipolano i valori memorizzati negli indirizzi degli attributi del puntatore, le modifiche si riflettono anche nell’object originale che viene passato come parametro, quindi questo può comportarsi come un Java ma non dimenticare che tutta la class gli attributi devono essere i puntatori, inoltre dovresti cambiare i valori dei puntatori, sarà molto chiaro con la spiegazione del codice.

 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 } 

Ma questa non è una buona idea in quanto finirai per scrivere molto codice che coinvolge i puntatori, che sono a rischio di perdite di memoria e non dimenticano di chiamare i distruttori. E per evitare questo c ++ hanno costruttori di copia in cui creerai nuova memoria quando gli oggetti contenenti puntatori vengono passati agli argomenti della funzione che smetteranno di manipolare altri dati degli oggetti, Java passa per valore e il valore è riferimento, quindi non richiede costruttori di copia.

Esistono tre metodi per passare un object a una funzione come parametro:

  1. Passare per riferimento
  2. passare di valore
  3. aggiunta costante nel parametro

Passare al seguente esempio:

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

Produzione:

Dì che sono in qualcheFunc
Il valore del puntatore è -17891602
Il valore della variabile è 4

I seguenti sono i modi per passare argomenti / parametri per funzionare in C ++.

1. in base al valore.

 // passing parameters by value . . . void foo(int x) { x = 6; } 

2. per riferimento.

 // 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. per object.

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