tipos de variables de cadena comunes de visual c
Una de las pautas completas para cadenas C: codificación de caracteres Win32
Autor original: Michael Dunn
Traducción: Chengjie Sun
Original fuente :CodeProject: La guía completa de cadenas C, Parte I
Introducción
No hay duda de que todos hemos visto varios tipos como TCHAR, std::string, BSTR, etc. . tipos de cadenas y esas extrañas macros que comienzan con _tcs. Es posible que estés mirando tu monitor y preocupándote. Esta guía resumirá el propósito de introducir varios tipos de caracteres, mostrará algunos usos simples y le indicará cómo convertir entre varios tipos de cadenas cuando sea necesario.
En la primera parte, presentaremos 3 tipos de codificación de caracteres. Es importante comprender cómo funcionan los distintos modos de codificación. Deberías leer esta sección incluso si ya sabes que una cadena es una matriz de caracteres. Una vez que comprenda esto, comprenderá claramente la relación entre los distintos tipos de cadenas.
En la segunda parte, hablaremos sobre la clase de cadena por separado, cómo usarla y lograr la conversión entre ellas.
Base de caracteres: ASCII, DBCS, Unicode
Todas las clases de cadenas se basan en cadenas de estilo C. Las cadenas de estilo C son matrices de caracteres. Así que primero introduzcamos los tipos de personajes. Hay 3 modos de codificación correspondientes a 3 tipos de caracteres. El primer tipo de codificación es el juego de caracteres de un solo byte o SBCS. En este modo de codificación, todos los caracteres están representados por un solo byte. ASCII es SBCS. Se utiliza un byte de 0 para marcar el final de la cadena SBCS.
El segundo modo de codificación es el juego de caracteres multibyte (juego de caracteres multibyte o MBCS). Una codificación MBCS contiene algunos caracteres que tienen una longitud de un byte y otros caracteres que tienen más de un byte. MBCS utilizado en Windows contiene dos tipos de caracteres, caracteres de un solo byte y caracteres de doble byte. Dado que la mayoría de los caracteres multibyte utilizados en Windows tienen una longitud de dos bytes, MBCS a menudo se reemplaza por DBCS.
En el modo de codificación DBCS, ciertos valores se reservan para indicar que forman parte de un carácter de doble byte. Por ejemplo, en la codificación Shift-JIS (un modo de codificación japonés comúnmente utilizado), los valores entre 0x81-0x9f y 0xe0-oxfc significan "Este es un carácter de doble byte y la siguiente subsección es parte de este carácter". Los valores se denominan "bytes iniciales" y todos son mayores que 0x7f. El byte que sigue a una subsección del byte inicial se denomina "byte final". En DBCS, el byte de seguimiento puede ser cualquier valor distinto de cero. Al igual que SBCS, la marca de fin de una cadena DBCS también es una representación de un solo byte de 0.
El tercer modo de codificación es Unicode. Unicode es un modo de codificación en el que todos los caracteres se codifican utilizando dos bytes. Los caracteres Unicode a veces se denominan caracteres anchos porque son más anchos (utilizan más espacio de almacenamiento) que los caracteres de un solo subbyte. Tenga en cuenta que Unicode no puede considerarse MBCS. MBCS es único porque sus caracteres están codificados utilizando bytes de diferentes longitudes. Las cadenas Unicode utilizan dos bytes de 0 como indicador de fin.
Los caracteres de un solo byte incluyen el alfabeto latino, caracteres acentuados y caracteres gráficos definidos por el estándar ASCII y el sistema operativo DOS. Los caracteres de doble byte se utilizan para representar idiomas de Asia Oriental y Medio Oriente. Unicode se utiliza en los sistemas operativos COM y Windows NT.
Ya debes estar familiarizado con los caracteres de un solo byte. Cuando usas char, estás tratando con caracteres de un solo byte. Los caracteres de doble byte también se manipulan utilizando el tipo char (ésta es una de las muchas rarezas que veremos sobre los caracteres de doble byte). Los caracteres Unicode están representados por wchar_t. Los caracteres Unicode y las constantes de cadena están representados por el prefijo L. Por ejemplo: wchar_t wch = L''1''; // 2 bytes, 0x0031wchar_t* wsz = L"Hola"; // 12 bytes, 6 caracteres anchos Cómo se almacenan los caracteres en la memoria
Un solo byte cadena: cada carácter ocupa un byte y se almacena en secuencia, terminando en 0 representado por un solo byte. Por ejemplo. La forma de almacenamiento de "Bob" es la siguiente: 426F6200BobBOSUforma de almacenamiento Unicode, L"Bob"42 006F 0062 0000 00BobBOS utiliza dos bytes de 0 como marca final.
A primera vista, las cadenas DBCS se parecen mucho a las cadenas SBCS, pero veremos en un momento las sutilezas de las cadenas DBCS que hacen que sea más fácil recorrer una cadena usando funciones de manipulación de cadenas y punteros de caracteres permanentes. producir resultados inesperados. La forma de almacenamiento de la cadena " " ("nihongo") en la memoria es la siguiente (LB y TB se utilizan para representar el byte inicial y el byte final respectivamente) 93 FA96 7B8C EA00LB TBLB TBLB TBEOSEOS Vale la pena señalar que el valor de "ni" no se puede interpretar como el valor de WORD 0xfa93, pero debe verse como dos valores 93 y fa en este orden codificados como "ni".
Usar funciones de procesamiento de cadenas
Todos hemos visto las funciones de cadenas en lenguaje C, strcpy(), sprintf(), atoll(), etc. Estas cadenas sólo deben usarse para manejar cadenas de caracteres de un solo byte. La biblioteca estándar también proporciona funciones que sólo son aplicables a cadenas de tipo Unicode, como wcscpy(), swprintf(), wtol(), etc.
Microsoft también ha añadido una versión para operar cadenas DBCS en su CRT (biblioteca de tiempo de ejecución C). La función Str***() tiene una versión DBCS con el nombre correspondiente _mbs***(). Si espera encontrar cadenas DBCS (es posible si su software se instalará en un país que usa codificación DBCS, como China, Japón, etc.), debe usar la función _mbs***(), porque También puede manejar cadenas SBCS. (Una cadena DBCS también puede contener caracteres de un solo byte, razón por la cual la función _mbs***() también puede manejar cadenas SBCS)
Veamos una cadena típica. Aclaremos por qué hay diferentes versiones de procesamiento de cadenas. Se necesitan funciones. Todavía usamos la cadena Unicode anterior L"Bob": 42 006F 0062 0000 00BobBOS Debido a que la CPU x86 es little-endian, el valor 0x0042 se almacena en la memoria como 42 00. ¿Puedes ver qué pasaría si esta cadena se pasara a la función strlen()? Primero verá el primer byte 42, luego 00 y 00 es el final de la cadena, por lo que strlen() devolverá 1. Si pasa "Bob" a wcslen(), obtendrá peores resultados.
wcslen() verá 0x6f42, luego 0x0062 y luego leerá hasta el final de su búfer hasta que encuentre el indicador de fin 00 00 o provoque un GPF.
Hasta ahora, hemos discutido el uso de str***() y wcs***() y las diferencias entre ellos. ¿Cuál es la diferencia entre Str***() y _mbs**()? Comprender la diferencia entre ellos es importante para utilizar el método correcto para recorrer cadenas DBCS. A continuación, primero introduciremos el recorrido de cadenas y luego volveremos a la diferencia entre str***() y _mbs***().
Atravesar e indexar cadenas correctamente
Debido a que la mayoría de nosotros crecimos usando cadenas SBCS, a menudo usamos punteros cuando recorremos cadenas y operaciones. También utilizamos la representación de subíndices de matriz para manipular caracteres en cadenas. Estos dos métodos se utilizan para cadenas SBCS y Unicode, porque los caracteres que contienen tienen el mismo ancho y el compilador puede devolver correctamente los caracteres que necesitamos.
Sin embargo, cuando se trata de cadenas DBCS, debemos abandonar estos hábitos. Hay dos reglas para usar punteros para atravesar cadenas DBCS. Si viola estas dos reglas, su programa tendrá errores relacionados con DBCS. 1. Durante el recorrido hacia adelante, no utilice la operación a menos que verifique el byte inicial cada vez; Nunca utilice la operación - para retroceder. Abordemos primero la regla 2, porque es fácil encontrar un ejemplo real de código que la viole. Suponga que tiene un programa que guarda un archivo de configuración en su propio directorio y guarda el directorio de instalación en el registro. En tiempo de ejecución, lee el directorio de instalación del registro, sintetiza el nombre del archivo de configuración y luego lee el archivo. Supongamos que su directorio de instalación es C:\Program Files\MyCoolApp, entonces el nombre de su archivo sintetizado debe ser C:\Program Files\MyCoolApp\config.bin. Cuando lo pruebas, descubres que el programa funciona bien.
Ahora, imagina que tu código para sintetizar el nombre del archivo podría verse así: bool GetConfigFileName (char* pszName, size_t nBuffSize){ char szConfigFilename[MAX_PATH] // Leer el directorio de instalación desde el registro... Asumiremos que tiene éxito. // Agregue una barra invertida si no estaba presente en el valor del registro. // Primero, obtenga un puntero al cero final. pLastChar = strchr (szConfigFilename, ''\0'). ' ); // Ahora retroceda un carácter. pLastChar--; if ( *pLastChar != ''\\'' ) strcat (szConfigFilename, "\\" ); strcat (szConfigFilename, "config.bin"); // Si el búfer de la persona que llama es lo suficientemente grande, devuelve el nombre del archivo. if (szConfigFilename) >= nBuffSize) return false { strcpy (pszName, szConfigFilename); return true; }} Este es un código muy robusto, sin embargo, fallará cuando encuentre caracteres DBCS. Echemos un vistazo a por qué. Supongamos que un usuario japonés usa su programa y lo instala en C:\. Así es como se almacena este nombre en la memoria: 433A5C83 8883 4583 5283 5C00 LB TBLB TBLB TBLB TB C:\EOS Cuando se utiliza GetConfigFileName() para comprobar el ''\\'' final, busca el último byte distinto de cero en el directorio de instalación el nombre es igual a ''\\'', por lo que no se agrega ningún ''\\'' nuevo. El resultado es que el código devuelve un nombre de archivo incorrecto.
¿Qué salió mal? Mire los dos bytes de arriba que están resaltados en azul. El valor de la barra diagonal ''\\'' es 0x5c. El valor de '''' es 83 5c. El código anterior lee incorrectamente un byte de seguimiento como un carácter.
El método de recorrido hacia atrás correcto es utilizar una función que reconozca los caracteres DBCS para mover el puntero el número correcto de bytes. A continuación se muestra el código correcto.
(El lugar donde se mueve el puntero está marcado en rojo) bool FixGetConfigFileName (char* pszName, size_t nBuffSize){ char szConfigFilename[MAX_PATH] // Leer el directorio de instalación del registro... asumiremos que se realizó correctamente // Agregar. en una barra invertida si no estaba presente en el valor del registro. // Primero, obtenga un puntero al cero final. char* pLastChar = _mbschr (szConfigFilename, ''\0''); carácter de doble byte. pLastChar = CharPrev (szConfigFilename, pLastChar); if (*pLastChar! = ''\\'') _mbscat (szConfigFilename, "\\"); szConfigFilename, " config.bin" ); // Si el búfer de la persona que llama es lo suficientemente grande, devuelve el nombre del archivo. if ( _mbslen ( szInstallDir ) gt; = nBuffSize ) return false; true; } } La función anterior utiliza la API CharPrev() para mover pLastChar hacia atrás un carácter, que puede tener dos bytes de longitud. En esta versión, la condición if funciona correctamente porque el byte inicial nunca es igual a 0x5c.
Imaginemos una situación en la que se viola la Regla 1. Por ejemplo, es posible que desee detectar si un nombre de archivo ingresado por un usuario aparece varias veces con '':''. Si, en lugar de utilizar CharNext(), utiliza el operador para iterar sobre la cadena, puede emitir advertencias de error incorrectas si hay un byte de seguimiento cuyo valor es igual a '':''.
Relevante para la regla 2 con respecto a la indexación de cadenas: 2a. Nunca utilices la resta para obtener el índice de una cadena. El código que viola esta regla es muy similar al código que viola la Regla 2. Por ejemplo, char* pLastChar = amp; szConfigFilename [strlen(szConfigFilename) - 1];
Volviendo a la diferencia entre str***() y _mbs***()
Ahora, debemos tener claro por qué la función _mbs***() es necesaria de . La función Str***() no considera caracteres DBCS en absoluto, mientras que _mbs***() sí lo hace.
Si llama a strrchr("C:\\", ''\\''), el resultado devuelto puede ser incorrecto; sin embargo, _mbsrchr() reconocerá el último carácter de doble byte y devolverá un puntero a verdadero' '\\' 'puntero.
El último punto sobre las funciones de cadena: las funciones str***() y _mbs***() creen que la longitud de la cadena se calcula en caracteres. Entonces, si una cadena contiene 3 caracteres de doble byte, _mbslen() devolverá 6. La longitud devuelta por la función Unicode se calcula como wchar_t. Por ejemplo, wcslen(L"Bob") devuelve 3.
MBCS y Unicode en la API de Win32
Dos conjuntos de API:
Aunque quizás nunca lo hayas notado, todas las funciones relacionadas con cadenas en Win32 Tanto la API como El mensaje tiene dos versiones. Una versión acepta cadenas MBCS, la otra acepta cadenas Unicode. Por ejemplo, no existe ninguna API SetWindowText(). En su lugar, existen SetWindowTextA() y SetWindowTextW(). El sufijo A indica que esta es la función MBCS y el sufijo W indica que esta es la versión Unicode de la función.
Cuando creas un programa de Windows, puedes elegir si deseas utilizar MBCS o API Unicode. Si ha utilizado el Asistente de VC y no ha cambiado la configuración de preprocesamiento, significa que está utilizando la versión MBCS. Entonces, dado que no existe una API SetWindowText(), ¿por qué podemos usarla? El archivo de encabezado winuser.h contiene algunas macros, como: BOOL WINAPI SetWindowTextA (HWND hWnd, LPCSTR lpString); BOOL WINAPI SetWindowTextW (HWND hWnd, LPCWSTR lpString); Al crear un programa utilizando las API de MBCS, UNICODE no está definido, por lo que el preprocesador ve: #define SetWindowText SetWindowTextA Esta definición de macro convierte todas las llamadas a SetWindowText en la función API real SetWindowTextA. (Por supuesto, puede llamar a SetWindowTextA() o SetWindowTextW() directamente, aunque no es necesario hacerlo).
Entonces, si desea cambiar la función API predeterminada a la versión Unicode, puede pre- En la configuración del procesador, eliminar _MBCS de la lista de macros predefinidas y luego agregar UNICODE y _UNICODE. (Debe definir ambos, porque diferentes archivos de encabezado pueden usar diferentes macros). Sin embargo, si usa char para definir sus cadenas, se encontrará en una situación incómoda.
Considere el siguiente código: HWND hwnd = GetSomeWindowHandle(); char szNewText[] = "¡amamos a Bob!"; SetWindowText (hwnd, szNewText); char szNewText[] = "¡amamos a Bob!"; SetWindowTextW (hwnd, szNewText); Pasamos la cadena de un solo byte a una función que toma una cadena Unicode como argumento. La primera solución a este problema es usar #ifdef para incluir la definición de la variable de cadena: HWND hwnd = GetSomeWindowHandle(); #ifdef UNICODEwchar_t szNewText[] = L"we love Bob!"; ¡Amo a Bob!"; #endifSetWindowText (hwnd, szNewText); Es posible que ya haya sentido el dolor de cabeza que esto le causará. La solución perfecta es usar TCHAR.
Usar TCHAR
TCHAR es un tipo de cadena que le permite usar el mismo código al crear programas con MBCS y UNNICODE. No es necesario usar engorrosos. definiciones de macros para incluir su código. La definición de TCHAR es la siguiente: #ifdef UNICODEtypedef wchar_t TCHAR; #elsetypedef char TCHAR; #endif entonces, cuando se usa MBCS para compilar, TCHAR es char y cuando se usa UNICODE, TCHAR es wchar_t. También hay una macro para manejar el prefijo L requerido al definir constantes de cadena Unicode. #ifdef UNICODE#define _T(x) L##x#else#define _T(x) x#endif ## es un operador de preprocesamiento que puede conectar dos parámetros entre sí. Si su código requiere una constante de cadena, antepóngala con la macro _T. Si construye usando Unicode, antepondrá las constantes de cadena con L. TCHAR szNewText[] = _T("¡amamos a Bob!" Al igual que usar macros para ocultar los detalles de SetWindowTextA/W, hay muchas macros que puedes usar para implementar str***() y _mbs** *()); y otras funciones de cadena. Por ejemplo, puede utilizar la macro _tcsrchr para reemplazar strrchr(), _mbsrchr() y wcsrchr(). _tcsrchr se expande a la función correcta dependiendo de si su macro predefinida es _MBCS o UNICODE, tal como lo hace SetWindowText.
No solo la función str***() tiene la macro TCHAR. Otras funciones incluyen, _stprintf (reemplazando sprinft() y swprintf()), _tfopen (reemplazando fopen() y _wfopen()). Una lista completa de macros está disponible en MSDN bajo el título "Asignaciones de rutinas de texto genérico".
Cadenas y definiciones de tipos TCHAR
Dado que la lista de funciones de la documentación de la API de Win32 usa el nombre común de la función (por ejemplo, "SetWindowText"), todas las cadenas se definen usando TCHAR.
(Excepto por la API exclusiva para Unicode introducida en XP). A continuación se enumeran algunos tipos de definición de uso común; puede verlos en msdn. tipoSignificado en compilaciones MBCSSignificado en compilaciones UnicodeWCHARwchar_twchar_tLPSTRcadena de caracteres terminada en cero (char*)cadena de caracteres terminada en cero (char*)LPCSTRcadena de caracteres constante terminada en cero (const char*)cadena de caracteres constante terminada en cero (const char*) LPWSTRcadena Unicode terminada en cero (wchar_t*)cadena Unicode terminada en cero (wchar_t*)LPCWSTRcadena Unicode constante terminada en cero (const wchar_t*)cadena Unicode constante terminada en cero (const wchar_t*)TCHARcharwchar_tLPTSTRcadena terminada en cero de TCHAR (TCHAR*) cadena terminada en cero de TCHAR (TCHAR*)LPCTSTRcadena terminada en cero constante de TCHAR (const TCHAR*)cadena terminada en cero constante de TCHAR (const TCHAR*) Cuándo usar TCHAR y Unicode
Hasta ahora Quizás se pregunte por qué usamos Unicode. He usado char durante muchos años. Se beneficiará del uso de Unicode en las siguientes tres situaciones: 1. Su programa sólo se ejecuta en sistemas Windows NT. 2. Su programa necesita manejar nombres de archivos con más de MAX_PATH caracteres. 3. Su programa necesita utilizar las versiones exclusivas de Unicode de las API introducidas en XP. La mayoría de las API en Windows 9x no implementan versiones Unicode. Por lo tanto, si su programa se va a ejecutar en Windows 9x, debe usar las API de MBCS. Sin embargo, dado que los sistemas NT usan Unicode internamente, el uso de API Unicode acelerará sus programas. Cada vez que pasa una cadena para llamar a la API MBCS, el sistema operativo convertirá la cadena en una cadena Unicode y luego llamará a la API Unicode correspondiente. Si se devuelve una cadena, el sistema operativo tiene que volver a convertirla. Aunque este proceso de conversión está altamente optimizado, la penalización que supone en la velocidad es inevitable.
Siempre que utilice la API Unicode, el sistema NT permite el uso de nombres de archivos muy largos (superando el límite MAX_PATH, MAX_PATH=260). Otra ventaja de utilizar la API Unicode es que su programa maneja automáticamente la entrada del usuario en varios idiomas. Por lo tanto, un usuario puede ingresar inglés, chino o japonés, y no es necesario escribir código adicional para manejarlos.
Finalmente, con la desaparición de los productos Windows 9x, Microsoft parece estar abandonando las API MBCS.
Por ejemplo, la API SetWindowTheme(), que contiene dos parámetros de cadena, solo está disponible en Unicode. Crear sus programas usando Unicode simplificará el procesamiento de cadenas y no tendrá que convertir entre MBCS y Unicode.
Incluso si no utilizas Unicode para crear tus programas ahora, deberías utilizar TCHAR y sus macros relacionadas. Esto no sólo dará como resultado un código que maneja bien DBCS, sino que si en el futuro desea crear su programa con Unicode, solo necesita cambiar la configuración en el preprocesador.