1. 程式人生 > >C++ Primer Plus 筆記第八章

C++ Primer Plus 筆記第八章

機制 設計 改變 etl cin 它的 數組 程序 類對象

C++內聯函數:

   內聯函數的編譯代碼與其他程序代碼 “內聯” 起來了,編譯器將使用相應的函數代碼替換函數調用;

   對於內聯函數,程序無需跳到另一個位置處執行代碼,再跳回來:

    內聯函數的運行速度比常規函數稍快;

    但是需要占用更多的內存

   使用內聯函數:

    1. 在函數聲明前加上關鍵字 inline;

    2. 在函數定義前加上關鍵字 inline;

    3. 通常做法是省略原型,將整個定義(函數頭和所有函數代碼)放在本應提供原型的地方

   內聯函數與常規函數一樣,也是按值傳遞參數

引用變量:

   C++新增了一種復合類型——引用變量,引用是已定義的變量的別名

   引用變量的主要用途是用作函數的形參,通過引用變量用作參數,函數使用原始數據,而不是拷貝數據;

創建引用變量:

   int rats;

   int & rodents = rats; // 此處引用聲明允許將 rats 和 radents 互換——他們指向相同的值和內存單元

   int & 表示的是指向 int 的引用

   記住:必須在聲明引用時將其初始化(所以引用更接近與 const 指針)

將引用用作函數參數:

   按引用傳遞允許被調用函數能夠訪問調用函數中的變量(C語言只能按值傳遞(還可以指針)——拷貝);

   參數傳遞對比:

    調用:

        swapr ( wallet1, wallet2);    // 引用傳遞調用

        swapp ( &wallet1, &wallet2 );  // 指針傳遞調用

        awapv (wallet1, wallet2);   // 按值傳遞調用

    原型:

        void ( int & a, int & b);     // 引用調用原型,變量a,b是 wallet1 和 wallet2 的別名

        void ( int * a, int * b);      // 指針傳遞原型,需要在函數使用 p 和 q 的整個過程使用解除引用操作符 *

        void ( int a, int b);        // 按值傳遞原型,變量 a,b 是參數 wallet1 和 wallet2 的拷貝,互補影響

   被調用函數中引用變量修改,會改變調用函數中的值,如不想修改則應使用常量引用:

    double refcube ( const double &ra ); // 這樣做,當編譯器發現代碼修改了 ra 的值時,將發生錯誤消息

臨時變量、引用參數和 const:

   如果實參與形參不匹配,C++將生成臨時變量(僅當參數為 const 引用時):

    1. 實參的類型正確,但不是左值;

    2. 實參的類型不正確,但可以轉換成正確的類型

盡可能使用 const:

   1. 使用 const 可以避免無意中修改數據的編程錯誤;

   2. 使用 const 使函數能夠處理 const 和非 const 實參,否則將只能接受非 const 數據;

   3. 使用 const 引用使函數能夠正確生成並使用臨時變量

何時使用引用參數:

   使用引用參數的原因:

    1. 程序員能夠修改調用函數中的數據對象;

    2. 通過傳遞引用而不是整個數據對象,可以提高程序運行速度(數據對象較大時更重要)

   對於使用傳遞的值而不作修改的函數:

    1. 如果數據對象很小,如內置數據類型或小型結構,按值傳遞;

    2. 如果數據對象是數組,則使用指針,因為這是唯一的選擇,並將指針聲明為指向 const 的指針;

    3. 如果數據對象是較大的結構,則使用 const 指針或 const 引用,可以節省復制結構所需的時間和空間;

    4. 若果數據對象是類對象,則使用 const 引用。傳遞類對象參數的標準方式是按引用傳遞

   對於修改調用函數中數據的函數:

    1. 如果數據對象是內置數據類型,則使用指針;

    2. 如果數據對象是數組,則只能使用指針;

    3. 如果數據對象是結構,則使用引用或指針;

    4. 如果數據對象是類對象,則使用引用

將引用用於結構:

   引用非常適用於結構和類(C++的用戶自定義類型),引入引用主要是為了用於這些類型,而不是基本的內置類型;

   例程:

 1 #include<iostream>
 2 using namespace std;
 3 struct sysop {
 4     char name[26];
 5     char quote[64];
 6     int used;
 7 };
 8 const sysop & use(sysop & sysopref);
 9 
10 int main()
11 {
12     sysop looper = 
13     {
14         "Rick \"Fortan\" Looper",
15         "I`m a goto kind of guy.",
16         0
17     };
18 
19     use(looper);
20     cout << "Looper: " << looper.used << " use(s)\n";
21 
22     sysop copycat;
23     copycat = use(looper);
24     cout << "Looper: " << looper.used << " use(s)\n";
25     cout << "copycat: " << copycat.used << " use(s)\n";
26     cout << "use(looper): " << use(looper).used << " use(s)\n";
27 
28     return 0;
29 }
30 
31 const sysop & use(sysop & sysopref)
32 {
33     cout << sysopref.name << " says:\n";
34     cout << sysopref.quote << endl;
35     sysopref.used++;
36     return sysopref;
37 }

   函數說明:

    1. 使用指向結構的引用

      use ( looper );

      函數調用結構 looper 按引用傳遞給 use() 函數,使得 sysopref 成為 looper 的別名

    2. 將引用作為返回值

      通常,返回機制將返回值復制到臨時存儲區域中,隨後調用程序將訪問該區域;

      返回引用意味著調用程序將直接訪問返回值,而不需要拷貝;

      通常,引用將指向傳遞給函數的引用,因此調用函數實際上是直接訪問自己的一個變量

    記住: 返回引用的函數實際上是被引用變量的別名

    3. 使用函數調用來訪問結構成員

      cout << "use(looper): " << use(looper).used << " use(s)\n;

      函數 use() 返回一個指向 looper 的引用,因此上述代碼與下面兩行代碼等效:

      use ( looper );

      cout << " use(looper): " << looper.used << " use(s) \n ";

 返回引用時需要註意的問題:

   避免返回當函數終止時不再存在的內存單元引用(返回引用時最重要的一點)

    1. 返回一個作為參數傳遞給函數的引用,將指向調用函數使用的數據,因此返回的引用也指向這些數據

    2. 使用 new 來分配新的存儲空間:

      sysop * psysop = new sysop;

 為何將 const 用於引用返回類型:

   const sysop & use(sysop & sysopref);

   const sysop & 表示不能使用返回的引用來直接修改它指向的結構

   省略 const 的情況:

    use ( looper ).used = 10;

    由於 use() 返回一個指向 looper 的引用,上述代碼將與下面的代等效:

    use ( looper );

    looper.used = 1;

     省略 const 後,可以編寫更簡短但含義更模糊的代碼

   通常,將返回類型聲明為 const 引用,可以減程序的模糊特性

將引用用於類對象:

   將類對象傳遞給函數時,C++通常做法是使用引用。例如可以通過使用引用,讓函數將類 string、ostream、istream、ofstream和ifstream等類對象作為參數

   將引用用於 string 類例程:

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 
 5 string version1(const string & s1, const string & s2);
 6 const string & version2(string & s1, const string & s2);
 7 //const string & version3(string & s1, const string & s2);
 8 
 9 int main()
10 {
11     string input;
12     string copy;
13     string result;
14 
15     cout << "Enter a string: ";
16     getline(cin, input);
17     copy = input;
18     cout << "Your string as entered: " << input << endl;
19     result = version1(input, "***");
20     cout << "Your string enhanced: " << result << endl;
21     cout << "Your original string: " << input << endl;
22 
23     result = version2(input, "***");
24     cout << "Your string enhanced: " << result << endl;
25     cout << "Your original string: " << input << endl;
26 
27     return 0;
28 }
29 
30 string version1(const string & s1, const string & s2)
31 {
32     string temp;
33     temp = s2 + s1 + s2;
34     return temp;
35 }
36 
37 const string & version2(string & s1, const string &s2)
38 {
39     s1 = s2 + s1 + s2;
40     return s1;
41 } 

   可以將C-風格字符串用作 string 對象引用參數:

    如果形參類型為 const string &,在調用函數時,使用的實參可以是 string 對象或C-風格字符串:

      因此代碼 result = version1(input, "***"); 將 "***" 賦給string對象是可行的

對象、繼承和引用:

   將特性從一個類傳遞給另一個類的語言特性被稱為繼承

   ostream 是基類,ofstream 是派生類,派生類繼承了基類方法,意味著 ofstream 可以使用基類的特性,如格式化方法 precision(),setf();

   基類引用可以指向派生類對象,而無需進行強制類型轉換:

    可以定義一個接收基類引用作為參數的函數,調用該函數時,可以將基類對象作為參數也可以將派生類對象作為參數;

    參數類型為 ostream & 的函數可以接受 ostream 對象(如 cout)或自己聲明的 ofstream 對象作為參數

   例程:

 1 #include<iostream>
 2 #include<fstream>
 3 #include<cstdlib>
 4 using namespace std;
 5 
 6 void file_it(ostream & os, double fo, const double fe[], int n);
 7 const int LIMIT = 5;
 8 
 9 int main(void)
10 {
11     ofstream fout;
12     const char * fn = "ep-data.txt";
13     fout.open(fn);
14     if (!fout.is_open())
15     {
16         cout << "Can`t open " << fn << ". Bye.\n";
17         exit(EXIT_FAILURE);
18     }
19     double objective;
20     cout << "Enter the focal length of your telescope objective in mm: ";
21     cin >> objective;
22     double eps[LIMIT];
23     cout << "Enter the focal lengths, in mm, of " << LIMIT << " eyepieces:\n";
24     for (int i = 0; i < LIMIT; i++)
25     {
26         cout << "Eyepiece #" << i + 1 << ": ";
27         cin >> eps[i];
28     }
29     file_it(fout, objective, eps, LIMIT);
30     file_it(cout, objective, eps, LIMIT);
31     cout << "Done\n";
32     return 0;
33 }
34 
35 void file_it(ostream & os, double fo, const double fe[], int n)
36 {
37     ios_base::fmtflags initial;
38     initial = os.setf(ios_base::fixed);  // save initial formatting state
39     os.precision(0);
40     os << "Focal length of objective: " << fo << " mm\n";
41     os.setf(ios::showpoint);
42     os.precision(1);
43     os.width(12);
44     os << "f eyepiece";
45     os.width(15);
46     os << "magnification" << endl;
47     for (int i = 0; i < n; i++)
48     {
49         os.width(12);
50         os << fe[i];
51         os.width(15);
52         os << int(fo / fe[i] + 0.5) << endl;
53     }
54     os.setf(initial);  // restore initial formatting state
55 }

  file_it (fout, objective, eps, LIMIT);  // 將目鏡數據寫入到文件 ep-data.txt 中

  file_it (cout, objective, eps, LIMIT); // 將目鏡數據顯示到屏幕上  

   程序演示了如何使用 ostream 類中的格式化方法:

    方法 setf() 能夠設置各種格式化狀態:

      setf (ios_base::fixed) 將對象置於使用定點表示法的模式;

      setf (ios_base::showpoint) 將對象置於顯示小數點的模式

    方法 precision() 指定此案時多少位小數(假定對象處於定點模式下);

    方法 width() 設置下一次輸出操作使用的字符段寬度,只在顯示下一個值時有效,然後將恢復默認設置。

   每個對象都存儲了自己的格式化設置,因此,當程序將 cout 或者 fout 傳遞給 file_it 時,先修改格式化設置再恢復

默認參數:

   默認參數指的是當函數調用中省略了實參時自動使用的一個值:

    char * left ( const char * str,int n = 1 ):

      默認參數值是初始化值,因此原型將 n 初始化為 1,調用函數 left 如果省略參數 n,則它的值為1;

      如果沒有省略參數 n 的傳遞,則傳遞值將覆蓋默認參數值

   對於帶參數列表的函數,必須從右向左添加默認值;

   實參按從左到右的順序依次被賦給相應的形參;

   通過使用默認參數可以減少要定義的析構函數、方法以及方法重載的數量

函數重載:

   函數多態(函數重載),讓我們可以使用多個同名的函數:

    可以通過函數重載來設計一系列函數——完成相同的工作,但使用不同的參數列表;

   函數重載的關鍵是函數的參數列表——也稱為函數特征標

   需要註意,是特征標而不是函數類型使得可以對函數進行重載

   函數重載例程:

 1 #include<iostream>
 2 using namespace std;
 3 unsigned long left(unsigned long num, unsigned ct);   // 處理整數
 4 char * left(const char * str, int n = 1);        // 處理字符串
 5 
 6 int main()
 7 {
 8     char * trip = "Hawaii!! ";
 9     unsigned long n = 12345678;
10     int i;
11     char *temp;
12     for (i = 1; i < 10; i++)
13     {
14         cout << left(n, i) << endl;
15         temp = left(trip, i);
16         cout << temp << endl;
17         delete[] temp;
18     }
19     return 0;
20 }
21 unsigned long left(unsigned long num, unsigned ct)
22 {
23     unsigned digits = 1;
24     unsigned long n = num;
25 
26     if (ct == 0 || num == 0)
27         return 0;
28     while (n /= 10)
29         digits++;
30     if (digits > ct)
31     {
32         ct = digits - ct;
33         while (ct--)
34             num /= 10;
35         return num;
36     }
37     else
38         return num;
39 }
40 
41 char * left(const char * str, int n)
42 {
43     if (n < 0)
44         n = 0;
45     char *p = new char[n + 1];
46     int i;
47     for (i = 0; i < n && str[i]; i++)
48         p[i] = str[i];
49     while (i <= n)
50         p[i++] = \0;
51     return p;
52     
53 }

何時使用函數重載:

   僅當函數基本上執行相同的任務,但不使用不同形式的數據時,才應采用函數重載

函數模板:

   函數模板是通用函數描述——使用通用類型來定義函數;

   通過將類型作為參數傳遞給模板,可以使編譯器生成該類型的函數;

   建立交換模板:

    template <class Any> // template <typename Any>

    void Swap (Any &a,Any &b)

    {

      Any temp;

      temp = a;

      a = b;

      b = temp;

    }

 重載的模板:

   並非所有的類型都使用相同的算法,可以像重載常規函數定義那樣重載模板定義;

   被重載的模板的函數特征標必須不同;

    template <class Any>

    void Swap (Any &a,Any &b);

    template <class Any>

    void Swap (Any *a,Any *b,int n);

 顯示具體化:

   可以提供一個具體化函數定義——稱為顯示具體化;

   當編譯器找到與函數調用匹配的具體化定義時,將使用該定義,而不再尋找模板;

   C++標準選擇的具體化方法:

    1. 對於給定的函數名,可以有非模板函數、模板函數和顯示具體化模板函數以及他們的重載版本;

    2. 顯示具體化的原型和定義應以 template<> 打頭,並通過名稱來指出類型;

    3. 具體化將覆蓋常規模板,而非模板函數將覆蓋具體化的原型

     void Swap (job &,job &); // 非模板函數原型

    template <class Any>

    void Swap (Any &,Any &); // 模板函數原型

    template <> void Swap<job> (job &,job &); // 具體化原型

 實例化和具體化:

   模板並非函數定義,編譯器使用模板為特定類型生成函數定義時,得到的是模板實例:

    函數調用 Swap(i,j) 導致編譯器生成一個 Swap() 的一個實例——隱式實例

    還可以直接命令編譯器創建特定的實例,如 Swap<int>() ——顯示化實例

      temeplate void Swap<int> (int,int);

   顯示具體化使用下面兩個等價聲明之一:

    template <> void Swap <int> (int &,int &);

    template <> void Swap ( int &,int &);

  

  

C++ Primer Plus 筆記第八章