C 语言中的面向对象?






4.88/5 (31投票s)
如何在 C(非 C++)
引言
如果您曾经必须为微处理器或其他没有C ++编译器或必须使用C而不是C ++的设备编写代码,您可能会重视类提供的抽象和结构层。这个项目将向您展示这在C中也是可能的。例如,我一直想在C中使用字符串和列表,而无需在各处传播新方法。对象应该具有属性、方法,并且还应该支持多态性(根据类型,通过相同的名称调用不同的方法)。
事实证明,您可以通过在struct
中定义指针并将其设置为构造函数中的特定方法来在C中做到这一点。您甚至可以传递任何对象,因为任何struct
都可以作为void
指针传递。
如果您现在只想尝试一下,请随时测试演示或下载源代码。
背景
C++于1979年由Bjarme Stroustrup开始。起初,C++(带类的C)开始于普通的C。正如您将看到的,在C中创建类状对象并使用它们是完全可能的。C++(至少在最初)是面向对象范式的一种便利更新。
C中的面向对象有一些缺点:您必须手动设置每个方法,private
和public
成员通过使用c和h文件实现。
您还将看到,在C中可以实现多态性和泛型模板式编程。一旦您在C中创建了一个类,您就可以在任何机器上使用它。
C中的字符串
如果我们要创建一个String
类,我们就必须创建一个包含以下内容的类:
C中不存在class
关键字,因此我们必须定义一个string
类型的结构
方法
构造函数(New_String)
析构函数(Free)
Append
打印
属性
长度
Content
您首先在头文件中定义一个“interface
”。
头文件中的定义是从代码角度可以看到的内容。
(公共成员)(String.h)
typedef struct String
{
char* Content; //Properties
int Length;
void(*Append)(void *self,char* text); //Method
void(*Print)(void *self);
void(*PrintLine)(void *self);
void(*Free)(void *self); //Destructor
} String;
String *New_String(char* text); //Constructor
现在您可以看到C中不存在“this
”指针的概念。您必须将实例传递给每个方法。将其视为每个方法都是static
。
现在我们必须定义构造函数
从代码角度来看,无法看到.c文件。
(私有成员)(String.c)
String *New_String(char* text)
{
struct String* k = malloc(sizeof(String));
k->Content = _strdup(text);
k->Length = strlen(k->Content);
k->Append = &Append;
k->Free = &Free_String;
k->Print = &Print;
k->PrintLine = &PrintLine;
return k;
}
构造函数返回指向类实例的指针。诸如Append
、Free_String
(destructor
)之类的方法都在上面的c文件中定义。您可以看到,对于string
的每个实例,append指针都设置为相同的方法。定义构造函数并清理每次使用的malloc
非常重要。
(String.c)
为了方便复制粘贴
#include "String.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void Free_String(String *Instance)
{
free(Instance->Content);
free(Instance);
}
void Append(String *Instance,char* text)
{
size_t len1 = Instance->Length;
size_t len2 = strlen(text);
char *result = malloc(len1 + len2 + 1);//+1 for the zero-terminator
memcpy(result, Instance->Content, len1);
memcpy(result + len1, text, len2 + 1);//+1 to copy the null-terminator
free(Instance->Content);
Instance->Content = result;
Instance->Length = strlen(Instance->Content);
}
void Print(String *Instance)
{
printf("%s", Instance->Content);
}
void PrintLine(String *Instance)
{
printf("%s\n", Instance->Content);
}
String *New_String(char* text)
{
struct String* k = malloc(sizeof(String));
k->Content = _strdup(text);
k->Length = strlen(k->Content);
k->Append = &Append;
k->Free = &Free_String;
k->Print = &Print;
k->PrintLine = &PrintLine;
return k;
}
请注意,Free
也会自行清理。不会留下任何泄漏。
请注意,每次追加后,Length
属性都会更新。通过在.h文件中定义一个方法,并在.c文件中编写代码,可以轻松地扩展此类。不要忘记将.h文件的函数指针“连接”到.c文件的函数指针。
如何在main
中使用
(main.c)
#include "String.h"
#include "List.h"
#include <stdio.h>
#include "Main.h"
int main(int argc, char **argv)
{
String* r = New_String("Hello");
r->Append(r, " World");
r->PrintLine(&r);
r->Free(r);
getch();
}
//append user string in main
char str[100];
gets_s(str, 100);
r->Append(r, &str);
不要在调用Free
后调用任何方法。这肯定会导致程序失败。此程序的输出为“Hello World
”。
C中的列表
现在我们想要一个C中的快速列表(它在后台实现为数组)。
列表应包含
方法
构造函数(New_List)
析构函数(Free)
Add
Get
RemoveAt
属性
长度
AsArray
(公共成员)(List.h)
实际上,AsArrayLength
应该是private
,但我还没有找到隐藏成员的方法。Count
属性告诉您List
中有多少数据。
typedef struct List
{
int Count; //Properties
void(*Add)(void *self, int value); //Method
int(*Get)(void *self,int index);
void(*RemoveAt)(void *self,int index);
void(*Clear)(void *self);
void(*Free)(void *self); //Destructor
int* AsArray;
int AsArrayLength;
} List;
List *New_List();
(私有成员)(List.c)
您可以看到类实现与String
相似。您在构造函数中设置所有相应的方法。此列表是类型安全的,因为您只能插入整数。您也可以插入任何数据的指针(仅限32位)。起初,列表有16个元素的空间。如果您添加了16个以上的元素,内部数组将调整为容纳64个元素,依此类推。实现与.NET Framework中的List<T>
几乎相同。
Count
属性始终具有正确的值,并且由于它只是一个数组,因此列表在内部非常高效。
#include "List.h";
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int* ResizeArray(int* Old, int oldsize, int newsize)
{
if (oldsize < newsize) //grow
{
int* newpos = malloc(newsize*sizeof(int));
memcpy(newpos, Old, oldsize*sizeof(int));
free(Old);
return newpos;
}
else //shrink
{
realloc(Old, newsize*sizeof(int));
return Old;
}
}
void AddMethod(List *self, int object)
{
if (self->Count + 1 > self->AsArrayLength) //needs to resize
{
self->AsArray=ResizeArray(self->AsArray,self->Count,self->Count*2);
self->AsArrayLength = self->Count * 2;
}
int index = self->Count;
self->AsArray[index] = object;
self->Count++;
}
void* Get(List *self, int index)
{
int nr = self->AsArray[index];
return nr;
}
void Removeat(List *self, int index)
{
memmove(self->AsArray + index, self->AsArray + index + 1,
(self->Count - index - 1)*sizeof(int)); //move one to the left
self->Count--;
if (self->AsArrayLength / 2 > self->Count && self->Count>16) //needs to shrink
{
self->AsArray = ResizeArray(self->AsArray, self->AsArrayLength, self->AsArrayLength / 2);
self->AsArrayLength = self->AsArrayLength / 2;
}
return;
}
void ClearMethod(List *self)
{
free(self->AsArray);
self->Count = 0;
self->AsArray = (int*)malloc(sizeof(int) * 16);
self->AsArrayLength = 16;
}
void FreeList(List *self)
{
free(self->AsArray);
free(self);
}
List *New_List()
{
struct List* inst = malloc(sizeof(List));
inst->Count = 0;
inst->Add = &AddMethod;
inst->Get = &Get;
inst->RemoveAt = &Removeat;
inst->Clear = &ClearMethod;
inst->Free = &FreeList;
inst->AsArray = (int*)malloc(sizeof(int) * 16);
inst->AsArrayLength = 16;
return inst;
}
如何在代码中使用
(Main.c)
此程序创建一个包含100000个元素的列表。然后它删除第三个元素。通过使用memshift
,数组的移位速度尽可能快。链表会更快,但此列表不会通过保存指向下一个元素的指针来浪费内存空间。
int main(int argc, char **argv)
{
List* List = New_List();
for (int i = 0; i < 1000000; i++)
{
List->Add(List, i);
}
int Size = List->Count;
int ThirdElement = List->Get(List, 3); //really the fourth as 0 is also in there
printf("Size of list is %d Third item is %d \n", Size, ThirdElement);
List->RemoveAt(List, 3);
int NowThird = List->Get(List, 3);
printf("After remove at 3 the third element is now %d", NowThird);
getch();
}
使用后不要忘记调用List->Free(List)
。内存泄漏可能非常难以查找。
多态
可以通过调用替代构造函数来实现多态性。让我们用string
试试
string.h
String *New_String(char* text); //Constructor
String *New_Specialstring(char* text); //Constructor 2
对于构造函数,我们所要做的就是将方法(print
)连接到另一个方法。从使用角度来看,两种string
类型看起来都一样,因为所有方法都具有相同的名称。在内部,会调用不同的方法。string.h只是定义了一个接口,我们可以用它在内部做任何事情。
string.c
String* New_String(char* text)
{
struct String* k = malloc(sizeof(String));
//...
k->Print = &Print;
k->PrintLine = &PrintLine;
return k;
}
void Print(String *Instance) { printf("%s", Instance->Content); }
void PrintAlt(String *Instance){printf("%s", "I AM A SPECIAL STRING");}
String* New_Specialstring(char* text)
{
k->Print = &PrintAlt;
}
您可以通过调用来创建两种类型
String* a = New_String("LOL");
String* b = New_Specialstring("LOL");
a->Print(a);
b->Print(b);
//Output: LOL I AM A SPECIAL STRING
C中的模板(泛型)
这有点棘手。您可以看到它有一个很好的预处理器。#define
会在任何使用它的地方插入您的代码片段。如果我们想要一个泛型列表,我们所要做的就是#define
一个泛型struct
。##T
关键字使我们能够获得传递给定义的string
。您还可以通过在行末添加\
来创建多行#defines
。
List.h
#define GenerateList(T) ListStruct(T) \
List_##T *New_List_Generic(int size);
#define ListStruct(T) typedef struct List_##T\
{\
int Count;\
void(*Add)(void *self, T value);\
T(*Get)(void *self, int index);\
void(*RemoveAt)(void *self, int index);\
void(*Clear)(void *self);\
void(*Free)(void *self);\
T* AsArray;\
int AsArrayLength;\
} List_##T;
//If we want a list of float we call:
GenerateList(float)
GenerateList(float)
将变成
typedef struct List_float { int Count; void(*Add)(void *self, float value);
float(*Get)(void *self, int index); void(*RemoveAt)(void *self, int index);
void(*Clear)(void *self); void(*Free)(void *self); float* AsArray; int AsArrayLength; }
List_float; List_float *New_List_Generic(int size);
这非常有用,因为您不必为int
、float
、double
等实现列表。有了它,您就不会失去类型安全。
警告:尽管这是类型安全的,但由预处理器引起的错误可能很难检测。直接使用具有指针的普通整数列表来实现泛型列表更容易。
这仅插入泛型列表的接口。对于实现,您必须使用另一个预处理器宏
List.c
您需要定义一个这样的宏
New_List_Generic() List_##T *New_List_##T()
{
struct List_##T* inst = malloc(sizeof(List_##T));
inst->Count = 0;
inst->Add = &AddMethod;
inst->Get = &Get;
inst->RemoveAt = &Removeat;
inst->Clear = &ClearMethod;
inst->Free = &FreeList;
inst->AsArray = (T*)malloc(sizeof(T) * 16);
inst->AsArrayLength = 16;
return inst;
}
如您所见,这不是完整的。现在,您需要为构造函数中的每个方法定义一个泛型方法。(Add
应该接受float
等等。)
关注点
创建这个项目非常有趣。如果您有任何问题或想分享有趣的内容,请发表评论。