Compartilhamento de tecnologia

Classes e Objetos (Parte 1)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

No capítulo anterior, mencionamos brevemente alguns conceitos de classes e objetos. Agora, vamos revisá-los juntos.

Definição de classe

Formato de definição de classe

class é a palavra-chave da classe definidora, seguida pelo nome da classe definidora, {} é o corpo principal da classe, seguido por ";", as funções de membro definidas na classe são expandidas por padrão (inline)

qualificador de acesso

Um formato de encapsulamento, dividido em três tipos: público, privado e protegido. O escopo de permissão de acesso começa na posição onde o qualificador de acesso aparece até que o próximo qualificador de acesso apareça. Se não houver nenhum qualificador de acesso subsequente, ele terminará. em } ou seja, classe.

domínio de classe

A classe define um novo escopo. Todos os membros da classe estão no escopo da classe. Ao definir membros fora da classe, você precisa usar o operador de escopo :: para indicar a qual domínio de classe o membro pertence.

A seguir está o longa-metragem ///

Instanciar

conceito

• O processo de criação de um objeto na memória física usando um tipo de classe é chamado de instanciação de classe.
• Uma classe é uma descrição abstrata de um objeto. É como um modelo. Ela limita apenas as variáveis-membro.
É uma declaração e nenhum espaço é alocado. O espaço será alocado somente quando um objeto for instanciado usando uma classe.
• Uma classe pode instanciar vários objetos. Os objetos instanciados ocupam espaço físico real e armazenam variáveis ​​de membros da classe.

Resumindo em uma frase: somente quando os objetos forem instanciados com classes irão alocar espaço (assim como construir uma casa com desenhos irá ocupar espaço).

  1. class Date
  2. {
  3. public:
  4. void Init(int year, int month, int day)
  5. {
  6. _year = year;
  7. _month = month;
  8. _day = day;
  9. }
  10. void Print()
  11. {
  12. cout << _year << "/" << _month << "/" << _day << endl;
  13. }
  14. private:
  15. // 这⾥只是声明,没有开空间
  16. int _year;
  17. int _month;
  18. int _day;
  19. };

No código acima, as variáveis-membro em private são apenas definidas e nenhum novo espaço é aberto;

  1. // Date类实例化出对象d1和d2
  2. Date d1;
  3. Date d2;

Dois novos objetos são instanciados, então um novo espaço é aberto.

Regras de alinhamento de memória

definição

• O primeiro membro está no deslocamento de endereço 0 da estrutura.
• Outras variáveis-membro devem ser mapeadas para endereços que sejam múltiplos inteiros de um determinado número (logaritmo).
• Nota: logaritmo = o menor entre o logaritmo padrão do compilador e o tamanho do membro.
• O logaritmo padrão em VS é 8
• O tamanho total da estrutura é: um múltiplo inteiro do número máximo de pares (o maior de todos os tipos de variáveis ​​e o menor parâmetro de emparelhamento padrão).
• Se uma estrutura estiver aninhada, a estrutura aninhada se alinha a um número inteiro múltiplo de seu logaritmo máximo e ao tamanho geral da estrutura
É um múltiplo inteiro de todos os maiores logaritmos (incluindo logaritmos de estruturas aninhadas).

Isso é o que sabemos na linguagem C. Tomemos as seguintes questões como exemplo:

  1. // 计算⼀下A实例化的对象是多⼤?
  2. class A
  3. {
  4. public:
  5. void Print()
  6. {
  7. cout << _ch << endl;
  8. }
  9. private:
  10. char _ch;
  11. int _i;
  12. };

Não há função chamada, então basta olhar para as variáveis ​​​​de membro. _ch é um tipo de caractere char, com tamanho de 1 byte. _i é um tipo int, com tamanho de 4 bytes. número de alinhamento, então é _ Coloque ch na posição com subscrito 0, deixe três espaços em branco e depois coloque _i Ocupa um total de 8 bytes de espaço de 0 a 7, que é um múltiplo inteiro do número máximo de alinhamento. . Atende às condições, então a resposta a esta pergunta é 8 .

E se forem essas duas perguntas?

  1. class B
  2. {
  3. public:
  4. void Print()
  5. {
  6. //...
  7. }
  8. };
  9. class C
  10. {
  11. };

Podemos descobrir que existe uma função vazia na classe B e nenhum membro na classe C. Na linguagem C, fazemos com que ambas as classes ocupem 1 byte de espaço.

este ponteiro

conceito

EExistem duas funções-membro, Init e Print, na classe ate. Não há distinção entre diferentes objetos no corpo da função. Então, quando d1 chama Init e Print.
Ao imprimir a função, como a função sabe se deve acessar o objeto d1 ou o objeto d2?Então aqui veremos que C++ dá
Um ponteiro this implícito resolve o problema aqui
• Após a compilação do compilador, as funções-membro da classe adicionarão um ponteiro do tipo de classe atual, chamado de ponteiro, à primeira posição do parâmetro formal por padrão. Por exemplo, o protótipo real da classe Init of Date é, void Init(Date* const this, int year,
int mês, int dia)
• Ao acessar variáveis-membro nas funções-membro de uma classe, elas são essencialmente acessadas através do ponteiro this. Por exemplo, ao atribuir um valor a _year na função Init, isto-.
&gt;_ano = ano;
• C++ estipula que esse ponteiro não pode ser escrito explicitamente na posição dos parâmetros reais e dos parâmetros formais (o compilador tratará disso durante a compilação), mas esse ponteiro pode ser usado explicitamente no corpo da função.

por exemplo

  1. class Date
  2. {
  3. public:
  4. // void Init(Date* const this, int year, int month, int day)
  5. void Init(int year, int month, int day)
  6. {
  7. // 编译报错:error C2106: “=”: 左操作数必须为左值
  8. // this = nullptr;
  9. // this->_year = year;
  10. _year = year;
  11. this->_month = month;
  12. this->_day = day;
  13. }
  14. void Print()
  15. {
  16. cout << _year << "/" << _month << "/" << _day << endl;
  17. }
  18. private:
  19. // 这⾥只是声明,没有开空间
  20. int _year;
  21. int _month;
  22. int _day;
  23. };
  24. int main()
  25. {
  26. // Date类实例化出对象d1和d2
  27. Date d1;
  28. Date d2;
  29. // d1.Init(&d1, 2024, 3, 31);this指针
  30. d1.Init(2024, 3, 31);
  31. // d1.Init(&d2, 2024, 7, 5);
  32. d2.Init(2024, 7, 5);
  33. //不能自己写
  34. return 0;
  35. }

Para a escrita dos parâmetros reais na função Init, C++ adicionará uma constante invisível a este ponteiro (que não pode ser modificada) à primeira variável da função, conforme mostrado nos comentários da função.

Perceber:

Contanto que o ponteiro nulo não seja desreferenciado, nenhum erro será relatado.

  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << "A::Print()" << endl;
  7. }
  8. private:
  9. int _a;
  10. };
  11. int main()
  12. {
  13. A* p = nullptr;
  14. p->Print();
  15. return 0;
  16. }

Embora p receba um ponteiro nulo e aponte para a função Print, ele não é desreferenciado, portanto, nenhum erro será relatado aqui.

  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << "A::Print()" << endl;
  7. cout << _a << endl;//这个地方多了一句
  8. }
  9. private:
  10. int _a;
  11. };
  12. int main()
  13. {
  14. A* p = nullptr;
  15. p->Print();
  16. return 0;
  17. }

Comparado com o programa anterior, há apenas mais uma frase cout &lt;&lt; _a &lt;&lt; endl, mas neste momento o ponteiro nulo aponta para uma variável membro, e o ponteiro nulo será desreferenciado, então o programa reportará um erro.

Função de membro padrão da classe

conceito

A função de membro padrão é uma função de membro que não é explicitamente implementada pelo usuário e é gerada automaticamente pelo compilador. É chamada de função de membro padrão. Para uma classe, se não a escrevermos, o compilador irá gerar as seguintes 6 funções de membro padrão por padrão, que são as 6 seguintes

Construtor

definição

O construtor é uma função de membro especial. Deve-se notar que embora o nome do construtor seja chamado de construtor, a principal tarefa do construtor não é abrir espaço para criar objetos (o objeto local que frequentemente usamos é o espaço quando a pilha). frame é criado) ), em vez disso, o objeto é inicializado quando o objeto é instanciado. A essência é substituir a função Init de inicialização.

Características

Cinco principais recursos

1. O nome da função é igual ao nome da classe.
2. Nenhum valor de retorno. (Não há necessidade de escrever vazio)
3. Quando o objeto for instanciado, o sistema chamará automaticamente o construtor correspondente.
4. Os construtores podem estar sobrecarregados.
5. Se não houver um construtor definido explicitamente na classe, o compilador C++ gerará automaticamente um construtor padrão sem parâmetros. Depois que o usuário o definir explicitamente, o compilador não o gerará mais.

Os primeiros cinco itens são relativamente simples, vamos dar um exemplo:

  1. class Date
  2. {
  3. public:
  4. //1.⽆参构造函数,无需写void
  5. Date()//若不写,则构造函数会写出这一中无参构造函数
  6. {
  7. _year = 1;
  8. _month = 1;
  9. _day = 1;
  10. }
  11. //2.全缺省构造函数
  12. Date(int year = 1, int month = 1, int day = 1)
  13. {
  14. _year = year;
  15. _month = month;
  16. _day = day;
  17. }
  18. private:
  19. int _year;
  20. int _month;
  21. int _day;
  22. }
  23. int main()
  24. {
  25. Date d1;//此时相当于已经自动调用了Init函数
  26. Date d2;
  27. return 0;
  28. }

Dois construtores são escritos no programa, um é um construtor sem parâmetros e o outro é um construtor padrão completo. No entanto, embora não escrevamos uma função, o programa irá escrever até um construtor sem parâmetros. duas funções são funções sobrecarregadas, devido à ambiguidade de chamada, as duas funções não podem existir ao mesmo tempo.

As duas últimas características

6. Construtores sem parâmetros, construtores padrão completos e construtores gerados pelo compilador por padrão quando não escrevemos um construtor são todos chamados de construtores padrão. No entanto, uma e apenas uma destas três funções existe e não pode existir ao mesmo tempo. Embora o construtor sem parâmetros e o construtor padrão completo constituam uma sobrecarga de função, haverá ambiguidade ao chamá-los. Deve-se notar que muitos estudantes pensam que o construtor padrão é o construtor padrão gerado pelo compilador. Na verdade, o construtor sem parâmetros e o construtor padrão completo também são construtores padrão. Para resumir, eles são construtores que podem ser chamados sem passar pelo real. parâmetros. É chamada de construção padrão.

6

O sexto recurso é mais complicado: o construtor sem parâmetros, o construtor padrão completo e o construtor gerado pelo compilador por padrão quando não escrevemos um construtor são todos chamados de construtores padrão e só pode haver um. construtor padrão como o construtor gerado pelo compilador por padrão!

7


7. Se não o escrevermos, o construtor gerado pelo compilador por padrão não possui requisitos para a inicialização de variáveis ​​​​de membro do tipo interno. Em outras palavras, se ele é inicializado ou não, é incerto, depende do compilador. . Para variáveis ​​de membro de tipo personalizado, é necessário chamar o construtor padrão desta variável de membro para inicializá-la. Se esta variável de membro não tiver um construtor padrão, um erro será relatado. Se quisermos inicializar esta variável de membro, precisaremos usar uma lista de inicialização para resolver o problema. Explicaremos a lista de inicialização em detalhes no próximo capítulo. .

destruidor

conceito

A função do destruidor é oposta à do construtor. O destruidor não completa a destruição do próprio objeto. Por exemplo, o objeto local possui um quadro de pilha. Quando a função termina e o quadro de pilha é destruído. liberado. Não precisamos controlá-lo. C++ estipula que os objetos Quando destruídos, o destruidor será automaticamente chamado para completar a limpeza e liberação de recursos no objeto. A função do destruidor é semelhante à função Destroy que implementamos antes no Stack. Por exemplo, Date não possui Destroy. Na verdade, não há recursos a serem liberados. destruidor é exatamente como a função Destroy. Ele deve ser usado para estruturas de dados que requerem objetos não locais, como pilhas e filas, para solicitar recursos.

Características

1. O nome do destruidor é precedido pelo caractere ~ antes do nome da classe. (Semelhante à negação bit a bit na linguagem C)
2. Sem parâmetros e sem valor de retorno. (Isso é semelhante à estrutura e não há necessidade de adicionar vazio)
3. Uma classe só pode ter um destruidor. Se não for definido explicitamente, o sistema gerará automaticamente um destruidor padrão.
4. Quando o ciclo de vida do objeto terminar, o sistema chamará automaticamente o destruidor.
5. Semelhante ao construtor, se não escrevermos um destruidor gerado automaticamente pelo compilador, ele não processará os membros do tipo integrado e chamará seus destruidores.
6. Deve-se notar também que quando escrevemos explicitamente o destruidor, o destruidor do membro do tipo personalizado também será chamado. Ou seja, o destruidor do membro do tipo personalizado será chamado automaticamente, independentemente da situação.
7. Se não houver recursos solicitados na classe, o destruidor não precisa ser escrito e o destruidor padrão gerado pelo compilador pode ser usado diretamente, como Date, se o destruidor gerado padrão puder ser usado, existe; não há necessidade de exibi-lo. Escreva o destruidor, como MyQueue, mas quando houver um aplicativo de recurso, você mesmo deve escrever o destruidor, caso contrário, causará vazamento de recursos, como Stack.
8. Para múltiplos objetos em um domínio local, C++ estipula que aqueles definidos posteriormente serão destruídos primeiro.

Uso de destruidor

Assim como o construtor acima, o destruidor padrão gerado automaticamente pelo C++ geralmente não será de grande ajuda para nós, mas é mais conveniente em alguns casos raros, como usar duas pilhas para implementar uma fila:

  1. public:
  2. Stack(int n = 4)//相当于Init
  3. {
  4. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  5. if (nullptr == _a)
  6. {
  7. perror("malloc fail!");
  8. return;
  9. }
  10. _capacity = n;
  11. _top = 0;
  12. }
  13. ~Stack()//相当于Destroy
  14. {
  15. cout << "~Stack()" << endl;
  16. free(_a);
  17. _a = nullptr;
  18. _top = _capacity = 0;
  19. }
  20. private:
  21. STDataType* _a;
  22. size_t _capacity;
  23. size_t _top;
  24. };
  25. class MyQueue
  26. {
  27. public:
  28. //编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
  29. // 显⽰写析构,也会⾃动调⽤Stack的析构
  30. private:
  31. Stack pushst;
  32. Stack popst;
  33. };

Neste momento, o compilador em MyQueue chama automaticamente o construtor (inicialização) e o destruidor (destruição) da pilha.