如何写出高质量的函数?快来学习这些coding技巧

目录

前言

函数的编码规范

函数设计技巧


前言

作为一个coder,设计出一个好的架构和写出一手高质量的代码,都是不可缺少的技能;在我理解,高质量的代码意味着代码具有比较强的扩展性、维护性,高内聚和低耦合和尽可能少的bug;函数是我们编码过程中使用频率比较高的不可缺少的步骤,如何写出高质量的函数?不仅要遵循编写函数的代码规范,而且还要遵循函数的一些设计技巧。

函数的编码规范

1.可重入函数使用局部变量;可重入函数使用全部变量需要保护。

2.防止将函数的参数作为工作变量。

3.函数的规模尽量限制在200行以内,不包括注释和空格行。

4.如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该指针在函数体内被意外修改。

5.函数功能尽可能的单一,不要把没有关联的语句放到一个函数中。

6.如果输入参数是以值传递的方式传递对象,则宜改用“ const &”方式来 传递,这样可以省去临时对象的构造和析构过程,从而提高效率。

7.避免设计多参数函数,参数个数尽量控制在 5 个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。

8.尽量不要使用类型和数目不确定的参数。

9.善于使用断言,检查参数非法性和避免错误情况,提高程序可测性。

说明:断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。

10.循环体内工作量最小化,多重循环中,应将最忙的循环放在最内层。

函数设计技巧

1.在用C语言编写一个函数封装成dll,设置一个回调,dll有事件就调用回调,通知上层,函数设计为:

typedef  void (*eventCallBack)(int x, int y, void* pParam);
void  setEventCallBack(eventCallBack, void* pParam);

这个参数void* pParam是关键,它可以把设置回调前的状态通过dll流入回调后,省去了程序自身保留状态的过程,简化了流程。举个例子,在C++中需要在类中调用这个函数,那就可以把这个函数和类的this关联起来,函数调用就比较顺手了,示例如下:

class MyTest
{
public:
    explicit MyTest(){
        setEventCallBack(&MyTest::_eventCallBack, this);
    } 
    void event(int x, int y){
        //...
    }

private:
    static void  _eventCallBack(int x, int y, void* pParam);
};

void  MyTest::_eventCallBack(int x, int y, void* pParam)
{
    MyTest* pTest = static_cast(pParam);
    if (pTest ){
        pTest->event(x, y);
    }
}

在构造函数把this指针通过函数setEventCallBack把回调函数_eventCallBack设置进去,事件通知的时候,又把void*转换为this指针,直接调用event函数。如果没有void*参数,需要事先保存MyTest*,有了viod*就省去这个步骤了。

2.设计函数通过两次调用获取返回值

在有些场景下,比如通过文章的id获取文章的内容,设计函数为:

int  getBookContent(char* pContent, int& len);

用户调用的时候,他不知道pContent需要开辟多大的空间,只能尽可能的把pContent弄大一些,尽管如此,但还是不一定能满足要求,怎么办呢?此时,就可以把函数设计分为两步:第一步获取内容的大小,第二步获取内容数据,当然也可以通过函数返回值来判断传入的pContent缓冲区是否满足要求,调用过程可以这样:

int  getBookContent(char* pContent, int& len);

void test()
{
    int len  = 0;
    int result = 0;
    
    //[1] 第一步
    result = getBookContent(null, len);

    //[2] 第二步
    std::unique_ptr pContent(new char[len]);
    result = getBookContent(pContent.get(), len);
}

实际案例:windows系统函数GetAdaptersInfo,获取网卡配置信息

DWORD GetAdaptersInfo(
  PIP_ADAPTER_INFO pAdapterInfo,  //指向一个缓冲区,用来取得IP_ADAPTER_INFO结构列表
  PULONG pOutBufLen   //指定上面缓冲区大小,如果大小不够,此参数返回所需大小
)

IP_ADAPTER_INFO结构包含了本地计算机网络适配器的信息

#include 
#include 
#include 
#pragma comment(lib, "Iphlpapi.lib")

using namespace std;

BOOL GetNetworkAdapterInfo()
{
    PIP_ADAPTER_INFO pIPAdapterInfo = nullptr;
    ULONG size = sizeof(IP_ADAPTER_INFO);
    //填充pIPadapterInfo变量,其中size既是一个输入量,也是一个输出量
    int nRet = GetAdaptersInfo(null, &size);
    //记录网卡数量
    int netCarNum = 0;

    if (ERROR_BUFFER_OVERFLOW == nRet)
    {
        //如果返回此参数,说明GetAdaptersInfo参数传递的内存空间大小不够,同时传出size表示需要的内存空间大小
        //释放原来的内存空间
        pIPAdapterInfo = (PIP_ADAPTER_INFO)new byte[size];
        //再次调用GetAdaptersInfo填充结构体
        nRet = GetAdaptersInfo(pIPAdapterInfo, &size);
    }

    if (ERROR_SUCCESS == nRet)
    {
        //...
    }
  //释放分配的内存
  if (pIPAdapterInfo)
    delete pIPAdapterInfo;    
    return true;
}

调用函数GetAdaptersInfo第一次传入null,获取到了电脑所有网络适配器数据占空间的总大小,第二次动态申请内存,获取所有的网络适配器真实数据。

3.实现链式表达式

就是为了后来函数调用者方便而设计的,这种方便的实现方法,看起来就是链子链在一起的,所以称为链式表达式;strcpy函数就是这样的典型:

char *strcpy(char *strDest, const char *strSrc);
{
    assert((strDest!=NULL) && (strSrc !=NULL)); 
    char *address = strDest; 
    while( (*strDest++ = * strSrc++) != ‘\0’ ) {;}
    return address ;
}

为什么要返回char*,就是为了实现链式表达式,实现如下面这样的调用:

int length = strlen( strcpy( strDest, “hello world”) );

实际案例:C++标准库中的std::cout 

std::cout类继承自ostream类,ostream(即basic_ostream)类继承自ios类,ios类继承自ios_base类,详细的继承关系如下图:

如何写出高质量的函数?快来学习这些coding技巧_第1张图片

在basic_ostream类中详细实现了operator<<操作符

class basic_ostream : virtual public basic_ios<_Elem, _Traits> { // control insertions into a stream buffer
    //...

    //...
    basic_ostream& __CLR_OR_THIS_CALL operator<<(
        basic_ostream&(__cdecl* _Pfn)(basic_ostream&) );
    basic_ostream& __CLR_OR_THIS_CALL operator<<(_Myios&(__cdecl* _Pfn)(_Myios&) );
    basic_ostream& __CLR_OR_THIS_CALL operator<<(ios_base&(__cdecl* _Pfn)(ios_base&) );
    basic_ostream& __CLR_OR_THIS_CALL operator<<(bool _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(short _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(unsigned short _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(int _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(unsigned int _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(long _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(unsigned long _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(long long _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(unsigned long long _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(float _Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(double_Val);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(long double_Val);
    basic_ostream& operator<<(nullptr_t);
    basic_ostream& __CLR_OR_THIS_CALL operator<<(_Mysb* _Strbuf);
    //...
};

重载操作符<<,函数都是返回的basic_ostream&,就是为了实现链式表达式,完成如下的调用:

bool a = false;
int  b = 100;
double c = 15.666
std::cout << a << b << c << "hello world" << std::endl;

4.函数参数类型选择。

C++17增加了std::string_view它在很多情况下会优于使用std::string 。

尤其是用做函数形参的时候,使用std::string_view基本一定优于老式的const std::string&这种写法。

C++17之前的写法:

void func(const std::string&s){
    std::cout << s << '\n';
}

C++17之后的写法:

void func(std::string_view s){
    std::cout << s << '\n';
}

std::string_view 只是一个视图,用来指代原字符串的,保有一个size和一个指针即可。比起std::string有一个构造的过程,要高效一些。

5.未完待续。。。

你们知道还有哪些函数编程规范和实现技巧,欢迎在评论区留言讨论。

你可能感兴趣的:(#编程技巧,c++,开发语言)