數組 算法
開發中數組是一種常見的數據結構,固然咱們知道數組至關於一種容器,可是它不只僅只能存數值和字符,同時它還能夠存放函數的入口地址,以及結構體的數據。 編程
typedef struct _value 數組
{ 安全
int val_1; 數據結構
int val_2; 多線程
}VALUE; 函數
typedef struct _table 性能
{ 優化
char index; this
char data[100];
int (*UniSetFunc)(VALUE*);
}TABLE;
int add(VALUE *val )
{
int temp = 0;
temp = val->val_1 + val->val_2;
return temp;
}
TABLE page[10]
{
{「 First section 」, 「abcdefghijklmn」, add},
{「Second section」, 「opqrstuvwxyz」, NULL}
};
int main()
{
VALUE AddValue;
AddValue.val_1 = 2;
AddValue.val_2 = 4;
int result = 0;
result = page[0]-> UniSetFunc(&AddValue);
printf(「The Result of add is %d\n」, result);
return 0;
}
此時數組就轉換爲相似於Python語言中的字典的結構,便於後續的開發利用以及追加升級和維護。
代碼分析:首先咱們知道函數的名字能夠作爲函數的入口地址(相似於數組的名錶明數組的地址同樣),因此在TABLE結構體中咱們定義了一個成員函數int (*UniSetFunc)(VALUE*); 此處UniSetFunc做爲函數的入口參數,VALUE*表明函數的形參類型;TABLE類型的數組 page[10]即包含告終構體的數據以及函數的入口地址,能夠經過調用page[0]-> UniSetFunc(&AddValue)來間接地調用add函數並實現AddValue中AddValue.val_1和AddValue.val_1兩個數求和的運算。
內存命中率問題:
爲了能夠提升代碼運行效率,要才充分的利用內存空間,應連續的訪問內存區域。
咱們知道數組在內存中存放的位置是一整塊的區域,而且是連續存放的,對於定義的數組array[2][2]來講,假設array[0][0]的地址爲0x04030,則array[0][1],array[1][0],array[1][1] 的地址分別爲0x04031, 0x04032, 0x04033;提升內存命中率的便是應該儘量的連續的訪問內存空間區域,而非跳躍式的訪問;接下來讓咱們來看一個矩陣相乘的問題。
for (int i = 0; i < 2; ++i)
{
for (int j = 0; j < 2; ++j)
{
for (int k = 0; k < 3; ++k)
{
matrix[i][j] += matrix1[i][k] * matrix2[k][j];
}
}
}
以上代碼是經常使用的將矩陣matrix1與matrix2相乘而後賦值給matrix的方法,即用matrix1矩陣獲得行向量乘以矩陣matrix2的列向量,而後賦值給matrix,這樣因爲矩陣在內存存儲的結構,咱們能夠清楚的知道訪問matrix2的時候並不是採用連續的訪問方式,故內存的命中率較低。接下來咱們看一種高內存命中率的方法。
for (int i = 0; i < 2; ++i)
{
for (int k = 0; k < 3; ++k)
{
for (int j = 0; j < 2; ++j)
{
matrix[i][j] += matrix1[i][k] * matrix2[k][j];
}
}
}
能夠看出代碼僅僅將第二個for循環與第三個for循環交換了位置,而其餘的部分沒有任何變化,然而內存的命中率卻大大的提升了,咱們採用將matrix1與matrix2矩陣內部各原素依次相乘而後再累加的方式,來進行矩陣相乘的目的,這樣在訪問matrix1與matrix2矩陣時沒有發生任何內存未命中的問題,從而提升了內存命中的機率。
volatile,const以及static之間的關係:
const關鍵字爲常量關鍵字,它做用的量爲常量,不容許程序去改變該常量的值,如const int value = 12;此常量value值不容許程序將其改變,在開發的過程const關鍵字會常常用到,爲了防止程序意外的改變某一固定的常量,咱們應及時的給其加上const關鍵字;另外const關鍵字做用於常量時必須直接給常量初始化,由於在整個程序運行大的過程當中不容許對其改變,故必須當即初始化,例如:const int value = 12 是正確的,而const int value; value = 12;這樣的語法是錯誤的! 接下來咱們來研究一個稍微難一點的問題,即常量指針與指針常量。先看一段代碼:
#define SWITCH 1
int main()
{
int val_1 = 5;
int val_2 = 10;
const int *p1 = &val_1;
int const *p2 = &val_1;
int *const p3 = &val_1;
#ifdef SWITCH // This is a switch
*p1 = 20;
*p2 = 21;
*p3 = 22;
#endif
#ifndef SWITCH
p1 = &val_2;
p2 = &val_2;
p3 = &val_2;
#endif
printf("%d\n", *p1);
printf("%d\n", *p2);
printf("%d\n", *p3);
return 0;
}
在cygwin編譯器下執行,咱們能夠看到這樣的錯誤:
從圖中咱們能夠清楚的看到,指針p1與p2僅能讀取val_1中的值爲指針常量,即不能改變它所指的變量的內容,因此*p1 = 20; *p2 = 21;兩條命令是錯誤的!(#ifdef SWITCH … #endif 爲條件編譯即爲宏開關)。而後咱們將#define SWITCH 1 語句給註釋掉,此時將運行第二塊代碼,獲得結果以下:
從錯誤中能夠看出p3爲常量指針,它只能指向一個固定的地址,而不能改變它所指的方向,故p3 = &val_2;的操做是錯誤的,所以正確的代碼以下:
int main()
{
int val_1 = 5;
int val_2 = 10;
const int *p1 = &val_1;
int const *p2 = &val_1;
int *const p3 = &val_1;
printf("Frist\n");
printf("%d\n", *p1);
printf("%d\n", *p2);
printf("%d\n", *p3);
p1 = &val_2;
p2 = &val_2;
*p3 = 22;
printf("Second\n");
printf("%d\n", *p1);
printf("%d\n", *p2);
printf("%d\n", *p3);
return 0;
}
運行的結果爲:
最後終結:常量指針(const int *p或int const *p)表示指針p不能改變它所指向地址裏面所存的值,而能夠改變它所指向的地址;指針常量(int *const p)表示指針p不能改變它所指向的地址,即指針不能改變它所指向的位置,可是能夠改變它所指的位置中的內容。若想要指針既不能改變所指向的位置,又不能改變該處的內容,那麼能夠這樣定義:
const int * const p = &a;或int const *const p = &a; 在定義函數的時候,若該入口參數在程序執行的過程當中不但願被改變,則必定要將該形參用const來修飾,一來這樣能夠防止該段程序將其改變,二來對於形參而言,一個不管是不是const修飾的實參均可以將其傳入const形的形參,而一個const形的實參是沒法傳入非const形的形參中,因此爲了使編譯不出錯在定義函數的時候,必定要將不但願被改變的量用const關鍵字來修飾。
Static關鍵字爲靜態關鍵字,它的做用是將做用的變量存入內存中,而非存入寄存器中(即將變量存入堆中而非棧中),而且該做用的變量僅保存最近一次獲取的值。接下來咱們來看一段代碼。
void countfun ()
{
static int count = 0;
++count;
printf(「This is %d number, enter into this function !\n」, count );
}
int main()
{
for (int i = 0; i < 5; ++i)
{
countfun();
}
return 0;
}
這段代碼的運行結果以下:
而若將除去static關鍵字,則運行的結果以下:
由此咱們能夠清楚的看出,static做用的變量count只會存入當前的結果,所以循環調用countfun( )函數的時候並無重新將count變量置爲0,而是保存了前一次的值。
Static關鍵字在項目中的應用是很普遍的,它不只僅有上述所介紹的特色,同時若想要定義的全局變量在整個工程中僅在當前.C文件中有效時,也應該將這個全局變量用static來修飾,這樣在其餘的文件中是沒法訪問這個變量,從而下降了模塊間的耦合度,提升了模塊的內聚性,防止其餘文件將其改變,從而更加的安全。
volatile關鍵字在嵌入式領域中是十分重要的一個關鍵字,尤爲是在與硬件相關或多線程的編程中更爲重要。volatile關鍵字修飾的變量說明它是能夠隨時發生改變的,咱們不但願編譯器去優化某些代碼的時候,須要將這個變量用volatile關鍵字來修飾,從而程序每次訪問該變量的時候是直接從內存中提取出來,而不是從臨時的寄存器中將該變量的副本給提取出來利用!例如當咱們想要實現某個中斷處理時,其用來作判斷條件的標記位則應該用volatile來修飾,這樣當這個中斷在別的地方被觸發的時候就能夠被實時的檢測到,不至於因爲優化而忽略中斷。接下來咱們看一段代碼:
int main()
{
volatile int i = 10;
int a = i;
printf(「i = %d\n」, a);
__asm
{
mov dword ptr[ebp-4], 0x10
}
int b = i;
printf(「i = %d\n」, b);
return 0;
}
此程序輸出結果爲i = 10;i = 16; 若將volatile關鍵字去掉,則結果爲i = 10;i = 10;
即不加關鍵字會將彙編代碼忽略掉,因此爲了防止代碼優化以及能夠及時檢測到外部程序對該變量的改變,咱們必須將該變量加上volatile關鍵字。咱們知道volatile關鍵字表徵該量是易變的,const關鍵字表明該量是常量不能改變,那麼volatile與const是否能夠一塊兒修飾同一個量呢,是確定的,例如在硬件編程中ROM所存儲的數據是不容許用戶改變的,即指向該數據的指針必須爲常量指針(const int *p = &ram_data),然而開發商卻能夠將其意外的改變,爲了防止ROM的內容被意外的改變時,而用戶程序沒有及時的發現,必須將該量用volatile修飾,因此應這樣定義該指針(volatile const int *p = &rom_data)。
位運算
在數字解碼與編碼的過程當中,位運算的操做是司空見慣的事,同時位運算在提升程序的性能方面也獨佔鰲頭,所以位運算操做是必須要深刻了解的問題。
在乘法以及除法的操做中我可使用未運行來提升代碼的質量,例如:a = a * 16;這種操做徹底能夠替換爲:a = a << 4;咱們知道左移一位至關於將原數乘以2,左移N位則至關於乘以2^N,前提是在沒有發生溢出的狀況下;故上例即至關於將數a左移4位,對於某些乘以非2的整數冪狀況,如 a = a * 9;則能夠改寫爲a = (a << 3) + a; 同理右移至關於除以2的整數冪,固然以上全部狀況都是在沒有發生數據溢出的狀況下,所以位運算操做要格外的當心,不然極有可能發生出錯的狀況。
在數據類型轉換的過程當中也須要作位運算操做,例如咱們想將一個unsigned short類型的數據存入unsigned char類型的數組中,就須要進行位運算,首先分析知道unsigned short佔用16個字節,unsigned char佔用8個字節,想要將大字節的數據存入小字節,必需要對大字節進行分割,即將高8位與低8爲分離開來分別存放,來看實現代碼:
unsigned char * DrawCompo_Pj_BT_Change(unsigned short *subarray)
{
unsigned char temp[500];
(void)_sys_memset(&temp, 0x00, sizeof(temp) );
unsigned short i = 0;
while (subarray[i] != 0x0000)
{
if( (subarray[i] & 0xff00) == 0x0000)
{
temp[i++] = (unsigned char)(subarray[i] & 0x00ff);
}
else
{
temp[i] = (unsigned char)( (subarray[i] & 0xff00) >> 8);
temp[i++] = (unsigned char)(subarray[i] & 0x00ff);
}
}
temp[i] = '\0';
return temp;
}
temp[i] = (unsigned char)( (subarray[i] & 0xff00) >> 8);即取subarray[i]數據的高8位,temp[i++] = (unsigned char)(subarray[i] & 0x00ff);取低8位。這樣就能夠實現將高字節的數據完整的存入到低字節中。
位運算還能夠用來判斷變量的符號,咱們知道對於一個有符號的變量,其最高位爲其符號位,故檢查改變的最高位便可知道該變量爲正仍是爲負。看一段代碼:
int main()
{
short test_data = -12;
if (test_data & 0xF000)
{
printf("This number is negative ");
}
else
{
printf("This number is positive ");
}
return 0;
}
對於想要交換兩個數的值的時候,一般咱們的作法以下:
void swap(int &data1, int &data2)
{
int temp = 0;
temp = data1;
data1 = data2;
data2 = temp;
}
這樣的代碼比較簡單易懂,然而美中不足的是它會產生一個臨時變量temp,接下來咱們用位運算來重寫這個程序;
void swap(int &data1, int &data2)
{
data1 = data1 ^ data2;
data2 = data1 ^ data2;
data1 = data1 ^ data2;
}
從上面的代碼咱們能夠看出少了一個臨時變量,同時也加快了代碼的運行效率。
尾遞歸:
遞歸調用給咱們帶來了不少方便,也簡化了代碼,使程序看起來更加的簡潔和明瞭,但遞歸調用也一般伴隨着一個潛在的危險:出棧,接下來咱們來看一個常見的遞歸。
int factorial(int n)
{
if (n < 1)
{
return 1;
}
else
{
return factorial(n-1)*n;
}
}
一般在求一個數的階乘的時候咱們習慣於採用上述方法,然而分析來看當輸入的n值較大時,factorial(n-1)*n所計算的值會不斷的壓入堆棧,生成不少的臨時變量,等待下一個的值的肯定才得以計算,然而在內存中堆棧的大小是固定的,當輸入的n值很大時,極有可能產生堆棧溢出!所以,有一個好的方法解決這種問題即尾遞歸調用。接下來咱們來看這種強大的算法。
int factorial(int n, int m)
{
if (n < 2)
{
return m;
}
else
{
factorial(n-1, n*m);
}
}
從代碼中能夠看出,經過引入一個新的參數m來存放每次遞歸所產生的值,這樣就避免了每次遞歸都要進行壓棧操做,也就不會產生堆棧溢出的現象,並且普通的遞歸每次遞歸只能等待下一次遞歸獲得的結果後才能繼續運算,而尾遞歸每次執行都會進行運算,一次循環執行完畢便可獲得結果,其時間複雜度爲O(n);