
Definition
Die Kapselung ist eine Reihe von Tools zur Steuerung des Zugriffs auf Daten oder Methoden, die diese Daten verwalten. Eine detaillierte Definition des Begriffs „Einkapselung“ finden Sie in meiner vorherigen Veröffentlichung auf dem Habré unter diesem Link . Dieser Artikel konzentriert sich auf Kapselungsbeispiele in C ++ und C.
Kapselung in C ++
Standardmäßig sind in einer Klasse ( class
) Daten und Methoden privat ( private
). Sie können nur von der Klasse gelesen und geändert werden, zu der sie gehören. Die Zugriffsebene kann mit den entsprechenden Schlüsselwörtern geändert werden, die C ++ bereitstellt.
In C ++ sind mehrere Qualifizierer verfügbar, die den Datenzugriff wie folgt ändern:
- öffentliche Daten stehen allen zur Verfügung;
- geschützt (
protected
) - nur für Klassen- und Kinderklassen verfügbar; - privat -
private
- nur für die Klasse verfügbar, zu der sie gehören.
Der Kürze halber werden in den Beispielen nur zwei Ebenen (privat und öffentlich) hervorgehoben.
Verkapselungsbeispiel
In der Contact
Klasse kann über das Hauptprogramm auf öffentliche Variablen und Methoden zugegriffen werden. Private Variablen und Methoden können nur von der Klasse selbst gelesen, aufgerufen oder geändert werden.
#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; }
Der Versuch, die private Variable mobile_number
aus dem Hauptprogramm ( main
) zu drucken oder zu ändern, führt zu einem Kompilierungsfehler, da der Zugriff auf private Daten in der Klasse eingeschränkt ist.
Verkapselungsverletzung mit Freunden (Gute Praxis)
In C ++ gibt es ein Schlüsselwort "Freund", mit dem Sie Ausnahmen zu den allgemeinen Regeln für den Zugriff auf Daten hinzufügen können. Wenn eine Funktion oder Klasse als Freund der Contact
Klasse bezeichnet wird, erhalten sie freien Zugriff auf geschützte oder private Daten.
Es gibt zwei Grundregeln der Freundschaft - Freundschaft wird nicht vererbt und ist nicht gegenseitig. Auch die Anwesenheit von „Freunden“ ändert nichts an der Datensicherheit - private Daten bleiben mit Ausnahme von „Freund“ privat.
#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; }
In diesem Beispiel ist die Funktion print_numbers()
eine normale Funktion und keine Methode der Contact
Klasse. Das Deklarieren der Funktion print_numbers()
"Freund" der Contact
Klasse ist der einzige Grund, print_numbers()
Funktion print_numbers()
Zugriff auf private Daten hat. Wenn Sie die Zeile mit der Definition eines Freundes entfernen, wird der Code nicht kompiliert.
Hinweis : Es ist besser, Freunde nicht zu missbrauchen. Das Hinzufügen eines Freundes sollte als Ausnahme und nicht als allgemeine Praxis betrachtet werden.
Kapselungsverletzung mit Typkonvertierung und Zeigern (schlechte Praxis)
Zunächst ist anzumerken, dass die Verwendung von Zeigern und die Typkonvertierung auf diese Weise eine schlechte Idee ist. Diese Methode garantiert nicht den Empfang der erforderlichen Daten. Es ist schlecht gelesen und schlecht gepflegt. Trotzdem existiert er.
C ++ hat viele Tools von C geerbt, von denen eines typecasting
. Standardmäßig sind alle Variablen und Methoden in der Klasse privat. Gleichzeitig ist die Standardstufe des Datenzugriffs in der Struktur ( struct
) öffentlich. Es ist möglich, eine Struktur oder eine vollständig öffentliche Klasse zu erstellen, in der sich die Daten identisch mit den Daten in der Contact
, und mithilfe der Typkonvertierung auf private Daten zuzugreifen.
#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; }
Private Daten wurden aufgrund der Typkonvertierung gelesen und geändert
C-Einkapselung
Die Kapselung wird traditionell als eines der wichtigsten OOP-Prinzipien angesehen. Dies schränkt jedoch die Verwendung dieses Prinzips in prozedural ausgerichteten Sprachen nicht ein. In C wird die Kapselung trotz des Fehlens der Schlüsselwörter "privat" und "öffentlich" lange Zeit verwendet.
Private Variablen
Im Zusammenhang mit der Kapselung können alle Daten in C standardmäßig als öffentlich betrachtet werden. Die Zugriffsebene auf Variablen in Strukturen ( struct
) kann in private geändert werden, wenn ihre Definition vom Hauptprogramm isoliert ist. Der gewünschte Effekt kann durch die Verwendung separater Header- (Header, .h) und Quelldateien (Source, .c) erzielt werden.
In diesem Beispiel wurde die Struktur in einer separaten Quelldatei „private_var.c“ definiert. Da das Initialisieren der Struktur in C das Zuweisen und Freigeben von Speicher erfordert, wurden mehrere Hilfsfunktionen hinzugefügt.
#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); }
In der entsprechenden Header-Datei "private_var.h" wurde die Contact
deklariert, ihr Inhalt blieb jedoch für das Hauptprogramm verborgen.
#ifndef PRIVATE_VAR #define PRIVATE_VAR struct Contact; struct Contact * create_contact(); void delete_contact( struct Contact * some_contact ); #endif
Daher ist für "main.c" der Inhalt der Struktur unbekannt, und Versuche, private Daten zu lesen oder zu ändern, führen zu einem Kompilierungsfehler.
#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; }
Zugriff auf private Variablen mit Zeigern
Die Typkonvertierung kann verwendet werden, um die Kapselung sowohl in C als auch in C ++ zu überwinden. Dieser Ansatz wurde jedoch bereits beschrieben. In dem Wissen, dass die Daten in der Struktur in der Reihenfolge ihrer Deklaration angeordnet sind, sind Zeiger und Arithmetik von Zeigern geeignet, um das Ziel zu erreichen.
Der Zugriff auf Variablen in der Struktur ist begrenzt. Es werden jedoch nur Variablen ausgeblendet, nicht der Speicher, in dem die Daten gespeichert sind. Zeiger können als Referenz auf die Speicheradresse betrachtet werden, und wenn dieser Speicher dem Programm zur Verfügung steht, können die in diesem Speicher gespeicherten Daten gelesen und geändert werden. Wenn der Zeiger dem Speicher zugewiesen ist, in dem die Struktur ihre Daten speichert, können sie gelesen werden. Bei Verwendung derselben Strukturdefinition (dieselben ".c" - und ".h" -Dateien) und der geänderten "main.c" -Datei wurde die Zugriffsbeschränkung überwunden.
#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; }
Die Daten in der Struktur wurden gelesen und geändert
Private Funktionen
Funktionen, die standardmäßig extern ( extern
) sind, sind in der gesamten sogenannten translation unit
sichtbar. Mit anderen Worten, wenn mehrere Dateien zu einer Objektdatei zusammengefasst werden, kann jede dieser Dateien von jeder anderen Datei aus auf jede Funktion zugreifen. Wenn Sie beim Erstellen einer Funktion das static
"static" verwenden, wird die Sichtbarkeit auf die Datei beschränkt, in der sie definiert wurde. Um die Privatsphäre der Funktion zu gewährleisten, müssen Sie daher mehrere Schritte ausführen:
- Die Funktion muss entweder in der Quelldatei (.c) oder in der entsprechenden Header-Datei (.h) als statisch (
static
) deklariert werden. - Die Funktionsdefinition muss sich in einer separaten Quelldatei befinden.
In diesem Beispiel wurde die statische Funktion print_numbers()
in der Datei "private_funct.c" definiert. Übrigens ruft die Funktion delete_contact()
erfolgreich print_numbers()
da sie sich in derselben Datei befinden.
#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); }
In der entsprechenden Header-Datei "private_funct.h" wurde print_numbers()
als statische Funktion deklariert.
#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
Das Hauptprogramm "main.c" ruft erfolgreich indirekt delete_contact()
über delete_contact()
, da sich beide Funktionen im selben Dokument befinden. Ein Versuch, print_numbers()
vom Hauptprogramm aus print_numbers()
Fehler.
#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; }
Zugriff auf private Funktionen
Es ist print_numbers()
vom Hauptprogramm aus print_numbers()
. Dazu können Sie das Schlüsselwort goto
oder einen Zeiger auf eine private Funktion in main
. Beide Methoden erfordern Änderungen entweder in der Quelldatei „private_funct.c“ oder direkt im Hauptteil der Funktion. Da diese Methoden die Kapselung nicht umgehen und aufheben, gehen sie über den Rahmen dieses Artikels hinaus.
Fazit
Die Kapselung existiert außerhalb der OOP-Sprachen. Moderne OOP-Sprachen machen die Kapselung bequem und natürlich. Es gibt viele Möglichkeiten, die Kapselung zu umgehen, und das Vermeiden fragwürdiger Praktiken hilft, sie sowohl in C als auch in C ++ beizubehalten.