从C语言到C++(适合有基础但是还不是高手的码农)

前言

从我上大学后学习谭公的C语言编程至今已有13个年头了,当时考完计算机二级后认为自己也算是会编程的人了,但是走过13年的学习和工作,蓦然回首发现自己还处在编程的初级阶段。这期间总体来说本人经历了如下几个阶段:
1、学校课程学习阶段:以谭浩强公的《c语言程序设计》为基础学习C语言知识,老师基本上讲完指针这一节就完事了,还特意强调指针很难不必深究。然后我们就开始用Visual C++这个软件开始练习编程,以通过计算机二级为目标,期间也做了一些好玩的事情,比如把某某人的名字打印出来,再按一个建再打印出”帅哥“字样。青涩的年纪,往事不堪回首呀。
2、单片机编程阶段:当时学习51单片机时候,老师以汇编语言讲解单片机编程,后来在大学生创新实验室参加智能车和机器人比赛,开始学习C语言在单片机上的应用,开始觉得自己牛掰了起来。
3、工程编程阶段:后来读了研究生,开始接触汽车级芯片,供应商开始提供SDK,开始接触操作系统和商用级中间件软件。
4、工作阶段:工作后开始量产产品的编程,对编程质量有了超级高的要求,一些软件bug出现概率千分之一甚至万分之一,而且这些bug出现后问题现场往往就消失了,同时这些bug又必须被查出来,否则后果不堪设想。
5、自主学习阶段:随着工作的时间增长,作为老员工,公司对你的期待也越来越高,你会发现自己的能力难以胜任工作,这时候如果你没有机会进入管理层,那就只有要求自己不断学习啦。这个阶段你会发现自己成长很快,同时又觉得自己很菜,正所谓”学然后知不足“。
本人在工作和学习中走过来很多湾路希望读者读了这篇文章后能够吸取我的教训,活学活用,早日实现编程自由。

队列引起的故事

需求:数据采集系统,要求车辆按照一定周期实时采集信号,当信号缓存10s后,按照先后顺序发送给云端。

数据先进先出的概念知道数据的存储结构为队列,所以我们需要编写一个队列的程序来满足客户需求。如果你知道C++的STL库,那么故事对你来说可能就可以结束了,本文初衷就是为初学者而准备的。

队列介绍

如下图所示:
在这里插入图片描述
队列由,队首指针、存储空间、队尾指针三部分组成,对于对列的操作分为创建队列、出队列和进队列三种操作。
创建队列就是为队列分配存储空间的过程,存储空间的长度也就是队列的长度。队首指针称为Header或者Top或者Front,总之一切为了编码方便而命名,Header指的是将要出队列的数据,也就是每个出队列操作要操作的空间。队尾指针称为Rear或者tail,是指将要插入数据的空间。对列有三种状态:空状态、满状态、非空非满状态。显然空状态只可以做入队列操作,满状态只可以做出队列操作,非空非满状态既可以入队也可以出队。
问题是上面的线性状态给人的感觉是队列只可以用一次,这显然是不合理的,那么怎么才能让队列看起来更合理呢,我们引入了环形队列的概念。

环形队列

环形队列在只是我们想象的概念,真实的存储空间仍然是线性的而不是环形的。如下图:
在这里插入图片描述
环形队列意味着出队后省下的空间将来仍然可以插入数据。观察环形队列示意图可以发现,当Header和Raer不重合时为非空非满状态,当Header和Rear重合时,要么为空,要么为满,那么如何区分空和满呢?这个时候需要在队列结构中引入状态成员,当Header”主动贴到“Rear身上时称为”空状态“,当Rear”主动贴到“Header身上时称为“满状态”,就像虽然都是两个人谈恋爱了,但是谁追谁还是不一样的对吧。那么有没有可能Rear追上了Header,此时明明队列已经满了,Rear仍然愣头愣脑的往前冲又导致Header和Rear分开了,从而让我们误以为队列还没满呢(Header追Rear也是一样的道理)。为了避免这种情况发生,我们需要在编程前设计好状态机,从而避免我们误操作。

状态机

很多初学者喜欢一开始接到任务就写代码,笔者也一样,在掌握了for循环和if else两件大杀器后一顿输出,for、if else过多意味着圈复杂度过大,导致单元测试困难,量产后也会出各种意想不到的问题。正确的做法是在程序编写之前设计好程序的”状态机“,将程序的活动限制在可以预见的状态。
状态机又称状态迁移图,常使用UML统一建模语言表达,以队列编程为例,我们将队列活动限制在空队列(empty)、满队列(full)、非空非满队列(space)三个状态。入下图所示:

在这里插入图片描述
状态机需要指定Init后进入的状态,如图Init后进入empty状态。箭头表示状态的迁移方向,箭头的配文分两个部分:”/“前表示状态的迁移条件,”/”后表示状态迁移的时候所做的操作,比如从empty进入space状态的迁移条件是做了入队操作enQueue,且返回Q_ENOK,状态迁移的同时伴随着队尾指针qRear的增加。具体队列的操作这里不再赘述,全部处于状态机之中。

编码

千呼万唤始出来,读者会觉得作者咋这么墨迹,讲了半天也没讲怎么写代码,其实真实项目开发可能比这还要墨迹很多倍,磨刀不误砍柴工,大家编程前把准备工作做好可以为后续工作省去很多时间。

数据类型和基本接口设计

由队列的状态机知,我们需要队列的状态变量,入队和出队操作也会返回相应的状态值,对列也有一定的数据结构等等,这种对数据类型的设计称为类设计,通常用UML的类图来表达。由于本案例的数据结构较为简单,这里就不再画图了。

状态值和返回值一般都局限于特定几个状态,所以一般采用枚举类型,本例的状态机和返回值的数据类型设计如下:
C/C++ 代码:

typedef enum {
    // queue status
    Q_FULL   = 0xFFu,
    Q_EMPTY  = 0xAAu,
    Q_SPACE  = 0xBBu,
    // return status
    Q_UINIT  = 0x00u,
    Q_INIT   = 0x01u,
    Q_ENOK   = 0x02u,
    Q_DEOK   = 0x03u,
} Q_STATUS;

C语言队列数据结构类型设计:

typedef unsigned char uint8;
typedef unsigned int uint32;
typedef struct
{
    uint32   qHeader;
    uint32   qRear;
    Q_STATUS qStatus;
    uint32   qSize;
    uint32*  qData;
} queueType;
到这里读者可能会有疑问:为啥C++代码没有类似于queueType这样的队列的数据类型设计?这里先解释一下:C++完全兼容C语言,读者可以完全将C语言中的结构体和函数搬移到C++代码中去。但是如果我们将C++看作另一种新语言的话C++和C语言最大的区别在于C++是面向对象的编程而C语言是面向过程的编程。现在读起来可能云里雾里,放心,读完本文后读者一定可以理解何为面向对象的编程。我们先按照C语言的编程风格把这个小小的编程练习讲完再讲C++。
由状态迁移图知队列的操作有三种:
1、创建队列:initQueue,
2、入队列:enQueue
3、出队列:deQueue
有人统计程序员编程超过50%的时间是用于给符号命名,当然这有可能只是个传说,但是也说明了编码时符号命名的重要性,代码编程要望文生义,做到代码自解释。很多规范如MISRA C对编码的命名做了很严格的规范,咱们这里推荐符号命名为“驼峰风格”。

PS:符号为函数和变量,后续学习链接将会接触“符号”的概念。
函数要有函数头,即函数注释,函数的注释通常包含返回值,入参,出参,修改时间,作者等信息,这里化繁就简,注释仅包含前三个部分。代码维护是代码生命周期的重要组成部分,养成良好的编程习惯和注释习惯有利于代码的维护。

/*
inout: q
ret: Q_UINIT/Q_INIT
*/
Q_STATUS initQueue(queueType* q, int* qData, int size)
{
}

/*
in: data
inout: q
ret: Q_ENOK/Q_FULL
*/
Q_STATUS enQueue(queueType* q, int data) 
{
}

/*
out: data
inout: q
ret: Q_DEOK/Q_EMPTY
*/
Q_STATUS deQueue(queueType* q, int* data)
{
} 
好了,到这里编程已经告一段落了,是不是有种还没开始就匆匆结束的感觉,其实咱们已经完成了很多重要的工作,很多软件规范到此就结束了,比如汽车行业的AUTOSAR规范,它定义了AUTOSAR软件的数据类型和API接口,但是并没有定义软件如何实现。

测试驱动开发

不管用什么语言编码,代码不出bug才是第一要务,保证代码不出bug最好的方法就是有足够多的测试用例,至于测试用例的设计有一套专门的测试学说,笔者也没有仔细研究,但是测试用例设计最基本的原则是找出边界值进行测试,以我们的队列代码为例,要测试队列满的状态继续入队的case,要测试队列空的状态继续出队的case,要测试队列初始化后入队和出队的case,要测试数据溢出时的case等等。
测试驱动开发(TDD)意味着代码的开发从测试开始,比如我们要编写enQueue和deQueue函数,我们首先想到的是当queue未创建时候的测试代码,于是我们添加了一个测试case:
typedef enum {
    PASS = 0,
    FAIL = 1,
} testRetType;

queueTestCase1(queueType* q)
{
	if (queue1.qData == NULL)
    {
        return FAIL;
    }
}

C语言编程
queue.h

最终代码

typedef enum {
    // queue status
    Q_FULL   = 0xFFu,
    Q_EMPTY  = 0xAAu,
    Q_SPACE  = 0xBBu,
    // return status
    Q_UINIT  = 0x00u,
    Q_INIT   = 0x01u,
    Q_ENOK   = 0x02u,
    Q_DEOK   = 0x03u,
} Q_STATUS;

typedef struct
{
    unsigned int qHeader;
    unsigned int qRear;
    Q_STATUS     qStatus;
    unsigned int qSize;
    int*         qData;
} queueType;

Q_STATUS initQueue(queueType* q, int* array, int size);
Q_STATUS enQueue(queueType *q, int data);
Q_STATUS deQueue(queueType *q, int* data);

queue.c

#include <stdio.h>
#include "queue.h"
/*
inout: q
ret: Q_UINIT/Q_INIT
*/
Q_STATUS initQueue(queueType* q, int* qData, int size)
{
    Q_STATUS qRet = Q_UINIT;
    if ((qData == NULL) || (size < sizeof(qData) / sizeof(int)))
    {
        return qRet;
    }
    q->qHeader = 0;
    q->qRear   = 0;
    q->qSize   = size;
    q->qStatus = Q_EMPTY;
    q->qData   = qData;
    qRet = Q_INIT;
    return qRet;
}

/*
in: data
inout: q
ret: Q_ENOK/Q_FULL
*/
Q_STATUS enQueue(queueType* q, int data) 
{
    // 非满情况下可以进行入队。
    if (q->qStatus == Q_FULL)
    {
        printf("full\n");
        return Q_FULL;
    }
    q->qStatus = Q_SPACE;
    q->qData[q->qRear] = data;
    q->qRear++;
    // 防止qRear数据类型溢出场景
    if (q->qRear >= q->qSize)
    {
       q->qRear = 0; 
    }
    if (q->qRear == q->qHeader)
    {
        q->qStatus = Q_FULL;
    }
    return Q_ENOK;
}

/*
out: data
inout: q
ret: Q_DEOK/Q_EMPTY
*/
Q_STATUS deQueue(queueType* q, int* data) 
{
    //如果Header==rear,表示队列为空
    if (q->qStatus == Q_EMPTY)
    {
        printf("empty\n");
        return Q_EMPTY;        
    }
    q->qStatus = Q_SPACE;
    *data = q->qData[q->qHeader];
    q->qHeader++;
    // 防止qHeader数据类型溢出场景
    if (q->qHeader >= q->qSize)
    {
       q->qHeader = 0; 
    }
    if (q->qHeader == q->qRear)
    {
        q->qStatus = Q_EMPTY;
    }
    return Q_DEOK;
}

int main() 
{
	// qTestCase();
	return 0;
}

C++编程
queue.h

typedef enum {
    // real
    Q_FULL   = 0xFFu,
    Q_EMPTY  = 0xAAu,
    Q_SPACE  = 0xBBu,
    // temp status
    Q_UINIT  = 0x00u,
    Q_INIT   = 0x01u,
    Q_ENOK   = 0x02u,
    Q_DEOK   = 0x03u,
} Q_STATUS;

typedef unsigned int uint32;

queue.cpp

#include <iostream>
#include "queue4.h"
using namespace std;

template<typename dataT>
class queueClass
{
private:
    uint32   qHeader;
    uint32   qRear;
    Q_STATUS qStatus;
    uint32   qSize;
    dataT*   qData;
public:
    queueClass(dataT* dataArray, int aSize);
    ~queueClass();
public:
    Q_STATUS enQueue(dataT data);
    Q_STATUS deQueue(dataT* data);
};

template<typename dataT>
queueClass<dataT>::queueClass(dataT* dataArray, int aSize)
{
    // cout <<"created"<<endl;
    qHeader = 0;
    qRear   = 0;
    qStatus = Q_EMPTY;
    qSize   = aSize;
    qData   = dataArray;
}
template<typename dataT>
queueClass<dataT>::~queueClass()
{
    qHeader = 0;
    qRear   = 0;
    qStatus = Q_EMPTY;
    qSize   = 0;
    qData   = NULL;
    // cout <<"destroyed"<<endl;
}

/*
in: data
inout: q
ret: Q_ENOK/Q_FULL
*/
template<typename dataT>
Q_STATUS queueClass<dataT>::enQueue(dataT data)
{
    // 非满情况下可以进行入队。
    if (qStatus == Q_FULL)
    {
        cout <<"full"<<endl;
        return Q_FULL;
    }
    qStatus = Q_SPACE;
    qData[qRear] = data;
    qRear++;
    // 防止qRear数据类型溢出场景
    if (qRear >= qSize)
    {
       qRear = 0; 
    }
    if (qRear == qHeader)
    {
        qStatus = Q_FULL;
    }
    return Q_ENOK;   
}

/*
out: data
inout: q
ret: Q_DEOK/Q_EMPTY
*/
template<typename dataT>
Q_STATUS queueClass<dataT>::deQueue(dataT* data) 
{
    //如果Header==rear,表示队列为空
    if (qStatus == Q_EMPTY)
    {
        cout <<"empty"<<endl;
        return Q_EMPTY;        
    }
    qStatus = Q_SPACE;
    *data = qData[qHeader];
    qHeader++;
    // 防止qHeader数据类型溢出场景
    if (qHeader >= qSize)
    {
       qHeader = 0; 
    }
    if (qHeader == qRear)
    {
        qStatus = Q_EMPTY;
    }
    return Q_DEOK;
}

uint32 testDataArray1[1] = {0};
uint32 testDataArray1_1[10] = {0};
uint8  testDataArray2[1] = {0};
uint32 testDeQData1 = 0;
uint8  testDeQData2 = 0;
template<class T>
int func(T& arr)
{
	return sizeof(T);
}
int main() 
{
    queueClass<uint32> q1(testDataArray1, 1);
    queueClass<uint32> q1_1(testDataArray1_1, 10);
    q1.deQueue(&testDeQData1);
    q1.enQueue(1000);
    q1.enQueue(1000);
    q1.deQueue(&testDeQData1);
    cout << testDeQData1 << endl;
cout << "\n" << endl;
    queueClass<uint8> q2(testDataArray2, 1);
    q2.deQueue(&testDeQData2);
    q2.enQueue(100);
    q2.enQueue(100);
    q2.deQueue(&testDeQData2);
    cout << testDeQData2 << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值