Come passare oggetti alle funzioni in C ++?


249

Sono nuovo nella programmazione 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 senza puntatore e senza riferimento? Ricordo che in Java non ci sono problemi del genere poiché passiamo solo la variabile che contiene il riferimento agli oggetti.

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


6
Da quale libro stai imparando il C ++?

17
Quel libro è fortemente non raccomandato. Scegli C ++ Primer di Stan Lippman.
Prasoon Saurav,

23
Bene, c'è il tuo problema. Schildt è fondamentalmente cr * p - get Accelerated C ++ di Koenig & Moo.

9
Mi chiedo come nessuno abbia menzionato il linguaggio di programmazione C ++ di Bjarne Stroustrup. Bjarne Stroustrup è il creatore di C ++. Un ottimo libro per imparare il C ++.
George,

15
@George: TC ++ PL non è per i principianti, ma è considerata la Bibbia per C ++.
XD

Risposte:


277

Regole empiriche per C ++ 11:

Passa per valore , tranne quando

  1. non hai bisogno della proprietà dell'oggetto e un semplice alias lo farà, nel qual caso passi per constriferimento ,
  2. devi mutare l'oggetto, nel qual caso, usa pass per un constriferimento non lvalue ,
  3. si passano oggetti di classi derivate come classi di base, nel qual caso è necessario passare per riferimento . (Utilizzare le regole precedenti per determinare se passare per constriferimento o meno.)

Il passaggio tramite puntatore non è praticamente mai consigliato. I parametri opzionali sono meglio espressi come std::optional( boost::optionalper le vecchie librerie std) e l'aliasing viene eseguito bene per riferimento.

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


Regole empiriche per C ++ 03:

Passa gli argomenti per constriferimento , tranne quando

  1. devono essere modificati all'interno della funzione e tali cambiamenti devono essere riflessi all'esterno, nel qual caso si passa per non constriferimento
  2. la funzione dovrebbe essere richiamabile senza alcun argomento, nel qual caso si passa dal puntatore, in modo che gli utenti possano passare NULL/ 0/ nullptrinvece; applica la regola precedente per determinare se devi passare un puntatore a un constargomento
  3. sono di tipo incorporato, che può essere passato per copia
  4. devono essere modificati all'interno della funzione e tali modifiche non devono essere riflesse all'esterno, nel qual caso è possibile 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 abbastanza lontano.


17
+1 - Vorrei anche notare che alcuni (ad esempio Google) ritengono che gli oggetti che verranno modificati all'interno della funzione debbano essere passati tramite un puntatore anziché un riferimento non const. Il ragionamento è che quando l'indirizzo di un oggetto viene passato a una funzione è più evidente che detta funzione può cambiarlo. Esempio: con i riferimenti, la chiamata è foo (barra); che il riferimento sia const o meno, con un puntatore è foo (& bar); ed è più evidente che a foo viene passato un oggetto mutabile.
RC.

19
@RC Ancora non ti dice se il puntatore è const o no. Le linee guida di Google sono arrivate per molte critiche nelle varie comunità online di C ++ - giustamente, IMHO.

14
Mentre in altri contesti google può essere all'avanguardia, in C ++ la loro guida di stile non è poi così buona.
David Rodríguez - dribeas,

4
@ArunSaha: Come pura guida di stile, Stroustrup ha una guida sviluppata per un'azienda aerospaziale. Ho sfogliato la guida di google e non mi è piaciuto per un paio di motivi. Sutter & Alexandrescu C ++ Coding Standards è un ottimo libro da leggere e puoi ottenere parecchi buoni consigli, ma non è proprio una guida di stile . Non conosco nessun controllore automatico per stile , se non gli umani e il buon senso.
David Rodríguez - dribeas,

3
@anon Tuttavia, si ottiene la garanzia che se un argomento non viene passato tramite un puntatore, NON viene modificato. È un valore IMHO piuttosto utile, altrimenti quando si tenta di tracciare ciò che accade a una variabile in una funzione, è necessario esaminare i file di intestazione di tutte le funzioni a cui è passato per determinare se è cambiato. In questo modo, devi solo guardare quelli che è stato passato tramite il puntatore.
smehmood,

107

Vi sono alcune differenze nelle convenzioni di chiamata in C ++ e Java. In C ++ ci sono tecnicamente solo due convenzioni: pass-by-value e pass-by-reference, con alcune pubblicazioni tra cui una terza convenzione pass-by-pointer (che in realtà è pass-by-value di un tipo di puntatore). Inoltre, puoi aggiungere costanza al tipo di argomento, migliorando la semantica.

Passa per riferimento

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

Passa per valore (e pass-by-pointer)

Il compilatore genererà una copia dell'oggetto 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 passa un puntatore (indirizzo dell'oggetto) 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 senza riferimenti verranno applicate all'istanza dell'oggetto in quella posizione di memoria, quindi la funzione può avere effetti collaterali. L'effetto dell'utilizzo del valore pass-by di un puntatore sull'oggetto consentirà alla funzione interna di modificare i valori esterni, come nel caso del riferimento pass-by e consentirà anche valori opzionali (passare un puntatore null).

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

Aggiungendo const all'equazione

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

Questo è ampiamente utilizzato nelle convenzioni di chiamata C ++. Quando gli oggetti sono piccoli, è possibile passare l'oggetto per valore. Il compilatore genererà una copia, ma quella copia non è un'operazione costosa. Per qualsiasi altro tipo, se la funzione non modificherà l'oggetto, è possibile passare un riferimento a un'istanza costante (in genere denominata riferimento costante) del tipo. Questo non copierà l'oggetto, ma lo passerà nella funzione. Allo stesso tempo, il compilatore garantirà che l'oggetto non venga modificato all'interno della funzione.

Regole pratiche

Queste sono alcune regole di base da seguire:

  • Preferisce il valore pass-by per i tipi primitivi
  • Preferisci il riferimento pass-by con riferimenti alla costante per altri tipi
  • Se la funzione deve modificare l'argomento, utilizzare il pass-by-reference
  • Se l'argomento è facoltativo, utilizzare il pass-by-pointer (per 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 oggetto. Quando un oggetto viene allocato dinamicamente con new, deve essere deallocato con delete (o le sue versioni []). L'oggetto o la funzione responsabile della distruzione dell'oggetto è considerato il proprietario della risorsa. Quando un oggetto allocato in modo dinamico viene creato in un pezzo di codice, ma la proprietà viene trasferita a un altro elemento, di solito viene eseguita con semantica pass-by-pointer o, se possibile, con smart pointer.

Nota a margine

È importante insistere sull'importanza della differenza tra riferimenti C ++ e Java. In C ++ i riferimenti sono concettualmente l'istanza dell'oggetto, non un accedente ad esso. L'esempio più semplice sta implementando 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 del puntatore che vengono passati per valore nelle funzioni.


4
@ david-rodriguez-dribeas Mi piacciono le regole del pollice, specialmente "Preferisci il pass-by-value per i tipi primitivi"
yadab

Secondo me, questa è una risposta molto migliore alla domanda.
unrealsoul007,

22

Vi 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 facoltativo, di seguito, e si desidera utilizzare un riferimento quando si sovraccarica 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 empirica è che i tipi "economici da copiare" sono passati per valore - questi sono generalmente piccoli (ma non sempre) - mentre altri sono passati da const ref. Tuttavia, se è necessario effettuare 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'è la minima differenza qui tra tutte le situazioni, quindi scegli quella che ti semplifica la vita.

Cost per valore è un dettaglio di implementazione

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

Queste dichiarazioni sono in realtà la stessa identica funzione! Quando passa per valore, const è semplicemente 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

3
+1 Non sapevo di constessere un'implementazione quando si passa per valore.
balki,

20

Passa per valore:

void func (vector v)

Passa le variabili per valore quando la funzione necessita di un completo isolamento dall'ambiente, ad esempio per impedire alla funzione di modificare la variabile originale e per impedire ad altri thread di modificare il suo valore mentre la funzione è in esecuzione.

Il rovescio della medaglia sono i cicli della CPU e la memoria aggiuntiva spesa per copiare l'oggetto.

Passa per riferimento const:

void func (const vector& v);

Questo modulo emula il comportamento pass-by-value mentre rimuove l'overhead della copia. La funzione ottiene l'accesso in lettura all'oggetto originale, ma non può modificarne il valore.

L'aspetto negativo è la sicurezza del thread: qualsiasi modifica apportata all'oggetto originale da un altro thread verrà visualizzata all'interno della funzione mentre è ancora in esecuzione.

Passa per riferimento non const:

void func (vector& v)

Utilizzare questo quando la funzione deve riscrivere un valore nella variabile, che alla fine verrà utilizzata dal chiamante.

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

Passa dal puntatore const:

void func (const vector* vp);

Funzionalmente identico a passa per riferimento costante ad eccezione della diversa sintassi, oltre al fatto che la funzione chiamante può passare il puntatore NULL per indicare che non ha dati validi da passare.

Non thread-safe.

Passa da un puntatore non const:

void func (vector* vp);

Simile al riferimento non const. Il chiamante in genere imposta la variabile su NULL quando la funzione non deve riscrivere 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 i passaggi per riferimento / puntatore, non thread-safe.


0

Dal momento che nessuno ha menzionato che lo sto aggiungendo, quando passi un oggetto a una funzione in c ++ viene chiamato il costruttore di copie predefinito dell'oggetto se non ne hai uno che crea un clone dell'oggetto e quindi lo passa al metodo, quindi quando si modificano i valori degli oggetti che si rifletteranno sulla copia dell'oggetto anziché sull'oggetto originale, questo è il problema in c ++, quindi se si fanno tutti gli attributi di classe come puntatori, i costruttori di copia copieranno gli indirizzi del attributi del puntatore, quindi quando il metodo richiama sull'oggetto che manipola i valori memorizzati negli indirizzi degli attributi del puntatore, le modifiche si riflettono anche nell'oggetto originale che viene passato come parametro, quindi questo può comportarsi come un Java ma non dimenticare che tutta la tua classe gli attributi devono essere puntatori, inoltre è necessario modificare 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 con i puntatori, che sono inclini a perdite di memoria e non dimenticare di chiamare i distruttori. E per evitare che questo c ++ abbia costruttori di copie in cui creerai nuova memoria quando gli oggetti contenenti puntatori vengono passati a argomenti di funzione che smetteranno di manipolare i dati di altri oggetti, Java passa per valore e valore è riferimento, quindi non richiede costruttori di copia.


-1

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

  1. Passa per riferimento
  2. passare per valore
  3. aggiungendo costante nel parametro

Passa attraverso il 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:

Supponiamo che io sia in someFunc
Il valore del puntatore è -17891602
Il valore della variabile è 4


Ci sono solo 2 metodi (i primi 2 che hai menzionato). Non ho idea di cosa intendevi con "passaggio di costante nel parametro"
MM

-1

Di seguito sono riportati i modi per passare argomenti / parametri al funzionamento in C ++.

1. per 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 oggetto.

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
"passare per oggetto" non è una cosa. C'è solo passaggio per valore e passaggio per riferimento. Il tuo "caso 3" in realtà mostra un caso di passaggio per valore e un caso di passaggio per riferimento.
MM
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.