程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> C語言中 const 和 static 和 extern

C語言中 const 和 static 和 extern

編輯:關於C

const關鍵字,很多人想到的可能是const常量,其實關鍵字const並不能把變量變成常量!在一個符號前加上const限定符只是表示這個符號 不能被賦值。也就是它的值對於這個符號來說是只讀的,但它並不能防止通過程序的內部(甚至是外部)的方法來修改這個值(C專家編程.p21)。也就是說 const變量是只讀變量,既然是變量那麼就可以取得其地址,然後修改其值。看來const也是防君子不防小人啊!:)

const 使用情況分類詳析

1、const的普通用法
const int n = 10;

意思很明顯,n是一個只讀變量,程序不可以直接修改其值。這裡還有一個問題需要注意,即如下使用:int a[n];在ANSI C中,這種寫法是錯誤的,因為數組的大小應該是個常量,而n只是一個變量。

2、const用於指針
const int *p;
int const *p;
int * const p;

在最後的一種情況下,指針是只讀的(即p只讀),而在另外兩種情況下,指針所指向的對象是只讀的(即*p只讀)。const 是一個左結合的類型修飾符,它與其左側的類型修飾符一起為一個類型修飾符,所以,int const 限定 *p,不限定p。int *const 限定p,不限定*p。這裡有一個簡便的區分方法:

沿著*號劃一條線,如果const位於*的左側,則const就是用來修飾指針所指向的變量,即指針指向為常量;如果const位於*的右側,const就是修飾指針本身,即指針本身是常量。

3、const用於函數的地址傳遞參數

void foo(const int *p)

這種形式通常用於在數組形式的參數中模擬傳值調用。也就是相當於函數調用者聲稱:"我給你一個指向它的指針,但你不能去修改它。"如果函數編寫者遵循了這個約定,那麼就相當於模擬了值傳遞。這也是const最有用之處了:用來限定函數的形參,這樣該函數將不會修改實參指針所指的數據。這裡注意了,是函數不應該去修改而不是不能修改,也就是說const不能阻止參數的修改(原因見上)。

4、const用於限定函數的返回值

const int foo();
const struct mytype foo();
上述寫法限定函數的返回值不可被更新,當函數返回內部的類型時,已經是一個數值,當然不可被賦值更新,所以,此時const無意義,最好去掉,以免困惑。當函數返回自定義的類型時,這個類型仍然包含可以被賦值的變量成員,所以,此時有意義。

一、宏定義:主要是一些語法問題和技巧

例如:

#define FIND(s,e) (size_t)&(((struct s*)(0))->e)//求結構體內的變量相對於結構體的偏移量

#define SECONDS_PER_YEAR (360*24*60*60)UL//求一年中的秒數

#define MIN(a,b) (((a)<=(b))?(a):(b))//求最小值

說明:盡可能考慮移植性,由於代碼可能在16位機,也有可能在32位機器上運行,所以采用size_t和UL都是基於移植性的考慮。

二、const用法:定義常量,修飾指針、函數的輸入參數和返回值,簡單說const表示只讀的意思,本質上來說它只是在全局數據段或者棧中定義的是一個只讀的常量,不是真正位於字符串常量區。Const的目的是為了產生高質量的代碼,提高代碼的可讀性,同時保護好不能被任意改變的內存,從而降低Bug產 生的概率。

    const int a = 10;

const int b;//錯誤,常量必須初始化

    int a = 10,b = 9;

    const int *p1 = &a;//指針指向的內容只讀,不能通過該指針去寫

    *p1 = 11;//錯誤

    int * const p2 = &a;//指針本身只讀,指針初始化到一個對象後,將不能被修改

p2 = &b;//錯誤

const int *p3 const= &a;//指針本身和指向的內容都是只讀

 

const char *fp1(void) //修飾返回值,表示返回的指針指向內容只讀

{

    char *p = "dddd";

    return p;

}

void fp1(const char *str)

{

    *str = 4; //錯誤

    const char *p = str;//p必須為const,才能接受str

}

int _tmain(int argc, _TCHAR* argv[])

{

    const char *d = fp1();

    printf("%s",d);

}

 

三、extern用法:在別的文件中定義的變量,要想在本文件中使用,必須先用extern聲明,例如:extern a;之後就當成在本文件中定義的變量一樣使用。

 

四、static用法:

1.修飾變量,從生存域和訪問域兩個方面說明,無論static變量定義在函數內或外,該變量都位於數據段中;定義於函數體外的static變量的訪問域僅僅是它所在文件中定義的函數,其他文件無法通過extern對其聲明後訪問。

2.修飾函數,使得函數的訪問域僅僅為其所定義的函數。

3.類中變量用static修飾表示變量是類變量,類中函數用static修飾表示函數只能訪問類中的static變量,不接受this指針,稱為類函數。

例如:

//file1

void fstatic(void);

static void fstatic(void)

{

    return;

}

//file2

void fstatic(void);

Main()

{

fstatic();//聲明仍然無法調用

}

總之,static實現了c語言的封裝性,一定程度上實現了信息的封裝和隱藏。

五、sizeof用法:sizeof是一個運算符,編譯器在編譯時就能確定其的運算結果,其運算對象可以是內部數據類型名、struct類型名、 class類型名、數組變量和其他類型的變量,只有計算對象是數組變量時,能得出數組實際的長度,其他類型的變量作為運算對象時,都將退化為對該對象的數據類型的運算。另外,在計算struct和class等用戶自定義數據類型的大小時,需要考慮編譯器會對其做優化處理,根據一定的原則調整,即會自動對齊,sizeof的結果往往會大於各個元素長度的和。

例一:

    short l;//sizeof(l)==2

    char *a ="abcddd";//sizeof(a)==4

    void *s = malloc(100);//sizeof(s)==4

    char b[] = "abcddd";//sizeof(b)==7*1

    float c[20];//sizeof(c)==20*4;

例二:

struct A

{

  short a;

  short b;

  short c;

};//sizeof(A)==6

struct B

{

  int a;

  char b;

  short c;

};//sizeof(A)==8

 

struct C

{

  double a1;

  long a;

  int a2;

  char b;

 

};//sizeof(B)==24

#pragma pack(1)//使用pack指令強制編譯器不做優化對其,這種方式一般用於這個

struct D//結構體要直接寫入文件或者直接用於網絡收發的情況下

{

  double a1;

  long a;

  int a2;

  char b;

 

};

#pragma pack()//sizeof(B)==17

說明:例二中演示了數據對其的情況,由於CPU訪問數據的特點是一次訪問多個字節,故如果多字節數據的首地址是2的整數倍的話,將可以一次內存訪問即可取得其所對應的所有數據,所以一個優化要求就是變量的地址是其數據類型長度的整數倍,例如int a的a的地址要求是4的整數倍。

針對結構體,如果結構體中所有元素類型相同,作為數組處理,入struct A;否則一般都是其最常元素的整數倍,例如struct B和C。這樣處理的目的是考慮定義結構體數組的情況,例如struct B b[10];那麼這10個結構體變量如果每個長度都是8,將能保證每個變量的第一個元素都能是4的整數倍,否則將達不到這個效果,不能實現高效的內存訪問,故編譯器將對不符合要求的結構體自動調整數據對齊。

最後需要交代的是,class中的static變量不被分配到棧上,所以不能計入sizeof中,空類的長度是1,有虛函數的長度為4,因為包含一個指向函數表的指針。

下面分析幾個面試題:

例一:

int fp1(char var[])//返回值為4,因為var作為參數傳遞時,已經退化為普通指針,

{//故其大小就是指針的大小,不作為數組名處理,無法計算出數組長度。

   returnsizeof(var);

}

 

 

 

 

 

 

例二:

//str1是一個數組名,該數組是一個指針數組,長度為3,故sizeof(str1)為3*4

void fp1(void)

{

    char *str1[] ={"hello","mico","china"};

    for(int i=0;i <sizeof(str1)/sizeof(char *);i++)

    {

      printf("%s",str1[i]);

    }

}

六、volatile的用法:該詞的意思是”易變的”,用於修飾變量的一個關鍵字,表示該變量在很多地方都能被改變,會被意象不到的改變,編譯器不能對其優化,往往用於多任務系統或嵌入式系統中,情況一:嵌入式系統中的很多外設寄存器的值會實時改變,如:#define PTA  *(volatile unsigned char *)(0x00000001);情況二:嵌入式系統內存中的某些變量有可能被中斷程序修改;情況三:多任務系統中的共享變量可能隨時改變。滿足這些特點之一的變量必須要用volatile修飾,保證編譯器不能對其優化處理,如果被優化,往往程序的執行結果出錯。

例一:

int a = 10;

if(a == 10)

{

   a = 11;

}

這短代碼正常被優化為

a = 11; 但是,如果volatile int a;則這段代碼將不被優化。

例二:

//原始代碼:

int square(volatile int *ptr)

{

    return *ptr * *ptr;

}

//編譯器優化完的代碼:

int square(volatile int *ptr)

{

    int a = *ptr;

    int b = *ptr;

    return a*b;

}

//正確代碼:由於*ptr的值隨時可能改變,故優化代碼中的a和b的值可能不一樣,故計

int square(volatile int *ptr) //算的結果可能有誤

{

    int a = *ptr;

    return a*a;

static有兩種用途:一是修飾變量,二是修飾函數

 

第一:修飾變量

例子:

static int a;

void Func()

{

    static int b;

}

全局變量默認的存儲類型是extern,若不加static修飾,在不同頭文件中定義名字相同的全局變量會發生沖突。static修飾符是一個能夠減少這類命名沖突的有用工具。例如,以下聲明語句

static int a;

其含義與下面的語句相同:

int a;

只不過,a的作用域限制在一個源文件內,對於其他源文件,a是不可見的。因此,如果若干個函數需要共享一組外部對象,可以將這些函數放到一個源文件中,把它們需要用到的對象也都在同一個源文件中以static修飾符聲明。

變量b是個局部變量,在程序退出函數Func後,b就不能被使用。但是當程序再次進入函數Func時,變量b保持上次運行後的值。

 

第二:修飾函數

static修飾符不僅適用於變量,也適用於函數。一個函數,其默認存儲狀態也是extern。如果函數f需要調用另一個函數g,而且只有函數f需要調用函數g,我們可以把函數f與函數g都放到同一個源文件中,並且聲明函數g為static:

static int

g(int x)

{

    // g函數體

}

void f()

{

    // 其他內容

    b=g (a);

}

我們可以在多個源文件中定義同名的函數g,只要所有的函數g都被定義為static,或者僅僅只有其中一個函數g不是static。因此,為了避免可能出現的命名沖突,如果一個函數僅僅被同一個源文件中的其他函數調用,我們就應該聲明該函數為static。

 

如果說得規范一點,C/C++沒有全局變量,只有外部和自動。C/C++裡的全局變量是靠你自己做出來的。

所有在某函數裡定義的變量全是自動變量。默認是auto。在所有函數外定義的是外部變量。

如果你的程序放在單個源文件裡,那它就是全局變量。如果有多個源文件,你在其中一個文件裡定義那個變量,再在所有其它文件裡用extern說明它。它也成了全局變量。

注意:這裡很仔細地使用了“定義”和“說明”。前者引起存儲分配,是真正產生那個變量。後者只是讓變量在那個文件裡也可見(可以訪問)。

如果在定義外部變量時,加上指示符static,則生成的是靜態外部變量。這種變量的作用域僅限於那個文件。也就是說,不能再在其它文件裡用extern說明它。

如果在定義自動變量時,加上指示符static,則生成的是靜態自動變量。作用域沒變,仍然是所在的語句塊。但是生命期卻變成永久。

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved