Encapsulamento em C ++ e C


Definição de


Encapsulamento é um conjunto de ferramentas para controlar o acesso a dados ou métodos que gerenciam esses dados. Uma definição detalhada do termo "encapsulamento" pode ser encontrada na minha publicação anterior no Habré neste link . Este artigo se concentra em exemplos de encapsulamento em C ++ e C.


Encapsulamento em C ++


Por padrão, em uma classe ( class ), os dados e métodos são privados ( private ); eles só podem ser lidos e modificados pela classe à qual pertencem. O nível de acesso pode ser alterado usando as palavras-chave apropriadas fornecidas pelo C ++.


Vários qualificadores estão disponíveis no C ++ e eles modificam o acesso a dados da seguinte maneira:


  • dados públicos estão disponíveis para todos;
  • protegido ( protected ) - disponível apenas para a classe e as classes filho;
  • privado - private - disponível apenas para a classe à qual eles pertencem.

Por uma questão de brevidade, apenas dois níveis (privado e público) serão destacados nos exemplos.


Exemplo de encapsulamento


Na classe Contact , variáveis ​​e métodos públicos são acessíveis no programa principal. Variáveis ​​e métodos privados só podem ser lidos, chamados ou alterados pela própria classe.


 #include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } void print_numbers() { cout << "Mobile number: " << mobile_number; cout << ", home number: " << home_number << endl; } }; int main() { Contact Tony; Tony.print_numbers(); // cout << Tony.mobile_number << endl; // will cause compile time error return 0; } 

Tentar imprimir ou modificar a variável privada mobile_number do programa principal ( main ) causará um erro de compilação porque o acesso aos dados privados na classe é limitado.


Violação de encapsulamento com amigos (boas práticas)


No C ++, existe a palavra-chave "amigo", que permite adicionar exceções às regras gerais para acessar dados. Se uma função ou classe é chamada de amiga da classe Contact , elas obtêm acesso gratuito a dados protegidos ou privados.


Existem duas regras básicas da amizade - a amizade não é herdada e não é mútua. Além disso, a presença de "amigos" não altera o nível de segurança dos dados - os dados privados permanecem privados, com exceção de "amigo".


 #include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } // Declaring a global 'friend' function friend void print_numbers( Contact some_contact ); }; void print_numbers( Contact some_contact ) { cout << "Mobile number: " << some_contact.mobile_number; cout << ", home number: " << some_contact.home_number << endl; } int main() { Contact Tony; print_numbers(Tony); return 0; } 

Neste exemplo, a função print_numbers() é uma função normal, não um método da classe Contact . Declarar a função print_numbers() "amigo" da classe Contact é o único motivo pelo qual a função print_numbers() tem acesso a dados privados. Se você remover a linha com a definição de amigo, o código não será compilado.


Nota : é melhor não abusar de amigos. Adicionar um amigo deve ser considerado uma exceção, não uma prática geral.


Violação de encapsulamento com conversão de tipo e ponteiros (más práticas)


Antes de tudo, vale a pena notar que usar ponteiros e conversão de tipos dessa maneira é uma má idéia. Este método não garante o recebimento dos dados necessários. É mal lido e mal conservado. Apesar disso, ele existe.


O C ++ herdou muitas ferramentas do C, uma das quais é a typecasting . Por padrão, todas as variáveis ​​e métodos na classe são privados. Ao mesmo tempo, o nível padrão de acesso a dados na estrutura ( struct ) é público. É possível criar uma estrutura ou uma classe totalmente pública na qual os dados serão localizados de forma idêntica aos dados na classe Contact e usando a conversão de tipo para acessar dados privados.


 #include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } void print_numbers() { cout << "Mobile number: " << mobile_number; cout << ", home number: " << home_number << endl; } }; struct Contact_struct { int mobile_number; int home_number; }; int main() { Contact Tony; Contact_struct * structured_Tony; Tony.print_numbers(); structured_Tony = (Contact_struct *) & Tony; structured_Tony->mobile_number = 20; structured_Tony->home_number = 30; Tony.print_numbers(); return 0; } 

Os dados privados foram lidos e modificados devido à conversão de tipo


Encapsulamento C


Tradicionalmente, o encapsulamento é considerado um dos principais princípios de POO. No entanto, isso não limita o uso desse princípio em linguagens orientadas a procedimentos. Em C, o encapsulamento é usado há muito tempo, apesar da falta das palavras-chave "privado" e "público".


Variáveis ​​privadas


No contexto do encapsulamento, todos os dados em C podem ser considerados públicos por padrão. O nível de acesso a variáveis ​​em estruturas ( struct ) pode ser alterado para privado se sua definição for isolada do programa principal. O efeito desejado pode ser alcançado usando os arquivos de cabeçalho (cabeçalho, .h) e de origem (origem, .c) separados.


Neste exemplo, a estrutura foi definida em um arquivo de origem separado “private_var.c”. Como a inicialização da estrutura em C requer alocação e liberação de memória, várias funções auxiliares foram adicionadas.


 #include "private_var.h" #include <stdio.h> #include <stdlib.h> struct Contact { int mobile_number; int home_number; }; struct Contact * create_contact() { struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); } void delete_contact( struct Contact * some_contact ) { free(some_contact); } 

No arquivo de cabeçalho correspondente "private_var.h", a estrutura de Contact foi declarada, mas seu conteúdo permaneceu oculto no programa principal.


 #ifndef PRIVATE_VAR #define PRIVATE_VAR struct Contact; struct Contact * create_contact(); void delete_contact( struct Contact * some_contact ); #endif /* PRIVATE_VAR */ 

Assim, para “main.c” o conteúdo da estrutura é desconhecido e as tentativas de ler ou modificar dados particulares causarão um erro de compilação.


 #include "private_var.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); // printf( "Mobile number: %d\n", Tony->mobile_number); // will cause compile time error delete_contact( Tony ); return 0; } 

Acessando variáveis ​​privadas com ponteiros


A conversão de tipo pode ser usada para superar o encapsulamento em C e também em C ++, mas essa abordagem já foi descrita. Sabendo que na estrutura os dados são organizados na ordem de sua declaração, ponteiros e aritmética de ponteiros são adequados para atingir a meta.


O acesso a variáveis ​​na estrutura é limitado. No entanto, apenas variáveis ​​estão ocultas, não a memória na qual os dados estão armazenados. Os ponteiros podem ser considerados como uma referência ao endereço da memória e, se essa memória estiver disponível para o programa, os dados armazenados nessa memória poderão ser lidos e alterados. Se o ponteiro estiver atribuído à memória na qual a estrutura armazena seus dados - eles podem ser lidos. Usando a mesma definição de estrutura (os mesmos arquivos ".c" e ".h") e o arquivo "main.c" modificado, a restrição de acesso foi superada.


 #include "private_var.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); int * mobile_number_is_here = (int *)Tony; printf("Mobile number: %d\n", *mobile_number_is_here); int * home_number_is_here = mobile_number_is_here + 1; *home_number_is_here = 1; printf("Modified home number: %d\n", *home_number_is_here); delete_contact( Tony ); return 0; } 

Os dados na estrutura foram lidos e modificados


Funções privadas


As funções, sendo externas (externas) por padrão, são visíveis em toda a chamada translation unit . Em outras palavras, se vários arquivos forem compilados juntos em um arquivo de objeto, qualquer um desses arquivos poderá acessar qualquer função de qualquer outro arquivo. O uso da static “estática” ao criar uma função limitará sua visibilidade ao arquivo em que foi definida, portanto, para garantir a privacidade da função, é necessário executar várias etapas:


  • a função deve ser declarada estática ( static ) no arquivo de origem (.c) ou no arquivo de cabeçalho correspondente (.h);
  • a definição da função deve estar em um arquivo de origem separado.

Neste exemplo, a função estática print_numbers() foi definida no arquivo "private_funct.c". A propósito, a função delete_contact() chama com êxito print_numbers() pois eles estão no mesmo arquivo.


 #include "private_funct.h" #include <stdio.h> #include <stdlib.h> struct Contact { int mobile_number; int home_number; }; struct Contact * create_contact() { struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); } static void print_numbers( struct Contact * some_contact ) { printf("Mobile number: %d, ", some_contact->mobile_number); printf("home number = %d\n", some_contact->home_number); } void delete_contact( struct Contact * some_contact ) { print_numbers(some_contact); free(some_contact); } 

No arquivo de cabeçalho correspondente "private_funct.h", print_numbers() foi declarado como uma função estática.


 #ifndef PRIVATE_FUNCT_H #define PRIVATE_FUNCT_H struct Contact; struct Contact * create_contact(); static void print_numbers( struct Contact * some_contact ); void delete_contact( struct Contact * my_points ); #endif /* PRIVATE_FUNCT_H */ 

O programa principal, “main.c”, chama com êxito print_numbers() indiretamente via delete_contact() , pois ambas as funções estão no mesmo documento. No entanto, uma tentativa de chamar print_numbers() do programa principal gerará print_numbers() erro.


 #include "private_funct.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); // print_numbers( Tony ); // will cause compile time error delete_contact( Tony ); return 0; } 

Acessando recursos particulares


É print_numbers() chamar print_numbers() no programa principal. Para fazer isso, você pode usar a palavra-chave goto ou passar um ponteiro para uma função privada em main . Ambos os métodos requerem alterações no arquivo de origem "private_funct.c" ou diretamente no corpo da própria função. Como esses métodos não ignoram o encapsulamento e o cancelam, estão além do escopo deste artigo.


Conclusão


O encapsulamento existe fora dos idiomas OOP. As linguagens modernas de POO tornam o uso do encapsulamento conveniente e natural. Existem muitas maneiras de contornar o encapsulamento e evitar práticas questionáveis ​​ajudará a preservá-lo em C e C ++.

Source: https://habr.com/ru/post/pt444602/


All Articles