<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
寫了一段時間C++後,真心感覺STL裡的容器是個好東西。一個容器可以容納任意型別,容器對外的介面可以操作任意型別的資料,甚至包括自定義型別的資料。這種泛型程式設計的思想,對於大型專案而言是非常有好處的。
對於C而言,想實現泛型程式設計並非易事,甚至可以說非常繁瑣,一大堆坑。最主要也沒有現成的輪子可用。當然也有一些通過宏實現了泛型的基礎功能,但是可讀性,可偵錯性太差了。
於是就想自己造一個輪子,實現基於C對視窗(順序表)的泛化,目標就是實現不同型別下,規範介面的一致性。拋磚引玉。
int main( void ) { // 1、建立一個視窗,並初始化它,大小為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 rn" ); ShowTheWindow( &tmp ); // 4、整個視窗排序 ValueWindowSelectSort( &tmp ); // 5、列印排序後的視窗 printf( "end sort rn" ); ShowTheWindow( &tmp ); break; } } printf( "test generics rn" ); return 0; }
列印log如下:
這時想換成建立一個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 rn" ); ShowTheWindow( &tmp ); ValueWindowSelectSort( &tmp ); printf( "end sort rn" ); ShowTheWindow( &tmp ); break; } } printf( "test generics rn" ); return 0; }
1.首先初始化一個空視窗物件,然後呼叫 InitValueWindow 傳入視窗型別,大小,然後初始化它。
2.呼叫 ValueWindowFixedInsert 往視窗中插入值,直到視窗滿後反饋狀態。
3.列印整個視窗
4.對視窗排序
5.列印整個視窗
這裡的泛型主要通過查表實現了,將希望包含的型別加入表中,然後初始化時傳入其型別和大小。
插入資料的時候,需要保證資料型別和視窗型別統一,這算是個侷限性了。
視窗被填充完畢後,會有反饋視窗狀態,這時可以呼叫 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 初始化視窗,根據視窗型別,大小,動態分配記憶體給到內部緩衝區 * 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 * 與滑動窗區別是,固定窗會採集多組資料,採集完成才能使用視窗,使用完後從頭重新採集 * 也就是降頻處理資料,視窗大小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 rn", 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 rn", i, msg[ i ] ); } } break; case INT: { int* msg = ( int* ) tmp->data; for ( int i = 0; i < tmp->max_size; ++i ) { printf( "i : {%d} , %d rn", i, msg[ i ] ); } } break; case FLOAT: { float* msg = ( float* ) tmp->data; for ( int i = 0; i < tmp->max_size; ++i ) { printf( "i : {%d} , %f rn", i, msg[ i ] ); } } break; case DOUBLE: { double* msg = ( double* ) tmp->data; for ( int i = 0; i < tmp->max_size; ++i ) { printf( "i : {%d} , %f rn", 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 rn" ); ShowTheWindow( &tmp ); ValueWindowSelectSort( &tmp ); printf( "end sort rn" ); ShowTheWindow( &tmp ); break; } } ResetValueWindow(&tmp); printf( "test generics rn" ); return 0; }
這是最開始的一版原始碼,基本的思路是基於 void* 實現對視窗的泛化,把視窗的地址,大小,型別 在初始化時設定好,以後所有的結構便基於這些資訊,實現介面一致性。
目前實現了兩種視窗型別, ValueWindowSlideInsert (滑動窗) 和 ValueWindowFixedInsert(固定窗) 。 兩者不同之處只是插入資料時的處理不同。滑動窗遵循FIFO模型,即先入先出,視窗狀態有未滿和開始滑動,一般開始滑動後再對視窗進行操作。
固定窗有未滿和已滿兩種狀態,已滿後會清空視窗,重新開始填充,這也是兩種常見的視窗模型。
在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語言 泛型程式設計的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45