C++代码重构——从C global到C++ template

在学数据结构的时候,我常有这样目标——写出能够最大程度复用的代码(算法正确,封装优秀)。我常想——如何能在短时间内达成“算法正确,封装优秀”这样的目标。经过一段时间的摸索,我的结论是:先用C写出正确的算法,再将它改写成C++ class,最后再考虑改为template。这种方法简单可行,基本实现了 逻辑(算法)设计与接口设计两个步骤的分离。

在写数据结构代码的场景下,使用这种方法的前提是——你必须先把这个数据结构需要哪些操作弄清楚,并且有如何实现的大致思路(如果不清楚,翻翻书☺)。

下面以一个有界队列为例,进行演示。

正确的算法

在考虑如何实现算法时,可以完全不用任何封装(C struct的封装也不用),只要实现最小操作集合就行了,但测试必须要写。
这里就是实现所谓的“环形缓冲”,空出一个用于区分队满和队空。连同测试,代码如下:

#include <stdio.h>
#include <stdbool.h>
#include <string.h>  // for memset.

#define BUFMAX 10
int buffer[BUFMAX];
int front = 0;
int rear = 0;

bool empty()
{
    return front == rear;
}

bool full()
{
    return (front+1) % BUFMAX == rear;
}

int size()
{
    return (front - rear + BUFMAX) % BUFMAX;
}

void clear()
{
    rear = front;
    memset(buffer, -1, sizeof(buffer)); // TEST
}

void push(int x)
{
    buffer[front] = x;
    front = (front+1) % BUFMAX;
}

int pop()
{
    int res = buffer[rear];
    buffer[rear] = -1;  // TEST
    rear = (rear+1) % BUFMAX;
    return res;
}

void show()
{
	int i=0;
	for(i=0; i<BUFMAX; i++)
		printf("%d ", buffer[i]);
	printf("\n");
}

#define TEST

#ifdef TEST
void test1()
{
    int i;
	
	puts("test1: ");
	puts("push test:");
    for (i=0; !full(); i++)
    {
        push((i+1)*10);
        show();
    }

    puts("pop test:");
    while (!empty())
    {
        printf("%d\n", pop());
    }
    show();
}

void test2()
{
	puts("test2: ");
	clear();
	push(1);
	push(2);
	push(3);
	pop();
	push(4);
	push(5);
	
	while(!empty()) {
		printf("%d\n", pop());
	}
}

int main()
{
    test1();
    test2();
    return 0;
}
#endif
(运行结果不在此贴出,感兴趣的同学自己运行)
在只考虑算法不考虑接口的情况下,写出这样的代码很容易(相信有点语言功底的都能写得出来)。当然,上面的#define TEST可以不写,用编译选项定义也是可以的。

封装为class

有了这样的基础,封装为C++ class就很容易了(这里直接包裹,实际情形可能有功能类似但参数不同的接口,略作封装即可)。这么简单的成员函数,没有必要分开来了,这里就用一个.h:
#include <stdio.h>
#include <string.h> // for memset.

class BoundedQueue {
	static const int BUFMAX = 10;

	int rear;
	int front;
	int buffer[BUFMAX];

public:
	BoundedQueue() : rear(0), front(0) { memset(buffer, -1, sizeof(buffer)); }

	bool empty()
	{
		return front == rear;
	}

	bool full()
	{
		return (front+1) % BUFMAX == rear;
	}

	int size()
	{
		return (front - rear + BUFMAX) % BUFMAX;
	}

	void clear()
	{
		rear = front;
	}

	void push(int x)
	{
		buffer[front] = x;
		front = (front+1) % BUFMAX;
	}

	int pop()
	{
		int res = buffer[rear];
		rear = (rear+1) % BUFMAX;
		return res;
	}
	
	void show()
	{
		int i=0;
		printf("front: %d, rear: %d\n", front, rear);
		for(i=0; i<BUFMAX; i++)
			printf("%d ", buffer[i]);
		printf("\n");
	}
};

封装为类之后,测试代码有必要分开来写:
         
         
#include "boundedQueue.h"
#include <stdlib.h>
#include <time.h>

int main()
{
	BoundedQueue que;

	srand(time(NULL));
	while( !que.full() ) 
	{
		int r = rand();
		printf("push: %d\n", r);
		que.push(r);
	}
	
	que.show();
	while( !que.empty() )
	{
		printf("pop: %d\n", que.pop());
	}
	return 0;
}

重构为class template

显然,这样直接封装的有界队列存在问题——元素类型固定(这通常是写成template的理由),缓冲大小固定。对于第一点,可以通过将代码重构为C++的类模板实现;即将元素的类型作为类模板的一个参数。对于第二点,也可以借助模板参数实现(模板除了有类型参数,也可以有值参数)。当然也可以把buffer改为指针,在ctor传入大小,这里不做介绍。代码如下:
            
            
#include <stdio.h>
#include <string.h> // for memset.

template<typename T, int BUFMAX=10>
class BoundedQueue 
{
	int rear;
	int front;
	T   buffer[BUFMAX];

public:
	typedef T value_type;

	BoundedQueue() : rear(0), front(0) { memset(buffer, -1, sizeof(buffer)); }

	bool empty()
	{
		return front == rear;
	}

	bool full()
	{
		return (front+1) % BUFMAX == rear;
	}

	int size()
	{
		return (front - rear + BUFMAX) % BUFMAX;
	}
	
	int capacity()
	{
		return BUFMAX-1;
	}

	void clear()
	{
		rear = front;
	}

	void push(int x)
	{
		buffer[front] = x;
		front = (front+1) % BUFMAX;
	}

	int pop()
	{
		int res = buffer[rear];
		rear = (rear+1) % BUFMAX;
		return res;
	}
	
	void show()
	{
		int i=0;
		printf("front: %d, rear: %d\n", front, rear);
		for(i=0; i<BUFMAX; i++)
			printf("%d ", buffer[i]);
		printf("\n");
	}
};
此时的测试代码:
            
            
#include "boundedQueue.hpp"
#include <stdlib.h>
#include <time.h>

int main()
{
	BoundedQueue<int, 20> que;

	srand(time(NULL));
	while( !que.full() ) 
	{
		int r = rand();
		printf("push: %d\n", r);
		que.push(r);
	}
	
	que.show();
	while( !que.empty() )
	{
		printf("pop: %d\n", que.pop());
	}
	return 0;
}
这里描述的方法(C->OO->template)主要是从代码复用的角度考虑,并非与传统OOP教材上的“先接口后实现”相违背。你可以理解为——如何让《数据结构》教材上的C代码的到最大程度上的复用。(多数《数据》教材采用C代码讲解,当然也有使用其他语言的)
直接使用“先接口,后实现”的方法,并非不行,只是可能要多改很多次代码(尤其是考虑模板的时候T^T,想想都是泪,不能让学弟学妹们掉同样的坑了)。使用本文所述方法的好处就是——可以让逻辑设计与接口设计两个过程解耦。

原文链接  http://blog.csdn.net/xusiwei1236/article/details/24042563
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值