Análisis del uso de la cuerda.

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:
ascii_Tabla2.png
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, charlos tipos se utilizan para representar datos de tipo carácter. charLa longitud del tipo suele ser de un byte y puede representar 256 caracteres diferentes, incluidos todos los caracteres del ASCIIcódigo de inclusión y el código de extensión .ASCII

  1. 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;
}
  1. cadena de caracteres

En el lenguaje C, una cadena es un conjunto de arreglos de caracteres organizados de una manera específica, terminados por \0un carácter nulo . ¿Por qué la cadena del lenguaje C \0termina 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
//---------------------------------.//
  1. 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:

  1. Construya una cadena vacía con una longitud de 0;
string();
  1. llama al constructor de copias;
string (const string& str);
  1. Construye un objeto de cadena usando partes de otro objeto de cadena;
string (const string& str, size_t pos, size_t len = npos);
  1. Use cadenas de lenguaje C;
string (const char* s);
  1. Use la parte de cadena del lenguaje C para construir un objeto de cadena;
string (const char* s, size_t n);
  1. inicialización repetida con un solo carácter;
string (size_t n, char c);
  1. 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, \0que termina automáticamente con;
  • La cadena de C++ es una clase de cadena y sus miembros tienen dos: char* ptry size_t len, el segundo miembro se usa para determinar la posición del final y no es necesario que \0termine 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

  1. 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.

  1. Cambiar el tamaño de la cadena a un tamaño fijo
void resize (size_t n);
void resize (size_t n, char c);
  1. Devuelve el tamaño de memoria asignado por el objeto de cadena
size_t capacity() const;
  1. Modificar la línea de memoria asignada por el objeto de cadena
void reserve (size_t n = 0);
  1. contenido claro
void clear();
  1. Determinar si está vacío.
bool empty() const;

elemento de acceso

El objeto de cadena tiene dos formas de acceder a elementos operator[]y atfunciones, la diferencia es que atsi el subíndice i está fuera de los límites, std::out_of_rangese 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 ipara obtener un nuevo puntero y lo desreferencia. Si ise cruzan los límites, el programa puede fallar o comportarse mal.

CRUD

  1. 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)
  1. 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);
  1. 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);
  1. 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);
  1. función de borrado

eraseLa función se utiliza para eliminar el contenido del possiguiente elemento de la posición len.

 string& erase (size_t pos = 0, size_t len = npos);
  1. encontrar función

findposLa función admite la búsqueda desde la posición especificada , ya sea que contenga un solo carácter co 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

substrLa función poscomenzará 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_rangeuna 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.

Link de referencia

Supongo que te gusta

Origin blog.csdn.net/hello_dear_you/article/details/129664013
Recomendado
Clasificación