这是一个非常经典且重要的面试题,因为它揭示了 C++ 与 C 在面向对象思想上的一个核心差异。

核心结论:C 语言本身没有 protected 关键字
在标准的 C 语言(C89, C99, C11, C17 等)中,不存在 protected、public 或 private 这样的访问修饰符关键字,这些是 C++ 语言为了实现面向对象封装而引入的特性。
C 语言是一种过程式编程语言,它更侧重于函数和数据结构,而不是类和对象,它没有内置的访问控制机制。
C 语言如何实现“受保护”的访问?
虽然 C 语言没有 protected 关键字,但我们可以通过一些编程技巧和设计模式来模拟或实现类似 protected 的行为,其核心思想是:利用文件作用域(static)和函数指针来隐藏数据,只暴露必要的操作接口。
下面是几种常见的方法,从简单到复杂:

使用 static 关键字(文件作用域隐藏)
这是最基础的方法,将数据结构和操作函数都定义在同一个 .c 文件中,并用 static 修饰,使得它们对于外部文件是不可见的。
person.h (头文件 - 对外接口)
#ifndef PERSON_H #define PERSON_H // 对外只暴露一个不透明的指针类型 typedef struct Person Person; // 对外暴露的公共接口函数 Person* person_create(const char* name, int age); void person_print_info(const Person* p); void person_destroy(Person* p); #endif // PERSON_H
person.c (源文件 - 实现细节)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "person.h"
// --- 这里的内容对于外部是 "protected" 的 ---
// 定义 Person 的具体结构
struct Person {
char* name;
int age;
};
// 创建 Person 的具体实现
Person* person_create(const char* name, int age) {
if (name == NULL) return NULL;
Person* p = (Person*)malloc(sizeof(Person));
if (p == NULL) return NULL;
p->name = strdup(name); // 复制字符串
p->age = age;
return p;
}
// 打印信息的具体实现
void person_print_info(const Person* p) {
if (p == NULL) return;
printf("Name: %s, Age: %d\n", p->name, p->age);
}
// 销毁的具体实现
void person_destroy(Person* p) {
if (p == NULL) return;
free(p->name); // 先释放内部成员
free(p); // 再释放结构体本身
}
main.c (使用方)

#include "person.h"
int main() {
Person* p = person_create("Alice", 30);
person_print_info(p);
// 下面这行代码是错误的!
// 因为 struct Person 的定义在 person.c 中,main.c 看不到。
// 编译器会报错:dereferencing pointer to incomplete type 'struct Person'
// p->age = 31; // Error!
person_destroy(p);
return 0;
}
分析:
struct Person的具体定义被隐藏在了person.c文件中。- 外部使用者(
main.c)只能看到typedef struct Person Person;这个不完整的类型声明。 - 他们无法直接访问
p->name或p->age,因为这些成员是“受保护”的。 - 他们只能通过
person.h中声明的公共函数(person_create,person_print_info,person_destroy)来操作Person对象。 - 这种方法模拟了 C++ 中
private的效果,所有成员都是“私有的”,因为外部完全无法触及。
使用函数指针和结构体(模拟虚函数表)
这是一种更高级的技巧,可以模拟 C++ 中继承和多态的行为,并更好地划分 public 和 protected 的接口。
animal.h (基类头文件)
#ifndef ANIMAL_H #define ANIMAL_H // 对外只暴露一个不透明的指针类型 typedef struct Animal Animal; // 公共接口(相当于 C++ 的 public) Animal* animal_create(const char* name); void animal_say_hello(const Animal* a); void animal_destroy(Animal* a); #endif // ANIMAL_H
animal.c (基类实现)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "animal.h"
// --- 这里的内容对于外部是 "protected" 的 ---
// 定义 Animal 的内部结构(包含函数指针表)
struct Animal {
char* name;
// 指向虚函数表的指针
void (*_say_hello)(const struct Animal*);
void (*_destroy)(struct Animal*);
};
// 基类自己的 say_hello 实现
void _animal_say_hello_impl(const Animal* a) {
if (a) {
printf("Animal %s says: Hello!\n", a->name);
}
}
// 基类自己的 destroy 实现
void _animal_destroy_impl(Animal* a) {
if (a) {
free(a->name);
free(a);
}
}
// 公共接口的实现
Animal* animal_create(const char* name) {
Animal* a = (Animal*)malloc(sizeof(Animal));
if (!a) return NULL;
a->name = strdup(name);
// 初始化函数指针表(这里调用基类的实现)
a->_say_hello = _animal_say_hello_impl;
a->_destroy = _animal_destroy_impl;
return a;
}
void animal_say_hello(const Animal* a) {
// 通过函数指针调用,支持多态
if (a && a->_say_hello) {
a->_say_hello(a);
}
}
void animal_destroy(Animal* a) {
if (a && a->_destroy) {
a->_destroy(a);
}
}
dog.c (派生类实现)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "animal.h"
// --- Dog 的实现细节对于外部是 "protected" 的 ---
// Dog 的结构体,它包含基 Animal 的结构
struct Dog {
// 必须包含基类的所有成员,通常放在最前面
struct Animal base;
char* breed; // Dog 特有的成员
};
// Dog 特有的 say_hello 实现(覆盖基类)
void _dog_say_hello_impl(const Animal* a) {
// 将 Animal 指针转换回 Dog 指针以访问特有成员
const Dog* d = (const Dog*)a;
if (d) {
printf("Dog %s (a %s) says: Woof!\n", d->base.name, d->breed);
}
}
// Dog 特有的 destroy 实现
void _dog_destroy_impl(Animal* a) {
Dog* d = (Dog*)a;
if (d) {
free(d->breed); // 先释放 Dog 特有的成员
// 然后调用基类的 destroy 函数
// 注意:这里直接调用了基类的实现,而不是通过函数指针,以避免循环调用
// 在更复杂的系统中,会有一个基类的析构函数指针
free(d->base.name);
free(d);
}
}
// 创建 Dog 的公共接口
Animal* dog_create(const char* name, const char* breed) {
Dog* d = (Dog*)malloc(sizeof(Dog));
if (!d) return NULL;
d->base.name = strdup(name);
d->breed = strdup(breed);
// 初始化 Dog 的函数指针表
d->base._say_hello = _dog_say_hello_impl;
d->base._destroy = _dog_destroy_impl;
// 返回基类指针,实现多态
return (Animal*)d;
}
main.c (使用方)
#include "animal.h"
// 注意:我们不需要包含 dog.h,因为我们只通过 Animal 指针操作
int main() {
Animal* a1 = animal_create("Generic Animal");
Animal* a2 = dog_create("Buddy", "Golden Retriever");
animal_say_hello(a1); // 调用 Animal 的实现
animal_say_hello(a2); // 调用 Dog 的实现(多态)
animal_destroy(a1);
animal_destroy(a2);
return 0;
}
分析:
struct Animal和struct Dog的具体定义都是受保护的。Animal的函数指针表 (_say_hello,_destroy) 对于外部也是受保护的,用户只能通过animal_say_hello这样的公共函数来间接调用。- 这种结构清晰地划分了接口(
animal.h)和实现(animal.c,dog.c)。 - 用户(
main.c)完全不知道Dog的存在,他只操作Animal指针,却能表现出不同的行为,这就是多态。 - 这种方法模拟了 C++ 中
protected成员函数和public接口的概念。_say_hello这样的内部函数可以被派生类(Dog)访问和覆盖,但外部用户不能直接调用。
总结对比
| 特性 | C++ | C 语言 (模拟方式) |
|---|---|---|
| 关键字 | public, protected, private |
无 |
| 封装 | 通过访问修饰符强制编译器检查 | 通过文件作用域 (static) 和不透明指针手动实现 |
protected |
允许子类访问,禁止外部访问 | 通过将函数/数据定义在 .c 文件中,并仅在内部使用 |
public |
允许所有人访问 | 在 .h 文件中声明函数接口 |
private |
只允许类自身访问 | 与 protected 模拟方式相同,通常统称为“实现细节隐藏” |
| 继承/多态 | 语言内置支持 | 通过结构体嵌套和函数指针表手动模拟,非常复杂且容易出错 |
虽然 C 语言没有 protected 关键字,但通过 头文件/源文件分离 和 不透明指针 的设计模式,C 语言可以实现比 C++ private 更强的封装性,而要模拟 protected 的概念(即允许“派生”代码访问,但禁止外部访问),则需要更复杂的结构,如方法二中的函数指针表,这在 C++ 中被称为 “Pimpl (Pointer to Implementation) Idiom” 的变种。
在实际的 C 项目开发中,第一种方法(static + 不透明指针)是构建健壮库的标准做法,它有效地隐藏了实现细节,保护了数据完整性。
