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

C++基礎細節2

編輯:C++入門知識

1.關於引用和指針   概念上,引用(&)並不是對象,而是一個已經存在的對象的別名;引用不可以重新綁定到另外一個對象,因此引用必須初始化。(類比const,一經定義就不能修改,所以必須初始化,是同樣的道理。)   引用和指針都是實現了其他對象的間接訪問。不同的是:指針本身就是一個對象,允許對指針進行賦值和拷貝;指針無需在定義時賦初值。   對於引用的概念,通過這段代碼加深印象:       int ival=12;     int *p=&ival;     int &refval=ival;     int *p1=&refval; int &refval=ival;中的&是代表引用聲明符號;而int *p1=&refval;的&則代表取地址。   如下圖的watch中所示,可以看出p和p1這兩個指針的值(所存放的地址值0x0039F1D8),以及這兩個指針所指向的值(12)是完全一樣的(其實有點廢話,既然兩個指針都是指向同一個地址,自然指向的值是一樣的),也就是ival和它的引用refval是等價的。   注意:雖然指針p1初始化為&refval,而&refval和&ival這兩個值是不一樣的,也就是refval和ival是存放在內存中的不同地址上,但是int *p1=&refval這句初始化之後,p1指針上存的值其實並不是refval的地址,而是refval所引用的變量ival的地址。因此,這裡就更明確了:引用的作用就相當於代言者,這個代言者所說所做的一切都代表了原始對象,與引用自身無關。       由於引用不是對象,所以不存在指向引用的指針。我想基本上這也上面那句int *p1=&refval之後,p1的值竟然是ival的地址的概念上的輔證。   但是存在指針的引用。   2.關於const   如果利用一個對象去初始化另外一個對象,則他們是否是const都無所謂。常量特性僅僅是用於限定其初始化之後不可修改。   如果想在多個文件中共享const對象,必須在變量的定義之前添加extern關鍵字。   3.C風格字符串    C風格字符串不是一種類型,而是為了表達和使用字符串而形成的一種約定俗成的寫法,按此習慣書寫的字符串存放在字符數組中並用空字符('\0')結束。   char ca[] = { 'C', '+', '+' }; cout << strlen(ca) << endl;   上面的示例中,ca雖然是一個字符數組,但是它並沒有以'\0'結束,因此這段代碼輸出的結果不可知。strlen函數在執行的時候可能沿著ca在內存中的位置不斷向前尋找,直到遇到'\0'才結束。   而只有這樣:   char ca[] = { 'C', '+', '+', '\0' }; cout << strlen(ca) << endl;   才能保證輸出的結果是3.       再定義兩個字符數組來說明:   char ca1[] = "string 1"; char ca2[] = "string 2";   由於在使用數組的時候,其實真正使用的是指向數組的首元素的指針。因此我們不能使用if(ca1<ca2)這樣的語句進行這兩個字符數組的比較;也不能使用ca1+ca2這樣的語句進行字符串的串聯操作。   我們必須使用strcmp進行字符串比較操作;使用strcpy和strcat進行字符串的拷貝和連接操作,而且在strcpy和strcat函數的使用的時候,我們要非常仔細的檢查字符數組的容量:       char sumStr[40];//注意數組容量,調試的時候又想起了最近看到的關於“燙燙燙燙”的冷笑話     strcpy(sumStr, ca1);     strcat(sumStr, "-");     strcat(sumStr, ca2);     (ps:在vs2013上進行編譯的時候,編譯器直接告訴我:'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead       char sumStr[40];     strcpy_s(sumStr, ca1);     strcat_s(sumStr, "-");     strcat_s(sumStr, ca2);     需要說的是:strcpy_s、strcat_s是VS後續版本中微軟新推出的更安全的函數,並非標准庫裡面的。所以還是建議使用string以保證可移植)   4.函數返回類型   函數返回類型不能是數組類型或者函數類型,但是可以是指向數組或者函數的指針。   5.通過引用避免拷貝   針對函數形參,拷貝大的類類型對象或者容器時效率比較低,甚至有的類型(比如IO類型)根本不支持對象拷貝,這種情況就必須使用引用參數。另外,為了避免在函數內修改實參,我們可以使用常量引用。   bool cmp_length(const string &s1, const string &s2) {     return s1.size() > s2.size(); } 另外,比較好的習慣是:&,*符號和參數名寫在一起,不要和類型連寫,以免理解上的誤會。   6.類   構造函數:   構造函數不能使用const。只有在類沒有聲明任何構造函數的時候,編譯器才會自動生成默認無參構造函數。因此,一旦我們定義了其他默認構造函數,那麼除非我們在定義一個無參數的構造函數,否則類將沒有默認構造函數。如果類包含有內置類型或者復合類型的成員,那麼只有在這些成員全部都賦予了類內的初始值的時候,這個類才適合於使用合成的默認構造函數。C++11新標准允許使用=default來要求編譯器生成默認構造函數。   大多數情況下,使用構造函數初始化列表,或者提供參數,在構造函數的函數體中使用賦值語句在效果上沒有什麼區別。但是針對const、引用,這種則必須通過構造函數初始化來處理。   如下這段處理就是錯誤的:   復制代碼 class A { public:     A()     {//提示報錯:常量成員b和引用成員c沒有提供初始值         b = 0;//錯誤,只讀根本不能修改         c = 0;//沒有初始化     } private:     int a;     const int b;     int &c; }; 復制代碼     正確的方式是使用構造函數初始化列表:   復制代碼 class A { public:     A(int x) :b(x), c(x)     {       } private:     int a;     const int b;     int &c; }; 復制代碼         友元(friend):   友元的聲明僅僅是制定了訪問的權限,而並不是一個通常意義上的函數聲明。如果我們希望類的用戶能夠調用某個友元函數,那麼我們必須在友元生命之外再專門這個函數進行一次聲明。雖然許多編譯器並不強制限定友元函數必須在使用之前在類外聲明,但是最好還是提供一個函數的獨立聲明,盡量別讓程序依賴於編譯器。   友元除了可以用於普通的非成員函數,也可以用於類、以及類的成員函數。另外,友元函數不存在傳遞性。比如說類A中聲明了類B是A的友元,類B中聲明了類C是B的友元,我們不能僅僅根據這個就認為C是A的友元。也就是說每個類需要自己控制自己的友元類和友元函數。   友元函數可以定義在類的內部。不過就算將友元函數的定義放在類的內部,我們也必須先在函數外面有對這個函數的聲明之後才能對這個友元函數進行調用。如下代碼:   復制代碼 class X {     friend void f()     {         //函數體     }     X(){ f(); } //error C3861 : “f” : 找不到標識符     void g();   };   void X::g(){ f(); } //error C3861 : “f” : 找不到標識符 void f(); 復制代碼 如果將f的聲明放在類X定義之前就不會報錯了。   復制代碼 void f();//聲明提前 class X {     friend void f()     {         //函數體     }     X(){ f(); } //編譯通過     void g();   };   void X::g(){ f(); } //編譯通過 復制代碼         可變數據成員:   有些情況下,我們希望能在即使是const成員函數中也能夠修改某個數據成員,可以通過在聲明變量的時候加上mutable關鍵字做到這一點:   復制代碼 class Screen{ public:     void some_member() const;     void print_count(); private:     mutable size_t count = 0; };   void Screen::some_member() const {     ++count; }   void Screen::print_count() {     cout << count << " "; }   int main(array<System::String ^> ^args) {     Console::WriteLine(L"Hello World");            Screen item = Screen();       item.print_count();//輸出0       item.some_member();     item.print_count();//輸出1 } 復制代碼 作用域:   如下代碼,可以通過類名::成員變量,或者::變量的方式來強制訪問   復制代碼 int param = 2; class Scorp { public:     void print(int param)     {         cout << param << endl;//形參作用域         cout << Scorp::param << endl;//類中作用域,同this->param         cout << ::param << endl;//類外作用域     } private:     int param = 1; };   int main(array<System::String ^> ^args) {     Console::WriteLine(L"Hello World");       Scorp scorp = Scorp();     scorp.print(3);//【param】輸出3,【Scorp::param】輸出1,【::param】輸出2 } 復制代碼 轉換構造函數:   如果構造函數只接受一個參數,那麼存在一種這個類型的隱式轉換機制:從構造函數的參數類型向類類型隱式轉換。如下代碼:   復制代碼 class A { public:     A(int param)     {         a = param;     }     const void print(const A &item)const     {         cout << item.a << "【注意另一個a】--> " << a << endl;     } private:     int a;     int b;     int c; };   int main(array<System::String ^> ^args) {     A a_instance = A(6);     a_instance.print(9); } 復制代碼 輸出結果是:9【注意另一個a】--> 6。也就是說,在print函數執行的時候,item.a等於9,a等於6。   注意這裡發生了兩次構造函數的執行,第一次是定義a_instance並且初始化的時候這時候param為6;第二次是調用a_instance.print(const A &item)函數的時候,特別要注意的是:這裡的形參是一個常量引用,因此這裡可以使用隱式轉化機制,傳入A的單個參數的構造函數的形參(整型),用這個方式來隱式處理:編譯器通過給定的int型的9自動調用對應的構造函數創建了一個A類型的對象,生成的這個臨時對象傳給了print函數,並且作為item在函數體中使用,自然item.a的值就是9。   另外要注意的是這裡我們的參數使用的int這個內置類型,所以處理的時候能夠使用print(9)這種字面量形式的參數。如果構造函數的唯一參數不是內置類型,是其它的比如string,那麼這裡在處理的時候就不能直接寫成print("9"),而是要分成兩句書寫:   string str="9";   a_instance.print(str);   原因是在這種非內置類型的使用的時候,傳入的是"9",實際上會調用string的默認構造函數string("9")進行一次轉換,而類類型的轉換只允許進行一次。int這樣的內置類型則沒有所謂的默認構造轉換的操作。   上面的書寫我們也可以合起來寫成a_instance.print(string("9")) 

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