什么是C中的编译时封装?


9

当我研究C相对于C ++的优势时,遇到了这一段:

用C进行封装的标准方法是转发声明一个结构,并且仅允许通过函数访问其数据。此方法还会创建编译时封装。编译时封装使我们能够更改数据结构成员,而无需重新编译客户端代码(使用我们接口的其他代码)。另一方面,进行封装C ++的标准方法(使用类)要求在添加或删除私有成员变量时重新编译客户端代码。

我了解如何通过函数声明结构并访问其成员如何隐藏该结构的实现细节。我不明白的是这条线是:

编译时封装使我们能够更改数据结构成员,而无需重新编译客户端代码(使用我们接口的其他代码)。

在什么情况下适用?


基本上,这struct是内部未知的黑匣子。如果客户端不了解内部结构,则永远无法直接访问它们,您可以随意更改它们。这类似于OOP中的封装。内部结构是私有的,您只能使用公共方法更改对象。
苏珊(Sulthan),

这并非总是如此。如果决定添加/删除结构的成员,请更改其大小。这将需要重新编译客户端代码。
DarkAtom

2
@DarkAtom不正确!如果客户端不知道内容(不透明的结构),则不知道其大小,因此更改大小不是问题。
阿德里安·摩尔

1
@DarkAtom:仅允许通过函数访问结构包括仅通过函数进行分配。该库将提供分配结构的功能,而客户端将永远不知道其大小。更改大小不需要重新编译客户端。
埃里克·波斯特皮希尔

3
请注意,从技术上讲,这并不是“ C优于C ++”,因为您可以(并且经常)在C ++中实现相同的想法。查找“ pimpl”成语
user4815162342 '19

Answers:


4

在硬盘空间非常有限的日子里编写的数据库库使用一个字节存储日期的“ year”字段时(例如11-NOV-1973),可能会发生在现实世界中将有73一年)。但是,当公元2000年问世时,这将不再足够,因此必须将年存储为短(16位)整数。该库的相关(更简化)标头可以是:

// dbEntry.h
typedef struct _dbEntry dbEntry;

dbEntry* CreateDBE(int day, int month, int year, int otherData);
void DeleteDBE(dbEntry* entry);
int GetYear(dbEntry* entry);

一个“客户”程序将是:

#include <stdio.h>
#include "dbEntry.h"

int main()
{
    int dataBlob = 42;
    dbEntry* test = CreateDBE(17, 11, 2019, dataBlob);
    //...
    int year = GetYear(test);
    printf("Year = %d\n", year);
    //...
    DeleteDBE(test);
    return 0;
}

“原始”实现:

#include <stdlib.h>
#include "dbEntry.h"

struct _dbEntry {
    unsigned char d;
    unsigned char m;
    unsigned char y;    // Fails at Y2K!
    int dummyData;
};

dbEntry* CreateDBE(int day, int month, int year, int otherData)
{
    dbEntry* local = malloc(sizeof(dbEntry));
    local->d = (unsigned char)(day);
    local->m = (unsigned char)(month);
    local->y = (unsigned char)(year % 100);
    local->dummyData = otherData;
    return local;
}

void DeleteDBE(dbEntry* entry)
{
    free(entry);
}

int GetYear(dbEntry* entry)
{
    return (int)(entry->y);
}

然后,以Y2K的方式,此实现文件将作如下更改(所有其他内容保持不变):

struct _dbEntry {
    unsigned char d;
    unsigned char m;
    unsigned short y;   // Can now differentiate 1969 from 2069
    int dummyData;
};

dbEntry* CreateDBE(int day, int month, int year, int otherData)
{
    dbEntry* local = malloc(sizeof(dbEntry));
    local->d = (unsigned char)(day);
    local->m = (unsigned char)(month);
    local->y = (unsigned short)(year);
    local->dummyData = otherData;
    return local;
}

当需要更新客户端以使用新的(Y2K安全)版本时,无需更改代码。事实上,你可能甚至没有重新编译:只需重新连接到更新的对象库(如果那是它是什么)可能是足够的。


2

注意:以下列表并不详尽。欢迎编辑!

适用的方案包括:

  • 由于某些原因您不想重新编译的多模块应用程序。
  • 在库中使用的结构,您不想在每次更改(已发布)结构时都强制库的用户重新编译。
  • 该模块在不同平台上包含不同元素的结构。

这种类型的最著名的结构是FILE。您只要fopen()成功调用即可获得一个指针。然后,该指针将移交给对文件起作用的其他函数。但是您不知道-也不想知道-细节,例如包含的元素和大小。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.