[C ++ Drifting] Comprender las características de los objetos de clases y objetos en un artículo

En C++, las clases y los objetos son los conceptos básicos de la programación orientada a objetos. Una clase es un tipo de datos abstracto que se utiliza para describir las propiedades y el comportamiento de un objeto. Un objeto es una instancia de una clase que incorpora las propiedades y el comportamiento de la clase. Este artículo presentará las características de los objetos de clases y objetos en C++, centrándose en las referencias a objetos.
Insertar descripción de la imagen aquí



Enlaces relacionados:
Un artículo para comprender la encapsulación de clases y objetos, un artículo para comprender la aplicación avanzada de funciones de referencia
en C++

1. Constructor y destructor

Cuando creamos una clase, puede tener algunas variables miembro y funciones miembro. Los constructores y destructores son funciones miembro especiales de una clase que se utilizan para inicializar y limpiar objetos .

  1. La función del constructor es inicializar las variables miembro del objeto al crear el objeto. Tiene el mismo nombre que la clase, no tiene tipo de retorno y puede tener parámetros. Un constructor puede tener varias versiones sobrecargadas y el constructor que se utilice depende del tipo y la cantidad de parámetros pasados. El constructor se llama automáticamente cuando se crea el objeto.

  2. La función del destructor es limpiar los recursos del objeto cuando se destruye el objeto. Tiene el mismo nombre que la clase, precedido por una tilde (~), no tiene tipo de retorno y no acepta parámetros. Sólo puede haber un destructor y no se puede sobrecargar. El destructor se llama automáticamente cuando se destruye el objeto.

Código de muestra:

class Person {
    
    
public:
    string name;
    int age;

    // 默认构造函数
    Person() {
    
    
        name = "Unknown";
        age = 0;
        cout << "Default constructor called" << endl;
    }

    // 带参数的构造函数
    Person(string n, int a) {
    
    
        name = n;
        age = a;
        cout << "Parameterized constructor called" << endl;
    }

    // 析构函数
    ~Person() {
    
    
        cout << "Destructor called" << endl;
    }
};

int main() {
    
    
    // 使用默认构造函数创建对象
    Person p1;
    cout << "Name: " << p1.name << ", Age: " << p1.age << endl;

    // 使用带参数的构造函数创建对象
    Person p2("John", 25);
    cout << "Name: " << p2.name << ", Age: " << p2.age << endl;

    return 0;
}

Resultado de salida:

Default constructor called
Name: Unknown, Age: 0
Parameterized constructor called
Name: John, Age: 25
Destructor called
Destructor called

Explicación del código:
en el ejemplo anterior, definimos una Personclase denominada que tiene dos variables miembro: namey age. Usamos constructores y destructores para inicializar y limpiar estas variables miembro.

Primero, definimos un constructor predeterminado, que no toma parámetros. En el constructor predeterminado, lo estableceremos en name0 e imprimiremos un mensaje para indicar que se llamó al constructor.Unknownage

A continuación, definimos un constructor parametrizado que acepta un parámetro de cadena y un parámetro de número entero. En el constructor con parámetros, asignamos los valores de los parámetros pasados ​​a namelas agevariables miembro e imprimimos un mensaje para indicar que se ha llamado al constructor.

En la función principal, primero creamos un p1objeto nombrado usando el constructor predeterminado. Como no se pasan parámetros, se llama al constructor predeterminado. Luego, imprimimos los valores de las variables miembro y p1del objeto , que son y 0 respectivamente .nameageUnknown

A continuación, creamos un objeto nombrado usando un constructor con parámetros p2. Pasamos cadenas "John"y números enteros 25como parámetros, por lo que se llama al constructor con parámetros. Luego, imprimimos los valores de las variables miembro y p2del objeto , que son y 25 respectivamente .nameageJohn

Al final del programa, los objetos p1quedan p2fuera de su alcance, por lo que son destruidos. Durante la destrucción de objetos, se llama automáticamente al destructor. En el ejemplo, imprimimos un mensaje en el destructor para indicar que se llamó al destructor.


2. Clasificación y llamada de funciones.

1. Clasificación

  1. Constructor predeterminado: si una clase no define explícitamente un constructor, el compilador genera automáticamente un constructor predeterminado. El constructor predeterminado no tiene parámetros y no realiza ninguna operación de inicialización. Se llama implícitamente al crear un objeto.

  2. Constructor parametrizado: un constructor parametrizado acepta uno o más parámetros y los utiliza para inicializar las variables miembro del objeto. Se llaman cuando se crea el objeto y el constructor que se utiliza se determina en función del tipo y la cantidad de parámetros pasados.

  3. Constructor de copia: el constructor de copia es un constructor especial que acepta una referencia a un objeto similar como parámetro y utiliza el valor del objeto para inicializar el objeto recién creado. Se llama al constructor de copia cuando:

    • Cuando se utiliza un objeto para inicializar otro objeto
    • Pasar objeto para que funcione como argumento de función
    • Devolver objeto de la función

2. Método de llamada

  1. Llamada directa: puede llamar al constructor directamente agregando paréntesis al nombre de la clase. Por ejemplo:MyClass obj(10);

  2. Llamada implícita: el constructor se llama implícitamente al crear un objeto. Por ejemplo: MyClass obj = MyClass(10);, donde se llama al constructor con parámetros para crear el objeto.

  3. Inicialización de copia: cuando se utiliza un objeto para inicializar otro objeto, se llama al constructor de copia. Por ejemplo:MyClass obj1(10); MyClass obj2 = obj1;

  4. Paso de parámetros de función: cuando un objeto se pasa a una función como parámetro de función, se llama al constructor de copia. Por ejemplo: void func(MyClass obj);, se llamará al constructor de copia al llamar a la función func(obj).

  5. La función devuelve un objeto: cuando una función devuelve un objeto, se llama al constructor de copia. Por ejemplo:MyClass func() { return MyClass(10); }

3. Código de muestra

#include <iostream>
using namespace std;

class MyClass {
    
    
public:
    int num;

    // 默认构造函数
    MyClass() {
    
    
        num = 0;
        cout << "Default constructor called" << endl;
    }

    // 带参数的构造函数
    MyClass(int n) {
    
    
        num = n;
        cout << "Parameterized constructor called" << endl;
    }

    // 拷贝构造函数
    MyClass(const MyClass& obj) {
    
    
        num = obj.num;
        cout << "Copy constructor called" << endl;
    }

    // 析构函数
    ~MyClass() {
    
    
        cout << "Destructor called" << endl;
    }
};

int main() {
    
    
    // 直接调用构造函数
    MyClass obj1(10);

    // 隐式调用构造函数
    MyClass obj2 = MyClass(20);

    // 拷贝初始化
    MyClass obj3(obj1);

    // 函数参数传递
    void func(MyClass obj);
    func(obj1);

    // 函数返回对象
    MyClass func();
    MyClass obj4 = func();

    return 0;
}

4. Resultados de salida:

Parameterized constructor called
Parameterized constructor called
Copy constructor called
Copy constructor called
Destructor called
Destructor called
Destructor called
Destructor called

5. Explicación del código

En el ejemplo anterior, hemos definido una MyClassclase denominada que contiene una variable miembro numy varios constructores. Creamos múltiples objetos y llamamos al constructor de diferentes maneras.

Primero, creamos el objeto llamando directamente al constructor obj1, que llama al constructor con parámetros. Luego creamos el objeto llamando implícitamente al constructor obj2, que también llama al constructor con parámetros.

A continuación, creamos el objeto mediante copy-initialization obj3, que obj1lo inicializa con el valor del objeto. Durante el proceso de inicialización de la copia, se llamará al constructor de la copia.

Luego, definimos una función funcque acepta un MyClassobjeto como parámetro. Cuando se llama a una función func(obj1), se llama al constructor de copia para obj1pasar el objeto a la función.

Finalmente, definimos una función funcque devuelve un MyClassobjeto. El constructor de copia se llama cuando se llama a una función func()y se asigna el objeto devuelto .obj4

Al final del programa, todos los objetos salen de su alcance, por lo que son destruidos. Durante la destrucción de objetos, se llama automáticamente al destructor. En el ejemplo, imprimimos un mensaje en el destructor para indicar que se llamó al destructor.


3. Momento del constructor de copias

  1. Cuando se usa un objeto para inicializar otro objeto : cuando se usa un objeto existente para inicializar un objeto nuevo, se llama al constructor de copia. Por ejemplo:
class MyClass {
    
    
public:
    MyClass(int value) : m_value(value) {
    
    }
    MyClass(const MyClass& other) : m_value(other.m_value) {
    
    
        std::cout << "Copy constructor called" << std::endl;
    }
private:
    int m_value;
};

MyClass obj1(10);
MyClass obj2 = obj1; // 调用拷贝构造函数
  1. Pasar un objeto a una función como argumento de función : cuando un objeto se pasa a una función como argumento de función, se llama al constructor de copia. Por ejemplo:
class MyClass {
    
    
public:
    MyClass(int value) : m_value(value) {
    
    }
    MyClass(const MyClass& other) : m_value(other.m_value) {
    
    
        std::cout << "Copy constructor called" << std::endl;
    }
private:
    int m_value;
};

void func(MyClass obj) {
    
    
    // Do something with obj
}

MyClass obj1(10);
func(obj1); // 调用拷贝构造函数

  1. Devolver un objeto desde una función : cuando se devuelve un objeto desde una función, se llama al constructor de copia. Por ejemplo:
class MyClass {
    
    
public:
    MyClass(int value) : m_value(value) {
    
    }
    MyClass(const MyClass& other) : m_value(other.m_value) {
    
    
        std::cout << "Copy constructor called" << std::endl;
    }
private:
    int m_value;
};

MyClass func() {
    
    
    MyClass obj(10);
    return obj; // 调用拷贝构造函数
}

  1. Cuando se utiliza un objeto de clase para la asignación, también se llamará al constructor de copia . Por ejemplo:
class MyClass {
    
    
public:
    MyClass(int value) : m_value(value) {
    
    }
    MyClass(const MyClass& other) : m_value(other.m_value) {
    
    
        std::cout << "Copy constructor called" << std::endl;
    }
private:
    int m_value;
};

MyClass obj1(10);
MyClass obj2;
obj2 = obj1; // 调用拷贝构造函数

Cabe señalar que el compilador a veces realiza optimizaciones para evitar llamadas innecesarias al constructor de copias. Esta optimización se denomina "elisión de copia". En algunos casos, el compilador puede mover el valor de un objeto directamente de una ubicación a otra en lugar de realizar una llamada al constructor de copia. Esto mejora el rendimiento pero no llama al constructor de copias.


4. Reglas de llamada del constructor

Las reglas de llamada del constructor son las siguientes:

  1. Constructor predeterminado : si no se define explícitamente ningún constructor, el compilador generará automáticamente un constructor predeterminado. El constructor predeterminado no tiene parámetros y realiza operaciones de inicialización predeterminadas. Cuando se crea un objeto, si no se proporcionan parámetros, se llama al constructor predeterminado.

  2. Constructor parametrizado : el constructor parametrizado acepta uno o más parámetros y los utiliza para inicializar las variables miembro del objeto. Cuando se crea un objeto, si se proporcionan parámetros, se llama al constructor parametrizado correspondiente.

  3. Constructor de copia : el constructor de copia acepta un objeto del mismo tipo como parámetro y utiliza el valor del objeto para inicializar el nuevo objeto. El constructor de copia se puede utilizar en escenarios como la inicialización de copia de objetos, el paso de parámetros de función y los objetos de retorno de función.

  4. Constructor de movimiento : el constructor de movimiento es una nueva característica introducida en C ++ 11. Acepta una referencia de valor como parámetro y utiliza el valor del parámetro para inicializar un nuevo objeto. Los constructores de movimientos se utilizan a menudo para mejorar el rendimiento cuando se transfiere la propiedad de los recursos de un objeto.

Las reglas de llamada del constructor son las siguientes:

  • Cuando se crea un objeto, se selecciona y llama al constructor apropiado según el tipo y la cantidad de parámetros proporcionados. Si no se proporcionan parámetros, se llama al constructor predeterminado.
  • Se llama al constructor de copia cuando se utiliza un objeto para inicializar otro objeto.
  • El constructor de copia se llama cuando se pasa un objeto a una función como parámetro de función.
  • Cuando una función devuelve un objeto, se llama al constructor de copia.
  • Cuando se utiliza un objeto de clase para la asignación, también se llamará al constructor de copia.
  • En algunos casos, el compilador realizará optimizaciones para evitar llamadas innecesarias al constructor de copias. Esta optimización se denomina "elisión de copia".

5. Copia profunda y copia superficial

La copia superficial se refiere a copiar el valor de un objeto a otro objeto, incluidas las variables miembro del objeto. Esto significa que ambos objetos comparten la misma dirección de memoria y las modificaciones en uno de ellos afectarán al otro. La copia superficial solo copia el nivel de superficie del objeto y no copia los recursos que posee el objeto.

La copia profunda se refiere a copiar el valor de un objeto a otro objeto y asignar espacio de memoria independiente para el nuevo objeto. De esta manera, los dos objetos tienen espacios de memoria independientes y las modificaciones en un objeto no afectarán al otro. Una copia profunda copia recursivamente todas las variables miembro de un objeto, incluidos los recursos que posee el objeto.

Código de muestra:

#include <iostream>
#include <cstring>

class Person {
    
    
public:
    Person(const char* name, int age) {
    
    
        m_name = new char[strlen(name) + 1];
        strcpy(m_name, name);
        m_age = age;
    }
    
    // 拷贝构造函数
    Person(const Person& other) {
    
    
        m_name = new char[strlen(other.m_name) + 1];
        strcpy(m_name, other.m_name);
        m_age = other.m_age;
    }
    
    // 析构函数
    ~Person() {
    
    
        delete[] m_name;
    }
    
    // 打印信息
    void printInfo() {
    
    
        std::cout << "Name: " << m_name << ", Age: " << m_age << std::endl;
    }
    
private:
    char* m_name;
    int m_age;
};

int main() {
    
    
    Person person1("Alice", 25);
    Person person2 = person1; // 浅拷贝
    person1.printInfo(); // 输出:Name: Alice, Age: 25
    person2.printInfo(); // 输出:Name: Alice, Age: 25
    
    person2.printInfo(); // 输出:Name: Alice, Age: 25
    person2.printInfo(); // 输出:Name: Alice, Age: 25
    
    person1.printInfo(); // 输出:Name: Bob, Age: 30
    person2.printInfo(); // 输出:Name: Alice, Age: 25
    
    return 0;
}

En el ejemplo anterior, definimos una Personclase que contiene una variable miembro de tipo cadena m_namey una variable miembro entera m_age. En el constructor, usamos newoperadores para m_nameasignar un espacio de memoria separado y copiar la cadena en ese espacio de memoria.

Luego, creamos un person1objeto y le asignamos su valor person2. Dado que el constructor de copia predeterminado es una copia superficial, el puntero person2del objeto m_nameapunta al person1mismo espacio de memoria que el objeto. Cuando modificamos person2el objeto m_name, en realidad modificamos person1el objeto m_name. Ésta es la característica de la copia superficial.

Para implementar una copia profunda, necesitamos personalizar el constructor de copia, asignarle m_nameun espacio de memoria separado y copiar la cadena en este espacio. De esta manera, person2el objeto tiene su propio m_nameespacio de memoria independiente y sus modificaciones no afectarán person1al objeto.

En resumen, la copia superficial solo copia el nivel de superficie del objeto, mientras que la copia profunda copia recursivamente todas las variables miembro del objeto, incluidos los recursos que posee el objeto. La copia profunda requiere un constructor de copia personalizado para implementarse.


6. Lista de inicialización

La lista de inicialización es un método para inicializar variables miembro en el constructor y puede usarse para implementar copia profunda.

En el ejemplo anterior, podemos usar una lista de inicialización para implementar una copia profunda sin asignar memoria manualmente ni copiar la cadena en el constructor de copia.

A continuación se muestra un ejemplo del uso de una lista de inicialización para implementar una copia profunda:

#include <iostream>
#include <cstring>

class Person {
    
    
public:
    Person(const char* name, int age) : m_age(age) {
    
    
        m_name = new char[strlen(name) + 1];
        strcpy(m_name, name);
    }
    
    // 拷贝构造函数
    Person(const Person& other) : m_age(other.m_age) {
    
    
        m_name = new char[strlen(other.m_name) + 1];
        strcpy(m_name, other.m_name);
    }
    
    // 析构函数
    ~Person() {
    
    
        delete[] m_name;
    }
    
    // 打印信息
    void printInfo() {
    
    
        std::cout << "Name: " << m_name << ", Age: " << m_age << std::endl;
    }
    
private:
    char* m_name;
    int m_age;
};

int main() {
    
    
    Person person1("Alice", 25);
    Person person2 = person1; // 深拷贝
    person1.printInfo(); // 输出:Name: Alice, Age: 25
    person2.printInfo(); // 输出:Name: Alice, Age: 25
    
    person2.printInfo(); // 输出:Name: Alice, Age: 25
    person2.printInfo(); // 输出:Name: Alice, Age: 25
    
    person1.printInfo(); // 输出:Name: Alice, Age: 25
    person2.printInfo(); // 输出:Name: Alice, Age: 25
    
    return 0;
}

En el ejemplo anterior, asignamos un espacio de memoria separado en la lista de inicialización del constructor y copiamos la cadena en ese espacio. De esta manera, person2el objeto tiene su propio m_nameespacio de memoria independiente y sus modificaciones no afectarán person1al objeto.

El uso de una lista de inicialización puede simplificar el código y garantizar que las variables miembro se hayan inicializado correctamente cuando se construye el objeto. Esto es útil para implementar copias profundas.


7. Objetos de clase como miembros de clase.

Cuando las variables miembro de una clase son objetos de otra clase, debemos copiar estas variables miembro correctamente en el constructor de copia.

Aquí hay un ejemplo donde Personuna de las variables miembro de una clase es Addressun objeto de la clase:

#include <iostream>
#include <cstring>

class Address {
    
    
public:
    Address(const char* city, const char* street) {
    
    
        m_city = new char[strlen(city) + 1];
        strcpy(m_city, city);
        
        m_street = new char[strlen(street) + 1];
        strcpy(m_street, street);
    }
    
    Address(const Address& other) {
    
    
        m_city = new char[strlen(other.m_city) + 1];
        strcpy(m_city, other.m_city);
        
        m_street = new char[strlen(other.m_street) + 1];
        strcpy(m_street, other.m_street);
    }
    
    ~Address() {
    
    
        delete[] m_city;
        delete[] m_street;
    }
    
    void printInfo() {
    
    
        std::cout << "City: " << m_city << ", Street: " << m_street << std::endl;
    }
    
private:
    char* m_city;
    char* m_street;
};

class Person {
    
    
public:
    Person(const char* name, int age, const Address& address) : m_age(age), m_address(address) {
    
    
        m_name = new char[strlen(name) + 1];
        strcpy(m_name, name);
    }
    
    Person(const Person& other) : m_age(other.m_age), m_address(other.m_address) {
    
    
        m_name = new char[strlen(other.m_name) + 1];
        strcpy(m_name, other.m_name);
    }
    
    ~Person() {
    
    
        delete[] m_name;
    }
    
    void printInfo() {
    
    
        std::cout << "Name: " << m_name << ", Age: " << m_age << std::endl;
        m_address.printInfo();
    }
    
private:
    char* m_name;
    int m_age;
    Address m_address;
};

int main() {
    
    
    Address address("New York", "Broadway");
    Person person1("Alice", 25, address);
    Person person2 = person1; // 深拷贝
    person1.printInfo(); // 输出:Name: Alice, Age: 25, City: New York, Street: Broadway
    person2.printInfo(); // 输出:Name: Alice, Age: 25, City: New York, Street: Broadway
    
    return 0;
}

En el ejemplo anterior, Personuna de las variables miembro de la clase es Addressun objeto de la clase. En Personel constructor de copia de la clase, usamos el constructor de copia para copiar el objeto correctamente Address. De esta manera, cuando copiamos un Personobjeto, Personel objeto y sus Addressobjetos variables miembro se copiarán en profundidad.

Cabe señalar que en Personel destructor de la clase, solo necesitamos liberar m_nameel espacio de memoria de las variables miembro, porque m_addressel espacio de memoria de las variables miembro se liberará Addressen el destructor de la clase.

En resumen, cuando las variables miembro de una clase son objetos de otra clase, debemos copiar correctamente estas variables miembro en el constructor de copia para implementar una copia profunda.


8. Miembros estáticos

Las variables miembro estáticas pertenecen a la clase misma y no a instancias de la clase. Por lo tanto, no es necesario copiar variables miembro estáticas en el constructor de copia porque se comparten entre todas las instancias de la clase.

A continuación se muestra un ejemplo en el que la Personclase tiene una variable miembro estática count:

#include <iostream>

class Person {
    
    
public:
    Person(const char* name, int age) : m_age(age) {
    
    
        m_name = new char[strlen(name) + 1];
        strcpy(m_name, name);
        count++;
    }
    
    Person(const Person& other) : m_age(other.m_age) {
    
    
        m_name = new char[strlen(other.m_name) + 1];
        strcpy(m_name, other.m_name);
        count++;
    }
    
    ~Person() {
    
    
        delete[] m_name;
        count--;
    }
    
    static int getCount() {
    
    
        return count;
    }
    
private:
    char* m_name;
    int m_age;
    static int count;
};

int Person::count = 0;

int main() {
    
    
    Person person1("Alice", 25);
    Person person2 = person1; // 深拷贝
    std::cout << "Count: " << Person::getCount() << std::endl; // 输出:Count: 2
    
    return 0;
}

En el ejemplo anterior, Personla clase tiene una variable miembro estática countque registra la cantidad de objetos creados Person. En el constructor, realizamos countun seguimiento del número de objetos incrementándolo, y en el destructor actualizamos countel número de objetos disminuyéndolo.

En el constructor de copia, no necesitamos copiar la variable miembro estática countporque pertenece a la clase misma y no a una instancia de la clase. Por lo tanto, solo es necesario copiar variables miembro no estáticas en el constructor de copia.

En resumen, no es necesario copiar las variables miembro estáticas en el constructor de copia porque pertenecen a la clase misma y no a instancias de la clase.

Supongo que te gusta

Origin blog.csdn.net/Goforyouqp/article/details/132791968
Recomendado
Clasificación