程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> c++基礎分析

c++基礎分析

編輯:C++入門知識

1、拷貝構造函數(深拷貝和淺拷貝)

拷貝構造函數的形式

復制代碼代碼如下:
Class A
{
public:
  A();
  A(const A&);//拷貝構造函數
} 拷貝構造參數是引用類型,其原因如下:當一個對象以傳遞值的方式傳一個函數的時候,拷貝構造函數自動被調用來生成函數中的對象(符合拷貝構造函數調用的情況)。如果一個對象是被傳入自己的拷貝構造函數,它的拷貝構造函數將會被調用來拷貝這個對象,這樣復制才可以傳入它自己的拷貝構造函數,這會導致無限循環直至棧溢出。

拷貝構造函數調用的三種形式:
1.一個對象作為函數參數,以值傳遞的方式傳入函數體;
2.一個對象作為函數返回值,以值傳遞的方式從函數返回;
3.一個對象用於給另外一個對象進行初始化(常稱為復制初始化)。
總結:當某對象是按值傳遞時(無論是作為函數參數,還是作為函數返回值),編譯器都會先建立一個此對象的臨時拷貝,而在建立該臨時拷貝時就會調用類的拷貝構造函數。

深拷貝和淺拷貝
如果在類中沒有顯式地聲明一個拷貝構造函數,那麼,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的淺拷貝。自定義拷貝構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的拷貝構造函數,提高源碼效率。
在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行淺拷貝,也就是把對象裡的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。事實上這就要用到深拷貝了,要自定義拷貝構造函數。
深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。

深拷貝例子:

#include 
using namespace std;
class A{
public:
  A(int b,char* cstr){
    a=b;
     str=new char[b];
    strcpy(str,cstr);
  }
  A(const A& C){
    a=C.a;
    str=new char[a]; //深拷貝
    if(str!=0)
     strcpy(str,C.str);
  }
  void Show()  {
    cout<


2、C++中的類成員聲明static

在類中聲明static變量或者函數時,初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員,這樣就出現以下作用:

(1)類的靜態成員函數是屬於整個類而非類的對象,所以它沒有this指針,這就導致 了它僅能訪問類的靜態數據和靜態成員函數

(2)不能將靜態成員函數定義為虛函數。

(3)由於靜態成員聲明於類中,操作於其外,所以對其取地址操作,就多少有些特殊 ,變量地址是指向其數據類型的指針 ,函數地址類型是一個“nonmember函數指針”。

(4)由於靜態成員函數沒有this指針,所以就差不多等同於nonmember函數,結果就 產生了一個意想不到的好處:成為一個callback函數,使得我們得以將C++和C-based X W indow系統結合,同時也成功的應用於線程函數身上。

(5)static並沒有增加程序的時空開銷,相反她還縮短了子類對父類靜態成員的訪問 時間,節省了子類的內存空間。

(6)靜態數據成員在<定義或說明>時前面加關鍵字static。

(7)靜態數據成員是靜態存儲的,所以必須對它進行初始化。 (程序員手動初始化,否則編譯時一般不會報錯,但是在Link時會報錯誤)

(8)靜態成員初始化與一般數據成員初始化不同:

初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆;
初始化時不加該成員的訪問權限控制符private,public等;
初始化時使用作用域運算符來標明它所屬類;
所以我們得出靜態數據成員初始化的格式:
<數據類型><類名>::<靜態數據成員名>=<值>

(9)為了防止父類的影響,可以在子類定義一個與父類相同的靜態變量,以屏蔽父類的影響。這裡有一點需要注意:我們說靜態成員為父類和子類共享,但我們有重復定義了靜態成員,這會不會引起錯誤呢?不會,我們的編譯器采用了一種絕妙的手法:name-mangling 用以生成唯一的標志

1、靜態數據成員

#include  
class Myclass { 
public: 
    Myclass(int a,int b,int c); 
    void GetSum(); 
private: 
    int a,b,c; 
    static int Sum;//聲明靜態數據成員 
}; 
int Myclass::Sum=0;//定義並初始化靜態數據成員 

Myclass::Myclass(int a,int b,int c) 
{ 
    this->a=a; 
    this->b=b; 
    this->c=c; 
    Sum+=a+b+c; 
} 

void Myclass::GetSum() 
{ 
    cout < <"Sum=" < 
2、靜態成員函數

#include  
class Myclass { 
    public: 
    Myclass(int a,int b,int c); 
    static void GetSum();/聲明靜態成員函數 
private: 
    int a,b,c; 
    static int Sum;//聲明靜態數據成員 
}; 
int Myclass::Sum=0;//定義並初始化靜態數據成員 

Myclass::Myclass(int a,int b,int c) 
{ 
    this->a=a; 
    this->b=b; 
    this->c=c; 
    Sum+=a+b+c; //非靜態成員函數可以訪問靜態數據成員 
} 

void Myclass::GetSum() //靜態成員函數的實現 
{ 
    // cout < 
3、Const關鍵字修飾變量、成員函數

3.1.用const 修飾函數的參數

如果參數作輸出用,不論它是什麼數據類型,也不論它采用“指針傳遞”還是“引用傳遞”,都不能加const 修飾,否則該參數將失去輸出功能。const 只能修飾輸入參數:

如果輸入參數采用“指針傳遞”,那麼加const 修飾可以防止意外地改動該指針,起到保護作用。

例如StringCopy 函數:

void StringCopy(char *strDestination, const char *strSource);

其中strSource 是輸入參數,strDestination 是輸出參數。給strSource 加上const修飾後,如果函數體內的語句試圖改動strSource 的內容,編譯器將指出錯誤。

如果輸入參數采用“值傳遞”,由於函數將自動產生臨時變量用於復制該參數,該輸入參數本來就無需保護,所以不要加const 修飾。

例如不要將函數void Func1(int x) 寫成void Func1(const int x)。同理不要將函數void Func2(A a) 寫成void Func2(const A a)。其中A 為用戶自定義的數據類型。

對於非內部數據類型的參數而言,象void Func(A a) 這樣聲明的函數注定效率比較底。因為函數體內將產生A 類型的臨時對象用於復制參數a,而臨時對象的構造、復制、析構過程都將消耗時間。

為了提高效率,可以將函數聲明改為void Func(A &a),因為“引用傳遞”僅借用一下參數的別名而已,不需要產生臨時對象。但是函數void Func(A &a) 存在一個缺點:

“引用傳遞”有可能改變參數a,這是我們不期望的。解決這個問題很容易,加const修飾即可,因此函數最終成為void Func(const A &a)。

對於非內部數據類型的輸入參數,應該將“值傳遞”的方式改為“const 引用傳遞”,目的是提高效率。例如將void Func(A a) 改為void Func(const A &a)

對於內部數據類型的輸入參數,不要將“值傳遞”的方式改為“const 引用傳遞”。否則既達不到提高效率的目的,又降低了函數的可理解性。例如void Func(int x) 不應該改為void Func(const int &x)

3.2 .用const 修飾函數的返回值
如果給以“指針傳遞”方式的函數返回值加const 修飾,那麼函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加const 修飾的同類型指針。例如函數
const char * GetString(void);
如下語句將出現編譯錯誤:
char *str = GetString();
正確的用法是
const char *str = GetString();
如果函數返回值采用“值傳遞方式”,由於函數會把返回值復制到外部臨時的存儲單元中,加const 修飾沒有任何價值。
例如不要把函數int GetInt(void) 寫成const int GetInt(void)。
同理不要把函數A GetA(void) 寫成const A GetA(void),其中A 為用戶自定義的數據類型。
如果返回值不是內部數據類型,將函數A GetA(void) 改寫為const A & GetA(void)的確能提高效率。但此時千萬千萬要小心,一定要搞清楚函數究竟是想返回一個對象的“拷貝”還是僅返回“別名”就可以了,否則程序會出錯。
函數返回值采用“引用傳遞”的場合並不多,這種方式一般只出現在類的賦值函數中,目的是為了實現鏈式表達。

例如:
class A
{
A & operate = (const A &other); // 賦值函數
};
A a, b, c; // a, b, c 為A 的對象

a = b = c; // 正常的鏈式賦值
(a = b) = c; // 不正常的鏈式賦值,但合法
如果將賦值函數的返回值加const 修飾,那麼該返回值的內容不允許被改動。上例中,語句 a = b = c 仍然正確,但是語句 (a = b) = c 則是非法的。

3.3. const 成員函數
任何不會修改數據成員(即函數中的變量)的函數都應該聲明為const 類型。如果在編寫const 成員函數時,不慎修改了數據成員,或者調用了其它非const 成員函數,編譯器將指出錯誤,這無疑會提高程序的健壯性。以下程序中,類stack 的成員函數GetCount 僅用於計數,從邏輯上講GetCount 應當為const 函數。編譯器將指出GetCount 函數中的錯誤。

class Stack
{
public:
	void Push(int elem);
	int Pop(void);
	int GetCount(void) const; // const 成員函數
private:
	int m_num;
	int m_data[100];
};
int Stack::GetCount(void) const
{
++ m_num; // 編譯錯誤,企圖修改數據成員m_num
Pop(); // 編譯錯誤,企圖調用非const 函數
return m_num;
}
const 成員函數的聲明看起來怪怪的:const 關鍵字只能放在函數聲明的尾部,大概是因為其它地方都已經被占用了。
關於Const函數的幾點規則:
a. const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數.
b. const對象的成員是不可修改的,然而const對象通過指針維護的對象卻是可以修改的.
c. const成員函數不可以修改對象的數據,不管對象是否具有const性質.它在編譯時,以是否修改成員數據為依據,進行檢查.
d. 然而加上mutable修飾符的數據成員,對於任何情況下通過任何手段都可修改,自然此時的const成員函數是可以修改它的。

4.C++引用詳解

4.1、引用引入了對象的一個同義詞。定義引用的表示方法與定義指針相似,只是用&代替了*。

  例如: Point pt1(10,10);

  Point &pt2=pt1; 定義了pt2為pt1的引用。通過這樣的定義,pt1和pt2表示同一對象,具有相同的地址。

  需要特別強調的是引用並不產生對象的副本,僅僅是對象的同義詞。因此,當下面的語句執行後:

  pt1.offset(2,2);

  pt1和pt2都具有(12,12)的值。

  引用必須在定義時馬上被初始化,因為它必須是某個東西的同義詞。你不能先定義一個引用後才初始化它。例如下面語句是非法的:

  Point &pt3;

  pt3=pt1;

4.2、引用參數

  1、傳遞可變參數

  傳統的c中,函數在調用時參數是通過值來傳遞的,這就是說函數的參數不具備返回值的能力。

  所以在傳統的c中,如果需要函數的參數具有返回值的能力,往往是通過指針來實現的。比如,實現

  兩整數變量值交換的c程序如下:

  void swapint(int *a,int *b)

  {

  int temp;

  temp=*a;

  *a=*b;

  *b=temp;

  }

  使用引用機制後,以上程序的c++版本為:

  void swapint(int &a,int &b)

  {

  int temp;

  temp=a;

  a=b;

  b=temp;

  }

  調用該函數的c++方法為:swapint(x,y); c++自動把x,y的地址作為參數傳遞給swapint函數。

  2、給函數傳遞大型對象

  當大型對象被傳遞給函數時,使用引用參數可使參數傳遞效率得到提高,因為引用並不產生對象的副本,也就是參數傳遞時,對象無須復制。

4.3、引用返回值

  如果一個函數返回了引用,那麼該函數的調用也可以被賦值。這裡有一函數,它擁有兩個引用參數並返回一個雙精度數的引用:

  double &max(double &d1,double &d2)

  {

  return d1>d2?d1:d2;

  }

  由於max()函數返回一個對雙精度數的引用,那麼我們就可以用max() 來對其中較大的雙精度數加1:

  max(x,y)+=1.0;

  4.5、常引用

  常引用聲明方式:const 類型標識符 &引用名=目標變量名;

  用這種方式聲明的引用,不能通過引用對目標變量的值進行修改,從而使引用的目標成為const,達到了引用的安全性。

int a ;
const int &ra=a;
ra=1; //錯誤
a=1; //正確

  這不光是讓代碼更健壯,也有些其它方面的需要。

  假設有如下函數聲明:

string foo( );
void bar(string & s);

  那麼下面的表達式將是非法的:

bar(foo( ));
bar('hello world');

  原因在於foo( )和'hello world'串都會產生一個臨時對象,而在C++中,這些臨時對象都是const類型的。因此上面的表達式就是試圖將一個const類型的對象轉換為非const類型,這是非法的。

引用型參數應該在能被定義為const的情況下,盡量定義為const 。

4.6、引用和指針的混用

在堆中創建一塊內存區域,必須要用指針指向它,否則該區域空間無法訪問,當然我們可以用引用來引用指向內存空間的指針,如:

int *p = new int;

int &r = *p;

這樣r就成了指針p讀取到的值的別名,我們可以將4賦給*p的別名:

r = 4;

cout << *p; //值為4

但是,我們不能直接引用來指向堆中新建空間,因為這個引用只是別名,它不可以作為指針使用,如:

int &r = new int; //這樣是錯誤的。

它會提示不能將指針轉化為引用,因此需要加上星號(*),如:

int *&r = new int; //正確

其意思是創建一個對空間,定義&r作為該空間的引用,這樣r就成了該空間的別名。





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