C语言封装

C语言封装和C++封装

问题

上一次写C代码,还是上学的时候写51单片机,后来就一直写C++。对C语言不太了解。
比如《Effective C++》条款2中强调

尽量以const,enum,inline替换 #define

头文件保护(也可以使用#pragma once)、控制编译除外
但是C大佬通常都是”宏孩儿”

之前在哪本书或哪个地方的代码看到过封装,但是忘了。
因项目着急,而且受C内存问题困扰,再加上LVGLlv_obj_t的“误导”,想着又不能隐藏数据,就没有封装。
写博客的时候才想到,LVGL这么暴漏细节,会不会是为了方便其他控件继承?
对于Lv_obj_t结构体

  1. 可以通过lv_obj_set_x()修改对象的x值;
  2. 可以直接通过obj->coords.x1修改对象的x值
    对于第二种,无法避免运行中对用户对x1随意修改导致运行错误,也无法在设置值时检查非法的赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
///lv_obj.h
typedef struct _lv_obj_t {
struct _lv_obj_t* parent; /**< Pointer to the parent object*/
lv_ll_t child_ll; /**< Linked list to store the children objects*/
lv_area_t coords; /**< Coordinates of the object (x1, y1, x2, y2)*/
lv_event_cb_t event_cb; /**< Event callback function */
lv_signal_cb_t signal_cb; /**< Object type specific signal function*/
lv_design_cb_t design_cb; /**< Object type specific design function*/
void* ext_attr; /**< Object type specific extended data*/
lv_style_list_t style_list;
#if LV_USE_EXT_CLICK_AREA == LV_EXT_CLICK_AREA_TINY
uint8_t ext_click_pad_hor; /**< Extra click padding in horizontal direction */
uint8_t ext_click_pad_ver; /**< Extra click padding in vertical direction */
#elif LV_USE_EXT_CLICK_AREA == LV_EXT_CLICK_AREA_FULL
lv_area_t ext_click_pad; /**< Extra click padding area. */
#endif
lv_coord_t ext_draw_pad; /**< EXTend the size in every direction for drawing. */
/*Attributes and states*/
uint8_t click : 1; /**< 1: Can be pressed by an input device*/
uint8_t drag : 1; /**< 1: Enable the dragging*/
uint8_t drag_throw : 1; /**< 1: Enable throwing with drag*/
uint8_t drag_parent : 1; /**< 1: Parent will be dragged instead*/
uint8_t hidden : 1; /**< 1: Object is hidden*/
uint8_t top : 1; /**< 1: If the object or its children is clicked it goes to the foreground*/
uint8_t parent_event : 1; /**< 1: Send the object's events to the parent too. */
uint8_t adv_hittest : 1; /**< 1: Use advanced hit-testing (slower) */
uint8_t gesture_parent : 1; /**< 1: Parent will be gesture instead*/
uint8_t focus_parent : 1; /**< 1: Parent will be focused instead*/
lv_drag_dir_t drag_dir : 3; /**< Which directions the object can be dragged in */
lv_bidi_dir_t base_dir : 2; /**< Base direction of texts related to this object */
#if LV_USE_GROUP != 0
void* group_p;
#endif
uint8_t protect; /**< Automatically happening actions can be prevented.
'OR'ed values from `lv_protect_t`*/
lv_state_t state;
#if LV_USE_ID
lv_id_t id; /**< Unique object ID in screen*/
#endif
#if LV_USE_OBJ_REALIGN
lv_realign_t realign; /**< Information about the last call to ::lv_obj_align. */
#endif
#if LV_USE_USER_DATA
lv_obj_user_data_t user_data; /**< Custom user data for object. */
#endif
} lv_obj_t;


/**
* Set relative the position of an object (relative to the parent)
* @param obj pointer to an object
* @param x new distance from the left side of the parent
* @param y new distance from the top of the parent
*/
void lv_obj_set_pos(lv_obj_t* obj, lv_coord_t x, lv_coord_t y);
/**
* Set the x coordinate of a object
* @param obj pointer to an object
* @param x new distance from the left side from the parent
*/
void lv_obj_set_x(lv_obj_t* obj, lv_coord_t x);
/**
* Set the y coordinate of a object
* @param obj pointer to an object
* @param y new distance from the top of the parent
*/
void lv_obj_set_y(lv_obj_t* obj, lv_coord_t y);

C++封装

C++本来就是面向对象的,问题在于对于头文件中私有变量的隐藏。
模块之间通常是继承接口,对外暴漏接口比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class IPerson
{
public:
IPerson() {};
virtual ~IPerson()=0 {};

//提供给外面使用的接口一般采用纯虚函数
virtual void SetName(const string &strName)= 0;
virtual const string GetName()= 0;
virtual void Work()= 0;
}

class CTeacherpublic IPerson
{
public:
CTeacher(){};
virtual ~CTeacher();
void SetName(const string &strName)override;
const string GetName()strName)override;

private:
string name_;
}

模块内采用Impl模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// network.h
class NetworkV2 {
public:
int Send(const std::string str);
int Recv(std::string &str);

private:
struct Impl;
std::shared_ptr<Impl> impl;
};

// network.cpp
struct NetworkV2::Impl {
int sockfd;
char buf[1024];
};

int NetworkV2::Send(const std::string str) {
// TODO ...
// send(impl->sockfd, str.c_str(), str.size(), 0);
return str.size();
}
int NetworkV2::Recv(std::string &str) {
// TODO ...
// recv(impl->sockfd, impl->buf, 1024, 0);
return str.size();
}

C封装

由于本人懒加上谷歌不推荐使用前置声明,就很少使用前置声明。
google-style-1.3. 前置声明

查看C语言第三方容器库中Array的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//array.h
typedef struct array_s Array;

enum cc_stat array_new(Array** out);
enum cc_stat array_new_conf(ArrayConf const* const conf, Array** out);
void array_conf_init(ArrayConf* conf);
void array_destroy(Array* ar);
void array_destroy_cb(Array* ar, void (*cb)(void*));
enum cc_stat array_add(Array* ar, void* element);
enum cc_stat array_add_at(Array* ar, void* element, size_t index);
enum cc_stat array_replace_at(Array* ar, void* element, size_t index, void** out);
enum cc_stat array_swap_at(Array* ar, size_t index1, size_t index2);
enum cc_stat array_remove(Array* ar, void* element, void** out);
enum cc_stat array_remove_at(Array* ar, size_t index, void** out);
enum cc_stat array_remove_last(Array* ar, void** out);
void array_remove_all(Array* ar);
void array_remove_all_free(Array* ar);

//array.c
struct array_s {
size_t size;
size_t capacity;
float exp_factor;
void** buffer;
void* (*mem_alloc)(size_t size);
void* (*mem_calloc)(size_t blocks, size_t size);
void (*mem_free)(void* block);
};

Array类似于c++std::vector,是个动态数组,当数组满时,会重新申请buffer,其大小是当前大小的二倍,该值即为capacity,然后将当前数值拷贝到新的内存中。size为其实际所存数据的大小,如果运行中被人修改sizecapacitybuffer,会导致Array运行异常。
所以Array对其内部实现进行了隐藏,用户无法直接修改Array结构体中的任何值,必须通过其提供的接口,对其正常运行非常重要的sizecapacitybuffer等内部数据进行了保护。

c语言封装就是利用前置声明实现的。

原来之前在书上见过这段代码,只不过当时在写C++没在意

架构整洁之道


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!