基于C語言實現泛型編程詳解
心理歷程
寫了一段時間C++后,真心感覺STL里的容器是個好東西。一個容器可以容納任意類型,容器對外的接口可以操作任意類型的數據,甚至包括自定義類型的數據。這種泛型編程的思想,對于大型項目而言是非常有好處的。
對于C而言,想實現泛型編程并非易事,甚至可以說非常繁瑣,一大堆坑。最主要也沒有現成的輪子可用。當然也有一些通過宏實現了泛型的基礎功能,但是可讀性,可調試性太差了。
于是就想自己造一個輪子,實現基于C對窗口(順序表)的泛化,目標就是實現不同類型下,規(guī)范接口的一致性。拋磚引玉。
輪子用法
int main( void ) { // 1、創(chuàng)建一個窗口,并初始化它,大小為10,類型為double ValueWindowSquential tmp; InitValueWindow( &tmp, kValueTypeList[ DOUBLE ], 10 ); double insert_data = 0; for ( int i = 0; i < tmp.max_size; i++ ) { // 2、填充這個窗口,直到窗口填滿 insert_data = i * 10; if ( kWindowAlreadyFull == ValueWindowFixedInsert( &tmp, &insert_data ) ) { // 3、打印整個窗口 printf( "start sort \r\n" ); ShowTheWindow( &tmp ); // 4、整個窗口排序 ValueWindowSelectSort( &tmp ); // 5、打印排序后的窗口 printf( "end sort \r\n" ); ShowTheWindow( &tmp ); break; } } printf( "test generics \r\n" ); return 0; }
打印log如下:
這時想換成創(chuàng)建一個uint8_t類型的串口,只需要改兩個地方,這兩個地方在C++里也避免不了。
int main( void ) { ValueWindowSquential tmp; InitValueWindow( &tmp, kValueTypeList[ UINT8 ], 10 ); uint8_t insert_data = 0; for ( int i = 0; i < tmp.max_size; i++ ) { insert_data = ( tmp.max_size - i ) * 1; if ( kWindowAlreadyFull == ValueWindowFixedInsert( &tmp, &insert_data ) ) { printf( "start sort \r\n" ); ShowTheWindow( &tmp ); ValueWindowSelectSort( &tmp ); printf( "end sort \r\n" ); ShowTheWindow( &tmp ); break; } } printf( "test generics \r\n" ); return 0; }
大體流程
1.首先初始化一個空窗口對象,然后調用 InitValueWindow 傳入窗口類型,大小,然后初始化它。
2.調用 ValueWindowFixedInsert 往窗口中插入值,直到窗口滿后反饋狀態(tài)。
3.打印整個窗口
4.對窗口排序
5.打印整個窗口
這里的泛型主要通過查表實現了,將希望包含的類型加入表中,然后初始化時傳入其類型和大小。
插入數據的時候,需要保證數據類型和窗口類型統一,這算是個局限性了。
窗口被填充完畢后,會有反饋窗口狀態(tài),這時可以調用 ShowTheWindow 將原始窗口打印。
在調用 ValueWindowSelectSort 將窗口排序。排序完后再次打印。
可以看到除了初始化的時候,需要設定窗口的類型,這和 std::vector< double > 沒什么兩樣,插入數據時需要調用者確保數據類型與窗口統一。
部分源碼
#ifndef __TEST_GENERICS_h #define __TEST_GENERICS_h #include <stdio.h> #include <string.h> #include <malloc.h> #include <stdlib.h> #include <assert.h> typedef signed char int8_t; typedef unsigned char uint8_t; typedef signed short int16_t; typedef unsigned short uint16_t; typedef signed int int32_t; typedef unsigned int uint32_t; typedef enum { UINT8 = 0, INT, FLOAT, DOUBLE, ERROR } TypeName; const char* kValueTypeList[ ERROR + 1 ] = { "uint8_t", "int", "float", "double", "error", }; TypeName ChangeStringToEnum( const char* tmp ); /** * @brief 該結構體用于構建基礎窗口順序表 * this structure is used to build the basic window sequence table */ typedef struct ValueWindowSquential { char* type; void* data; uint32_t max_size; uint32_t sequence; } ValueWindowSquential; /** * @brief 初始化窗口,根據窗口類型,大小,動態(tài)分配內存給到內部緩沖區(qū) * initialize the window, and dynamically allocate memory to the internal buffer according to the window type and size * * @param tmp base structure for Window * @param type Window type * @param max_size Window size * * @throw assert */ void InitValueWindow( ValueWindowSquential* tmp, const char* type, uint32_t max_size ); /** * @brief 重置或銷毀窗口 * reset or destroy window * * @param tmp base structure for Window */ void ResetValueWindow( ValueWindowSquential* tmp ); typedef enum { kWindowIsNotFull = ( 0 ), kWindowIsSliding, kWindowCanNotInsert, kWindowInputFail, } SlideWindowState; /** * @brief 滑動插入數據進入窗口,先入先出(FIFO模型) * slide insert data into the window, first in first out (FIFO model) * * @param tmp base structure for Window * @param data insert data * * @return SlideWindowState * kWindowIsNotFull 窗口未填充滿 * kWindowIsSliding 窗口已填充滿并開始滑動 * * kWindowCanNotInsert 窗口不允許插入 * kWindowInputFail 窗口插入數據失敗 */ SlideWindowState ValueWindowSlideInsert( ValueWindowSquential* tmp, void* data ); typedef enum { kWindowNotFull = ( 0 ), kWindowAlreadyFull, kFixWindowCanNotInsert, kFixWindowInputFail, } FixedWindowState; /** * @brief 固定窗,往窗口里插入數據,直到窗口滿了反饋 kWindowAlreadyFull ,否在反饋 kWindowNotFull * 與滑動窗區(qū)別是,固定窗會采集多組數據,采集完成才能使用窗口,使用完后從頭重新采集 * 也就是降頻處理數據,窗口大小20,10ms插入一次,那么降頻到200ms處理一次窗口(數據) * * @param tmp base structure for Window * @param data insert data * * @return FixedWindowState * kWindowNotFull 窗口未滿 * kWindowAlreadyFull 窗口已滿,可以開始操作 * * kFixWindowCanNotInsert 窗口不允許插入 * kFixWindowInputFail 窗口插入數據失敗 */ FixedWindowState ValueWindowFixedInsert( ValueWindowSquential* tmp, void* data ); /** * @brief 遍歷并打印窗口 * * @param tmp base structure for Window */ void ShowTheWindow( ValueWindowSquential* tmp ); #endif // __TEST_GENERICS_h
/** * @file test_generics.cpp * @author benzs_war_pig (benzwarpig@outlook.com) * @brief 構建一種基于C的泛型順序表,針對不同類型的順序表,實現接口一致化。 * 同時針對順序表實現一些常用操作(排序,濾波,統計等) * * build a generic sequence table based on C, and realize interface consistency * for different types of sequence tables. At the same time, some common operations (sorting, filtering, statistics, etc.) * are implemented for the sequence table * * @version 1.0 * @date 2022-06-30 * * @copyright Copyright (c) 2022 * */ #include "test_generics.h" #include "generics_impl.h" /** * @brief 將字符串轉換成TypeName * private interface * * @param tmp * @return TypeName */ TypeName ChangeStringToEnum( const char* tmp ) { assert( tmp != NULL ); TypeName return_tmp = ERROR; if ( strcmp( tmp, kValueTypeList[ UINT8 ] ) == 0 ) { return_tmp = UINT8; } else if ( strcmp( tmp, kValueTypeList[ FLOAT ] ) == 0 ) { return_tmp = FLOAT; } else if ( strcmp( tmp, kValueTypeList[ DOUBLE ] ) == 0 ) { return_tmp = DOUBLE; } else if ( strcmp( tmp, kValueTypeList[ INT ] ) == 0 ) { return_tmp = INT; } else { printf( "error char* input !!!" ); assert( 0 ); } return return_tmp; } // 初始化窗口 // Initialize window void InitValueWindow( ValueWindowSquential* tmp, const char* type, uint32_t max_size ) { assert( tmp != NULL ); tmp->type = ( char* ) malloc( strlen( type ) * sizeof( char ) ); strncpy( tmp->type, type, strlen( type ) ); tmp->max_size = max_size; tmp->sequence = 0; switch ( ChangeStringToEnum( tmp->type ) ) { case UINT8: { tmp->data = ( uint8_t* ) malloc( max_size * sizeof( uint8_t ) ); memset( tmp->data, 0, tmp->max_size ); } break; case INT: { tmp->data = ( int* ) malloc( max_size * sizeof( int ) ); memset( tmp->data, 0, tmp->max_size ); } break; case FLOAT: { tmp->data = ( float* ) malloc( max_size * sizeof( float ) ); memset( tmp->data, 0, tmp->max_size ); } break; case DOUBLE: { tmp->data = ( double* ) malloc( max_size * sizeof( double ) ); memset( tmp->data, 0, tmp->max_size ); } break; default: { printf( "error tmp->type input !!!" ); assert( 0 ); } break; } printf( "type is : %s , number is : %d \r\n", tmp->type, max_size ); } // 重置/銷毀窗口 void ResetValueWindow( ValueWindowSquential* tmp ) { tmp->sequence = 0; tmp->max_size = 0; if ( tmp->data != NULL ) { free( tmp->data ); tmp->data = NULL; } if ( tmp->type != NULL ) { free( tmp->type ); tmp->type = NULL; } } // 滑動往窗口插入數據 SlideWindowState ValueWindowSlideInsert( ValueWindowSquential* tmp, void* data ) { SlideWindowState return_tmp = kWindowIsNotFull; switch ( ChangeStringToEnum( tmp->type ) ) { case UINT8: { uint8_t* tmp_buffer = ( uint8_t* ) tmp->data; for ( int i = 1; i < tmp->max_size; i++ ) { tmp_buffer[ i - 1 ] = tmp_buffer[ i ]; } uint8_t* res = ( uint8_t* ) data; tmp_buffer[ tmp->max_size - 1 ] = *res; } break; case INT: { int* tmp_buffer = ( int* ) tmp->data; for ( int i = 1; i < tmp->max_size; i++ ) { tmp_buffer[ i - 1 ] = tmp_buffer[ i ]; } int* res = ( int* ) data; tmp_buffer[ tmp->max_size - 1 ] = *res; } break; case FLOAT: { float* tmp_buffer = ( float* ) tmp->data; for ( int i = 1; i < tmp->max_size; i++ ) { tmp_buffer[ i - 1 ] = tmp_buffer[ i ]; } float* res = ( float* ) data; tmp_buffer[ tmp->max_size - 1 ] = *res; } break; case DOUBLE: { double* tmp_buffer = ( double* ) tmp->data; for ( int i = 1; i < tmp->max_size; i++ ) { tmp_buffer[ i - 1 ] = tmp_buffer[ i ]; } double* res = ( double* ) data; tmp_buffer[ tmp->max_size - 1 ] = *res; } break; default: { printf( "error tmp->type input !!!" ); assert( 0 ); } break; } if ( ++tmp->sequence > tmp->max_size ) { return_tmp = kWindowIsSliding; tmp->sequence = tmp->max_size; } return return_tmp; } // 插入數據直到填滿整個窗口 FixedWindowState ValueWindowFixedInsert( ValueWindowSquential* tmp, void* data ) { FixedWindowState return_tmp = kWindowNotFull; switch ( ChangeStringToEnum( tmp->type ) ) { case UINT8: { uint8_t* tmp_buffer = ( uint8_t* ) tmp->data; uint8_t* res = ( uint8_t* ) data; tmp_buffer[ tmp->sequence ] = *res; } break; case INT: { int* tmp_buffer = ( int* ) tmp->data; int* res = ( int* ) data; tmp_buffer[ tmp->sequence ] = *res; } break; case FLOAT: { float* tmp_buffer = ( float* ) tmp->data; float* res = ( float* ) data; tmp_buffer[ tmp->sequence ] = *res; } break; case DOUBLE: { double* tmp_buffer = ( double* ) tmp->data; double* res = ( double* ) data; tmp_buffer[ tmp->sequence ] = *res; } break; default: { printf( "error tmp->type input !!!" ); assert( 0 ); } break; } if ( ++tmp->sequence >= tmp->max_size ) { tmp->sequence = 0; return_tmp = kWindowAlreadyFull; } return return_tmp; } // 打印窗口內全部值 void ShowTheWindow( ValueWindowSquential* tmp ) { // printf("current_type:{%d}", ChangeStringToEnum(tmp->type)); switch ( ChangeStringToEnum( tmp->type ) ) { case UINT8: { uint8_t* msg = ( uint8_t* ) tmp->data; for ( int i = 0; i < tmp->max_size; ++i ) { printf( "i : {%d} , %d \r\n", i, msg[ i ] ); } } break; case INT: { int* msg = ( int* ) tmp->data; for ( int i = 0; i < tmp->max_size; ++i ) { printf( "i : {%d} , %d \r\n", i, msg[ i ] ); } } break; case FLOAT: { float* msg = ( float* ) tmp->data; for ( int i = 0; i < tmp->max_size; ++i ) { printf( "i : {%d} , %f \r\n", i, msg[ i ] ); } } break; case DOUBLE: { double* msg = ( double* ) tmp->data; for ( int i = 0; i < tmp->max_size; ++i ) { printf( "i : {%d} , %f \r\n", i, msg[ i ] ); } } break; default: { printf( "error tmp->type input !!!" ); assert( 0 ); } break; } } int main( void ) { ValueWindowSquential tmp; InitValueWindow( &tmp, kValueTypeList[ DOUBLE ], 10 ); double insert_data = 0; for ( int i = 0; i < tmp.max_size; i++ ) { insert_data = ( tmp.max_size - i ) * 10; if ( kWindowAlreadyFull == ValueWindowFixedInsert( &tmp, &insert_data ) ) { printf( "start sort \r\n" ); ShowTheWindow( &tmp ); ValueWindowSelectSort( &tmp ); printf( "end sort \r\n" ); ShowTheWindow( &tmp ); break; } } ResetValueWindow(&tmp); printf( "test generics \r\n" ); return 0; }
這是最開始的一版源碼,基本的思路是基于 void* 實現對窗口的泛化,把窗口的地址,大小,類型 在初始化時設定好,以后所有的結構便基于這些信息,實現接口一致性。
目前實現了兩種窗口類型, ValueWindowSlideInsert (滑動窗) 和 ValueWindowFixedInsert(固定窗) 。 兩者不同之處只是插入數據時的處理不同?;瑒哟白裱璅IFO模型,即先入先出,窗口狀態(tài)有未滿和開始滑動,一般開始滑動后再對窗口進行操作。
固定窗有未滿和已滿兩種狀態(tài),已滿后會清空窗口,重新開始填充,這也是兩種常見的窗口模型。
在STL里,當有一些底層數據結構去存儲數據時,要有一些容器的方法(算法),比如排序等,這里先實現了一些基礎的泛型算法接口:
#ifndef GENERICS_IMPL_H #define GENERICS_IMPL_H #include <stdbool.h> #include "test_generics.h" /** * @file generics_impl.h * @author benzs_war_pig (benzwarpig@outlook.com) * @brief 該文件實現了一些操作泛型順序表的算法,如排序,查找,遍歷,判斷變化率等等 * * this file implements some algorithms for operating generic sequential tables, such as sorting, searching, traversing, * judging the rate of change, and so on * * @version 1.0 * @date 2022-06-30 * * @copyright Copyright (c) 2022 * */ /** * @brief 交換順序表中兩個成員的值 * * @param tmp base structure for Window * @param i * @param j */ static void swap( ValueWindowSquential* tmp, uint32_t i, uint32_t j ) { assert( tmp != NULL ); // assert( i > tmp->max_size || j > tmp->max_size ); // assert( i >= tmp->max_size || j >= tmp->max_size ); switch ( ChangeStringToEnum( tmp->type ) ) { case UINT8: { uint8_t* tmp_buffer = ( uint8_t* ) tmp->data; uint8_t res = tmp_buffer[ i ]; tmp_buffer[ i ] = tmp_buffer[ j ]; tmp_buffer[ j ] = res; } break; case INT: { int* tmp_buffer = ( int* ) tmp->data; int res = tmp_buffer[ i ]; tmp_buffer[ i ] = tmp_buffer[ j ]; tmp_buffer[ j ] = res; } break; case FLOAT: { float* tmp_buffer = ( float* ) tmp->data; float res = tmp_buffer[ i ]; tmp_buffer[ i ] = tmp_buffer[ j ]; tmp_buffer[ j ] = res; } break; case DOUBLE: { double* tmp_buffer = ( double* ) tmp->data; double res = tmp_buffer[ i ]; tmp_buffer[ i ] = tmp_buffer[ j ]; tmp_buffer[ j ] = res; } break; default: { printf( "error tmp->type input !!!" ); assert( 0 ); } break; } } static inline void ValueWindowBubbleSort( ValueWindowSquential* tmp ) { switch ( ChangeStringToEnum( tmp->type ) ) { case UINT8: { uint8_t* tmp_buffer = ( uint8_t* ) tmp->data; bool is_end_loop = true; for ( int i = 0; i < tmp->max_size && is_end_loop; i++ ) { is_end_loop = false; for ( int j = tmp->max_size - 1; j >= i; j-- ) { if ( tmp_buffer[ j - 1 ] > tmp_buffer[ j ] ) { swap( tmp, j - 1, j ); is_end_loop = true; } } } } break; case INT: { int* tmp_buffer = ( int* ) tmp->data; bool is_end_loop = true; for ( int i = 0; i < tmp->max_size && is_end_loop; i++ ) { is_end_loop = false; for ( int j = tmp->max_size - 1; j >= i; j-- ) { if ( tmp_buffer[ j - 1 ] > tmp_buffer[ j ] ) { swap( tmp, j - 1, j ); is_end_loop = true; } } } } break; case FLOAT: { float* tmp_buffer = ( float* ) tmp->data; bool is_end_loop = true; for ( int i = 0; i < tmp->max_size && is_end_loop; i++ ) { is_end_loop = false; for ( int j = tmp->max_size - 1; j >= i; j-- ) { if ( tmp_buffer[ j - 1 ] > tmp_buffer[ j ] ) { swap( tmp, j - 1, j ); is_end_loop = true; } } } } break; case DOUBLE: { double* tmp_buffer = ( double* ) tmp->data; bool is_end_loop = true; for ( int i = 0; i < tmp->max_size && is_end_loop; i++ ) { is_end_loop = false; for ( int j = tmp->max_size - 1; j >= i; j-- ) { if ( tmp_buffer[ j - 1 ] > tmp_buffer[ j ] ) { swap( tmp, j - 1, j ); is_end_loop = true; } } } } break; default: { printf( "error tmp->type input !!!" ); assert( 0 ); } break; } } static inline void ValueWindowSelectSort( ValueWindowSquential* tmp ) { switch ( ChangeStringToEnum( tmp->type ) ) { case UINT8: { uint8_t* tmp_buffer = ( uint8_t* ) tmp->data; int tmp_data = 0; for ( int i = 0; i < tmp->max_size; i++ ) { tmp_data = i; for ( int j = i; j < tmp->max_size; j++ ) { if ( tmp_buffer[ tmp_data ] > tmp_buffer[ j ] ) { tmp_data = j; } } if ( tmp_data != i ) { swap( tmp, i, tmp_data ); } } } break; case INT: { int* tmp_buffer = ( int* ) tmp->data; int tmp_data = 0; for ( int i = 0; i < tmp->max_size; i++ ) { tmp_data = i; for ( int j = i; j < tmp->max_size; j++ ) { if ( tmp_buffer[ tmp_data ] > tmp_buffer[ j ] ) { tmp_data = j; } } if ( tmp_data != i ) { swap( tmp, i, tmp_data ); } } } break; case FLOAT: { float* tmp_buffer = ( float* ) tmp->data; int tmp_data = 0; for ( int i = 0; i < tmp->max_size; i++ ) { tmp_data = i; for ( int j = i; j < tmp->max_size; j++ ) { if ( tmp_buffer[ tmp_data ] > tmp_buffer[ j ] ) { tmp_data = j; } } if ( tmp_data != i ) { swap( tmp, i, tmp_data ); } } } break; case DOUBLE: { double* tmp_buffer = ( double* ) tmp->data; int tmp_data = 0; for ( int i = 0; i < tmp->max_size; i++ ) { tmp_data = i; for ( int j = i; j < tmp->max_size; j++ ) { if ( tmp_buffer[ tmp_data ] > tmp_buffer[ j ] ) { tmp_data = j; } } if ( tmp_data != i ) { swap( tmp, i, tmp_data ); } } } break; default: { printf( "error tmp->type input !!!" ); assert( 0 ); } break; } } static inline void ValueWindowInsertSort( ValueWindowSquential* tmp ) { switch ( ChangeStringToEnum( tmp->type ) ) { case UINT8: { uint8_t* tmp_buffer = ( uint8_t* ) tmp->data; uint8_t tmp_data = 0; int j = 0; for ( int i = 1; i < tmp->max_size; i++ ) { if ( tmp_buffer[ i ] < tmp_buffer[ i - 1 ] ) { tmp_data = tmp_buffer[ i ]; // TAG : 數據整體向后遷移,尋找數值更大的成員 for ( j = i - 1; tmp_buffer[ j ] > tmp_data && j >= 0; j-- ) { tmp_buffer[ j + 1 ] = tmp_buffer[ j ]; } tmp_buffer[ j + 1 ] = tmp_data; } } } break; case INT: { int* tmp_buffer = ( int* ) tmp->data; int tmp_data = 0; int j = 0; for ( int i = 1; i < tmp->max_size; i++ ) { if ( tmp_buffer[ i ] < tmp_buffer[ i - 1 ] ) { tmp_data = tmp_buffer[ i ]; // TAG : 數據整體向后遷移,尋找數值更大的成員 for ( j = i - 1; tmp_buffer[ j ] > tmp_data && j >= 0; j-- ) { tmp_buffer[ j + 1 ] = tmp_buffer[ j ]; } tmp_buffer[ j + 1 ] = tmp_data; } } } break; case FLOAT: { float* tmp_buffer = ( float* ) tmp->data; float tmp_data = 0; int j = 0; for ( int i = 1; i < tmp->max_size; i++ ) { if ( tmp_buffer[ i ] < tmp_buffer[ i - 1 ] ) { tmp_data = tmp_buffer[ i ]; // TAG : 數據整體向后遷移,尋找數值更大的成員 for ( j = i - 1; tmp_buffer[ j ] > tmp_data && j >= 0; j-- ) { tmp_buffer[ j + 1 ] = tmp_buffer[ j ]; } tmp_buffer[ j + 1 ] = tmp_data; } } } break; case DOUBLE: { double* tmp_buffer = ( double* ) tmp->data; double tmp_data = 0; int j = 0; for ( int i = 1; i < tmp->max_size; i++ ) { if ( tmp_buffer[ i ] < tmp_buffer[ i - 1 ] ) { tmp_data = tmp_buffer[ i ]; // TAG : 數據整體向后遷移,尋找數值更大的成員 for ( j = i - 1; tmp_buffer[ j ] > tmp_data && j >= 0; j-- ) { tmp_buffer[ j + 1 ] = tmp_buffer[ j ]; } tmp_buffer[ j + 1 ] = tmp_data; } } } break; default: { printf( "error tmp->type input !!!" ); assert( 0 ); } break; } } #endif // GENERICS_IMPL_H
以上就是基于C語言實現泛型編程詳解的詳細內容,更多關于C語言 泛型編程的資料請關注腳本之家其它相關文章!
相關文章
Visual Studio 2019配置OpenCV4.1.1詳細圖解教程
這篇文章主要介紹了Visual Studio 2019配置OpenCV4.1.1詳細圖解教程 ,需要的朋友可以參考下2020-02-02