65.9K
CodeProject 正在变化。 阅读更多。
Home

C 语言中的面向对象?

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (31投票s)

2016年8月11日

MIT

6分钟阅读

viewsIcon

43466

downloadIcon

1858

如何在 C(非 C++) 中实现和使用对象

引言

如果您曾经必须为微处理器或其他没有C ++编译器或必须使用C而不是C ++的设备编写代码,您可能会重视类提供的抽象和结构层。这个项目将向您展示这在C中也是可能的。例如,我一直想在C中使用字符串和列表,而无需在各处传播新方法。对象应该具有属性、方法,并且还应该支持多态性(根据类型,通过相同的名称调用不同的方法)。

事实证明,您可以通过在struct中定义指针并将其设置为构造函数中的特定方法来在C中做到这一点。您甚至可以传递任何对象,因为任何struct都可以作为void指针传递。

如果您现在只想尝试一下,请随时测试演示或下载源代码。

背景

C++于1979年由Bjarme Stroustrup开始。起初,C++(带类的C)开始于普通的C。正如您将看到的,在C中创建类状对象并使用它们是完全可能的。C++(至少在最初)是面向对象范式的一种便利更新。

C中的面向对象有一些缺点:您必须手动设置每个方法,privatepublic成员通过使用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;
}

构造函数返回指向类实例的指针。诸如AppendFree_Stringdestructor)之类的方法都在上面的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);

这非常有用,因为您不必为intfloatdouble等实现列表。有了它,您就不会失去类型安全。
警告:尽管这是类型安全的,但由预处理器引起的错误可能很难检测。直接使用具有指针的普通整数列表来实现泛型列表更容易。

这仅插入泛型列表的接口。对于实现,您必须使用另一个预处理器宏

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等等。)

关注点

创建这个项目非常有趣。如果您有任何问题或想分享有趣的内容,请发表评论。

© . All rights reserved.