天天看點

【轉并修改】讓C程式更高效的10種方法

代碼之美,不僅在于為一個給定問題找到解決方案,而且還在代碼的簡單性、有效性、緊湊性和效率(記憶體)。代碼設計比實際執行更難 。是以,每一個程式員當用C語言程式設計時,都應該記着這些東西。本文向你介紹規範你的C代碼的10種方法。

0. 避免不必要的函數調用

考慮下面的2個函數:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

void

str_print(

char

*str )

{

int

i;

for

( i = 0; i <

strlen

( str ); i++ ) {

printf

(

"%c"

,str[ i ] );

}

}

void

str_print1 (

char

*str )

{

int

len;

len =

strlen

( str );

for

( i = 0; i < len; i++ ) {

printf

(

"%c"

,str[ i ] );

}

}

請注意 這兩個函數的功能相似。然而,第一個函數調用strlen()函數多次,而第二個函數隻調用函數strlen()一次。是以第二個函數性能明顯比第一個好。

注:在上述代碼中其實可以采用另外的方式來寫,效率更高

for (i=0;str[i];i++)
 printf("%c",str[ i ] );
           

這樣str[i]為空自然就跳出了

1、避免不必要的記憶體引用這次我們再用2個例子來對比解釋:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

int

multiply (

int

*num1 ,

int

*num2 )

{

*num1 = *num2;

*num1 += *num2;

return

*num1;

}

int

multiply1 (

int

*num1 ,

int

*num2 )

{

*num1 = 2 * *num2;

return

*num1;

}

同樣,這兩個函數具有類似的功能。所不同的是在第一個函數( 1 for reading *num1 , 2 for reading *num2 and 2 for writing to *num1)有5個記憶體的引用,而在第二個函數是隻有2個記憶體引用(one for reading *num2 and one for writing to *num1)。現在你認為哪一個好些?

2、節約記憶體(記憶體對齊和填充的概念)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

struct

{

char

c;

int

i;

short

s;

}str_1;

struct

{

char

c;

short

s;

int

i;

}str_2;

假設一個字元需要1個位元組,short占用2個位元組和int需要4位元組的記憶體。起初,我們會認為上面定義的結構是相同的,是以占據相同數量的記憶體。然而,而str_1占用12個位元組,第二個結構隻需要8個位元組?這怎麼可能呢?

請注意,在第一個結構,3個不同的4個位元組被配置設定到三種資料類型,而在第二個結構的前4個自己char和short可以被采用,int可以采納在第二個的4個位元組邊界(一共8個位元組)。

3、使用無符号整數,而不是整數的,如果你知道的值将永遠是否定的。

有些處理器可以處理無符号的整數比有符号整數的運算速度要快。(這也是很好的實踐,幫助self-documenting代碼)。

4、在一個邏輯條件語句中常數項永遠在左側。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

int

x = 4;

if

( x = 1 ) {

x = x + 2;

printf

(

"%d"

,x);         

// Output is 3

}

int

x = 4;

if

( 1 = x ) {

x = x + 2;

printf

(

"%d"

,x);  

// Compilation error

}

使用“=”指派運算符,替代“==”相等運算符,這是個常見的輸入錯誤。 常數項放在左側,将産生一個編譯時錯誤,讓你輕松捕獲你的錯誤。注:“=”是指派運算符。 b = 1會設定變量b等于值1。 “==”相等運算符。如果左側等于右側,傳回true,否則傳回false。

5、在可能的情況下使用typedef替代macro。當然有時候你無法避免macro,但是typedef更好。

1 2 3 4 5 6 7

typedef

int

*

INT_PTR

;

INT_PTR

a , b;

# define INT_PTR int*;

INT_PTR

a , b;

在這個宏定義中,a是一個指向整數的指針,而b是隻有一個整數聲明。使用typedef a和b都是 整數的指針。

6、確定聲明和定義是靜态的,除非您希望從不同的檔案中調用該函數。

在同一檔案函數對其他函數可見,才稱之為靜态函數。它限制其他通路内部函數,如果我們希望從外界隐藏該函數。現在我們并不需要為内部函數建立頭檔案,其他看不到該函數。

靜态聲明一個函數的優點包括:

  • A)兩個或兩個以上具有相同名稱的靜态函數,可用于在不同的檔案。
  • B)編譯消耗減少,因為沒有外部符号處理。

讓我們做更好的了解,下面的例子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

static

int

foo (

int

a )

{

}

int

foo (

int

)

int

main()

{

foo();     

// This is not a valid function call as the function foo can only be called by any other function within first_file.c where it is defined.

return

0;

}

注:使用靜态一定要先了解靜态便是優先記憶體配置設定,且不自動回收記憶體,函數如果調用完成不會回收函數所使用的記憶體,這裡要特别注意。

7、使用Memoization,以避免遞歸重複計算

考慮Fibonacci(斐波那契)問題;

Fibonacci問題是可以通過簡單的遞歸方法來解決:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

int

fib ( n )

{

if

( n == 0 || n == 1 ) {

return

1;

}

else

{

return

fib( n - 2 ) + fib ( n - 1 );

}

}

注:在這裡,我們考慮Fibonacci 系列從1開始,是以,該系列看起來:1,1,2,3,5,8,…

【轉并修改】讓C程式更高效的10種方法

注意:從遞歸樹,我們計算fib(3)函數2次,fib(2)函數3次。這是相同函數的重複計算。如果n非常大,fib

這個簡單的技術叫做Memoization,可以被用在遞歸,加強計算速度。

fibonacci 函數Memoization的代碼,應該是下面的這個樣子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

int

calc_fib (

int

n )

{

int

val[ n ] , i;

for

( i = 0; i <=n; i++ ) {

val[ i ] = -1;     

// Value of the first n + 1 terms of the fibonacci terms set to -1

}

val[ 0 ] = 1;              

// Value of fib ( 0 ) is set to 1

val[ 1 ] = 1;          

// Value of fib ( 1 ) is set to 1

return

fib( n , val );

}

int

fib(

int

n ,

int

* value )

{

if

( value[ n ] != -1 ) {

return

value[ n ];             

// Using memoization

}

else

{

value[ n ] = fib( n - 2 , value ) + fib ( n - 1 , value );         

// Computing the fibonacci term

}

return

value[ n ];               

// Returning the value

}

這裡calc_fib( n )函數被main()調用。

注:這裡原作者寫的比較複雜...其實采用滾動數組,或者矩陣的話能便是更短的代碼和更好的效率。

8、避免懸空指針和野指針

一個指針的指向對象已被删除,那麼就成了懸空指針。野指針是那些未初始化的指針,需要注意的是野指針不指向任何特定的記憶體位置。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

void

dangling_example()

{

int

*dp =

malloc

(

sizeof

(

int

));

free

( dp );            

// dp is now a dangling pointer

dp = NULL;     

// dp is no longer a dangling pointer

}

void

wild_example()

{

int

*ptr;      

// Uninitialized pointer

printf

(

"%u"

\n",ptr );

printf

(

"%d"

,*ptr );

}

當遭遇這些指針,程式通常是”怪異“的表現。

9、 永遠記住釋放你配置設定給程式的任何記憶體。上面的例子就是如果釋放dp指針(我們使用malloc()函數調用)。

原文:fortystones  譯文:oschina  注釋:驟風

c