Cómo representan los personajes los ordenadores
Las computadoras usan codificación digital para representar caracteres. El método de codificación más común es ASCII (Código estándar estadounidense para el intercambio de información). Al asignar cada carácter a números binarios de 8 bits, hay un total de 128 caracteres, incluidas letras, números, signos de puntuación y algunos personajes de control . La tabla de códigos ASCII se muestra en la siguiente figura:
Fuente de la imagen: http://www.asciima.com/ascii/12.html
Con el desarrollo de la tecnología informática, el código ASCII ha sido reemplazado gradualmente por el código Unicode. Unicode es una codificación estándar utilizada para representar todos los caracteres, incluidos los de ASCII, así como los caracteres de casi todos los idiomas del mundo. La codificación Unicode utiliza 32 dígitos binarios para representar cada carácter, lo que puede representar un total de 1,1 millones de caracteres .
Independientemente de la codificación utilizada, las computadoras convierten los caracteres en números para su procesamiento.
El tipo de carácter char en lenguaje C
Repita el principio básico: bajo la computadora, los caracteres se convierten en números para su procesamiento.
En el lenguaje C, char
los tipos se utilizan para representar datos de tipo carácter. char
La longitud del tipo suele ser de un byte y puede representar 256 caracteres diferentes, incluidos todos los caracteres del ASCII
código de inclusión y el código de extensión .ASCII
- caracter es un numero
En el lenguaje C, las constantes de caracteres se almacenan en forma de códigos ASCII en la memoria, por lo que las variables de tipo char se pueden tratar como tipos enteros y el valor del código ASCII se puede usar directamente para el cálculo .
El lenguaje C solo estipula que el carácter sin signo es un número entero de 8 bits sin signo, el carácter con signo es un número entero de 8 bits con signo y el tipo de carácter solo necesita ser un número entero de 8 bits, que puede estar firmado o sin firmar, dependiendo de la compilación decisión del dispositivo.
int main()
{
char temp = 'A'; // int: 65
cout << (int)temp << endl;
temp += 32; // int: 97, char: a
cout << temp << endl;
return 0;
}
- cadena de caracteres
En el lenguaje C, una cadena es un conjunto de arreglos de caracteres organizados de una manera específica, terminados por \0
un carácter nulo . ¿Por qué la cadena del lenguaje C \0
termina con un carácter nulo? Dado que cada carácter de la cadena del lenguaje C se organiza de forma continua y compacta en una matriz, para que el programa sepa la longitud de la cadena, la cadena debe contener un carácter especial, Utilizado para identificar el final de la cadena, este carácter especial es el carácter nulo \0
.
El maravilloso uso del carácter nulo, utilizando la función de la cadena de lenguaje C "que termina en 0", puede escribir 0 en un carácter que originalmente no es cero para finalizar la cadena antes de tiempo.
int main()
{
char temp[12] = "hello c"; // 空格对应的ASCII码值为32
cout << "sizeof: " << sizeof(temp) << endl;
cout << "strlen: " << strlen(temp) << endl;
temp [4] = 0;
cout << "sizeof: " << sizeof(temp) << endl;
cout << "strlen: " << strlen(temp) << endl;
return 0;
}
//----------outputs-----------------//
// sizeof: 12
// strlen: 7
// sizeof: 12
// strlen: 4
//---------------------------------.//
- Carácter de escape en lenguaje C
常见的转义符:
'\n': 换行符
'\\': 反斜杠
'\0': 空字符, ASCII码值为0
'0' : 字符0, ASCII码值为48
clase de cadena C++
En C++, std::string es una clase de cadena proporcionada en la biblioteca estándar, que puede almacenar cadenas de cualquier longitud.
Constructor
C++98 proporciona 7 formas de construir objetos de cadena y los métodos de construcción son los siguientes:
- Construya una cadena vacía con una longitud de 0;
string();
- llama al constructor de copias;
string (const string& str);
- Construye un objeto de cadena usando partes de otro objeto de cadena;
string (const string& str, size_t pos, size_t len = npos);
- Use cadenas de lenguaje C;
string (const char* s);
- Use la parte de cadena del lenguaje C para construir un objeto de cadena;
string (const char* s, size_t n);
- inicialización repetida con un solo carácter;
string (size_t n, char c);
- Inicializar con rango de iterador;
template <class InputIterator>
string (InputIterator first, InputIterator last);
Un ejemplo de inicialización de objeto de cadena es el siguiente:
// string constructor
#include <iostream>
#include <string>
int main ()
{
std::string s0 ("Initial string");
// constructors used in the same order as described above:
std::string s1; \\ 1
std::string s2 (s0); \\ 2
std::string s3 (s0, 8, 3); \\ 3
std::string s4 ("A character sequence"); \\ 4
std::string s5 ("Another character sequence", 12); \\ 5
std::string s6a (10, 'x'); \\ 6
std::string s6b (10, 42); // 42 is the ASCII code for '*'
std::string s7 (s0.begin(), s0.begin()+7); \\ 7
std::cout << "s1: " << s1 << "\ns2: " << s2 << "\ns3: " << s3;
std::cout << "\ns4: " << s4 << "\ns5: " << s5 << "\ns6a: " << s6a;
std::cout << "\ns6b: " << s6b << "\ns7: " << s7 << '\n';
return 0;
}
Diferencias entre cadenas C++ y cadenas C
La principal diferencia entre la cadena C++ y la cadena C es que la cadena C++ es una clase, mientras que la cadena C es una matriz de caracteres. También existen las siguientes diferencias en la expresión:
- La cadena del lenguaje C es una cadena separada
char* ptr
,\0
que termina automáticamente con; - La cadena de C++ es una clase de cadena y sus miembros tienen dos:
char* ptr
ysize_t len
, el segundo miembro se usa para determinar la posición del final y no es necesario que\0
termine con;
char data[20] = "hello\0world";
cout << data << endl; // hello
string str1(data);
cout << str1 << endl; // hello
string str2(data, 11);
cout << str2 << endl; // helloworld
Operaciones comunes de cadenas
operación de capacidad
- Devuelve la longitud de la cadena.
size_t size() const;
size_t length() const;
Las funciones de las dos funciones anteriores son las mismas y devuelven la longitud de la cadena.
- Cambiar el tamaño de la cadena a un tamaño fijo
void resize (size_t n);
void resize (size_t n, char c);
- Devuelve el tamaño de memoria asignado por el objeto de cadena
size_t capacity() const;
- Modificar la línea de memoria asignada por el objeto de cadena
void reserve (size_t n = 0);
- contenido claro
void clear();
- Determinar si está vacío.
bool empty() const;
elemento de acceso
El objeto de cadena tiene dos formas de acceder a elementos operator[]
y at
funciones, la diferencia es que at
si el subíndice i está fuera de los límites, std::out_of_range
se lanzará una excepción para terminar el programa. En lugar de operator[]
lanzar una excepción, el conocimiento simplemente agrega el primer puntero de dirección de la cadena i
para obtener un nuevo puntero y lo desreferencia. Si i
se cruzan los límites, el programa puede fallar o comportarse mal.
CRUD
- función de inserción
La función de inserción admite la inserción de un solo carácter o una cadena después del carácter pos en la cadena original.
string& insert (size_t pos, const char* s);
string& insert(size_t pos, char c)
- función push_back
Llame a la función de inserción para insertar un solo carácter al final de la cadena.
void push_back (char c);
- agregar función
Llame a la función de inserción para insertar el contenido de la cadena al final de la cadena.
string& append (const char* s);
- operador+= sobrecarga del operador
Llame a la función de inserción para insertar un solo carácter y contenido de cadena al final de la cadena.
string& operator+= (const char* s);
string& operator+= (char c);
- función de borrado
erase
La función se utiliza para eliminar el contenido del pos
siguiente elemento de la posición len
.
string& erase (size_t pos = 0, size_t len = npos);
- encontrar función
find
pos
La función admite la búsqueda desde la posición especificada , ya sea que contenga un solo carácter c
o contenido de cadena.
size_t find (const char* s, size_t pos = 0) const;
size_t find (char c, size_t pos = 0) const;
cortar una subcadena
substr
La función pos
comenzará desde el primer carácter e interceptará una subcadena con una longitud de len, y el contenido de la cadena original no cambiará.
Cuestiones de límites:
- Si la longitud de la parte restante de la cadena original es menor que len, se devuelve sin error una subcadena cuya longitud es menor que len.
- Si pos excede el rango de la cadena original, se lanza
std::out_of_range
una excepción
string substr (size_t pos = 0, size_t len = npos) const;
Implementación de simulación
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
static const size_t npos;
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end() const
{
return _str + _size;
}
// 构造函数
string(const char* str="")
: _size(strlen(str)), _capacity(_size)
{
_str = new char[_capacity + 1] ;
strcpy(_str, str);
}
// 析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// 常用操作
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
char& operator[] (size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char&
operator[] (size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
// request a change in capacity
void reverse(size_t n=0)
{
if(n > _capacity)
{
// 重新分配内存, 并释放旧的内存
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete [] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char c)
{
if (n <= _size)
{
_size = n;
_str[n] = '\0';
}
else
{
if(n > _capacity)
{
reverse(n);
}
memset(_str + _size, c, n - _size);
_size = n;
_str[_size] = '\0';
}
}
// 在指定位置插入一个字符
string& insert(size_t pos, char c)
{
assert(pos <= _size); // 分配的内存大小为size + 1
if(_size == _capacity)
{
// 两倍扩容
reverse(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while(end > pos)
{
// 指定位置后面的数据向后移动一步
_str[end] = _str[end - 1];
--end;
}
_str[pos] = c;
++_size;
return *this;
}
// 在指定位置插入字符串
string& insert(size_t pos, const char* s)
{
assert( pos <= _size);
size_t len = strlen(s);
if(_size + len > _capacity)
{
reverse(_size + len);
}
size_t end = _size + len;
while (end >= pos +len)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
void push_back(char ch)
{
insert(_size, ch);
}
void append(const char* str)
{
insert(_size, str);
}
string& operator+= (char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
// 删除
string& erase(size_t pos=0, size_t len=npos)
{
assert(pos < _size);
if(len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len); // 使用后面的内容覆盖前面的内容
_size -= len;
}
return *this;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
// 查询
size_t find(char ch)
{
// 顺序查找
for(size_t i=0; i<_size; ++i)
{
if(ch == _str[i])
return i;
}
return npos; // -1
}
size_t find(const char* s, size_t pos = 0)
{
const char* ptr = strstr(_str+pos, s);
if(ptr == nullptr)
return npos;
else
return ptr - _str;
}
};
const size_t string::npos = -1;
Implementaciones comunes de sobrecarga de operadores:
// 常见运算符重载
bool operator< (const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator== (const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<= (const string& s1, const string& s2)
{
return s1 < s2 || s1 == s2;
}
bool operator> (const string& s1, const string& s2)
{
return !(s1 <= s2);
}
bool operator>= (const string& s1, const string& s2)
{
return !(s1 < s2);
}
bool operator!= (const string& s1, const string& s2)
{
return !(s1 == s2);
}
// output stream
ostream& operator<< (ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
// input stream
istream& operator>>(istream& in, string& s)
{
char ch = in.get();
while(ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
copia superficial y copia profunda
copia superficial
Copia superficial: el compilador simplemente copia los valores en el objeto . Si hay recursos de administración en el objeto, eventualmente hará que varios objetos compartan el mismo recurso . Cuando se destruye un objeto, el recurso se liberará y en esta vez otro El objeto no sabe que el recurso se ha liberado y, si continúa operando en el recurso, se producirá un problema de infracción de acceso.
Como se muestra en el código siguiente, se define una clase de cadena y solo se proporcionan el constructor y el destructor, mientras que el compilador genera la sobrecarga del constructor de copia y el operador de asignación de forma predeterminada.
#include <cstring>
class string
{
private:
char* _str;
public:
string(const char* str="")
: _str(new char[strlen(str) + 1]
{
if(str != nullptr)
strcpy(_str, str);
}
~string()
{
delete [] _str;
_str = nullptr;
}
};
El siguiente código, al copiar y asignar la inicialización, aparecerá que los objetos de cadena apuntan al mismo espacio , al ejecutar return 0
, los destructores de los tres objetos serán llamados a su vez para liberar el espacio.
Dado que los espacios de direcciones a los que apuntan los tres objetos de cadena son los mismos, cuando se libera str2 después de que se destruye el objeto str3, la liberación falla y se produce un error porque el espacio ya se ha liberado.
int main()
{
string str1("Hello C++");
string str2(str1); // 调用拷贝构造函数
string str3 = str2; // 调用赋值运算符重载
return 0;
}
copia profunda
Para evitar el problema de la copia superficial, se puede utilizar una copia profunda. El código de implementación simple es el siguiente:
// 拷贝构造函数
string::string(const string& s)
: _str(new char[strlen(s._str) + 1)
{
if(s._str != nullptr)
strcpy(_str, s._str);
}
// 赋值运算符重载
string& string::operator=(const string& s)
{
if( nullptr != s._str)
{
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
recuento de referencia
#include <cstring>
class string
{
private:
char* _str;
int* _pCount;
public:
string(const char* str="")
: _str(new char[strlen(str) + 1], _pCount(new int)
{
if(str != nullptr)
strcpy(_str, str);
}
// 拷贝构造函数
string(const string& s)
{
_str = s._str;
_pCount = s._pCount;
++(*_pCount);
}
// 赋值运算符重载
string& operator=(const string& s)
{
delete[] _str;
_str = s._str;
_pCount = s._pCount;
++(*pCount);
return *this;
}
~string()
{
if(--(*_pCount) == 0)
{
delete [] _str;
delete _pCount;
_str = nullptr;
_pCount = nullptr;
}
}
};
¿Por qué utilizar punteros para el recuento de referencias en lugar de variables miembro y variables miembro estáticas?
- Variables miembro: al llamar a la copia y asignación para inicializar objetos, si las variables miembro se utilizan para el conteo de referencia, solo cambia el valor de conteo del objeto actual, mientras que otros objetos compartidos no se actualizan ;
- variable miembro estática
class String
{
private:
char* _str;
public:
static int _count;
String(const char* s="")
: _str(new char[strlen(s) + 1])
{
if(s != nullptr)
{
strcpy(_str, s);
}
}
String(const String& s)
{
_str = s._str;
++_count;
}
String& operator=(const String& s)
{
delete[] _str;
_str = s._str;
++_count;
return *this;
}
~String()
{
if(--_count == 0)
{
delete[] _str;
_str = nullptr;
}
}
};
int String::_count = 1;
int main()
{
String str1("Hello C++");
String str2(str1);
String str3 = str1;
cout << String::_count << endl; // 3
String str4("other data");
cout << String::_count << endl; // 3
return 0;
}
Cuando se usan variables miembro estáticas para contar, dado que una clase tiene solo una copia de la variable, cuando un objeto existente llama a copiar y asignar, varios objetos apuntan al mismo espacio de memoria, es decir, el recuento de referencia es mayor que 1 en este momento. . En este momento, se redefine una nueva variable y solo hay una variable de conteo, el contenido no cambiará y los datos se desordenarán en este momento .
El uso de punteros como variables de referencia no solo puede cumplir con los requisitos, sino también garantizar que se cree un recuento de referencia para cada objeto diferente sin afectar los recuentos de referencia de otros objetos idénticos.
vaca de copia en escritura
Problemas con la copia superficial:
- Destrucción varias veces
- Las modificaciones realizadas por uno de los objetos afectan al otro
En respuesta a los problemas anteriores, se propone un conteo de referencias de copia en escritura Los principios específicos son los siguientes:
- Para el primer problema, al usar copia superficial, varios objetos apuntan a la misma área. En este momento se puede introducir un contador para sumar uno al contador, al destruir si el contador es mayor a 1 se decrementa en 1 el contador, cuando el contador es 1 se vuelve a llamar al destructor;
- Para la segunda pregunta, si el contador no es 1 y se necesita modificar el objeto, se debe realizar una copia profunda;
Por lo tanto, la ventaja de la copia en escritura del conteo de referencias es que si el objeto no se modifica, solo necesita aumentar el conteo, sin copia profunda, lo que mejora la eficiencia. Desventajas: hay problemas de seguridad de subprocesos al citar números impares, se requieren bloqueos y hay un precio a pagar en un entorno de subprocesos múltiples.