我有Java编程背景(在我的计算机科学课程中学过)和一学期的C++。我刚刚完成了一个OpenCV项目,该项目是用纯C编写的,所以我有些晚才问这个问题。
纯C的设计流程和编码标准是什么?
我熟悉面向对象编程、设计和最佳实践。但对于非面向对象语言,比如C,我有点不知所措。每个变量和函数似乎都是全局的,这让我感觉很混乱。
我有Java编程背景(在我的计算机科学课程中学过)和一学期的C++。我刚刚完成了一个OpenCV项目,该项目是用纯C编写的,所以我有些晚才问这个问题。
纯C的设计流程和编码标准是什么?
我熟悉面向对象编程、设计和最佳实践。但对于非面向对象语言,比如C,我有点不知所措。每个变量和函数似乎都是全局的,这让我感觉很混乱。
你可能会对我不久前提出的一个类似问题的答案感兴趣。此外,如果你对C语言风格指南感兴趣,可以看看这个页面,因为它是C(和C ++)风格指南的存储库。如果你想要一些欢乐时光,请看看NASA C语言风格指南。特别是那个巨大的注释...你会知道我在说哪个。请不要写这样的注释。
我个人推荐Indian Hill C Style Guide并进行一些修改。此外,如果您在设计C语言的大型程序时遇到困难,可以考虑购买C Interfaces and Implementations这本书。
我认为在StackOverflow上获得的任何答案都无法教授你如何设计和编写良好结构化的C程序。你需要阅读一本好书,而最明显的选择是由Kernighan和Ritchie所写的The C Programming Language。
我在C语言方面没有专业经验(只有C++),所以请不要太认真地对待我的建议、技巧和提示,因为它们是“对象式导向”的。
模拟基本的对象式特性可以很容易地完成:
在头文件中,前向声明你的类型,typedef它,并声明“方法”。例如:
/* MyString.h */
#include <string.h>
/* Forward declaration */
struct StructMyString ;
/* Typedef of forward-declaration (note: Not possible in C++) */
typedef struct StructMyString MyString ;
MyString * MyString_new() ;
MyString * MyString_create(const char * p_pString) ;
void MyString_delete(MyString * p_pThis) ;
size_t MyString_length(const MyString * p_pThis) ;
MyString * MyString_copy(MyString * p_pThis, const MyString * p_pSource) ;
MyString * MyString_concat(MyString * p_pThis, const MyString * p_pSource) ;
const char * MyString_get_c_string(const MyString * p_pThis) ;
MyString * MyString_copy_c_string(MyString * p_pThis, const char * p_pSource) ;
MyString * MyString_concat_c_string(MyString * p_pThis, const char * p_pSource) ;
你会看到每个函数都有前缀。我选择了“struct”的名称,以确保不会与其他代码发生冲突。
你也会看到,我使用“p_pThis”来与面向对象的思想保持一致。
在源文件中,定义你的类型,并定义函数:
/* MyString.c */
#include "MyString.h"
#include <string.h>
#include <stdlib.h>
struct StructMyString
{
char * m_pString ;
size_t m_iSize ;
} ;
MyString * MyString_new()
{
MyString * pMyString = malloc(sizeof(MyString)) ;
pMyString->m_iSize = 0 ;
pMyString->m_pString = malloc((pMyString->m_iSize + 1) * sizeof(char)) ;
pMyString->m_pString[0] = 0 ;
return pMyString ;
}
/* etc. */
static void doSomethingPrivate()
{
/* etc. */
}
static int g_iMyPrivateCounter = 0 ;
如果你想要继承,那么你几乎是处境艰难的。如果你认为C语言中的所有内容都是全局的,包括变量,那么在尝试思考如何模拟继承之前,你应该获得更多的C语言经验。
例如,多个返回值是有风险的。例如:
void doSomething(int i)
{
void * p = malloc(25) ;
if(i > 0)
{
/* this will leak memory ! */
return ;
}
free(p) ;
}
这包括“静态”变量(不是静态函数)。
全局非const变量几乎总是一个坏主意(例如,查看C API strtok以获取糟糕函数的示例),如果生成多线程安全代码,则处理它们会很麻烦。
为您的函数和定义选择一个“命名空间”。这可以是:
#define GROOVY_LIB_x_MY_CONST_INT 42
void GroovyLib_dosomething() ;
#define MAX(a, b) (a > b) ? (a) : (b)
void doSomething()
{
int i = 0, j = 1, k ;
k = MAX(i, j) ; /* now, k == 1, i == 0 and j == 1 */
k = MAX(i, j++) ; /* now, k == 2, i == 0 and j == 3, NOT 2, and NOT 1 !!! */
}
避免声明未初始化的变量:
int i = 42 ; /* now i = 42 */
int j ; /* now j can have any value */
double k ; /* now f can have any value, including invalid ones ! */
未初始化的变量是痛苦错误的原因。
在K&R中描述的C API函数列表非常小。您可以在20分钟内阅读整个列表。您必须知道这些函数。
重写C API。例如,尝试编写自己版本的string.h函数,以查看如何完成。
和其他优秀的C程序员一起工作。与他们进行代码审查。不仅让他们查看你的代码,也要查看他们的代码。
你可能希望仔细研究Linux内核的源代码......顺便说一句,这不是最容易入手的代码,但由于你有编程背景,它可能会有所帮助......作为一个面向对象的程序员,你可能会发现在C语言中处理错误是一个艰巨的任务。也许这可以帮助你——> http://www.freetype.org/david/reliable-c.html
你可以在纯C中进行面向对象设计。一种简单的方法是将模块视为一个带有公共方法的class
,这些公共方法作为普通函数,需要将this
参数作为显式的第一个参数。
如果class
名称是函数名称的前缀,并且所有私有函数和类数据都声明为static
,则会有所帮助。您可以使用malloc()
构建构造函数以获取内存,并明确初始化数据字段。
具有完全私有数据成员的对象的构造函数可以公开不透明指针(甚至可以类型为void *
,或者作为指向不完整类型的指针,如果需要类型安全性)。如果您只想拥有公共数据成员,则指向公开定义的struct
的指针效果很好。
许多库都遵循此模式。初始化函数返回必须传回所有库方法的cookie,而一个方法则充当析构函数。
当然,在纯C中还有其他良好的设计方式,但如果面向对象适用于您,您不必完全放弃它。
您可以将文件范围的变量和函数的可见性限制在它们各自的源文件中(尽管这并不能阻止您在对象周围传递指针)。
例如:
/** foo.c */
static void foo_helper() {...} /* foo_helper cannot be called by name
outside of foo.c */
static int local_state; /* local state is visible at file scope,
but is not exported to the linker */