<C++> Clases y Objetos - Orientado a Objetos

1. Orientado a objetos

El lenguaje C está orientado a procesos , centrándose en el proceso, analizando los pasos para resolver el problema y resolviendo el problema paso a paso a través de llamadas a funciones.

C ++ se basa en la orientación a objetos , centrándose en los objetos, dividiendo una cosa en diferentes objetos y confiando en la interacción entre los objetos para completar.

2. clase

Solo se pueden definir variables en la estructura del lenguaje C. En C++, no solo se pueden definir variables sino también funciones en la estructura. ,

C++ es compatible con el uso de la estructura C y C++ actualiza la estructura a la clase.

Ejemplo:

typedef struct ListNode {
    
    
    int val;
    struct ListNode *next;
} LTN;

// C struct ListNode是类型

struct ListNode {
    
    
    int val;
    ListNode *next;
};

Las funciones miembro se definen directamente dentro de la clase:

struct Stack {
    
    
    // 成员函数
    void Init(int n = 4) {
    
    
        a = (int *) malloc(sizeof(int) * n);
        if (nullptr == a) {
    
    
            perror("malloc申请空间失败");
            return;
        }

        capacity = n;
        size = 0;
    }

    void Push(int x) {
    
    
        //...
        a[size++] = x;
    }

    int Top(){
    
    
    }

    void Destroy(){
    
    

    }


    // 成员变量
    int *a;
    int size;
    int capacity;
};

int main(){
    
    
    Stack s;
    s.Init(10);
    s.Push(1);
    int x = s.Top();
    s.Destroy();
    return 0;
}

Se prefiere la definición de la estructura anterior para usar class en lugar de C++ .

3. Definición de clase

Una definición de clase se utiliza para declarar y definir un nuevo tipo de datos definido por el usuario, que describe un conjunto de miembros de datos y funciones de miembros.

class ClassName {
    
    
private:
    // 私有成员变量

public:
    // 公有成员变量

    // 构造函数

    // 成员函数
};  // 这里有一个分号

Los contenidos del cuerpo de la clase se denominan miembros de la clase : las variables de la clase se denominan atributos o variables miembro de la clase ; las funciones de la clase se denominan métodos o funciones miembro de la clase**.

Hay dos formas de definir una clase:

1. La declaración y la definición se colocan en el cuerpo de la clase. Nota: si la función miembro está definida en la clase , el compilador puede tratarla como una función en línea .

class Person {
    
    
private:
    char *_name;
    char *_sex;
    int _age;

public:
    void showInfo() {
    
    
        cout << _name << "-" << _sex << "-" << _age << endl;
    }
};

2. La declaración de la clase se coloca en el archivo .h y la definición de la función miembro se coloca en el archivo .cpp. Nota: el nombre de la clase debe agregarse antes del nombre de la función miembro:

inserte la descripción de la imagen aquí

En general, el segundo enfoque es más deseable.

Sugerencias para reglas de nomenclatura para variables miembro

Echemos un vistazo a esta función, ¿no es muy rígida?

class Date {
    
    
public:
    void Init(int year) {
    
    
        // 这里的year到底是成员变量,还是函数形参?
        year = year;
    }

private:
    int year;
};

Por lo que generalmente se recomienda que:

class Date {
    
    
public:
    void Init(int year) {
    
    
        _year = year;
    }

private:
    int _year;   //成员变量前加上_,用以区分
};

4. Calificadores de acceso a clases y encapsulación

La forma de C++ para lograr la encapsulación: use clases para combinar las propiedades y los métodos del objeto para que el objeto sea más completo y proporcione selectivamente su interfaz a usuarios externos a través de derechos de acceso.

4.1 Calificadores de acceso

**Calificadores de acceso: **Los calificadores de acceso se utilizan para controlar la visibilidad y los derechos de acceso de los miembros de una clase (variables de miembro y funciones de miembro) dentro y fuera de la clase. C++ proporciona tres calificadores de acceso principales: public, privatey protected.

  1. público :
    • publicLos miembros son visibles tanto dentro como fuera de la clase.
    • Se puede acceder a estos miembros desde cualquier lugar, incluido el código fuera de la clase.
    • publicLos miembros generalmente se usan para representar la interfaz de una clase y se proporcionan para que los use un código externo.
  2. privado :
    • privateLos miembros solo son visibles dentro de la clase y están ocultos del código externo.
    • El código externo no puede acceder directamente ni modificar estos miembros.
    • privateLos miembros se utilizan a menudo para encapsular los detalles de implementación de una clase para garantizar la seguridad y el encapsulamiento de los datos.
  3. protegido :
    • protectedLos miembros son visibles dentro de la clase y también son visibles para las clases derivadas (clases heredadas).
    • Para el código fuera de la clase, protectedlos miembros privatese comportan como miembros y no son accesibles directamente.

No se puede acceder directamente a los miembros modificados protegidos y privados fuera de la clase. ¿Cuál es la diferencia entre ellos? Esto implica el concepto de herencia.

class Base {
    
    
private:
    int privateVar;    // 只能在 Base 类内部访问

protected:
    int protectedVar;  // Base 类内部和派生类内部都可访问
};

//Derived类是继承Base类的,继承在后面会介绍
class Derived : public Base {
    
    
public:
    void AccessBaseMembers() {
    
    
        // 可以访问 Base 类的 protected 成员
        protectedVar = 10;
        // 但不能访问 privateVar,因为它是 private 的
    }
};

La clase Derivada hereda de la clase Base, que se presentará en capítulos posteriores.

Aviso:

Los ámbitos de acceso comienzan en la ocurrencia de este calificador de acceso hasta la siguiente ocurrencia del calificador de acceso

Si no hay un calificador de acceso detrás, el alcance irá a }, que es el final de la clase.

El permiso de acceso predeterminado de class es privado y struct es público (porque struct es compatible con C)

4.2 Embalaje

Las tres características principales de la orientación a objetos: encapsulación, herencia y polimorfismo.

En la etapa de clase y objeto, se trata principalmente de estudiar las características de encapsulación de la clase, entonces, ¿qué es la encapsulación?

La encapsulación es un concepto importante en la programación orientada a objetos. Se refiere a la combinación de datos (variables miembro) y métodos de manipulación de datos (funciones miembro) para formar una clase y restringir el acceso externo directo a los datos dentro de la clase. El propósito de la encapsulación es ocultar los detalles de implementación interna de la clase, proporcionar cierto control de acceso para administrar y mantener mejor el código y garantizar la seguridad y la coherencia de los datos.

La encapsulación es esencialmente una gestión que facilita a los usuarios el uso de clases . Por ejemplo: para un dispositivo tan complejo como una computadora, lo único que se proporciona al usuario es el botón de encendido, la entrada del teclado, el monitor, el conector USB, etc., lo que le permite al usuario interactuar con la computadora y completar los asuntos diarios. Pero, de hecho, el verdadero trabajo de la computadora son algunos componentes de hardware como la CPU, la tarjeta gráfica y la memoria.

Para los usuarios de computadoras, no es necesario que se preocupen por los componentes centrales internos, como la disposición de los circuitos en la placa base, el diseño interior de la CPU, etc. Los usuarios solo necesitan saber cómo encender la computadora y cómo para interactuar con la computadora a través del teclado y el mouse. Por lo tanto, cuando el fabricante de la computadora sale de fábrica, coloca un caparazón en el exterior para ocultar los detalles internos de implementación y solo proporciona interruptores externos, conectores para mouse y teclado, etc., para que los usuarios puedan interactuar con la computadora.

La encapsulación en el lenguaje C++ puede combinar orgánicamente datos y métodos de manipulación de datos a través de clases, ocultar detalles de implementación interna de objetos a través de derechos de acceso y controlar qué métodos se pueden usar directamente fuera de la clase.

5. Ámbito de clase

El alcance de la clase se refiere a la visibilidad y accesibilidad de los miembros dentro de la clase. El alcance de una clase se divide en las siguientes partes:

Alcance en clase :

  • Dentro de la clase, todos los miembros (variables miembro y funciones miembro) pueden acceder entre sí sin prefijo.
  • Las funciones miembro pueden acceder directamente a los miembros privados de una clase.
class MyClass {
    
    
private:
    int privateVar;

public:
    void publicFunc() {
    
    
        privateVar = 10;  // 直接访问私有成员
    }
};

Alcance fuera de clase :

  • Al acceder a un miembro fuera de la clase, debe usar el operador de acceso a miembros .o ->(en el caso de un objeto de puntero) y ::.
  • Solo se puede acceder directamente fuera de la clase a los miembros públicos y a los miembros protegidos (visibles dentro de las clases derivadas).
MyClass obj;
obj.publicFunc();  // 访问公有成员函数

obj.privateVar = 20;  // 错误,无法直接访问私有成员

Alcance de las clases heredadas y derivadas :

  • Una clase derivada puede acceder a los miembros públicos y protegidos de su clase base, pero no a los miembros privados de la clase base.
  • Una clase derivada puede usar operadores de acceso a miembros para acceder a miembros heredados de una clase base.
class Derived : public MyClass {
    
    
public:
    void derivedFunc() {
    
    
        privateVar = 30;  // 错误,不能访问基类的私有成员

        publicFunc();     // 可以访问继承来的公有成员函数
    }
};

6. Instanciación de clases

El proceso de creación de un objeto con un tipo de clase se llama instanciación de la clase ,

  1. Una clase es una descripción de un objeto . Es algo similar a un modelo, que define qué miembros tiene una clase y define una clase sin asignar espacio de memoria real para almacenarla; Piense en ello como una clase para describir información específica del estudiante.
  2. Una clase puede crear instancias de varios objetos, y los objetos instanciados ocupan el espacio físico real y almacenan las variables de los miembros de la clase.
  3. Instanciar un objeto de una clase es como construir una casa utilizando dibujos de diseño arquitectónico en la realidad . Los objetos pueden almacenar datos y ocupar espacio físico.

Ejemplo 1:

#include <iostream>
using namespace std;
class Person {
    
    
public:
    char* _name;
    char* _sex;
    int _age;

public:
    void showInfo() {
    
    
        cout << _name << "-" << _sex << "-" << _age << endl;
    }
};

int main() {
    
    
    //Person::_age = 100;// 编译失败:error C2059
    Person p;   //先实例化对象
    p._age = 100;  //在访问对象成员
    cout << p._age << endl;   //100
    return 0;
}

Ejemplo 2:

#include <iostream>
using namespace std;
// 类 -- 别墅设计图
class Date {
    
    
public:
    // 定义
    void Init(int year, int month, int day) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

private:
    //对数据声明,而不是定义,定义是在创建对象的时候
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    // 类对象实例化 -- 开空间
    // 实例化 -- 用设计图建造一栋栋别墅
    Date d1;
    Date d2;

    //Date.Init(2023,2,2); //错误,调用成员函数,要用对象,不能用类
    d1.Init(2023, 12, 12);
    d2.Init(2022, 1, 1);
    //d1,d2中的成员变量占用不同的内存空间
    return 0;
}

¿Por qué las variables miembro están en el objeto, pero las funciones miembro no están en el objeto?

Cada variable miembro del objeto es diferente y debe almacenarse de forma independiente

Llamar a las funciones miembro de cada objeto es el mismo, colocado en el área pública compartida (segmento de código)

7. Alineación de tamaño y memoria de objetos de clase

7.1 Tamaño del objeto de clase

Cómo calcular el tamaño de un objeto de clase

class A {
    
    
public:
    void PrintA() {
    
    
        cout << _a << endl;
    }

private:
    char _a;
};

**Pregunta:** Una clase puede tener variables miembro y funciones miembro, entonces, ¿qué contiene un objeto de una clase? ¿Cómo calcular el tamaño de una clase?

7.2 Adivinando el método de almacenamiento de objetos de clase

  • El objeto contiene cada miembro de la clase
    inserte la descripción de la imagen aquí
    Defecto: Las variables miembro en cada objeto son diferentes, pero se llama a la misma función.Si se almacena de esta manera, cuando una clase crea múltiples objetos, cada objeto guardará un código, el mismo código es guardado varias veces, desperdiciando espacio . Entonces, ¿cómo resolverlo?

  • Solo se guarda una copia del código y la dirección donde se almacena el código se guarda en el objeto
    inserte la descripción de la imagen aquí

  • Solo se guardan las variables miembro y las funciones miembro se almacenan en segmentos de código público
    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí

Pregunta: Para los tres métodos de almacenamiento anteriores, ¿en qué método almacena la computadora?

#include <iostream>
using namespace std;
// 类中既有成员变量,又有成员函数
class A1 {
    
    
public:
    void f1() {
    
    }

private:
    int _a;
};
// 类中仅有成员函数
class A2 {
    
    
public:
    void f2() {
    
    }
};
// 类中什么都没有---空类
class A3 {
    
    };

int main(){
    
    
    cout<<sizeof(A1)<<endl;   //4
    cout<<sizeof(A2)<<endl;   //1
    cout<<sizeof(A3)<<endl;   //1
    return 0;
}

en conclusión:

El tamaño de una clase es en realidad la suma de las "variables miembro" en la clase. Por supuesto, preste atención a la alineación de la memoria y al tamaño de la clase vacía. La clase vacía es especial. El compilador le da a la clase vacía un byte para identificar el objeto de esta clase.

7.3 Reglas de alineación de memoria para clases

Al igual que las reglas de memoria de la estructura del lenguaje C, puede acceder a las reglas de alineación de la memoria de la estructura en el capítulo Tipo autodefinido del lenguaje C Tipo autodefinido del lenguaje C

8. este puntero

8.1 Derivación de este puntero

Primero definamos una clase de fechaDate

#include <iostream>
using namespace std;
class Date {
    
    
public:
    void Init(int year, int month, int day) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
    
    
        cout << _year << "-" << _month << "-" << _day << endl;
    }

private:
    int _year; // 年
    int _month;// 月
    int _day;  // 日
};

int main() {
    
    
    Date d1, d2;
    d1.Init(2022, 1, 11);
    d2.Init(2022, 1, 12);
    d1.Print();
    d2.Print();
    return 0;
}

En las dos funciones miembro Init e Print en la clase Date anterior, no hay distinción entre diferentes objetos en el cuerpo de la función, por lo que cuando d1 llama a la función Init, ¿cómo sabe la función que el objeto d1 debe establecerse en lugar del d2? ¿objeto?

C++ resuelve este problema introduciendo el puntero this, es decir: el compilador de C++ agrega un parámetro de puntero oculto a cada "función miembro no estática", haciendo que el puntero apunte al objeto actual (el objeto que llama a la función cuando la función es en ejecución), se accede a todas las operaciones de "variables miembro" en el cuerpo de la función a través de este puntero. Es solo que todas las operaciones son transparentes para el usuario, es decir, el usuario no necesita pasarlo y el compilador lo completa automáticamente.

8.2 Características de este puntero

1. El tipo de este puntero: tipo de clase const *, es decir, en las funciones miembro, al puntero this no se le puede asignar un valor.
2. Solo se puede usar dentro de la "función miembro"
3**. Este puntero es esencialmente el parámetro formal de la "función miembro". Cuando el objeto llama a la función miembro, la dirección del objeto se pasa como parámetro real a la este parámetro formal. Por lo tanto, el puntero this no se almacena en el objeto .
4. El puntero this es el primer parámetro de puntero implícito de la "función miembro" . Generalmente, el compilador lo pasa automáticamente a través del registro ecx y no es necesario que lo pase el usuario.

[Falló la transferencia de la imagen del enlace externo, el sitio de origen puede tener un mecanismo de enlace antirrobo, se recomienda guardar la imagen y cargarla directamente (img-ZDTYVzHc-1690953629477) (C:\Users\32784\AppData\Roaming\Typora \typora-user-images\ image-20230802124337941.png)]

Determine si los siguientes tres resultados son fallas, errores de compilación u operaciones normales

#include<iostream>
using namespace std;
class Date{
    
    
public:
    // 定义
    void Init(int year, int month, int day){
    
    
        /*_year = year;
        _month = month;
        _day = day;*/
        cout << this << endl;
        this->_year = year;
        this->_month = month;
        this->_day = day;
    }

    void func(){
    
    
        cout << this << endl;
        cout << "func()" << endl;
    }

//private:
    int _year;  // 声明
    int _month;
    int _day;
};

int main(){
    
    
    Date d1;
    Date d2;
    d1.Init(2022, 2, 2);
    d2.Init(2023, 2, 2);
    
    //判断下面三种结果是运行崩溃,还是编译错误,还是正常运行
    Date* ptr = nullptr;
    //ptr为一个对象指针,ptr->表示访问对象成员
    ptr->Init(2022, 2, 2); 
    //运行崩溃,进入Init函数体,this指针为nullptr,this指针访问对象成员变量,出现空指针解引用。

    ptr->func();           
    // 正常运行,func函数中,没有this指针访问成员变量,不会发生空指针解引用
    (*ptr).func();           
    // 正常运行,(*ptr)等价于ptr,

    return 0;
}

9. Comparación entre el lenguaje C y la implementación C++ de Stack

Implementación del lenguaje C

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
typedef struct Stack {
    
    
    DataType *array;
    int capacity;
    int size;
} Stack;

void StackInit(Stack *ps) {
    
    
    assert(ps);
    ps->array = (DataType *) malloc(sizeof(DataType) * 3);
    if (NULL == ps->array) {
    
    
        assert(0);
        return;
    }
    ps->capacity = 3;
    ps->size = 0;
}

void StackDestroy(Stack *ps) {
    
    
    assert(ps);
    if (ps->array) {
    
    
        free(ps->array);
        ps->array = NULL;
        ps->capacity = 0;
        ps->size = 0;
    }
}

void CheckCapacity(Stack *ps) {
    
    
    if (ps->size == ps->capacity) {
    
    
        int newcapacity = ps->capacity * 2;
        DataType *temp = (DataType *) realloc(ps->array,
                                              newcapacity * sizeof(DataType));
        if (temp == NULL) {
    
    
            perror("realloc申请空间失败!!!");
            return;
        }
        ps->array = temp;
        ps->capacity = newcapacity;
    }
}

void StackPush(Stack *ps, DataType data) {
    
    
    assert(ps);
    CheckCapacity(ps);
    ps->array[ps->size] = data;
    ps->size++;
}

int StackEmpty(Stack *ps) {
    
    
    assert(ps);
    return 0 == ps->size;
}

void StackPop(Stack *ps) {
    
    
    if (StackEmpty(ps))
        return;
    ps->size--;
}

DataType StackTop(Stack *ps) {
    
    
    assert(!StackEmpty(ps));
    return ps->array[ps->size - 1];
}

int StackSize(Stack *ps) {
    
    
    assert(ps);
    return ps->size;
}

int main() {
    
    
    Stack s;
    StackInit(&s);
    StackPush(&s, 1);
    StackPush(&s, 2);
    StackPush(&s, 3);
    StackPush(&s, 4);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackPop(&s);
    StackPop(&s);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackDestroy(&s);
    return 0;
}

Se puede ver que cuando se implementan en lenguaje C, las funciones de operación relacionadas con Stack tienen las siguientes similitudes:

  • El primer parámetro de cada función es Stack*
  • El primer parámetro debe ser detectado en la función, porque el parámetro puede ser NULL
  • En la función, la pila se manipula a través del parámetro Stack*
  • La dirección de la variable de estructura de pila debe pasarse al llamar

Solo la estructura para almacenar datos se puede definir en la estructura, y el método de operación de datos no se puede colocar en la estructura, es decir, los datos y la forma de operar datos están separados, y la implementación es bastante complicada, lo que implica una gran número de operaciones de puntero, así que presta atención Puede salir mal.

implementación de C++

#include <iostream>
#include <stdlib.h>
using namespace std;
typedef int DataType;
class Stack {
    
    
public:
    void Init() {
    
    
        _array = (DataType *) malloc(sizeof(DataType) * 3);
        if (NULL == _array) {
    
    
            perror("malloc申请空间失败!!!");
            return;
        }
        _capacity = 3;
        _size = 0;
    }

    void Push(DataType data) {
    
    
        CheckCapacity();
        _array[_size] = data;
        _size++;
    }

    void Pop() {
    
    
        if (Empty())
            return;
        _size--;
    }

    DataType Top() {
    
    
        return _array[_size - 1];
    }

    int Empty() {
    
    
        return 0 == _size;
    }

    int Size() {
    
    
        return _size;
    }

    void Destroy() {
    
    
        if (_array) {
    
    
            free(_array);
            _array = NULL;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    void CheckCapacity() {
    
    
        if (_size == _capacity) {
    
    
            int newcapacity = _capacity * 2;
            DataType *temp = (DataType *) realloc(_array, newcapacity *
                                                                  sizeof(DataType));
            if (temp == NULL) {
    
    
                perror("realloc申请空间失败!!!");
                return;
            }
            _array = temp;
            _capacity = newcapacity;
        }
    }

private:
    DataType *_array;
    int _capacity;
    int _size;
};

int main() {
    
    
    Stack s;
    s.Init();
    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);

    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Pop();
    s.Pop();
    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Destroy();
    return 0;
}

En C++, los datos y los métodos de manipulación de datos se pueden combinar perfectamente a través de la clase, y aquellos métodos que se pueden llamar fuera de la clase se pueden controlar a través de la autoridad de acceso, es decir, la encapsulación, la cognición de las cosas . Además, cada método no necesita pasar el parámetro Stack*, y el parámetro se restaurará automáticamente después de que el compilador compile, es decir, el compilador mantiene el parámetro Stack * en C++, pero en lenguaje C, debe ser mantenida por el usuario .

Supongo que te gusta

Origin blog.csdn.net/ikun66666/article/details/132059987
Recomendado
Clasificación