Notas de lectura de "Programación avanzada en C++" (1: C++ y curso intensivo de biblioteca estándar)

1. Referencias

2. Se recomienda leer el libro "21 Días para aprender C++" para empezar, el enlace de las notas es el siguiente

1. Conocimientos básicos de C++

1.1 Subprograma "hola mundo"

// helloworld.cpp
/*
    helloworld.cpp
*/
#include <iostream>

int main() {
    
    
    std::cout << "Hello, World!" << std::endl;
    
    return 0;
}
  • nota

    • Las primeras cuatro líneas de este programa son comentarios de una sola línea y comentarios de varias líneas, que son solo mensajes para que los programadores los lean y el compilador los ignorará.
  • directiva de preprocesamiento

    • Hay tres pasos para generar un programa C++: primero, el código se ejecuta en el preprocesador, que reconoce la metainformación en el código; segundo, el código se compila o convierte en un archivo de objeto reconocible por la computadora; finalmente, el individuo los archivos de objetos están vinculados entre sí en una aplicación.
    • #include <iostream> es una directiva de preprocesamiento, y la directiva de preprocesamiento comienza con el carácter #
    • La directiva include le dice al preprocesador que: extraiga todo el contenido en el archivo de encabezado <iostream> y lo proporcione al archivo actual
    • El uso más común de los archivos de encabezado es declarar funciones definidas en otro lugar
    • La declaración de función informa al compilador cómo llamar a la función y declara el número y tipos de parámetros en la función, así como el tipo de retorno de la función. La definición de función contiene el código real de la función. En C++, las declaraciones generalmente se colocan en archivos con la extensión .h, llamados archivos de encabezado, y sus definiciones generalmente están contenidas en archivos con la extensión .cpp, llamados archivos fuente.
    • Los encabezados de biblioteca estándar en C todavía existen en C++, pero use las siguientes dos versiones
      • No use el sufijo .h, use el prefijo c en su lugar, como <cstdio>, estas versiones se colocan en el espacio de nombres estándar
      • Use el sufijo .h, que es una versión anterior como <stdio.h>, que no usa espacios de nombres
    • Aquí hay un ejemplo del uso de una directiva de preprocesador para evitar la doble inclusión.
    #ifndef MYHEADER_H
    #define MYHEADER_H
    //...the contents of this header filef
    #endif
    // 或(上下等价)
    #pragma once
    //...the contents of this header filef
    
  • función principal

    • main() El punto de entrada de un programa funcional, la declaración de retorno explícita puede ignorarse
    • argc proporciona la lista de argumentos pasados ​​al programa y argv contiene estos argumentos. Tenga en cuenta que argv[0] puede ser el nombre del programa o una cadena vacía
  • flujo de E/S

    // std::cout 用于输出信息
    // std::cerr 用于输出错误信息
    // std::endl 代表序列末尾,换行,表明一行末尾的另一种方法是使用 \n 字符(转义字符)
    // std::cin 接受键盘输入信息
    // 注:printf() 和 scanf() 未提供类型安全,不建议使用
    

1.2 Espacios de nombres

  • Los espacios de nombres se utilizan para manejar conflictos de nombres entre diferentes segmentos de código.
    // 头文件 namespaces.h
    namespace mycode {
          
          
        void foo();
    }
    
    // namespaces.cpp
    // 名称空间中还可实现方法或函数
    #include <iostream>
    #include "namespaces.h"
    
    void mycode::foo() {
          
          
        std::cout << "foo() called in the mycode namespace" << std::endl;
    }
    
    // usingnamespaces.cpp
    #include "namespaces.h"
    
    using namespace mycode;
    
    int main() {
          
          
        mycode::foo();	// 调用 mycode 名称空间中的 foo() 函数
        foo();			// 使用 using 后也可隐式调用
        return 0;
    }
    

Nunca use directivas de uso o declaraciones de uso en archivos de encabezado, o todos los que agreguen su archivo de encabezado tendrán que usarlo

  • C++17 permite el uso conveniente de espacios de nombres anidados, es decir, poner un espacio de nombres dentro de otro
    namespace MyLibraries::Networking::FTP {
          
          }
    
  • C ++ 17 también usa alias de espacio de nombres, que le dan a otro espacio de nombres un nombre nuevo y más corto
    namespace MyFTP = MyLibraries::Networking::FTP;
    

1.3 Literales

  • Los literales se utilizan para escribir números o cadenas en código.
  • El siguiente literal especifica el número 123
    • Literal decimal 123
    • Literal octal 0173
    • Hexadecimal literal 0x7B
    • Literal binario 0b1111011
  • otros literales
    • valor de punto flotante (por ejemplo, 3.14f)
    • valor de punto flotante de precisión doble (por ejemplo, 3.14)
    • carácter único (por ejemplo, 'a')
    • Una matriz de caracteres terminada en cero (como "matriz de caracteres")
    • También puede personalizar el tipo de argumento

1.4 Variables

  • En C++, las variables se pueden declarar en cualquier lugar y una variable se puede usar en cualquier lugar después de la línea en la que se declara.
  • Cuando se usan variables no inicializadas en el código, la mayoría de los compiladores darán un mensaje de advertencia o error.
  • Tipos de variables comunes de C++
    inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

  • método de conversión de tipo
    float myFloat = 3.14f;
    int i1 = (int)myFloat; // 方法一,目前最常使用,但不建议
    int i2 = int(myFloat); // 方法二,很少使用
    int i3 = static_cast<int>(myFloat); // 方法三,最建议使用
    

1.5 Operadores

inserte la descripción de la imagen aquí

1.6 Tipo

  • tipo enumerado

    • El tipo enumerado es solo un valor entero, el valor real de PieceTypeKing es 0
    • El compilador emitirá un mensaje de advertencia o error si intenta realizar operaciones aritméticas en una variable PieceType o si la trata como un número entero.
    enum PieceType {
          
           
        PieceTypeKing,
        PieceTypeQueen,
        PieceTypeRook,
        PieceTypePawn
    };
    
  • Los valores enteros también se pueden especificar para los miembros de la enumeración

    • PieceTypeKing tiene un valor entero de 1, el compilador asigna un valor entero de 2 a PieceTypeQueen, PieceTypeRook tiene un valor de 10 y el compilador asigna automáticamente un valor de 11 a PieceTypePawn
    enum PieceType {
          
           
        PieceTypeKing = 1,
        PieceTypeQueen,
        PieceTypeRook = 10,
        PieceTypePawn
    };
    
  • enumeración fuertemente tipada

    • La enumeración anterior no está fuertemente tipada, lo que significa que no es segura . Siempre se interpretan como datos enteros, por lo que se pueden comparar valores de enumeración en tipos de enumeración completamente diferentes. Las enumeraciones de clase de enumeración fuertemente tipadas resuelven estos problemas
    enum class PieceType {
          
          
        King = 1,
        Queen,
        Rook = 10,
        Pawn
    }
    
    • Para las clases de enumeración, los nombres de valores de enumeración no salen automáticamente del alcance adjunto, lo que significa usar siempre el operador de resolución de alcance
    PieceType piece = PieceType::King;
    
    • De forma predeterminada, el tipo base de un valor de enumeración es un número entero, pero esto se puede cambiar de las siguientes maneras
    enum class PieceType : unsigned long {
          
          
        King = 1,
        Queen,
        Rook = 10,
        Pawn
    }
    

    Se recomienda utilizar la enumeración de clase de enumeración de tipo seguro en lugar de la enumeración de enumeración de tipo no seguro

  • estructura

    • Una estructura (struct) permite encapsular uno o más tipos existentes en un nuevo tipo
    // employeestruct.h
    #pragma once
    
    struct Employee {
          
          
        char firstInitial;
        char lastInitial;
        int employeeNumber;
        int salary;
    }; 
    
    // structtest.cpp
    #include <iostream>
    #include "employeestruct.h"
    
    using namespace std;
    
    int main() {
          
          
        // Create and populate an employee.
        Employee anEmployee;
    
        anEmployee.firstInitial = 'M';
        anEmployee.lastInitial = 'G';
        anEmployee.employeeNumber = 42;
        anEmployee.salary = 80000;
    
        // Output the values of an employee.
        cout << "Employee: " << anEmployee.firstInitial << anEmployee.lastInitial << endl;
        cout << "Number: " << anEmployee.employeeNumber << endl;
        cout << "Salary: $" << anEmployee.salary << endl;
    
        return 0;
    }
    

1.7 Declaraciones condicionales

  • declaración si/si no

    • La expresión entre paréntesis de la instrucción if debe ser un valor booleano, o el resultado de la evaluación debe ser un valor booleano
    • 0 es falso, distinto de cero es verdadero
    if (i > 4) {
          
          
        // Do something.
    } else if {
          
          
        // Do something else.
    } else {
          
          
        // Do something else.
    }
    
  • C++17 permite incluir un inicializador en una declaración if

    if (<initializer> ; <conditional expression>) ( <body> }
    // 示例:初始化器获得一名雇员,以及检查所检索雇员的薪水是否超出 1000 的条件
          // 只有满足条件才执行 if 语句体
    if (Employee employee = GetEmployee(); employee.salary > 1000) {
          
          
        ...
    }
    
  • declaración de cambio

    • La expresión de la instrucción switch debe ser un número entero, un tipo que se pueda convertir en un número entero, un tipo de enumeración o una enumeración fuertemente tipada, y debe compararse con un valor constante. Cada valor constante representa un "caso". el patrón de expresión coincide con este caso, las líneas de código subsiguientes se ejecutarán hasta que se encuentre una declaración de ruptura
    • Además, se puede proporcionar un caso predeterminado y el valor de la expresión coincidirá con el caso predeterminado si ningún otro caso coincide con el valor de la expresión.
    switch(menuItem) {
          
          
        case OpenMenuItem:
            // Code to open a file
            break;
        case SaveMenuItem:
            // Code to save a file
            break;
        default:
            // Code to give an error message
            break;
    }
    
    • Al igual que con las sentencias if, C++17 admite el uso de inicializadores en las sentencias switch.
    switch (<initializer>; <expression>) {
          
          <body>}
    
  • operador condicional

    • También llamado operador ternario porque usa tres operandos
    // i 大于 2 吗?如果是真的,结果就是 yes,否则结果就是 no
    std::cout << ((i > 2) ? "yes" : "no");
    

1.8 Operadores lógicos de comparación

  • C++ usa lógica de cortocircuito al evaluar expresiones: esto significa que una vez que se puede determinar el resultado final, el resto de la expresión no se evalúa.
    inserte la descripción de la imagen aquí

1.9 Funciones

  • En C++, para que otro código use una función, primero se debe declarar la función
    • Si la función se usa dentro de un archivo en particular, generalmente se declara y define en el archivo fuente
    • Si la función está destinada a ser utilizada por otros módulos o archivos, es común declarar la función en el archivo de encabezado y definir la función en el archivo fuente.
    // 函数声明
    void myFunction(int i, char c);
    // 函数定义
    void myFunction(int i, char c) {
          
          
        std::cout << "the value of i is" << i << std::endl;
        std::cout << "the value of c is" << c << std::endl;
    }
    // 函数调用
    myFunction(8,a');
    
  • Inferencia del tipo de retorno de función
    • Para usar esta función, debe especificar auto como el tipo de devolución, y el compilador infiere el tipo de devolución en función de la expresión utilizada en la declaración de devolución.
    auto addNumbers(int number1, int number2) {
          
          
        return number1 + number2;
    }
    
  • el nombre de la función actual
    • Cada función tiene una variable local predefinida _func_ que contiene el nombre de la función actual. Un uso de esta variable es para registrar
    int addNumbers(int number1, int number2) {
          
          
        std::cout << "Entering function " << _func_ << std::endl;
        return number1 + number2;
    }
    

1.10 matrices de estilo C

  • En C++, las matrices de estilo C se deben evitar tanto como sea posible, y en su lugar se deben usar std::array y std::vector en STL
    int myArray[3];
    myArray[0];
    myArray[1];
    myArray[2];
    
    int myArray[3] = {
          
          0};
    int myArray[3] = {
          
          };
    int myArray[] = {
          
          1, 2, 3, 4};
    
    char ticTacToeBoard[3][3];
    ticTacToeBoard[1][1] = 'o';
    

1.11 estándar::matriz

  • C++ tiene un contenedor especial de tamaño fijo std::array, que se define en el archivo de encabezado <array> Usar std:array en lugar de matrices de estilo C traerá muchos beneficios, como se muestra a continuación
    • siempre sabe su propio tamaño
    • no se convierte automáticamente en un puntero, lo que evita ciertos tipos de errores
    • Tiene un iterador para atravesar elementos convenientemente
    array<int, 3> arr = {
          
          9, 8, 7};
    cout << "Array size = " << arr.size() << endl;
    cout << "2nd element = " << arr[1] << endl;
    

1.12 estándar::vector

  • Si desea que el tamaño de la matriz sea dinámico, se recomienda utilizar std:vector. Cuando se agregan nuevos elementos al vector, el vector aumentará automáticamente su tamaño
    #include <vector> // 包含头文件
    
    vector<int> myVector = {
          
          11, 22};
    myVector.push_back(33); // 向 vector 中动态添加元素
    myVector.push_back(44);
    
    cout << "1st element: " << myVector[0] << endl;
    

1.13 Encuadernación estructurada

  • C ++ 17 introdujo el concepto de enlaces estructurados, que permiten la declaración de múltiples variables inicializadas con elementos de una matriz, estructura, par o tupla. Por ejemplo, suponga que tiene la siguiente matriz
    std::array<int, 3> values = {
          
          112233};
    
  • Se pueden declarar tres variables x, y y z, inicializadas con los tres valores en la matriz subsiguiente. Tenga en cuenta que la palabra clave auto debe usarse para enlaces estructurados
    auto [x, y, z] = values;
    
  • El número de variables declaradas con enlace estructurado debe coincidir con el número de valores en la expresión de la derecha

1.14 Bucle

  • mientras bucle

    • Un bucle while ejecuta un bloque de código repetidamente siempre que la expresión condicional se evalúe como verdadera
    int i = 0;
    while (i < 5) {
          
          
        std::cout << "This is silly." << std::endl;
        ++i;
    }
    
  • bucle do/while

    • El código se ejecutará una vez primero, y la verificación condicional para determinar si continuar la ejecución se coloca al final . Puede usar esta versión de bucle cuando desee que un bloque de código se ejecute al menos una vez y, dependiendo de una condición, si se ejecutará varias veces. El siguiente código se imprime una vez a pesar de que la condición es falsa
    int i = 100;
    do {
          
          
        std::cout << "This is silly." << std::endl;
        ++i;
    } while (i < 5);
    
  • en bucle

    • La sintaxis del ciclo for es generalmente más simple, porque puede ver la expresión inicial del ciclo, la condición final y la declaración ejecutada después de cada iteración.
    for (int i = 0; i < 5; ++i) {
          
          
        std::cout << "This is silly." << std::endl;
    }
    
  • bucle for basado en rango

    • Este tipo de bucle permite una iteración conveniente sobre los elementos en un contenedor y se puede usar para matrices de estilo C, listas de inicializadores y también para tipos que tienen funciones begin() y end() que devuelven iteradores: por ejemplo, std::array , estándar:: vector
    std::array<int, 4> arr = {
          
          1, 2, 3, 4};
    for (int i : arr) {
          
          
        std::cout << i << std::endl;
    }
    

1.15 Listas de inicialización

  • La lista de inicialización se define en el archivo de cabecera <initializer list> Con la lista de inicialización, es fácil escribir funciones que pueden recibir un número variable de parámetros.
  • La clase de lista de inicializadores es una plantilla que requiere especificar el tipo de elementos en la lista entre paréntesis angulares, similar a especificar el tipo de objeto almacenado en un vector
    #include <iostream>
    #include <initializer_list>
    
    using namespace std;
    
    int makeSum(initializer_list<int> lst) {
          
          
        int total = 0;
        for (int value : lst) {
          
          
            total += value;
        }
        return total;
    }
    
    int main() {
          
          
        int a = makeSum({
          
          1, 2, 3});
        int b = makeSum({
          
          10, 20, 30, 40, 50, 60});
    
        cout << a << endl;
        cout << b << endl;
    
        return 0;
    }
    

2. Profundice en C++

2.1 Cadenas en C++

  • Hay tres formas de usar cadenas en C++
    • Uno es el estilo C, que trata a los caracteres como matrices de caracteres.
    • Uno es el estilo C++, que encapsula cadenas en un tipo de cadena fácil de usar.
    • También hay una clase común no estándar (presentada en el Capítulo 2)
    #include <string>
    
    string myString = "Hello, World";
    cout << "The value of myString is " << myString << endl;
    cout << "The second letter is " << myString[1] << endl;
    

2.2 Punteros y memoria dinámica

  • La memoria dinámica permite que se creen programas con datos de tamaño variable en tiempo de compilación, y la mayoría de los programas complejos utilizan la memoria dinámica de alguna manera.
2.2.1 Pila y Montón

La memoria en un programa C++ se divide en dos partes: la pila y el montón

  • Apilar (y apilar significa lo mismo)

    • La pila es como una baraja de cartas, la carta superior actual representa el alcance actual del programa, generalmente la función que se ejecuta actualmente. Todas las variables declaradas en la función actual ocuparán la memoria del marco de la pila superior (es decir, la tarjeta superior) . Si la función actual foo() llama a otra función bar(), se entregará una nueva tarjeta, por lo que bar() tendrá su propio marco de pila para que se ejecute
    • Todos los argumentos pasados ​​de foo() a bar() se copian del marco de la pila foo() al marco de la pila de la barra
    • Un marco de pila proporciona un espacio de memoria independiente para cada función. Si se declara una variable en la pila foo(), llamar a la función bar() no cambia esa variable a menos que se solicite específicamente
    • Cuando la función foo() termina de ejecutarse, la pila desaparece y todas las variables declaradas en la función ya no ocupan memoria.
    • Las variables que asignan memoria en la pila no requieren que el programador libere manualmente la memoria (elimine), este proceso se realiza automáticamente
  • marco de pila

    • Un marco de pila se refiere al área (o espacio) asignado en la pila para la función que se está ejecutando actualmente . Los parámetros pasados, la dirección de retorno (a la que se debe saltar cuando finaliza la función) y la unidad de memoria utilizada por la función (es decir, las variables locales almacenadas por la función en la pila) están todos en el marco de la pila.
    • Los marcos de pila generalmente se crean cuando se llama a una nueva función y se destruyen cuando la función regresa . Para decirlo sin rodeos, la pila se compone de marcos de pila. Los marcos de pila se colocan en la pila cuando se llama a una función, y los marcos de pila se extraen de la pila cuando la función regresa.
  • montón

    • El montón es un área de memoria que no tiene absolutamente ninguna relación con la función actual o el marco de pila . Si desea guardar las variables declaradas en la llamada de función después del final, puede colocar las variables en el montón
    • La estructura del montón no es tan complicada como la pila. El montón se puede considerar como un montón de bits. El programa puede agregar nuevos bits al montón o modificar los bits existentes en el montón en cualquier momento.
    • Debe asegurarse de que se libere (elimine) cualquier memoria asignada en el montón; este proceso no se realiza automáticamente a menos que se utilicen punteros inteligentes
2.2.2 Uso de punteros
  • Puede colocar cualquier cosa en el montón asignando memoria explícitamente. Por ejemplo, para colocar un entero en el montón, se debe asignar memoria para él, pero primero se debe declarar un puntero.
    • Un puntero es simplemente una dirección que apunta a un valor entero . Para acceder a este valor, el puntero debe ser desreferenciado
    • Puede pensar en desreferenciar como buscar el valor real en el montón en la dirección de la flecha del puntero
    • El puntero debe ser válido antes de ser desreferenciado . La desreferenciación de un puntero nulo o no inicializado da como resultado un comportamiento indeterminado
    // 应避免使用未初始化的变量/指针
    int *myIntegerPointer = new int; // 使用 new 操作符分配内存
    int *myIntegerPointer = nullptr; // 如果不希望立即分配内存,可以把它们初始化为空指针
    
  • asigna un valor a un entero recién asignado en el montón
    *myIntegerPointer = 8; // 没改变指针,只是改变指针指向的内存
    
  • Después de usar la memoria asignada dinámicamente, debe usar el operador de eliminación para liberar la memoria. Para evitar que el puntero se use después de liberar la memoria a la que apunta el puntero, se recomienda establecer el puntero en nullptr
    delete myIntegerPointer;
    myIntegerPointer = nullptr;
    
  • Los punteros no siempre apuntan a la memoria del montón, puede declarar un puntero a una variable en la pila o incluso a otros punteros
    • Para hacer que un puntero apunte a una variable, debe usar el operador "dirección" &
    int i = 8;
    int *myIntegerPointer = &i;
    
  • Si el puntero apunta a una estructura, primero puede desreferenciar el puntero con * y luego usar la sintaxis .normal para acceder a los campos de la estructura.
    Employee *anEmployee = getEmployee();
    cout << (*anEmployee).salary << endl; // 同下等价
    cout << anEmployee->salary << endl; // -> 运算符允许同时对指针解除引用并访问字段
    
2.2.3 Arreglos asignados dinámicamente
  • El montón también se puede usar para asignar matrices dinámicamente. Use el operador new[] para asignar memoria para una matriz
    • Como se puede ver en la figura a continuación: la variable de puntero todavía está en la pila, pero la matriz creada dinámicamente está en el montón
    int arraySize = 8;
    int *myVariableSizedArray = new int[arraySize];
    

inserte la descripción de la imagen aquí

  • Ahora que se ha asignado la memoria, use myVariableSizedArray como una matriz normal basada en pilas.
    myVariableSizedArray[3] = 2;
    // 使用完这个数组后,应该将其从堆中删除,这样其他变量就可以使用这块内存
    delete[] myVariableSizedArray;
    myVariableSizedArray = nullptr;
    

Evite malloc() y free() en C, use new y delete, o use new[] y delete[]

2.2.4 Variables de puntero nulo
void func(char* str) {
    
    cout << "char* version" << endl;}
void func(int i) {
    
    cout <<"int version" << endl;}

int main() {
    
    
    func(NULL); // NULL 不是指针,而等价于 0,所以调用整数版本
    func(nullptr); // 真正的空指针常量 nullptr,调用 char* 版本

    return 0;
}
2.2.5 Punteros inteligentes
  • Un objeto de puntero inteligente libera memoria automáticamente cuando queda fuera del alcance, por ejemplo, después de que una función termina de ejecutarse . Hay dos punteros inteligentes más importantes en C++

  • estándar::único_ptr

    • Como un puntero normal, pero automáticamente libera memoria o recursos cuando se sale del alcance o se elimina
    • unique_ptr solo pertenece al objeto al que apunta
    • Pros: la memoria y los recursos siempre se liberan (incluso cuando se ejecuta una declaración de devolución o se lanza una excepción)
    • crear único_ptr
    /* 
        unique_ptr 是一个通用的智能指针,它可以指向任意类型的内存
        所以它是一个模板,而模板需要用尖括号指定模板参数
        在尖括号中必须指定 unique_ptr 要指向的内存类型
        make_unique 为 C++14 引入
    */
    auto anEmployee = make_unique<Employee>(); // 不再需要调用 delete,因为会自动完成
    unique_ptr<Employee> anEmployee(new Employee); // C++11 标准
    
    • unique_ptr también se puede usar para almacenar matrices de estilo C
    auto employees = make_unique<Employee[]>(10);
    cout << "Salary: " << employees[0].salary << endl;
    
  • estándar::shared_ptr

    • shared ptr permite la "propiedad" distribuida de los datos: cada vez que se especifica shared ptr, se incrementa un recuento de referencia, lo que indica que los datos tienen un "propietario" más. Cuando el ptr compartido sale del alcance, el recuento de referencias se reduce. Cuando el recuento de referencias es 0, significa que los datos ya no tienen propietario, por lo que se libera el objeto al que hace referencia el puntero.
    • crear shared_ptr
    auto anEmployee = make_shared<Employee>();
    if (anEmployee) {
          
          
        cout << "Salary: " << anEmployee->salary << endl;
    }
    
    • Desde C++17, las matrices también se pueden almacenar en shared_ptr
    shared_ptr<Employee[]> employees(new Employee[10]);
    cout << "Salary: " << employees[0].salary << endl;
    

2.3 Múltiples usos de const

2.3.1 Usando const para definir constantes
  • Use const en lugar de #define para definir constantes
    const int versionNumberMajor = 2;
    const int versionNumberMinor = 1;
    const std::string productName = "Super Hyper Net Modulator";
    
2.3.2 Uso de const para proteger parámetros
void mysteryFunction(const std::string *someString) {
    
    
    *someString ="Test"; // 不允许修改
}

int main() {
    
    
    std::string myString = "The string";
    mysteryFunction(&myString);
    
    return 0;
}

2.4 Referencias

  • Añadir un & a un tipo indica que la variable correspondiente es una referencia . Detrás de escena, en realidad es un puntero a la variable original. La variable x y la variable de referencia xReference apuntan al mismo valor. Si cambia un valor a través de uno, el cambio también será visible en el otro
    int x = 42;
    int &xReference = x;
    
2.4.1 Pasar por referencia
  • Por lo general, cuando pasa variables a funciones, pasa valores. Si la función toma un parámetro entero, lo que realmente se pasa es una copia del entero, por lo que el valor de la variable original no se modifica. Los punteros en las variables de pila se usan comúnmente en C para permitir que las funciones modifiquen variables en otro marco de pila
  • En C++, en lugar de pasar punteros a funciones, los parámetros se pasan por referencia
    // 不会影响传递给它的变量,因为变量是按照值传递的
    // 因此函数接收的是传递给它的值的一个副本
    void addOne(int i) {
          
          
        i++;
    }
    // 使用了引用,因此可以改变原始变量的值
    void addOne(int &i) {
          
          
        i++;
    }
    
2.4.2 Pasar por referencia constante
  • Al pasar un valor a una función, se realiza una copia completa. Al pasar por referencia, en realidad solo está pasando un puntero a los datos originales , por lo que no necesita hacer una copia. Al pasar una referencia const, puede lograr ambas cosas: no se requiere copia y la variable original no se modifica
    void printString(const std::string &myString) {
          
          
        std::cout << myString << std::endl;
    }
    
    int main() {
          
          
        std::string someString = "Hello world!";
        printString(someString);
        printString("Hello World!");
    
        return 0;
    }
    

2.5 Anormal

#include <stdexcept>

double divideNumbers(double numerator, double denominator) {
    
    
    if (denominator == 0) {
    
    
        throw invalid_argument("Denominator cannot be 0."); // 函数立刻结束而不会返回值
    }
    return numerator / denominator;
}
// 捕获异常并处理
try {
    
    
    std::cout << divideNumbers(2.50.5) << std::endl; // 返回 5
    std::cout << divideNumbers(2.30) << std::endl; // 抛出异常,不返回值,并直接跳到 catch 块
    std::cout << divideNumbers(4.52.5) << std::endl; // 程序已跳转,该行不执行
} catch (const invalid_argument &exception) {
    
    
    std::cout << "Exception caught:" << exception.what() << std::endl;
}

2.6 Inferencia de tipos

2.6.1 Palabra clave automática
  • auto se puede usar para decirle al compilador que infiera automáticamente el tipo de variable en el momento de la compilación
  • Pero usar auto elimina los calificadores de referencia y const
#include <string>

const std::string message = "Test";
const std::string &foo() {
    
    
    return message;
}
// 因为 auto 去除了引用和 const 限定符,且 f1 是 string 类型,所以建立一个副本
auto f1 = foo();
// 如果不需要副本,可使用 auto& 或 const auto&
const auto &f2 = foo();
2.6.2 tipo de declaración
  • La palabra clave decltype toma una expresión como un parámetro real y calcula el tipo de la expresión
    // 编译器推断出 y 的类型是 int,因为这是 x 的类型
    int x = 123;
    decltype(x) y = 456;
    
  • La diferencia entre auto y decltype es que
    • decltype no elimina las referencias y los calificadores const
    • f2 se define con decltype de la siguiente manera, lo que hace que f2 sea del tipo const string&, por lo que no se realizan copias
    decltype(foo()) f2 = foo();
    

3. C++ como lenguaje orientado a objetos

3.1 Definir clases

  • En C++, una clase generalmente se declara en un archivo de encabezado (.h) y sus métodos no en línea y miembros de datos estáticos se definen en el archivo fuente correspondiente (.cpp)
// AirlineTicket.h
#pragma once

#include <string>

class AirlineTicket {
    
    
public:
    AirlineTicket(); // 当创建类的对象时会自动调用构造函数
    ~AirlineTicket(); // 当销毁对象时会自动调用析构函数
    // 最好将不改变对象的任何数据成员的成员函数声明为 const
    double calculatePriceInDollars() const;
    
    const std::string &getPassengerName() const;
    void setPassengerName(const std::string& name);
    
    int getNumberOfMiles() const;
    void setNumberOfMiles(int miles);
    
    bool hasEliteSuperRewardsStatus() const;
    void setHasEliteSuperRewardsStatus(bool status);

private:
    std::string mPassengerName;
    int mNumberOfMiles;
    bool mHasEliteSuperRewardsStatus;
};
  • Esta definición primero declara un nombre de clase y declara los miembros de datos (atributos) y métodos (comportamiento) de la clase entre llaves.
  • Cada miembro de datos y método tiene un nivel de acceso específico: público, protegido o privado. Estos tokens pueden aparecer en cualquier orden y pueden reutilizarse
    • los miembros públicos son accesibles fuera de la clase
    • No se puede acceder a los miembros privados fuera de la clase (se recomienda declarar todos los miembros de datos como privados y, cuando sea necesario, se puede acceder a ellos a través de lectores y configuradores públicos)
3.1.1 Inicialización del constructor
  • Método 1: Inicializador de constructor
    AirlineTicket::AirlineTicket() : mPassengerName("Unknown Passenger"),
                                     mNumberOfMiles(0),
                                     mHasEliteSuperRewardsStatus(false) {
          
          }
    
  • Método 2: poner la tarea de inicialización en el cuerpo del constructor
    AirlineTicket::AirlineTicket() {
          
          
        mPassengerName = "Unknown Passenger";
        mNumberOfMiles = 0;
        mHasEliteSuperRewardsStatus = false;
    }
    
  • Si el constructor es solo para inicializar los miembros de datos, en realidad no hay necesidad de usar el constructor , ya que los miembros de datos se pueden inicializar directamente en la definición de clase. Si la clase también necesita realizar otros tipos de inicialización, como abrir archivos, asignar memoria, etc., debe escribir un constructor para manejarlo.
    private:
        std::string mPassengerName = "Unknown Passenger";
        int mNumberOfMiles = 0;
        bool mHasEliteSuperRewardsStatus = false;
    
3.1.2 Definición de algunos métodos de la clase AirlineTicket
// AirlineTicket.cpp
#include "AirlineTicket.h"
using namespace std;

AirlineTicket::AirlineTicket() : mPassengerName("Unknown Passenger"),
                                     mNumberOfMiles(0),
                                     mHasEliteSuperRewardsStatus(false) {
    
    }
AirlineTicket::~AirlineTicket() {
    
    }

double AirlineTicket::calculatePriceInDollars() const {
    
    
    if (hasEliteSuperRewardsStatus()) {
    
    
        // Elite Super Rewards customers fly for free!
        return 0;
    }

    return getNumberOfMiles() * 0.1;
}

const string &AirlineTicket::getPassengerName() const {
    
    
    return mPassengerName;
}
...

3.2 Uso de clases

// AirlineTicketTest.cpp
#include <iostream>
#include <memory>
#include "AirlineTicket.h"

using namespace std;

int main() {
    
    
    // 1. 基于堆栈的类的使用方法
    AirlineTicket myTicket;
    myTicket.setPassengerName("Sherman T. Socketwrench");
    myTicket.setNumberOfMiles(700);
    double cost = myTicket.calculatePriceInDollars();
    cout << "This ticket will cost $" << cost << endl;
    
    // 2. 基于堆的类的使用方法(使用智能指针)
    auto myTicket2 = make_unique<AirlineTicket>();
    myTicket2->setPassengerName("Laudimore M. Hallidue");
    myTicket2->setNumberOfMiles(2000);
    myTicket2->setHasEliteSuperRewardsStatus(true);
    double cost2 = myTicket2->calculatePriceInDollars();
    cout << "This other ticket will cost $" << cost2 << endl;
    // No need to delete myTicket2, happens automatically
    
    // 3. 基于堆的类的使用方法(不使用智能指针)(不推荐使用该方式)
    AirlineTicket *myTicket3 = new AirlineTicket();
    // ... Use ticket 3
    delete myTicket3;  // delete the heap object!
    
    return 0;
} 

4. Inicialización uniforme

struct CircleStruct {
    
    
	int x, y;
	double radius;
};

class CircleClass {
    
    
public:
	CircleClass(int x, int y, double radius)
		: mX(x), mY(y), mRadius(radius) {
    
    }
private:
	int mX, mY;
	double mRadius;
};
  • Antes de C++11, los métodos de inicialización para estructuras y clases eran diferentes
    CircleStruct myCirclel = {
          
          10102.5};
    CircleClass myCircle2(10102.5);
    
  • Después de C++11, se permite usar la sintaxis {…} para inicializar tipos
    // 其中 = 号是可选的
    CircleStruct myCirclel = {
          
          10102.5};
    CircleClass myCircle2 = {
          
          10102.5};
    
  • La inicialización uniforme también se puede usar para inicializar matrices asignadas dinámicamente
int *pArray = new int[4]{
    
    0123};
  • La inicialización uniforme también puede inicializar matrices de miembros de clase en inicializadores de constructores
class MyClass {
    
    
public:
    MyClass() : mArray{
    
    0123} {
    
    }
private:
    int mArray[4];
};
  • Inicialización de lista directa frente a inicialización de lista de copias
    • Inicialización de la lista de copias: T obj = {arg1, arg2, ...};
      • Para la inicialización de la lista de copias, todos los elementos del inicializador encerrados entre llaves deben ser del mismo tipo
    • Inicialización de lista directa: T obj {arg1, arg2, ...};

5. El primer programa útil en C++

  • Crear una base de datos de empleados

5.1 Sistema de registros de empleados

  • Un programa para administrar los registros de empleados de una empresa debe ser flexible y tener funciones eficientes que incluyan las siguientes funciones:

    • añadir empleado
    • despedir empleados
    • promoción de empleados
    • Ver todos los empleados, pasados ​​y presentes
    • ver todos los empleados actuales
    • ver todos los ex empleados
  • El código del programa se divide en tres partes.

    • La clase Employee encapsula información sobre un solo empleado.
    • Todos los empleados de la empresa de gestión de clases de base de datos.
    • Interfaces para proveedores de interfaces de usuario individuales

5.2 Clase de empleado

  • Empleado.h
#pragma once // 防止文件被包含多次
#include <string>

// 自定义 Records 名称空间
namespace Records {
    
    
    const int kDefaultStartingSalary = 30000; // 设置新雇员默认起薪
    
    class Employee {
    
    
    public:
        Employee() = default; // 显式的默认构造函数
        // 包含接收姓名的构造函数
        Employee(const std::string& firstName, const std::string& lastName);
    
        void promote(int raiseAmount = 1000); // 设定了默认值
        void demote(int demeritAmount = 1000); // 设定了默认值
        void hire(); // Hires or rehires the employee
        void fire(); // Dismisses the employee
        void display() const;// Outputs employee info to console
    
        // 提供修改 set 或查询 get 雇员信息的机制
        void setFirstName(const std::string& firstName);
        const std::string& getFirstName() const;
    
        void setLastName(const std::string& lastName);
        const std::string& getLastName() const;
    
        void setEmployeeNumber(int employeeNumber);
        int getEmployeeNumber() const;
    
        void setSalary(int newSalary);
        int getSalary() const;
    
        bool isHired() const;
    
    private:
        std::string mFirstName;
        std::string mLastName;
        int mEmployeeNumber = -1;
        int mSalary = kDefaultStartingSalary;
        bool mHired = false;
    };
}
  • Empleado.cpp
#include <iostream>
#include "Employee.h"

using namespace std;

namespace Records {
    
    
    Employee::Employee(const std::string &firstName, const std::string &lastName)
                      : mFirstName(firstName), mLastName(lastName) {
    
    }
    // 只是用一些新值调用 setSalary() 方法
    // 注意:整型参数的默认值不显示在源文件中,只能出现在函数声明中,不能出现在函数定义中
    void Employee::promote(int raiseAmount) {
    
    
        setSalary(getSalary() + raiseAmount);
    }
    void Employee::demote(int demeritAmount) {
    
    
        setSalary(getSalary() - demeritAmount);
    }
    // 正确设置了 mHired 成员
    void Employee::hire() {
    
    
        mHired = true;
    }
    void Employee::fire() {
    
    
        mHired = false;
    }
    // 使用控制台输出流显示当前雇员的信息
    void Employee::display() const{
    
    
        cout << "Employee: " << getLastName() << ", " << getFirstName() << endl;
        cout << "-------------------------" << endl;
        cout << (isHired() ? "Current Employee" : "Former Employee") << endl;
        cout << "Employee Number: " << getEmployeeNumber() << endl;
        cout << "Salary: $" << getSalary() << endl;
        cout << endl;
    }
    // 许多获取器(get)和设置器(set)执行获取值以及设置值的任务
    // 使用这些获取器和设置器的方式要优于将数据成员设置为 public
    // 1. 方便设置断点,简化调试
    // 2. 修改类中存储数据的方式时,只需修改这些获取器和设置器
    void Employee::setFirstName(const string &firstName) {
    
    
        mFirstName = firstName;
    }
    const string& Employee::getFirstName() const {
    
    
        return mFirstName;
    }
    
    void Employee::setLastName(const string& lastName) {
    
    
        mLastName = lastName;
    }
    
    const string& Employee::getLastName() const {
    
    
        return mLastName;
    }
    
    void Employee::setEmployeeNumber(int employeeNumber) {
    
    
        mEmployeeNumber = employeeNumber;
    }
    
    int Employee::getEmployeeNumber() const {
    
    
        return mEmployeeNumber;
    }
    
    void Employee::setSalary(int salary) {
    
    
        mSalary = salary;
    }
    
    int Employee::getSalary() const {
    
    
        return mSalary;
    }
    
    bool Employee::isHired() const {
    
    
        return mHired;
    } 
}

5.3 Clase de base de datos

  • Base de datos.h
#pragma

#include <iostream>
#include <vector>
#include "Employee.h"

namespace Records {
    
    
    const int kFirstEmployeeNumber = 1000;

    class Database {
    
    
    public:
        Employee &addEmployee(const std::string &firstName,
                              const std::string &lastName);
        // 1. 允许按雇员号进行检索
        Employee &getEmployee(int employeeNumber);
        // 2. 要求提供雇员姓名
        Employee &getEmployee(const std::string &firstName,
                              const std::string &lastName);
        // 输出所有雇员、当前在职雇员和离职雇员的方法
        void displayAll() const;
        void displayCurrent() const;
        void displayFormer() const;

    private:
        std::vector<Employee> mEmployees;
        // 跟踪新雇员的雇员号
        int mNextEmployeeNumber = kFirstEmployeeNumber;
    };
}
  • Base de datos.cpp
#include <iostream>
#include <stdexcept>
#include "Database.h"

using namespace std;

namespace Records {
    
    
    Employee &Database::addEmployee(const string &firstName,
                                    const string &lastName) {
    
    
        // 使用输入参数初始化成员变量
        Employee theEmployee(firstName, lastName);
        // 数据成员 mNextEmployeeNumber 值递增,因此下一个雇员将获得新编号
        theEmployee.setEmployeeNumber(mNextEmployeeNumber++);
        theEmployee.hire(); // 将其聘用状态设置为 "已聘用"
        mEmployees.push_back(theEmployee);
        // 返回 mEmployees 向量中的最后一个元素,即新添加的员工
        return mEmployees[mEmployees.size() - 1];
    }

    Employee &Database::getEmployee(int employeeNumber) {
    
    
        // 基于区间的 for 循环遍历 mEmployees 中所有雇员
        for (auto &employee : mEmployees) {
    
    
            if (employee.getEmployeeNumber() == employeeNumber) {
    
    
                return employee;
            }
        }
        throw logic_error("No employee found.");
    }
    Employee &Database::getEmployee(const string &firstName,
                                    const string &lastName) {
    
    
        for (auto &employee : mEmployees) {
    
    
            if (employee.getFirstName() == firstName &&
                employee.getLastName() == lastName) {
    
    
                    return employee;
            }
        }
        throw logic_error("No employee found.");
    }
    void Database::displayAll() const {
    
    
        for (const auto &employee : mEmployees) {
    
    
            employee.display();
        }
    }
    void Database::displayCurrent() const {
    
    
        for (const auto &employee : mEmployees) {
    
    
            if (employee.isHired()) {
    
    
                employee.display();
            }
        }
    }
    void Database::displayFormer() const {
    
    
        for (const auto &employee : mEmployees) {
    
    
            if (!employee.isHired()) {
    
    
                employee.display();
            }
        }
    }
}

5.4 Interfaz de usuario

  • InterfazUsuario.cpp
#include <iostream>
#include <stdexcept>
#include <exception>
#include "Database.h"

using namespace std;
using namespace Records;

int displayMenu();
void doHire(Database& db);
void doFire(Database& db);
void doPromote(Database& db);

int main(int argc, char *argv[]) {
    
    
    Database employeeDB;

    bool done = false;
    while (!done) {
    
    
        int selection = displayMenu();
        switch (selection) {
    
    
        case 0:
            done = true;
            break;
        case 1:
            doHire(employeeDB);
            break;
        case 2:
            doFire(employeeDB);
            break;
        case 3:
            doPromote(employeeDB);
            break;
        case 4:
            employeeDB.displayAll();
            break;
        case 5:
            employeeDB.displayCurrent();
            break;
        case 6:
            employeeDB.displayFormer();
            break;
        default:
            cerr << "Unknown command." << endl;
            break;
        }
    }
    return 0;
}

int displayMenu() {
    
    
    int selection;

    cout << endl;
    cout << "Employee Database" << endl;
    cout << "-----------------" << endl;
    cout << "1) Hire a new employee" << endl;
    cout << "2) Fire an employee" << endl;
    cout << "3) Promote an employee" << endl;
    cout << "4) List all employees" << endl;
    cout << "5) List all current employees" << endl;
    cout << "6) List all former employees" << endl;
    cout << "0) Quit" << endl;
    cout << endl;
    cout << "---> ";

    cin >> selection;

    return selection;
}
// 获取用户输入的新的雇员的姓名,并通知数据库添加这个雇员
void doHire(Database &db) {
    
    
    string firstName;
    string lastName;

    cout << "First name?";
    cin >> firstName;
    cout << "Last name?";
    cin >> lastName;

    db.addEmployee(firstName, lastName);
}
// 要求数据库根据雇员号找到雇员的记录
void doFire(Database &db) {
    
    
    int employeeNumber;

    cout << "Employee number? ";
    cin >> employeeNumber;

    try {
    
    
        Employee &emp = db.getEmployee(employeeNumber);
        emp.fire();
        cout << "Employee " << employeeNumber << " terminated." << endl;
    } catch (const std::logic_error &exception) {
    
    
        cerr << "Unable to terminate employee: " << exception.what() << endl;
    }
}

void doPromote(Database& db) {
    
    
    int employeeNumber;
    int raiseAmount;

    cout << "Employee number? ";
    cin >> employeeNumber;
    cout << "How much of a raise? ";
    cin >> raiseAmount;

    try {
    
    
        Employee& emp = db.getEmployee(employeeNumber);
        emp.promote(raiseAmount);
    } catch (const std::logic_error& exception) {
    
    
        cerr << "Unable to promote employee: " << exception.what() << endl;
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_42994487/article/details/131065119
Recomendado
Clasificación