数码资讯
C语言使用结构体面向对象编程举例讲解
示例1:验证器
例1来自于 百度文库 C语言实现面向对象编程,现在本文给他大家提供了可以自由学习的版本。
validator.h
#ifndef ZIYU_LEARN_C_VALIDATOR_H
#define ZIYU_LEARN_C_VALIDATOR_H
#include <stdbool.h>
typedef struct Validator_t {
bool (* const validate)(struct Validator_t *this, int value);
} Validator;
typedef struct {
Validator proxy;
int min;
int max;
} RangeValidator;
typedef struct {
Validator proxy;
bool isEven; //
} OddEvenValidator;
bool isInRange(Validator* this, int value); // 范围校验函数
bool isEvenOrElseOdd(Validator* this, int value); // 奇偶校验函数
#define newRangeValidator(min, max) {{isInRange}, (min), (max)}
#define newOddEvenValidator(isEven) {{isEvenOrElseOdd}, (isEven)}
#endif //ZIYU_LEARN_C_VALIDATOR_H
易错点:
Validator proxy;不要写成Validator* proxy(加入星号表示指针),否则会让宏定义变得更复杂!稍后 Animal 的例子中会讲。
- 如果不明白 struct 语法的,可以 移步 C语言结构体详解
- 如果不明白 typedef struct 语法的,可以 移步 C语言中typedef和指针连用实用讲解
本文准备详细讲解的是宏定义的这段语法:
首先,min,max, isEven 都是用到了带参数的宏:
#define MAX(x,y) ((x)>(y) ? (x) :(y))
i = MAX ( j+k, m-n );
i = (( j+k ) > ( m-n ) ? ( j+k ) : ( m-n ));
#define IS_EVEN(n) ((n)%2==0)
if (IS_EVEN( i )) i++;
if (( ( i ) % 2 == 0 )) i++;
解析 #define newXXX(arg0) { func0 , (arg0)} 语法
预处理器会将宏定义的参数“原封不动”进行替换。因此,我们需要关注的是 { },及它所代表的 struct 结构体变量整体赋值的语法:
#include <stdio.h>
typedef struct {
int age;
void (* swim)();
} Animal;
void practice(); // 声明一个表示练习游泳的函数
int main() {
// 定义结构体变量a的同时,进行整体赋值。
Animal a = {11, practice};
printf("%d\n", a.age);
a.swim();
return 0;
}
void practice() {
printf("practising the swim.\n");
}
可以尝试将结构体Animal整体赋值的部分写成宏定义,如下表所示:
#define newAnimal(age) {(age), practice}
Animal a = newAnimal( i );
Animal a = {( i ), practice};
因此,上面的代码可以改写成:
#include <stdio.h>
typedef struct {
int age;
void (* swim)();
} Animal;
void practice(); // 声明一个表示练习游泳的函数
#define newAnimal(age) {(age), practice}
int main() {
Animal a = newAnimal(11);
printf("%d\n", a.age);
a.swim();
return 0;
}
void practice() {
printf("practising the swim.\n");
}
解析 #define newXXX(arg0) &(XXX){ func0 , (arg0)} 语法
接下来,某种意义上讲,是我自己把问题搞复杂了,但是也算给大家提个醒吧:
Animal* pa = newAnimal(11); // 我误加了星号,引出了后面的讨论
编译时出错,我截取了关键的报错信息:
/home/geekziyu/CLionProjects/ziyu-learn-jvm-c/learnc/main.c:11:25: warning: initialization of ‘Animal *’ {aka ‘struct <anonymous> *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
11 | #define newAnimal(age) {(age), practice}
...
/home/geekziyu/CLionProjects/ziyu-learn-jvm-c/learnc/main.c:11:32: warning: excess elements in scalar initializer
11 | #define newAnimal(age) {(age), practice}
第一个报错没搜索出有用的解决方案,但是第二个报错搜出来了:https://stackoverflow.com/questions/50614424/excess-elements-in-scalar-initializer-for-struct
因此,代码修改为:
#include "validator.h"
#include <stdio.h>
typedef struct {
int age;
void (* swim)();
} Animal;
void practice(); // 声明一个表示练习游泳的函数
#define newAnimal(age) &(Animal){(age), practice}
int main() {
// 注意这里pa是指针,而非结构体变量
Animal* pa = newAnimal(11);
printf("%d\n", pa->age);
pa->swim();
return 0;
}
void practice() {
printf("practising the swim.\n");
}
宏定义的预处理替换结果如下表所示:
#define newAnimal(age) &(Animal){(age), practice}
Animal* a = newAnimal( i );
Animal* a = &(Animal){( i ), practice};
validator.c
#include "validator.h"
bool isInRange(Validator* this, int value) {
RangeValidator* validator = (RangeValidator*) this;
return (*validator).min > value && validator->max < value;
}
bool isEvenOrElseOdd(Validator* this, int value) {
OddEvenValidator* validator = (OddEvenValidator*) this;
if (validator->isEven) { // 偶数
return value % 2 == 0;
} else {
return value % 2 == 1;
}
}
main.c
#include "validator.h"
#include <stdio.h>
int main() {
// 测试范围校验器
RangeValidator rangeValidator = newRangeValidator(1, 10);
Validator* rangeProxy = &rangeValidator.proxy;
int inputVal;
printf("Please input an integer :");
scanf("%d", &inputVal);
printf("\n");
if (rangeProxy->validate(rangeProxy, inputVal)) {
printf("The integer %d is in the range from 1 to 10.", inputVal);
} else {
printf("The integer %d is NOT in the range from 1 to 10.", inputVal);
}
// 测试奇偶校验器
OddEvenValidator evenValidator = newOddEvenValidator(true);
Validator* evenProxy = &evenValidator.proxy;
printf("\nPlease input an integer :");
scanf("%d", &inputVal);
printf("\n");
if (evenProxy->validate(evenProxy, inputVal)) {
printf("The integer %d is an even number.", inputVal);
} else {
printf("The integer %d is NOT an even number.", inputVal);
}
return 0;
}
本例小节
- C语言中没有
class关键字(那是 C++ 才有的),因此使用结构体及其关键字struct表示“类”; - C语言中也没有构造器,带参数的宏(例如
#define newXXX(arg0, arg1) {(arg0) , (arg1)}} 作用类似于 “类的构造器”; - C语言中也没有类的继承,运用结构体内嵌套其他结构体的方法,可以实现“组合”,效果类似于“继承”;
(例如typedef struct { Validator proxy; ... } RangeValidator;) - C语言中也没有多态,运用函数指针(指向函数的指针)作为结构体的组成部分,通过传入不同的函数,可以实现“多态”的效果;
(例如typedef struct { void (* swim)();} Animal;)
示例2:Dog继承Animal
参考自 电子发烧友 C语言要如何面向对象编程?,给大家提供一个看起来更友好的版本。
父类Animal
animal.h :
#ifndef ZIYU_LEARN_JVM_C_ANIMAL_H
#define ZIYU_LEARN_JVM_C_ANIMAL_H
// 定义父类结构
typedef struct {
int age;
int weight;
} Animal;
// 构造函数声明
void Animal_Init(Animal *this, int age, int weight);
// 获取父类属性声明
int Animal_GetAge (Animal *this);
int Animal_GetWeight (Animal *this);
#endif //ZIYU_LEARN_JVM_C_ANIMAL_H
animal.c :
#include "animal.h"
void Animal_Init(Animal *this, int age, int weight) {
this->age = age;
this->weight = weight;
}
int Animal_GetAge (Animal *this){
return this->age;
}
int Animal_GetWeight(Animal *this) {
return this->weight;
}
animal.c 中实现了 animal.h 中声明而未定义的方法。
与C++对比:在C++的方法中,隐含着第一个参数 this 指针。当调用一个对象的方法时,编译器会自动把对象的地址传递给这个指针。
所以,在 animal.h 中函数我们就模拟一下,显示的定义这个this指针,在调用时主动把对象的地址传递给它,这样的话,函数就可以对任意一个 Animal 对象进行处理了。
子类Dog
dog.h
#ifndef ZIYU_LEARN_JVM_C_DOG_H
#define ZIYU_LEARN_JVM_C_DOG_H
#include "animal.h"
// 定义子类结构
typedef struct {
Animal parent;// 第一个位置放置父类结构
int legs;// 添加子类自己的属性
} Dog;
// 子类构造函数声明
void Dog_Init(Dog *this, int age, int weight, int legs);
// 子类属性声明
int Dog_GetAge(Dog *this);
int Dog_GetWeight(Dog *this);
int Dog_GetLegs(Dog *this);
#endif //ZIYU_LEARN_JVM_C_DOG_H
dog.c
#include "dog.h"
// 子类构造函数实现
void Dog_Init(Dog *this, int age, int weight, int legs) {
// 首先调用父类构造函数,来初始化从父类继承的数据
Animal_Init(&this->parent, age, weight);
// 然后初始化子类自己的数据
this->legs = legs;
}
int Dog_GetAge(Dog *this) {
// age属性是继承而来,转发给父类中的获取属性函数
return Animal_GetAge(&this->parent);
}
int Dog_GetWeight(Dog *this) {
return Animal_GetWeight(&this->parent);
}
int Dog_GetLegs(Dog *this) {
// 子类自己的属性,直接返回
return this->legs;
}
测试类
main.c :
#include <stdio.h>
#include "animal.h"
#include "dog.h"
int main() {
// 在栈上创建一个Animal对象
Animal a;
// 构造对象
Animal_Init(&a, 1, 3);
printf("age = %d, weight = %d \n", Animal_GetAge(&a), Animal_GetWeight(&a));
// 在栈上创建一个Dog对象
Dog d;
Dog_Init(&d, 6, 9, 4);
printf("age = %d, weight = %d, legs = %d \n", Dog_GetAge(&d), Dog_GetWeight(&d), Dog_GetLegs(&d));
return 0;
}
参考文档
- 电子发烧友 C语言要如何面向对象编程?
- 百度文库 C语言实现面向对象编程
- waring: excess elements in scalar initializer
- 干货 | C语言实现面向对象编程(附代码)
- C语言 第八章 函数、指针与宏