C語言進階技術:同事這些操作把我驚呆了!

2021-02-13 C語言與C++編程

#include"xxx.c"咱們先體驗一波#include"xxx.c"文件能不能用:

1//FileName :main 
2#include <stdio.h>
3#include <stdlib.h>
4
5/***************************
6 * .c文件聲明區域 
7 **************************/
8#include"module1.c"
9#include"module2.c"
10
11/***************************
12 * Fuction: main
13 * Author :(最後一個bug) 
14 **************************/
15int main(int argc, char *argv[]) {
16
17    Fuction1(); 
18    Fuction2(); 
19    printf("歡迎關注公眾號:最後一個bug\n");
20    return 0;
21}

1//FileName: Module1.c 
2#include<stdio.h>
3/***************************
4 * Fuction: Fuction1
5 * Author :(最後一個bug) 
6 **************************/
7void Fuction1()
8{
9    printf("Run Fuction1\n");
10} 

1//FileName: Module2.c 
2#include<stdio.h>
3/***************************
4 * Fuction: Fuction2
5 * Author :(最後一個bug) 
6 **************************/
7void Fuction2()
8{
9    printf("Run Fuction2\n");
10} 

看來這波操作可行,似乎還省去了.h文件,之前bug菌說過,分析.h文件的時候直接把.h文件在對應的.c文件中的位置處展開然後進一步分析即可,其實這.c文件也是如此,接著往下看。

1//FileName :main 
2#include <stdio.h>
3#include <stdlib.h>
4
5char * cBug1 = "bugNo1";  //這裡是位置1 
6char * cBug2 = "bugNo2";
7/***************************
8 * .c文件聲明區域 
9 **************************/
10#include"module1.c"
11#include"module2.c"
12
13//char * cBug1 = "bugNo1";//這裡是位置2 
14//char * cBug2 = "bugNo2";
15
16/***************************
17 * Fuction: main
18 * Author :(最後一個bug) 
19 **************************/
20int main(int argc, char *argv[]) {
21
22    Fuction1(); 
23    Fuction2(); 
24    printf("歡迎關注公眾號:最後一個bug\n");
25    return 0;
26}

1//FileName: Module2.c 
2#include<stdio.h>
3/***************************
4 * Fuction: Fuction1
5 * Author :(最後一個bug) 
6 **************************/
7void Fuction1()
8{
9    printf("Run Fuction1\n");
10    printf("%s\n",cBug1);
11} 

1//FileName: Module2.c 
2#include<stdio.h>
3/***************************
4 * Fuction: Fuction2
5 * Author :(最後一個bug) 
6 **************************/
7void Fuction2()
8{
9    printf("Run Fuction2\n");
10    printf("%s\n",cBug2);
11} 

我們在位置1進行兩個變量的定義,成功編譯運行得到如上的結果,符合我們的預期,然而當我們去掉位置1進行位置2的定義,程序卻無法進行編譯,看來跟我們預期在編譯過程中直接展開.c文件是一致的。

這種方式在bug菌的編碼歷史長河中一般只在兩種情況下用到:

有些歷史悠久的項目經過了N多位大佬的蹂躪,說實在的代碼結構上已經非常可怕了,往往每個源文件內容非常之長,為了保持代碼原樣,會採用#include"xxx.c"把這幾的相關文件嵌入進去,也便於自己後期維護。

在前期進行軟體調試的時候可能自己會在不同的文件中安插不同測試功能函數,通過這樣方法可以方便的引入和剔除。

比如說你需要對源文件中的一些靜態變量進行相關的監控處理,然而又不想在本文件中增加測試代碼,於是便可以在#include"xxx.c"中進行測試函數的編寫來供使用,比如 :

1//FileName :main 
2#include <stdio.h>
3#include <stdlib.h>
4
5static int a = 5; 
6/***************************
7 * .c文件聲明區域 
8 **************************/
9#include"module1.c"
10
11/***************************
12 * Fuction: main
13 * Author :(最後一個bug) 
14 **************************/
15int main(int argc, char *argv[]) {
16
17    Fuction1(); 
18    printf("main %d\n",a);
19    printf("歡迎關注公眾號:最後一個bug\n");
20    return 0;
21}

1//FileName: Module2.c 
2#include<stdio.h>
3/***************************
4 * Fuction: Fuction1
5 * Author :(最後一個bug) 
6 **************************/
7void Fuction1()
8{
9    printf("Run Fuction1\n");
10    printf("Fuction1 %d\n",a);
11} 

那麼之前有小夥伴說 : " static的作用域僅僅在對應的文件中 ",通過上面的多個.c文件使用靜態a變量,那麼這位小夥伴表述就不那麼貼切了!大家在正常的開發過程中bug菌還是不建議使用#include"xxx.c",因為在我們程序的設計過程中,.h文件就是一種外部的引用接口,而.c是對應的內部實現,如果濫用#include"xxx.c"有可能造成函數等等的重複定義,同時也對調試相關程序帶來一些困擾,當然如果遊刃有餘就沒啥問題的啦。不過對於喜歡寫長文件的小夥伴來說卻是是福音,把一個長的.c文件分成多個.c文件,這樣至少可以把不知道這種用法的同事面前秀一秀!

voidvoid在大部分小夥伴的程序中都只是用於函數無參數傳入,或者無類型返回。然而我們平時所定義的變量都會有具體的類型,int,float,char等等,那是否有void類型的變量呢?大家可以動手實驗一下,答案是:不行,編譯會出錯。上圖很明顯編譯器不允許定義void類型的變量,變量都是需要佔用一定內存的,既然void表示無類型,編譯器自然也就不知道該為其分配多大的內存,於是造成編譯失敗。雖然void不能直接修飾變量,但是其可以用於修飾指針的指向即無類型指針void*,無類型指針那就有意義了,無類型指針不是一定要指向無類型數據,而是可以指向任意類型的數據。大家其實在使用動態內存分配的使用就已經遇到了void *的使用,來我們一起看看如下幾個標準函數的原型定義:上面這些函數都是與內存操作有關的函數,可能一些小夥伴使用過也不一定知道每個參數的具體類型是什麼,這些void*部分的形參所傳入的實參都是不需要進行強制類型轉化的,所以根本就不需要關注傳入指針所指向的具體類型,然而函數所返回的void *一般都需要通過強制類型轉化為對應的具體類型,除非你最後所傳遞的變量也是void*類型。

1#include <stdio.h>
2#include <stdlib.h>
3#include <malloc.h> 
4
5#define NUM 10
6/*************************************
7 * Fuction:了解一下void*的使用 
8 * Author : (最後一個bug) 
9 *************************************/
10int main(int argc, char *argv[]) {
11    int *p1 = (int *)malloc(NUM*sizeof(int)); 
12    int *p2 = (int *)malloc(NUM*sizeof(int)); 
13    int  i = 0;
14
15    //初始化p1 
16    for(i = 0;i < NUM;i++) 
17    {
18        *(p1+i) = i;
19    }
20    //進行內存copy 
21    memcpy(p2,p1,NUM*sizeof(int));
22
23    //輸出另外一個分配的內存 
24    for(i = 0;i < NUM;i++) 
25    {
26       printf("%d,",*(p2+i)); 
27    }   
28    //釋放內存 
29    free(p1);
30    free(p2);
31    return 0;
32}

為了保持文章的完整性,也許這裡才是作者最想跟大家介紹的,void*既然如此的靈活一定大有用處,如果僅僅只是用來簡單的傳遞參數似乎有點大材小用,我們得把其用到上層的軟體設計上來。在一些項目中經常看到有小夥伴把數據類型轉來轉去,甚至有時候為了一個數據類型的變化還得重新寫一個僅僅數據類型不同的函數,這樣的代碼上萬行代碼指日可待,按下面我們以一個例子來跟大家介紹一種辦法能夠減少數據類型變化所帶來的程序重複代碼的增加。

1#include <stdio.h>
2#include <stdlib.h>
3/********************************** 
4 * Fuction : add
5 * descir :  加法的相關數據及處理辦法 
6 * Author : (最後一個bug)
7 **********************************/ 
8typedef struct _tag_Add
9{
10    int a;
11    int b;
12    int result;
13}sAdd;
14
15void Add(void *param)
16{
17    sAdd *p = (sAdd *) param;
18    p->result = p->a + p->b;
19}
20/********************************** 
21 * Fuction : add
22 * descir :  乘法的相關數據及處理辦法 
23 * Author : (最後一個bug)
24 **********************************/ 
25typedef struct _tag_Mul
26{
27    float a;
28    float b;
29    float result;
30}sMul;
31
32void Mul(void *param)
33{
34    sMul *p = (sMul *) param;
35    p->result = p->a * p->b;
36}
37
38/************************************* 
39 * Fuction : sCal
40 * descir :  公共的調用接口 
41 * Author : (最後一個bug)
42 ************************************/ 
43void sCal(void *param,void *fuc)
44{
45    ((void (*)(void*))fuc)(param);
46}
47
48/********************************** 
49 * Fuction : main
50 * descir : 應用接口實例 
51 * Author : (最後一個bug)
52 **********************************/  
53int main(void)
54{
55    sAdd stAdd;
56    sMul stMul;
57
58    //數據初始化 
59    stAdd.a = 10;
60    stAdd.b = 20;
61
62    stMul.a = 5;
63    stMul.b = 5;
64    //接口直接用 
65    sCal(&stAdd,Add);
66    sCal(&stMul,Mul);
67    //對應的輸出 
68    printf("a + b = %d\n",stAdd.result);
69    printf("a * b = %f\n",stMul.result);
70    printf("公眾號:最後一個bug\n");
71    return 0;
72 } 

上面的例子可能還是無法完全彰顯void*的強悍之處了,不過其主要的作用就是為了隱藏數據類型,大家也可以理解為一種數據類型的抽象處理,這也是面向對象編程的一種體現。大家一定要記得對於一些編程技巧一定要嘗試著去使用,可能達到項目目標的方式有很多種,但是一些好的設計不僅僅會讓你的代碼增色不少,同時也會讓同事們覺得你是一個喜歡專研技術的人。

「 逗號表達式 」 

1#include <stdio.h>
2#include <stdlib.h>
3/******************************************
4 * Fuction: Main 
5 * Descir : 測試一個逗號表達式 
6 * Author :(最後一個bug) 
7 *****************************************/ 
8int main(int argc, char *argv[]) {
9    int Val = 1;
10
11    Val = ++Val,Val+10,Val*10; //逗號表達式 
12
13    printf("Val = %d",Val);
14
15    return 0;
16}

大家首先可以自己算一下最後輸出的結果,然後再去看下面的答案,其實對於逗號表達式的語法規則並不是很難,主要是大家在平時的開發中使用得比較少,一旦經常不使用就容易淡忘。逗號表達式的形式 : 表達式1,表達式2,.,表達式n

可能有部分小夥伴算出的結果是10,主要是沒有考慮其逗號表達式優先級最低,所以第一賦值表達式優先執行。
既然大家平時都用得不多,是不是這個逗號表達式就是多此一舉呢 ? C發展這麼多年,如果真的沒有價值估計早就不存在了吧,所以還是要秉承著"存在即是合理"的態度看待逗號表達式。

    

大家在平時閱讀代碼的時候應該都是按照從左至右,然後從上至下來的方式吧。基本上一個分號結束一行的書寫,由於電腦屏幕的限制,有效代碼暴露在人的視野中是有限的,同時人瞬間記憶時間也是有限的,如果在一個小小的屏幕上閱碼勢必會阻礙程式設計師的閱讀和理解,比如下面兩種書寫方式:

1/******************************************
2 * Fuction: 非逗號表達式書寫 
3 * Descir : 
4 * Author :(最後一個bug) 
5 *****************************************/ 
6if(IsOk())
7{
8    sOkProc();
9    return GetOkCode(); 
10} 
11else
12{
13    sNoProc();
14    return GetNoCode(); 
15}
16/******************************************
17 * Fuction: 採用逗號表達式書寫 
18 * Descir : 
19 * Author :(最後一個bug) 
20 *****************************************/ 
21return (IsOk())?(sOkProc(),GetOkCode()):(sNoProc(),GetNoCode());

上面是兩種代碼書寫方式,第一種佔據了多行,而第二種進佔據一行,這樣同樣一個屏幕所容納的有效代碼第一種就明顯少於第二種方式,所以很多程式設計師都會選擇使用一種大長屏或者多屏進行開發。

第二種方式似乎很多小夥伴覺得代碼不夠美觀,也不便於維護,其實這僅僅只是一種習慣罷了,就好像編碼的時候 : 第一個大括號是否需要另外起一行,或者是使用==號一定要像if( 1== b)這樣把數據放左邊,當你習慣了這種編碼風格也會覺得用第二方式來得直接。

下面為大家介紹幾個用逗號表示式比較多的地方:

1#include <stdio.h>
2#include <stdlib.h>
3#define  ROW_NUM  (5)
4#define  LINE_NUM (5) 
5/******************************************
6 * Fuction: Main 
7 * Descir :for 遍歷查找 
8 * Author :(最後一個bug) 
9 *****************************************/ 
10int main(int argc, char *argv[]) {
11    int i = 0,j = 0;
12    int Matrix[ROW_NUM][LINE_NUM] ={{1,1,1,1,1},\
13                                    {2,2,2,2,2},\
14                                    {3,3,3,3,3},\
15                                    {4,4,4,4,4},\
16                                    {5,5,5,5,5},\
17                                    };
18
19    for(i = 0,j = 0;(i < ROW_NUM)&&(j < LINE_NUM);i++,j += 2) 
20    {
21         printf("Matrix[%d][%d] = %d\n",i,j,Matrix[i][j]);
22    }
23    printf("公眾號:最後一個bug\n");
24    return 0;
25}

大家應該都知道++在前先執行自加,然後再進行相應處理,而++在後則相反,那麼我們可以使用逗號運算符優先級最低的特點來弱化該問題,避免編碼出現bug。

1#include <stdio.h>
2#include <stdlib.h>
3/******************************************
4 * Fuction: Main 
5 * Descir :弱化++前後問題 
6 * Author :(最後一個bug) 
7 *****************************************/ 
8int main(int argc, char *argv[]) {
9    int i = 0;
10
11    //1、常規操作
12    i = 0;
13    while(++i < 3)
14    {
15        printf(" i = %d\n",i);
16    }
17    printf("*****************\n");
18
19    i = 0;
20    while(i++ < 3)
21    {
22        printf(" i = %d\n",i);
23    }
24    printf("*****************\n");
25
26    //2、逗號表達式處理一下
27    i = 0;
28    while( i++,i < 3)
29    {
30        printf(" i = %d\n",i);
31    }
32    printf("*****************\n");
33
34    i = 0;
35    while( ++i,i < 3)
36    {
37        printf(" i = %d\n",i);
38    }
39    printf("*****************\n");
40
41    printf("公眾號:最後一個bug\n");
42    return 0;
43}
44

1#include <stdio.h>
2#include <stdlib.h>
3
4#define  GET_INDEX(a ,b)  ( a+= 2,a + b)
5/******************************************
6 * Fuction: Main 
7 * Descir : 簡化宏 
8 * Author :(最後一個bug) 
9 *****************************************/ 
10int main(int argc, char *argv[]) {
11    int i = 0,Val = 0;
12    int Param1 = 0, Param2 = 0;
13    int Matrix[5] ={5,5,5,5,5};
14
15    printf(" Matrix = %d\n",Matrix[GET_INDEX(Param1,Param2)]);
16    printf("公眾號:最後一個bug\n");
17    return 0;
18}


逗號表達式其實就是橫向編碼的一種方式,能夠讓程式設計師更好的利用一行的空間,使得代碼更加緊湊,所以使用逗號表達式並沒炫技,而是增強了代碼的靈活度,不過話說回來逗號表達式在C混亂編碼大賽上的使用頻度是非常之高的。


分享C/C++技術文章

相關焦點

  • 初學者:如何學好C語言?
    不幸的是,學校通常會幫你指定一本很差勁的C語言課本;而幸運的是,你還可以再次選擇。大名鼎鼎的譚浩強教授出了一本《C語言程序設計》,據說發行量有超過400萬,據我所知,很多學校都會推薦這本書作為C語言課本。雖然本人的名字(譚浩宇)跟教授僅僅一字之差,但我是無比堅定地黑他這本書的。這本書不是寫給計算機專業的學生的,而是給那些需要考計算機等級考試的其它專業學生看的。
  • C語言基礎:第一個最簡單程序——Hello World!
    我是先完成的《C語言深處》再寫的《C語言基礎》。很多朋友看過了《C語言深處》後向我反映:直接看這個系列覺得很難,不好理解。並希望我能編寫一個關於C語言編程基礎的系列文章。所以我後來才再寫的《C語言基礎》,這個系列借鑑了很多優秀的C語言教材,比如Stephen Prata的《C Primer Plus》和Brian W.
  • 漫畫 | C語言哭了,過年回家,只有我還沒對象
    Python端著酒杯來到C語言身邊。好不容易熬到聚餐結束,C語言鬱悶地回到了冷冷清清的家中。C語言很聰明,很快看懂了。這裡定義了一個叫做Shape的結構體,外界只能通過相關的函數來對這個Shape進行操作,例如創建(Shape_create), 移動(Shape_move),等,不能直接訪問Shape的內部數據結構。 雖然這裡沒有class這樣的關鍵字,數據結構和相關操作是分開寫的,看起來不太完美, 但確實是實現了封裝。
  • C語言:優雅的字符串函數庫
    以學習一門語言為例:大多數人都持有一種觀念,要真正學好一門語言必須得去所學語言當地學習或生活一段時間。而事實上,大多數人都沒有這樣的學習條件。例如:用新語言寫代碼注釋 / commit message / README / issue;對了,我作為英文的愛好者,一直想重啟我的英文學習之路,後續想在公眾號裡記錄一些英文相關的知識,請你們不要笑話我~~~二、字符串函數庫:Simple
  • 春之櫻日語日語初級進階課程N4
    春之櫻教育自創立「春之櫻日語」這一品牌以來,進入日語教育培訓領域,一直致力於日語語言培訓以及日本文化的宣傳。
  • C語言-搶紅包軟體原理
    //C語言-搶紅包軟體原理//講課老師:範志軍  QQ:208824435#include <stdio.h>#include <stdlib.h>#include <time.h>int main(){ float a[11];//保存10個紅包金額
  • 姐姐問我什麼是變基操作(git-rebase)
    :我要丟棄該commit(縮寫:d)label:用名稱標記當前HEAD(縮寫:l)reset:將HEAD重置為標籤(縮寫:t)merge:創建一個合併分支並使用原版分支的commit的注釋(縮寫:m)根據這些指令,我們可以進行修改,如下:
  • Python 炫技操作:條件語句的七種寫法
    ,這點我非常贊同。Python 語言裡有許多(而且是越來越多)的高級特性,是 Python 發燒友們非常喜歡的。在這些人的眼裡,能夠寫出那些一般開發者看不懂的高級特性,就是高手,就是大神。本文我將寫一寫很簡單的條件判斷語句裡那些炫技操作,在這裡,如果你是 Python 發燒友,你可以學到一些寫出超酷的代碼書寫技巧,但學習歸學習,希望你區分場景使用。
  • R語言:天氣數據抓取RNCEP簡介
    之前提到過每三年都要重新審視自己職業規劃的前同事Dr.
  • Go語言無孔不入的2016:躋身主流程式語言、國內大熱、極速提升、尖端應用……
    N多初創網際網路企業都選用Go語言作為他們的基礎技術棧。我還發現,已經有在大數據、機器人等尖端科技領域耕耘的國內公司開始使用Go語言。這門語言現在已經是無孔不入了。遙想去年的1.5版本,Go運行時系統和標準庫剛完成去C化,轉而完全由Go語言和彙編語言重寫。到現在,Go的源碼已有了較大的改進,Go語言版本的Go語言也更加成熟了。
  • 神奇的C語言:一段令人百思不得其解的代碼
    今天在翻閱頭條時,無意看到這樣一篇微頭條,說是作者的同事在工作中遇到一個BUG,兩人一起研究了大半天,非但沒有解決問題,反而一起陷入了鬱悶,甚至三觀盡毀
  • SQL語言基礎:資料庫語言概念介紹
    2.2 資料庫操縱語言(DML)用來表示用戶對資料庫的操作請求,功能主要包括查詢資料庫的查詢、刪除、修改、新增功能。DML過程性語言:要求用戶只要說明資料庫中的什麼數據,也要說明怎樣檢索這些數據。DML非過程性語言:只需要用戶說明資料庫需要什麼數據,不必關心怎麼檢索數據。特點:易學習、容易理解。但非過程性語言產生的處理程序產生的代碼效率低,可以通過查詢優化解決。
  • 【日語快進階】「行けたら行く」究竟是去還是不去?
    日語中有很多曖昧性的語言,你問日本人一個問題,他曖昧的回答總會讓你撓頭想問到底什麼意思?
  • Skype實時語音翻譯技術:掃清語言障礙
    2014年12月份,Skype推出了英語和西班牙語的實時語音翻譯功能,自此,兩種語言進行無障礙切換成為現實。今天就讓我們走近Skype 實時語音翻譯技術。在Skype上應用實時語音翻譯技術時主要包括以下三個階段:假設A、B來自不同國家,雙方通過Skype進行交流。首先,當A開始講話時,Skype會利用語音識別功能,識別語言形成文本;接著,軟體會應用機器學習技術,在後臺將A語言文本翻譯成B語言文本;最後運用文本發聲技術朗讀給B聽。就這樣,雙方在語言不通的情況下實現了無障礙交流。
  • C語言入門經典:必背18個經典程序
    C語言必背18個經典程序1、/*輸出9*9口訣。共9行9列,i控制行,j控制列。
  • 進階操作:國內的那些手遊動畫化
    陰陽師·平安物語以上列舉的這些遊戲,都可以稱得上國產手遊中的佼佼者,仔細觀察這些已經定檔的動畫,不難發現他們都有一個共同點,都是由國內動畫製作廠商生產的3-6分鐘左右的短篇動畫,俗稱「泡麵番」。碧藍航線至於國內的遊戲改編動畫為何大多都採用泡麵番形式,我個人推測,拋去技術成本這些因素外,其重點在於對動畫化的一個定位,對於《崩壞3》、《陰陽師》這些遊戲而言,動畫只是對於原IP的一個價值提升,作為一個衍生作品來豐富主體的內容
  • 6個變態的Hello world C語言程序
    ++]); } #include<stdio.h> #define __(a) goto a; #define ___(a) putchar(a); #define _(a,b) ___(a) __(b); main() { _:__(t)a:_('r',g)b:_('$',p) c:
  • 三國殺:技術流武將!這些武將操作難度滿星,新手上路一定要謹慎
    ,但是如果新手選到這幾個武將基本就可以涼涼了,他們可個個都是技術流武將,沒有一定操作拿不下。第二位:虞翻這個曾經被譽為最難操作的武將之一強度並不大,但是操作好了就可以天秀一番,尤其是縱懸這個技能,如果發揮的好給隊友回血上裝備完全不在話下,甚至還能看下家牌,這不就相當於擁有了透視眼。
  • 【狩獵進階】勇者的遊戲,狩獵有多險?(上篇)
    北美狩獵微信號:NorthAmericaHunting (←長按複製)【狩獵進階】狩獵第一常識,看狩獵進階!