天天看点

C++开发者对C语言开发的一些建议

作为一名C/C++混合开发人员,在C语言和C++的交替开发时, 常遇到一些很不有好的C语言接口,这样给C++的开发带来了不少的麻烦,个人认为, 有必要在给出一些合理性建议来规避一些不必要的麻烦

关于头文件

我们都知道, C++是兼容C语言的, 所以我们在使用C++的时候, 可以直接包含C语言的头文件来调用C语言的函数, 但是一般C语言初学者或者C语言编程习惯不是很好的人写的C语言接口头文件, 即使包含了也会编译报错, 错误原因就是找不到符号链接。这不奇怪,因为C++的编译方式与C语言的编译方式存在很大差异, 所以如果没有在头文件里面处理好两者的关系,就会有这个问题出现。 解决方式是, 在C语言的头文件里加上

#ifdef _cplusplus
extern "C" {
#endif

/// 你的C语言函数声明

#ifdef _cplusplus
}
#endif
           

比如以下代码:

#ifndef _XXX_YYY_H_
#define _XXX_YYY_H_

#ifdef _cplusplus
extern "C" {
#endif

void doSomethingInC();

#ifdef _cplusplus
}
#endif
#endif
           

关于字符串

如果我们使用C风格的字符串, 有以下几点需要注意

1. 如果使用字符串做参数,且函数一定不会改变该字符串的任何值时, 请务必使用const char *类型,这样做的好处是, 你可以使用字符串字面值来调用这个函数, 比如

void doSomeWork(char *workName); //不好的声明方式
void doSomeWorkBetter(const char *workName); //推荐的声明方式

int main()
{
    doSomeWork("吃饭"); //编译会报错
    doSomeWorkBetter("写代码"); //可以正常调用
    return ;
}
           

原则: 只有在字符串参数作为输出或者需要将该字符串交由某个对象来管理(比如初始化某个有char *成员的结构体)时, 才使用非const, 比如我们常用的string.h中的strxxx系列的函数就都遵循了这个原则的前一条

关于回调函数

  1. 尽量使用typedef 将某个类型的函数进行类型重定义, 然后在一些类似注册回调的函数里面, 将回调函数类型直接替换成重定义后的类型, 这样做的好处是, 在别人注册回调是,可以很容易的根据typedef 的内容来定义自己的回调函数,尤其是多重typdef嵌套的情况下尤其效果明显
  2. 回调函数请务必带有一个void *类型的参数, 注册回调的接口除了这个回调函数, 也请务必增加一个void *类型参数。这样要求是因为, void * 指针是个相当于任何类型的参数,有了这个参数, 调用者在回调中能做的事也有了更多的灵活性, 因为注册的时候可以通过void *类型的那个参数传入任何调用者在回调中使用的某个值,这样的话, 不仅在C语言中有了更好的灵活性, 也能更好的向上兼容到C++的面向对象(这样在C++中回调可以通过很多方式设置成成员函数,而不仅仅是静态函数和普通函数), 举个例子:
/**
* 某个C的头文件
* 
*/
typedef void (*CallbackA)(int);
typedef void (*CallbackB)(void *, int);
void registerCallbackA(CallbackA cba);
void registerCallbackB(CallbackB cbb, void *pData);

/**
* 对应的.c文件
*/

static CallbackA *callbackA = NULL;

void *callbackBData = NULL;
static CallbackB *callbackB = NULL;

void registerCallbackA(CallbackA cba)
{
    callbackA = cba;
}

void registerCallbackB(CallbackB cbb, void *data)
{
    callbackB = cbb;
    callbackBData = data;
}

void invokeCallbackA(int value)
{
    if(callbackA)
        callbackA(value);
}

void invokeCallbackB(int value)
{
    if(callbackB)
    {
        callbackB(callbackBData, value);
    }
}
           

那么我们在用的时候, registerCallbackA的灵活性明显要比registerCallbackB的灵活性差很多, 使用registerCallbackA的时候, 我们只能使用普通函数和类的静态函数,而且无法使用到该类的具体成员值, 但是registerCallbackB却没有这个限制, 我们既可以相registerCallback一样注册普通函数(后面的void *data直接传nullptr、NULL或者 0即可), 也可以注册到某个已经定义好的对象的成员函数上, 如:

class CallbackBHandle
{
public:
    void handle(int value)
    {
        /// 某些回调处理
    }   
};

/// 调用
int main()
{
    CallbackBHandle bhandle;
    registerCallbackB(method_thunk<CallbackBHandle, &CallbackBHandle::handle>, &bhandle);

    /// 其他代码
    return ;
}
           

注: 上面使用到的method_thunk方法是一种将类成员函数转换为对应回调类型的方法, 具体实现可见另一篇博文: C++模板技术之method_thunk, 使用场景: C++中类成员函数作为回调函数

当然除了上述的method_thunk,还有很多的方法的,有兴趣读者可以自己去了解下

偷偷告诉你,如果你的void *类型参数是第一个的话,你可以直接将成员函数转换成对应的普通函数来调用, 比如

#include <iostream>
typedef void (*func)(void *, int);
class Man
{
public:
    Man(int age): _age(age) {}
    void work(int hours) {
        std::cout<< "I'm " << _age << " years old, so I just need work " << hours << " hours!" << std::endl;
    }
private:
    int _age;
};

int main(int argc, char **argv)
{

    Man m();
    func funca = (func)&Man::work;
    funca(&m, );
    return ;
}
           

点击运行有惊喜哦^_^