嵌入式C語言實戰開發詳解(一)
一、概述
1、嵌入式開發中為什麼選擇C語言?
因為作業系統的核心都是使用的C語言,而且C語言也有如下幾個優點:
(1)出色的移植性,能在多種不同體系結構的軟/硬平臺上執行(修改量越小,移植性越好);
(2)簡潔緊湊,使用靈活的語法機制,並能直接訪問硬體(效率高);
(3)很高的執行效率
拓展:
什麼時候使用匯編什麼時候使用C語言呢?(C VS 彙編)
彙編是低階語言,不能實現複雜的功能,所以:
當對硬體做初始化——彙編
當對硬體做複雜操作——C語言
面向過程處理機制 VS 面向物件處理機制(詳情戳網址)
http://blog.csdn.net/wzhcalex/article/details/51878170
2、嵌入式開發中的地位:
(1)嵌入式Linux應用軟體開發工程設計;
(2)嵌入式Linux驅動開發工程師;
(3)嵌入式BSP開發工程師;
(4)嵌入式Kernel(核心)開發工程師;
3、精通C語言考核標準:
(1)企業筆試題;
(2)累積的程式碼量(強化程式設計訓練)
(3)良好的編碼規範(華為的編碼規範要求);
(4)行業應用的專案經驗;
4、如何學習C語言(外功與內功兼修)
(1)零基礎學習經歷過程(菜鳥如何修煉成老鳥)
(2)演算法在C語言開發
(3)《C和指標》《C語言專家程式設計》《程式設計師的自我修養》《高質量C/C++程式設計》《程式設計之美》
5、C語言的標準有哪些?
K&RC、C89、C99、C11
注:
gcc支援的C89,部分相容C99
不同的編譯器標準不一樣
很多編譯器支援的是C89
二、資料型別
1、什麼是資料型別?
資料集合的劃分,不同的資料型別對CPU的意義是不一樣的。
2、資料型別有哪些?
3、左右法則
右左法則:首先從最裡面的圓括號內未定義的識別符號開始閱讀看起,然後往右看,再往左看。每當遇到圓括號時,就應該掉轉閱讀方向。一旦解析完圓括號裡面所有的東西,就跳出圓括號。重複這個過程直到整個宣告解析完畢。
企業筆試題:
1、用變數a給出下列定義
a) 一個整型數(An integer):int a;
b) 一個指向整型數的指標(A pointer to an integer):int *a;
c) 一個指向指標的的指標,它指向的指標是指向一個整型數(A pointer to a
pointer to an integer):int **a;
d) 一個有10個整型數的陣列(An array of 10 integers):int a[10];
e) 一個有 10 個指標的陣列,該指標是指向一個整型數的(An array of 10
pointers to integers):int *a[10];
f) 一個指向有10個整型數陣列的指標(A pointer to an array of 10 integers):
int (*a)[10];
g) 一個指向函式的指標,該函式有一個整型引數並返回一個整型數(A pointer
to a function that takes an integer as an argument and returns an integer):
int (*a)(int);
h) 一個有10個指標的陣列,該指標指向一個函式,該函式有一個整型引數並返
回一個整型數( An array of ten pointers to functions that take an integer
argument and return an integer ): int (*a[10])(int).
2、int *(*(*fpl)(int))[10];
fpl:函式指標變數,該函式指標指向一個形參為int,返回值為陣列指標,該陣列指標指向一個整型指標;
int *(*(*arr[5])())();
arr:函式指標陣列,該數組裡的元素指向一個形參為空,返回值為函式指標的函式,該函式指標指向一個形參為空,返回值為整型的指標。
float (*(*b())[ ])();
b:函式,形參為空,返回值為陣列指標,該指標指向一個函式指標陣列,該數組裡的元素指向一個形參為空,返回值為float的函式。
void* (*c)(char, int(*)());
c:函式指標變數,該函式指標指向一個形參為char 、函式指標,返回值為void*,該函式指標的形參為空,返回值為int。
void** (*d)(int *,char **(*)(char *,char **));
d:函式指標變數,指向函式形參為char,函式指標,返回值為void **,該函式指標形參為 char*,char**,返回值為char**;
float(*(*e[10])(int *))[5];
e:函式指標陣列,指向函式形參為 int *,返回值為陣列指標,該陣列元素指向float;
4、隱式型別轉化與強制型別轉化:
char < int < float < double 隱式型別轉化(自動)
程式碼示例:
這就是隱式型別轉換;
強制型別轉化:
程式碼示例:
5、資料型別的重要知識點:
位元組長度:
bit;
位元組 = 8bit;
半字 = 2個位元組 = 16bit;
字 = 4個位元組 = 32;
6、基本資料型別
char 1個位元組;
short 2個位元組;
int 4個位元組;
long 4個位元組;
float
4個位元組;
double
8個位元組;
long
long 8個位元組;
程式碼查詢:
拓展:
為什麼任何型別的指標都是4位元組?
因為指標指向地址,地址長度都是固定的。而地址長度是由作業系統決定,如果作業系統是32位的,地址長度為4位元組;如果作業系統是64位的,則地址長度為8位元組。
char 取值範圍:
無符號:0~255(2^8-1)
有符號:-128~127
有符號時:第一個為符號位:
0為整數:0 000 0000 = 0; 0 111 1111 = 127;
1為負數:負數時計算機儲存補碼
1 000 0000 取反加1 = -128
1 111 1111 取反加1 = -1
補碼取反加1成原碼
例如:
列印~2:常量都是有符號的:0 000 0010
取反:1 111 1101 負值
取反加1:0 000 0011 = -3
程式碼舉例:
分析:-128 1 000 0000
取反 1 111 1111
加1 0 111 1111 = 127
企業筆試題:
輸出結果分析:
有符號:i = 127 a[127] = -128
i = 128 a[128] = 127
...
i = 255 a[255] = 0; 0 相當於‘\0’
strlen(a)統計到‘\0’跳出,共255個。
無符號:i = 0 a[0] = 255;
i = 1 a[1] = 254;
...
i = 255 a[255] = 0;
strlen(a)統計到‘\0’跳出,共255個。
有符號與無符號資料的區別:
例項解析:
分析:
有符號數和無符號數進行比較運算時(==、<、>、<=、>=)有符號數隱式轉換成了無符號數(即底層的補碼)但是此數從有符號數變成了無符號數。
7、sizeof與strlen區別(詳情請戳網址):
http://blog.csdn.net/wzhcalex/article/details/51852632
8、變數與常量
變數三大特點:
變數的資料型別:主要說明變數佔用的記憶體空間的大小,如 int型;
變數的作用域:變數的有效性範圍,如變數的使用範圍;
變數的儲存類別:變數在記憶體中的儲存方式,不同的儲存方式影響變數在記憶體中的生存週期。
MMU:虛擬地址單元 解決記憶體資源稀缺問題
(列印一個地址:列印的都是虛擬的地址)
為了保護資料的安全,作業系統會對空間做劃分:
棧空間:區域性變數、函式形參、自動變數(呼叫後釋放)
特點:先進後出,管理許可權:系統
堆空間:malloc、ralloc、calloc 分配空間
特點:先進先出,管理許可權:使用者
資料段:bss:儲存未初始化的全域性變數
rodata:常量
.data(靜態資料區):全域性變數(程式結束後釋放)、static 修飾變數
全域性變數與區域性變數的區別:
(主要從空間的分配,沒有初始化的值 什麼時候釋放等角度入手)
9、宣告與定義:
宣告:不分配記憶體空間,可以宣告多次;
定義:分配記憶體空間,只可以定義一次。
變數的宣告有兩種情況:
定義性宣告:需要建立儲存空間的,例如:int a在宣告時就已經建立了儲存空間;
引用性宣告:不需要建立儲存空間, 例如:extern int a 其中變數a是在別的檔案中定義的。
10、格式化輸出與輸入:
首先,通過程式碼來示例以下不同的資料型別的不同的輸入輸出方法:
#include<stdio.h>
int main()
{
int i;
int num;
char ch;
float f_num;
double d_num;
int a[3];
char src[100];
scanf("%d",&num);
printf("num = %d\n",num);
getchar();
scanf("%c",&ch);
printf("ch = %c\n",ch);
scanf("%f",&f_num);
printf("f_num = %f\n",f_num);
scanf("%lf",&d_num);
printf("d_num = %lf\n",d_num);
printf("please input a[3]:\n");
for(i = 0; i < 3; i++)
{
scanf("%d",&a[i]);
}
for(i = 0; i < 3; i++)
{
printf("a[%d] = %d\n",i,a[i]);
}
scanf("%s",src);
printf("src[100] = %s\n",src);
return 0;
}
執行結果:
在獲取字元是用一個getchar()清空快取區
以下是getchar()的作用(轉自網路):
1.從緩衝區讀走一個字元,相當於清除緩衝區
2.前面的scanf()在讀取輸入時會在緩衝區中留下一個字元'\n'(輸入完s[i]的值後按回車鍵所致),所以如果不在此加一個getchar()把
這個回車符取走的話,gets()就不會等待從鍵盤鍵入字元,而是會直接取走這個“無用的”回車符,從而導致讀取有誤
3. getchar()是在輸入緩衝區順序讀入一個字元(包括空格、回車和Tab) getchar()使用不方便,解決方法:
(1)使用下面的語句清除回車: while(getchar()!='\n');
(2)用getche()或getch()代替getchar(),其作用是從鍵盤讀入一個字元(不用按回車),注意要包含標頭檔案<conio.h>
三種獲得字串的方法:scanf gets getchar
scanf 與 gets 的區別(以下轉自網路):
gets(s)函式與 scanf("%s",&s) 相似,但不完全相同,使用scanf("%s",&s) 函式輸入字串時存在一個問題,就是如果輸入了空格會認
為字串結束,空格後的字元將作為下一個輸入項處理,但gets()函式將接收輸入的整個字串直到遇到換行為止。
1.scanf() 所在標頭檔案:stdio.h
語法:scanf("格式控制字串",變數地址列表);
接受字串時:scanf("%s",字元陣列名或指標);
2.gets() 所在標頭檔案:stdio.h
語法:gets(字元陣列名或指標);
兩者在接受字串時:
1.不同點: scanf不能接受空格、製表符Tab、回車等; 而gets能夠接受空格、製表符Tab和回車等;
2.相同點: 字串接受結束後自動加'\0'。
使用gets獲取字串程式碼:
#include<stdio.h>
int main()
{
char src[100];
printf("input a string:");
gets(src);
printf("src = %s\n",src);
return 0;
}
使用getchar()獲取字串:
#include<stdio.h>
int main()
{
char ch;
char src[10];
int i = 0;
while((ch = getchar()) != '\n')
{
src[i] = ch;
i++;
if(i == 9)
{
printf("error!\n");
exit(1);
}
src[i] = '\0';
}
return 0;
}
如何讓scanf不遇到空格結束呢?
在%前面加空格
詳情請戳網址:http://blog.csdn.net/mishifangxiangdefeng/article/details/7163002
(技術大牛寫的帖)
獲取陣列方法:
1、
#include<stdio.h>
int main()
{
int a[3];
int i;
int *p = a;
for(i = 0; i <= 2; i++)
{
scanf("%d",p++);
}
p = a;
for(i = 0;i < 3; i++)
{
printf("a[%d] = %d\n",i,*(p + i));
}
return 0;
}
</pre><pre name="code" class="html">2、
#include<stdio.h>
int main()
{
int a[3];
int i;
for(i = 0; i <= 2; i++)
{
scanf("%d",&a[i]);
}
p = a;
for(i = 0;i < 3; i++)
{
printf("a[%d] = %d\n",i,a[i]);
}
return 0;
}
3、
#include<stdio.h>
int main()
{
int a[3];
int i;
for(i = 0; i <= 2; i++)
{
scanf("%d",a+i);
}
p = a;
for(i = 0;i < 3; i++)
{
printf("a[%d] = %d\n",i,*(a + i));
}
return 0;
}
第一版本程式碼中
scanf("%d",p++);
不可以用a++ 是因為a是常量不可以自加,*p = a 指標可以自加 p++
printf是行緩衝,我們用以下程式碼驗證:
1、
2、
3、
4、