鳥の巣箱

ネトゲしたり、機械いじったり、ソフト書いたり、山篭ったり、ギャンブルしたりする人

C言語でオブジェクト指向"風"のリングバッファと移動平均の計算

STMicroを使った仕事をしていて、なんとなく書いてみたくなったのでやってみた。

基本的には構造体を駆使すれば、オブジェクト指向風な書き方はできるみたい。
ただ、あくまでもオブジェクト指向”風”なんでね。

リングバッファの設計

#ifndef INC_TSRINGBUFFER_H_
#define INC_TSRINGBUFFER_H_

#include <stdlib.h>

typedef struct ringbuffer{
	unsigned long	     Count;
	unsigned long	     WriteAddress;
	unsigned long	     ReadAddress;
    void (*WriteData)(struct ringbuffer* this, double data);
    double (*ReadData)(struct ringbuffer* this);
    double (*ReadOldData)(struct ringbuffer* this);
    double    *Buffer;
}TTscRingBuffer;

TTscRingBuffer *RingBuffer_Create(unsigned long	  Length);
unsigned long	     RingBuffer_IncAddress(unsigned long	  adr, unsigned long	  length);
void RingBuffer_WriteData(TTscRingBuffer* this, double data);
double RingBuffer_ReadData(TTscRingBuffer* this);
double RingBuffer_ReadOldData(TTscRingBuffer* this);

#endif /* INC_TSRINGBUFFER_H_ */

まずはヘッダから。

typedef struct ringbuffer{
	unsigned long	     Count;
	unsigned long	     WriteAddress;
	unsigned long	     ReadAddress;
    void (*WriteData)(struct ringbuffer* this, double data);
    double (*ReadData)(struct ringbuffer* this);
    double (*ReadOldData)(struct ringbuffer* this);
    double    *Buffer;
}TTscRingBuffer;

この部分でいわゆるオブジェクトを定義します。
メンバ変数やメソッドの定義ですね。
メソッドの実態は.cファイルの方に記述していくことになります。
プロトタイプ宣言はヘッダに書いてしまいます。

TTscRingBuffer *RingBuffer_Create(unsigned long	  Length);
unsigned long	     RingBuffer_IncAddress(unsigned long	  adr, unsigned long	  length);
void RingBuffer_WriteData(TTscRingBuffer* this, double data);
double RingBuffer_ReadData(TTscRingBuffer* this);
double RingBuffer_ReadOldData(TTscRingBuffer* this);

でソース部分

#include "TsRingBuffer.h"

TTscRingBuffer *RingBuffer_Create(unsigned long	  Length){
    TTscRingBuffer *buf = malloc(sizeof(TTscRingBuffer));
    if (buf != NULL){
        buf->Count          = Length;
        buf->WriteAddress   = 0;
        buf->ReadAddress    = 0;
        buf->Buffer = malloc(sizeof(double)*Length);
        buf->WriteData  = RingBuffer_WriteData;
        buf->ReadData   = RingBuffer_ReadData;
        buf->ReadOldData= RingBuffer_ReadOldData;
        if (buf->Buffer == NULL){
            free(buf);
            return NULL;
        }
    }
    return buf;
}

unsigned long	RingBuffer_IncAddress(unsigned long	  adr, unsigned long	length){
    adr++;
    if (adr >= length){
        adr  = 0;
    }
    return adr;
}

void RingBuffer_WriteData(TTscRingBuffer* this, double data){
    this->Buffer[this->WriteAddress]    = data;
    this->WriteAddress  = RingBuffer_IncAddress(this->WriteAddress, this->Count);
}

double RingBuffer_ReadData(TTscRingBuffer* this){
    double cash;
    cash    = this->Buffer[this->ReadAddress];
    this->ReadAddress   = RingBuffer_IncAddress(this->ReadAddress, this->Count);
    return cash;
}

double RingBuffer_ReadOldData(TTscRingBuffer* this){
	return this->Buffer[this->WriteAddress];
}

ソースはこんな感じ。
それぞれを軽く解説しておきますと

#include "TsRingBuffer.h"

TTscRingBuffer *RingBuffer_Create(unsigned long	  Length){
    TTscRingBuffer *buf = malloc(sizeof(TTscRingBuffer));
    if (buf != NULL){
        buf->Count          = Length;
        buf->WriteAddress   = 0;
        buf->ReadAddress    = 0;
        buf->Buffer = malloc(sizeof(double)*Length);
        buf->WriteData  = RingBuffer_WriteData;
        buf->ReadData   = RingBuffer_ReadData;
        buf->ReadOldData= RingBuffer_ReadOldData;
        if (buf->Buffer == NULL){
            free(buf);
            return NULL;
        }
    }
    return buf;
}

これがコンストラクタになります。
この中で変数の初期化と、配列の初期化をやっています。
メソッドのアドレスもここで代入します。

unsigned long	RingBuffer_IncAddress(unsigned long	  adr, unsigned long	length){
    adr++;
    if (adr >= length){
        adr  = 0;
    }
    return adr;
}

void RingBuffer_WriteData(TTscRingBuffer* this, double data){
    this->Buffer[this->WriteAddress]    = data;
    this->WriteAddress  = RingBuffer_IncAddress(this->WriteAddress, this->Count);
}

double RingBuffer_ReadData(TTscRingBuffer* this){
    double cash;
    cash    = this->Buffer[this->ReadAddress];
    this->ReadAddress   = RingBuffer_IncAddress(this->ReadAddress, this->Count);
    return cash;
}

double RingBuffer_ReadOldData(TTscRingBuffer* this){
//    return this->Buffer[RingBuffer_IncAddress(this->WriteAddress, this->Count)];
	return this->Buffer[this->WriteAddress];
}

メソッドの記述です。
pythonのselfのような感じで、基本的にメソッドの第一引数には自身の構造体を与えてやります。

移動平均計算

リングバッファと似たような感じで定義していきます。
まずヘッダ。

#ifndef INC_TSAVERAGE_H_
#define INC_TSAVERAGE_H_

#include "TsRingBuffer.h"

typedef struct moveavg{
    double  Value;
    unsigned long NowCount;
    TTscRingBuffer* Buffer;
    double (*AddData)(struct moveavg* this, double data);
    void (*Clear)(struct moveavg* this);
}TTscMovingAverage;

TTscMovingAverage *MovingAverage_Create(unsigned long Length);
double MovingAverage_AddData(TTscMovingAverage* this, double data);
void MovingAverage_Clear(TTscMovingAverage* this);

#endif /* INC_TSAVERAGE_H_ */

次にソース。

#include "TsAverage.h"
//#include "stdio.h"

TTscMovingAverage *MovingAverage_Create(unsigned long Length){
    TTscMovingAverage *avg  = malloc(sizeof(TTscMovingAverage));
    if(avg!=NULL){
        avg->NowCount   = 0;
        avg->Value      = 0;
        avg->Buffer = RingBuffer_Create(Length);
        avg->AddData= MovingAverage_AddData;
        avg->Clear  = MovingAverage_Clear;
        if (avg->Buffer == NULL){
            free(avg);
            return NULL;
        }
    }
    return avg;
}

double MovingAverage_AddData(TTscMovingAverage* this, double data){
    // unsigned long n;
    if (this->NowCount >= this->Buffer->Count){
    	this->Value = ((this->Value*this->Buffer->Count) -this->Buffer->ReadOldData(this->Buffer) +data) / this->Buffer->Count;
    }
    else{
        this->Value = ((this->Value*this->NowCount) +data) / (this->NowCount+1);
        this->NowCount++;
    }
    this->Buffer->WriteData(this->Buffer,data);
    return this->Value;
}

void MovingAverage_Clear(TTscMovingAverage* this){
    this->NowCount  = 0;
    this->Value     = 0;
}

移動平均の計算アルゴリズムは、以前解説した記事を見てください。
birdhouse.hateblo.jp

使い方例

雑な使い方ですが、こんな感じで使います。

#include <stdio.h>
#include "TsAverage.h"

int main(void){
    TTscMovingAverage* avg  = MovingAverage_Create(10);
    int i;
    for(i=0;i<20;i++){
        printf("%f\n", avg->AddData(avg, i));
    }
}

出力はこんな感じで、移動平均が計算できてるのが確認できます。

0.000000
0.500000
1.000000
1.500000
2.000000
2.500000
3.000000
3.500000
4.000000
4.500000
5.500000
6.500000
7.500000
8.500000
9.500000
10.500000
11.500000
12.500000
13.500000
14.500000