
定义
封装是一组用于控制对数据的访问或管理该数据的方法的工具。 可以在我之前在Habré上的出版物上的链接找到“封装”一词的详细定义。 本文重点介绍C ++和C中的封装示例。
用C ++封装
默认情况下,在一个类( class
)中,数据和方法是私有的( private
); 它们只能由它们所属的类读取和修改。 可以使用C ++提供的适当关键字来更改访问级别。
C ++中提供了几个限定符,它们可以按以下方式修改数据访问:
- 公开数据可供所有人使用;
- protected(
protected
)-仅适用于班级和子班级; - 私有-
private
-仅对它们所属的类可用。
为简洁起见,示例中仅突出两个级别(私有和公共)。
封装实例
在Contact
类中,可以从主程序访问公共变量和方法。 私有变量和方法只能由类本身读取,调用或更改。
#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; }
尝试从主程序( main
)打印或更改私有变量mobile_number
会导致编译错误,因为对类中私有数据的访问受到限制。
与朋友的封装违规(良好实践)
在C ++中,有关键字“ friend”,它使您可以为访问数据的一般规则添加例外。 如果某个函数或类称为Contact
类的朋友,则他们可以自由访问受保护的数据或私有数据。
友谊有两个基本规则-友谊不是继承的,也不是相互的。 而且,“朋友”的存在不会改变数据安全性的级别-私有数据除“朋友”以外都保持私有。
#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; }
在此示例中, print_numbers()
函数是常规函数,而不是Contact
类的方法。 将print_numbers()
函数声明print_numbers()
Contact
类print_numbers()
“朋友”是print_numbers()
函数可以访问私有数据的唯一原因。 如果删除带有朋友定义的行,则代码将无法编译。
注意 :最好不要虐待朋友。 添加朋友应视为例外,而不是一般惯例。
具有类型转换和指针的封装违规(不良做法)
首先,值得注意的是,以这种方式使用指针和类型转换是一个坏主意。 此方法不能保证接收到必要的数据。 它阅读不佳,维护不善。 尽管如此,他仍然存在。
C ++从C继承了许多工具,其中之一就是typecasting
。 默认情况下,该类中的所有变量和方法都是私有的。 同时,结构( struct
)中数据访问的标准级别是公共的。 可以创建一个结构或完全公共的类,其中的数据将与Contact
类中的数据位于同一位置,并使用类型转换来访问私有数据。
#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; }
由于类型转换,已读取和修改了私有数据
C封装
传统上,封装是OOP的关键原则之一。 但是,这并不限制在面向过程的语言中使用此原理。 尽管缺少关键字“ private”和“ public”,但在C语言中封装已经使用了很长时间。
私有变量
在封装的上下文中,默认情况下可以将C中的所有数据视为公开的。 如果结构( struct
)的定义与主程序隔离,则可以将其访问级别更改为私有。 通过使用单独的头文件(header,.h)和源文件(source,.c),可以实现所需的效果。
在此示例中,该结构在单独的源文件“ private_var.c”中定义。 由于在C中初始化结构需要分配和释放内存,因此添加了一些辅助函数。
#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); }
在相应的头文件“ private_var.h”中,声明了Contact
结构,但其内容仍对主程序隐藏。
#ifndef PRIVATE_VAR #define PRIVATE_VAR struct Contact; struct Contact * create_contact(); void delete_contact( struct Contact * some_contact ); #endif
因此,对于“ main.c”而言,结构的内容是未知的,并且尝试读取或修改私有数据将导致编译错误。
#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; }
使用指针访问私有变量
类型转换可以用来克服在C和C ++中的封装,但是已经描述了这种方法。 知道结构中的数据是按其声明的顺序排列的,因此指针和指针的算术适用于实现目标。
对结构中变量的访问受到限制。 但是,仅隐藏变量,而不隐藏存储数据的内存。 可以将指针视为对内存地址的引用,如果程序可以使用该内存,则可以读取和更改存储在该内存中的数据。 如果将指针分配给结构存储其数据的内存,则可以读取它们。 使用相同的结构定义(相同的“ .c”和“ .h”文件)和修改后的“ main.c”文件,克服了访问限制。
#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; }
结构中的数据已被读取和修改
私人职能
默认情况下,函数是外部( extern
),在整个所谓的translation unit
中可见。 换句话说,如果将几个文件一起编译到一个目标文件中,则这些文件中的任何一个都将能够从任何其他文件访问任何功能。 创建函数时使用“ static” static
会将其可见性限制在定义该文件的文件中,因此,为了确保函数的私密性,您需要执行以下步骤:
- 必须在源文件(.c)或相应的头文件(.h)中将函数声明为static(
static
); - 函数定义必须在单独的源文件中。
在此示例中,静态函数print_numbers()
在文件“ private_funct.c”中定义。 顺便说一句,由于delete_contact()
函数位于同一文件中,因此它们成功调用了print_numbers()
。
#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); }
在相应的头文件“ private_funct.h”中, print_numbers()
已声明为静态函数。
#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
主程序“ main.c”通过delete_contact()
间接成功调用了delete_contact()
,因为两个函数都在同一文档中。 但是,尝试从主程序调用print_numbers()
会print_numbers()
错误。
#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; }
访问私有功能
print_numbers()
从主程序调用print_numbers()
。 为此,可以使用goto
关键字或将指针传递给main
的私有函数。 两种方法都需要在源文件“ private_funct.c”中或直接在函数本身中进行更改。 由于这些方法不会绕过封装并取消封装,因此它们不在本文的讨论范围之内。
结论
封装存在于OOP语言之外。 现代的OOP语言使使用封装变得方便而自然。 有许多方法可以规避封装,避免有问题的做法将有助于将其保存在C和C ++中。