1. 程式人生 > 其它 >嵌入式C第一次任務學習記錄

嵌入式C第一次任務學習記錄

技術標籤:嵌入式C經驗分享

一、位運算

位運算主要有六種,如下圖所示:

位運算子作用於,並逐位執行操作。(個人見解:位運算和數值計算不同,位運算針對位進行運算,而一般的C語言中數值計算是直接進行賦值運算的)&、 | 和 ^ (三個主要的位運算)的真值表如下所示:

pqp & qp | qp ^ q
00000
01011
11110
10011

個人而言覺得可以這麼理解:位運算與相當於乘法,位運算或相當於加法,位運算異或相當於一個比較(如果相同,則值為0,若不同,則值為1),位運算取反的話就相當於一種全部變換(0變成1,1變成0),位運算左移右移本質上是一樣的,相當於將原來的編碼進行整體向左平移或者向右平移。

如下是一個非常便於理解的例子:

假設如果 A = 60,且 B = 13,現在以二進位制格式表示,它們如下所示:

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A = 1100 0011

下表顯示了 C 語言支援的位運算子。假設變數A的值為 60,變數B的值為 13,則:

運算子描述例項
&

按位與操作,按二進位制位進行"與"運算。運算規則:

0&0=0;   
0&1=0;    
1&0=0;     
1&1=1;
(A & B) 將得到 12(十進位制數值),即為 0000 1100
|

按位或運算子,按二進位制位進行"或"運算。運算規則:

0|0=0;   
0|1=1;   
1|0=1;    
1|1=1;
(A | B) 將得到 61(十進位制數值),即為 0011 1101
^

異或運算子,按二進位制位進行"異或"運算。運算規則:

0^0=0;   
0^1=1;   
1^0=1;  
1^1=0;
(A ^ B) 將得到 49(十進位制數值),即為 0011 0001
~

取反運算子,按二進位制位進行"取反"運算。運算規則:

~1=0;   
~0=1;
(~A ) 將得到 -61(十進位制數值),即為 1100 0011,一個有符號二進位制數的補碼形式。
<<二進位制左移運算子。將一個運算物件的各二進位制位全部左移若干位(左邊的二進位制位丟棄,右邊補0)。A << 2 將得到 240(十進位制數值),即為 1111 0000
>>二進位制右移運算子。將一個數的各二進位制位全部右移若干位,正數左補0,負數左補1,右邊丟棄。A >> 2 將得到 15(十進位制數值),即為 0000 1111

例項:

#include <stdio.h>
 
int main()
{
 
   unsigned int a = 60;    /* 60 = 0011 1100 */  
   unsigned int b = 13;    /* 13 = 0000 1101 */
   int c = 0;           
 
   c = a & b;       /* 12 = 0000 1100 */ 
   printf("Line 1 - c 的值是 %d\n", c );
 
   c = a | b;       /* 61 = 0011 1101 */
   printf("Line 2 - c 的值是 %d\n", c );
 
   c = a ^ b;       /* 49 = 0011 0001 */
   printf("Line 3 - c 的值是 %d\n", c );
 
   c = ~a;          /*-61 = 1100 0011 */
   printf("Line 4 - c 的值是 %d\n", c );
 
   c = a << 2;     /* 240 = 1111 0000 */
   printf("Line 5 - c 的值是 %d\n", c );
 
   c = a >> 2;     /* 15 = 0000 1111 */
   printf("Line 6 - c 的值是 %d\n", c );
}

輸出結果如下:

Line 1 - c 的值是 12
Line 2 - c 的值是 61
Line 3 - c 的值是 49
Line 4 - c 的值是 -61
Line 5 - c 的值是 240
Line 6 - c 的值是 15

參考網址:https://www.runoob.com/cprogramming/c-operators.html

二、static變數

個人感覺:static就是定義一個靜態變數,使之不能夠被全域性呼叫,只起一次作用。

定義:

我們知道在函式內部定義的變數,當程式執行到它的定義處時,編譯器為它在棧上分配空間,函式在棧上分配的空間在此函式執行結束時會釋放掉,這樣就產生了一個問題: 如果想將函式中此變數的值儲存至下一次呼叫時,如何實現? 最容易想到的方法是定義為全域性的變數,但定義一個全域性變數有許多缺點,最明顯的缺點是破壞了此變數的訪問範圍(使得在此函式中定義的變數,不僅僅只受此函式控制)。static 關鍵字則可以很好的解決這個問題。另外,在 C++ 中,需要一個數據物件為整個類而非某個物件服務,同時又力求不破壞類的封裝性,即要求此成員隱藏在類的內部,對外不可見時,可將其定義為靜態資料。

作用:

  • (1)在修飾變數的時候,static 修飾的靜態區域性變數只執行初始化一次,而且延長了區域性變數的生命週期,直到程式執行結束以後才釋放。
  • (2)static 修飾全域性變數的時候,這個全域性變數只能在本檔案中訪問,不能在其它檔案中訪問,即便是 extern 外部宣告也不可以。
  • (3)static 修飾一個函式,則這個函式的只能在本檔案中呼叫,不能被其他檔案呼叫。static 修飾的變數存放在全域性資料區的靜態變數區,包括全域性靜態變數和區域性靜態變數,都在全域性資料區分配記憶體。初始化的時候自動初始化為 0。
  • (4)不想被釋放的時候,可以使用static修飾。比如修飾函式中存放在棧空間的陣列。如果不想讓這個陣列在函式呼叫結束釋放可以使用 static 修飾。
  • (5)考慮到資料安全性(當程式想要使用全域性變數的時候應該先考慮使用 static)。

比較:

靜態全域性變數有以下特點:

  • (1)靜態變數都在全域性資料區分配記憶體,包括後面將要提到的靜態區域性變數;
  • (2)未經初始化的靜態全域性變數會被程式自動初始化為0(在函式體內宣告的自動變數的值是隨機的,除非它被顯式初始化,而在函式體外被宣告的自動變數也會被初始化為 0);
  • (3)靜態全域性變數在宣告它的整個檔案都是可見的,而在檔案之外是不可見的。

優點:靜態全域性變數不能被其它檔案所用;其它檔案中可以定義相同名字的變數,不會發生衝突。

全域性變數和全域性靜態變數的區別

  • (1)全域性變數是不顯式用 static 修飾的全域性變數,全域性變數預設是有外部連結性的,作用域是整個工程,在一個檔案內定義的全域性變數,在另一個檔案中,通過 extern 全域性變數名的宣告,就可以使用全域性變數。
  • (2)全域性靜態變數是顯式用 static 修飾的全域性變數,作用域是宣告此變數所在的檔案,其他的檔案即使用 extern 宣告也不能使用。

靜態區域性變數有以下特點:

  • (1)該變數在全域性資料區分配記憶體;
  • (2)靜態區域性變數在程式執行到該物件的宣告處時被首次初始化,即以後的函式呼叫不再進行初始化;
  • (3)靜態區域性變數一般在宣告處初始化,如果沒有顯式初始化,會被程式自動初始化為 0;
  • (4)它始終駐留在全域性資料區,直到程式執行結束。但其作用域為區域性作用域,當定義它的函式或語句塊結束時,其作用域隨之結束。

一般程式把新產生的動態資料存放在堆區,函式內部的自動變數存放在棧區。自動變數一般會隨著函式的退出而釋放空間,靜態資料(即使是函式內部的靜態區域性變數)也存放在全域性資料區。全域性資料區的資料並不會因為函式的退出而釋放空間。

例項

#include <stdio.h>  
#include <stdlib.h>  
int k1 = 1;
int k2;
static int k3 = 2;
static int k4;
int main()
{
    static int m1 = 2, m2;
    int i = 1;
    char*p;
    char str[10] = "hello";
    char*q = "hello";
    p = (char *)malloc(100);
    free(p);
    printf("棧區-變數地址    i:%p\n", &i);
    printf("棧區-變數地址   p:%p\n", &p);
    printf("棧區-變數地址 str:%p\n", str);
    printf("棧區-變數地址   q:%p\n", &q);
    printf("堆區地址-動態申請:%p\n", p);
    printf("全域性外部有初值 k1:%p\n", &k1);
    printf("   外部無初值 k2:%p\n", &k2);
    printf("靜態外部有初值 k3:%p\n", &k3);
    printf("   外靜無初值 k4:%p\n", &k4);
    printf("  內靜態有初值 m1:%p\n", &m1);
    printf("  內靜態無初值 m2:%p\n", &m2);
    printf("    文字常量地址:%p, %s\n", q, q);
    printf("      程式區地址:%p\n", &main);
    return 0;
}

輸出結果如下:

補充:在類中,static 可以用來修飾靜態資料成員和靜態成員方法。

靜態資料成員

  • (1)靜態資料成員可以實現多個物件之間的資料共享,它是類的所有物件的共享成員,它在記憶體中只佔一份空間,如果改變它的值,則各物件中這個資料成員的值都被改變。

  • (2)靜態資料成員是在程式開始執行時被分配空間,到程式結束之後才釋放,只要類中指定了靜態資料成員,即使不定義物件,也會為靜態資料成員分配空間。

  • (3)靜態資料成員可以被初始化,但是隻能在類體外進行初始化,若未對靜態資料成員賦初值,則編譯器會自動為其初始化為 0。

  • (4)靜態資料成員既可以通過物件名引用,也可以通過類名引用。

靜態成員函式

  • (1)靜態成員函式和靜態資料成員一樣,他們都屬於類的靜態成員,而不是物件成員。

  • (2)非靜態成員函式有 this 指標,而靜態成員函式沒有 this 指標。

  • (3)靜態成員函式主要用來方位靜態資料成員而不能訪問非靜態成員。

總結:

  • (1)靜態成員函式中不能呼叫非靜態成員。

  • (2)非靜態成員函式中可以呼叫靜態成員。因為靜態成員屬於類本身,在類的物件產生之前就已經存在了,所以在非靜態成員函式中是可以呼叫靜態成員的。

  • (3)靜態成員變數使用前必須先初始化(如int MyClass::m_nNumber = 0;),否則會在 linker 時出錯。

參考網址:https://www.runoob.com/w3cnote/cpp-static-usage.html

三、extern變數

個人感覺:extern變數和static變數正好是相反的,static多用於定義靜態變數,使之不能被全域性呼叫,只能單次在一個檔案裡被呼叫;而extern則多用於定義全域性變數,是指可以被全域性呼叫甚至跨檔案呼叫,多次被呼叫

定義:

在定義變數的時候,這個extern居然可以被省略(定義時,預設均省略);在宣告變數的時候,這個extern必須新增在變數前,所以有時會讓你搞不清楚到底是宣告還是定義。或者說,變數前有extern不一定就是宣告,而變數前無extern就只能是定義。注:定義要為變數分配記憶體空間;而宣告不需要為變數分配記憶體空間。

(1)變數

例項:

extern int a; // 宣告一個全域性變數 a
int a; // 定義一個全域性變數 a
extern int a =0 ; // 定義一個全域性變數 a 並給初值。
int a =0; // 定義一個全域性變數 a, 並給初值,

第四個等於第三個,都是定義一個可以被外部使用的全域性變數,並給初值。

糊塗了吧,他們看上去可真像。但是定義只能出現在一處。也就是說,不管是int a;還是extern int a=0;還是int a=0;都只能出現一次,而那個extern int a可以出現很多次。

當你要引用一個全域性變數的時候,你就必須要宣告,extern int a; 這時候extern不能省略,因為省略了,就變成int a;這是一個定義,不是宣告。注:extern int a; 中型別 int 可省略,即 extern a; 但其他型別則不能省略。

(2)函式

對於函式也一樣,也是定義和宣告,定義的時候用extern,說明這個函式是可以被外部引用的,宣告的時候用extern說明這是一個宣告。 但由於函式的定義和宣告是有區別的,定義函式要有函式體,宣告函式沒有函式體(還有以分號結尾),所以函式定義和宣告時都可以將extern省略掉,反正其他檔案也是知道這個函式是在其他地方定義的,所以不加extern也行。兩者如此不同,所以省略了extern也不會有問題。

例項:

/*某cpp檔案*/
int fun(void)
{
      return 0;
}

很好,我們定義了一個全域性函式:

/*另一cpp檔案*/
int fun(void);

我們對它做了個宣告,然後後面就可以用了, 加不加extern都一樣, 我們也可以把對 fun 的宣告放在一個頭檔案裡,最後變成這樣:

/*fun.h*/
int fun(void); //函式宣告,所以省略了extern,完整些是extern int fun(void);
/*對應的fun.cpp檔案*/
int fun(void)
{
     return 0;
}//一個完整的全域性函式定義,因為有函式體,extern同樣被省略了。

然後,一個要使用你的fun的客戶,把這個標頭檔案包含進去,ok,一個全域性的宣告。沒有問題。但是,,如果是這個客戶要使用全域性變數,那麼要extern 某某變數,不然就成了定義了。

總結:

對變數而言,如果你想在本原始檔(例如檔名A)中使用另一個原始檔(例如檔名B)的變數,方法有2種:(1)在A檔案中必須用extern宣告在B檔案中定義的變數(當然是全域性變數);(2)在A檔案中新增B檔案對應的標頭檔案,當然這個標頭檔案包含B檔案中的變數宣告,也即在這個標頭檔案中必須用extern宣告該變數,否則,該變數又被定義一次。

對函式而言,如果你想在本原始檔(例如檔名A)中使用另一個原始檔(例如檔名B)的函式,方法有2種:(1)在A檔案中用extern宣告在B檔案中定義的函式(其實,也可省略extern,只需在A檔案中出現B檔案定義函式原型即可);(2)在A檔案中新增B檔案對應的標頭檔案,當然這個標頭檔案包含B檔案中的函式原型,在標頭檔案中函式可以不用加extern。

對上述總結換一種說法:

(a)對於一個檔案中呼叫另一個檔案的全域性變數,因為全域性變數一般定義在原檔案.c中,我們不能用#include包含原始檔而只能包含標頭檔案,所以常用的方法是用extern int a來宣告外部變數。 另外一種方法是可以是在a.c檔案中定義了全域性變數int global_num ,可以在對應的a.h標頭檔案中寫extern int global_num ,這樣其他原始檔可以通過include a.h來宣告她是外部變數就可以了。

(b)還有變數和函式的不同舉例int fun();和extern int fun();都是宣告(定義要有實現體)。 用extern int fun()只是更明確指明是宣告而已。而int a;是定義extern int a;是宣告。

(c)此外,extern修飾符可用於C++程式中呼叫c函式的規範問題。

比如在C++中呼叫C庫函式,就需要在C++程式中用extern "C"宣告要引用的函式。這是給連結器用的,告訴連結器在連結的時候用C函式規範來連結。主要原因是C++和C程式編譯完成後在目的碼中命名規則不同。

C++語言在編譯的時候為了解決的多型問題,會將名和引數聯合起來生成一箇中間的名稱,而c語言則不會,因此會造成連結時找不到對應的情況,此時C就需要用extern "C"進行連結指定,這告訴編譯器,請保持我的名稱,不要給我生成用於連結的中間名。

參考網址:https://www.runoob.com/w3cnote/extern-head-h-different.html

四、關鍵詞const

const 是 constant 的縮寫,本意是不變的,不易改變的意思。在 C++ 中是用來修飾內建型別變數,自定義物件,成員函式,返回值,函式引數。C++ const 允許指定一個語義約束,編譯器會強制實施這個約束,允許程式設計師告訴編譯器某值是保持不變的。如果在程式設計中確實有某個值保持不變,就應該明確使用const,這樣可以獲得編譯器的幫助。(個人感覺:const主要就是設定不變數,不變的量可以是值、指標、返回值)

(1)const修飾普通型別的變數

const int  a = 7; 
int  b = a; // 正確
a = 8; // 錯誤,不能改變

a 被定義為一個常量,並且可以將 a 賦值給 b,但是不能給 a 再次賦值。對一個常量賦值是違法的事情,因為 a 被編譯器認為是一個常量,其值不允許修改。

接著看如下的操作:

例項一

#include<iostream>
 
using namespace std;
 
int main(void)
{
    const int  a = 7;
    int  *p = (int*)&a;
    *p = 8;
    cout<<a;
    system("pause");
    return 0;
}

對於 const 變數 a,我們取變數的地址並轉換賦值給 指向 int 的指標,然後利用 *p = 8; 重新對變數 a 地址內的值賦值,然後輸出檢視 a 的值。

從下面的除錯視窗看到 a 的值被改變為 8,但是輸出的結果仍然是 7。

從結果中我們可以看到,編譯器然後認為 a 的值為一開始定義的 7,所以對 const a 的操作就會產生上面的情況。所以千萬不要輕易對 const 變數設法賦值,這會產生意想不到的行為。

如果不想讓編譯器察覺到上面到對 const 的操作,我們可以在 const 前面加上 volatile 關鍵字。

Volatile 關鍵字跟 const 對應相反,是易變的,容易改變的意思。所以不會被編譯器優化,編譯器也就不會改變對 a 變數的操作。

(2)const 修飾指標變數。

const 修飾指標變數有以下三種情況。

  • A: const 修飾指標指向的內容,則內容為不可變數。

  • B: const 修飾指標,則指標為不可變數。

  • C: const 修飾指標和指標指向的內容,則指標和指標指向的內容都為不可變數。

對於 A:

const int *p = 8;

則指標指向的內容 8 不可改變。簡稱左定值,因為 const 位於 * 號的左邊。

對於 B:

int a = 8;
int* const p = &a;
*p = 9; // 正確
int  b = 7;
p = &b; // 錯誤

對於 const 指標 p 其指向的記憶體地址不能夠被改變,但其內容可以改變。簡稱,右定向。因為 const 位於 * 號的右邊。

對於 C:則是 A 和 B的合併

int a = 8;
const int * const  p = &a;

這時,const p 的指向的內容和指向的記憶體地址都已固定,不可改變。

對於 A,B,C 三種情況,根據 const 位於 * 號的位置不同,我總結三句話便於記憶的話:"左定值,右定向,const修飾不變數"。

(3)const引數傳遞和函式返回值。

對於 const 修飾函式引數可以分為三種情況。

A:值傳遞的 const 修飾傳遞,一般這種情況不需要 const 修飾,因為函式會自動產生臨時變數複製實參值。

例項

#include<iostream>

using namespace std;

void Cpf(const int a)
{
    cout<<a;
    // ++a;  是錯誤的,a 不能被改變
}
int main(void)
 
{
    Cpf(8);
    system("pause");
    return 0;
}

B:當 const 引數為指標時,可以防止指標被意外篡改。

例項

#include<iostream>
 
using namespace std;
 
void Cpf(int *const a)
{
    cout<<*a<<" ";
    *a = 9;
}
 
int main(void)
{
    int a = 8;
    Cpf(&a);
    cout<<a; // a 為 9
    system("pause");
    return 0;
}

C:自定義型別的引數傳遞,需要臨時物件複製引數,對於臨時物件的構造,需要呼叫建構函式,比較浪費時間,因此我們採取 const 外加引用傳遞的方法。並且對於一般的 int、double 等內建型別,我們不採用引用的傳遞方式。

例項

#include<iostream>
 
using namespace std;
 
class Test
{
public:
    Test(){}
    Test(int _m):_cm(_m){}
    int get_cm()const
    {
       return _cm;
    }
 
private:
    int _cm;
};
 
 
 
void Cmf(const Test& _tt)
{
    cout<<_tt.get_cm();
}
 
int main(void)
{
    Test t(8);
    Cmf(t);
    system("pause");
    return 0;
}

結果輸出8

對於 const 修飾函式的返回值。

Const 修飾返回值分三種情況。

A:const 修飾內建型別的返回值,修飾與不修飾返回值作用一樣。

例項

#include<iostream>
 
using namespace std;
 
const int Cmf()
{
    return 1;
}
 
int Cpf()
{
    return 0;
}
 
int main(void)
{
    int _m = Cmf();
    int _n = Cpf();
 
    cout<<_m<<" "<<_n;
    system("pause");
    return 0;
}

B: const 修飾自定義型別的作為返回值,此時返回的值不能作為左值使用,既不能被賦值,也不能被修改。

C: const 修飾返回的指標或者引用,是否返回一個指向 const 的指標,取決於我們想讓使用者幹什麼。

(4)const修飾類成員函式

const 修飾類成員函式,其目的是防止成員函式修改被呼叫物件的值,如果我們不想修改一個呼叫物件的值,所有的成員函式都應當宣告為 const 成員函式。

注意:const 關鍵字不能與 static 關鍵字同時使用,因為 static 關鍵字修飾靜態成員函式,靜態成員函式不含有 this 指標,即不能例項化,const 成員函式必須具體到某一例項。

下面的 get_cm()const; 函式用到了 const 成員函式:

例項

#include<iostream>
 
using namespace std;
 
class Test
{
public:
    Test(){}
    Test(int _m):_cm(_m){}
    int get_cm()const
    {
       return _cm;
    }
 
private:
    int _cm;
};
 
 
 
void Cmf(const Test& _tt)
{
    cout<<_tt.get_cm();
}
 
int main(void)
{
    Test t(8);
    Cmf(t);
    system("pause");
    return 0;
}

如果 get_cm() 去掉 const 修飾,則 Cmf 傳遞的 const _tt 即使沒有改變物件的值,編譯器也認為函式會改變物件的值,所以我們儘量按照要求將所有的不需要改變物件內容的函式都作為 const 成員函式。

如果有個成員函式想修改物件中的某一個成員怎麼辦?這時我們可以使用 mutable 關鍵字修飾這個成員,mutable 的意思也是易變的,容易改變的意思,被 mutable 關鍵字修飾的成員可以處於不斷變化中,如下面的例子。

例項

#include<iostream>

using namespace std;

class Test
{
public:
    Test(int _m,int _t):_cm(_m),_ct(_t){}
    void Kf()const
    {
        ++_cm; // 錯誤
        ++_ct; // 正確
    }
private:
    int _cm;
    mutable int _ct;
};
 
int main(void)
{
    Test t(8,7);
    return 0;
}

這裡我們在 Kf()const 中通過 ++_ct; 修改 _ct 的值,但是通過 ++_cm 修改 _cm 則會報錯。因為 ++_cm 沒有用 mutable 修飾。

參考網址:https://www.runoob.com/w3cnote/cpp-const-keyword.html