Diferencia entre los tipos de cadena y char [] en C ++

126

Sé un poco de C y ahora estoy echando un vistazo a C ++. Estoy acostumbrado a las matrices de caracteres para tratar con cadenas C, pero mientras miro el código C ++ veo que hay ejemplos que utilizan tanto el tipo de cadena como las matrices de caracteres:

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

y

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

(ambos ejemplos de http://www.cplusplus.com )

Supongo que esta es una pregunta ampliamente formulada y respondida (¿obvia?), Pero sería bueno si alguien pudiera decirme cuál es exactamente la diferencia entre esas dos formas de manejar cadenas en C ++ (rendimiento, integración API, la forma en que cada una es mejor, ...).

Gracias.

ramosg
fuente
Esto puede ayudar: C ++ char * vs std :: string
Wael Dalloul

Respuestas:

187

Una matriz de caracteres es solo eso: una matriz de caracteres:

  • Si se asigna en la pila (como en su ejemplo), siempre ocupará, por ejemplo. 256 bytes, independientemente de la longitud del texto que contiene
  • Si se asigna en el montón (usando malloc () o nuevo char []), usted es responsable de liberar la memoria después y siempre tendrá la sobrecarga de una asignación de montón.
  • Si copia un texto de más de 256 caracteres en la matriz, podría bloquearse, producir mensajes de afirmación feos o causar un comportamiento inexplicable (incorrecto) en otro lugar de su programa.
  • Para determinar la longitud del texto, se debe escanear la matriz, carácter por carácter, para obtener un carácter \ 0.

Una cadena es una clase que contiene una matriz de caracteres, pero la administra automáticamente por usted. La mayoría de las implementaciones de cadenas tienen una matriz integrada de 16 caracteres (por lo que las cadenas cortas no fragmentan el montón) y usan el montón para cadenas más largas.

Puede acceder a la matriz de caracteres de una cadena de esta manera:

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

Las cadenas de C ++ pueden contener caracteres \ 0 incrustados, conocer su longitud sin contar, son más rápidas que las matrices de caracteres asignadas en el montón para textos cortos y lo protegen de desbordamientos del búfer. Además, son más legibles y fáciles de usar.


Sin embargo, las cadenas de C ++ no son (muy) adecuadas para su uso a través de los límites de DLL, ya que esto requeriría que cualquier usuario de dicha función de DLL se asegure de que está usando exactamente el mismo compilador y la implementación de tiempo de ejecución de C ++, para no arriesgarse a que su clase de cadena se comporte de manera diferente.

Normalmente, una clase de cadena también liberaría su memoria de almacenamiento dinámico en el almacenamiento dinámico de llamadas, por lo que solo podrá liberar memoria nuevamente si está utilizando una versión compartida (.dll o .so) del tiempo de ejecución.

En resumen: use cadenas de C ++ en todas sus funciones y métodos internos. Si alguna vez escribe un .dll o .so, use cadenas C en sus funciones públicas (dll / so-expuestos).

Cygon
fuente
44
Además, las cadenas tienen un montón de funciones auxiliares que pueden ser realmente ordenadas.
Håkon
1
No creo nada acerca de los límites de DLL. En circunstancias muy especiales, podría romperse ((una DLL está enlazando estáticamente con una versión diferente del tiempo de ejecución que la utilizada por otras DLL) y cosas peores probablemente sucederían primero en estas situaciones), pero en el caso general en el que todos usan la predeterminada versión compartida del tiempo de ejecución estándar (el valor predeterminado) esto no sucederá.
Martin York
2
Ejemplo: distribuye binarios compilados con VC2008SP1 de una biblioteca pública llamada libfoo, que tiene un std :: string & en su API pública. Ahora alguien descarga su libfoo.dll y realiza una compilación de depuración. Su std :: string podría tener algunos campos de depuración adicionales, causando el desplazamiento del puntero para que se muevan las cadenas dinámicas.
Cygon
2
Ejemplo 2: en 2010, alguien descarga su libfoo.dll y lo usa en su aplicación integrada VC2010. Su código carga MSVCP100.dll y su libfoo.dll aún carga MSVCP90.dll -> obtiene dos montones -> la memoria no puede liberarse, errores de aserción en modo de depuración si libfoo modifica la referencia de cadena y entrega una cadena std :: con una nueva puntero de vuelta.
Cygon
1
Solo voy a seguir con "En resumen: use cadenas de C ++ en todas sus funciones y métodos internos". Tratando de entender sus ejemplos, mucama mi cerebro pop.
Stephen
12

Arkaitz es correcto que stringes un tipo administrado. Lo que esto significa para usted es que nunca debe preocuparse por la longitud de la cadena ni preocuparse por liberar o reasignar la memoria de la cadena.

Por otro lado, la char[]notación en el caso anterior ha restringido el búfer de caracteres a exactamente 256 caracteres. Si trató de escribir más de 256 caracteres en ese búfer, en el mejor de los casos sobrescribirá otra memoria que "posee" su programa. En el peor de los casos, intentará sobrescribir la memoria que no posee y su sistema operativo matará su programa en el acto.

¿Línea de fondo? Las cadenas son mucho más amigables para los programadores, los char [] s son mucho más eficientes para la computadora.

Mark Rushakoff
fuente
44
En el peor de los casos, otras personas sobrescribirán la memoria y ejecutarán código malicioso en su computadora. Ver también desbordamiento de búfer .
David Johnstone
6

Bueno, el tipo de cadena es una clase completamente administrada para cadenas de caracteres, mientras que char [] sigue siendo lo que era en C, una matriz de bytes que representa una cadena de caracteres para usted.

En términos de API y biblioteca estándar, todo se implementa en términos de cadenas y no char [], pero todavía hay muchas funciones de la biblioteca que reciben char [], por lo que es posible que deba usarlo para ellos, aparte de eso, lo haría siempre use std :: string.

En términos de eficiencia, por supuesto, un búfer sin procesar de memoria no administrada casi siempre será más rápido para muchas cosas, pero tenga en cuenta la comparación de cadenas, por ejemplo, std :: string siempre tiene el tamaño para verificarlo primero, mientras que con char [] usted necesita comparar personaje por personaje.

Arkaitz Jiménez
fuente
5

Personalmente, no veo ninguna razón por la que a uno le gustaría usar char * o char [], excepto por la compatibilidad con el código anterior. std :: string no es más lento que usar una c-string, excepto que manejará la reasignación por usted. Puede establecer su tamaño cuando lo crea, y así evitar la reasignación si lo desea. Su operador de indexación ([]) proporciona acceso de tiempo constante (y en todos los sentidos de la palabra es exactamente lo mismo que usar un indexador de cadena c). El uso del método at también proporciona seguridad comprobada en los límites, algo que no se obtiene con las cadenas en C, a menos que lo escriba. Su compilador a menudo optimizará el uso del indexador en modo de lanzamiento. Es fácil perder el tiempo con cuerdas en C; cosas como eliminar vs eliminar [], seguridad de excepción, incluso cómo reasignar una cadena en C.

Y cuando tenga que lidiar con conceptos avanzados como tener cadenas COW y no COW para MT, etc., necesitará std :: string.

Si está preocupado por las copias, siempre y cuando use referencias y referencias constantes siempre que pueda, no tendrá gastos generales debido a las copias, y es lo mismo que haría con la cadena c.

Abhay
fuente
+1 Aunque no consideró problemas de implementación como la compatibilidad con DLL, obtuvo COW.
¿Qué pasa si sé que mi matriz de caracteres en 12 bytes? Si instancia una cadena para eso, podría no ser realmente eficiente, ¿verdad?
David 天宇 Wong
@David: Si tienes un código extremadamente sensible al rendimiento, entonces sí. Puede considerar la llamada std :: string ctor como una sobrecarga además de la inicialización de los miembros std :: string. Pero recuerde que la optimización prematura ha creado una gran cantidad de bases de código innecesariamente estilo C, así que tenga cuidado.
Abhay
1

Las cadenas tienen funciones auxiliares y administran matrices de caracteres automáticamente. Puede concatenar cadenas, para una matriz de caracteres necesitaría copiarla en una nueva matriz, las cadenas pueden cambiar su longitud en tiempo de ejecución. Una matriz de caracteres es más difícil de administrar que una cadena y ciertas funciones solo pueden aceptar una cadena como entrada, lo que requiere que convierta la matriz en una cadena. Es mejor usar cadenas, se hicieron para que no tenga que usar matrices. Si las matrices fueran objetivamente mejores, no tendríamos cadenas.


fuente
0

Piense en (char *) como string.begin (). La diferencia esencial es que (char *) es un iterador y std :: string es un contenedor. Si se apega a las cadenas básicas, a (char *) le dará lo que hace std :: string :: iterator. Puede usar (char *) cuando desee el beneficio de un iterador y también la compatibilidad con C, pero esa es la excepción y no la regla. Como siempre, tenga cuidado con la invalidación del iterador. Cuando la gente dice (char *) no es seguro, esto es lo que quieren decir. Es tan seguro como cualquier otro iterador de C ++.

Samuel Danielson
fuente
0

Una de las diferencias es la terminación nula (\ 0).

En C y C ++, char * o char [] tomará un puntero a un único char como parámetro y rastreará a lo largo de la memoria hasta que se alcance un valor de memoria 0 (a menudo llamado terminador nulo).

Las cadenas de C ++ pueden contener caracteres \ 0 incrustados, conozca su longitud sin contar.

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

Salida:

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


strlen = 11
sizeof = 12
Fee
Eswaran Pandi
fuente
"desde específico, todos los caracteres se eliminan" no, no se "eliminan", imprimir un puntero de caracteres solo imprime hasta el terminador nulo. (dado que esa es la única forma en que un char * conoce el final) la clase de cadena conoce el tamaño completo en sí mismo, por lo que solo usa eso. Si conoce el tamaño de su carácter *, también puede imprimir / utilizar todos los caracteres.
Charco