当在源文件中使用typedef结构并且外部系统需要通过接口连接到此类时,处理输入的正确方法是什么?
一个例子是子系统内的源文件是否
typedef struct
{
double latitude;
double longitude;
}
GPSLocation;
并且外部类想要使用以下函数
void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination)
让我说我使用接口抽象它并具有外部类调用的函数。该函数是否应该采用类型为GPSLocation的参数(从而强制外部系统为#include
存在结构的源文件,现在不是外部的)或者更好地保留子系统内使用的所有typedef结构,从而具有如下界面功能?
void setupGPSSystem(char* navigationSystem, double startLat, double startLong, double destinationLat, double destinationLong)
答案 0 :(得分:2)
您可以在接口的公共标头中声明类型,并在实现源文件中隐藏结构定义。所以,你可能有:
gps_system.h:
typedef struct GPSLocation GPSLocation;
void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination);
gps_system.c:
struct GPSLocation
{
double latitude;
double longitude;
}
这样,您可以充分利用这两个方面:您的用户可以在界面中使用有意义的类型,并且您的实现是私有的。
答案 1 :(得分:2)
在设计模块时,您需要确定此模块的用户是否需要直接访问结构中的字段,这将决定您最终定义结构的位置。我将描述几种不同的场景:
<强> 1。 (模块的)用户将直接操作结构中的GPS数据。您别无选择,只能在标头(.h)文件中定义结构作为API的一部分。优点是这种技术很简单,并且使用户能够根据需要静态或动态地为您的结构分配内存(因为结构的构成对于您的其他模块是已知的)。这个缺点是这种技术不会隐藏你的数据;它可能被用户有意或无意地破坏。
<强> 2。用户不关心“GPS位置”是什么,它将始终使用模块的功能来操作您的数据结构。在这种情况下,您的结构可能是不透明的。您在头文件(.h)中声明它并在源(.c)文件中定义它(由Graham的答案所涵盖)。优点是您可以隐藏所有数据,这意味着用户不能轻易地破坏它或(在专有库的情况下)了解它是如何实现的。缺点是你的模块必须管理你的opaque类型的分配(和释放)(参见下面的第3点,以扩展这个想法)。
<强> gps_system.h 强>
typedef struct _GPSLocation GPSLocation;
void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination);
<强> gps_system.c 强>
struct _GPSLocation
{
double latitude;
double longitude;
}
第3。您希望为用户提供对某些字段的只读访问权限,但隐藏其他字段。这可以是隐藏实现详细信息并防止用户破坏数据同时使其易于访问的有用技巧信息。缺点是你仍然需要你的模块来管理opaque类型的分配。
<强> gps_system.h 强>
typedef struct
{
double latitude;
double longitude;
}
GPSLocation;
/* Create a new GPS object. */
void gps_new(GPSLocation **gps);
/* Set the GPS object's current location. */
void gps_set(GPSLocation *gps, double latitude, double longitude);
/* Calculate the distance from a GPS coordinate to a different location. */
void gps_distance(GPSLocation *gps, double latitude, double longitude);
/* Free all memory allocated to a gps object. */
void gps_delete(GPSLocation *gps);
<强> gps_system.c 强>
struct GPSLocation_private
{
GPSLocation gps_public;
int field_0;
int field_1;
};
/** Convert from the public version of the gps object object to the private. */
#define gps_get_private(gps_public) ((struct GPSLocation_private *)(((char *)(gps_public)) - offsetof(struct GPSLocation_private, gps_public)))
void gps_new(GPSLocation **gps)
{
struct GPSLocation_private *priv;
priv = malloc(sizeof(struct GPSLocation_private));
if (priv)
{
priv->field_0 = 1234;
priv->field_1 = 4567;
priv->gps_public.latitude = 1111;
priv->gps_public.longitude = 2222;
gps = &priv->gps_public;
}
else
{
*gps = NULL;
}
}
void gps_set(GPSLocation *gps, double latitude, double longitude)
{
struct GPSLocation_private *priv;
priv = gps_get_private(gps);
/* Do stuff with 'priv'. */
}
void gps_delete(GPSLocation *gps)
{
struct GPSLocation_private *priv;
priv = gps_get_private(gps);
free(priv);
}
答案 2 :(得分:1)
您的子系统可能(应该)有一个头文件,用于定义其他人用来访问您系统的函数原型(API)。在头文件中定义结构也没有错,完全使用typedef。实际上它很常见。这样,提供你的函数原型遵循该原型,你没问题。所以这个:
#ifndef MYSUBSYSTEM_H
#define MYSUBSYSTEM_H
typedef struct
{
double latitude;
double longitude;
}
GPSLocation;
void setupGPSSystem(char* navigationSystem,
GPSLocation *start, GPSLocation *destination);
#endif
会成为一个好头。功能中间的换行符使其适合SO。 #include
在链接到您的代码中,并且您已设置。
答案 3 :(得分:0)
GPSLocation
类型属于API,应位于头文件(以.h
结尾的文件)中,您的API用户将#include
。
答案 4 :(得分:0)
你必须让用户#include
成为你的接口函数原型的标题,所以你也可以将结构定义粘贴在那个标题中。
答案 5 :(得分:0)
在关于C语言结构的问题中提及“类”可能不是一个好主意:-)只是为了避免与C ++有任何混淆。
你问“函数是否应采用GPSLocation类型的参数(因此强制外部系统#include包含结构的源文件,......”
您编写的函数不采用GPSLocation类型的参数;它需要指向的参数的参数(即, 的地址)GPSLocation结构,这是一个非常不同和奇妙的事情。
并且,非常正确和正确地说,除了定义GPSLocation的头文件(.h)之外,你不会强迫任何人#include任何东西。那是因为在你的函数入口中(据我所知)你期望指针到 instantiations 错误,这些结构的副本(开始和结束),因为它们存在于调用程序中。您将通过其地址/指针获取每个传递引用结构的成员。
这是 到目前为止 在C中传递结构的最佳方法 - 通过指向调用程序的结构副本的指针。
忘掉你给的第二个选择。永远!