Domanda

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

Devo passare puntatori, riferimenti, o non-puntatore e non-valori di riferimento?Mi ricordo che in Java non ci sono problemi dal momento che si passa solo la variabile che contiene il riferimento agli oggetti.

Sarebbe bello se si potrebbe anche spiegare dove utilizzare ciascuna di tali opzioni.

È stato utile?

Soluzione

Regole empiriche per il C ++ 11:

Passo per valore , tranne quando

  1. non è necessario la proprietà dell'oggetto e un semplice alias farà, nel qual caso si passare per riferimento const ,
  2. è necessario mutare l'oggetto, in questo caso, utilizzare passare da un riferimento lvalue non const ,
  3. si passano oggetti di classi derivate come le classi di base, nel qual caso è necessario passare per riferimento . (Utilizzare le regole precedenti per determinare se passare per riferimento const oppure no.)

Passaggio dal puntatore viene praticamente mai consigliato. I parametri facoltativi sono meglio espresse in std::optional (boost::optional per anziani librerie std), e l'aliasing è fatto bene da riferimento.

C ++ 11 di semantica mossa fanno passaggio e la restituzione per valore molto più attraente anche per oggetti complessi.


Regole empiriche per il C ++ 03:

passare argomenti con riferimento const , tranne quando

  1. devono essere modificati all'interno della funzione e tali modifiche dovrebbero riflettersi all'esterno, nel qual caso si passaggio per riferimento non const
  2. La funzione deve essere richiamabile senza alcun argomento, nel qual caso si passa da puntatore, in modo che gli utenti possano passare NULL / 0 / nullptr invece; applicare la regola precedente per determinare se è necessario passare da un puntatore ad un argomento const
  3. sono di tipi predefiniti, che può essere passò copia
  4. devono essere modificati all'interno della funzione e tali modifiche dovrebbero non riflettersi all'esterno, nel qual caso si può passare copiando (in alternativa potrebbe essere quella di passare secondo le regole precedenti e fare una copia all'interno della funzione)

(qui, "passaggio per valore" si chiama "passare copia", in quanto passaggio per valore crea sempre una copia in C ++ 03)


C'è di più a questo, ma le regole questi pochi principiante ti porterà molto lontano.

Altri suggerimenti

Ci sono alcune differenze nelle convenzioni di chiamata in C++ e Java.In C++ ci sono tecnicamente parlando solo due convenzioni:il passaggio per valore e di passaggio per riferimento, con alcuni testi tra cui un terzo passaggio per puntatore convenzione (che è in realtà il passaggio per valore di un puntatore di tipo).In cima a quello, è possibile aggiungere const-ness per il tipo di argomento, migliorando la semantica.

Passaggio per riferimento

Il passaggio per riferimento, significa che la funzione sarà concettualmente ricevere la vostra istanza di oggetto e non una copia.Il riferimento è concettualmente un alias per l'oggetto che è stato usato nella chiamata contesto, e non può essere null.Tutte le operazioni eseguite all'interno della funzione si applicano all'oggetto al di fuori della funzione.La presente convenzione non è disponibile in Java o C.

Passaggio per valore (e di passaggio per puntatore)

Il compilatore genererà una copia dell'oggetto nella chiamata contesto e usare la copia all'interno della funzione.Tutte le operazioni eseguite all'interno della funzione sono fatto per la copia, non l'elemento esterno.Questa è la convenzione per i tipi primitivi in Java.

Una versione speciale di sta passando un puntatore (indirizzo dell'oggetto) in una funzione.La funzione riceve il puntatore, e tutte le operazioni applicato il puntatore stesso, sono applicati per la copia (puntatore), d'altra parte, le operazioni di applicare il puntatore dereferenziato si applicano per l'istanza dell'oggetto in quella posizione di memoria, in modo che la funzione può avere effetti collaterali.L'effetto dell'utilizzo di passaggio per valore di un puntatore all'oggetto consentirà all'interno funzione per modificare i valori esterni, come con il passaggio per riferimento e per valori opzionali (passare un puntatore null).

Questa è la convenzione utilizzata in C quando una funzione ha la necessità di modificare una variabile esterna, e la convenzione utilizzata in Java con i tipi di riferimento:il riferimento è copiato, ma il cui oggetto è lo stesso:modifiche al riferimento/puntatore non sono visibili al di fuori della funzione, ma cambia la punta di memoria sono.

L'aggiunta di const l'equazione

In C++ è possibile assegnare costante-ness di oggetti nella definizione di variabili, i puntatori e riferimenti a diversi livelli.È possibile dichiarare una variabile e costante, si può dichiarare un riferimento a un costante esempio, e si può definire di tutti i puntatori a oggetti costanti, costanti puntatori a oggetti mutabili e costante puntatori ad elementi costanti.Al contrario in Java è possibile definire un solo livello di costante-ness (parola chiave finale):che della variabile di istanza per i tipi di base, di riferimento per i tipi di riferimento), ma non è possibile definire un riferimento a un elemento immutabile (a meno che la classe stessa è immutabile).

Questo è ampiamente utilizzato in C++ convenzioni di chiamata.Quando gli oggetti sono piccoli si può passare l'oggetto di valore.Il compilatore genererà una copia, ma che la copia non è un'operazione costosa.Per qualsiasi altro tipo, se la funzione non modificare l'oggetto, è possibile passare un riferimento a un costante istanza (di solito chiamato costante di riferimento) del tipo.Questo non copia l'oggetto, ma passa in funzione.Ma, allo stesso tempo, il compilatore garantisce che l'oggetto non è cambiato all'interno della funzione.

Regole di pollice

Questo sono alcune regole di base da seguire:

  • Preferisco pass-by-value per i tipi primitivi
  • Preferisce il passaggio per riferimento, con riferimenti costanti per altri tipi di
  • Se la funzione deve modificare l'argomento usare il passaggio per riferimento
  • Se l'argomento è opzionale, di passaggio per puntatore a costante opzionale, se il valore non deve essere modificato)

Ci sono altre piccole deviazioni da queste regole, la prima delle quali è di gestire la proprietà di un oggetto.Quando un oggetto viene allocata dinamicamente con il nuovo, deve essere rilasciato con eliminare (o [] versioni dello stesso).L'oggetto o una funzione che è responsabile per la distruzione di un oggetto è considerato il proprietario della risorsa.Quando un oggetto allocato dinamicamente è creato in un pezzo di codice, ma la proprietà è trasferita a un elemento diverso e di solito è fatto con il passaggio per puntatore semantica, o, se possibile, con i puntatori intelligenti.

Nota a margine

L'importante è insistere nell'importanza della differenza tra C++ e Java riferimenti.In C++ riferimenti sono concettualmente l'istanza dell'oggetto, non una funzione di accesso 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 di cui sopra modifiche entrambi gli argomenti attraverso l'utilizzo di riferimenti.Il più vicino di codice 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 di codice modificare le copie dei riferimenti internamente, ma non possono modificare gli oggetti esterni.Java riferimenti sono puntatori C senza aritmetica dei puntatori, che passa dal valore di funzioni.

Ci sono diversi casi da considerare.

parametro modificato ( "out" e "in / out" parametri)

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

Questo caso è in gran parte una questione di stile: vuoi il codice a guardare come call (obj) o di chiamata (& obj) ? Tuttavia, ci sono due punti in cui la differenza conta: il caso facoltativa, di seguito, e si desidera utilizzare un riferimento quando il sovraccarico operatori

.

... e opzionali

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

Parametro non modificati

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

Questo è il caso interessante. La regola generale è "a buon mercato per copiare" tipi sono passati per valore - questi sono generalmente di piccole dimensioni tipi (ma non sempre) - mentre altri sono passati per ref const. Tuttavia, se avete bisogno di fare una copia all'interno della vostra funzione, indipendentemente, è dovrebbe passare per valore . (Sì, questo espone un po 'di dettaglio di implementazione. C'est le C ++. )

... e opzionali

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

Non c'è la minima differenza qui tra tutte le situazioni, in modo da scegliere a seconda di quale rende la vita più semplice.

Const per valore è un dettaglio di implementazione

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

Queste dichiarazioni sono in realtà la esattamente la stessa funzione! Quando passaggio per valore, const è puramente un dettaglio implementativo. Provalo ora:

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

passaggio per valore:

void func (vector v)

variabili passaggio per valore quando la funzione necessita completo isolamento dall'ambiente cioè per impedire la funzione di modificare la variabile originale e per evitare altri thread di modificare il suo valore durante l'esecuzione della funzione.

Il lato negativo è i cicli di CPU e memoria extra speso per copiare l'oggetto.

Passaggio per Riferimento const:

void func (const vector& v);

Questa forma emula comportamento passaggio per valore, eliminando l'overhead copiatura. La funzione viene accesso all'oggetto originale lettura, ma non può modificare il suo valore.

Lo svantaggio è la sicurezza filo: qualsiasi modifica apportata all'oggetto originale da un altro thread apparirà all'interno della funzione mentre è ancora in esecuzione

.

Passaggio per Riferimento non-const:

void func (vector& v)

Utilizzare questa quando la funzione deve scrivere di nuovo un po 'di valore alla variabile, che in ultima analisi abituarsi dal chiamante.

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

Le domande Pass dal puntatore const:

void func (const vector* vp);

Funzionalmente stesso passaggio da const riferimento salvo la diversa sintassi, oltre al fatto che la funzione chiamante può passare puntatore NULL per indicare che non ha dati validi per passare.

Non thread-safe.

Le domande Pass per puntatore non-const:

void func (vector* vp);

Simile a riferimento non-const. Il chiamante imposta in genere la variabile NULL quando la funzione non dovrebbe scrivere di nuovo un valore. Questa convenzione è visto in molte API glibc. Esempio:

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

Come tutti passaggio per riferimento / puntatore, non thread-safe.

Dal momento che nessuno ha menzionato sto aggiungendo su di esso, quando si passa un oggetto a una funzione in C ++ copia di default costruttore dell'oggetto viene chiamato se non avete uno che crea un clone dell'oggetto e poi passarlo al metodo, in modo che quando si modificano i valori di oggetto che rifletteranno sulla copia dell'oggetto anziché l'oggetto originale, che è il problema in C ++, quindi, se si effettua tutta la classe attributi da puntatori, quindi i costruttori di copia copierà i indirizzi degli attributi di puntatore, in modo che quando le chiamate di metodo per l'oggetto che manipola i valori memorizzati nel puntatore attribuisce gli indirizzi, i cambiamenti riflettono anche nella dell'oggetto originale, che è passato come parametro, quindi questo possono comportarsi stesso un Java ma non dimenticate che tutti i tuoi attributi di classe devono essere puntatori, inoltre si dovrebbe modificare i valori di puntatori, sarà molto chiaro con il codice di spiegazione.

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 come si sarà finire a scrivere molto codice che coinvolge con i puntatori, che sono soggetti a perdite di memoria e non dimenticare di chiamare i distruttori. E per evitare questo C ++ hanno costruttori di copia in cui verrà creato nuova memoria quando gli oggetti che contengono puntatori sono passato alla funzione argomenti che si fermerà la manipolazione dei dati di altri oggetti, Java non passa per valore e il valore è di riferimento, in modo che non richiedono costruttori di copia.

Ci sono tre metodi di passaggio di un oggetto a una funzione come parametro:

  1. Passo con riferimento
  2. passaggio per valore
  3. l'aggiunta costante nel parametro

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

Output:

  

dire che sono in somefunc
  Il valore del puntatore è -17.891.602
  Il valore della variabile è 4

Di seguito sono i modi per passare un argomenti / parametri di funzionare 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();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top