Classes e objetos C++ (1)


prefácio

Este capítulo iniciará oficialmente C++uma série de explicações e este artigo fornecerá uma compreensão preliminar de classes e objetos

1. Da orientação a processos à orientação a objetos

  • C语言É orientado ao processo, focando no processo , analisando as etapas para resolver o problema e resolvendo o problema passo a passo por meio de chamadas de função
  • C++É baseado em orientação a objetos, focando em objetos , dividindo uma coisa em diferentes objetos e contando com a interação entre objetos para ser concluído.

Por exemplo: Para lavar a roupa

C语言:
insira a descrição da imagem aqui
C++:

  1. Quatro objetos: pessoas, roupas, sabão em pó, máquina de lavar
  2. O processo de lavagem de roupas: a pessoa coloca as roupas na máquina de lavar, despeja o sabão em pó, liga a máquina de lavar, a máquina de lavar lava as roupas e seca
  3. Todo o processo é completado pela interação entre os quatro objetos de pessoas, roupas, sabão em pó e máquina de lavar.As pessoas não precisam se preocupar em como a máquina de lavar lava e seca as roupas.
    insira a descrição da imagem aqui

2. A introdução da aula

C语言Em , somente variáveis ​​podem ser definidas na estrutura . Em , não apenas variáveis ​​podem ser definidasC++ na estrutura , mas também funções podem ser definidas .

Quando implementávamos a estrutura de dados - pilha no passado C语言, definimos assim:

//C语言
typedef int dataOfStackType;

typedef struct stack
{
    
    
	dataOfStackType* a;
	int top;
	int capacity;
}stack;

void StackInit(stack* ps);

void StackPush(stack* ps, dataOfStackType data);

void StackPop(stack* ps);

//...

Considerando que em C++, podemos defini-lo assim:

//C++
typedef int dataOfStackType;

typedef struct stack
{
    
    
	void StackInit(stack* ps);

	void StackPush(stack* ps, dataOfStackType data);

	void StackPop(stack* ps);
//...

	dataOfStackType* a;
	int top;
	int capacity;
}stack;

Como na definição acima, C++中prefira usar um novo nome - classem vez disso struct:

class stack
{
    
    
	void StackInit(stack* ps);

	void StackPush(stack* ps, dataOfStackType data);

	void StackPop(stack* ps);
//...

	dataOfStackType* a;
	int top;
	int capacity;
};

3. Definição de classe

1. O que é uma classe

class className
{
    
    
	//...类的主体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

Como mostrado acima, a definição de uma classe é muito semelhante à definição de uma struct:

  1. class: Uma palavra-chave que define uma classe
  2. class Name: o nome da classe
  3. O corpo principal da classe: {}o meio é o corpo principal da classe, que consiste em variáveis ​​de membro e funções de membro
  4. Variável de membro: também conhecido como atributo da classe, refere-se à variável definida na classe
  5. Função de membro: também conhecido como método de classe, refere-se à função definida na classe

2. Definição de classe

Geralmente, existem duas maneiras de definir uma classe:

  1. A declaração e a definição são colocadas no corpo da classe Nota: Se a função de membro for definida na classe, o compilador pode tratá-la como uma função inline .
//日期类
class Date
{
    
    
	void Init(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	int _year;
	int _month;
	int _day;
};
  1. A declaração da classe é colocada no arquivo .h , e a definição da função membro é colocada no arquivo .cpp Nota: o nome da classe precisa ser adicionado antes do nome da função membro ::.
//Date.h文件中声明类
//日期类
class Date
{
    
    
public:

	void Init(int year, int month, int day);
	void Print();
	
	int _year;
	int _month;
	int _day;
};
//Date.c文件中定义成员函数
#include"Date.h"

void Date::Init(int year, int month, int day)
{
    
    
	_year = year;
	_month = month;
	_day = day;
}
void Date::Print()
{
    
    
	cout << _year << "-" << _month << "-" << _day << endl;
}

Em geral, a segunda abordagem é mais desejável.

3. Sugestões para regras de nomenclatura de variáveis ​​de membros

Vejamos a seguinte classe:

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

As variáveis ​​acima são complicadas? São variáveis ​​de membro ou parâmetros de função? Para evitar confusão, recomendamos nomear as variáveis ​​de membro da seguinte maneira Init函数:year

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

// 或者这样
class Date
{
    
    
public:
 	void Init(int year)
 	{
    
    
 		mYear = year;
 	}
private:
 	int mYear;
};

Outros métodos também são possíveis, dependendo dos requisitos da empresa. Geralmente, basta adicionar um prefixo ou sufixo para identificar a diferença.

4. Qualificador de acesso de classe e encapsulamento

1. Qualificadores de acesso

Definimos várias funções de membro e variáveis ​​de membro na classe. Às vezes, não queremos que outros acessem alguns membros da classe casualmente, como variáveis ​​de membro, mas outros membros estão abertos ao mundo externo, como funções de membro. Então, precisamos usar qualificadores de acesso para modificar esses membros .

Existem três qualificadores de acesso: public, protected,private

insira a descrição da imagem aqui
Descrição do qualificador de acesso :

  1. classAs permissões de acesso padrão são private, structpara public(devido à structcompatibilidade C)
  2. publicMembros modificados podem ser acessados ​​diretamente fora da classe
  3. protectede membros modificados não podem ser acessados ​​diretamenteprivate fora da classe (aqui e são semelhantes)protectedprivate
  4. Os escopos de acesso começam na ocorrência deste qualificador de acesso até a próxima ocorrência do qualificador de acesso
  5. Se não houver qualificador de acesso atrás, o escopo termina no }final da classe

Nota: Os qualificadores de acesso são úteis apenas em tempo de compilação , quando os dados são mapeados para a memória, não há diferença nos qualificadores de acesso.

Então a pergunta é: qual é a diferença entre c++neutralização structe neutralização?class

Resposta: C++Ele precisa ser compatível C语言, então não C++structpode ser usado como uma estrutura , C++mas structtambém pode ser usado para definir uma classe , que classé o mesmo que definir uma classe. A diferença é que a permissão de acesso padrãostruct da classe definida é , e a permissão de acesso padrão da classe definida é .publicclassprivate

2. Encapsulamento

A orientação a objetos tem três características: encapsulamento, herança e polimorfismo.

No estágio atual, aprendemos apenas os recursos do pacote. Então, o que é encapsulamento?

Encapsulamento: combine organicamente dados e métodos de manipulação de dados , oculte propriedades de objetos e detalhes de implementação e apenas exponha interfaces para interagir com objetos

O encapsulamento é essencialmente um tipo de gerenciamento, facilitando o uso de classes pelos usuários :

  • Por exemplo: para um dispositivo tão complexo como um computador, a única coisa fornecida ao usuário é o botão liga / desliga, entrada de teclado, monitor, USBconector etc., permitindo que o usuário interaja com o computador e conclua os assuntos diários. Mas, na verdade, o verdadeiro trabalho do computador são alguns componentes de hardware, como CPU, placa gráfica e memória.
  • Para usuários de computador, eles não precisam se preocupar com os componentes principais internos , como a disposição dos circuitos da placa-mãe, como a CPU é projetada por dentro etc. Portanto, quando o fabricante do computador sai da fábrica, ele coloca um shell do lado de fora para ocultar os detalhes internos da implementação e fornece apenas interruptores externos, mouse e teclado, etc., para que os usuários possam interagir com o computador .
  • O encapsulamento na C++linguagem pode combinar organicamente dados e métodos de manipulação de dados por meio de classes, ocultar os detalhes de implementação interna de objetos por meio de direitos de acesso e controlar quais métodos podem ser usados ​​diretamente fora da classe .

Cinco. O escopo da aula

Uma classe define um novo escopo e 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 :

class Person
{
    
    
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
    
    
	cout << _name << " "<< _gender << " " << _age << endl;
}

Seis. Instanciação de classe

O processo de criação de um objeto com uma classe é chamado de instanciação da classe:

//类的声明
class Date
{
    
    
public:
	//...
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	//类的实例化对象
	Date d1;
	Date d2;
	Date d3;
	return 0;
}

A instanciação de classe de objetos é como construir uma casa usando desenhos de projeto na realidade . Classes são como desenhos de projeto . Somente o que é necessário é projetado, mas não há construção física. O objeto instanciado é a casa real que pode realmente armazenar dados e ocupar espaço físico.
Como um desenho de projeto, não é que um desenho de projeto possa construir apenas uma casa, e uma classe também pode instanciar vários objetos.

Sete. Modelo de objeto de classe

Uma classe pode ter variáveis ​​de membro e funções de membro, então como calcular o tamanho de um objeto de classe?

Como mencionado acima, C++classes e estruturas são essencialmente as mesmas. Para ser compatível C语言, C++serão seguidas as regras de cálculo do tamanho da estrutura , sendo que a classe é a mesma da estrutura, portanto a classe continuará utilizando as mesmas regras de cálculo da estrutura .

Observação: as funções de membro estão no segmento de código público , portanto, as funções de membro não ocupam espaço . Portanto, o tamanho de uma classe é, na verdade, o tamanho das variáveis ​​membros da classe, seguindo as regras de alinhamento de memória.

Vamos comprová-lo:

// 类中既有成员变量,又有成员函数
class A1 {
    
    
public:
	void f1() {
    
    }
private:
	int _a;
	char _b;
};

// 类中仅有成员函数
class A2 {
    
    
public:
	void f2() {
    
    }
};

// 类中什么都没有---空类
class A3
{
    
    };

int main()
{
    
    
	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;
	cout << sizeof(A3) << endl;

	return 0;
}

resultado da operação:

insira a descrição da imagem aqui

Conforme mostrado na figura acima, o resultado é consistente com a conclusão: o tamanho da classe calcula apenas o tamanho das variáveis ​​de membro na classe e segue as regras de alinhamento de memória .

  • Então, por que o tamanho da classe vazia 1?
    Isso ocorre porque a classe vazia é especial e o compilador fornece à classe vazia um byte para identificar exclusivamente os objetos dessa classe .

Suplemento: Regras de alinhamento de memória de estrutura :

  1. O primeiro membro está no endereço da estrutura em offset .0
  2. Outras variáveis ​​de membro devem ser alinhadas a um endereço que seja um múltiplo inteiro de um determinado número (número de alinhamento) . Nota: Alinhamento = o menor valor entre o alinhamento padrão do compilador e o tamanho do membro . O número de alinhamento padrão no VS é 8
  3. O tamanho total da estrutura é: um múltiplo inteiro do número de alinhamento máximo (o maior de todos os tipos de variáveis ​​e o menor parâmetro de alinhamento padrão) .
  4. Se uma estrutura for aninhada, a estrutura aninhada será alinhada a um múltiplo inteiro de seu próprio alinhamento máximo e o tamanho geral da estrutura será um múltiplo inteiro de todos os alinhamentos máximos (incluindo o alinhamento da estrutura aninhada) .

Oito. este ponteiro

1. Conheça este ponteiro

Vamos definir uma classe de data primeiro Date:

//日期类
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;
}

Para a operação acima, existe a seguinte questão:

  • DateExistem duas funções de membro Inite na classe , e não há distinção entre objetos diferentes no corpo da função , portanto, quando a função é chamada , como a função sabe que o objeto deve ser definido em vez do objeto?Printd1Initd1d2

C++this指针Resolva este problema introduzindo :

C++O compilador adiciona um parâmetro de ponteiro oculto a cada função de membro não estática , fazendo com que o ponteiro aponte para o objeto atual (o objeto que chama a função quando a função está em execução) e todas as operações de variável de membro no corpo da função são acessadas por meio desse ponteiro . Só que todas as operações são transparentes para o usuário, ou seja, o usuário não precisa passar, e o compilador completa automaticamente .

2. Características deste ponteiro

insira a descrição da imagem aqui
thisOs ponteiros têm as seguintes propriedades :

  1. thisO tipo do ponteiro: o tipo * const, ou seja, na função de membro, não pode atribuir thisum valor ao ponteiro
void Init(int year, int month, int day)
{
    
    
	//错误示例
	this=nullptr;
}
  1. Só pode ser usado dentro de funções de membro
  2. thisUm ponteiro é essencialmente um parâmetro formal de uma função de membro.Quando um objeto chama uma função de membro, o endereço do objeto é passado para o parâmetro formal como um thisparâmetro real . Portanto, nenhum ponteiro é armazenado no objetothis .
  3. thisO ponteiro é o primeiro parâmetro de ponteiro implícito da função de membro.Geralmente , ele é passado automaticamente pelo compilador através ecxdo registro e não precisa ser passado pelo usuário.

Com thisponteiros, a classe de data que implementamos acima também pode ser implementada assim:

//日期类
class Date
{
    
    
public:
	void Init(int year, int month, int day)
	{
    
    
			this->_year = year;
			this->_month = month;
			this->_day = day;
	}
	void Print()
	{
    
    
		cout <<this-> _year << "-" <<this-> _month << "-" <<this-> _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

Mas não há necessidade de escrever isso no processo de escrever o código, porque o compilador já fez isso por nós, então não queremos fazer coisas ingratas.

【Questões de entrevista】

  • thisOnde existem ponteiros ?
    Resposta: Este ponteiro é um parâmetro formal de uma função de membro, portanto, há uma área de pilha .
  • thisUm ponteiro pode ser nulo ?
    Observe os dois trechos de código a seguir:
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
    
    
public:
	void Print()
	{
    
    
		cout << "Print()" << endl;
	}
private:
	int _a;
};

int main()
{
    
    
	A* p = nullptr;
	p->Print();
	return 0;
}

// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
    
     
public:
	void PrintA() 
	{
    
    
		cout<<_a<<endl;
	}
private:
	int _a;
};
int main()
{
    
    
	A* p = nullptr;
    p->PrintA();
    return 0;
}

Escolha C para o primeiro curso e B para o segundo curso, porque:

  • PCall Print, não será desreferenciado, pois o endereço Printde não está no objeto , Ppassado como argumento para thiso ponteiro
  • Programa 1: thisO ponteiro é nulo, mas a função não desreferencia thiso ponteiro
  • O segundo programa: thiso ponteiro está vazio, mas o acesso dentro da função _a, a essência é this->_a, ou seja, desreferenciar o ponteiro nulo, e ocorrerá um erro de operação.

Nove. Comparação da linguagem C e da pilha de implementação C++

1. Implementação da linguagem C

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;
}

Pode-se ver que, quando implementadas na linguagem C, as funções de operação relacionadas à pilha têm o seguinte em comum:

  1. O primeiro parâmetro de cada função éStack*
  2. O primeiro parâmetro deve ser detectado na função , pois o parâmetro pode serNULL
  3. Na função, Stack*a pilha é manipulada por meio de parâmetros.
  4. O endereço daStack variável de estrutura deve ser passado ao chamar

Na estrutura pode ser definida apenas a estrutura de armazenamento dos dados , não podendo ser colocado na estrutura o método de operação dos dados, ou seja, os dados e a forma de operação dos dados são separados , e a implementação é um pouco mais complicada, envolvendo um grande número de operações de ponteiro, podendo ocorrer erros caso não tenha cuidado.

2. Implementação C++

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;
}

C++As classes podem ser usadas para combinar perfeitamente dados e métodos de manipulação de dados , e os direitos de acesso podem ser usados ​​para controlar os métodos que podem ser chamados fora da classe , ou seja, encapsulamento , que está mais de acordo com a cognição humana de uma coisa, assim como usar seus próprios membros . Além disso, cada método não precisa passar parâmetros , e os parâmetros serão restaurados automaticamente após a compilação do compilador, ou seja, os parâmetros são mantidos pelo compilador e os parâmetros precisam ser mantidos pelo usuário.Stack*C++Stack *C语言


Este é o final deste artigo, o texto do código não é fácil, por favor, me apoie muito!

Acho que você gosta

Origin blog.csdn.net/weixin_67401157/article/details/130302091
Recomendado
Clasificación