Perché il C ++ non supporta le funzioni di restituzione degli array?

Alcuni linguaggi consentono di dichiarare semplicemente una funzione che restituisce un array come una funzione normale, come Java:

public String[] funcarray() { String[] test = new String[]{"hi", "hello"}; return test; } 

Perché il C ++ non supporta qualcosa come int[] funcarray(){} ? Puoi restituire un array, ma è una vera seccatura fare una tale funzione. E inoltre, ho sentito da qualche parte che le stringhe sono solo matrici di caratteri. Quindi, se puoi restituire una stringa in C ++, perché non un array?

Scommetterei che per essere concisi, era semplicemente una decisione di progettazione. Più specificamente, se vuoi veramente sapere perché, devi lavorare da zero.

Pensiamo prima a C. Nel linguaggio C, c’è una chiara distinzione tra “passa per riferimento” e “passa per valore”. Per trattarlo con leggerezza, il nome di un array in C è in realtà solo un puntatore. A tutti gli effetti, la differenza (generalmente) si riduce all’assegnazione. Il codice

 int array[n]; 

creerebbe 4 * n byte di memoria (su un sistema a 32 bit) sullo stack correlato all’ambito di applicazione del blocco di codice che effettua la dichiarazione. A sua volta,

 int* array = (int*) malloc(sizeof(int)*n); 

creerebbe la stessa quantità di memoria, ma sull’heap. In questo caso, ciò che è in quella memoria non è legato all’ambito, solo il riferimento alla memoria è limitato dall’ambito. Ecco dove passare per valore e passare per riferimento entrare. Passare per valore, come probabilmente sapete, significa che quando qualcosa viene passato o restituito da una funzione, la “cosa” che viene superata è il risultato della valutazione della variabile. In altre parole,

 int n = 4; printf("%d", n); 

stamperà il numero 4 perché il costrutto n valuti a 4 (mi dispiace se questo è elementare, voglio solo coprire tutte le basi). Questo 4 non ha assolutamente alcun rapporto o relazione con lo spazio di memoria del tuo programma, è solo un valore letterale, e così una volta che lasci lo scope in cui quel 4 ha un contesto, lo perdi. Che ne dici di passare per riferimento? Il passaggio per riferimento non è diverso nel contesto di una funzione; si valuta semplicemente il costrutto che viene passato. L’unica differenza è che dopo aver valutato la “cosa” passata, si utilizza il risultato della valutazione come indirizzo di memoria. Una volta avevo un cinico istruttore CS che amava affermare che non esiste una cosa come passare per riferimento, solo un modo per trasmettere valori intelligenti. Davvero, ha ragione. Quindi ora pensiamo all’ambito in termini di una funzione. Fai finta di avere un tipo di ritorno dell’array:

 int[] foo(args){ result[n]; // Some code return result; } 

Il problema qui è che il risultato valuta l’indirizzo dello 0 ° elemento dell’array. Ma quando si tenta di accedere a questa memoria dall’esterno di questa funzione (tramite il valore di ritorno), si verifica un problema perché si sta tentando di accedere alla memoria che non si trova nell’ambito con cui si sta lavorando (lo stack della chiamata della funzione). Quindi il modo in cui ci aggiriamo con lo jiggery-pokery standard “passa per riferimento”:

 int* foo(args){ int* result = (int*) malloc(sizeof(int)*n)); // Some code return result; } 

Abbiamo ancora un indirizzo di memoria che punta allo 0 ° elemento della matrice, ma ora abbiamo accesso a quella memoria.

Qual è il mio punto? In Java, è comune affermare che “tutto è passato per valore”. Questo è vero. Lo stesso cinico istruttore di cui sopra aveva anche da dire su Java e OOP in generale: tutto è solo un puntatore. E ha anche ragione. Mentre tutto in Java è infatti passato per valore, quasi tutti questi valori sono in realtà degli indirizzi di memoria. Quindi, in Java, il linguaggio ti consente di restituire un array o una stringa, ma lo fa convertendola nella versione con i puntatori. Gestisce anche la tua memoria per te. E la gestione automatica della memoria, sebbene utile, non è efficiente.

Questo ci porta al C ++. L’intera ragione per cui il C ++ è stato inventato era perché Bjarne Stroustrup aveva fatto esperimenti con Simula (fondamentalmente l’OOPL originale) durante il suo dottorato, e pensava che fosse fantastico dal punto di vista concettuale, ma notò che si comportava in modo piuttosto terribile. E così ha iniziato a lavorare su ciò che è stato chiamato C with Classes, che è stato rinominato in C ++. In tal modo, il suo objective era quello di creare un linguaggio di programmazione che prendesse ALCUNE delle migliori caratteristiche di Simula ma rimanesse potente e veloce. Ha scelto di estendere C a causa della sua già leggendaria performance, e un compromesso è stato che ha scelto di non implementare la gestione automatica della memoria o la raccolta dei rifiuti su una scala così ampia come le altre OOPL. Restituire un array da una delle classi template funziona perché, beh, stai usando una class. Ma se vuoi restituire un array C, devi farlo nel modo C. In altre parole, C ++ supporta la restituzione di un array ESATTAMENTE nello stesso modo in cui lo fa Java; semplicemente non fa tutto il lavoro per te. Perché un tizio danese pensava che sarebbe stato troppo lento.

Il C ++ lo supporta – bene una specie di:

 vector< string> func() { vector res; res.push_back( "hello" ); res.push_back( "world" ); return res; } 

Persino C lo supporta:

 struct somearray { struct somestruct d[50]; }; struct somearray func() { struct somearray res; for( int i = 0; i < 50; ++i ) { res.d[i] = whatever; } // fill them all in return res; } 

Una std::string è una class ma quando dici una stringa intendi probabilmente un letterale. È ansible restituire un letterale in modo sicuro da una funzione, ma in realtà è ansible creare staticamente qualsiasi array e restituirlo da una funzione. Questo sarebbe thread-safe se fosse un array const (di sola lettura), che è il caso di stringhe letterali.

Tuttavia, l'array che si restituisce si degraderebbe a un puntatore, quindi non si sarebbe in grado di calcolare le sue dimensioni solo dal suo ritorno.

Restituire una matrice, se fosse ansible, dovrebbe essere la lunghezza fissa in primo luogo, dato che il compilatore ha bisogno di creare lo stack di chiamate, e quindi ha il problema che gli array non sono valori di l in modo da riceverlo nella funzione di chiamata dovrebbe usare una nuova variabile con l'inizializzazione, che è poco pratica. Restituire uno potrebbe essere impraticabile anche per lo stesso motivo, anche se potrebbero aver usato una notazione speciale per i valori di ritorno.

Ricorda che nei primi giorni di C tutte le variabili dovevano essere dichiarate all'inizio della funzione e non si poteva semplicemente dichiararle al primo utilizzo. Quindi era imansible al momento.

Hanno dato la soluzione alternativa di mettere la matrice in una struttura e questo è solo il modo in cui ora deve rimanere in C ++ perché utilizza la stessa convenzione di chiamata.

Nota: in lingue come Java, un array è una class. Ne crei uno con nuovo. Puoi riassegnarli (sono valori-l).

Le matrici in C (e in C ++ per retrocompatibilità) hanno una semantica speciale che differisce dal resto dei tipi. In particolare, mentre per il resto dei tipi, C ha solo semantica pass-by-value, nel caso degli array l’effetto della syntax del pass-by-value simula il pass-by-reference in modo strano:

In una firma di funzione, un argomento di tipo array di elementi N di tipo T viene convertito in puntatore a T. In una chiamata di funzione il passaggio di un array come argomento a una funzione decompone l’array in un puntatore al primo elemento e quel puntatore viene copiato nella funzione.

A causa di questo particolare trattamento per gli array – non possono essere passati per valore–, non possono essere restituiti in base al valore. In C puoi restituire un puntatore, e in C ++ puoi anche restituire un riferimento, ma l’array stesso non può essere allocato nello stack.

Se ci pensi, questo non è diverso dalla lingua che stai usando nella domanda, dato che la matrice è allocata dynamicmente e stai solo restituendo un puntatore / riferimento ad essa.

Il linguaggio C ++, d’altro canto, abilita diverse soluzioni a quel particolare problema, come l’uso di std::vector nello standard corrente (i contenuti sono allocati dynamicmente) o std::array nello standard imminente (i contenuti possono essere allocati nello stack , ma potrebbe avere un costo maggiore, poiché ogni elemento dovrà essere copiato in quei casi in cui la copia non può essere eliminata dal compilatore). In effetti, è ansible utilizzare lo stesso tipo di approccio con lo standard corrente utilizzando librerie off-the-shelf come boost::array .

“Non è ansible restituire un array dalla funzione perché tale array sarebbe dichiarato all’interno della funzione e la sua posizione sarebbe quindi il frame dello stack, tuttavia lo stack frame viene cancellato quando si esce dalla funzione. Le funzioni devono copiare il valore di ritorno dallo stack frame per restituire posizione, e questo non è ansible con gli array. ”

Da una discussione qui:

http://forum.codecall.net/cc/32457-function-return-array-c.html

Altri hanno detto che in C ++, un vettore uso <> invece degli array ereditati da C.

Quindi, perché C ++ non consente di restituire array C? Perché C non lo fa.

Perché C non lo fa? Perché C si è evoluto da B, un linguaggio non tipizzato in cui restituire un array non ha alcun senso. Quando si aggiungono i tipi a B, sarebbe stato significativo rendere ansible la restituzione di un array ma ciò non è stato fatto per mantenere validi alcuni idiomi B e facilitare la conversione dei programmi da B a C. E da allora, la possibilità di rendere i C array più utilizzabili come sempre rifiutati (e anche di più, nemmeno considerati) in quanto infrangerebbe troppo il codice esistente.

È ansible restituire un puntatore all’array. Fai solo attenzione a liberare la memoria più tardi.

 public std::string* funcarray() { std::string* test = new std::string[2]; test[0] = "hi"; test[1] = "hello"; return test; } // somewhere else: std::string* arr = funcarray(); std::cout << arr[0] << " MisterSir" << std::endl; delete[] arr; 

Oppure puoi semplicemente usare uno dei contenitori nello spazio dei nomi std, come std :: vector.

“Perché C ++ non supporta qualcosa di simile”: Perché non avrebbe alcun senso. Nei linguaggi di riferimento come JAVA o PHP, la gestione della memoria è basata sulla garbage collection. Le parti della memoria che non hanno riferimenti (nessuna variabile nel tuo programma punta ad essa) viene automaticamente liberata. In questo contesto è ansible allocare memoria e passare il riferimento in modo affettuoso.

Il codice C ++ verrà tradotto in codice macchina e non vi è alcun GC definito in esso. Quindi in C e C ++ c’è un forte senso di proprietà dei blocchi di memoria. Devi sapere se il puntatore che vai è il tuo da liberare in qualsiasi momento (in effetti lo puoi liberare dopo l’uso), o hai un puntatore a una porzione di memoria condivisa, che è un assoluto no-no da liberare.

In questo ambiente non vinceresti nulla con il creta di infinite copie di un array ogni volta che passa e da una funzione. È un compito molto più complesso gestire i tuoi array di dati in linguaggi c-like. Non esiste una soluzione valida per tutti e devi sapere quando liberare memoria.

Un array restituito da una funzione dovrebbe sempre essere una copia (la tua da liberare) o devi farne una copia? Per chi vincerebbe ottenendo un array insted di un puntatore a un array?

Restituisce uno std::vector<> invece di un array. In generale, gli array non funzionano bene con C ++ e generalmente dovrebbero essere evitati.

Inoltre, il tipo di dati string non è solo un array di caratteri, sebbene sia una “stringa quotata”. La string gestisce un array di caratteri e puoi accedervi con .c_str() , ma c’è di più in una string .

Dai un’occhiata qui. Davvero utile

  • Come posso restituire un array da una funzione?
  • C ++ Restituzione dell’array multidimensionale dalla funzione
  • Restituisce array 2d dalla funzione in C ++