程式設計程式碼:用C語言來實現下雪效果,這個冬天,雪花很美
前言
正文
1.1先簡單處理跨平臺
本文寫作動機,還是感謝一下大學的啟蒙老師,讓我知道了有條路叫做程式設計師,可以作為工作生存下去.那就上程式碼了.
首先程式碼定位是面向簡單跨平臺,至少讓gcc和vs能夠跑起來.
其實跨平臺都是嚼頭,說白了就是一些醜陋的巨集.真希望所有系統合二為一,採用統一的標準api設計,但這是不可能的,就相當於很早之前的電視制式一樣.
那麼我們先看圍繞跨平臺的巨集
#include #include #include #include /** 時間 : 2015年12月26日11:43:22 * 描述 : 應該算過節吧,今天,寫了個雪花特效 程式碼, * *//** 清除螢幕的shell 命令/控制檯命令,還有一些依賴平臺的實現 * 如果定義了 __GNUC__ 就假定是 使用gcc 編譯器,為Linux平臺 * 否則 認為是 Window 平臺*/#ifdefined(__GNUC__)//下面是依賴 Linux 實現#include #definesleep_ms(m) \ usleep(m *1000)//向上移動游標函式 Linuxstaticvoid__curup(int height) { inti= -1; while(++i printf("\033[1A");//先回到上一行 }#else// 建立等待函式 1s 60 幀 相當於 16.7ms => 1幀, 我們取16ms// 咱麼的這螢幕 推薦 1s 25幀吧 40ms// 這裡建立等待函式 以毫秒為單位 , 需要依賴作業系統實現#include #definesleep_ms(m) \ Sleep(m)//向上移動游標staticvoid__curup(int height) { COORD cr = {0,0}; // GetStdHandle(STD_OUTPUT_HANDLE) 獲取螢幕物件, 設定游標 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cr);}#endif/*__GNUC__ 跨平臺的程式碼都很醜陋 */
首先是sleep_ms這個巨集,傳入一個毫秒數,讓作業系統等待.
對於__curup實現的不好.功能是讓控制檯當前游標移動到上面的height位置,對於window直接移動到第一行(0,0)位置.
上面一共用了 5個頭檔案還是容易的程式碼.string.h主要用的是memset函式,讓一段記憶體初始化,用0填充.
對於time.h主要是為了初始化時間種子,方便每次執行都不一樣.
// 初始化隨機數種子,改變雪花軌跡srand((unsigned)time(NULL));
1.2再說主業務程式碼
這裡程式設計師執行的主業務,先說一說這裡用的資料結構如下
// 定義初始螢幕的寬高畫素巨集#define_INT_WIDTH (100)#define_INT_HEIGHT (50)// 螢幕重新整理幀的速率#define_INT_FRATE (40)// 雪花飄落的速率,相對於 螢幕重新整理幀 的倍數#define_INT_VSNOW (10)/** 錯誤處理巨集,msg必須是""括起來的字串常量 * __FILE__ : 檔案全路徑 * __func__ : 函式名 * __LINE__ : 行數行 * __VA_ARGS__ : 可變引數巨集, * ##表示直接連線, 例如 a##b <=> ab*/#definecerr(msg,...) \ fprintf(stderr, "[%s:%s:%d]"msg"\n",__FILE__,__func__,__LINE__,##__VA_ARGS__);/** 螢幕結構體, 具有 寬高 * frate : 繪製一幀的週期, 單位是 毫秒 * width : 螢幕的寬,基於視窗的左上角(0,0) * height : 螢幕的高 * pix : 用一維模擬二維 主要結構如下 * 0 0 0 1 0 0 1 0 1 0 * 0 1 0 1 0 1 0 1 2 0 * . . . * => 0表示沒畫素, 1表示1個畫素,2表示2個畫素....*/struct screen { intfrate;// 也可以用 unsigned 結構int width; int height; char*pix; };
建立了一個繪圖物件structscreen這裡構建這個結構體的時候用了下面一個技巧
//後面是 為 scr->pix 分配的記憶體 width*heightscr =malloc(sizeof(structscreen) +sizeof(char)*width*height);
一次分配兩個記憶體空間.下面是主要實現的api物件
/** 建立一個 螢幕結構指標 返回 * * int frate : 繪製一幀的週期 * int width : 螢幕寬度 * int height : 螢幕高度 * return : 指向螢幕結構的指標 * */structscreen* screen_create(intfrate,intwidth,int height);/** 銷燬一個 螢幕結構指標, 併為其置空 * struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的 * */voidscreen_destory(structscreen** pscr);/** * 螢幕繪製函式,主要生成一個雪花效果 * * struct screen* : 螢幕資料 * return : 0表示可以繪製了,1表示圖案不變*/intscreen_draw_snow(structscreen* scr);/** * 螢幕繪製動畫效果, 繪製雪花動畫 * * struct screen* : 螢幕結構指標*/voidscreen_flash_snow(structscreen* scr);
建立銷燬,繪製一個雪花介面,繪製雪花動畫效果的api.其實都很相似,用opengl庫,主要讓我們省略了需要單獨和作業系統顯示層打交道工作.
這裡介紹一下,個人一個簡單避免野指標的的方法,具體看下面實現
/** 銷燬一個 螢幕結構指標, 併為其置空 * struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的 * */voidscreen_destory(structscreen** pscr) { if(NULL == pscr || NULL == *pscr) return; free(*pscr); // 避免野指標*pscr = NULL; }
在執行之後置空,因為C程式設計師對NULL一定要敏感,形成條件反射.和大家開個玩笑 ,
請問 :
C 語言中, NULL , 0,'\0',"0",false有什麼異同 ?
歡迎同行,在招聘的時候問問,應聘初級開發工作者.為什麼C需要扣的那麼細.因為其它語言.你不明白是什麼,
你可以用的很好.但是C你寫的程式碼,如果不知道會有怎樣的結果,那麼線上就一大片伺服器直接崩掉.而且還很難找出
問題所在.因為C很簡單,越簡單就是越複雜.就越需要專業的維護人員.導致它成了'玩具'.
最後看一下主業務
// 主函式,主業務在此執行intmain(intargc,char*argv[]) { structscreen* scr = NULL; //建立一個螢幕物件scr = screen_create(_INT_FRATE, _INT_WIDTH, _INT_HEIGHT); if(NULL == scr) exit(EXIT_FAILURE); //繪製雪花動畫 screen_flash_snow(scr); //銷燬這個螢幕物件screen_destory(&scr); return0; }
還是非常容易看懂的,建立一個螢幕物件,繪製雪花效果.銷燬螢幕物件.
1.3說一寫介面的實現細節
先看幾個簡單的api實現,建立和銷魂程式碼如下,很直白.
/** 建立一個 螢幕結構指標 返回 * * int frate : 繪製一幀的週期 * int width : 螢幕寬度 * int height : 螢幕高度 * return : 指向螢幕結構的指標 * */structscreen* screen_create(intfrate,intwidth,int height) { structscreen *scr = NULL; if(frate<0|| width <=0|| height <=0) { cerr("[WARNING]check is frate<0 || width<=0 || height<=0 err!"); return NULL; } //後面是 為 scr->pix 分配的記憶體 width*heightscr =malloc(sizeof(structscreen) +sizeof(char)*width*height); if(NULL == scr) { cerr("[FATALG]Out of memory!"); return NULL; } scr->frate = frate; scr->width = width; scr->height = height; //減少malloc次數,malloc消耗很大,記憶體洩露呀,記憶體碎片呀scr->pix = ((char*)scr) +sizeof(struct screen); return scr; }/** 銷燬一個 螢幕結構指標, 併為其置空 * struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的 * */voidscreen_destory(structscreen** pscr) { if(NULL == pscr || NULL == *pscr) return; free(*pscr); // 避免野指標*pscr = NULL; }
後面說一下如何繪製螢幕中雪花
主要演算法是
a.有個螢幕wxh
b.螢幕從上面第一行出雪花 ,出雪花位置是隨機的[0,w],但是有個距離,這個距離內只有一個雪花
c.下一行雪花依賴上一行雪花的生成,每個雪花在可以飄動的時候,只能在[-1,1]範圍內
d.實現動畫效果就是每畫一幀就等待一段時間
下面看具體一點的a
//建立一個螢幕物件scr = screen_create(_INT_FRATE, _INT_WIDTH, _INT_HEIGHT);
scr物件就是我們的建立螢幕. _INT_WIDTH和 _INT_HEIGHT就是螢幕大小.對於_INT_FRATE表示繪製一幀時間.
b實現程式碼如下:
//構建開頭 的雪花,下面巨集表示每 _INT_SHEAD 個步長,一個雪花,需要是2的冪//static 可以理解為 private, 巨集,位操作程式碼多了確實難讀#define_INT_SHEAD (1<<2)staticvoid__snow_head(char* snow,int len) { intr =0; //資料需要清空memset(snow,0, len); for (;;) { //取餘一個技巧 2^3 - 1 = 7 => 111 , 並就是取餘數intt = rand() & (_INT_SHEAD -1); if(r + t >= len) break; snow[r + t] =1; r += _INT_SHEAD; } }#undef_INT_SHEAD
技巧如上,可以看說明.這裡科普一下,對於for(;;) {}和while(true) {}異同.
for(;;) {}和while(true) {}這兩段程式碼轉成彙編是一樣的,不一樣的是強加的意願.第一個希望跳過檢測步驟速度更快一點.
再擴充套件一點.
//另一種 迴圈語句, goto 還是 很強大實用的__for_loop: if(false) goto __for_break; goto __for_loop; __for_break:
可以再擴充套件深一點,還有一種api比這個goto還NB.有機會分享.特別強大,是異常處理程式本質.
對於c.
//通過 上一個 scr->pix[scr->width*(idx-1)] => scr->pix[scr->width*idx]//下面的巨集 規定 雪花左右搖擺 0 向左一個畫素, 1 表示 不變, 2表示向右一個畫素#define_INT_SWING (3)staticvoid__snow_next(structscreen* scr,int idx) { intwidth = scr->width; char* psnow = scr->pix + width*(idx -1); char* snow = psnow + width; inti, j, t;// i索引, j儲存下一個瞬間雪花的位置,t 臨時補得,解決雪花重疊問題 //為當前行重置memset(snow,0, width); //通過上一次雪花位置 計算下一次雪花位置for(i =0; i for(t = psnow[i]; t>0; --t) {// 雪花可以重疊 // rand()%_INT_SWING - 1 表示 雪花 橫軸的偏移量,相對上一次位置j = i + rand() % _INT_SWING -1; j = j<0? width -1: j >= width ?0: j;// j如果越界了,左邊越界讓它到右邊,右邊越界到左邊++snow[j]; } } }
下一行雪花依賴上一行雪花,這裡有點像插入排序.
整體的繪製程式碼如下
/** * 螢幕繪製函式,主要生成一個雪花效果 * * struct screen* : 螢幕資料 * return : 0表示可以繪製了,1表示圖案不變*/intscreen_draw_snow(structscreen* scr) { // 靜態變數,預設初始化為0,每次都共用staticint__speed =0; int idx; if(++__speed != _INT_VSNOW) return1; //下面 就是 到了雪花飄落的時刻了 既 __speed == _INT_VSNOW__speed =0; //這裡重新構建雪花介面,先構建頭部,再從尾部開始構建for(idx = scr->height -1; idx >0; --idx) __snow_next(scr, idx); //構建頭部__snow_head(scr->pix, scr->width); return0; }
繪製了一個螢幕物件的雪花. __speed記錄繪製次數, _INT_VSNOW控制繪製速率
d的實現程式碼如下
首先實現一個銷燬螢幕程式碼和繪製程式碼
//buf 儲存scr 中pix 資料,構建後為 (width+1)*height, 後面巨集是雪花圖案#define_CHAR_SNOW '*'staticvoid__flash_snow_buffer(structscreen* scr,char* buf) { int i, j, rt; intheight = scr->height, width = scr->width; intfrate = scr->frate;//重新整理的幀頻率 //每次都等一下for (;;sleep_ms(frate)) { //開始繪製螢幕rt = screen_draw_snow(scr); if (rt) continue; for(i =0;i char* snow = scr->pix + i*width; for(j =0; j buf[rt++] = snow[j] ? _CHAR_SNOW :''; buf[rt++] ='\n'; } buf[rt -1] ='\0'; //正式繪製到螢幕上 puts(buf); //清空老螢幕,螢幕游標回到最上面 __curup(height); } }#undef_CHAR_SNOW
這裡sleep_ms(frate);是等待時間,否則太快,人眼看不見.
繪製原理是讓螢幕轉成控制檯能夠認識的字元.塞入到buf中.
__curup(height);讓繪製游標回到開頭.
後面還有一段程式碼實現
/** * 螢幕繪製動畫效果, 繪製雪花動畫 * * struct screen* : 螢幕結構指標*/voidscreen_flash_snow(structscreen* scr) { char* buf = NULL; // 初始化隨機數種子,改變雪花軌跡 srand((unsigned)time(NULL)); buf =malloc(sizeof(char)*(scr->width +1)*scr->height); if(NULL == buf) { cerr("[FATAL]Out of memory!"); exit(EXIT_FAILURE); } __flash_snow_buffer(scr, buf); //1.這裡理論上不會執行到這,沒加控制器. 2.對於buf=NULL,這種程式碼 可以省掉,看程式設計習慣free(buf); buf = NULL; }
這種雙函式實現一個功能技巧用的也很多.例如寫快速排序程式碼,就是這樣.
到這裡我們設計和實現都完成了.
2.程式碼效果展示
2.1window上展示
使用VS新建一個控制檯專案,F5就可以了效果如下
是動態的.
2.2對於Linux
直接使用
gcc -g -Wall snow.c -o snow.out./snow.out
執行效果如下
到這裡,C語言實現雪花效果就如上了.
2.3完整的程式碼展示.感謝有你,一路同行.
#include #include #include #include /** 時間 : 2015年12月26日11:43:22 * 描述 : 應該算過節吧,今天,寫了個雪花特效 程式碼, * 送給 大學啟蒙 蘆老師 * 學生王志 祝福上 * *//** 清除螢幕的shell 命令/控制檯命令,還有一些依賴平臺的實現 * 如果定義了 __GNUC__ 就假定是 使用gcc 編譯器,為Linux平臺 * 否則 認為是 Window 平臺*/#ifdefined(__GNUC__)//下面是依賴 Linux 實現#include #definesleep_ms(m) \ usleep(m *1000)//向上移動游標函式 Linuxstaticvoid__curup(int height) { inti = -1; while(++i printf("\033[1A");//先回到上一行 }#else// 建立等待函式 1s 60 幀 相當於 16.7ms => 1幀, 我們取16ms// 咱麼的這螢幕 推薦 1s 25幀吧 40ms// 這裡建立等待函式 以毫秒為單位 , 需要依賴作業系統實現#include #definesleep_ms(m) \ Sleep(m)//向上移動游標staticvoid__curup(int height) { COORD cr = {0,0}; // GetStdHandle(STD_OUTPUT_HANDLE) 獲取螢幕物件, 設定游標 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cr); }#endif/*__GNUC__ 跨平臺的程式碼都很醜陋 */// 定義初始螢幕的寬高畫素巨集#define_INT_WIDTH (100)#define_INT_HEIGHT (50)// 螢幕重新整理幀的速率#define_INT_FRATE (40)// 雪花飄落的速率,相對於 螢幕重新整理幀 的倍數#define_INT_VSNOW (10)/** 錯誤處理巨集,msg必須是""括起來的字串常量 * __FILE__ : 檔案全路徑 * __func__ : 函式名 * __LINE__ : 行數行 * __VA_ARGS__ : 可變引數巨集, * ##表示直接連線, 例如 a##b <=> ab*/#definecerr(msg,...) \ fprintf(stderr, "[%s:%s:%d]"msg"\n",__FILE__,__func__,__LINE__,##__VA_ARGS__);/** 螢幕結構體, 具有 寬高 * frate : 繪製一幀的週期, 單位是 毫秒 * width : 螢幕的寬,基於視窗的左上角(0,0) * height : 螢幕的高 * pix : 用一維模擬二維 主要結構如下 * 0 0 0 1 0 0 1 0 1 0 * 0 1 0 1 0 1 0 1 2 0 * . . . * => 0表示沒畫素, 1表示1個畫素,2表示2個畫素....*/struct screen { intfrate;// 也可以用 unsigned 結構int width; int height; char*pix; };/** 建立一個 螢幕結構指標 返回 * * int frate : 繪製一幀的週期 * int width : 螢幕寬度 * int height : 螢幕高度 * return : 指向螢幕結構的指標 * */structscreen* screen_create(intfrate,intwidth,int height);/** 銷燬一個 螢幕結構指標, 併為其置空 * struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的 * */voidscreen_destory(structscreen** pscr);/** * 螢幕繪製函式,主要生成一個雪花效果 * * struct screen* : 螢幕資料 * return : 0表示可以繪製了,1表示圖案不變*/intscreen_draw_snow(structscreen* scr);/** * 螢幕繪製動畫效果, 繪製雪花動畫 * * struct screen* : 螢幕結構指標*/voidscreen_flash_snow(structscreen* scr);// 主函式,主業務在此執行intmain(intargc,char*argv[]) { structscreen* scr = NULL; //建立一個螢幕物件scr = screen_create(_INT_FRATE, _INT_WIDTH, _INT_HEIGHT); if(NULL == scr) exit(EXIT_FAILURE); //繪製雪花動畫 screen_flash_snow(scr); //銷燬這個螢幕物件screen_destory(&scr); return0; }/** 建立一個 螢幕結構指標 返回 * * int frate : 繪製一幀的週期 * int width : 螢幕寬度 * int height : 螢幕高度 * return : 指向螢幕結構的指標 * */structscreen* screen_create(intfrate,intwidth,int height) { structscreen *scr = NULL; if(frate<0|| width <=0|| height <=0) { cerr("[WARNING]check is frate<0 || width<=0 || height<=0 err!"); return NULL; } //後面是 為 scr->pix 分配的記憶體 width*heightscr =malloc(sizeof(structscreen) +sizeof(char)*width*height); if(NULL == scr) { cerr("[FATALG]Out of memory!"); return NULL; } scr->frate = frate; scr->width = width; scr->height = height; //減少malloc次數,malloc消耗很大,記憶體洩露呀,記憶體碎片呀scr->pix = ((char*)scr) +sizeof(struct screen); return scr; }/** 銷燬一個 螢幕結構指標, 併為其置空 * struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的 * */voidscreen_destory(structscreen** pscr) { if(NULL == pscr || NULL == *pscr) return; free(*pscr); // 避免野指標*pscr = NULL; }//構建開頭 的雪花,下面巨集表示每 _INT_SHEAD 個步長,一個雪花,需要是2的冪//static 可以理解為 private, 巨集,位操作程式碼多了確實難讀#define_INT_SHEAD (1<<2)staticvoid__snow_head(char* snow,int len) { intr =0; //資料需要清空memset(snow,0, len); for (;;) { //取餘一個技巧 2^3 - 1 = 7 => 111 , 並就是取餘數intt = rand() & (_INT_SHEAD -1); if(r + t >= len) break; snow[r + t] =1; r += _INT_SHEAD; } }#undef_INT_SHEAD//通過 上一個 scr->pix[scr->width*(idx-1)] => scr->pix[scr->width*idx]//下面的巨集 規定 雪花左右搖擺 0 向左一個畫素, 1 表示 不變, 2表示向右一個畫素#define_INT_SWING (3)staticvoid__snow_next(structscreen* scr,int idx) { intwidth = scr->width; char* psnow = scr->pix + width*(idx -1); char* snow = psnow + width; inti, j, t;// i索引, j儲存下一個瞬間雪花的位置,t 臨時補得,解決雪花重疊問題 //為當前行重置memset(snow,0, width); //通過上一次雪花位置 計算下一次雪花位置for(i =0; i for(t = psnow[i]; t>0; --t) {// 雪花可以重疊 // rand()%_INT_SWING - 1 表示 雪花 橫軸的偏移量,相對上一次位置j = i + rand() % _INT_SWING -1; j = j<0? width -1: j >= width ?0: j;// j如果越界了,左邊越界讓它到右邊,右邊越界到左邊++snow[j]; } } }/** * 螢幕繪製函式,主要生成一個雪花效果 * * struct screen* : 螢幕資料 * return : 0表示可以繪製了,1表示圖案不變*/intscreen_draw_snow(structscreen* scr) { // 靜態變數,預設初始化為0,每次都共用staticint__speed =0; int idx; if(++__speed != _INT_VSNOW) return1; //下面 就是 到了雪花飄落的時刻了 既 __speed == _INT_VSNOW__speed =0; //這裡重新構建雪花介面,先構建頭部,再從尾部開始構建for(idx = scr->height -1; idx >0; --idx) __snow_next(scr, idx); //構建頭部__snow_head(scr->pix, scr->width); return0; }//buf 儲存scr 中pix 資料,構建後為 (width+1)*height, 後面巨集是雪花圖案#define_CHAR_SNOW '*'staticvoid__flash_snow_buffer(structscreen* scr,char* buf) { int i, j, rt; intheight = scr->height, width = scr->width; intfrate = scr->frate;//重新整理的幀頻率 //每次都等一下for (;;sleep_ms(frate)) { //開始繪製螢幕rt = screen_draw_snow(scr); if (rt) continue; for(i =0;i char* snow = scr->pix + i*width; for(j =0; j buf[rt++] = snow[j] ? _CHAR_SNOW :''; buf[rt++] ='\n'; } buf[rt -1] ='\0'; //正式繪製到螢幕上 puts(buf); //清空老螢幕,螢幕游標回到最上面 __curup(height); } }#undef_CHAR_SNOW/** * 螢幕繪製動畫效果, 繪製雪花動畫 * * struct screen* : 螢幕結構指標*/voidscreen_flash_snow(structscreen* scr) { char* buf = NULL; // 初始化隨機數種子,改變雪花軌跡 srand((unsigned)time(NULL)); buf =malloc(sizeof(char)*(scr->width +1)*scr->height); if(NULL == buf) { cerr("[FATAL]Out of memory!"); exit(EXIT_FAILURE); } __flash_snow_buffer(scr, buf); //1.這裡理論上不會執行到這,沒加控制器. 2.對於buf=NULL,這種程式碼 可以省掉,看程式設計習慣free(buf); buf = NULL; }
後記
到這裡就結束了,這次分享的比較簡單,有興趣的同學可以看看,推薦寫一遍.程式碼看不懂的時候,多歇歇,看得懂的時候,多寫寫,
就有套路了.歡迎吐槽.錯誤是在所難免的.
這個冬天,雪花很美,(。⌒∇⌒)