程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 02--C編程細節整理(一),02--c編程細節整理

02--C編程細節整理(一),02--c編程細節整理

編輯:C++入門知識

02--C編程細節整理(一),02--c編程細節整理


用C語言比較多,這篇是平時攢下的。有些內容在工作後可能會很常見,但是不用容易忘,所以就寫篇博客吧。

1.        printf的用法

%*可以用來跳過字符,可以用於未知縮進。像下面一樣.

for(i = 1; i < 10; i++)

{

printf("%*c\r%*c\n",  9 - abs(i - 5), '*', abs(i - 5) + 1, '*');

}

%[]可以用來讀取指定的內容,%[^]可以用來忽略指定內容(正則表達式?)

%m可以不帶參數,輸出產生的錯誤信息

2.        二.關於define的用法

#是將後面所跟隨內容字符串化,##則是將前後內容連接起來

還有linux下各種多層宏定義。。。宏定義可謂博大精深啊。。

3.        .關於setjmp和longjmp

目前在linux多線程的頭文件pthreadtypes.h中包含了此函數的頭文件。

這個主要作用是用來從異常恢復的.也可以做"軟重啟".

因為setjmp把環境變量的設置和棧的值都保存在參數env數組中,當longjmp時,根據其值來恢復setjmp時的位置.

這個env通常是一個全局變量.畢竟這是要跨越函數的.

4.        #include

其實這個東西可以寫在任何的地方,你可以試試將main裡面代碼寫在某個文件中,然後在main中包含這個文件。代碼照樣運行。

5.        .sizeof

當sizeof一個數組名的時候,如果數組和sizeof在同一個代碼塊中,那麼返回數組長,否則返回指針大小。

(當數組名傳遞給函數的時候傳遞的僅僅是個指針)

6.        .volatile

讓每次讀取數據都從內存中讀取,而不是在其他位置。可以防止部分編譯器優化。在嵌入式中和多線程中有所應用。

最近學linux發現還可以用用來描述自己定義的鎖!從而避免了編譯器直接把鎖優化了。

7.        main函數的奇妙用法.

其實main函數是可以遞歸的。

8.        關於||和&&

其實||的優先級比&&低。

9.        如何讓返回值有多重類型?

用union定義一個結構體,那麼就可以選擇返回多種類型了.

10.    為何通常c中的指針大小為4個字節?

一種可能是CPU的地址線只有32根.另一種是由於最大程序限制在4G=2^32B來確定的.

11.    當函數返回double值的時候是80位的.

所以在返回值賦給一個double變量的時候會損失精度.

具體的可以看 http://stackoverflow.com/questions/16888621/why-does-returning-a-floating-point-value-change-its-value

12.    . __cdecl關鍵字

這個關鍵字是用來定義函數參數入棧順序從右向左的.並且我在VC上測試的時候發現是先計算再入棧.這個估計用的很少吧..

剛學AT&T的32位匯編,發現其實是用pushl來將參數逐一入棧,然後彈出傳給函數的.估計加了__cdecl的話就會使用pushl的方式來傳值吧.

貌似這樣就只能是從右到左傳參數給函數啊..嗯..還需學習..

13.    內存對齊

結構體和聯合體中,比如struct{int a;char b; double c};那麼這個結構體大小為16(32位機),不是13.這一點我經常忘掉..但是發現這個錯誤的時候又很容易想起來..

14.    位段

這個是1位1位的來分配的..(原來還以為是按字節來的..)

這個東西剛開始覺得似乎蠻有用.但是似乎用途只有在網絡協議需要這麼斤斤計較..

百度百科上說只能是unsigned和int類型指定,但是我在dev c++上寫c程序,也可以用char類型.

並且這個後面:加的值不能超過原來的類型的大小.

考慮到存儲器的對齊和效率應該應用不多.

15.    關於union的用法

其實這個可以用來顯示一些不能見的內容..比方說將一個指針和一個int定義在一起..如果指針是不能見的,那麼可以通過以int的形式來訪問..

union a{

point A;

};

如果A不能直接訪問,那麼就可以用B來訪問。也算是編譯器的漏洞吧。

我記得C++好像有一種類型指針是這樣的,不允許見到其值.但是可以用int來知道其值.

原先刷題還遇到過一個問題.VC不能用long long類型的於是想用union{char a[8];int a}.這樣來變成long long的..結果printf貌似只能識別到32位長度的大小..

所以還是老老實實去linux下編譯了...

16.    關於typedef與const聯合使用時一個值得注意的地方

typedef char* zifu;

const zifu a;

那麼a是個什麼樣的指針呢?是不能改變所指向地址的值還是不能改變自身?

這個問題我一開是也想錯了...想成用define的效果了...其實上面的定義等價於

char* const a;

也就是說不能改變指針自身,但是可以改變所指向的值..typedef與define相比會擴展一些內容.

17.    .關於typedef的其他容易忘記的方法.

函數指針 typedef int (*)(char ) a;

那麼 a c; c就是一個返回值為int,參數為char類型的函數指針.

避免弄成int (*)(*f)(int ,char)(char)這種一團麻亂的樣子..很容易就看錯的..

申請數組 typedef int int_shuzu[10];

那麼int_shuzu a; a就是一個數組指針了..這種很容易看不懂的..

18.    關於參數傳遞

void fun(a,b,c)

int a,b,c;

{}

你很可能沒見過這種傳值方式,不過它確實是可以編譯成功的。可以少寫幾個int~

19.    assert

C的異常處理宏。最近才發現有這個東西。這個比try--catch簡單多了。用於判斷某個條件是否滿足,不滿足的話跳轉到自己的函數上來顯示信息。用來調試程序很方便的。建議學習~

20.    關於如何把遞歸用函數指針來代替的方式.

這個方法很奇葩..感覺還是能有點用的.

#include <stdio.h>

typedef int (*PFUN)(int);

PFUN ptr[2];

int end(int a){

  return 0;

}

int sum(int a){

  return a + ptr[!!a](a - 1);

}

int main(){

  int T, m;

  scanf("%d", &T);

  while(T--){

    ptr[0] = end;

    ptr[1] = sum;

    scanf("%d", &m);

    printf("%d\n", sum(m));

  }

  return 0;

}

這是通過函數指針加位運算來模仿遞歸...目標是求1+2+...+n..

兩次邏輯取反剛好把大於1的數轉為1,原來為零仍然為零.

這個技巧就當作好玩吧~~

21.    字符串常量

sizeof( 'a' );你猜猜是幾?結果是4!!!!這是C裡面估計很少人注意到的差別..當然也沒什麼用就是了..(C++裡面是1,但是C++還有'aa'這種單引號裡面兩個字符的情況..結果是4)

22.    .強制轉換

這是我看一個opencv程序中發現的。你可能永遠都只將強制轉換用於賦值符號的右邊,但其實在左邊也是可以的.

就向這樣

((float*)(img->imageData + i* img->widthStep))[j]=mapp[i][j];

23.    重定向調試

freopen("文件名","r",stdin);寫在所有從輸入流讀取信息的函數之前。

將標准輸入重定向為文件中的內容。對於那些喜歡刷ACM的人來說,用了這個函數調試真是方便多了啊。

對於stdin這個是操作系統默認分給C程序的3個流指針之一,在linux下貌似文件描述符號0就是指stdin。當然還有1和2,分別是stdout,stderr。

三個指針定義在stdio.h中。

24.    關於指針與int類型

其實int類型可以賦值給指針類型!!

以前一直記得是必須加強制轉換的,但是在一本書上看到居然可以直接這樣!!

只是給個warming!!這對於底層程序員是多麼方便的事情啊!!!

在dev c++和VC上測試通過。還可以換成short*,double*,long*~

int i;

char*p;

i=1354;

p = i;

25.    關於宏函數與逗號表達式

最近看操作系統內核源碼發現的。。

其實逗號表達式和宏是一對好基友啊~不僅讓宏函數的功能更多,還可以讓宏函數帶上返回值!

並且和內聯函數一樣高效率!!

好比說交換a和b兩個值並且返回它們的和的宏函數,可以這樣寫:

#define  swap(a,b,c)   (c=a,a=b,b=c,a+b)

這樣只用保證abc三個變量都是同一類型即可,就不用寫那麼多函數了~~

是不是有點類似C++的模版啊~~~還提高了不少開發效率呢~~~

由於是宏函數,所以就無法用於下面的回調函數中了,不過宏函數本身就這麼方便和高效,這點缺點還是可以容忍的.

26.    .回調函數

原來一直用qsort,但是不知道還有回調函數這一名字.

這就有點像C++中的泛型算法了.可以把判斷的部分通過函數封裝起來.

將判斷方法通過參數的方式傳遞給函數,然後實現泛型~~

void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));

注意最後一個參數就是排序的條件判斷函數,也就是回調函數。

 類似的還有多線程中指定的入口函數。

 

27.    關於_exit和exit的差別

 

_exit終止調用進程,但不關閉文件,不清除輸出緩存,也不調用出口函數。

exit函數將終止調用進程。在退出程序之前,所有文件關閉,緩沖輸出內容.

 

這實在其他地方直接抄來的.在linux下如果fork子進程來做點事的話可能會用到.

28.    .關於int a[0];的用法。

 

原來學c的時候弄過這個,但只是當作實驗。。沒想到還真的有人會用這個東西。

主要用來當作內存地址的標簽用。可以通過編譯器來記住指針地址,但是不會消耗實際內存。

只有當分配空間之後才能使用。這是得多麼節約空間才會這麼寫啊。。。。

注意,這貌似是C99標准才支持的一個用法,並且 gcc編譯的時候需要帶上-pedantic選項。

29.    關於unsigned整數做循環變量

unsigned  0-1答案的二進制碼是全一啊!!!

原來寫單片機程序就被坑過..編譯器雖然會警告,但是通常都被我忽略了...

記住,只要是for中的循環變量比較,要麼全是有符號的,要麼全是無符號的!!

30.    關於為什麼要使用二進制讀文件和寫文件

這是學python的時候曉得的。。當你的文件保存為二進制的話,就可以用你的方法來解析文件。

如果不用二進制,那麼就得看操作系統了。所以,對於保存獨有的數據,多用二進制保存吧。話說數據庫的數據文件都是這種方式讀寫文件的吧。

而且二進制也不容被人了解。。好多游戲的文件都是明文的呢。。。

31.    .sizeof(表達式)的問題

sizeof的表達式並不計算!!!只得出是什麼類型的而已!!

int a=1;

printf("%d\n",sizeof(a=2));

printf("%d\n",a);

結果是4,1

測試環境dev c++

32.    三目運算符與if/else哪個效率高?

個人在gcc下測試,發現匯編沒有差別。不知道其他平台是否也是這樣。當然我測試的方式可能不對。下文還會再說三目運算符的。

在CSAPP中說gcc會優化三目運算符。所以估計在某些特定情況下效率比if高點。

33.    .一種簡單的宏交換

 

#define  swap(a,b)   (a ^= b^= a^= b)

 

多麼簡潔啊~雖然以前也寫過這種異或交換,但是從來想過還能用連等啊~~

34.    long double

這是C99標准中的新類型.在<深入理解計算機系統>裡面說是擴展浮點數.有80個二進制位.

這80位剛好對應intel的CPU中的FPU.我的ATT匯編總結中講過這個寄存器,原來以為只是浮點返回值和浮點運算中間過程用到.

現在看來還可以當作變量用.需要注意的是在gcc下一般是會按4字節對齊的,所以不是10個字節,而是12個字節.

35.  關於<符號

這是看CSAPP上的.說<符號對於分支跳轉指令來說預測的准確率並不是特別的好.

所以剛好想到c++中循環的條件判斷用!=的習慣.雖然C++ primer上說是因為迭代器可能在循環中改變的原因,但是從這個來看還是養成寫!=的習慣比較好.

可能得到的效率不一定提高特別多,但是對於高並發高吞吐量的系統來說,我猜這點細微的差別還是有變化的.

還是養成寫!=的習慣吧~

36.   關於三目運算符

一般情況下沒什麼問題,但是在某些編譯器下,對於   xp?*xp:0這種xp是NULL指針的時候就不能用了,否則會造成內存錯誤!

這時因為編譯器對三目運算符進行了優化。先算出後面兩個表達式的結果,然後再來判斷條件。

不過我用gcc測試了一下,發現gcc不是這樣的,所以在gcc下這麼用沒什麼問題。

以後見到這種情況別奇怪就是了~

37.    .關於宏函數中使用 #和##

之前一直不曉得這有什麼實際用處,但是在描述路徑的時候很有用.

好比說你之前文件在inc/現在改成在incc/下.那麼完全可以寫一個函數用來拼接

#define  f(PATH,FILE)   # x##y

這種形式.那麼更換文件目錄的時候就很方便了.

38.    .關於定義多個同名全局變量

在同一文件中是不能定義多個同名全局變量的,但是在多個文件中就可以。

CSAPP上寫的.gcc的連接器在linking的時候額會把全局變量分為弱和強兩種狀態.

並且優先選擇強狀態,但可能類型會存在問題

好比在A文件如下定義了x和y

void other();

int x=112233;

int y=332211;

 

int main(){

     prntf("x=0x%x  y=0x%x \n",x,y);

     other();

     prntf("x=0x%x  y=0x%x \n",x,y);

}

在另一個文件中也定義了x,但類型是double

double x;

 

void other(){

   x=-0.0;

}

然後gcc 兩個文件。有一個警告。暫時先不管。

你運行了之後會發現,y的值居然變了!

也就是說,在other中,仍然認為x是double類型的!

double由於是8字節而int是4字節,給x賦值會覆蓋原來的y!

表示如果other函數中換成純粹的0.那麼y就是0!

39.    .restrict關鍵字

這個常常忘記是什麼意思...是C99新加的標准.讓編譯器翻譯的時候,對其內存的操作只能通過其指針來完成.方便編譯器優化.

不過C++裡面好像還沒有這個關鍵字.

40.    .const與define定義一個常量的區別

const指定的是一個變量,而define相當於直接替換.

const是可以帶指定類型的,define則是默認類型的.

同時const定義的變量還可以用取地址符&來得到其值.

41.    關於GCC內聯匯編

用一般的asm volatile()形式的話,在默認情況下是沒什麼問題的。但是如果在編譯條件中加上了-std=c99的話則不行。

具體我也不清楚怎麼搞。。沒查到什麼有用的東西。自己試過在__asm__volatile_和__asm volatile這些形式都不行。

有高手會的話還請指教!

42.    .關於GNU C的__attribute__

這個是GNU C的一個特色。可以設置函數,變量,以及類型的屬性值。通常不用管這個東西,但是有一些軟件有要求會需要用到這個。

具體我就不展開了,百度可以搜出很多內容。這裡提一個__attribute__(( aligned(n))).在結構體後面加上後可以按照n字節對齊。默認按最大變量字節對齊。

43.    .關於可變參數宏的實現(va_arg).

有一部分靠的是編譯器支持"..."這種參數形式,然後保證不等長參數都可以完全入棧。實際上,根據C標准的入棧規則,參數是依次從右往左入棧的。只要順著esp寄存器來找,那麼就可以依次讀出參數的其值。需要注意的是讀出多少個字節,不然棧就亂了。所以必須在讀出的時候加上參數類型。printf實現中也是先根據字符串中的類型再讀的。我個人實測後確實如此~

 

測試結果如下,Linux下gcc編譯。

 

說明: http://img2.tuicool.com/AN7Fr2E.png!web

如圖所示,顯然可以看到printf中實現的算法是先根據字符串對應的格式來從內存中讀取的。也就是說如果類型選錯了,內容顯示一定亂了。

44.    關於返回結構體

最近看編譯器實現方面的東西,貌似C++那種傳遞性的使用(如cout<< xxx<<<xx;這種)應該是不難實現的。然後我就上測試了下,我發現GCC下函數返回結構體是可以直接在後面加上.成員來訪問成員的。。結果如下

說明: http://img1.tuicool.com/ERVZFn.png!web

45.    關於**指針和(*)[]指針

我發現很多人還是不清楚**指針和(*)[]指針的差別。第一個我就叫它雙層指針,第二個叫行指針。有人喜歡把第一個叫二維指針吧,如果這麼叫很容易暈。

實際上前者是一個指向指針的指針,而後者是一個帶長度的指針。前者可以指向後者。後者與一般指針的差別就是你p++的時候是移動多個變量長度!其他指針只移動一個變量長度。這裡的長度說的是存儲變量地址的大小。前者指向一個指針變量的地址,而後者多用於指向一個二維數組的地址。所以傳遞二維數組參數的時候,用後者!

46.    關於%模運算

其實是可以模負數的!但和數學定義不同,對於負數的模仍然帶有符號。

 

 

 

用C語言比較多,這篇是平時攢下的。有些內容在工作後可能會很常見,但是不用容易忘,所以就寫篇博客吧。

1.        printf的用法

%*可以用來跳過字符,可以用於未知縮進。像下面一樣.

for(i = 1; i < 10; i++)

{

printf("%*c\r%*c\n",  9 - abs(i - 5), '*', abs(i - 5) + 1, '*');

}

%[]可以用來讀取指定的內容,%[^]可以用來忽略指定內容(正則表達式?)

%m可以不帶參數,輸出產生的錯誤信息

2.        二.關於define的用法

#是將後面所跟隨內容字符串化,##則是將前後內容連接起來

還有linux下各種多層宏定義。。。宏定義可謂博大精深啊。。

3.        .關於setjmp和longjmp

目前在linux多線程的頭文件pthreadtypes.h中包含了此函數的頭文件。

這個主要作用是用來從異常恢復的.也可以做"軟重啟".

因為setjmp把環境變量的設置和棧的值都保存在參數env數組中,當longjmp時,根據其值來恢復setjmp時的位置.

這個env通常是一個全局變量.畢竟這是要跨越函數的.

4.        #include

其實這個東西可以寫在任何的地方,你可以試試將main裡面代碼寫在某個文件中,然後在main中包含這個文件。代碼照樣運行。

5.        .sizeof

當sizeof一個數組名的時候,如果數組和sizeof在同一個代碼塊中,那麼返回數組長,否則返回指針大小。

(當數組名傳遞給函數的時候傳遞的僅僅是個指針)

6.        .volatile

讓每次讀取數據都從內存中讀取,而不是在其他位置。可以防止部分編譯器優化。在嵌入式中和多線程中有所應用。

最近學linux發現還可以用用來描述自己定義的鎖!從而避免了編譯器直接把鎖優化了。

7.        .main函數的奇妙用法.

其實main函數是可以遞歸的。

8.        .關於||和&&

其實||的優先級比&&低。

9.        ,如何讓返回值有多重類型?

用union定義一個結構體,那麼就可以選擇返回多種類型了.

10.    .為何通常c中的指針大小為4個字節?

一種可能是CPU的地址線只有32根.另一種是由於最大程序限制在4G=2^32B來確定的.

11.    .當函數返回double值的時候是80位的.

所以在返回值賦給一個double變量的時候會損失精度.

具體的可以看 http://stackoverflow.com/questions/16888621/why-does-returning-a-floating-point-value-change-its-value

12.    . __cdecl關鍵字

這個關鍵字是用來定義函數參數入棧順序從右向左的.並且我在VC上測試的時候發現是先計算再入棧.這個估計用的很少吧..

剛學AT&T的32位匯編,發現其實是用pushl來將參數逐一入棧,然後彈出傳給函數的.估計加了__cdecl的話就會使用pushl的方式來傳值吧.

貌似這樣就只能是從右到左傳參數給函數啊..嗯..還需學習..

13.    內存對齊

結構體和聯合體中,比如struct{int a;char b; double c};那麼這個結構體大小為16(32位機),不是13.這一點我經常忘掉..但是發現這個錯誤的時候又很容易想起來..

14.    .位段

這個是1位1位的來分配的..(原來還以為是按字節來的..)

這個東西剛開始覺得似乎蠻有用.但是似乎用途只有在網絡協議需要這麼斤斤計較..

百度百科上說只能是unsigned和int類型指定,但是我在dev c++上寫c程序,也可以用char類型.

並且這個後面:加的值不能超過原來的類型的大小.

考慮到存儲器的對齊和效率應該應用不多.

15.    .關於union的用法

其實這個可以用來顯示一些不能見的內容..比方說將一個指針和一個int定義在一起..如果指針是不能見的,那麼可以通過以int的形式來訪問..

union a{

point A;

};

如果A不能直接訪問,那麼就可以用B來訪問。也算是編譯器的漏洞吧。

我記得C++好像有一種類型指針是這樣的,不允許見到其值.但是可以用int來知道其值.

原先刷題還遇到過一個問題.VC不能用long long類型的於是想用union{char a[8];int a}.這樣來變成long long的..結果printf貌似只能識別到32位長度的大小..

所以還是老老實實去linux下編譯了...

16.    .關於typedef與const聯合使用時一個值得注意的地方

typedef char* zifu;

const zifu a;

那麼a是個什麼樣的指針呢?是不能改變所指向地址的值還是不能改變自身?

這個問題我一開是也想錯了...想成用define的效果了...其實上面的定義等價於

char* const a;

也就是說不能改變指針自身,但是可以改變所指向的值..typedef與define相比會擴展一些內容.

17.    .關於typedef的其他容易忘記的方法.

函數指針 typedef int (*)(char ) a;

那麼 a c; c就是一個返回值為int,參數為char類型的函數指針.

避免弄成int (*)(*f)(int ,char)(char)這種一團麻亂的樣子..很容易就看錯的..

申請數組 typedef int int_shuzu[10];

那麼int_shuzu a; a就是一個數組指針了..這種很容易看不懂的..

18.    .關於參數傳遞

void fun(a,b,c)

int a,b,c;

{}

你很可能沒見過這種傳值方式,不過它確實是可以編譯成功的。可以少寫幾個int~

19.    .assert

C的異常處理宏。最近才發現有這個東西。這個比try--catch簡單多了。用於判斷某個條件是否滿足,不滿足的話跳轉到自己的函數上來顯示信息。用來調試程序很方便的。建議學習~

20.    關於如何把遞歸用函數指針來代替的方式.

這個方法很奇葩..感覺還是能有點用的.

#include <stdio.h>

typedef int (*PFUN)(int);

PFUN ptr[2];

int end(int a){

  return 0;

}

int sum(int a){

  return a + ptr[!!a](a - 1);

}

int main(){

  int T, m;

  scanf("%d", &T);

  while(T--){

    ptr[0] = end;

    ptr[1] = sum;

    scanf("%d", &m);

    printf("%d\n", sum(m));

  }

  return 0;

}

這是通過函數指針加位運算來模仿遞歸...目標是求1+2+...+n..

兩次邏輯取反剛好把大於1的數轉為1,原來為零仍然為零.

這個技巧就當作好玩吧~~

21.    字符串常量

sizeof( 'a' );你猜猜是幾?結果是4!!!!這是C裡面估計很少人注意到的差別..當然也沒什麼用就是了..(C++裡面是1,但是C++還有'aa'這種單引號裡面兩個字符的情況..結果是4)

22.    .強制轉換

這是我看一個opencv程序中發現的。你可能永遠都只將強制轉換用於賦值符號的右邊,但其實在左邊也是可以的.

就向這樣

((float*)(img->imageData + i* img->widthStep))[j]=mapp[i][j];

23.    重定向調試

freopen("文件名","r",stdin);寫在所有從輸入流讀取信息的函數之前。

將標准輸入重定向為文件中的內容。對於那些喜歡刷ACM的人來說,用了這個函數調試真是方便多了啊。

對於stdin這個是操作系統默認分給C程序的3個流指針之一,在linux下貌似文件描述符號0就是指stdin。當然還有1和2,分別是stdout,stderr。

三個指針定義在stdio.h中。

24.    關於指針與int類型

其實int類型可以賦值給指針類型!!

以前一直記得是必須加強制轉換的,但是在一本書上看到居然可以直接這樣!!

只是給個warming!!這對於底層程序員是多麼方便的事情啊!!!

在dev c++和VC上測試通過。還可以換成short*,double*,long*~

int i;

char*p;

i=1354;

p = i;

25.    關於宏函數與逗號表達式

最近看操作系統內核源碼發現的。。

其實逗號表達式和宏是一對好基友啊~不僅讓宏函數的功能更多,還可以讓宏函數帶上返回值!

並且和內聯函數一樣高效率!!

好比說交換a和b兩個值並且返回它們的和的宏函數,可以這樣寫:

#define  swap(a,b,c)   (c=a,a=b,b=c,a+b)

這樣只用保證abc三個變量都是同一類型即可,就不用寫那麼多函數了~~

是不是有點類似C++的模版啊~~~還提高了不少開發效率呢~~~

由於是宏函數,所以就無法用於下面的回調函數中了,不過宏函數本身就這麼方便和高效,這點缺點還是可以容忍的.

26.    .回調函數

原來一直用qsort,但是不知道還有回調函數這一名字.

這就有點像C++中的泛型算法了.可以把判斷的部分通過函數封裝起來.

將判斷方法通過參數的方式傳遞給函數,然後實現泛型~~

void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));

注意最後一個參數就是排序的條件判斷函數,也就是回調函數。

 類似的還有多線程中指定的入口函數。

 

27.    關於_exit和exit的差別

 

_exit終止調用進程,但不關閉文件,不清除輸出緩存,也不調用出口函數。

exit函數將終止調用進程。在退出程序之前,所有文件關閉,緩沖輸出內容.

 

這實在其他地方直接抄來的.在linux下如果fork子進程來做點事的話可能會用到.

28.    .關於int a[0];的用法。

原來學c的時候弄過這個,但只是當作實驗。。沒想到還真的有人會用這個東西。

主要用來當作內存地址的標簽用。可以通過編譯器來記住指針地址,但是不會消耗實際內存。

只有當分配空間之後才能使用。這是得多麼節約空間才會這麼寫啊。。。。

注意,這貌似是C99標准才支持的一個用法,並且 gcc編譯的時候需要帶上-pedantic選項。

29.    關於unsigned整數做循環變量

unsigned  0-1答案的二進制碼是全一啊!!!

原來寫單片機程序就被坑過..編譯器雖然會警告,但是通常都被我忽略了...

記住,只要是for中的循環變量比較,要麼全是有符號的,要麼全是無符號的!!

30.    關於為什麼要使用二進制讀文件和寫文件

這是學python的時候曉得的。。當你的文件保存為二進制的話,就可以用你的方法來解析文件。

如果不用二進制,那麼就得看操作系統了。所以,對於保存獨有的數據,多用二進制保存吧。話說數據庫的數據文件都是這種方式讀寫文件的吧。

而且二進制也不容被人了解。。好多游戲的文件都是明文的呢。。。

31.    .sizeof(表達式)的問題

sizeof的表達式並不計算!!!只得出是什麼類型的而已!!

int a=1;

printf("%d\n",sizeof(a=2));

printf("%d\n",a);

結果是4,1

測試環境dev c++

32.    三目運算符與if/else哪個效率高?

個人在gcc下測試,發現匯編沒有差別。不知道其他平台是否也是這樣。當然我測試的方式可能不對。下文還會再說三目運算符的。

在CSAPP中說gcc會優化三目運算符。所以估計在某些特定情況下效率比if高點。

33.    .一種簡單的宏交換

 

#define  swap(a,b)   (a ^= b^= a^= b)

 

多麼簡潔啊~雖然以前也寫過這種異或交換,但是從來想過還能用連等啊~~

34.    long double

這是C99標准中的新類型.在<深入理解計算機系統>裡面說是擴展浮點數.有80個二進制位.

這80位剛好對應intel的CPU中的FPU.我的ATT匯編總結中講過這個寄存器,原來以為只是浮點返回值和浮點運算中間過程用到.

現在看來還可以當作變量用.需要注意的是在gcc下一般是會按4字節對齊的,所以不是10個字節,而是12個字節.

35.    .關於<符號

這是看CSAPP上的.說<符號對於分支跳轉指令來說預測的准確率並不是特別的好.

所以剛好想到c++中循環的條件判斷用!=的習慣.雖然C++ primer上說是因為迭代器可能在循環中改變的原因,但是從這個來看還是養成寫!=的習慣比較好.

可能得到的效率不一定提高特別多,但是對於高並發高吞吐量的系統來說,我猜這點細微的差別還是有變化的.

還是養成寫!=的習慣吧~

36.    .關於三目運算符

一般情況下沒什麼問題,但是在某些編譯器下,對於   xp?*xp:0這種xp是NULL指針的時候就不能用了,否則會造成內存錯誤!

這時因為編譯器對三目運算符進行了優化。先算出後面兩個表達式的結果,然後再來判斷條件。

不過我用gcc測試了一下,發現gcc不是這樣的,所以在gcc下這麼用沒什麼問題。

以後見到這種情況別奇怪就是了~

37.    .關於宏函數中使用 #和##

之前一直不曉得這有什麼實際用處,但是在描述路徑的時候很有用.

好比說你之前文件在inc/現在改成在incc/下.那麼完全可以寫一個函數用來拼接

#define  f(PATH,FILE)   # x##y

這種形式.那麼更換文件目錄的時候就很方便了.

38.    .關於定義多個同名全局變量

在同一文件中是不能定義多個同名全局變量的,但是在多個文件中就可以。

CSAPP上寫的.gcc的連接器在linking的時候額會把全局變量分為弱和強兩種狀態.

並且優先選擇強狀態,但可能類型會存在問題

好比在A文件如下定義了x和y

void other();

int x=112233;

int y=332211;

 

int main(){

     prntf("x=0x%x  y=0x%x \n",x,y);

     other();

     prntf("x=0x%x  y=0x%x \n",x,y);

}

在另一個文件中也定義了x,但類型是double

double x;

 

void other(){

   x=-0.0;

}

然後gcc 兩個文件。有一個警告。暫時先不管。

你運行了之後會發現,y的值居然變了!

也就是說,在other中,仍然認為x是double類型的!

double由於是8字節而int是4字節,給x賦值會覆蓋原來的y!

表示如果other函數中換成純粹的0.那麼y就是0!

39.    .restrict關鍵字

這個常常忘記是什麼意思...是C99新加的標准.讓編譯器翻譯的時候,對其內存的操作只能通過其指針來完成.方便編譯器優化.

不過C++裡面好像還沒有這個關鍵字.

40.    .const與define定義一個常量的區別

const指定的是一個變量,而define相當於直接替換.

const是可以帶指定類型的,define則是默認類型的.

同時const定義的變量還可以用取地址符&來得到其值.

41.    關於GCC內聯匯編

用一般的asm volatile()形式的話,在默認情況下是沒什麼問題的。但是如果在編譯條件中加上了-std=c99的話則不行。

具體我也不清楚怎麼搞。。沒查到什麼有用的東西。自己試過在__asm__volatile_和__asm volatile這些形式都不行。

有高手會的話還請指教!

42.    .關於GNU C的__attribute__

這個是GNU C的一個特色。可以設置函數,變量,以及類型的屬性值。通常不用管這個東西,但是有一些軟件有要求會需要用到這個。

具體我就不展開了,百度可以搜出很多內容。這裡提一個__attribute__(( aligned(n))).在結構體後面加上後可以按照n字節對齊。默認按最大變量字節對齊。

43.    .關於可變參數宏的實現(va_arg).

有一部分靠的是編譯器支持"..."這種參數形式,然後保證不等長參數都可以完全入棧。實際上,根據C標准的入棧規則,參數是依次從右往左入棧的。只要順著esp寄存器來找,那麼就可以依次讀出參數的其值。需要注意的是讀出多少個字節,不然棧就亂了。所以必須在讀出的時候加上參數類型。printf實現中也是先根據字符串中的類型再讀的。我個人實測後確實如此~

 

測試結果如下,Linux下gcc編譯。

 

說明: http://img2.tuicool.com/AN7Fr2E.png!web

如圖所示,顯然可以看到printf中實現的算法是先根據字符串對應的格式來從內存中讀取的。也就是說如果類型選錯了,內容顯示一定亂了。

44.    關於返回結構體

最近看編譯器實現方面的東西,貌似C++那種傳遞性的使用(如cout<< xxx<<<xx;這種)應該是不難實現的。然後我就上測試了下,我發現GCC下函數返回結構體是可以直接在後面加上.成員來訪問成員的。。結果如下

說明: http://img1.tuicool.com/ERVZFn.png!web

45.    關於**指針和(*)[]指針

我發現很多人還是不清楚**指針和(*)[]指針的差別。第一個我就叫它雙層指針,第二個叫行指針。有人喜歡把第一個叫二維指針吧,如果這麼叫很容易暈。

實際上前者是一個指向指針的指針,而後者是一個帶長度的指針。前者可以指向後者。後者與一般指針的差別就是你p++的時候是移動多個變量長度!其他指針只移動一個變量長度。這裡的長度說的是存儲變量地址的大小。前者指向一個指針變量的地址,而後者多用於指向一個二維數組的地址。所以傳遞二維數組參數的時候,用後者!

46.    關於%模運算

其實是可以模負數的!但和數學定義不同,對於負數的模仍然帶有符號。

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved