一、函數
函數的定義
函數是一個定義好的、可重用的功能模塊
函數的構成:
- 函數名
- 函數參數
- 函數返回值
- 函數體
語法
函數調用
調用函數需要先聲明函數原型
- 若函數定義在調用點之前,可以不另外聲明
- 若函數定義在調用點之后,必須要在調用函數前聲明函數原型
函數原型:類型標識符 被調用函數名(含類型說明的形參表)
函數調用形式
函數名(實參列表)
編寫一個求x的n次方的函數
#include <iostream> using namespace std; //計算x的n次方 double power(double x, int n) { double val = 1.0; while (n--) val *= x; return val; } int main() { cout << "5 to the power 2 is " << power(5, 2) << endl; return 0; }
數制轉換
輸入一個8位二進制數,將其轉換為十進制數輸出。
例如:從鍵盤輸入1101
11012=1×23+1×22+0×21+1×20=1310
所以,程序應輸出13
源代碼:
#include <iostream> using namespace std; double power (double x, int n); //計算x的n次方 int main() { int value = 0; cout << "Enter an 8 bit binary number "; for (int i = 7; i >= 0; i--) { char ch; cin >> ch; if (ch == '1') value += static_cast<int>(power(2, i)); } cout << "Decimal value is " << value << endl; return 0; } double power (double x, int n) { double val = 1.0; while (n--) val *= x; return val;
編寫程序求π的值
l π的計算公式如下:
l 其中arctan用如下形式的級數計算:
l 直到級數某項絕對值不大於10-15為止;π和x均為double型。
arctan函數
// 函數程序 #include <iostream> using namespace std; double arctan(double x) { double sqr = x * x; double e = x; double r = 0; int i = 1; while (e / i > 1e-15) { double f = e / i; r = (i % 4 == 1) ? r + f : r - f; e = e * sqr; i += 2; } return r; } // 主程序 int main() { double a = 16.0 * arctan(1/5.0); double b = 4.0 * arctan(1/239.0); //注意:因為整數相除結果取整,如果參數寫1/5,1/239,結果就都是0 cout << "PI = " << a - b << endl; return 0; }
尋找並輸出11~999之間的數M,它滿足M、M2和M3均為回文數。
l 回文:各位數字左右對稱的整數。
l 例如:11滿足上述條件
n 112=121,113=1331。
分析:
用除以10取余的方法,從最低位開始,依次取出該數的各位數字。按反序重新構成新的數,比較與原數是否相等,若相等,則原數為回文。
源代碼:
#include <iostream> using namespace std; //判斷n是否為回文數 bool symm(unsigned n) { unsigned i = n; unsigned m = 0; while (i > 0) { m = m * 10 + i % 10; i /= 10; } return m == n; } int main() { for(unsigned m = 11; m < 1000; m++) if (symm(m) && symm(m * m) && symm(m * m * m)) { cout << "m = " << m; cout << " m * m = " << m * m; cout << " m * m * m = " << m * m * m << endl; } return 0; }
運行結果:
m=11 m*m=121 m*m*m=1331 m=101 m*m=10201 m*m*m=1030301 m=111 m*m=12321 m*m*m=1367631
投骰子的隨機游戲
每個骰子有六面,點數分別為1、2、3、4、5、6。游戲者在程序開始時輸入一個無符號整數,作為產生隨機數的種子。
每輪投兩次骰子,第一輪如果和數為7或11則為勝,游戲結束;和數為2、3或12則為負,游戲結束;和數為其它值則將此值作為自己的點數,繼續第二輪、第三輪...直到某輪的和數等於點數則取勝,若在此前出現和數為7則為負。
rand函數
l 函數原型:int rand(void);
l 所需頭文件:<cstdlib>
l 功能和返回值:求出並返回一個偽隨機數
srand函數
l void srand(unsigned int seed);
l 參數:seed產生隨機數的種子
l 所需頭文件:<cstdlib>
l 功能:為使rand()產生一序列偽隨機整數而設置起始點。使用1作為seed參數,可以重新初化rand()。
源代碼:
#include <iostream> #include <cstdlib> using namespace std; enum GameStatus { WIN, LOSE, PLAYING }; int main() { int sum, myPoint; GameStatus status; unsigned seed; int rollDice(); cout<<"Please enter an unsigned integer: "; cin >> seed; //輸入隨機數種子 srand(seed); //將種子傳遞給rand() sum = rollDice(); //第一輪投骰子、計算和數 switch (sum) { case 7: //如果和數為7或11則為勝,狀態為WIN case 11: status = WIN; break; case 2: //和數為2、3或12則為負,狀態為LOSE case 3: case 12: status = LOSE; break; default: //其它情況,尚無結果,狀態為 PLAYING,記下點數 status = PLAYING; myPoint = sum; cout << "point is " << myPoint << endl; break; } while (status == PLAYING) { //只要狀態為PLAYING,繼續 sum = rollDice(); if (sum == myPoint) //某輪的和數等於點數則取勝 status = WIN; else if (sum == 7) //出現和數為7則為負 status = LOSE; } //當狀態不為PLAYING時循環結束,輸出游戲結果 if (status == WIN) cout << "player wins" << endl; else cout << "player loses" << endl; return 0; } //投骰子、計算和數、輸出和數 int rollDice() { int die1 = 1 + rand() % 6; int die2 = 1 + rand() % 6; int sum = die1 + die2; cout << "player rolled " << die1 << " + " << die2 << " = " << sum << endl; return sum; } 運行結果: Please enter an unsigned integer:23 player rolled 6 + 3 = 9 point is 9 player rolled 5 + 4 = 9 player wins
嵌套和遞歸
參數傳遞
-
在函數被調用時才分配形參的存儲單元
-
實參可以是常量、變量或表達式
-
實參類型必須與形參相符
-
值傳遞是傳遞參數值,即單向傳遞
-
引用傳遞可以實現雙向傳遞
-
常引用作參數可以保障實參數據的安全
引用類型
引用的概念
-
引用(&)是標識符的別名;
-
定義一個引用時,必須同時對它進行初始化,使它指向一個已存在的對象。
-
例如:
int i, j; int &ri = i; //定義int引用ri,並初始化為變量i的引用 j = 10; ri = j; //相當於 i = j;
-
一旦一個引用被初始化后,就不能改為指向其它對象。
-
引用可以作為形參
輸入兩個整數並交換(值傳遞)
#include<iostream> using namespace std; void swap(int a, int b) { int t = a; a = b; b = t; } int main() { int x = 5, y = 10; cout<<"x = "<<x<<" y = "<<y<<endl; swap(x, y); cout<<"x = "<<x<<" y = "<<y<<endl; return 0; }
運行結果:
x = 5 y = 10
x = 5 y = 10
輸入兩個整數並交換(引用傳遞)
#include<iostream> using namespace std; void swap(int& a, int& b) { int t = a; a = b; b = t; } int main() { int x = 5, y = 10; cout<<"x = "<<x<<" y = "<<y<<endl; swap(x, y); cout<<"x = "<<x<<" y = "<<y<< endl; return 0; }
引用與取地址符
引用是給已定義的變量起別名,在聲明的時候一定要初始化。
int a = 88; int &c = a; //聲明變量a的一個引用c,c是變量a的一個別名,如果引用,聲明的時候一定要初始化 int &d = a; //引用聲明的時候一定要初始化,一個變量可以有多個引用
&(引用)用來傳值,出現在變量聲明語句中位於變量左邊時,表示聲明的是引用.
&(取地址運算符)用來獲取首地址,在給變量賦初值時出現在等號右邊或在執行語句中作為一元運算符出現時表示取對象的地址.
總而言之,和類型在一起的是引用,和變量在一起的是取址
實例如下:1)引用在賦值=的左邊,而取地址在賦值的右邊,比如
int a=3; int &b=a; //引用 int *p=&a; //取地址
2)和類型在一起的是引用,和變量在一起的是取址。 舉例同樣如上,還有下例:
int function(int &i) { } //引用
3)對於vector,上面2條同樣適合
vector<int> vec1(10,1); //initialize vec1: 10 elements, every element's value is 1 vector<int> &vec2 = vec1; // vec2 is reference to vec1 vector<int> *vec3 = &vec2; //vec3 is addresss of vec1 and vec2
含有可變參數的函數
-
C++標准中提供了兩種主要的方法
-
如果所有的實參類型相同,可以傳遞一個名為initializer_list的標准庫類型;
-
如果實參的類型不同,我們可以編寫可變參數的模板。
-
initializer_list
-
initializer_list是一種標准庫類型,用於表示某種特定類型的值的數組,該類型定義在同名的頭文件中
initializer_list提供的操作
initializer_list的使用方法
-
initializer_list是一個類模板
-
使用模板時,我們需要在模板名字后面跟一對尖括號,括號內給出類型參數。例如:
-
initializer_list<string> ls; // initializer_list的元素類型是string
-
initializer_list<int> li; // initializer_list的元素類型是int
-
initializer_list比較特殊的一點是,其對象中的元素永遠是常量值,我們無法改變initializer_list對象中元素的值。
-
含有initializer_list形參的函數也可以同時擁有其他形參
initializer_list使用舉例
-
在編寫代碼輸出程序產生的錯誤信息時,最好統一用一個函數實現該功能,使得對所有錯誤的處理能夠整齊划一。然而錯誤信息的種類不同,調用錯誤信息輸出函數時傳遞的參數也會各不相同。
-
使用initializer_list編寫一個錯誤信息輸出函數,使其可以作用於可變數量的形參。
內聯函數
聲明時使用關鍵字 inline。
編譯時在調用處用函數體進行替換,節省了參數傳遞、控制轉移等開銷。
注意:
- 內聯函數體內不能有循環語句和switch語句;
- 內聯函數的定義必須出現在內聯函數第一次被調用之前;
- 對內聯函數不能進行異常接口聲明。
內聯函數應用舉例
#include <iostream> using namespace std; const double PI = 3.14159265358979; inline double calArea(double radius) { return PI * radius * radius; } int main() { double r = 3.0; double area = calArea(r); cout << area << endl; return 0; }
constexpr函數
constexpr函數語法規定
l constexpr修飾的函數在其所有參數都是constexpr時,一定返回constexpr;
l 函數體中必須有且僅有一條return語句。
constexpr函數舉例
l constexpr int get_size() { return 20; }
l constexpr int foo = get_size(); //正確:foo是一個常量表達式
帶默認參數值的函數
默認參數值
可以預先設置默認的參數值,調用時如給出實參,則采用實參值,否則采用預先設置的默認參數值。
l 例:
int add(int x = 5,int y = 6) { return x + y; } int main() { add(10,20); //10+20 add(10); //10+6 add(); //5+6 }
默認參數值的說明次序
l 有默認參數的形參必須列在形參列表的最右,即默認參數值的右面不能有無默認值的參數;
l 調用時實參與形參的結合次序是從左向右。
l 例:
int add(int x, int y = 5, int z = 6);//正確 int add(int x = 1, int y = 5, int z);//錯誤 int add(int x = 1, int y, int z = 6);//錯誤
默認參數值與函數的調用位置
l 如果一個函數有原型聲明,且原型聲明在定義之前,則默認參數值應在函數原型聲明中給出;如果只有函數的定義,或函數定義在前,則默認參數值可以函數定義中給出。
l 例:
計算長方體的體積
函數getVolume計算體積
有三個形參:length(長)、width(寬)、height(高),其中width和height帶有默認值2和3。
主函數中以不同形式調用getVolume函數。
源代碼:
#include <iostream> #include <iomanip> using namespace std; int getVolume(int length, int width = 2, int height = 3); int main() { const int X = 10, Y = 12, Z = 15; cout << "Some box data is " ; cout << getVolume(X, Y, Z) << endl; cout << "Some box data is " ; cout << getVolume(X, Y) << endl; cout << "Some box data is " ; cout << getVolume(X) << endl; return 0; } int getVolume(int length, int width, int height) { cout << setw(5) << length << setw(5) << width << setw(5) << height << '\t'; return length * width * height; }
C++系統函數
系統函數
C++的系統庫中提供了幾百個函數可供程序員使用,例如:
- 求平方根函數(sqrt)
- 求絕對值函數(abs)
使用系統函數時要包含相應的頭文件,例如:
- cmath
系統函數應用舉例
題目:
從鍵盤輸入一個角度值,求出該角度的正弦值、余弦值和正切值。
分析:
系統函數中提供了求正弦值、余弦值和正切值的函數:sin()、cos()、tan(),函數的說明在頭文件cmath中。
源代碼
#include <iostream> #include <cmath> using namespace std; const double PI = 3.14159265358979; int main() { double angle; cout << "Please enter an angle: "; cin >> angle; //輸入角度值 double radian = angle * PI / 180; //轉為弧度 cout << "sin(" << angle << ") = " << sin(radian) <<endl; cout << "cos(" << angle << ") = " << cos(radian) <<endl; cout << "tan(" << angle << ") = " << tan(radian) <<endl; return 0; } l 運行結果 30 sin(30)=0.5 cos(30)=0.866025 tan(30)=0.57735
函數重載
函數重載的概念
C++允許功能相近的函數在相同的作用域內以相同函數名聲明,從而形成重載。方便使用,便於記憶。
例:
注意事項
- 重載函數的形參必須不同:個數不同或類型不同。
- 編譯程序將根據實參和形參的類型及個數的最佳匹配來選擇調用哪一個函數。
- 不要將不同功能的函數聲明為重載函數,以免出現調用結果的誤解、混淆。這樣不好:
重載函數應用舉例
編寫兩個名為sumOfSquare的重載函數,分別求兩整數的平方和及兩實數的平方和。
源代碼:
#include <iostream> using namespace std; int sumOfSquare(int a, int b) { return a * a + b * b; } double sumOfSquare(double a, double b) { return a * a + b * b; } int main() { int m, n; cout << "Enter two integer: "; cin >> m >> n; cout<<"Their sum of square: "<<sumOfSquare(m, n)<<endl; double x, y; cout << "Enter two real number: "; cin >> x >> y; cout<<"Their sum of square: "<<sumOfSquare(x, y)<<endl; return 0; } 運行結果: Enter two integer: 3 5 Their sum of square: 34 Enter two real number: 2.3 5.8 Their sum of square: 38.93
二、類與對象
面向對象程序設計的基本特點
抽象
對同一類對象的共同屬性和行為進行概括,形成類。
先注意問題的本質及描述,其次是實現過程或細節。
數據抽象:描述某類對象的屬性或狀態(對象相互區別的物理量)。
代碼抽象:描述某類對象的共有的行為特征或具有的功能。
抽象的實現:類。
抽象實例——鍾表
數據抽象:
int hour,int minute,int second
代碼抽象:
setTime(),showTime()
class Clock { public: void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; };
封裝
將抽象出的數據、代碼封裝在一起,形成類。
目的:增強安全性和簡化編程,使用者不必了解具體的實現細節,而只需要通過外部接口,以特定的訪問權限,來使用類的成員。
實現封裝:類聲明中的{}
例:
class Clock { public: void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; };
繼承
在已有類的基礎上,進行擴展形成新的類。
多態
多態:同一名稱,不同的功能實現方式。
目的:達到行為標識統一,減少程序中標識符的個數。
實現:重載函數和虛函數
類和對象的定義
對象是現實中的對象在程序中的模擬。
類是同一類對象的抽象,對象時類的某一特定實體。
定義類的對象,才可以通過對象使用類中定義的功能。
設計類就是設計類型
此類型的“合法值”是什么?
此類型應該有什么樣的函數和操作符?
新類型的對象該如何被創建和銷毀?
如何進行對象的初始化和賦值?
對象作為函數的參數如何以值傳遞?
誰將使用此類型的對象成員?
類定義的語法形式
class 類名稱 { public: 公有成員(外部接口) private: 私有成員 protected: 保護型成員 };
類內初始值
可以為數據成員提供一個類內初始值
在創建對象時,類內初始值用於初始化數據成員
沒有初始值的成員將被默認初始化。
類內初始值舉例
class Clock { public: void setTime(int newH, int newM, int newS); void showTime(); private: int hour = 0, minute = 0, second = 0; };
類成員的訪問控制
公有類型成員
在關鍵字public后面聲明,它們是類與外部的接口,任何外部函數都可以訪問公有類型數據和函數。
私有類型成員
在關鍵字private后面聲明,只允許本類中的函數訪問,而類外部的任何函數都不能訪問。
如果緊跟在類名稱的后面聲明私有成員,則關鍵字private可以省略。
保護類型成員
與private類似,其差別表現在繼承與派生時對派生類的影響不同。
對象定義的語法
類名 對象名;
例:Clock myClock;
類成員的訪問權限
類中成員互相訪問
直接使用成員名訪問
類外訪問
使用“對象名.成員名”方式訪問 public 屬性的成員
類的成員函數
在類中說明函數原型;
可以在類外給出函數體實現,並在函數名前使用類名加以限定;
可以直接在類中給出函數體,形成內聯成員函數;
允許聲明重載函數和帶默認參數值的函數。
內聯成員函數
為了提高運行時的效率,對於較簡單的函數可以聲明為內聯形式。
內聯函數體中不要有復雜結構(如循環語句和switch語句)。
在類中聲明內聯成員函數的方式:
- 將函數體放在類的聲明中。
- 使用inline關鍵字。
類的定義 #include<iostream> using namespace std; class Clock{ public: void setTime(int newH = 0, int newM = 0, int newS = 0); void showTime(); private: int hour, minute, second; }; 成員函數的實現 void Clock::setTime(int newH, int newM, int newS) { hour = newH; minute = newM; second = newS; } void Clock::showTime() { cout << hour << ":" << minute << ":" << second; } 對象的使用 int main() { Clock myClock; myClock.setTime(8, 30, 30); myClock.showTime(); return 0; }
構造函數
構造函數的作用
在對象被創建時使用特定的值構造對象,將對象初始化為一個特定的初始狀態。
例如:
希望在構造一個Clock類對象時,將初試時間設為0:0:0,就可以通過構造函數來設置。
構造函數的形式
- 函數名與類名相同;
- 不能定義返回值類型,也不能有return語句;
- 可以有形式參數,也可以沒有形式參數;
- 可以是內聯函數;
- 可以重載;
- 可以帶默認參數值。
構造函數的調用時機
在對象創建時被自動調用
例如:
Clock myClock(0,0,0);
默認構造函數
調用時可以不需要實參的構造函數
參數表為空的構造函數
全部參數都有默認值的構造函數
下面兩個都是默認構造函數,如在類中同時出現,將產生編譯錯誤:
Clock(); Clock(int newH=0,int newM=0,int newS=0);
隱含生成的構造函數
如果程序中未定義構造函數,編譯器將在需要時自動生成一個默認構造函數
參數列表為空,不為數據成員設置初始值;
如果類內定義了成員的初始值,則使用內類定義的初始值;
如果沒有定義類內的初始值,則以默認方式初始化;
基本類型的數據默認初始化的值是不確定的。
“=default”
如果程序中已定義構造函數,默認情況下編譯器就不再隱含生成默認構造函數。如果此時依然希望編譯器隱含生成默認構造函數,可以使用“=default”。
例如
class Clock { public: Clock() =default; //指示編譯器提供默認構造函數 Clock(int newH, int newM, int newS); //構造函數 private: int hour, minute, second; };
//類定義 class Clock { public: Clock(int newH,int newM,int newS);//構造函數 void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; }; //構造函數的實現: Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM), second(newS) { } //其它函數實現同例4_1 int main() { Clock c(0,0,0); //自動調用構造函數 c.showTime(); return 0; }
class Clock { public: Clock(int newH, int newM, int newS); //構造函數 Clock(); //默認構造函數 void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; }; Clock::Clock(): hour(0),minute(0),second(0) { }//默認構造函數 //其它函數實現同前 int main() { Clock c1(0, 0, 0); //調用有參數的構造函數 Clock c2; //調用無參數的構造函數 …… }
委托構造函數
類中往往有多個構造函數,只是參數表和初始化列表不同,其初始化算法都是相同的,這時,為了避免代碼重復,可以使用委托構造函數。
回顧
Clock類的兩個構造函數:
Clock(int newH, int newM, int newS) : hour(newH),minute(newM), second(newS) { //構造函數 } Clock::Clock(): hour(0),minute(0),second(0) { }//默認構造函數
委托構造函數
委托構造函數使用類的其他構造函數執行初始化過程
例如:
Clock(int newH, int newM, int newS): hour(newH),minute(newM), second(newS){ } Clock(): Clock(0, 0, 0) { }
復制構造函數
復制構造函數定義
復制構造函數是一種特殊的構造函數,其形參為本類的對象引用。作用是用一個已存在的對象去初始化同類型的新對象。
class 類名 { public : 類名(形參);//構造函數 類名(const 類名 &對象名);//復制構造函數 // ... };
類名::類( const 類名 &對象名)//復制構造函數的實現
{ 函數體 }
隱含的復制構造函數
如果程序員沒有為類聲明拷貝初始化構造函數,則編譯器自己生成一個隱含的復制構造函數。
這個構造函數執行的功能是:用作為初始值的對象的每個數據成員的值,初始化將要建立的對象的對應數據成員。
“=delete”
如果不希望對象被復制構造
- C++98做法:將復制構造函數聲明為private,並且不提供函數的實現。
- C++11做法:用“=delete”指示編譯器不生成默認復制構造函數。
例:
class Point { //Point 類的定義 public: Point(int xx=0, int yy=0) { x = xx; y = yy; } //構造函數,內聯 Point(const Point& p) =delete; //指示編譯器不生成默認復制構造函數 private: int x, y; //私有數據 };
復制構造函數被調用的三種情況
定義一個對象時,以本類另一個對象作為初始值,發生復制構造;
如果函數的形參是類的對象,調用函數時,將使用實參對象初始化形參對象,發生復制構造;
如果函數的返回值是類的對象,函數執行完成返回主調函數時,將使用return語句中的對象初始化一個臨時無名對象,傳遞給主調函數,此時發生復制構造。
這種情況也可以通過移動構造避免不必要的復制
class Point { //Point 類的定義 public: Point(int xx=0, int yy=0) { x = xx; y = yy; } //構造函數,內聯 Point(const Point& p); //復制構造函數 void setX(int xx) {x=xx;} void setY(int yy) {y=yy;} int getX() const { return x; } //常函數(第5章) int getY() const { return y; } //常函數(第5章) private: int x, y; //私有數據 }; //復制構造函數的實現 Point::Point (const Point& p) { x = p.x; y = p.y; cout << "Calling the copy constructor " << endl; } //形參為Point類對象void fun1(Point p) { cout << p.getX() << endl; } //返回值為Point類對象Point fun2() { Point a(1, 2); return a; } int main() { Point a(4, 5); Point b(a); //用a初始化b。 cout << b.getX() << endl; fun1(b); //對象b作為fun1的實參 b = fun2(); //函數的返回值是類對象 cout << b.getX() << endl; return 0; }
析構函數
- 完成對象被刪除前的一些清理工作。
- 在對象的生存期結束的時刻系統自動調用它,然后再釋放此對象所屬的空間。
- 如果程序中未聲明析構函數,編譯器將自動產生一個默認的析構函數,其函數體為空。
- 構造函數和析構函數舉例
#include using namespace std; class Point { public: Point(int xx,int yy); ~Point(); //...其他函數原型 private: int x, y; };
類的組合
組合的概念
- 類中的成員是另一個類的對象。
- 可以在已有抽象的基礎上實現更復雜的抽象。
類組合的構造函數設計
- 原則:不僅要負責對本類中的基本類型成員數據初始化,也要對對象成員初始化。
- 聲明形式:
類名::類名(對象成員所需的形參,本類成員形參) :對象1(參數),對象2(參數),...... { //函數體其他語句 }
構造組合類對象時的初始化次序
-
首先對構造函數初始化列表中列出的成員(包括基本類型成員和對象成員)進行初始化,初始化次序是成員在類體中定義的次序。
-
成員對象構造函數調用順序:按對象成員的聲明順序,先聲明者先構造。
-
初始化列表中未出現的成員對象,調用用默認構造函數(即無形參的)初始化
-
處理完初始化列表之后,再執行構造函數的函數體。
#include <iostream> #include <cmath> using namespace std; class Point { //Point類定義 public: Point(int xx = 0, int yy = 0) { x = xx; y = yy; } Point(Point &p); int getX() { return x; } int getY() { return y; } private: int x, y; }; Point::Point(Point &p) { //復制構造函數的實現 x = p.x; y = p.y; cout << "Calling the copy constructor of Point" << endl; } //類的組合 class Line { //Line類的定義 public: //外部接口 Line(Point xp1, Point xp2); Line(Line &l); double getLen() { return len; } private: //私有數據成員 Point p1, p2; //Point類的對象p1,p2 double len; }; //組合類的構造函數 Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) { cout << "Calling constructor of Line" << endl; double x = static_cast<double>(p1.getX() - p2.getX()); double y = static_cast<double>(p1.getY() - p2.getY()); len = sqrt(x * x + y * y); } Line::Line (Line &l): p1(l.p1), p2(l.p2) { //組合類的復制構造函數 cout << "Calling the copy constructor of Line" << endl; len = l.len; } //主函數 int main() { Point myp1(1, 1), myp2(4, 5); //建立Point類的對象 Line line(myp1, myp2); //建立Line類的對象 Line line2(line); //利用復制構造函數建立一個新對象 cout << "The length of the line is: "; cout << line.getLen() << endl; cout << "The length of the line2 is: "; cout << line2.getLen() << endl; return 0; }
前向引用聲明
類應該先聲明,后使用
如果需要在某個類的聲明之前,引用該類,則應進行前向引用聲明。
前向引用聲明只為程序引入一個標識符,但具體聲明在其他地方。
例:
class B; //前向引用聲明 class A { public: void f(B b); }; class B { public: void g(A a); };
前向引用聲明注意事項
使用前向引用聲明雖然可以解決一些問題,但它並不是萬能的。
在提供一個完整的類聲明之前,不能聲明該類的對象,也不能在內聯成員函數中使用該類的對象。
當使用前向引用聲明時,只能使用被聲明的符號,而不能涉及類的任何細節。
例
class Fred; //前向引用聲明 class Barney { Fred x; //錯誤:類Fred的聲明尚不完善 }; class Fred { Barney y; };
結構體
- 結構體是一種特殊形態的類
- 與類的唯一區別:類的缺省訪問權限是private,結構體的缺省訪問權限是public
- 結構體存在的主要原因:與C語言保持兼容
- 什么時候用結構體而不用類
- 定義主要用來保存數據、而沒有什么操作的類型
- 人們習慣將結構體的數據成員設為公有,因此這時用結構體更方便
結構體的定義
struct 結構體名稱 { 公有成員 protected: 保護型成員 private: 私有成員 };
結構體的初始化
- 如果一個結構體的全部數據成員都是公共成員,並且沒有用戶定義的構造函數,沒有基類和虛函數(基類和虛函數將在后面的章節中介紹),這個結構體的變量可以用下面的語法形式賦初值
類型名 變量名 = { 成員數據1初值, 成員數據2初值, …… };
用結構體表示學生的基本信息
#include #include #include using namespace std; struct Student { //學生信息結構體 int num; //學號 string name; //姓名,字符串對象,將在第6章詳細介紹 char sex; //性別 int age; //年齡 }; int main() { Student stu = { 97001, "Lin Lin", 'F', 19 }; cout << "Num: " << stu.num << endl; cout << "Name: " << stu.name << endl; cout << "Sex: " << stu.sex << endl; cout << "Age: " << stu.age << endl; return 0; } 運行結果: Num: 97001 Name: Lin Lin Sex: F Age: 19
聯合體
聲明形式
union 聯合體名稱 { 公有成員 protected: 保護型成員 private: 私有成員 };
特點
- 成員共用同一組內存單元
- 任何兩個成員不會同時有效
聯合體的內存分配
- 舉例說明:
union Mark { //表示成績的聯合體 char grade; //等級制的成績 bool pass; //只記是否通過課程的成績 int percent; //百分制的成績 };
無名聯合
- 例:
union { int i; float f; }
在程序中可以這樣使用: i = 10; f = 2.2;
使用聯合體保存成績信息,並且輸出。
#include #include using namespace std; class ExamInfo { private: string name; //課程名稱 enum { GRADE, PASS, PERCENTAGE } mode;//計分方式 union { char grade; //等級制的成績 bool pass; //只記是否通過課程的成績 int percent; //百分制的成績 }; public: //三種構造函數,分別用等級、是否通過和百分初始化 ExamInfo(string name, char grade) : name(name), mode(GRADE), grade(grade) { } ExamInfo(string name, bool pass) : name(name), mode(PASS), pass(pass) { } ExamInfo(string name, int percent) : name(name), mode(PERCENTAGE), percent(percent) { } void show(); } void ExamInfo::show() { cout << name << ": "; switch (mode) { case GRADE: cout << grade; break; case PASS: cout << (pass ? "PASS" : "FAIL"); break; case PERCENTAGE: cout << percent; break; } cout << endl; } int main() { ExamInfo course1("English", 'B'); ExamInfo course2("Calculus", true); ExamInfo course3("C++ Programming", 85); course1.show(); course2.show(); course3.show(); return 0; } 運行結果: English: B Calculus: PASS C++ Programming: 85
枚舉類
枚舉類定義
語法形式
enum class 枚舉類型名: 底層類型 {枚舉值列表};
例:
enum class Type { General, Light, Medium, Heavy}; enum class Type: char { General, Light, Medium, Heavy}; enum class Category { General=1, Pistol, MachineGun, Cannon};
枚舉類的優勢
強作用域,其作用域限制在枚舉類中。
例:使用Type的枚舉值General:
Type::General
轉換限制,枚舉類對象不可以與整型隱式地互相轉換。
可以指定底層類型
例:
enum class Type: char { General, Light, Medium, Heavy};
枚舉類舉例
#include<iostream> using namespace std; enum class Side{ Right, Left }; enum class Thing{ Wrong, Right }; //不沖突 int main() { Side s = Side::Right; Thing w = Thing::Wrong; cout << (s == w) << endl; //編譯錯誤,無法直接比較不同枚舉類 return 0; }