在C語言中,static的意思是靜態,他有3個明顯的做用:ios
(1)在函數體內,靜態變量具備「記憶」功能,即一個被聲明爲靜態的變量在這一函數被調用的過程當中值維持不變。程序員
(2)在模塊內(但在函數體外),他的做用範圍是有限制的,但若是一個變量被聲明爲靜態的,那麼該變量能夠被模塊內全部函數訪問,但不能被模塊外其餘函數訪問。它是一個本地的全局變量。若是一個函數被聲明爲靜態的,那麼其做用與僅在本文件內,它只能夠被本文件內的其餘函數調用,不能被模塊外的其餘函數調用,也就是說這個函數被限制在本地範圍內使用。編程
(3)內部函數應該在當前源文件中說明和定義,對於可在當前源文件之外使用的函數,應該在一個頭文件中說明,使用這些函數的源文件要包含這個頭文件。數組
具體而言,static全局變量和普通的全局變量的區別在於static全局變量只初始化一次,這樣作的目的是爲了防止在其餘文件單元中被引用。static局部變量和普通變量的區別在於它只被初始化一次。安全
在C++中,在類內數據成員的聲明前加上關鍵字static,該數據成員就是類內的靜態數據成員。靜態數據成員有如下特色:多線程
(1)對於非靜態成員,每一個類對象都有本身的複製品。而靜態數據成員被看成是類的成員。不管這個類的對象被定義了多少個,靜態數據成員在程序中也只有一份複製品,由該類型的全部對象又該類型的全部對象共享訪問。函數
(2)靜態數據成員存儲在全局數據去,定義時要分配控件,因此不能在類聲明中定義。因爲靜態數據成員屬於本類的全部對象共享,因此它不屬於特定的類對象,在沒有產生類對象時其做用域就可見,即在沒有產生類的實例時,程序員也可使用它。性能
(3)靜態數據成員和普通數據成員同樣聽從Public、protected、private訪問規則。測試
(4)static成員變量的初始化是在類外,此時不能再帶上static的關鍵字。private、protected的static成員雖然能夠在類外初始化,可是不能在類外被訪問。優化
與全局變量相比,使用靜態變量有如下兩個優點:
(1)靜態數據成員沒有進入程序的全局名字空間,所以不存在與程序中其餘全局名字衝突的可能性。
(2)能夠實現信息隱藏。靜態數據成員能夠是private,而全局變量不能。
引伸問題:
一、爲何static變量只初始化一次?
由於靜態變量具備記憶功能,初始化後,一直沒被銷燬,而是保存在內存區域中,因此不會再次初始化。
二、在頭文件中定義靜態變量,是否可行?爲何?
不可行,若是在頭文件中定義靜態變量,會形成資源浪費的問題,同時也會引發程序錯誤。
常類型也被成爲const類型,是指使用類型修飾符const說明的類型。const是C和C++中常見的關鍵字,在C語言中,它主要用於定義變量爲常類型以及修飾函數參數和返回值,而在C++中還能夠修飾函數的定義,定義類的成員函數。常類型的變量或對象的值是不能更新的。
通常而言,const有如下幾個方面的做用:
(1)定義const變量,具備不可變性。例如:
const int MAX=100; int Array[MAX];
(2)進行類型檢查,是編譯器對處理內容有更多的連接,消除了一些隱患。例如:
void f(const int i){....}
(3)避免意義模糊的數字出現,一樣能夠很方便的的進行參數的調整和修改。通紅定義同樣,能夠作到不變則已,一變都變。如1中,若是想修改MAX的內容,子須要定義const int MAX=指望值便可。
(4)保護被修飾的東西,防止被意外的修改,加強程序的健壯性。上例中,若是在函數體內修改了變量i的值,那麼編譯其就會報錯。例如
void f(const int i) { i=10; }
上述代碼對i賦值會致使編譯出錯。
(5)爲函數從在提供參考
class A { void f(int i){...} //定義一個函數 void f(int i )const {...}//上一個函數的重載 }
(6)節省空間,避免沒必要要的內存分配。例如
#define Pi 3.1415926//該宏用來定義常量 const double Pi=3.1415926//此時並未將Pi放入只讀存儲器中 double i=Pi; //此時爲Pi分配內存,之後再也不分配 double I=Pi; //編譯期間進行宏替換,分配內存 double j=Pi; //沒有進行內存分配 double J=Pi; //再次進行宏替換
Const從彙編的角度來看,只是給出了對應的內存地址,而不是想#define同樣給出的是當即數,因此const定義的常量在程序運行過程當中只有一份複製品,而#define定義的常量在內存中有若干個複製品。
(7)提升程序的效率。編譯器一般不爲普通const常量分配存儲空間,而是將他們保存在符號表中,這使得它成爲一個編譯期間的常量,沒有了存儲與讀內存的操做,使得它的效率也很高。
引伸問題:
一、引伸1:什麼狀況下須要使用const關鍵字?
(1)修飾通常常量。通常常量是指簡單類型的常量。這種常量在定義時,修飾符const能夠用在類型說明符,也能夠用在類型說明符後。例如:
int const x=2; 或 const int x=2;
(2)修飾常數組。定義或說明一個常數組能夠採用以下形式:
int const a[0]={1,2,3,4,5,6,7,8}; const int a[0]={1,2,3,4,5,6,7,8};
(3)修飾常對象。常對象時指對象常量,定義格式以下:
class A; const A a; A const a;
定義常對象時,一樣要進行初始化,而且該對象不能再被更新,修飾符const 能夠放在類名後面,也能夠放在類名前面。
(4)修飾常指針。
const int *A;//const 修飾指向的對象,A可變,A指向的對象不可變 int const *A;//const 修飾指向的對象,A可變,A指向的對象不可變 int *const A;//const 修飾指針A,A不可變,A指向的對象可變 const int *const A;//指針A和A指向的指針都不可變
(5)修飾常引用。使用const修飾符也能夠說明引用,被說明的引用,該引用所引用的對象不能被更新。其定義格式以下:
const double & v;
(6)修飾函數的常參數。const修飾符也能夠修飾函數的傳遞參數,格式以下:
void Fun(const int Var)
告訴編譯器Var在函數體中的沒法改變,從而防止了使用者一些無心的或錯誤的修改。
(7)修飾函數的返回值。const修飾符也能夠修飾函數的返回值,返回值不可被改變,格式以下:
const int Fun1(); const MyClass Fun2();
(8)修飾類的成員函數。const修飾符也能夠修飾類的成員函數。格式以下:
class ClassName { public: int Fun() const; };
這y樣,在調用函數Fun()時就不能修改類或對象的屬性。
(9)在另以鏈接文件中引用const常量。使用方式有:
extern const int i; extern const int j=10;
第一種用法是正確的。而第二種用法是錯誤的,常量不能夠被再次賦值。另外,還須要注意,常量必須初始化,如 const int i=5.
二、引伸2:什麼是常引用?
常引用也成爲const引用。之因此引入常引用,是爲了不在使用的引用時,在絕不知情的狀況下改變了值,從而引發程序錯誤。常引用主要用於定義一個普通變量的只讀屬性的別名,做爲函數的傳入參數,避免實參在調用函數中被意外的改變。
const引用的意思是指向const對象的引用,非const引用表示指向非const類型的引用。若是即要利用引用提升程序的效率,又要保護傳遞給函數的數據再也不函數中被改變,就應使用常引用。常引用申明方式:
const 類型標識符 & 引用名 = 目標變量名
常引用的主要用途以下:
若是是對一個變量進行引用,則編譯器首先創建一個臨時變量,而後將該變量的值置入臨時變量中,對該引用的操做就是對該臨時變量的操做,對變量的引用能夠用其餘任何引用來初始化,但不能改變。
關於引用的初始化,通常須要注意如下問題:當初始化值是一個左值(能夠取得地址)時,沒有任何問題;而當初始化值不是一個左值時,則只能對一個常引用賦值,並且這個賦值有一個過程,首先將值隱式轉換到類型T,而後將這個轉換結果存放到一個臨時對象裏,最後用這個臨時對象來初始化這個引用變量。例以下面兩種使用方式:
一、double & dr=1; 二、const double &cdr=1;
第一種方式錯誤,初始化值不是左值。而第二種方式正確,執行過程以下:
double temp = double(1); const double & cdr = temp;
若是對第1種使用方法進行相應的改造,也能夠變爲合法,例如:
const int ival = 1024; 一、const int & refVal = ival; 2. int & ref2 = ival;
這裏第一種方法是正確的,第二種是非法的。上例中,能夠讀取refVal的值,可是不能修改它。由於不能修改它,由於refVar的類型是const,任何對refVal的賦值都是不合法的(const引用是隻讀的,常量既不能做爲左值的量,定義式中賦初值除外)。同時,const引用能夠初始化爲不一樣類型的對象或者初始化爲右值,如字面值常量,而非const引用只能綁定到該引用同類型的對象。例以下面的const引用都是合法的。
int i=42; const int &r = 42; const int &r2 = =r+i;
在使用cosnt引用進行函數調用的時候,須要注意一個問題,例以下面函數聲明
void bar(string &s);
那麼下面的表達式將是非法的:
bar("hello world")
程序實例以下:
#include <iostream> #include <string> using namespace std; void bar(string &s) { cout<<s<<endl; } int main() { var("hello world"); return 0; }
程序輸出爲
hello world
緣由在於「hello world」會產生一個臨時對象,而在C++中,臨時對象是const類型的。所以上面的表達式就試圖將一個const類型的對象轉換爲非const類型,這是非法的。引用型類型參數應該在能被定義爲const的狀況下,儘可能定義爲const。
編譯器優化的時候可能會出現問題,如當遇到多線程編程時,變量的值可能由於別的線程而改變了,而該寄存器的值不會相應改變,從而形成應用程序讀取的值和實際的變量值不一致。例如:在本次線程內,當讀取一個變量時,爲提升存取熟讀,編譯其優化過程當中有時會先把變量量讀取到一個寄存器內;當之後再取變量值時,就直接從寄存器中取值;當變量值再本線程裏改變時,會同時把變量的新值複製到該寄存器中,以便保持一致。
volatile是一個類型修飾符,它用來修飾被不一樣線程訪問和修改的變量。被volatile類型定義的變量,系統每次用到它時都是直接從對應的內存中提取,而不會利用cache中的原有值,以適應它的未知什麼時候會發生的變化,系統對這種變量的處理不會作優化。因此,volatile通常用於修飾多線程間被多個任務共享的變量和並行設備硬件寄存器等。
對於volatile關鍵字的做用,能夠經過再代碼中插入彙編代碼,測試有無volatile關鍵字對程序最終代碼的影響。
首先創建一個voltest.cpp文件,輸入下面的代碼:
#include <stdio.h> int main() { int i=10; int a=i; print("i=%d\n",a);//下面彙編語句的做用是改變內存中i的值,可是又不讓編譯器知道 _asm { mov dword ptr [ebp-4],20h } int b=i; print('i=%d\n",b); return 0; }
再debug調試版本模式運行程序,輸出結果以下:
i=10
i=32
在release模式下,輸出結果以下:
i=10
i=10
輸出結果明顯代表,在release模式下,編譯器對代碼進行了優化,第二次沒有輸出正確的i值。把i的聲明加上volatile關鍵字,程序示例以下:
#include <stdio.h> int main() { volatile int i=10; int a=i; print("i=%d\n",a);//下面彙編語句的做用是改變內存中i的值,可是又不讓編譯器知道 _asm { mov dword ptr [ebp-4],20h } int b=i; print('i=%d\n",b); return 0; }
分別在debug調試版本和release發佈版本運行,輸出以下所示:
i=10 i=32
一個定義爲volatile的變量是值這個變量可能會被一項不到地改變,這樣編譯器就不會去假設這個變量的值。準確的說,優化器在用到這個變量時,必須每次都當心的從新讀取這個變量的值,而不是使用保存在寄存器裏的備份。
·ADDERT()通常被稱爲斷言,它是一個調試程序時常用的宏。它定義在<assert.h>頭文件中,一般用於判斷程序中是否出現了非法的數據,在程序運行時它計算括號內的表達式的值。若是表達式的值爲false(0),程序報告錯誤,總之運行,以避免致使嚴重後果,同時也便於查找錯誤;若是表達式的值不爲0,則繼續執行後後面語句。在此須要強調一點,ASSERT()捕獲的是非法狀況,而非錯誤狀況,錯誤狀況是必然存在的,而且月底ing須要作出相應的處理,而非法不是,它可能只是漏洞而已。
其用法以下:
ASSERT(n!=0) k=10/n
須要注意的是,ASSERT()只在Debug版本中,編譯的Release版本則被忽略。
還須要注意的一個問題是ASSERT()與assert()的區別,ASSERT()是宏,而assert()是ANSIC標準中規定的函數,它與ASSERT()的功能相似,可是能夠應用在Release版本中。
使用assert()的缺點是,頻繁的調用會極大的影響程序的性能,增長額外的開銷。在調試結束後,能夠在包含#include<assert.h>的語句以前插入#define NDEBUG來禁用assert()調用,示例代碼以下:
#include<stdio.h> #define NDEBUG #include<assert.h>
對於assert()的使用,須要注意如下幾個方面:
(1)在函數開始處檢驗傳入參數的合法性。例如:
assert(nNewSize>=0); assert(nNewSize<=MAX_BUFFER_SIZE);
(2)每一個asset()通常只檢驗一個條件,而不對多個條件進行檢驗,由於同時檢驗多個條件時,若是斷言失敗,則沒法直觀的判斷是哪一個條件失敗。例如,assert(nOffset>=0&&noffset+nSize<=m_nInformationSize)就不是一種高效的方式。將其分開更好
assert(nOffset>=0); assert(noffset+nSize<=m_nInformationSize);
(3)不能使用改變環境的語句,由於assert只在DEBUG時生效,若是這麼作,會使程序在真正運行時遇到問題。例如,assert(i++<100)就是錯誤的。若是執行錯誤,在執行以前i=100,那麼這條語句就不會執行,i++這條命令就沒有執行。而正確的寫法是:
assert(i<100); i++;
(4)並不是全部的assert()都能代替過濾條件,對於有的地方,assert()沒法達到條件過濾的目的。
(5)通常在編程的時候,爲了造成邏輯和視覺上的一致性,會將assert()與後面的語句之間空一行來隔開。
C語言設計原則是把函數做爲程序的構成模塊。在C99標準中,容許main()函數沒有參數,或者有兩個參數(有些實現容許更多的參數,但這只是對標準的擴展)。
命令有時用來啓動一個程序的執行,如int main(int argc,char *argv[])
其中第一個參數表示明亮行參數的數目,它是int型的;
第二個參數argv是一個指向字符串的指針數組,因爲參數的數目並無內在的限制,因此argv指向這組參數值(從本質上是一個數組)的第一個元素,這些元素中的每一個都是指向一個參數文本的指針。
++a表示取a的地址,增長它的內容,而後把值放到寄存器中。
a++表示取a的地址,把它的值裝到寄存器中,而後增長內存中a的值。
在C++中,申請動態內存和釋放內存動態,用new/delete與malloc/free均可以,並且他們的存儲方式相同,new和malloc動態申請的內存都位於堆中,沒法被操做系統自動收回,須要對應的delete和free來釋放空間,同時對於通常的數據類型,如int、char型,他們的效果同樣。
malloc/free是C/C++語言的標準庫函數,在C語言中須要頭文件<stdlib.h>的支持,new/delete是C++的運算符。對於類的對象而言,malloc/free沒法知足動態對象的要求,對象在建立的同時要自動執行構造函數,對象消亡以前要自動執行析構函數,而malloc/free再也不編譯器控制權限以內,沒法執行構造函數和析構函數。
具體而言,new/delete與malloc/free的區別主要表如今如下幾個方面:
(1)new可以自動計算須要分配的的內存空間,而malloc須要手工計算字節數。例如
int *p1=new int [2] int *p2=malloc(2*sizeof(int))
(2)new與delete直接帶具體類型的指針,malloc與free返回void類型的指針。
(3)new是類型安全的,而malloc不是,例如,int *p=new float[2],編譯時就會報錯。而int *p=malloc(2*sizeof(float)),編譯時就沒法指出錯誤來。
(4)new通常由兩步構成,分別是new操做和構造。new操做對應於malloc,但new操做能夠重載,能夠自定義內存分配策略,不作內存分配,甚至分配到非內存設備上,而malloc不行。
(5)new將調用構造函數,而malloc不能,delete將調用析構函數,而free不能。
(6)malloc/free須要庫文件stdlib.h支持,new/delete則不須要庫文件支持。
示例程序以下:
#include "stdafx.h"
#include<iostream> using namespace std; class A { public: A() { cout<<"A is here!"<<endl; } ~A() { cout <<"A is dead!"<<endl; } private: int i; }; int main() { A *pA= new A; delete pA; return 0; }
運行結果以下:
須要注意的是,有資源的申請,就有資源的回收,不然就會出現資源泄露(也稱內存泄漏問題)的問題,因此new/delete必須配對使用。並且delete和free被調用後,內存不會當即收回,指針也不會指向空,delete或free僅僅是告訴操做系統,這一塊內存被釋放了,能夠用做其餘用途。可是,因爲沒有從新對這塊內存進行寫操做,因此內存中的變量數值並無發生改變,出現野指針的狀況。所以,釋放完內存後,應該將指針指向位置爲空。
程序示例以下:
#include "stdafx.h" #include<stdlib.h> #include<stdio.h> #include<string.h> void TestFree() { char *str = (char*)malloc(100); strcpy(str, "helo"); free(str); if (str != NULL) { strcpy(str, "world"); printf("%s", str); } } int main() { TestFree(); return 0; }
程序運行結果以下:
經過上例可值,free或delete調用後,內存其實並無釋放,也沒有爲空,而是還存儲有內容,因此在將free或delete調用後,還須要將其置爲NULL才行。
此時,便產生了一個問題,既然new/delete的功能徹底覆蓋了malloc/free,爲何在C++中沒有取消掉malloc/free呢?
由於C++常要調用C,而C只能用malloc/.free管理動態內存,因此保留了。
export是C++新增的關鍵字,他的做用是實現模板函數的外部調用,樂視與extern關鍵字。爲了訪問其餘關鍵字。爲了訪問其餘代碼文件中的變量或對象(包括基本數據類、結構和類)能夠利用extern來使用這些變量或對象,但對於模板類型,則能夠在頭文件中聲明模板類和模板函數,在代碼文件中使用關鍵字export來定義具體的模板對象和模板函數,而後在其餘用戶代碼中,包含聲明頭文件後,就可使用這些對象和函數了,使用方式以下:
extern int n; extern struct Point p; extern class A a; export template <class T>class Stack<int>s; export template <class T>void f(T&t){...}
在C++中,以下聲明是合法的/
class String { String(const char *p; ... }
String s1="hello";
上例中,String s1="hello"會執行隱式轉換,等價於String s1=String("hello").爲了不這種狀況的發生,C++引入了關鍵字explicit,他能夠組織不該該容許的通過轉換構造函數進行的隱式轉換的發生,聲明爲explicit的構造函數不能在隱式轉換中使用。
在C++中,一個參數的構造函數(或者除了第一個參數外其他參數都有默認值的多參構造函數)通常具備兩個功能:構造器和默認且隱含的類型轉換操做符。因此AAA=XXX,剛好XXX的類型正好也是AAA單參數構造器的參數類型,這時候編譯器就自動調用這個構造器,建立一個AAA的對象。而在某些狀況下,卻違背了程序員的本意。此時就要在這個構造器前面加上explicit修飾,指定這個構造器只能被明確的調用、使用,不能做爲類型轉換操做符被隱含的使用。
程序代碼以下:
class Test1 { public: Test1(int n) { num = n; } private: int num; }; class Test2 { public: explicit Test2(int n) { num = n; }//explicit(顯示)構造函數 private: int num; }; int main() { Test1 t1 = 12;//隱式調用器構造函數,車功能 Test2 t2 = 12;//編譯錯誤,不能隱式調用其構造函數 Test2 t3(12);//顯示調用成功 return 0; }
Test1的構造函數帶一個int型的參數,Test t1=12會隱式轉換成調用Test1的這個構造函數,而Test2的構造函數被聲明爲explicit(顯示),這表示不能經過隱式轉換調用這個構造函數,所以Test2 t2=12會出現編譯錯誤。普通構造函數可以被隱式調用,而explicit構造函數之只能被顯式調用。
C++異常處理使用的關鍵字有try、catch、throw。C++中的異常處理機制只能處理有throw捕獲的異常,沒有捕獲的將被忽略。使用try{}catch{}來捕獲異常,把可能發生異常的代碼放到try{}語句中,後面跟若干個catch(){}負責處理具體的異常類型,這樣一組有try塊和很多於一個catch塊構成了一級異常捕獲。若是本機沒有帶適當類型參數的catch塊,將不能捕獲異常,異常就會向上一級傳遞,函數調用出若是沒有捕獲住異常,則直接跳到更高一層的使用者,若是一直捕獲該異常,C++就會使用默認的異常處理函數,該函數可能會讓程序最終跳出main()函數並致使程序異常終止。
catch的做用是捕獲異常,finally 無論代碼是否有異常都執行。try中若是有return,仍然須要執行finally語句。此種狀況的執行過程以下:
(1)執行return返回語句(return以後的語句內容),計算返回值,暫存在一個臨時變量中。
(2)執行finally語句塊
(3)return原來已經計算獲得的結果值。
若是在finally區段中又調用一次return語句,則try區段中的返回值將會被掩蓋,是的方法調用者獲得的是finally區段中的返回值,這經常與程序編寫的初衷相違背。