C++ Primer Plus 筆記第八章
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 筆記第八章