C语言面向对象

关注微信公众号塔容万物

面向对象的思想

c语言没有在语言层面支持面向对象的特性,但是可以通过结构体和函数模拟面向对象的思想。

在Python中,可以通过class关键字实现面向对象的写法

class User:
  """ 定义类 """
  def __init__(
      self,
      username: str,
      password: str
  ):
    """ 初始化类 """
    self.username = username
    self.password = password

  def user_print(self):
    """ 定义类方法 """
    return f'User(username={self.username}, password={self.password})'

if __name__ == '__main__':
    user = User('admin', '123456')
    print(user.user_print()) # User(username=admin, password=123456)

上面的self其实是一个指向当前对象的指针,在Python中,编译器会自动将user传入user_print函数中,所以我们可以直接调用user_print函数。

在C语言中,我们需要手动传入user指针,所以我们需要将user_print函数的第一个参数改为struct User*类型。


// 定义类
struct User {
  char* username;
  char* password;
};

// 初始化类
struct User*
user_init(char* username, char* password)
{
  struct User* user = malloc(sizeof(struct User));
  user->username = strdup(username);
  user->password = strdup(password);
  return user;
}

// 定义类方法
void
user_print(struct User* user)
{
  printf("User(username=%s, password=%s)\n", user->username, user->password);
}

int
main()
{
  struct User* user = user_init("admin", "123456");
  user_print(user);
  free(user->username);
  free(user->password);
  free(user);
  return 0;
}

通过对比可以发现,Python中的类和C语言中的结构体+函数的写法非常相似,只是Python中的类和类方法的定义更加简洁。

如何实现继承

在Python中,我们可以通过class关键字的()来实现继承,通过指定父类的类名来实现继承。

class VIPUser(User):
  """ 定义类 """
  def __init__(
      self,
      username: str,
      password: str,
      level: int
  ):
    """ 初始化类 """
    super().__init__(username, password)
    self.level = level

由于在C语言中,并没有类的概念,优雅的实现继承非常困难。下面介绍几种实现继承的方法。

通过结构体嵌套实现继承

// 定义父类
struct User {
  char* username;
  char* password;
};

// 定义子类
struct VIPUser {
  struct User user;
  int level;
};

// 初始化子类
struct VIPUser*
vip_user_init(char* username, char* password, int level)
{
  struct VIPUser* vip_user = malloc(sizeof(struct VIPUser));
  vip_user->user.username = strdup(username);
  vip_user->user.password = strdup(password);
  vip_user->level = level;
  return vip_user;
}

通过结构体嵌套的方式,我们可以实现继承,但是这种方式有一个缺点,在访问父类的属性时,需要加上user前缀,比较麻烦。

通过宏实现继承

将子类与父类的属性合并到一起,通过宏定义放在结构体的最前面,这样就可以直接访问父类的属性。cpython的PyObjectlua语言中GCObject结构体,都是通过宏定义实现继承的。

// 定义父类
#define UserHeader \
  char* username; \
  char* password

// 定义父类
struct User {
  UserHeader;
};

// 定义子类
struct VIPUser {
  UserHeader;
  int level;
};

// 初始化子类
struct VIPUser*
vip_user_init(char* username, char* password, int level)
{
  struct VIPUser* vip_user = malloc(sizeof(struct VIPUser));
  vip_user->username = strdup(username);
  vip_user->password = strdup(password);
  vip_user->level = level;
  return vip_user;
}

这种实现方法相比于第一种,可以直接访问共同的(父类)属性,而不需要加上user前缀。通过在父类中添加类型标注信息,可以实现多态,即处理不同的对象时,调用相同的方法,但是会根据对象的类型,调用不同的方法。

typedef enum {
  USER_TYPE,
  VIP_USER_TYPE,
} UserType;

// 定义父类
#define UserHeader                                                            \
  char* username;                                                             \
  char* password;                                                             \
  UserType type;

添加type成员,用来标识对象的类型,然后在初始化对象时,将type成员设置为对应的类型。

// 定义父类
typedef struct {
  UserHeader;
} User;

// 定义子类
typedef struct {
  UserHeader;
  int level;
} VIPUser;

// 当是普通用户时,调用这个函数
void
default_print_user(User* user)
{
  printf("User(username=%s, password=%s)\n", user->username, user->password);
}

// 当是 VIP 用户时,调用这个函数
void
vip_print_user(VIPUser* user)
{
  printf("VIPUser(username=%s, password=%s, level=%d)\n", user->username,
         user->password, user->level);
}

// User的打印函数
// 可以同时打印普通用户和 VIP 用户
void
print_user(User* user)
{
  switch (user->type) {
  case USER_TYPE:
    default_print_user(user);
    break;
  case VIP_USER_TYPE:
    vip_print_user((VIPUser*)user);
    break;
  }
}