Differenza tra tipi stringa e char [] in C ++


126

Conosco un po 'di C e ora sto dando un'occhiata a C ++. Sono abituato ai caratteri char per gestire le stringhe C, ma mentre guardo il codice C ++ vedo che ci sono esempi che usano sia il tipo di stringa sia i caratteri char:

#include <iostream>
#include <string>
using namespace std;

int main () {
  string mystr;
  cout << "What's your name? ";
  getline (cin, mystr);
  cout << "Hello " << mystr << ".\n";
  cout << "What is your favorite team? ";
  getline (cin, mystr);
  cout << "I like " << mystr << " too!\n";
  return 0;
}

e

#include <iostream>
using namespace std;

int main () {
  char name[256], title[256];

  cout << "Enter your name: ";
  cin.getline (name,256);

  cout << "Enter your favourite movie: ";
  cin.getline (title,256);

  cout << name << "'s favourite movie is " << title;

  return 0;
}

(entrambi esempi da http://www.cplusplus.com )

Suppongo che questa sia una domanda ampiamente posta e risposta (ovvia?), Ma sarebbe bello se qualcuno potesse dirmi qual è esattamente la differenza tra quei due modi per gestire le stringhe in C ++ (prestazioni, integrazione API, il modo in cui ognuna è meglio, ...).

Grazie.


Risposte:


187

Un array di caratteri è proprio questo: un array di caratteri:

  • Se allocato in pila (come nel tuo esempio), occuperà sempre ad es. 256 byte, indipendentemente dalla lunghezza del testo in esso contenuto
  • Se allocato sull'heap (usando malloc () o new char []), sei responsabile del rilascio della memoria in seguito e avrai sempre il sovraccarico di un'allocazione dell'heap.
  • Se si copia un testo di oltre 256 caratteri nell'array, si potrebbe verificare un arresto anomalo, produrre messaggi di asserzione brutti o causare comportamenti inspiegabili (errati) da qualche altra parte nel programma.
  • Per determinare la lunghezza del testo, l'array deve essere scansionato, carattere per carattere, per un carattere \ 0.

Una stringa è una classe che contiene un array di caratteri, ma lo gestisce automaticamente per te. La maggior parte delle implementazioni di stringhe ha un array incorporato di 16 caratteri (quindi le stringhe brevi non frammentano l'heap) e usano l'heap per stringhe più lunghe.

Puoi accedere all'array di caratteri di una stringa in questo modo:

std::string myString = "Hello World";
const char *myStringChars = myString.c_str();

Le stringhe C ++ possono contenere caratteri incorporati \ 0, conoscerne la lunghezza senza contare, sono più veloci di array di caratteri allocati in heap per i brevi testi e ti proteggono dai sovraccarichi del buffer. Inoltre sono più leggibili e più facili da usare.


Tuttavia, le stringhe C ++ non sono (molto) adatte per l'uso oltre i confini della DLL, poiché ciò richiederebbe a qualsiasi utente di tale funzione DLL di assicurarsi di utilizzare esattamente lo stesso compilatore e l'implementazione del runtime C ++, per non rischiare che la sua classe di stringa si comporti in modo diverso.

Normalmente, una classe di stringa rilascerebbe anche la sua memoria heap sull'heap chiamante, quindi sarà in grado di liberare nuovamente memoria solo se si utilizza una versione condivisa (.dll o .so) del runtime.

In breve: utilizzare le stringhe C ++ in tutte le funzioni e i metodi interni. Se mai scrivi una .dll o .so, usa le stringhe C nelle tue funzioni pubbliche (dll / so-Exposed).


4
Inoltre, le stringhe hanno un sacco di funzioni di supporto che possono essere davvero pulite.
Håkon,

1
Non credo ai bit sui limiti delle DLL. In circostanze molto particolari potrebbe potenzialmente rompersi ((una DLL si collega staticamente a una versione diversa del runtime rispetto a quella utilizzata da altre DLL) e probabilmente in questo caso accaderebbero cose peggiori prima) ma nel caso generale in cui tutti utilizzano l'impostazione predefinita versione condivisa del runtime standard (impostazione predefinita) ciò non accadrà.
Martin York,

2
Esempio: distribuisci i binari compilati VC2008SP1 di una libreria pubblica chiamata libfoo, che ha una std :: string e nella sua API pubblica. Ora qualcuno scarica libfoo.dll ed esegue una build di debug. La sua stringa std :: string potrebbe contenere alcuni campi di debug aggiuntivi, causando lo spostamento dell'offset del puntatore per le stringhe dinamiche.
Cygon

2
Esempio 2: nel 2010, qualcuno scarica libfoo.dll e lo utilizza nella sua applicazione VC2010. Il suo codice carica MSVCP100.dll e il tuo libfoo.dll carica ancora MSVCP90.dll -> ottieni due heap -> la memoria non può essere liberata, errori di asserzione in modalità debug se libfoo modifica il riferimento di stringa e passa uno std :: string con un nuovo puntatore indietro.
Cygon

1
Continuerò con "In breve: usa le stringhe C ++ in tutte le tue funzioni e metodi interni". Sto cercando di capire i tuoi esempi maid my pop pop.
Stephen,

12

Arkaitz ha ragione che stringè un tipo gestito. Ciò significa per te che non devi mai preoccuparti della lunghezza della stringa, né devi preoccuparti di liberare o riallocare la memoria della stringa.

D'altra parte, la char[]notazione nel caso precedente ha limitato il buffer dei caratteri a esattamente 256 caratteri. Se hai provato a scrivere più di 256 caratteri in quel buffer, nella migliore delle ipotesi sovrascriverai altra memoria che il tuo programma "possiede". Nel peggiore dei casi, proverai a sovrascrivere la memoria che non possiedi e il tuo sistema operativo ucciderà il tuo programma sul posto.

Linea di fondo? Le stringhe sono molto più facili da programmare, i caratteri sono molto più efficienti per il computer.


4
Nel peggiore dei casi, altre persone sovrascriveranno la memoria ed eseguiranno codice dannoso sul tuo computer. Vedi anche buffer overflow .
David Johnstone,

6

Bene, il tipo di stringa è una classe completamente gestita per le stringhe di caratteri, mentre char [] è ancora quello che era in C, un array di byte che rappresenta una stringa di caratteri per te.

In termini di API e libreria standard, tutto è implementato in termini di stringhe e non di char [], ma ci sono ancora molte funzioni dal libc che ricevono char [], quindi potrebbe essere necessario usarlo per quelli, a parte quello usa sempre std :: string.

In termini di efficienza, ovviamente, un buffer grezzo di memoria non gestita sarà quasi sempre più veloce per molte cose, ma prendi in considerazione il confronto delle stringhe, ad esempio, std :: string ha sempre le dimensioni per controllarlo prima, mentre con char [] tu è necessario confrontare carattere per carattere.


5

Personalmente non vedo alcun motivo per cui si vorrebbe usare char * o char [] tranne la compatibilità con il vecchio codice. std :: string non è più lento dell'uso di una c-string, tranne per il fatto che gestirà la riassegnazione per te. È possibile impostare le dimensioni quando lo si crea e quindi evitare di riassegnare se lo si desidera. Il suo operatore di indicizzazione ([]) fornisce un accesso costante al tempo (ed è in ogni senso della parola esattamente la stessa cosa che usare un indicizzatore c-string). L'uso del metodo at ti dà anche limiti di sicurezza controllata, qualcosa che non ottieni con le stringhe c, a meno che tu non lo scriva. Il compilatore ottimizzerà molto spesso l'utilizzo dell'indicizzatore in modalità di rilascio. È facile scherzare con le stringhe c; cose come delete vs delete [], sicurezza delle eccezioni, anche come riallocare una stringa c.

E quando hai a che fare con concetti avanzati come avere stringhe COW e non COW per MT ecc., Avrai bisogno di std :: string.

Se sei preoccupato per le copie, fintanto che usi riferimenti e riferimenti const ovunque puoi, non avrai alcun overhead a causa delle copie, ed è la stessa cosa che faresti con la c-stringa.


+1 Anche se non hai preso in considerazione problemi di implementazione come la compatibilità DLL, hai ottenuto COW.

che dire so che il mio array di caratteri in 12 byte? Se creo un'istanza di una stringa per questo potrebbe non essere davvero efficiente, giusto?
David 天宇 Wong,

@David: se hai un codice estremamente sensibile, allora sì. È possibile considerare la chiamata ctor std :: string come overhead oltre all'inizializzazione dei membri std :: string. Ma ricorda che l'ottimizzazione prematura ha reso inutilmente molte basi di codice in stile C, quindi fai attenzione.
Abhay,

1

Le stringhe hanno funzioni di supporto e gestiscono automaticamente i array di caratteri. È possibile concatenare le stringhe, per un array di caratteri è necessario copiarlo in un nuovo array, le stringhe possono modificarne la lunghezza in fase di esecuzione. Un array di caratteri è più difficile da gestire rispetto a una stringa e alcune funzioni possono accettare solo una stringa come input, richiedendo la conversione dell'array in una stringa. È meglio usare le stringhe, sono state create in modo da non dover usare le matrici. Se le matrici fossero oggettivamente migliori non avremmo le stringhe.


0

Pensa a (char *) come string.begin (). La differenza essenziale è che (char *) è un iteratore e std :: string è un contenitore. Se ti attieni alle stringhe di base un (char *) ti darà ciò che fa std :: string :: iterator. È possibile utilizzare (char *) quando si desidera il vantaggio di un iteratore e anche la compatibilità con C, ma questa è l'eccezione e non la regola. Come sempre, fai attenzione all'invalidazione dell'iteratore. Quando le persone dicono (char *) non è sicuro, questo è ciò che significano. È sicuro come qualsiasi altro iteratore C ++.


0

Una delle differenze è la terminazione Null (\ 0).

In C e C ++, char * o char [] prenderà un puntatore a un singolo carattere come parametro e seguirà la memoria fino al raggiungimento di un valore di memoria 0 (spesso chiamato terminatore null).

Le stringhe C ++ possono contenere caratteri incorporati \ 0, conoscerne la lunghezza senza contare.

#include<stdio.h>
#include<string.h>
#include<iostream>

using namespace std;

void NullTerminatedString(string str){
   int NUll_term = 3;
   str[NUll_term] = '\0';       // specific character is kept as NULL in string
   cout << str << endl <<endl <<endl;
}

void NullTerminatedChar(char *str){
   int NUll_term = 3;
   str[NUll_term] = 0;     // from specific, all the character are removed 
   cout << str << endl;
}

int main(){
  string str = "Feels Happy";
  printf("string = %s\n", str.c_str());
  printf("strlen = %d\n", strlen(str.c_str()));  
  printf("size = %d\n", str.size());  
  printf("sizeof = %d\n", sizeof(str)); // sizeof std::string class  and compiler dependent
  NullTerminatedString(str);


  char str1[12] = "Feels Happy";
  printf("char[] = %s\n", str1);
  printf("strlen = %d\n", strlen(str1));
  printf("sizeof = %d\n", sizeof(str1));    // sizeof char array
  NullTerminatedChar(str1);
  return 0;
}

Produzione:

strlen = 11
size = 11
sizeof = 32  
Fee s Happy


strlen = 11
sizeof = 12
Fee

"da specifico, tutti i caratteri vengono rimossi" no, non vengono "rimossi", la stampa di un puntatore a caratteri viene stampata solo fino al terminatore null. (dato che è l'unico modo in cui un carattere * conosce la fine) la classe stringa conosce la dimensione intera stessa, quindi la utilizza. se conosci le dimensioni del tuo carattere *, puoi anche stampare / utilizzare tutti i caratteri.
Pozza
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.