1. 程式人生 > >C語言關鍵字(三)

C語言關鍵字(三)

之前的兩篇文章

嵌入式Linux:c語言深度解剖(資料型別關鍵字)​zhuanlan.zhihu.com

嵌入式Linux:c語言深度解剖(入門篇)​zhuanlan.zhihu.com

這篇檔案繼續講解C語言關鍵字

想問大家一個問題,什麼是宣告什麼是定義?

舉個例子:

A)int i;

B)extern int i;(關於 extern,後面解釋)

哪個是定義?哪個是宣告?或者都是定義或者都是宣告?

什麼是定義:所謂的定義就是(編譯器)建立一個物件,為這個物件分配一塊記憶體並給它取上一個名字,這個名字就是我們經常所說的變數名或物件名。但注意,這個名字一旦和這塊記憶體匹配起來,它們就同生共死,終生不離不棄。並且這塊記憶體的位置也不能被改變。一個變數或物件在一定的區域內(比如函式內,全域性等)只能被定義一次,如果定義多次,編譯器會提示你重複定義同一個變數或物件。

什麼是宣告:有兩重含義,如下:

第一重含義:告訴編譯器,這個名字已經匹配到一塊記憶體上了,(就比如你拿出結婚證告訴別人,我已經和別的記憶體塊結婚了,請不要再勾搭我了)。

第二重含義:告訴編譯器,我這個名字我先預定了,別的地方再也不能用它來作為變數名或物件名。(這個就有點耍賴,你拿著一個假的結婚證告訴別人,我已經結婚了,但是實際上,你還沒有妹子,還沒有分配記憶體空間)。

好,這樣一解釋,我們可以很清楚的判斷:A)是定義;B)是宣告。

那他們的區別也很清晰了。記住,定義宣告最重要的區別:定義建立了物件併為這個物件一塊記憶體,而宣告的時候是沒有分配記憶體空間的。

 

1,不需要記住的關鍵字----auto

auto:他太普通了,你就當它不存在吧。編譯器在預設的預設情況下,所有變數
都是 auto 的。

2,最快的關鍵字---- register

register:這個關鍵字請求編譯器儘可能的將變數存在 CPU 內部暫存器中而不是通過內
存定址訪問以提高效率。注意是儘可能,不是絕對。你想想,一個 CPU 的暫存器也就那麼
幾個或幾十個,你要是定義了很多很多 register 變數,它累死也可能不能全部把這些變數放
入暫存器吧,輪也可能輪不到你。

2.1,皇帝(CPU)身邊的小太監----暫存器

把cpu類比成為一個皇帝,那register就是皇帝身邊的小太監了,不知道大家見過太監沒有,我們看各種宮鬥劇的時候,太監是唯命是從,只要皇帝叫做什麼,太監馬上就去做,速度之快令人瞠目結舌,也就是因為速度快,所以皇帝才配有太監,而且不止有一個太監,太監就像一個檔案中轉站,把下面人的摺子拿給皇帝批閱。

所以太監的特點是

1、響應速度快

2、數量少,只給皇帝工作

3、價格貴



2.2,使用 register 修飾符的注意點

雖然暫存器的速度非常快,但是使用 register 修飾符也有些限制的:register 變數必須是能被 CPU 暫存器所接受的型別。意味著 register 變數必須是一個單個的值,並且其長度應小於或等於整型的長度。 而且 register 變數可能不存放在記憶體中,所以不能用取址運算子“&”來獲取 register 變數的地址。

3、確定位置的關鍵字----static

3.1、static 修飾變數

修飾靜態全域性變數:作用域僅限制於被定義的檔案中,其他檔案即使用extern宣告也沒有辦法使用,作用域從定義之處開始,到檔案結尾處,在定義之前的程式碼不能使用。本檔案可以在之前加extern ,不過這樣還不如直接在頂端定義。

 

靜態全域性變數:在函式體裡面定義的,就只能在函式裡面使用了,由於static定義的變數存在靜態區,改函式執行結束,變數的值還是存在,不會銷燬,下次該函式呼叫時,static定義的變數值是上一次呼叫時候的值。

3.2、static修飾函式

在函式前面加static表示函式成為靜態函式,表示該函式的作用域僅限於定義處到檔案結尾。如果全域性函式有一個函式名字和靜態函式一樣的名字,編譯器不會報錯,使用本檔案的靜態函式執行。

#include <stdio.h>
 
static int j;
 
void func1(void)
{
	static int i = 0;
	i++;
	printf("i = %d\n", i);
}
 
void func2(void)
{
	j = 0;
	j++;
	printf("j = %d\n", j);
}
 
int main(int argc, char *argv[])
{
	int k = 0;
	for(k = 0; k<10; k++)
	{
		func1();
		func2();
		printf("\n");
	}
	return 0;
}

大家執行上面程式碼加深下對static的理解

 

4、大喇叭關鍵字----extern

上面有一個例子已經說到了extern,extern就像一個大喇叭一樣,他不分配記憶體,就是不討老婆,但是總是跟別人說,誰誰娶媳婦了,這個識別符號就定義了,這個函式被定義了,如此如此,不過這個大喇叭非常有用,比如他可以跟編譯器說,這個傢伙已經討老婆了,你可以用他生孩子了,就不要再讓他二婚了。

既然extern不能給別人髮結婚證,那麼下面這個

extern int i = 10;

是否有問題?

不能髮結婚證,就是不能分配內儲存,沒有記憶體怎麼把10存下來?所以肯定是錯的。

 

5、讓cpu最累的關鍵字----volitile

volitile這個關鍵字讓cpu非常累,每次執行到他的位置,都要去記憶體重新確定他的值,在中斷的使用的變數最好用這個關鍵字修飾,因為中斷的變數你不知道什麼時候會被改變,volatile的變數是說這變數可能會被意想不到地改變,這樣,編譯器就不會去假設這個變數的值了。說白了就是不能讓cpu偷懶,所以說是類似老大CPU了。

 

6、只能讀的關鍵字----const

const可以理解為別人只能來讀我的值,不能改變我,有的人寫程式碼加上這個就是擔心自己的值被改變,比如const int i =5;下一次,i =6;這就是有問題的,i 的值已經確定了,就像define一樣(不過define不是關鍵字)。

/*************************************************************************
    > File Name: const.c
    > Author:           
    > Mail:             
    > Created Time: Mon 29 Oct 2018 02:33:19 PM CST
 ************************************************************************/
                        
#include<stdio.h>       
#define M 3             
const int N = 5;        
                        
void main(void)         
{                       
    printf("%p\n",&N);
    int i = M;          
    int j = N;          
    printf("%p\n",&N);                                                                                                 
    int x = M;          
    int y = N;          
    printf("%p %p %p %p\n",&i,&j,&x,&y);
    printf("%d %d %d %d\n",i,j,x,y);
}         

7、不色不空的關鍵字----void

大家可以看看void 和void* ,誰是色,誰是空呢?void 表示這個是空的,什麼都沒有,比如void i; 我們定義一個空的i,這就有問題了,這個i編譯器到底要不要給他分配空間呢?就是這樣的情況,編譯器報錯了,你搞什麼飛機給我一個空的東西還想讓我給你討老婆。

但是void 不一樣,void *表示所有型別的指標,這就是色啊,女人都想入非非。

說明:既然提供了void的這兩種用法,就去運用。即函式沒返回值就將其返回值型別寫為void,函式沒有形參就將其形參寫為void。不瞭解編譯器預設操作時,不要依賴。即使瞭解其預設操作,也別依賴,因為肯定有人不瞭解的,這樣別人就看不懂你的程式碼了。

大家看看這個是否正確

add(int a,int b)
{
    return (a+b);
}

下面兩個函式就是用void*作為返回值

memcpy  

原型:extern void *memcpy(void *dest, void *src, unsigned int count);   

用法:#include   

功能:由src所指記憶體區域複製count個位元組到dest所指記憶體區域。   

說明:src和dest所指記憶體區域不能重疊,函式返回指向dest的指標。   

注意:與strcpy相比,memcpy並不是遇到'\0'就結束,而是一定會拷貝完n個位元組。

 

memset

原型:extern void *memset(void *buffer, int c, int count);

用法:#include

功能:把buffer所指記憶體區域的前count個位元組設定成字元c。

說明:返回指向buffer的指標。

8、return 關鍵字

return 關鍵字終止一個函式並返回一個值。

#include "stdio.h"

add(int a,int b)
{
    return (a+b);
}

char * str(void)
{
    char *strc = "This is c\n";
    return strc;
}

main(void)
{
    printf("%d\n",add(1,4));
    printf("%s\n",str);
    return ;
}

看看上面這個函式的輸出,會有陷阱的

9、柔性陣列

講了很多關鍵字,現在討論一個酷的東西,以後也會經常用到,柔性陣列是可以自由變化長度的陣列,對開發上來說是非常有用的。

C99中,結構體的最後一個元素允許是未知大小的陣列,這就叫做柔性陣列成員,但結構體的柔性陣列成員前面必須至少一個其他成員。

#include<stdio.h>
typedef struct _SoftArray{
    int len;
    int array[];
}SoftArray;

int main()
{
    int len = 10;

    printf("The struct's size is %d\n",sizeof(SoftArray));
}

我們可以看出,_SoftArray結構體的大小是4,顯然,在32位作業系統下一個int型變數大小剛好為4,也就說結構體中的陣列沒有佔用記憶體。為什麼會沒有佔用記憶體,我們平時用陣列時不時都要明確指明陣列大小的嗎?但這裡卻可以編譯通過呢?這就是我們常說的動態陣列,也就是柔性陣列。

#include<stdio.h>
#include<malloc.h>
typedef struct _SoftArray{
int len;
int array[];
}SoftArray;
int main()
{
    int len=10,i=0;
    
    SoftArray *p=(SoftArray*)malloc(sizeof(SoftArray)+sizeof(int)*len);
    p->len=len;
    
    for(i=0;i<p->len;i++)
   {
        p->array[i]=i+1;
    }
    for(i=0;i<p->len;i++)
   {  
        printf("%d\n",p->array[i]);
    }

    free(p);

    return 0;
}

這程式碼的作用是用柔性陣列動態建立陣列並輸出陣列內容,這裡我就直接解釋解釋這兩句程式碼

   SoftArray* p = (SoftArray*)malloc(sizeof(SoftArray) + sizeof(int) *10);
      p->len = 10;

第一句,主要是根據你要定義的陣列長度和資料型別以及柔性陣列本身的大小來開闢一塊記憶體空間給柔性陣列,第二個是定義len的長度,便於確定迴圈列印輸出

覺得對你有用,請關注微信公眾號【嵌入式Linux】