1. 程式人生 > >const,volatile,static,typdef,幾個關鍵字辨析和理解

const,volatile,static,typdef,幾個關鍵字辨析和理解

iostream 很好 都是 種類 狀態 優點 ror 識別 出錯

1、const類型修飾符

const它限定一個變量初始化後就不允許被改變的修飾符。使用const在一定程度上可以提高程序的安全性和可靠性。它即有預編譯命令的優點也有預編譯沒有的優點。const修飾的變量被編譯器處理只讀變量(不是常量,常量是放到內存的只讀區域的)放在內存中,由編譯器限定不允許改變。

(1)具有不可變性。
  例如:const int Max=100; Max++會產生錯誤;
(2)便於進行類型檢查,使編譯器對處理內容有更多了解,消除了一些隱患。
  例如: void f(const int i) { .........} 編譯器就會知道i是一個不允許修改的變量;
(3)可以避免意義模糊的數字出現,同樣可以很方便地進行參數的調整和修改。 同宏定義一樣,可以做到不變則已,一變都變!
  如(1)中,如果想修改Max的內容,只需要:const int Max=you want;即可!   (實際這樣去改變const變量是錯誤的,會報重復定義的錯誤) (4)可以保護被修飾的東西,防止意外的修改,增強程序的健壯性。 還是上面的例子,如果在函數體內修改了i,編譯器就會報錯;
  例如: void f(const int i)        {         i=10;//error!        } (5)如果你非有意的用一個指針去修改const變量的值, 編譯器會在你取const變量地址時給出警告
(6) 可以節省空間,避免不必要的內存分配。 例如:
 
 #define
PI 3.14159 //常量宏放入只讀內存中   const double Pi=3.14159; //此時並未將Pi放入RAM中 ......   double i=Pi; //此時為Pi分配內存,以後不再分配!   double I=PI; //編譯期間進行宏替換,分配內存   double j=Pi; //沒有內存分配   double J=PI; //再進行宏替換,又一次分配內存!
  const定義常量從匯編的角度來看,只是給出了對應的內存地址,而不是像#define一樣給出的是立即數,所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內存中有若幹份拷貝。
(6) 提高了效率。
  編譯器通常不為普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的常量,沒有了存儲與讀內存的操作,使得它的效率也很高。(參考百度百科)

2、volatile類型修飾符

用來修飾被不同線程訪問和修改的變量(即隨時會被意想不到的改變)。volatile的作用是作為指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值。

優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器裏的備份。下面是volatile變量的幾個例子: 1)並行設備的硬件寄存器(如:狀態寄存器) 2)一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables) 3)多線程應用中被幾個任務共享的變量 1)一個參數既可以是const還可以是volatile嗎?解釋為什麽。   是的。一個例子是只讀的狀態寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。 2)一個指針可以是volatile 嗎?解釋為什麽。   是的。盡管這並不很常見。一個例子是當一個中斷服務子程序修改一個指向一個buffer的指針時。 3)下面的函數被用來計算某個整數的平方,它能實現預期設計目標嗎?如果不能,試回答存在什麽問題:
int
square(volatile int *ptr) { return ((*ptr) * (*ptr)); }
這段代碼是個惡作劇。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由於*ptr指向一個volatile型參數,編譯器將產生類似下面的代碼:
int square(volatile int* &ptr)//這裏參數應該申明為引用,不然函數體裏只會使用副本,外部沒法更改
{
    int a,b;
    a = *ptr;
    b = *ptr;
    return a*b;
}
由於*ptr的值可能在兩次取值語句之間發生改變,因此a和b可能是不同的。結果,這段代碼可能返回的不是你所期望的平方值!正確的代碼如下:
long square(volatile int*ptr)
{
    int a;
    a = *ptr;
    return a*a;
}
在一次線程內,當讀取一個變量時,為提高存取速度,編譯器優化時有時會先把變量讀取到一個寄存器中;以後再取變量值時,就直接從寄存器中取值; 當變量值在本線程裏改變時,會同時把變量的新值copy到該寄存器中,以便保持一致當變量在因別的線程等而改變了值,該寄存器的值不會相應改變,從而造成應用程序讀取的值和實際的變量值不一致當該寄存器在因別的線程等而改變了值,原變量的值不會改變,從而造成應用程序讀取的值和實際的變量值不一致,此時如果使用了volatile類型修飾符就不會出現這種問題,所以說volatile可以保證對特殊變量的穩定訪問。

3、static類型修飾符

  當一個進程的全局變量被聲明為static之後,它就是靜態全局變量。靜態全局變量和其他的全局變量的存儲地點並沒有區別,要說有區別就是static是在.data段因為編譯器會在你未初始化時自動初始化為0,而普通變量已初始化的變量在data段或者未初始化在.bss段內;static變量它只在定義它的源文件內有效,其他源文件無法訪問它。普通全局變量extern後,它就可以被其他源文件及其函數訪問,而static變量是無法extern 因為編譯器在其他源文件中看不到被static修飾的變量。

1.用在全局變量上時例如

 1 //test.c
 2 #include <stdio.h>
 3 #include "test.h"
 4 
 5 static  char str[] = "read ok !";
 6 int test()
 7 {    
 8     printf("static is %s\n",str);
 9     return 0;
10 }

主函數:

 1 //main.c
 2 
 3 #include <stdio.h>
 4 #include "test.h"
 5 
 6 char str[]="is not ok!";
 7 
 8 int main() 
 9 {
10     test();
11     printf("static is %s\n",str);
12     return 0;
13 }

上面程序輸出的結果是明顯的,但如果去掉test.c內的str的static修飾符後編譯時鏈接會出錯,因為是有兩個地方存在相同的變量,導致編譯器編譯時無法識別應該使用哪一個。但當你用static 修飾test.c內的str變量後編譯器就知道在那個源文件內該使用哪一個。

2,在局部變量上使用時

普通的局部變量在棧空間上分配,這個局部變量所在的函數被多次調用時,每次調用這個局部變量在棧上的位置都不一定相同。局部變量也可以在堆上動態分配,但是記得使用完這個堆空間後要釋放。

static局部變量中文名叫靜態局部變量。它與普通的局部變量比起來有如下幾個區別:

1)位置:靜態局部變量被編譯器放在全局存儲區.data(前面提到編譯器會自動初始化),所以它雖然是局部的,但是在程序的整個生命周期中存在。

2)訪問權限:靜態局部變量只能被其作用域內的變量或函數訪問。也就是說雖然它會在程序的整個生命周期中存在,由於它是static的,它不能被其他的函數或源文件訪問。

3)值:靜態局部變量如果沒有被用戶初始化,則會被編譯器自動賦值為0,以後每次調用靜態局部變量的時候都用上次調用後的值。這個比較好理解,每次函數調用靜態局部變量的時候都修改它然後離開,下次讀的時候從全局存儲區讀出的靜態局部變量就是上次修改後的值。

例如:

1 #include "test.h"
2 #include <stdio.h>
3 int test()
4 {
5     int b = 0;
6     static int a;
7     printf("%d,%d\n",a++,b++);
8     return 0;
9 }

主函數:

 1 #include <stdio.h>
 2 #include "test.h"
 3 
 4 int main() 
 5 {
 6     test();
 7     test();
 8     test();
 9     test();
10     test();
11     return 0;
12 }

結果:

0,0   註意這裏驗證了static變量的自動初始化
1,0
2,0
3,0
4,0 

3.static函數

既然static變量在其他源程序中不可以訪問,那用在函數前是否也有相同的功能呢?驗證一下:

//test.c
#include "test.h"
#include <stdio.h>
static int test()
{
    int b = 0;
    printf("%d\n",b);
    return 0;
}
int mytry()
{
    test();
    return 0;
}

頭文件:

//test.h
#ifndef __TEST_H__
#define __TEST_H__
#include"test.h"

static int test();
int mytry();
 
#endif
 

主函數:

//main.c
#include <stdio.h>
#include "test.h"

int main() 
{
    //test();//加上這一句程序鏈接時會出錯 
    mytry();
    return 0;
}

結果你猜對了嗎?所以static函數可以很好地解決不同原文件中函數同名的問題,因為一個源文件對於其他源文件中的static函數是不可見的。參考博客

4、typdef 或 #define 類型修飾符

(網上好多地方都有關於這兩個關鍵字如何使用的討論,本文只是簡單羅列各自的特性。)

1.#define是預處理指令,通常用來替代常量(大寫)在編譯預處理時進行簡單的替換,不作正確性檢查,不關含義是否正確照樣帶入。即使你在末尾習慣性的寫上 ; define 還是會乖乖的替換,幽默點說如果你愛浪愛自由#define是不二之選(但是記住出來混是要還的)

知乎上看到這樣的代碼:

#include <iostream>
#define start using namespace std; 
int main(int argc, char *argv[]) {
#define end <<endl;}
#define print cout<<

start
print"hello world!"
end

作者:DreamPiggy
鏈接:https://www.zhihu.com/question/29798061/answer/78916243
來源:知乎。

另一個例子:

#define Pi 3.1415926;

(Pi*R*R ) 就會成為 (3.1415926;*R*R)

2.typdef 是由編譯器處理的,由名字也能感覺到功能的一點差別,他是用於長類型 有一個剪短的名字而常常被使用,其實typdef創造的是一個類型(官方的解釋是任何聲明變量的語句前面加上typedef之後,原來是變量的都變成一種類型。不管這個聲明中的標識符號出現在中間還是最後.)

如:

define   PINT int*

PINT a,b  就是  int *a;int b;

而

typedef PINT int*

PINT a,b 就是 int  *a,int  *b;

常見用法:

  • 用在C代碼中,幫助struct。聲明struct新對象時,必須要帶上struct,即形式為: struct 結構名對象名,如:

  struct tagPOINT1

  {
   int x;

  int y;
  };

  struct tagPOINT1 p1;

  而在C++中,則可以直接寫:結構名對象名,即:tagPOINT1 p1;

  typedef struct tagPOINT
  {
   int x;

  int y;
  }POINT;

  POINT p1; 簡潔多了吧

  • 用typedef來定義與平臺無關的類型。

  比如定義一個叫 FLOAT的浮點類型,在目標平臺一上,讓它表示最高精度的類型為:

  typedef long double FLOAT;

  在不支持 long double 的平臺二上,改為:

  typedef double FLOAT;

  在連 double 都不支持的平臺三上,改為:

  typedef float FLOAT;

  也就是說,當跨平臺時,只要改下 typedef 本身就行,不用對其他源碼做任何修改。

  標準庫就廣泛使用了這個技巧,比如size_t。另外,因為typedef是定義了一種類型的新別名,不是簡單的字符串替換,所以它比宏來得穩健。

  • 還有就是類似#deine的作用

3.區別

  • typedef是有作用域的,而#define不管怎麽寫都是全局的
  • 細節上的差別

  typedef int * pint ;

  #define PINT int *
  那麽:
  const pint p ;//p不可更改,但p指向的內容可更改
  const PINT p ;//p可更改,但是p指向的內容不可更改。

兩者的區別還很多。。。。。。在同樣的功能下看個人喜好。

5、extern

  • 默認情況下所有文件的變量都可以訪問,只需要在定義變量時添加一個extern(extern int age)(而且沒有分配內存)引用一下就行,這個不管int age是定義在哪個文件中都可以得到,而且此變量還可以被修改。
  • extern引用的時候,優先找本文件夾,如果找不到再去其它文件夾。

2017.9.1

本博客起筆與於一個月前剛到實習地點,在實習最後一天再次繼續完成。


const,volatile,static,typdef,幾個關鍵字辨析和理解