Compartilhamento de tecnologia

Noções básicas de C (2)

2024-07-12

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

Índice

1. Classes e Objetos

1.1 Definição de classe

1.2 Qualificadores de acesso

1.3 Domínio de classe

2. Instanciação

2.1 Conceito de instanciação

2.2 Tamanho do objeto

3.este ponteiro

4. Funções de membro padrão de classes

4.1Construtor

4.2 Destruidor

4.5 Sobrecarga do operador


1. Classes e Objetos

1.1 Definição de classe

Formato de definição de classe

class é a palavra-chave que define a classe, Stack é o nome da classe e {} é o corpo da classe. Observe que o ponto e vírgula no final da definição não é omitido. O conteúdo do corpo da classe é chamado de membro da classe: as variáveis ​​da classe são chamadas de atributos ou as funções de membro da classe são chamadas de métodos ou funções de membro da classe;

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. //成员变量
  7. int* a;
  8. int top;
  9. int capacity;
  10. //成员函数
  11. void Push()
  12. {
  13. }
  14. void Pop()
  15. {
  16. }
  17. };//分号不能省略
  18. int main()
  19. {
  20. return 0;
  21. }

  • Para distinguir variáveis ​​de membro, geralmente é comum adicionar um identificador especial à variável de membro, como começar com _ ou m_ antes ou depois da variável de membro.Não há regulamentações sobre esta sintaxe C++, é apenas uma questão de preferência pessoal ou da empresa.
  1. //为区分成员变量,一般前面加_
  2. //成员变量
  3. int* _a;
  4. int _top;
  5. int _capacity;
  • Struct em C++ também pode definir classes. C++ é compatível com o uso de struct em C. Ao mesmo tempo, struct foi atualizado para uma classe. ainda recomendo usar class para definir classes.

  • Membros definidos em uma classe são padronizados como embutidos

1.2 Qualificadores de acesso

C++ é uma forma de implementar encapsulamento, usando classes para combinar as propriedades e métodos de um objeto para torná-lo mais completo e fornecer seletivamente sua interface para usuários externos por meio de direitos de acesso.

  • Membros modificados por público (público) podem ser acessados ​​diretamente fora da classe, membros modificados por protegido (protegido) e privado (privado) não podem ser acessados ​​diretamente fora da classe, protegido e privado são iguais
  • O escopo da permissão de acesso começa na posição onde a permissão de acesso aparece até que o próximo qualificador de acesso apareça. Se não houver nenhum qualificador de acesso subsequente, o escopo termina em }, que é o final da classe.
  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. ///
  7. void Push()
  8. {
  9. }
  10. //Push 没给限定符 class默认私有 private
  11. ///
  12. public:
  13. void Pop()
  14. {
  15. }
  16. int Swap()
  17. {
  18. }
  19. //Pop和Swap 被public修饰,直到下一个限定符出现之前都为公有
  20. ///
  21. protected:
  22. int add();
  23. //add 被public修饰,直到下一个限定符出现之前都为保护
  24. /// /
  25. private:
  26. int* _a;
  27. int _top;
  28. int _capacity;
  29. //成员变量被private修饰,直到}结束都为私有
  30. };
  31. int main()
  32. {
  33. Stack st;
  34. //公有可以访问
  35. st.Pop();
  36. st.Swap();
  37. //私有不可访问
  38. st._top;
  39. return 0;
  40. }

Notas Adicionais:

  1. Quando os membros da definição de classe não são modificados pelos qualificadores de acesso, eles são padronizados como privados e os padrões da estrutura são públicos.
  2. Geralmente, as variáveis-membro serão restritas a privadas/protegidas, e as funções-membro que precisam ser usadas por outros serão colocadas como públicas.

1.3 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 ::scope para indicar a qual escopo de classe o membro pertence.

O domínio de classe afeta as regras de pesquisa de compilação. Se Init no programa a seguir não especificar o domínio de classe Stack, o compilador tratará Init como uma função global. Se não conseguir encontrar membros como _top durante a compilação, ele irá para o. domínio de classe para encontrá-los.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. public:
  7. void Init(int x, int y);
  8. };
  9. void Stack::Init(int x, int y)
  10. {
  11. _top = x;
  12. _capacity = y;
  13. }
  14. int main()
  15. {
  16. return 0;
  17. }

Perceber:

  1. As declarações e definições de função na classe são separadas. Após a criação da classe, um novo domínio de classe é formado. O domínio da classe precisa ser especificado, caso contrário, ficará inacessível.

2. Instanciação

2.1 Conceito de instanciação

  • O processo de criação de um tipo na memória física é chamado de instanciação de classe.
  • Uma classe é uma descrição abstrata de um objeto. É algo como um modelo. Ela limita as variáveis ​​de membro da classe. Essas variáveis ​​de membro são apenas declaradas e nenhum espaço é alocado quando um objeto é instanciado com uma classe.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. //声明
  7. int* _a;
  8. int _top;
  9. int _capacity;
  10. };
  11. int main()
  12. {
  13. Stack::_top = 2024;
  14. //编译器报错,_top只是声明,并未实例化
  15. return 0;
  16. }
  • Uma classe pode instanciar vários objetos, e os objetos instanciados ocupam espaço físico real e armazenam variáveis ​​de membro. Por exemplo: instanciar objetos de uma classe é como usar desenhos de projeto arquitetônico para construir uma casa na realidade. As aulas são como desenhos de projeto. Os desenhos de projeto planejam o número de cômodos, o tamanho da casa, etc., mas não há nenhum físico. edifício, e não pode morar nas pessoas, somente quando uma casa é construída usando os desenhos do projeto as pessoas podem morar na casa.A mesma classe é como um desenho de projeto. Ela apenas informa ao compilador quanta memória será aberta, mas não abre a memória. Apenas os objetos instanciados recebem memória física para armazenar dados.
    1. //text.cpp
    2. #include<iostream>
    3. using namespace std;
    4. class Stack
    5. {
    6. //声明
    7. int* _a;
    8. int _top;
    9. int _capacity;
    10. };
    11. int main()
    12. {
    13. Stack st;
    14. st._top=2024;
    15. //Stack实例化出st,系统已经给st分配内存了,可以存储数据,编译通过
    16. return 0;
    17. }

    2.2 Tamanho do objeto

  • Analise quais membros o objeto de classe possui? Cada objeto instanciado por uma classe possui um espaço de dados independente, portanto o objeto deve conter variáveis ​​de membro. Então, as funções de membro estão incluídas? Primeiro, depois que a função é compilada, ela é uma seção de instruções que não pode ser armazenada no objeto. Essas instruções são armazenadas em uma área separada (segmento de código). Se o objeto precisar ser armazenado, ele poderá ser apenas um ponteiro para. uma função membro. É necessário armazenar ponteiros no objeto? Date instancia dois objetos d1 e d2 Ambos di e d2 possuem variáveis ​​​​membro independentes _year/_month/_day para armazenar seus próprios dados, mas as funções membro Init de d1 e d2 O ponteiro /Print. é o mesmo e é desperdiçado se armazenado no objeto. Se você usar Date para instanciar 100 objetos, o ponteiro da função membro será armazenado repetidamente 100 vezes, o que é um desperdício demais. Na verdade, o ponteiro de função não precisa ser armazenado. O ponteiro de função é um endereço. A função de chamada é compilada em uma instrução assembly [endereço de chamada]. . Não é encontrado em tempo de execução. Ele só pode ser encontrado dinamicamente. O estado é encontrado em tempo de execução, portanto, o endereço da função precisa ser armazenado.

Regras de alinhamento de memória

  • O primeiro membro está no deslocamento de endereço 0 da estrutura
  • Outras variáveis ​​de membro devem ser alinhadas em endereços múltiplos do número de alinhamento.
  • Número de alinhamento = O menor número de alinhamento padrão do compilador e o tamanho do membro
  • O número de alinhamento padrão da plataforma VS x64 é 4 e o número de alinhamento padrão de x86 é 8
  • O tamanho total da estrutura é: um múltiplo inteiro do número máximo de alinhamento (o maior de todas as variáveis ​​de tipo e o menor número de alinhamento padrão)
  • Se as estruturas estiverem aninhadas, a estrutura aninhada será alinhada a um múltiplo inteiro de seu próprio número máximo de alinhamento, e o tamanho geral da estrutura será um múltiplo inteiro de todos os números máximos de alinhamento (incluindo o número de alinhamento de estruturas aninhadas).
  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << _ch << endl;
  7. }
  8. private:
  9. char _ch;
  10. int _i;
  11. };
  12. //_ch 是一个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存在_ch
  13. // _i是4个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存储_i
  14. class B
  15. {
  16. public:
  17. void Print()
  18. {
  19. //。。。
  20. }
  21. };
  22. class B
  23. {
  24. };
  25. //B和C里面没有存储任何成员变量,只有一个函数,可成员函数不存对象里面
  26. // 按理来说是0,但是结构体怎么会没大小,为表示对象存在C++对这种规定大小为1,为了占位标识对象存在

3.este ponteiro

Após a compilação do compilador, as funções-membro da classe adicionarão um ponteiro para a classe atual na primeira posição do parâmetro formal por padrão, chamado de ponteiro.

Por exemplo, o protótipo Init na classe Date é void Init (Data * const this, int ano, int mês, int dia Ao acessar variáveis-membro nas funções-membro da classe, a essência é acessada por meio do ponteiro this). como _year na função Init, this-&gt;_year=year.

protótipo:

  1. class Date
  2. {
  3. void Print()
  4. {
  5. cout << _year << "n" << _month << "n" << _day << endl;
  6. }
  7. void Init( int year, int month,int day)
  8. {
  9. _year = year;
  10. _month = month;
  11. _day = day;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

  1. Date d1;
  2. d1.Init(2024,7,10);
  3. d1.Print();
  4. Date d2;
  5. d2.Init(2024, 7, 9);
  6. d2.Print();

protótipo real

  1. class Date
  2. {
  3. void Init(Date* const this,int year, int month,int day)
  4. {
  5. this->_year = year;
  6. this->_month = month;
  7. this->_day = day;
  8. }
  9. void Printf(Date* const this)
  10. {
  11. cout << this->_year << "n" <<this-> _month << "n" << this->_day << endl;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

  1. Date d1;
  2. d1.Init(&d1,2024,7,10);
  3. d1.Print(&d1);
  4. Date d2;
  5. d2.Init(&d2,2024, 7, 9);
  6. d2.Print();

C++ estipula que não é permitido escrever este ponteiro na posição de parâmetros reais e parâmetros formais (o compilador irá lidar com isso durante a compilação), mas este ponteiro pode ser usado explicitamente no corpo da função. o conteúdo apontado por este ponteiro pode

este ponteiro é armazenado na pilha

4. Funções de membro padrão de classes

A função de membro padrão é uma função de membro que não é definida explicitamente pelo usuário e é gerada automaticamente pelo compilador. É chamada de função de membro padrão.

4.1Construtor

O construtor é uma função de membro especial. Deve-se notar que embora o construtor seja chamado de construtor, o conteúdo principal do construtor não é abrir espaço para criar objetos (o objeto local que normalmente usamos é o espaço que é aberto quando. o quadro de pilha é criado) ), mas o objeto é inicializado quando o objeto é instanciado. A essência do construtor é substituir a função Init que escrevemos anteriormente nas classes Stack e Date. A chamada automática do construtor substitui perfeitamente a função Init.

Recursos do construtor:

  1. O nome da função é igual ao nome da classe
  2. Nenhum valor de retorno (você não precisa fornecer nada como valor de retorno e não escreva void. Esta é a regra do C++)
  3. Quando o objeto for instanciado, o sistema chamará automaticamente o construtor correspondente.
  4. Construtores podem ficar sobrecarregados
  5. Se não houver nenhum construtor explícito definido na classe, o compilador C++ gerará automaticamente um construtor padrão sem parâmetros. Assim que o usuário definir explicitamente o construtor, o compilador não o gerará mais.

  1. class Date
  2. {public:
  3. //1.无参构造函数
  4. Date()
  5. {
  6. _year = 1;
  7. _month = 1;
  8. _day = 1;
  9. }
  10. //2.带参构造函数
  11. Date(int year, int month, int day)
  12. {
  13. _year = year;
  14. _month = month;
  15. _day = day;
  16. }
  17. //3.全缺省构造函数
  18. Date(int year = 1, int month = 1, int day = 1)
  19. {
  20. _year = year;
  21. _month = month;
  22. _day = day;
  23. }
  24. private:
  25. int _year;
  26. int _month;
  27. int _day;
  28. };

O construtor sem parâmetros, o construtor totalmente padrão e o construtor gerado pelo compilador por padrão quando não escrevemos um construtor são todos chamados de construtores padrão. Mas apenas um destes três pode existir, não 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.Observe que não apenas o construtor padrão é aquele gerado pelo compilador por padrão, ele é o construtor, o construtor sem parâmetros, e o construtor padrão completo também é o construtor padrão. Resumindo, ele pode ser chamado sem passar parâmetros.

Não escrevemos. A construção gerada pelo compilador por padrão não possui requisitos para a inicialização de variáveis ​​de membro do tipo interno. Ou seja, se é inicializado ou não, é incerto, depende do compilador.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. Stack(int n = 4)
  9. {
  10. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11. if (nullptr == _a)
  12. {
  13. perror("malloc申请失败");
  14. }
  15. _capacity = n;
  16. _top = 0;
  17. }
  18. private:
  19. STDataType* _a;
  20. size_t _capacity;
  21. size_t _top;
  22. };
  23. //两个Stack实现队列
  24. class MyQueue
  25. {
  26. private:
  27. int size;
  28. Stack pushst;
  29. Stack popst;
  30. };
  31. int main()
  32. {
  33. MyQueue my;
  34. return 0;
  35. }

C++ divide os tipos em tipos personalizados e tipos integrados (tipos básicos). Tipos integrados são os tipos de dados nativos fornecidos pela linguagem, como int/char/double/pointer, etc. Tipos personalizados são tipos que nós mesmos definimos usando palavras-chave como class/struct.O construtor aqui é inicializado automaticamente e o VS também inicializa o tamanho do tipo integrado. Diferentes compiladores têm diferentes valores de inicialização e C++ não os especifica.

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 membro não tiver um construtor padrão, um erro será relatado. Se quisermos inicializar esta variável membro, precisamos usar uma lista de inicialização para resolvê-la.

Resumo: Na maioria dos casos, precisamos implementar o construtor nós mesmos. Em alguns casos, é semelhante ao MyQueue e quando Stack tem um construtor padrão, MyQueue pode ser gerado e usado automaticamente.

4.2 Destruidor

  1. ~Stack()
  2. {
  3. free(_a);
  4. _a = nullptr;
  5. _top = _capacity = 0;
  6. }

Características do destruidor:

1. O nome do destruidor é precedido por caracteres ~

2. Sem parâmetros e sem valor de retorno (consistente com o construtor)

3. Uma classe só pode ter um destruidor.Se a definição não for exibida, o sistema gerará automaticamente um destruidor padrão.

4. Quando o ciclo de declaração do objeto terminar, o sistema chamará automaticamente o destruidor.

5. Semelhante ao construtor, não escrevemos o destruidor gerado automaticamente pelo compilador e não processamos os membros do tipo integrado que chamarão outros destruidores.

6. Também deve ser observado que quando exibirmos o destruidor, o destruidor do membro do tipo customizado também será chamado, o que significa que o destruidor do membro do tipo customizado será chamado automaticamente independentemente da situação.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. Stack(int n = 4)
  9. {
  10. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11. if (nullptr == _a)
  12. {
  13. perror("malloc申请失败");
  14. }
  15. _capacity = n;
  16. _top = 0;
  17. }
  18. ~Stack()
  19. {
  20. free(_a);
  21. _a = nullptr;
  22. _top=_capacity=0;
  23. }
  24. private:
  25. STDataType* _a;
  26. size_t _capacity;
  27. size_t _top;
  28. };
  29. //两个Stack实现队列
  30. class MyQueue
  31. {public:
  32. //编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化
  33. //编译器默认生成MyQueue的析构函数调用了Stack的析构,释放了Stack内部的资源
  34. //显示写析构也会调用Stack的析构
  35. ~MyQueue()
  36. {
  37. cout << "~MyQueue" << endl;
  38. }
  39. private:
  40. Stack pushst;
  41. Stack popst;
  42. };
  43. int main()
  44. {
  45. MyQueue my;
  46. return 0;
  47. }

O destruidor em MyQueue não faz nada, mas o C++ estipula que outros destruidores serão chamados para liberar a memória.

Se nenhum recurso for solicitado, o destruidor não precisa ser escrito e o destruidor padrão gerado pelo compilador pode ser usado diretamente, como Date. Se o destruidor padrão gerado puder ser usado, não há necessidade de escrever explicitamente o destruidor. como MyQueue, mas há um aplicativo de recursos. Ao destruir, certifique-se de escrever o destruidor diretamente, caso contrário, causará vazamento de recursos, como Stack.

4.5 Sobrecarga do operador

  • Quando operadores são usados ​​em objetos digitados, a linguagem C++ nos permite especificar novos significados na forma de sobrecarga de operadores. C++ estipula que quando um objeto de tipo de classe usa um operador, ele deve ser convertido em uma chamada para a sobrecarga do operador correspondente. Caso contrário, o compilador reportará um erro.
  • A sobrecarga do operador é uma função com um nome específico. Seu nome é composto pelo operador e pelo operador a ser definido posteriormente.Como outras funções, ela também possui seu tipo de retorno e lista de parâmetros, bem como um corpo de função
  1. bool operator<(Date d1, Date d2)
  2. {
  3. }
  4. bool operator==(Date d1,Date d2)
  5. {
  6. return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
  7. }
  • Uma função de operador sobrecarregada recebe tantos parâmetros quantos os parâmetros sobre os quais o operador atua.O conforto de transporte unário possui um parâmetro e o operador binário possui dois parâmetros. O operando esquerdo do operador binário é passado para o primeiro parâmetro e o operando direito é passado para o segundo parâmetro.
  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Date
  5. {
  6. public:
  7. Date(int year, int month, int day)
  8. {
  9. _year= year;
  10. _month = month;
  11. _day = day;
  12. }
  13. int _year;
  14. int _month;
  15. int _day;
  16. };
  17. bool operator<(Date d1, Date d2)
  18. {
  19. }
  20. bool operator==(Date d1,Date d2)
  21. {
  22. return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
  23. }
  24. int main()
  25. {
  26. Date d1(2024, 7, 10);
  27. Date d2(2024,7,9);
  28. //两种用法都可以
  29. d1 == d2;
  30. operator==(d1 , d2);
  31. return 0;
  32. }
  • Se uma função de operador sobrecarregada for uma função de membro, seu primeiro operando será passado para o ponteiro this implícito por padrão. Portanto, quando o operador estiver sobrecarregado como uma função de membro, ele terá um parâmetro a menos que o operando.
  • Depois que um operador é sobrecarregado, sua precedência e associatividade permanecem consistentes com as operações de tipo integradas.
  • Você não pode criar um operador sexual concatenando correspondências que não existem na sintaxe: por exemplo, operador@