当前位置: 首页 > 工具软件 > Mojoc > 使用案例 >

「游戏引擎Mojoc」(2)C代码风格

盖昀
2023-12-01

代码风格的问题,很微妙,也很有趣。因为它并不影响代码的运行和功能,但它连接着人的心灵和信仰。代码风格目的简单明确,就是增加代码的可阅读性,降低维护成本,减少心智负担。纠结的地方在于,每个人对“可阅读性”的理解和喜好不同。专注容易产生喜爱,喜爱容易滋生癖好,代码写的越多,越会形成个人风格习惯。而逻辑性,还容易在大脑中创造强迫症的倾向,所以代码风格会演变成信仰,难以动摇。

因为代码风格不喜欢,很可能就会否定,一门编程语言,一个框架,一个库,一个功能等等。

但其实,代码风格是大脑虚幻确定感的又一力证。因为,无论什么风格,喜欢的还是不喜欢的,被迫写上一段时间,写上成千上万行,不喜欢也喜欢了。大脑会潜意识的为内心的感受,寻找各种各样的解释和理由,而感受是会被环境数据所改写的,到时候又会有新的解读和视角。

Mojoc的代码风格,完全不同于C和C++的常用习惯 ,而是最大限度的减少下划线和宏的使用,没有好坏对错,只是个人喜好。

全局命名

使用驼峰命名规则,BigCamelCased 这是大驼峰, smallCamelCased 这是小驼峰。

变量命名

  • 一般变量使用小驼峰。
int               keyLength;
int               valueTypeSize;
SkeletonBone*     bone;
SkeletonBoneData* boneData;
Drawable*         drawable;
  • const 变量使用小驼峰。(因为还是可以修改的所以和变量一样)
static const char*  ids[AudioId_Length];
static const char*  saveDataFileName = "MojocSaveDataFile";
static const int    bezierSize       = (BEZIER_SEGMENTS + 1) * 2 - 1;
static const float  subDivPre        = subDivStep3           / pre5;
  • bool 变量使用“is”前缀,整体小驼峰。
SLboolean isLoopEnabled;
bool      isFound;
bool      isRemoved;
particle->isActive
  • 单例变量使用“A”前缀,整体大驼峰。(表示“一个”,从NDK学来的)
extern struct AComponent   AComponent  [1];
extern struct ADrawable    ADrawable   [1];
extern struct AParticle    AParticle   [1];
extern struct AApplication AApplication[1];
  • 函数参数带出返回值的变量使用“out”前缀,整体小驼峰。
void (*OnSaveData)(void**   outSaveData,   int*         outLength);
void (*Inverse)   (Matrix4* matrix4,       Matrix4*     outInverse);
void Init         (int      valueTypeSize, ArrayIntMap* outArrayIntMap);
  • 函数指针变量使用大驼峰。(因为会像函数一样带括号调用)
typedef struct
{
    void (*OnPause)  ();
    void (*OnResume) ();
    void (*OnDestroy)();
}
ApplicationCallbacks;


typedef float (*TweenActionValueOnGet)(void* target);
typedef void  (*TweenActionValueOnSet)(void* target, float value);


typedef struct
{
    TweenActionValueOnGet OnGet;
    TweenActionValueOnSet OnSet;
}
TweenActionValueGetSet;

缩写命名

  • 缩写单词要么全部大写,要么全部小写。
typedef struct
{
}
RGB;


RGB rgb;
RGB myRGB;
RGB rgbDatas[10];
RGB myRGBData;


void SetRGB    (RGB* rgb);
void RGBSet    (RGB* rgb);
void SetRGBData(RGB* rgb);

Goto 标签命名

  • 标签使用大驼峰,标签地址使用一般变量命名。
goto ParseArrayEnd;
goto ParseObjectEnd;
goto UseVBO;
goto UseVAO;
goto *coroutine->step;

枚举命名

  • 枚举类型使用大驼峰,枚举变量大驼峰并用下划线分割前缀。(前缀避免冲突)
enum
{
    HeroState_Stand,
    HeroState_DieOver,
};


enum
{
    CollisionGroup_HeroBody   = 1,
    CollisionGroup_HeroAttack = 1 << 1,
};


typedef enum
{
    FontTextAlignment_HorizontalLeft,
    FontTextAlignment_HorizontalRight,
    FontTextAlignment_VerticalTop,
    FontTextAlignment_VerticalBottom,
}
FontTextAlignment;


typedef enum
{
    InputTouchType_Up     = 1,
    InputTouchType_Down   = 2,
    InputTouchType_Move   = 3,
    InputTouchType_Cancel = 4,
}
InputTouchType;

函数命名

  • 全局函数包括内联的,使用大驼峰,并用下划线分割前缀。(前缀避免冲突)
extern void         Application_Main        ();
static inline void  AApplication_AppendChild(Component* child);
static inline float AGLTool_ToGLWidth       (float      screenWidth);
static inline float AMath_Random            ();
  • 局部函数包括内联的,使用大驼峰。
static void         LoadingRun    (Coroutine* coroutine);
static inline float GetWorldScaleY(Drawable*  drawable);
  • 函数类型定义,使用大驼峰,必须有前缀。(前缀避免冲突)。
typedef float (*TweenActionValueOnGet)(void*      target);
typedef void  (*TweenActionValueOnSet)(void*      target, float value);
typedef void  (*CoroutineRun)         (Coroutine* coroutine);
  • 函数返回bool表示操作是否成功的,使用“Try”前缀。
void* (*TryPut)   (ArrayIntMap* arrayIntMap, intptr_t key, void* valuePtr);
bool  (*TryRemove)(ArrayIntMap* arrayIntMap, intptr_t key);
  • 函数返回bool表示结果的,使用“Is”,“Test”,“Check”前缀。
bool (*IsContains)         (ArrayIntSet*  arrayIntSet, intptr_t element);
bool (*TestPolygonPoint)   (Array(float)* vertexArr,   float x, float y);
bool ADrawable_CheckVisible(Drawable*     drawable);
  • “Release” 命名代表释放结构体成员所持有的内存空间。
  • “Create” 前缀表示在堆上分配内存。
ArrayList* (*Create)            (int elementTypeSize);
ArrayList* (*CreateWithSize)    (int elementTypeSize, int size);
ArrayList* (*CreateWithCapacity)(int elementTypeSize, int capacity);
  • “Init” 前缀表示初始化已有的内存空间。
void (*Init)            (int elementTypeSize, ArrayList*               outArrayList);
void (*InitWithSize)    (int elementTypeSize, int size,     ArrayList* outArrayList);
void (*InitWithCapacity)(int elementTypeSize, int capacity, ArrayList* outArrayList);

结构体命名

结构体和别名,包括联合及别名,使用大驼峰

宏定义命名

  • 头文件的定义,单词全大写下划线分割,“H”后缀。
#ifndef STYLE_GUIDE_H
#define STYLE_GUIDE_H
  • 无参数宏,单词大写下划线分割。
#define MATH_PI  3.141592653589793
#define MATH_2PI 6.283185307179586
#define MATH_PI2 1.570796326794897
  • 有参数宏,大驼峰下划线分割前缀。(前缀避免冲突)
// not aligned brackets, because macro rules limit
#define AMath_Min(x, y) 
#define AStruct_GetParent2(memberPtr, structType)
#define ACoroutine_YieldFrames(waitFrames)

其它命名

文件名,资源名,文件夹名,通通使用大驼峰。

代码格式

  • 使用空格缩进,不使用tab缩进。
  • 缩进使用4个空格。
  • 指针类型的星号,靠近变量类型名的一边。
int*  p1;
int** p2 = &p1;
void* Function(char* str);
  • 左右花括号“{}”垂直对齐。
{
  // vertical alignment
}
  • 参数括号“()” 如果换行了,就垂直对齐。
AMath_Max
(
    animationData->duration,
    AArray_Get
    (
        deformTimeline->frameArr,
        deformTimeline->frameArr->length - 1,
        float
    )
);

static void ReadAnimationDeform
(
    SkeletonData*          skeletonData,
    JsonObject*            jsonDeform,
    SkeletonAnimationData* animationData,
    ArrayList*             skeletonTimelineArr
)
{
   // ...
}
  • if, while, for, switch, 必须有花括号“{}”,与“()”空一格距离。
  • 操作符两边至少空一格。
vertexX + (y - vertexY) / (preY - vertexY) * (vertexData[preIndex] - vertexX)
  • case,break依次缩进。
switch (111)
{
    case 0:
        break;

    case 1:
    {
        break;
    }
}
  • 函数上下空两行间距。
static int a = 100;


static void Function1()
{
}


static inline void Function2()
{
}


struct A
{
}
  • 不同的内容之间空两行间距。
#include "AAA.h"
#include "BBB.h"


typedef float (*Function1)(void* target);
typedef float (*Function2)();


struct A
{
}


struct B
{
}


extern struct B B[1];
  • 使用分割线,区分不同的逻辑的内容。
// this is split line
//--------------------


void Function1();


//--------------------


void Function2();


void Function3()
{
    int a;

    //---------------- 

    int b;
}
  • goto的标签缩进与当前行保持一致。
static void Function()
{ 
     goto Label:

     Label1:
     int a;

     Label2:
     int b;

     Label3:
     int c;
}
  • 条件编译的缩进与当前行保持一致。
typedef struct
{
     Sprite       sprite[1];
     PhysicsBody* body;
     Enemy*       enemy;
     ArrowHitType hitType;

     #ifdef APP_DEBUG
     Drawable     debugDrawable[1];
     #endif
}
Arrow;

void Function()
{
    int a;

    #ifdef APP_DEBUG
    int b;
    #endif
}


#ifdef APP_DEBUG
Drawable debugDrawable[1];
#endif
  • 所有代码尽量保持,垂直对齐,参看上面所有的例子。(ID Software的规则)

代码注释

  • 函数外部使用块注释。
/**
 * Comment struct
 */  
struct A
{   
    /**
     * Comment property
     */ 
    int a;

    /**
     * Comment function
     */
    void (*Function)();
}
  • 函数内体使用行注释 “//”。
  • 注释代码块或多行代码,如下格式:
/*
--------------------------------------------
  This is means comment few blocks of code
--------------------------------------------
*/

其它规则

  • 参数宏,只有在inline无法满足的情况下使用。比如,带有默认参数的函数别名,宏特有的功能体现,泛型参数,变长参数。

  • 不使用0和1作为bool值判断,非bool值使用明确的表达式判断。const变量不能被修改,不可在头文件定义const变量,在c文件修改const变量的值。


「习惯就好」

 类似资料: