https://blog.csdn.net/weixin_44368437/article/details/117563488?utm_source=app&app_version=5.3.0&code=app_1562916241&uLinkId=usr1mkqgl919blen
一、面向?qū)ο蟪绦蛟O計
面向?qū)ο蟪绦蛟O計(Object-Oriented Programming ,OOP )是一種新的程序設計范型。程序設計范型是指設計程序的規(guī)范、模型和風格,它是一類程序設計語言的基礎。
面向過程程序設計范型是使用較廣泛的面向過程性語言,其主要特征是:程序由過程定義和過程調(diào)用組成(簡單地說,過程就是程序執(zhí)行某項操作的一段代碼,函數(shù)就是最常用的過程)。
面向?qū)ο蟪绦虻幕驹厥菍ο?,面向?qū)ο蟪绦虻闹饕Y構特點是:第一,程序一般由類的定義和類的使用兩部分組成;第二,程序中的一切操作都是通過向?qū)ο蟀l(fā)送消息來實現(xiàn)的,對象接收到消息后,啟動有關方法完成相應的操作。
對象:描述其屬性的數(shù)據(jù)以及對這些數(shù)據(jù)施加的一組操作封裝在一起構成的統(tǒng)一體。對象可認為是數(shù)據(jù)+操作。
類:類是具有相同的數(shù)據(jù)和相同的操作的一組對象的集合。
消息傳遞:對象之間的交互。
**方法:**對象實現(xiàn)的行為稱為方法。
面向?qū)ο蟪绦蛟O計的基本特征:抽象、封裝、繼承、多態(tài)。
二、C++基礎
~
2.1 C++的產(chǎn)生和特點
C++是美國貝爾實驗室的Bjarne Stroustrup博士在C語言的基礎上,彌補了C語言存在的一些缺陷,增加了面向?qū)ο蟮奶卣?,?980年開發(fā)出來的一種面向過程性與面向?qū)ο笮韵嘟Y合的程序設計語言。最初他把這種新的語言稱為“含類的C”,到1983年才取名為C++。
相比C語言,C++的主要特點是增加了面向?qū)ο?/strong>機制。
~
2.2 一個簡單的C++示例程序
詳細創(chuàng)建步驟可參考博客【Visual Studio】 創(chuàng)建C/C++項目
#include <iostream> //編譯預處理命令
using namespace std; //使用命名空間
int add(int a, int b); //函數(shù)原型說明
int main() //主函數(shù)
{
int x, y;
cout << 'Enter two numbers: ' << endl;
cin >> x;
cin >> y;
int sum = add(x, y);
cout << 'The sum is : ' << sum << '\n';
return 0;
}
int add(int a, int b) //定義add()函數(shù),函數(shù)值為整型
{
return a + b;
}
~
2.3 C++在非面向?qū)ο蠓矫鎸語言的擴充
輸入和輸出
int i;
float f;
cin >> i;
cout << f;
------------
scanf('%d', &i);
printf('%f', f);
----------------
連續(xù)讀入
cin >> a >> b >> c;
cin
- 在默認情況下,運算符“
>> ”將跳過空白符,然后讀入后面與變量類型相對應的值。因此,給一組變量輸入值時可用空格符、回車符、制表符將輸入的數(shù)據(jù)間隔開。 - 當輸入字符串(即類型為string的變量)時,提取運算符“
>> ”的作用是跳過空白字符,讀入后面的非空白字符,直到遇到另一個空白字符為止,并在串尾放一個字符串結束標志'\0 ’。
~
C++允許在代碼塊中的任何地方聲明局部變量。
const修飾符
在C語言中,習慣使用#define 來定義常量,例如#define PI 3.14 ,C++提供了一種更靈活、更安全的方式來定義常量,即使用const 修飾符來定義常量。例如const float PI = 3.14;
const可以與指針一起使用,它們的組合情況復雜,可歸納為3種:指向常量的指針、常指針和指向常量的常指針。
-
指向常量的指針:一個指向常量的指針變量。 const char* pc = 'abcd';
該方法不允許改變指針所指的變量,即
pc[3] = 'x'; 是錯誤的,
但是,由于pc是一個指向常量的普通指針變量,不是常指針,因此可以改變pc所指的地址,例如
pc = 'ervfs';
該語句付給了指針另一個字符串的地址,改變了pc的值。
-
常指針:將指針變量所指的地址聲明為常量 char* const pc = 'abcd';
創(chuàng)建一個常指針,一個不能移動的固定指針,可更改內(nèi)容,如
pc[3] = 'x';
但不能改變地址,如
pc = 'dsff'; 不合法
-
指向常量的常指針:這個指針所指的地址不能改變,它所指向的地址中的內(nèi)容也不能改變。 const char* const pc = 'abcd';
內(nèi)容和地址均不能改變
說明:
-
如果用const定義整型常量,關鍵字可以省略。即 const in bufsize = 100 與 const bufsize = 100 等價; -
常量一旦被建立,在程序的任何地方都不能再更改。 -
與#define不同,const定義的常量可以有自己的數(shù)據(jù)類型。 -
函數(shù)參數(shù)也可以用const說明,用于保證實參在該函數(shù)內(nèi)不被改動。
void型指針
void通常表示無值,但將void作為指針的類型時,它卻表示不確定的類型。這種void型指針是一種通用型指針,也就是說任何類型的指針值都可以賦給void類型的指針變量。
需要指出的是,這里說void型指針是通用指針,是指它可以接受任何類型的指針的賦值,但對已獲值的void型指針,對它進行再處理,如輸出或者傳遞指針值時,則必須再進行顯式類型轉(zhuǎn)換,否則會出錯。
void* pc;
int i = 123;
char c = 'a';
pc = &i;
cout << pc << endl; //輸出指針地址006FF730
cout << *(int*)pc << endl; //輸出值123
pc = &c;
cout << *(char*)pc << endl; //輸出值a
內(nèi)聯(lián)函數(shù)
在函數(shù)名前冠以關鍵字inline ,該函數(shù)就被聲明為內(nèi)聯(lián)函數(shù)。每當程序中出現(xiàn)對該函數(shù)的調(diào)用時,C++編譯器使用函數(shù)體中的代碼插入到調(diào)用該函數(shù)的語句之處,同時使用實參代替形參,以便在程序運行時不再進行函數(shù)調(diào)用。引入內(nèi)聯(lián)函數(shù)主要是為了消除調(diào)用函數(shù)時的系統(tǒng)開銷,以提高運行速度。
說明:
- 內(nèi)聯(lián)函數(shù)在第一次被調(diào)用之前必須進行完整的定義,否則編譯器將無法知道應該插入什么代碼
- 在內(nèi)聯(lián)函數(shù)體內(nèi)一般不能含有復雜的控制語句,如for語句和switch語句等
- 使用內(nèi)聯(lián)函數(shù)是一種空間換時間的措施,若內(nèi)聯(lián)函數(shù)較長,較復雜且調(diào)用較為頻繁時不建議使用
#include <iostream>
using namespace std;
inline double circle(double r) //內(nèi)聯(lián)函數(shù)
{
double PI = 3.14;
return PI * r * r;
}
int main()
{
for (int i = 1; i <= 3; i++)
cout << 'r = ' << i << ' area = ' << circle(i) << endl;
return 0;
}
使用內(nèi)聯(lián)函數(shù)替代宏定義,能消除宏定義的不安全性
帶有默認參數(shù)值的函數(shù)
當進行函數(shù)調(diào)用時,編譯器按從左到右的順序?qū)崊⑴c形參結合,若未指定足夠的實參,則編譯器按順序用函數(shù)原型中的默認值來補足所缺少的實參。
void init(int x = 5, int y = 10);
init (100, 19); // 100 , 19
init(25); // 25, 10
init(); // 5, 10
-
在函數(shù)原型中,所有取默認值的參數(shù)都必須出現(xiàn)在不取默認值的參數(shù)的右邊。 如 int fun(int a, int b, int c = 111);
-
在函數(shù)調(diào)用時,若某個參數(shù)省略,則其后的參數(shù)皆應省略而采取默認值。不允許某個參數(shù)省略后,再給其后的參數(shù)指定參數(shù)值。
函數(shù)重載
在C++中,用戶可以重載函數(shù)。這意味著,在同一作用域內(nèi),只要函數(shù)參數(shù)的類型不同,或者參數(shù)的個數(shù)不同,或者二者兼而有之,兩個或者兩個以上的函數(shù)可以使用相同的函數(shù)名。
#include <iostream>
using namespace std;
int add(int x, int y)
{
return x + y;
}
double add(double x, double y)
{
return x + y;
}
int add(int x, int y, int z)
{
return x + y + z;
}
int main()
{
int a = 3, b = 5, c = 7;
double x = 10.334, y = 8.9003;
cout << add(a, b) << endl;
cout << add(x, y) << endl;
cout << add(a, b, c) << endl;
return 0;
}
說明:
-
調(diào)用重載函數(shù)時,函數(shù)返回值類型不在參數(shù)匹配檢查之列。因此,若兩個函數(shù)的參數(shù)個數(shù)和類型都相同,而只有返回值類型不同,則不允許重載。 int mul(int x, int y);
double mul(int x, int y);
-
函數(shù)的重載與帶默認值的函數(shù)一起使用時,有可能引起二義性。 void Drawcircle(int r = 0, int x = 0, int y = 0);
void Drawcircle(int r);
Drawcircle(20);
-
在調(diào)用函數(shù)時,如果給出的實參和形參類型不相符,C++的編譯器會自動地做類型轉(zhuǎn)換工作。如果轉(zhuǎn)換成功,則程序繼續(xù)執(zhí)行,在這種情況下,有可能產(chǎn)生不可識別的錯誤。 void f_a(int x);
void f_a(long x);
f_a(20.83);
作用域標識符'::'
通常情況下,如果有兩個同名變量,一個是全局的,另一個是局部的,那么局部變量在其作用域內(nèi)具有較高的優(yōu)先權,它將屏蔽全局變量。
如果希望在局部變量的作用域內(nèi)使用同名的全局變量,可以在該變量前加上“:: ”,此時::value 代表全局變量value,“:: ”稱為作用域標識符。
#include <iostream>
using namespace std;
int value; //定義全局變量value
int main()
{
int value; //定義局部變量value
value = 100;
::value = 1000;
cout << 'local value : ' << value << endl;
cout << 'global value : ' << ::value << endl;
return 0;
}
強制類型轉(zhuǎn)換
可用強制類型轉(zhuǎn)換將不同類型的數(shù)據(jù)進行轉(zhuǎn)換。例如,要把一個整型數(shù)(int)轉(zhuǎn)換為雙精度型數(shù)(double),可使用如下的格式:
int i = 10;
double x = (double)i;
或
int i = 10;
double x = double(i);
以上兩種方法C++都能接受,建議使用后一種方法。
new和delete運算符
程序運行時,計算機的內(nèi)存被分為4個區(qū):程序代碼區(qū)、全局數(shù)據(jù)區(qū)、堆和棧。其中,堆可由用戶分配和釋放。C語言中使用函數(shù)malloc() 和free() 來進行動態(tài)內(nèi)存管理。C++則提供了運算符new 和delete 來做同樣的工作,而且后者比前者性能更優(yōu)越,使用更靈活方便。
指針變量名 = new 類型
int *p;
p = new int;
delete 指針變量名
delete p;
下面對new和delete的使用再做一下幾點說明:
-
用運算符new分配的空間,使用結束后應該用也只能用delete顯式地釋放,否則這部分空間將不能回收而變成死空間。 -
在使用運算符new動態(tài)分配內(nèi)存時,如果沒有足夠的內(nèi)存滿足分配要求,new將返回空指針(NULL )。 -
使用運算符new可以為數(shù)組動態(tài)分配內(nèi)存空間,這時需要在類型后面加上數(shù)組大小。 指針變量名 = new 類型名[下標表達式];
int *p = new int[10];
釋放動態(tài)分配的數(shù)組存儲區(qū)時,可使用delete運算符。 delete []指針變量名;
delete p;
-
new 可在為簡單變量分配空間的同時,進行初始化 指針變量名 = new 類型名(初值);
int *p;
p = new int(99);
···
delete p;
引用
引用(reference )是C++對C的一個重要擴充。變量的引用就是變量的別名,因此引用又稱別名。
類型 &引用名 = 已定義的變量名
引用與其所代表的變量共享同一內(nèi)存單元,系統(tǒng)并不為引用另外分配存儲空間。實際上,編譯系統(tǒng)使引用和其代表的變量具有相同的地址。
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int &j = i;
cout << 'i = ' << i << ' j = ' << j << endl;
cout << 'i的地址為 ' << &i << endl;
cout << 'j的地址為 ' << &j << endl;
return 0;
}
上面代碼輸出i和j的值相同,地址也相同。
- 引用并不是一種獨立的數(shù)據(jù)類型,它必須與某一種類型的變量相聯(lián)系。在聲明引用時,必須立即對它進行初始化,不能聲明完成后再賦值。
- 為引用提供的初始值,可以是一個變量或者另一個引用。
- 指針是通過地址間接訪問某個變量,而引用則是通過別名直接訪問某個變量。
引用作為函數(shù)參數(shù)、使用引用返回函數(shù)值
#include <iostream>
using namespace std;
void swap(int &a, int &b)
{
int t = a;
a = b;
b = t;
}
int a[] = {1, 3, 5, 7, 9};
int& index(int i)
{
return a[i];
}
int main()
{
int a = 5, b = 10;
//交換數(shù)字a和b
swap(a, b);
cout << 'a = ' << a << ' b = ' << b << endl;
cout << index(2) << endl; //等價于輸出元素a[2]的值
index(2) = 100; //等價于將a[2]的值賦為100;
cout << index(2) << endl;
return 0;
}
對引用的進一步說明
- 不允許建立void類型的引用
- 不能建立引用的數(shù)組
- 不能建立引用的引用。不能建立指向引用的指針。引用本身不是一種數(shù)據(jù)類型,所以沒有引用的引用,也沒有引用的指針。
- 可以將引用的地址賦值給一個指針,此時指針指向的是原來的變量。
- 可以用const對引用加以限定,不允許改變該引用的值,但是它不阻止引用所代表的變量的值。
~
3.1 類的構成
類聲明中的內(nèi)容包括數(shù)據(jù)和函數(shù),分別稱為數(shù)據(jù)成員和成員函數(shù)。按訪問權限劃分,數(shù)據(jù)成員和成員函數(shù)又可分為共有、保護和私有3種。
class 類名{
public:
公有數(shù)據(jù)成員;
公有成員函數(shù);
protected:
保護數(shù)據(jù)成員;
保護成員函數(shù);
private:
私有數(shù)據(jù)成員;
私有成員函數(shù);
};
如成績類
class Score{
public:
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
- 對一個具體的類來講,類聲明格式中的3個部分并非一定要全有,但至少要有其中的一個部分。一般情況下,一個類的數(shù)據(jù)成員應該聲明為私有成員,成員函數(shù)聲明為共有成員。這樣,內(nèi)部的數(shù)據(jù)整個隱蔽在類中,在類的外部根本就無法看到,使數(shù)據(jù)得到有效的保護,也不會對該類以外的其余部分造成影響,程序之間的相互作用就被降低到最小。
- 類聲明中的關鍵字private、protected、public可以任意順序出現(xiàn)。
- 若私有部分處于類的第一部分時,關鍵字private可以省略。這樣,如果一個類體中沒有一個訪問權限關鍵字,則其中的數(shù)據(jù)成員和成員函數(shù)都默認為私有的。
- 不能在類聲明中給數(shù)據(jù)成員賦初值。
~
3.2 成員函數(shù)的定義
普通成員函數(shù)的定義
在類的聲明中只給出成員函數(shù)的原型,而成員函數(shù)的定義寫在類的外部。這種成員函數(shù)在類外定義的一般形式是:
返回值類型 類名::成員函數(shù)名(參數(shù)表){ 函數(shù)體}
例如,表示分數(shù)的類Score可聲明如下:
class Score{
public:
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
void Score::setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
void Score::showScore()
{
cout << '期中成績: ' << mid_exam << endl;
cout << '期末成績:' << fin_exam << endl;
}
內(nèi)聯(lián)成員函數(shù)的定義
- 隱式聲明:將成員函數(shù)直接定義在類的內(nèi)部
class Score{
public:
void setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
void showScore()
{
cout << '期中成績: ' << mid_exam << endl;
cout << '期末成績:' << fin_exam << endl;
}
private:
int mid_exam;
int fin_exam;
};
- 顯式聲明:在類聲明中只給出成員函數(shù)的原型,而將成員函數(shù)的定義放在類的外部。
class Score{
public:
inline void setScore(int m, int f);
inline void showScore();
private:
int mid_exam;
int fin_exam;
};
inline void Score::setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
inline void Score::showScore()
{
cout << '期中成績: ' << mid_exam << endl;
cout << '期末成績:' << fin_exam << endl;
}
說明:在類中,使用inline定義內(nèi)聯(lián)函數(shù)時,必須將類的聲明和內(nèi)聯(lián)成員函數(shù)的定義都放在同一個文件(或同一個頭文件)中,否則編譯時無法進行代碼置換。
~
3.3 對象的定義和使用
通常把具有共同屬性和行為的事物所構成的集合稱為類。
類的對象可以看成該類類型的一個實例,定義一個對象和定義一個一般變量相似。
對象的定義
class Score{
public:
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
}op1, op2;
Score op1, op2;
對象中成員的訪問
對象名.數(shù)據(jù)成員名對象名.成員函數(shù)名[(參數(shù)表)]op1.setScore(89, 99);
op1.showScore();
說明:
-
在類的內(nèi)部所有成員之間都可以通過成員函數(shù)直接訪問,但是類的外部不能訪問對象的私有成員。 -
在定義對象時,若定義的是指向此對象的指針變量,則訪問此對象的成員時,不能用“. ”操作符,而應該使用“-> “操作符。如 Score op, *sc;
sc = &op;
sc->setScore(99, 100);
op.showScore();
類的作用域和類成員的訪問屬性
私有成員只能被類中的成員函數(shù)訪問,不能在類的外部,通過類的對象進行訪問。
一般來說,公有成員是類的對外接口,而私有成員是類的內(nèi)部數(shù)據(jù)和內(nèi)部實現(xiàn),不希望外界訪問。將類的成員劃分為不同的訪問級別有兩個好處:一是信息隱蔽,即實現(xiàn)封裝,將類的內(nèi)部數(shù)據(jù)與內(nèi)部實現(xiàn)和外部接口分開,這樣使該類的外部程序不需要了解類的詳細實現(xiàn);二是數(shù)據(jù)保護,即將類的重要信息保護起來,以免其他程序進行不恰當?shù)男薷摹?/p>
對象賦值語句
Score op1, op2;
op1.setScore(99, 100);
op2 = op1;
op2.showScore();
~
構造函數(shù)
構造函數(shù)是一種特殊的成員函數(shù),它主要用于為對象分配空間,進行初始化。構造函數(shù)的名字必須與類名相同,而不能由用戶任意命名。它可以有任意類型的參數(shù),但不能具有返回值。它不需要用戶來調(diào)用,而是在建立對象時自動執(zhí)行。
class Score{
public:
Score(int m, int f); //構造函數(shù)
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
Score::Score(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
在建立對象的同時,采用構造函數(shù)給數(shù)據(jù)成員賦值,通常由以下兩種形式
-
類名 對象名[(實參表)]
Score op1(99, 100);
op1.showScore();
-
類名 *指針變量名 = new 類名[(實參表)]
Score *p;
p = new Score(99, 100);
p->showScore();
-----------------------
Score *p = new Score(99, 100);
p->showScore();
說明:
- 構造函數(shù)的名字必須與類名相同,否則編譯程序?qū)阉斪鲆话愕某蓡T函數(shù)來處理。
- 構造函數(shù)沒有返回值,在定義構造函數(shù)時,是不能說明它的類型的。
- 與普通的成員函數(shù)一樣,構造函數(shù)的函數(shù)體可以寫在類體內(nèi),也可寫在類體外。
- 構造函數(shù)一般聲明為共有成員,但它不需要也不能像其他成員函數(shù)那樣被顯式地調(diào)用,它是在定義對象的同時被自動調(diào)用,而且只執(zhí)行一次。
- 構造函數(shù)可以不帶參數(shù)。
成員初始化列表
在聲明類時,對數(shù)據(jù)成員的初始化工作一般在構造函數(shù)中用賦值語句進行。此外還可以用成員初始化列表實現(xiàn)對數(shù)據(jù)成員的初始化。
類名::構造函數(shù)名([參數(shù)表])[:(成員初始化列表)]
{
//構造函數(shù)體
}
class A{
private:
int x;
int& rx;
const double pi;
public:
A(int v) : x(v), rx(x), pi(3.14) //成員初始化列表
{ }
void print()
{
cout << 'x = ' << x << ' rx = ' << rx << ' pi = ' << pi << endl;
}
};
**說明:**類成員是按照它們在類里被聲明的順序進行初始化的,與它們在成員初始化列表中列出的順序無關。
帶默認參數(shù)的構造函數(shù)
#include <iostream>
using namespace std;
class Score{
public:
Score(int m = 0, int f = 0); //帶默認參數(shù)的構造函數(shù)
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
Score::Score(int m, int f) : mid_exam(m), fin_exam(f)
{
cout << '構造函數(shù)使用中...' << endl;
}
void Score::setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
void Score::showScore()
{
cout << '期中成績: ' << mid_exam << endl;
cout << '期末成績:' << fin_exam << endl;
}
int main()
{
Score op1(99, 100);
Score op2(88);
Score op3;
op1.showScore();
op2.showScore();
op3.showScore();
return 0;
}
析構函數(shù)
析構函數(shù)也是一種特殊的成員函數(shù)。它執(zhí)行與構造函數(shù)相反的操作,通常用于撤銷對象時的一些清理任務,如釋放分配給對象的內(nèi)存空間等。析構函數(shù)有以下一些特點:
- 析構函數(shù)與構造函數(shù)名字相同,但它前面必須加一個波浪號(~)。
- 析構函數(shù)沒有參數(shù)和返回值,也不能被重載,因此只有一個。
- 當撤銷對象時,編譯系統(tǒng)會自動調(diào)用析構函數(shù)。
class Score{
public:
Score(int m = 0, int f = 0);
~Score(); //析構函數(shù)
private:
int mid_exam;
int fin_exam;
};
Score::Score(int m, int f) : mid_exam(m), fin_exam(f)
{
cout << '構造函數(shù)使用中...' << endl;
}
Score::~Score()
{
cout << '析構函數(shù)使用中...' << endl;
}
**說明:**在以下情況中,當對象的生命周期結束時,析構函數(shù)會被自動調(diào)用:
- 如果定義了一個全局對象,則在程序流程離開其作用域時,調(diào)用該全局對象的析構函數(shù)。
- 如果一個對象定義在一個函數(shù)體內(nèi),則當這個函數(shù)被調(diào)用結束時,該對象應該被釋放,析構函數(shù)被自動調(diào)用。
- 若一個對象是使用
new 運算符創(chuàng)建的,在使用delete 運算符釋放它時,delete 會自動調(diào)用析構函數(shù)。
如下示例:
#include <iostream>
#include <string>
using namespace std;
class Student{
private:
char *name;
char *stu_no;
float score;
public:
Student(char *name1, char *stu_no1, float score1);
~Student();
void modify(float score1);
void show();
};
Student::Student(char *name1, char *stu_no1, float score1)
{
name = new char[strlen(name1) + 1];
strcpy(name, name1);
stu_no = new char[strlen(stu_no1) + 1];
strcpy(stu_no, stu_no1);
score = score1;
}
Student::~Student()
{
delete []name;
delete []stu_no;
}
void Student::modify(float score1)
{
score = score1;
}
void Student::show()
{
cout << '姓名: ' << name << endl;
cout << '學號: ' << stu_no << endl;
cout << '成績:' << score << endl;
}
int main()
{
Student stu('雪女', '2020199012', 99);
stu.modify(100);
stu.show();
return 0;
}
默認的構造函數(shù)和析構函數(shù)
如果沒有給類定義構造函數(shù),則編譯系統(tǒng)自動生成一個默認的構造函數(shù)。
說明:
-
對沒有定義構造函數(shù)的類,其公有數(shù)據(jù)成員可以用初始值列表進行初始化。 class A{
public:
char name[10];
int no;
};
A a = {'chen', 23};
cout << a.name << a.no << endl;
-
只要一個類定義了一個構造函數(shù)(不一定是無參構造函數(shù)),系統(tǒng)將不再給它提供默認的構造函數(shù)。
每個類必須有一個析構函數(shù)。若沒有顯示地為一個類定義析構函數(shù),編譯系統(tǒng)會自動生成一個默認的析構函數(shù)。
構造函數(shù)的重載
class Score{
public:
Score(int m, int f); //構造函數(shù)
Score();
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
**注意:**在一個類中,當無參數(shù)的構造函數(shù)和帶默認參數(shù)的構造函數(shù)重載時,有可能產(chǎn)生二義性。
拷貝構造函數(shù)
拷貝構造函數(shù)是一種特殊的構造函數(shù),其形參是本類對象的引用??截悩嬙旌瘮?shù)的作用是在建立一個新對象時,使用一個已存在的對象去初始化這個新對象。
拷貝構造函數(shù)具有以下特點:
- 因為拷貝構造函數(shù)也是一種構造函數(shù),所以其函數(shù)名與類名相同,并且該函數(shù)也沒有返回值。
- 拷貝構造函數(shù)只有一個參數(shù),并且是同類對象的引用。
- 每個類都必須有一個拷貝構造函數(shù)??梢宰约憾x拷貝構造函數(shù),用于按照需要初始化新對象;如果沒有定義類的拷貝構造函數(shù),系統(tǒng)就會自動生成一個默認拷貝構造函數(shù),用于復制出與數(shù)據(jù)成員值完全相同的新對象。
自定義拷貝構造函數(shù)
類名::類名(const 類名 &對象名)
{
拷貝構造函數(shù)的函數(shù)體;
}
class Score{
public:
Score(int m, int f); //構造函數(shù)
Score();
Score(const Score &p); //拷貝構造函數(shù)
~Score(); //析構函數(shù)
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
Score::Score(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
Score::Score(const Score &p)
{
mid_exam = p.mid_exam;
fin_exam = p.fin_exam;
}
調(diào)用拷貝構造函數(shù)的一般形式為:
類名 對象2(對象1);
類名 對象2 = 對象1;
Score sc1(98, 87);
Score sc2(sc1); //調(diào)用拷貝構造函數(shù)
Score sc3 = sc2; //調(diào)用拷貝構造函數(shù)
調(diào)用拷貝構造函數(shù)的三種情況:
- 當用類的一個對象去初始化該類的另一個對象時;
- 當函數(shù)的形參是類的對象,調(diào)用函數(shù)進行形參和實參結合時;
- 當函數(shù)的返回值是對象,函數(shù)執(zhí)行完成返回調(diào)用者時。
淺拷貝和深拷貝
淺拷貝,就是由默認的拷貝構造函數(shù)所實現(xiàn)的數(shù)據(jù)成員逐一賦值。通常默認的拷貝構造函數(shù)是能夠勝任此工作的,但若類中含有指針類型 的數(shù)據(jù),則這種按數(shù)據(jù)成員逐一賦值的方法會產(chǎn)生錯誤。
class Student{
public:
Student(char *name1, float score1);
~Student();
private:
char *name;
float score;
};
如下語句會產(chǎn)生錯誤
Student stu1('白', 89);
Student stu2 = stu1;
上述錯誤是因為stu1和stu2所指的內(nèi)存空間相同,在析構函數(shù)釋放stu1所指的內(nèi)存后,再釋放stu2所指的內(nèi)存會發(fā)生錯誤,因為此內(nèi)存空間已被釋放。解決方法就是重定義拷貝構造函數(shù),為其變量重新生成內(nèi)存空間。
Student::Student(const Student& stu)
{
name = new char[strlen(stu.name) + 1];
if (name != 0) {
strcpy(name, stu.name);
score = stu.score;
}
}
四、類和對象(二)
~
4.1 自引用指針this
this 指針保存當前對象的地址,稱為自引用指針。
void Sample::copy(Sample& xy)
{
if (this == &xy) return;
*this = xy;
}
~
4.2 對象數(shù)組與對象指針
對象數(shù)組
類名 數(shù)組名[下標表達式]
用只有一個參數(shù)的構造函數(shù)給對象數(shù)組賦值
Exam ob[4] = {89, 97, 79, 88};
用不帶參數(shù)和帶一個參數(shù)的構造函數(shù)給對象數(shù)組賦值
Exam ob[4] = {89, 90};
用帶有多個參數(shù)的構造函數(shù)給對象數(shù)組賦值
Score rec[3] = {Score(33, 99), Score(87, 78), Score(99, 100)};
對象指針
每一個對象在初始化后都會在內(nèi)存中占有一定的空間。因此,既可以通過對象名訪問對象,也可以通過對象地址來訪問對象。對象指針就是用于存放對象地址的變量。聲明對象指針的一半語法形式為:類名 *對象指針名
Score score;
Score *p;
p = &score;
p->成員函數(shù)();
用對象指針訪問對象數(shù)組
Score score[2];
score[0].setScore(90, 99);
score[1].setScore(67, 89);
Score *p;
p = score; //將對象score的地址賦值給p
p->showScore();
p++; //對象指針變量加1
p->showSccore();
Score *q;
q =&score[1]; //將第二個數(shù)組元素的地址賦值給對象指針變量q
~
4.3 string類
C++支持兩種類型的字符串,第一種是C語言中介紹過的、包括一個結束符’\0 ’(即以NULL 結束)的字符數(shù)組,標準庫函數(shù)提供了一組對其進行操作的函數(shù),可以完成許多常用的字符串操作。
C++標準庫中聲明了一種更方便的字符串類型,即字符串類string,類string提供了對字符串進行處理所需要的操作。使用string類必須在程序的開始包括頭文件string,即要有以下語句:#include <string>
常用的string類運算符如下:
=、+、+=、==、!=、<、<=、>、>=、[](訪問下標對應字符)、>>(輸入)、<<(輸出)
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1 = 'ABC';
string str2('dfdf');
string str3 = str1 + str2;
cout<< 'str1 = ' << str1 << ' str2 = ' << str2 << ' str3 = ' << str3 << endl;
str2 += str2;
str3 += 'aff';
cout << 'str2 = ' << str2 << ' str3 = ' << str3 << endl;
cout << 'str1[1] = ' << str1[1] << ' str1 == str2 ? ' << (str1 == str2) << endl;
string str = 'ABC';
cout << 'str == str1 ? ' << (str == str1) << endl;
return 0;
}
~
4.4 向函數(shù)傳遞對象
- 使用對象作為函數(shù)參數(shù):對象可以作為參數(shù)傳遞給函數(shù),其方法與傳遞其他類型的數(shù)據(jù)相同。在向函數(shù)傳遞對象時,是通過“傳值調(diào)用”的方法傳遞給函數(shù)的。因此,函數(shù)中對對象的任何修改均不影響調(diào)用該函數(shù)的對象(實參本身)。
- 使用對象指針作為函數(shù)參數(shù):對象指針可以作為函數(shù)的參數(shù),使用對象指針作為函數(shù)參數(shù)可以實現(xiàn)傳值調(diào)用,即在函數(shù)調(diào)用時使實參對象和形參對象指針變量指向同一內(nèi)存地址,在函數(shù)調(diào)用過程中,形參對象指針所指的對象值的改變也同樣影響著實參對象的值。
- 使用對象引用作為函數(shù)參數(shù):在實際中,使用對象引用作為函數(shù)參數(shù)非常普遍,大部分程序員喜歡使用對象引用替代對象指針作為函數(shù)參數(shù)。因為使用對象引用作為函數(shù)參數(shù)不但具有用對象指針做函數(shù)參數(shù)的優(yōu)點,而且用對象引用作函數(shù)參數(shù)將更簡單、更直接。
#include <iostream>
using namespace std;
class Point{
public:
int x;
int y;
Point(int x1, int y1) : x(x1), y(y1) //成員初始化列表
{ }
int getDistance()
{
return x * x + y * y;
}
};
void changePoint1(Point point) //使用對象作為函數(shù)參數(shù)
{
point.x += 1;
point.y -= 1;
}
void changePoint2(Point *point) //使用對象指針作為函數(shù)參數(shù)
{
point->x += 1;
point->y -= 1;
}
void changePoint3(Point &point) //使用對象引用作為函數(shù)參數(shù)
{
point.x += 1;
point.y -= 1;
}
int main()
{
Point point[3] = {Point(1, 1), Point(2, 2), Point(3, 3)};
Point *p = point;
changePoint1(*p);
cout << 'the distance is ' << p[0].getDistance() << endl;
p++;
changePoint2(p);
cout << 'the distance is ' << p->getDistance() << endl;
changePoint3(point[2]);
cout << 'the distance is ' << point[2].getDistance() << endl;
return 0;
}
~
4.5 靜態(tài)成員
靜態(tài)數(shù)據(jù)成員
在一個類中,若將一個數(shù)據(jù)成員說明為static ,則這種成員被稱為靜態(tài)數(shù)據(jù)成員。與一般的數(shù)據(jù)成員不同,無論建立多少個類的對象,都只有一個靜態(tài)數(shù)據(jù)成員的拷貝。從而實現(xiàn)了同一個類的不同對象之間的數(shù)據(jù)共享。
定義靜態(tài)數(shù)據(jù)成員的格式如下:static 數(shù)據(jù)類型 數(shù)據(jù)成員名;
說明:
-
靜態(tài)數(shù)據(jù)成員的定義與普通數(shù)據(jù)成員相似,但前面要加上static關鍵字。 -
靜態(tài)數(shù)據(jù)成員的初始化與普通數(shù)據(jù)成員不同。靜態(tài)數(shù)據(jù)成員初始化應在類外單獨進行,而且應在定義對象之前進行。一般在main()函數(shù)之前、類聲明之后的特殊地帶為它提供定義和初始化。 -
靜態(tài)數(shù)據(jù)成員屬于類(準確地說,是屬于類中對象的集合),而不像普通數(shù)據(jù)成員那樣屬于某一對象,因此,可以使用“類名:: ”訪問靜態(tài)的數(shù)據(jù)成員。格式如下:類名::靜態(tài)數(shù)據(jù)成員名 。 -
靜態(tài)數(shù)據(jù)成員與靜態(tài)變量一樣,是在編譯時創(chuàng)建并初始化。它在該類的任何對象被建立之前就存在。因此,共有的靜態(tài)數(shù)據(jù)成員可以在對象定義之前被訪問。對象定以后,共有的靜態(tài)數(shù)據(jù)成員也可以通過對象進行訪問。其訪問格式如下 對象名.靜態(tài)數(shù)據(jù)成員名;
對象指針->靜態(tài)數(shù)據(jù)成員名;
靜態(tài)成員函數(shù)
在類定義中,前面有static說明的成員函數(shù)稱為靜態(tài)成員函數(shù)。靜態(tài)成員函數(shù)屬于整個類,是該類所有對象共享的成員函數(shù),而不屬于類中的某個對象。靜態(tài)成員函數(shù)的作用不是為了對象之間的溝通,而是為了處理靜態(tài)數(shù)據(jù)成員。定義靜態(tài)成員函數(shù)的格式如下:
static 返回類型 靜態(tài)成員函數(shù)名(參數(shù)表) ;
與靜態(tài)數(shù)據(jù)成員類似,調(diào)用公有靜態(tài)成員函數(shù)的一般格式有如下幾種:
類名::靜態(tài)成員函數(shù)名(實參表);
對象.靜態(tài)成員函數(shù)名(實參表);
對象指針->靜態(tài)成員函數(shù)名(實參表);
一般而言,靜態(tài)成員函數(shù)不訪問類中的非靜態(tài)成員。若確實需要,靜態(tài)成員函數(shù)只能通過對象名(或?qū)ο笾羔?、對象引用)訪問該對象的非靜態(tài)成員。
下面對靜態(tài)成員函數(shù)的使用再做幾點說明:
- 一般情況下,靜態(tài)函數(shù)成員主要用來訪問靜態(tài)成員函數(shù)。當它與靜態(tài)數(shù)據(jù)成員一起使用時,達到了對同一個類中對象之間共享數(shù)據(jù)的目的。
- 私有靜態(tài)成員函數(shù)不能被類外部的函數(shù)和對象訪問。
- 使用靜態(tài)成員函數(shù)的一個原因是,可以用它在建立任何對象之前調(diào)用靜態(tài)成員函數(shù),以處理靜態(tài)數(shù)據(jù)成員,這是普通成員函數(shù)不能實現(xiàn)的功能
- 編譯系統(tǒng)將靜態(tài)成員函數(shù)限定為內(nèi)部連接,也就是說,與現(xiàn)行文件相連接的其他文件中的同名函數(shù)不會與該函數(shù)發(fā)生沖突,維護了該函數(shù)使用的安全性,這是使用靜態(tài)成員函數(shù)的另一個原因。
- 靜態(tài)成員函數(shù)是類的一部分,而不是對象的一部分。如果要在類外調(diào)用公有的靜態(tài)成員函數(shù),使用如下格式較好:
類名::靜態(tài)成員函數(shù)名()
#include <iostream>
using namespace std;
class Score{
private:
int mid_exam;
int fin_exam;
static int count; //靜態(tài)數(shù)據(jù)成員,用于統(tǒng)計學生人數(shù)
static float sum; //靜態(tài)數(shù)據(jù)成員,用于統(tǒng)計期末累加成績
static float ave; //靜態(tài)數(shù)據(jù)成員,用于統(tǒng)計期末平均成績
public:
Score(int m, int f);
~Score();
static void show_count_sum_ave(); //靜態(tài)成員函數(shù)
};
Score::Score(int m, int f)
{
mid_exam = m;
fin_exam = f;
++count;
sum += fin_exam;
ave = sum / count;
}
Score::~Score()
{
}
/*** 靜態(tài)成員初始化 ***/
int Score::count = 0;
float Score::sum = 0.0;
float Score::ave = 0.0;
void Score::show_count_sum_ave()
{
cout << '學生人數(shù): ' << count << endl;
cout << '期末累加成績: ' << sum << endl;
cout << '期末平均成績: ' << ave << endl;
}
int main()
{
Score sco[3] = {Score(90, 89), Score(78, 99), Score(89, 88)};
sco[2].show_count_sum_ave();
Score::show_count_sum_ave();
return 0;
}
~
4.6 友元
類的主要特點之一是數(shù)據(jù)隱藏和封裝,即類的私有成員(或保護成員)只能在類定義的范圍內(nèi)使用,也就是說私有成員只能通過它的成員函數(shù)來訪問。但是,有時為了訪問類的私有成員而需要在程序中多次調(diào)用成員函數(shù),這樣會因為頻繁調(diào)用帶來較大的時間和空間開銷,從而降低程序的運行效率。為此,C++提供了友元來對私有或保護成員進行訪問。友元包括友元函數(shù)和友元類。
友元函數(shù)
友元函數(shù)既可以是不屬于任何類的非成員函數(shù),也可以是另一個類的成員函數(shù)。友元函數(shù)不是當前類的成員函數(shù),但它可以訪問該類的所有成員,包括私有成員、保護成員和公有成員。
在類中聲明友元函數(shù)時,需要在其函數(shù)名前加上關鍵字friend。此聲明可以放在公有部分,也可以放在保護部分和私有部分。友元函數(shù)可以定義在類內(nèi)部,也可以定義在類外部。
1、將非成員函數(shù)聲明為友元函數(shù)
#include <iostream>
using namespace std;
class Score{
private:
int mid_exam;
int fin_exam;
public:
Score(int m, int f);
void showScore();
friend int getScore(Score &ob);
};
Score::Score(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
int getScore(Score &ob)
{
return (int)(0.3 * ob.mid_exam + 0.7 * ob.fin_exam);
}
int main()
{
Score score(98, 78);
cout << '成績?yōu)? ' << getScore(score) << endl;
return 0;
}
說明:
- 友元函數(shù)雖然可以訪問類對象的私有成員,但他畢竟不是成員函數(shù)。因此,在類的外部定義友元函數(shù)時,不必像成員函數(shù)那樣,在函數(shù)名前加上“
類名:: ”。 - 因為友元函數(shù)不是類的成員,所以它不能直接訪問對象的數(shù)據(jù)成員,也不能通過this指針訪問對象的數(shù)據(jù)成員,它必須通過作為入口參數(shù)傳遞進來的對象名(或?qū)ο笾羔槨ο笠茫﹣碓L問該對象的數(shù)據(jù)成員。
- 友元函數(shù)提供了不同類的成員函數(shù)之間、類的成員函數(shù)與一般函數(shù)之間進行數(shù)據(jù)共享的機制。尤其當一個函數(shù)需要訪問多個類時,友元函數(shù)非常有用,普通的成員函數(shù)只能訪問其所屬的類,但是多個類的友元函數(shù)能夠訪問相關的所有類的數(shù)據(jù)。
例子:一個函數(shù)同時定義為兩個類的友元函數(shù)
#include <iostream>
#include <string>
using namespace std;
class Score; //對Score類的提前引用說明
class Student{
private:
string name;
int number;
public:
Student(string na, int nu) {
name = na;
number = nu;
}
friend void show(Score &sc, Student &st);
};
class Score{
private:
int mid_exam;
int fin_exam;
public:
Score(int m, int f) {
mid_exam = m;
fin_exam = f;
}
friend void show(Score &sc, Student &st);
};
void show(Score &sc, Student &st) {
cout << '姓名:' << st.name << ' 學號:' << st.number << endl;
cout << '期中成績:' << sc.mid_exam << ' 期末成績:' << sc.fin_exam << endl;
}
int main() {
Score sc(89, 99);
Student st('白', 12467);
show(sc, st);
return 0;
}
2、將成員函數(shù)聲明為友元函數(shù)
一個類的成員函數(shù)可以作為另一個類的友元,它是友元函數(shù)中的一種,稱為友元成員函數(shù)。友元成員函數(shù)不僅可以訪問自己所在類對象中的私有成員和公有成員,還可以訪問friend聲明語句所在類對象中的所有成員,這樣能使兩個類相互合作、協(xié)調(diào)工作,完成某一任務。
#include <iostream>
#include <string>
using namespace std;
class Score; //對Score類的提前引用說明
class Student{
private:
string name;
int number;
public:
Student(string na, int nu) {
name = na;
number = nu;
}
void show(Score &sc);
};
class Score{
private:
int mid_exam;
int fin_exam;
public:
Score(int m, int f) {
mid_exam = m;
fin_exam = f;
}
friend void Student::show(Score &sc);
};
void Student::show(Score &sc) {
cout << '姓名:' << name << ' 學號:' << number << endl;
cout << '期中成績:' << sc.mid_exam << ' 期末成績:' << sc.fin_exam << endl;
}
int main() {
Score sc(89, 99);
Student st('白', 12467);
st.show(sc);
return 0;
}
說明:
- 一個類的成員函數(shù)作為另一個類的友元函數(shù)時,必須先定義這個類。并且在聲明友元函數(shù)時,需要加上成員函數(shù)所在類的類名;
友元類
可以將一個類聲明為另一個類的友元
class Y{
···
};
class X{
friend Y; //聲明類Y為類X的友元類
};
當一個類被說明為另一個類的友元類時,它所有的成員函數(shù)都成為另一個類的友元函數(shù),這就意味著作為友元類中的所有成員函數(shù)都可以訪問另一個類中的所有成員。
友元關系不具有交換性和傳遞性。
~
4.7 類的組合
在一個類中內(nèi)嵌另一個類的對象作為數(shù)據(jù)成員,稱為類的組合。該內(nèi)嵌對象稱為對象成員,又稱為子對象。
class Y{
···
};
class X{
Y y;
···
};
~
4.8 共享數(shù)據(jù)的保護
常類型的引入就是為了既保護數(shù)據(jù)共享又防止數(shù)據(jù)被改動。常類型是指使用類型修飾符const說明的類型,常類型的變量或?qū)ο蟪蓡T的值在程序運行期間是不可改變的。
常引用
如果在說明引用時用const修飾,則被說明的引用為常引用。常引用所引用的對象不能被更新。如果用常引用做形參,便不會產(chǎn)生對實參的不希望的更改。
const 類型& 引用名
int a = 5;
const int& b = a;
此時再對b賦值是非法的。
---------------------------
int add(const int& m, const int& n) {
return m + n;
}
在此函數(shù)中對變量m和變量n更新時非法的
常對象
如果在說明對象時用const修飾,則被說明的對象為常對象。常對象中的數(shù)據(jù)成員為常量且必須要有初值。
類名 const 對象名[(參數(shù)表)];
const Date date(2021, 5, 31);
常對象成員
1、常數(shù)據(jù)成員
類的數(shù)據(jù)成員可以是常量或常引用,使用const說明的數(shù)據(jù)成員稱為常數(shù)據(jù)成員。如果在一個類中說明了常數(shù)據(jù)成員,那么構造函數(shù)就只能通過成員初始化列表對該數(shù)據(jù)成員進行初始化,而任何其他函數(shù)都不能對該成員賦值。
class Date{
private:
int year;
int month;
int day;
public:
Date(int y, int m, int d) : year(y), month(m), day(d) {
}
};
一旦某對象的常數(shù)據(jù)成員初始化后,該數(shù)據(jù)成員的值是不能改變的。
2、常成員函數(shù)
類型 函數(shù)名(參數(shù)表) const;
const是函數(shù)類型的一個組成部分,因此在聲明函數(shù)和定義函數(shù)時都要有關鍵字const。在調(diào)用時不必加const。
class Date{
private:
int year;
int month;
int day;
public:
Date(int y, int m, int d) : year(y), month(m), day(d){
}
void showDate();
void showDate() const;
};
void Date::showDate() {
//···
}
void Date::showDate() const {
//···
}
關鍵字const可以被用于對重載函數(shù)進行區(qū)分。
說明:
- 常成員函數(shù)可以訪問常數(shù)據(jù)成員,也可以訪問普通數(shù)據(jù)成員。
- 常對象只能調(diào)用它的常成員對象,而不能調(diào)用普通成員函數(shù)。常成員函數(shù)是常對象唯一的對外接口。
- 常對象函數(shù)不能更新對象的數(shù)據(jù)成員,也不能調(diào)用該類的普通成員函數(shù),這就保證了在常成員函數(shù)中絕不會更新數(shù)據(jù)成員的值。
五、繼承與派生
繼承可以在已有類的基礎上創(chuàng)建新的類,新類可以從一個或多個已有類中繼承成員函數(shù)和數(shù)據(jù)成員,而且可以重新定義或加進新的數(shù)據(jù)和函數(shù),從而形成類的層次或等級。其中,已有類稱為基類或父類,在它基礎上建立的新類稱為派生類或子類。
~
5.1 繼承與派生的概念
類的繼承是新的類從已有類那里得到已有的特性。從另一個角度來看這個問題,從已有類產(chǎn)生新類的過程就是類的派生。類的繼承和派生機制較好地解決了代碼重用的問題。
關于基類和派生類的關系,可以表述為:派生類是基類的具體化,而基類則是派生類的抽象。
使用繼承的案例如下:
#include <iostream>
#include <string>
using namespace std;
class Person{
private:
string name;
string id_number;
int age;
public:
Person(string name1, string id_number1, int age1) {
name = name1;
id_number = id_number1;
age = age1;
}
~Person() {
}
void show() {
cout << '姓名: ' << name << ' 身份證號: ' << id_number << ' 年齡: ' << age << endl;
}
};
class Student:public Person{
private:
int credit;
public:
Student(string name1, string id_number1, int age1, int credit1):Person(name1, id_number1, credit1) {
credit = credit1;
}
~Student() {
}
void show() {
Person::show();
cout << '學分: ' << credit << endl;
}
};
int main() {
Student stu('白', '110103**********23', 12, 123);
stu.show();
return 0;
}
從已有類派生出新類時,可以在派生類內(nèi)完成以下幾種功能:
- 可以增加新的數(shù)據(jù)成員和成員函數(shù)
- 可以對基類的成員進行重定義
- 可以改變基類成員在派生類中的訪問屬性
基類成員在派生類中的訪問屬性
派生類可以繼承基類中除了構造函數(shù)與析構函數(shù)之外的成員,但是這些成員的訪問屬性在派生過程中是可以調(diào)整的。從基類繼承來的成員在派生類中的訪問屬性也有所不同。
基類中的成員 | 繼承方式 | 基類在派生類中的訪問屬性 |
---|
private | public|protected|private | 不可直接訪問 | public | public|protected|private | public|protected|private | protected | public|protected|private | protected|protected|private |
派生類對基類成員的訪問規(guī)則
基類的成員可以有public、protected、private3中訪問屬性,基類的成員函數(shù)可以訪問基類中其他成員,但是在類外通過基類的對象,就只能訪問該基類的公有成員。同樣,派生類的成員也可以有public、protected、private3種訪問屬性,派生類的成員函數(shù)可以訪問派生類中自己增加的成員,但是在派生類外通過派生類的對象,就只能訪問該派生類的公有成員。
派生類對基類成員的訪問形式主要有以下兩種:
- 內(nèi)部訪問:由派生類中新增的成員函數(shù)對基類繼承來的成員的訪問。
- 對象訪問:在派生類外部,通過派生類的對象對從基類繼承來的成員的訪問。
~
5.2 派生類的構造函數(shù)和析構函數(shù)
構造函數(shù)的主要作用是對數(shù)據(jù)進行初始化。在派生類中,如果對派生類新增的成員進行初始化,就需要加入派生類的構造函數(shù)。與此同時,對所有從基類繼承下來的成員的初始化工作,還是由基類的構造函數(shù)完成,但是基類的構造函數(shù)和析構函數(shù)不能被繼承,因此必須在派生類的構造函數(shù)中對基類的構造函數(shù)所需要的參數(shù)進行設置。同樣,對撤銷派生類對象的掃尾、清理工作也需要加入新的析構函數(shù)來完成。
調(diào)用順序
#include <iostream>
#include <string>
using namespace std;
class A{
public:
A() {
cout << 'A類對象構造中...' << endl;
}
~A() {
cout << '析構A類對象...' << endl;
}
};
class B : public A{
public:
B() {
cout << 'B類對象構造中...' << endl;
}
~B(){
cout << '析構B類對象...' << endl;
}
};
int main() {
B b;
return 0;
}
代碼運行結果如下:
A類對象構造中...
B類對象構造中...
析構B類對象...
析構A類對象...
可見:構造函數(shù)的調(diào)用嚴格地按照先調(diào)用基類的構造函數(shù),后調(diào)用派生類的構造函數(shù)的順序執(zhí)行。析構函數(shù)的調(diào)用順序與構造函數(shù)的調(diào)用順序正好相反,先調(diào)用派生類的析構函數(shù),后調(diào)用基類的析構函數(shù)。
派生類構造函數(shù)和析構函數(shù)的構造規(guī)則
派生類構造函數(shù)的一般格式為:
派生類名(參數(shù)總表):基類名(參數(shù)表) {
派生類新增數(shù)據(jù)成員的初始化語句
}
-----------------------------------------------------------------
含有子對象的派生類的構造函數(shù):
派生類名(參數(shù)總表):基類名(參數(shù)表0),子對象名1(參數(shù)表1),...,子對象名n(參數(shù)表n)
{
派生類新增成員的初始化語句
}
在定義派生類對象時,構造函數(shù)的調(diào)用順序如下:
調(diào)用基類的構造函數(shù),對基類數(shù)據(jù)成員初始化。
調(diào)用子對象的構造函數(shù),對子對象的數(shù)據(jù)成員初始化。
調(diào)用派生類的構造函數(shù)體,對派生類的數(shù)據(jù)成員初始化。
說明:
- 當基類構造函數(shù)不帶參數(shù)時,派生類不一定需要定義構造函數(shù);然而當基類的構造函數(shù)哪怕只帶有一個參數(shù),它所有的派生類都必須定義構造函數(shù),甚至所定義的派生類構造函數(shù)的函數(shù)體可能為空,它僅僅起參數(shù)的傳遞作用。
- 若基類使用默認構造函數(shù)或不帶參數(shù)的構造函數(shù),則在派生類中定義構造函數(shù)時可略去“
:基類構造函數(shù)名(參數(shù)表) ”,此時若派生類也不需要構造函數(shù),則可不定義構造函數(shù)。 - 如果派生類的基類也是一個派生類,每個派生類只需負責其直接基類數(shù)據(jù)成員的初始化,依次上溯。
~
5.3 調(diào)整基類成員在派生類中的訪問屬性的其他方法
派生類可以聲明與基類成員同名的成員。在沒有虛函數(shù)的情況下,如果在派生類中定義了與基類成員同名的成員,則稱派生類成員覆蓋了基類的同名成員,在派生類中使用這個名字意味著訪問在派生類中聲明的成員。為了在派生類中使用與基類同名的成員,必須在該成員名之前加上基類名和作用域標識符“:: ”,即
基類名::成員名
訪問聲明
訪問聲明的方法就是把基類的保護成員或共有成員直接寫在私有派生類定義式中的同名段中,同時給成員名前冠以基類名和作用域標識符“::”。利用這種方法,該成員就成為派生類的保護成員或共有成員了。
class B:private A{
private:
int y;
public:
B(int x1, int y1) : A(x1) {
y = y1;
}
A::show; //訪問聲明
};
訪問聲明在使用時應注意以下幾點:
- 數(shù)據(jù)成員也可以使用訪問聲明。
- 訪問聲明中只含不帶類型和參數(shù)的函數(shù)名或變量名。
- 訪問聲明不能改變成員在基類中的訪問屬性。
- 對于基類的重載函數(shù)名,訪問聲明將對基類中所有同名函數(shù)其起作用。
~
5.4 多繼承
聲明多繼承派生類的一般形式如下:
class 派生類名:繼承方式1 基類名1,...,繼承方式n 基類名n {
派生類新增的數(shù)據(jù)成員和成員函數(shù)
};
默認的繼承方式是private
多繼承派生類的構造函數(shù)與析構函數(shù):
與單繼承派生類構造函數(shù)相同,多重繼承派生類構造函數(shù)必須同時負責該派生類所有基類構造函數(shù)的調(diào)用。
多繼承構造函數(shù)的調(diào)用順序與單繼承構造函數(shù)的調(diào)用順序相同,也是遵循先調(diào)用基類的構造函數(shù),再調(diào)用對象成員的構造函數(shù),最后調(diào)用派生類構造函數(shù)的原則。析構函數(shù)的調(diào)用與之相反。
~
5.5 虛基類
虛基類的作用:如果一個類有多個直接基類,而這些直接基類又有一個共同的基類,則在最低層的派生類中會保留這個間接的共同基類數(shù)據(jù)成員的多份同名成員。在訪問這些同名成員時,必須在派生類對象名后增加直接基類名,使其唯一地標識一個成員,以免產(chǎn)生二義性。
#include <iostream>
#include <string>
using namespace std;
class Base{
protected:
int a;
public:
Base(){
a = 5;
cout << 'Base a = ' << a << endl;
}
};
class Base1: public Base{
public:
Base1() {
a = a + 10;
cout << 'Base1 a = ' << a << endl;
}
};
class Base2: public Base{
public:
Base2() {
a = a + 20;
cout << 'Base2 a = ' << a << endl;
}
};
class Derived: public Base1, public Base2{
public:
Derived() {
cout << 'Base1::a = ' << Base1::a << endl;
cout << 'Base2::a = ' << Base2::a << endl;
}
};
int main() {
Derived obj;
return 0;
}
代碼執(zhí)行結果如下
Base a = 5
Base1 a = 15
Base a = 5
Base2 a = 25
Base1::a = 15
Base2::a = 25
虛基類的聲明:
不難理解,如果在上列中類base只存在一個拷貝(即只有一個數(shù)據(jù)成員a),那么對a的訪問就不會產(chǎn)生二義性。在C++中,可以通過將這個公共的基類聲明為虛基類來解決這個問題。這就要求從類base派生新類時,使用關鍵字virtual 將base聲明為虛基類。
聲明虛基類的語法形式如下:
class 派生類:virtual 繼承方式 類名{
·····
};
上述代碼修改如下:
class Base1:virtual public Base{
public:
Base1() {
a = a + 10;
cout << 'Base1 a = ' << a << endl;
}
};
class Base2:virtual public Base{
public:
Base2() {
a = a + 20;
cout << 'Base2 a = ' << a << endl;
}
};
運行結果如下:
Base a = 5
Base1 a = 15
Base2 a = 35
Base1::a = 35
Base2::a = 35
虛基類的初始化:
虛基類的初始化與一般的多繼承的初始化在語法上是一樣的,但構造函數(shù)的調(diào)用順序不同。在使用虛基類機制時應該注意以下幾點:
- 如果在虛基類中定義有帶形參的構造函數(shù),并且沒有定義默認形式的構造函數(shù),則整個繼承結構中,所有直接或間接的派生類都必須在構造函數(shù)的成員初始化列表中列出對虛基類構造函數(shù)的調(diào)用,以初始化在虛基類中定義的數(shù)據(jù)成員。
- 建立一個對象時,如果這個對象中含有從虛基類繼承來的成員,則虛基類的成員是由最遠派生類的構造函數(shù)通過調(diào)用虛基類的構造函數(shù)進行初始化的。該派生類的其他基類對虛基類構造函數(shù)的調(diào)用都被自動忽略。
- 若同一層次中同時包含虛基類和非虛基類,應先調(diào)用虛基類的構造函數(shù),再調(diào)用非虛基類的構造函數(shù),最后調(diào)用派生類構造函數(shù)。
- 對于多個虛基類,構造函數(shù)的執(zhí)行順序仍然是先左后右,自上而下。
- 若虛基類由非虛基類派生而來,則仍然先調(diào)用基類構造函數(shù),再調(diào)用派生類的構造函數(shù)。
~
5.6 賦值兼容規(guī)則
在一定條件下,不同類型的數(shù)據(jù)之間可以進行類型轉(zhuǎn)換,如可以將整型數(shù)據(jù)賦值給雙精度型變量。在賦值之前,先把整型數(shù)據(jù)轉(zhuǎn)換成雙精度數(shù)據(jù),然后再把它賦給雙精度變量。這種不同數(shù)據(jù)類型之間的自動轉(zhuǎn)換和賦值,稱為賦值兼容。在基類和派生類對象之間也存有賦值兼容關系,基類和派生類對象之間的賦值兼容規(guī)則是指在需要基類對象的任何地方,都可以用子類的對象代替。
例如,下面聲明的兩個類:
class Base{
·····
};
class Derived: public Base{
·····
};
根據(jù)賦值兼容規(guī)則,在基類Base的對象可以使用的任何地方,都可以使用派生類Derived的對象來代替,但只能使用從基類繼承來的成員。具體的表現(xiàn)在以下幾個方面:
-
派生類對象可以賦值給基類對象,即用派生類對象中從基類繼承來的數(shù)據(jù)成員,逐個賦值給基類對象的數(shù)據(jù)成員。 Base b;
Derived d;
b = d;
-
派生類對象可以初始化基類對象的引用。 Derived d;
Base &br = d;
-
派生類對象的地址可以賦值給指向基類對象的指針。 Derived d;
Base *bp = &d;
六、多態(tài)性與虛函數(shù)
多態(tài)性是面向?qū)ο蟪绦蛟O計的重要特征之一。多態(tài)性機制不僅增加了面向?qū)ο筌浖到y(tǒng)的靈活性,進一步減少了冗余信息,而且顯著提高了軟件的可重用性和可擴充性。多態(tài)性的應用可以使編程顯得更簡潔便利,它為程序的模塊化設計又提供了一種手段。
~
6.1 多態(tài)性概述
所謂多態(tài)性就是不同對象收到相同的消息時,產(chǎn)生不同的動作。這樣,就可以用同樣的接口訪問不同功能的函數(shù),從而實現(xiàn)“一個接口,多種方法”。
從實現(xiàn)的角度來講,多態(tài)可以劃分為兩類:編譯時的多態(tài)和運行時的多態(tài)。在C++中,多態(tài)的實現(xiàn)和連編這一概念有關。所謂連編就是把函數(shù)名與函數(shù)體的程序代碼連接在一起的過程。靜態(tài)連編就是在編譯階段完成的連編。編譯時的多態(tài)是通過靜態(tài)連編來實現(xiàn)的。靜態(tài)連編時,系統(tǒng)用實參與形參進行匹配,對于同名的重載函數(shù)便根據(jù)參數(shù)上的差異進行區(qū)分,然后進行連編,從而實現(xiàn)了多態(tài)性。運行時的多態(tài)是用動態(tài)連編實現(xiàn)的。動態(tài)連編時運行階段完成的,即當程序調(diào)用到某一函數(shù)名時,才去尋找和連接其程序代碼,對面向?qū)ο蟪绦蛟O計而言,就是當對象接收到某一消息時,才去尋找和連接相應的方法。
一般而言,編譯型語言(如C,Pascal)采用靜態(tài)連編,而解釋型語言(如LISP)采用動態(tài)連編。靜態(tài)連編要求在程序編譯時就知道調(diào)用函數(shù)的全部信息。因此,這種連編類型的函數(shù)調(diào)用速度快、效率高,但缺乏靈活性;而動態(tài)連編方式恰好相反,采用這種連編方式,一直要到程序運行時才能確定調(diào)用哪個函數(shù),它降低了程序的運行效率,但增強了程序的靈活性。純粹的面向?qū)ο蟪绦蛘Z言由于其執(zhí)行機制是消息傳遞,所以只能采用動態(tài)連編。C++實際上采用了靜態(tài)連編和動態(tài)連編相結合的方式。
在C++中,編譯時多態(tài)性主要是通過函數(shù)重載和運算符重載實現(xiàn)的;運行時多態(tài)性主要是通過虛函數(shù)來實現(xiàn)的。
~
6.2 虛函數(shù)
虛函數(shù)的定義是在基類中進行的,它是在基類中需要定義為虛函數(shù)的成員函數(shù)的聲明中冠以關鍵字virtual,從而提供一種接口界面。定義虛函數(shù)的方法如下:
virtual 返回類型 函數(shù)名(形參表) {
函數(shù)體
}
在基類中的某個成員函數(shù)被聲明為虛函數(shù)后,此虛函數(shù)就可以在一個或多個派生類中被重新定義。虛函數(shù)在派生類中重新定義時,其函數(shù)原型,包括返回類型、函數(shù)名、參數(shù)個數(shù)、參數(shù)類型的順序,都必須與基類中的原型完全相同。
#include <iostream>
#include <string>
using namespace std;
class Family{
private:
string flower;
public:
Family(string name = '鮮花'): flower(name) { }
string getName() {
return flower;
}
virtual void like() {
cout << '家人喜歡不同的花: ' << endl;
}
};
class Mother: public Family{
public:
Mother(string name = '月季'): Family(name) { }
void like() {
cout << '媽媽喜歡' << getName() << endl;
}
};
class Daughter: public Family{
public:
Daughter(string name = '百合'): Family(name) { }
void like() {
cout << '女兒喜歡' << getName() << endl;
}
};
int main() {
Family *p;
Family f;
Mother mom;
Daughter dau;
p = &f;
p->like();
p = &mom;
p->like();
p = &dau;
p->like();
return 0;
}
程序運行結果如下:
家人喜歡不同的花:
媽媽喜歡月季
女兒喜歡百合
C++規(guī)定,如果在派生類中,沒有用virtual顯式地給出虛函數(shù)聲明,這時系統(tǒng)就會遵循以下的規(guī)則來判斷一個成員函數(shù)是不是虛函數(shù):該函數(shù)與基類的虛函數(shù)是否有相同的名稱、參數(shù)個數(shù)以及對應的參數(shù)類型、返回類型或者滿足賦值兼容的指針、引用型的返回類型。
下面對虛函數(shù)的定義做幾點說明:
- 由于虛函數(shù)使用的基礎是賦值兼容規(guī)則,而賦值兼容規(guī)則成立的前提條件是派生類從其基類公有派生。因此,通過定義虛函數(shù)來使用多態(tài)性機制時,派生類必須從它的基類公有派生。
- 必須首先在基類中定義虛函數(shù);
- 在派生類對基類中聲明的虛函數(shù)進行重新定義時,關鍵字virtual可以寫也可以不寫。
- 雖然使用對象名和點運算符的方式也可以調(diào)用虛函數(shù),如mom.like()可以調(diào)用虛函數(shù)Mother::like()。但是,這種調(diào)用是在編譯時進行的靜態(tài)連編,它沒有充分利用虛函數(shù)的特性,只有通過基類指針訪問虛函數(shù)時才能獲得運行時的多態(tài)性
- 一個虛函數(shù)無論被公有繼承多少次,它仍然保持其虛函數(shù)的特性。
- 虛函數(shù)必須是其所在類的成員函數(shù),而不能是友元函數(shù),也不能是靜態(tài)成員函數(shù),因為虛函數(shù)調(diào)用要靠特定的對象來決定該激活哪個函數(shù)。
- 內(nèi)聯(lián)函數(shù)不能是虛函數(shù),因為內(nèi)聯(lián)函數(shù)是不能在運行中動態(tài)確定其位置的。即使虛函數(shù)在類的內(nèi)部定義,編譯時仍將其看做非內(nèi)聯(lián)的。
- 構造函數(shù)不能是虛函數(shù),但是析構函數(shù)可以是虛函數(shù),而且通常說明為虛函數(shù)。
~
在一個派生類中重新定義基類的虛函數(shù)是函數(shù)重載的另一種形式。
多繼承可以視為多個單繼承的組合,因此,多繼承情況下的虛函數(shù)調(diào)用與單繼承下的虛函數(shù)調(diào)用由相似之處。
~
6.3 虛析構函數(shù)
如果在主函數(shù)中用new運算符建立一個派生類的無名對象和定義一個基類的對象指針,并將無名對象的地址賦值給這個對象指針,當用delete運算符撤銷無名對象時,系統(tǒng)只執(zhí)行基類的析構函數(shù),而不執(zhí)行派生類的析構函數(shù)。
Base *p;
p = new Derived;
delete p;
-----------------
輸出:調(diào)用基類Base的析構函數(shù)
原因是當撤銷指針p所指的派生類的無名對象,而調(diào)用析構函數(shù)時,采用了靜態(tài)連編方式,只調(diào)用了基類Base的析構函數(shù)。
如果希望程序執(zhí)行動態(tài)連編方式,在用delete運算符撤銷派生類的無名對象時,先調(diào)用派生類的析構函數(shù),再調(diào)用基類的析構函數(shù),可以將基類的析構函數(shù)聲明為虛析構函數(shù)。一般格式為
virtual ~類名(){
·····
}
雖然派生類的析構函數(shù)與基類的析構函數(shù)名字不相同,但是如果將基類的析構函數(shù)定義為虛函數(shù),由該類所派生的所有派生類的析構函數(shù)也都自動成為虛函數(shù)。示例如下,
#include <iostream>
#include <string>
using namespace std;
class Base{
public:
virtual ~Base() {
cout << '調(diào)用基類Base的析構函數(shù)...' << endl;
}
};
class Derived: public Base{
public:
~Derived() {
cout << '調(diào)用派生類Derived的析構函數(shù)...' << endl;
}
};
int main() {
Base *p;
p = new Derived;
delete p;
return 0;
}
輸出如下:
調(diào)用派生類Derived的析構函數(shù)...
調(diào)用基類Base的析構函數(shù)...
~
6.4 純虛函數(shù)
純虛函數(shù)是在聲明虛函數(shù)時被“初始化為0的函數(shù)”,聲明純虛函數(shù)的一般形式如下:
virtual 函數(shù)類型 函數(shù)名(參數(shù)表) = 0;
聲明為純虛函數(shù)后,基類中就不再給出程序的實現(xiàn)部分。純虛函數(shù)的作用是在基類中為其派生類保留一個函數(shù)的名字,以便派生類根據(jù)需要重新定義。
~
6.5 抽象類
如果一個類至少有一個純虛函數(shù),那么就稱該類為抽象類,對于抽象類的使用有以下幾點規(guī)定:
- 由于抽象類中至少包含一個沒有定義功能的純虛函數(shù)。因此,抽象類只能作為其他類的基類來使用,不能建立抽象類對象。
- 不允許從具體類派生出抽象類。所謂具體類,就是不包含純虛函數(shù)的普通類。
- 抽象類不能用作函數(shù)的參數(shù)類型、函數(shù)的返回類型或是顯式轉(zhuǎn)換的類型。
- 可以聲明指向抽象類的指針或引用,此指針可以指向它的派生類,進而實現(xiàn)多態(tài)性。
- 如果派生類中沒有定義純虛函數(shù)的實現(xiàn),而派生類中只是繼承基類的純虛函數(shù),則這個派生類仍然是一個抽象類。如果派生類中給出了基類純虛函數(shù)的實現(xiàn),則該派生類就不再是抽象類了,它是一個可以建立對象的具體類了。
~
6.6 示例:利用多態(tài)計算面積
應用C++的多態(tài)性,計算三角形、矩形和圓的面積。
#include <iostream>
using namespace std;
/*** 定義一個公共基類 ***/
class Figure{
protected:
double x, y;
public:
Figure(double a, double b): x(a), y(b) { }
virtual void getArea() //虛函數(shù)
{
cout << 'No area computation defind for this class.\n';
}
};
class Triangle: public Figure{
public:
Triangle(double a, double b): Figure(a, b){ }
//虛函數(shù)重定義,用于求三角形的面積
void getArea(){
cout << 'Triangle with height ' << x << ' and base ' << y;
cout << ' has an area of ' << x * y * 0.5 << endl;
}
};
class Square: public Figure{
public:
Square(double a, double b): Figure(a, b){ }
//虛函數(shù)重定義,用于求矩形的面積
void getArea(){
cout << 'Square with dimension ' << x << ' and ' << y;
cout << ' has an area of ' << x * y << endl;
}
};
class Circle: public Figure{
public:
Circle(double a): Figure(a, a){ }
//虛函數(shù)重定義,用于求圓的面積
void getArea(){
cout << 'Circle with radius ' << x ;
cout << ' has an area of ' << x * x * 3.14 << endl;
}
};
int main(){
Figure *p;
Triangle t(10.0, 6.0);
Square s(10.0, 6.0);
Circle c(10.0);
p = &t;
p->getArea();
p = &s;
p->getArea();
p = &c;
p->getArea();
return 0;
}
程序輸出如下:
Triangle with height 10 and base 6 has an area of 30
Square with dimension 10 and 6 has an area of 60
Circle with radius 10 has an area of 314
七、運算符重載
運算符重載是面向?qū)ο蟪绦蛟O計的重要特征。
~
7.1 運算符重載概述
運算符重載是對已有的運算符賦予多重含義,使同一個運算符作用于不同類型的數(shù)據(jù)導致不同的行為。
下面的案例實現(xiàn)+號 運算符重載:
#include <iostream>
using namespace std;
class Complex{
private:
double real, imag;
public:
Complex(double r = 0.0, double i = 0.0): real(r), imag(i) { }
friend Complex operator+(Complex& a, Complex& b) {
Complex temp;
temp.real = a.real + b.real;
temp.imag = a.imag + b.imag;
return temp;
}
void display() {
cout << real;
if (imag > 0) cout << '+';
if (imag != 0) cout << imag << 'i' << endl;
}
};
int main()
{
Complex a(2.3, 4.6), b(3.6, 2.8), c;
a.display();
b.display();
c = a + b;
c.display();
c = operator+(a, b);
c.display();
return 0;
}
程序輸出結果如下:
2.3+4.6i
3.6+2.8i
5.9+7.4i
5.9+7.4i
~
這一章偷個懶??
八、函數(shù)模板與類模板
利用模板機制可以顯著減少冗余信息,能大幅度地節(jié)約程序代碼,進一步提高面向?qū)ο蟪绦虻目芍赜眯院涂删S護性。模板是實現(xiàn)代碼重用機制的一種工具,它可以實現(xiàn)類型參數(shù)化,即把類型定義為參數(shù),從而實現(xiàn)代碼的重用,使得一段程序可以用于處理多種不同類型的對象,大幅度地提高程序設計的效率。
~
8.1 模板的概念
在程序設計中往往存在這樣的現(xiàn)象:兩個或多個函數(shù)的函數(shù)體完全相同,差別僅在與它們的參數(shù)類型不同。
例如:
int Max(int x, int y) {
return x >= y ? x : y;
}
double Max(double x, double y) {
return x >= y ? x : y;
}
能否為上述這些函數(shù)只寫出一套代碼呢?解決這個問題的一種方式是使用宏定義
#define Max(x, y)((x >= y) ? x : y)
宏定義帶來的另一個問題是,可能在不該替換的地方進行了替換,而造成錯誤。事實上,由于宏定義會造成不少麻煩,所以在C++中不主張使用宏定義。解決以上問題的另一個方法就是使用模板。
~
8.2 函數(shù)模板
所謂函數(shù)模板,實際上是建立一個通用函數(shù),其函數(shù)返回類型和形參類型不具體指定,用一個虛擬的類型來代表,這個通用函數(shù)就稱為函數(shù)模板。在調(diào)用函數(shù)時,系統(tǒng)會根據(jù)實參的類型(模板實參)來取代模板中的虛擬類型,從而實現(xiàn)不同函數(shù)的功能。
函數(shù)的聲明格式如下
template <typename 類型參數(shù)>
返回類型 函數(shù)名(模板形參表)
{
函數(shù)體
}
也可以定義為如下形式
template <class 類型參數(shù)>
返回類型 函數(shù)名(模板形參表)
{
函數(shù)體
}
實際上,template 是一個聲明模板的關鍵字,它表示聲明一個模板。類型參數(shù)(通常用C++標識符表示,如T、type等)實際上是一個虛擬的類型名,使用前并未指定它是哪一種具體的類型,但使用函數(shù)模板時,必須將類型實例化。類型參數(shù)前需加關鍵字typename 或class ,typename 和class 的作用相同,都是表示一個虛擬的類型名(即類型參數(shù))。
例1:一個與指針有關的函數(shù)模板
#include <iostream>
using namespace std;
template <typename T>
T Max(T *array, int size = 0) {
T max = array[0];
for (int i = 1 ; i < size; i++) {
if (array[i] > max) max = array[i];
}
return max;
}
int main() {
int array_int[] = {783, 78, 234, 34, 90, 1};
double array_double[] = {99.02, 21.9, 23.90, 12.89, 1.09, 34.9};
int imax = Max(array_int, 6);
double dmax = Max(array_double, 6);
cout << '整型數(shù)組的最大值是:' << imax << endl;
cout << '雙精度型數(shù)組的最大值是:' << dmax << endl;
return 0;
}
例2:函數(shù)模板的重載
#include <iostream>
using namespace std;
template <class Type>
Type Max(Type x, Type y) {
return x > y ? x : y;
}
template <class Type>
Type Max(Type x, Type y, Type z) {
Type t = x > y ? x : y;
t = t > z ? t : z;
return t;
}
int main() {
cout << '33,66中最大值為 ' << Max(33, 66) << endl;
cout << '33,66,44中最大值為 ' << Max(33, 66, 44) << endl;
return 0;
}
注意:
- 在函數(shù)模板中允許使用多個類型參數(shù)。但是,應當注意template定義部分的每個類型參數(shù)前必須有關鍵字typename或class。
- 在template語句與函數(shù)模板定義語句之間不允許插入別的語句。
- 同一般函數(shù)一樣,函數(shù)模板也可以重載。
- 函數(shù)模板與同名的非模板函數(shù)可以重載。在這種情況下,調(diào)用的順序是:首先尋找一個參數(shù)完全匹配的非模板函數(shù),如果找到了就調(diào)用它;若沒有找到,則尋找函數(shù)模板,將其實例化,產(chǎn)生一個匹配的模板參數(shù),若找到了,就調(diào)用它。
~
8.3 類模板
所謂類模板,實際上就是建立一個通用類,其數(shù)據(jù)成員、成員函數(shù)的返回類型和形參類型不具體指定,用一個虛擬的類型來代表。使用類模板定義對象時,系統(tǒng)會根據(jù)實參的類型來取代類模板中虛擬類型,從而實現(xiàn)不同類的功能。
template <typename T>
class Three{
private:
T x, y, z;
public:
Three(T a, T b, T c) {
x = a; y = b; z = c;
}
T sum() {
return x + y + z;
}
}
上面的例子中,成員函數(shù)(其中含有類型參數(shù))是定義在類體內(nèi)的。但是,類模板中的成員函數(shù)也可以在類模板體外定義。此時,若成員函數(shù)中有類型參數(shù)存在,則C++有一些特殊的規(guī)定:
- 需要在成員函數(shù)定義之前進行模板聲明;
- 在成員函數(shù)名前要加上“類名<類型參數(shù)>::”;
在類模板體外定義的成員函數(shù)的一般形式如下:
template <typename 類型參數(shù)>
函數(shù)類型 類名<類型參數(shù)>::成員函數(shù)名(形參表)
{
·····
}
例如,上例中成員函數(shù)sum()在類外定義時,應該寫成
template<typename T>
T Three<T>::sum() {
return x + y + z;
}
**例子:**棧類模板的使用
#include <iostream>
#include <string>
using namespace std;
const int size = 10;
template <class T>
class Stack{
private:
T stack[size];
int top;
public:
void init() {
top = 0;
}
void push(T t);
T pop();
};
template <class T>
void Stack<T>::push(T t) {
if (top == size) {
cout << 'Stack is full!' << endl;
return;
}
stack[top++] = t;
}
template <class T>
T Stack<T>::pop() {
if (top == 0) {
cout << 'Stack is empty!' <<endl;
return 0;
}
return stack[--top];
}
int main() {
Stack<string> st;
st.init();
st.push('aaa');
st.push('bbb');
cout << st.pop() << endl;
cout << st.pop() << endl;
return 0;
}
九、C++的輸入和輸出
~
9.1 C++為何建立自己的輸入/輸出系統(tǒng)
C++除了完全支持C語言的輸入輸出系統(tǒng)外,還定義了一套面向?qū)ο蟮妮斎?輸出系統(tǒng)。C++的輸入輸出系統(tǒng)比C語言更安全、可靠。
c++的輸入/輸出系統(tǒng)明顯地優(yōu)于C語言的輸入/輸出系統(tǒng)。首先,它是類型安全的、可以防止格式控制符與輸入輸出數(shù)據(jù)的類型不一致的錯誤。另外,C++可以通過重載運算符“>>”和'<<',使之能用于用戶自定義類型的輸入和輸出,并且向預定義類型一樣有效方便。C++的輸入/輸出的書寫形式也很簡單、清晰,這使程序代碼具有更好的可讀性。
~
9.2 C++的流庫及其基本結構
“流”指的是數(shù)據(jù)從一個源流到一個目的的抽象,它負責在數(shù)據(jù)的生產(chǎn)者(源)和數(shù)據(jù)的消費者(目的)之間建立聯(lián)系,并管理數(shù)據(jù)的流動。凡是數(shù)據(jù)從一個地方傳輸?shù)搅硪粋€地方的操作都是流的操作,從流中提取數(shù)據(jù)稱為輸入操作(通常又稱提取操作),向流中添加數(shù)據(jù)稱為輸出操作(通常又稱插入操作)。
C++的輸入/輸出是以字節(jié)流的形式實現(xiàn)的。在輸入操作中,字節(jié)流從輸入設備(如鍵盤、磁盤、網(wǎng)絡連接等)流向內(nèi)存;在輸出操作中,字節(jié)流從內(nèi)存流向輸出設備(如顯示器、打印機、網(wǎng)絡連接等)。字節(jié)流可以是ASCII碼、二進制形式的數(shù)據(jù)、圖形/圖像、音頻/視頻等信息。文件和字符串也可以看成有序的字節(jié)流,分別稱為文件流和字符串流。
~
用于輸入/輸出的頭文件
C++編譯系統(tǒng)提供了用于輸入/輸出的I/O類流庫。I/O流類庫提供了數(shù)百種輸入/輸出功能,I/O流類庫中各種類的聲明被放在相應的頭文件中,用戶在程序中用#include命令包含了有關的頭文件就相當于在本程序中聲明了所需要用到的類。常用的頭文件有:
iostream 包含了對輸入/輸出流進行操作所需的基本信息。使用cin 、cout 等流對象進行針對標準設備的I/O操作時,須包含此頭文件。fstream 用于用戶管理文件的I/O操作。使用文件流對象進行針對磁盤文件的操作,須包含此頭文件。strstream 用于字符串流的I/O操作。使用字符串流對象進行針對內(nèi)存字符串空間的I/O操作,須包含此頭文件。iomanip 用于輸入/輸出的格式控制。在使用setw 、fixed 等大多數(shù)操作符進行格式控制時,須包含此頭文件。
用于輸入/輸出的流類
I/O流類庫中包含了許多用于輸入/輸出操作的類。其中,類istream 支持流輸入操作,類ostream 支持流輸出操作,類iostream 同時支持流輸入和輸出操作。
下表列出了iostream 流類庫中常用的流類,以及指出了這些流類在哪個頭文件中聲明。
類名 | 類名 | 說明 | 頭文件 |
---|
抽象流基類 | ios | 流基類 | iostream | 輸入流類 | istream | 通用輸入流類和其他輸入流的基類 | iostream | 輸入流類 | ifstream | 輸入文件流類 | fstream | 輸入流類 | istrstream | 輸入字符串流類 | strstream | 輸出流類 | ostream | 通用輸出流類和其他輸出流的基類 | iostream | 輸出流類 | ofstream | 輸出文件流類 | fstream | 輸出流類 | ostrstream | 輸出字符串流類 | strstream | 輸入/輸出流類 | iostream | 通用輸入輸出流類和其他輸入/輸出流的基類 | iostream | 輸入/輸出流類 | fstream | 輸入/輸出文件流類 | fstream | 輸入/輸出流類 | strstream | 輸入/輸出字符串流類 | strstream |
~
9.3 預定義的流對象
用流定義的對象稱為流對象。與輸入設備(如鍵盤)相關聯(lián)的流對象稱為輸入流對象;與輸出設備(如屏幕)相聯(lián)系的流對象稱為輸出流對象。
C++中包含幾個預定義的流對象,它們是標準輸入流對象cin 、標準輸出流對象cout 、非緩沖型的標準出錯流對象cerr 和緩沖型的標準出錯流對象clog 。
~
9.4 輸入/輸出流的成員函數(shù)
使用istream 和類ostream 流對象的一些成員函數(shù),實現(xiàn)字符的輸出和輸入。
1、put()函數(shù)
cout.put(單字符/字符形變量/ASCII碼);
2、get()函數(shù)
get()函數(shù)在讀入數(shù)據(jù)時可包括空白符,而提取運算符“>>”在默認情況下拒絕接收空白字符。
cin.get(字符型變量)
3、getline()函數(shù)
cin.getline(字符數(shù)組, 字符個數(shù)n, 終止標志字符)
cin.getline(字符指針, 字符個數(shù)n, 終止標志字符)
4、ignore()函數(shù)
cin.ignore(n, 終止字符)
ignore()函數(shù)的功能是跳過輸入流中n個字符(默認個數(shù)為1),或在遇到指定的終止字符(默認終止字符是EOF)時提前結束。
~
9.5 預定義類型輸入/輸出的格式控制
在很多情況下,需要對預定義類型(如int、float、double型等)的數(shù)據(jù)的輸入/輸出格式進行控制。在C++中,仍然可以使用C中的printf() 和scanf() 函數(shù)進行格式化。除此之外,C++還提供了兩種進行格式控制的方法:一種是使用ios類中有關格式控制的流成員函數(shù)進行格式控制;另一種是使用稱為操作符的特殊類型的函數(shù)進行格式控制。
1、用流成員函數(shù)進行輸入/輸出格式控制
- 設置狀態(tài)標志的流成員函數(shù)
setf() - 清除狀態(tài)標志的流成員函數(shù)
unsetf() - 設置域?qū)挼牧鞒蓡T函數(shù)
width() - 設置實數(shù)的精度流成員函數(shù)
precision() - 填充字符的流成員函數(shù)
fill()
2、使用預定義的操作符進行輸入/輸出格式控制
3、使用用戶自定義的操作符進行輸入/輸出格式控制
若為輸出流定義操作符函數(shù),則定義形式如下:
ostream &操作符名(ostream &stream)
{
自定義代碼
return stream;
}
若為輸入流定義操作符函數(shù),則定義形式如下:
istream &操作符名(istream &stream)
{
自定義代碼
return stream;
}
例如,
#include <iostream>
#include <iomanip>
using namespace std;
ostream &output(ostream &stream)
{
stream.setf(ios::left);
stream << setw(10) << hex << setfill('-');
return stream;
}
int main() {
cout << 123 << endl;
cout << output << 123 << endl;
return 0;
}
輸出結果如下:
123
7b--------
~
9.6 文件的輸入/輸出
所謂文件,一般指存放在外部介質(zhì)上的數(shù)據(jù)的集合。
文件流是以外存文件為輸入/輸出對象的數(shù)據(jù)流。輸出文件流是從內(nèi)存流向外存文件的數(shù)據(jù),輸入文件流是從外存流向內(nèi)存的數(shù)據(jù)。
根據(jù)文件中數(shù)據(jù)的組織形式,文件可分為兩類:文本文件和二進制文件。
在C++中進行文件操作的一般步驟如下:
- 為要進行操作的文件定義一個流對象。
- 建立(或打開)文件。如果文件不存在,則建立該文件。如果磁盤上已存在該文件,則打開它。
- 進行讀寫操作。在建立(或打開)的文件基礎上執(zhí)行所要求的輸入/輸出操作。
- 關閉文件。當完成輸入/輸出操作時,應把已打開的文件關閉。
~
9.7 文件的打開與關閉
為了執(zhí)行文件的輸入/輸出,C++提供了3個文件流類。
類名 | 說明 | 功能 |
---|
istream | 輸入文件流類 | 用于文件的輸入 | ofstream | 輸出文件流類 | 用于文件的輸出 | fstream | 輸入/輸出文件流類 | 用于文件的輸入/輸出 |
這3個文件流類都定義在頭文件fstream 中。
要執(zhí)行文件的輸入/輸出,須完成以下幾件工作:
- 在程序中包含頭文件
fstream 。 - 建立流對象
- 使用成員函數(shù)
open() 打開文件。 - 進行讀寫操作。
- 使用
close() 函數(shù)將打開的文件關閉。
~
9.8 文本文件的讀/寫
**例子:**把字符串“I am a student.”寫入磁盤文件text.txt中。
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream fout('../test.txt', ios::out);
if (!fout) {
cout << 'Cannot open output file.' << endl;
exit(1);
}
fout << 'I am a student.';
fout.close();
return 0;
}
**例子:**把磁盤文件test1.dat中的內(nèi)容讀出并顯示在屏幕上。
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream fin('../test.txt', ios::in);
if (!fin) {
cout << 'Cannot open output file.' << endl;
exit(1);
}
char str[80];
fin.getline(str , 80);
cout << str <<endl;
fin.close();
return 0;
}
~
9.9 二進制文件的讀寫
用get()函數(shù)和put()函數(shù)讀/寫二進制文件
**例子:**將a~z的26個英文字母寫入文件,而后從該文件中讀出并顯示出來。
#include <iostream>
#include <fstream>
using namespace std;
int cput() {
ofstream outf('test.txt', ios::binary);
if (!outf) {
cout << 'Cannot open output file.\n';
exit(1);
}
char ch = 'a';
for (int i = 0; i < 26; i++) {
outf.put(ch);
ch++;
}
outf.close();
return 0;
}
int cget() {
fstream inf('test.txt', ios::binary);
if (!inf) {
cout << 'Cannot open input file.\n';
exit(1);
}
char ch;
while (inf.get(ch)) {
cout << ch;
}
inf.close();
return 0;
}
int main() {
cput();
cget(); //此處文件打不開,不知為什么。。。
return 0;
}
用read()函數(shù)和write()函數(shù)讀寫二進制文件
有時需要讀寫一組數(shù)據(jù)(如一個結構變量的值),為此C++提供了兩個函數(shù)read()和write(),用來讀寫一個數(shù)據(jù)塊,這兩個函數(shù)最常用的調(diào)用格式如下:
inf.read(char *buf, int len);
outf.write(const char *buf, int len);
**例子:**將兩門課程的課程名和成績以二進制形式存放在磁盤文件中。
#include <iostream>
#include <fstream>
using namespace std;
struct list{
char course[15];
int score;
};
int main() {
list ob[2] = {'Computer', 90, 'History', 99};
ofstream out('test.txt', ios::binary);
if (!out) {
cout << 'Cannot open output file.\n';
abort(); //退出程序,作用與exit相同。
}
for (int i = 0; i < 2; i++) {
out.write((char*) &ob[i], sizeof(ob[i]));
}
out.close();
return 0;
}
**例子:**將上述例子以二進制形式存放在磁盤文件中的數(shù)據(jù)讀入內(nèi)存。
#include <iostream>
#include <fstream>
using namespace std;
struct list{
char course[15];
int score;
};
int main() {
list ob[2];
ifstream in('test.txt', ios::binary);
if (!in) {
cout << 'Cannot open input file.\n';
abort();
}
for (int i = 0; i < 2; i++) {
in.read((char *) &ob[i], sizeof(ob[i]));
cout << ob[i].course << ' ' << ob[i].score << endl;
}
in.close();
return 0;
}
檢測文件結束
在文件結束的地方有一個標志位,即為EOF 。采用文件流方式讀取文件時,使用成員函數(shù)eof() 可以檢測到這個結束符。如果該函數(shù)的返回值非零,表示到達文件尾。返回值為零表示未達到文件尾。該函數(shù)的原型是:
int eof();
函數(shù)eof()的用法示例如下:
ifstream ifs;
···
if (!ifs.eof()) //尚未到達文件尾
···
還有一個檢測方法就是檢查該流對象是否為零,為零表示文件結束。
ifstream ifs;
···
if (!ifs)
···
如下例子:
while (cin.get(ch))
cut.put(ch);
這是一個很通用的方法,就是檢測文件流對象的某些成員函數(shù)的返回值是否為0,為0表示該流(亦即對應的文件)到達了末尾。
從鍵盤上輸入字符時,其結束符是Ctrl+Z ,也就是說,按下【Ctrl+Z】 組合鍵,eof() 函數(shù)返回的值為真。
~
十、異常處理和命名空間
10.1 異常處理
程序中常見的錯位分為兩大類:編譯時錯誤和運行時錯誤。編譯時的錯誤主要是語法錯誤,如關鍵字拼寫錯誤、語句末尾缺分號、括號不匹配等。運行時出現(xiàn)的錯誤統(tǒng)稱為異常,對異常的處理稱為異常處理。
C++處理異常的辦法:如果在執(zhí)行一個函數(shù)的過程中出現(xiàn)異常,可以不在本函數(shù)中立即處理,而是發(fā)出一個信息,傳給它的上一級(即調(diào)用函數(shù))來解決,如果上一級函數(shù)也不能處理,就再傳給其上一級,由其上一級處理。如此逐級上傳,如果到最高一級還無法處理,運行系統(tǒng)一般會自動調(diào)用系統(tǒng)函數(shù)terminate() ,由它調(diào)用abort 終止程序。
**例子:**輸入三角形的三條邊長,求三角形的面積。當輸入邊的長度小于0時,或者當三條邊都大于0時但不能構成三角形時,分別拋出異常,結束程序運行。
#include <iostream>
#include <cmath>
using namespace std;
double triangle(double a, double b, double c) {
double s = (a + b + c) / 2;
if (a + b <= c || a + c <= b || b + c <= a) {
throw 1.0; //語句throw拋出double異常
}
return sqrt(s * (s - a) * (s - b) * (s - c));
}
int main() {
double a, b, c;
try {
cout << '請輸入三角形的三個邊長(a, b, c): ' << endl;
cin >> a >> b >> c;
if (a < 0 || b < 0 || c < 0) {
throw 1; //語句throw拋出int異常
}
while (a > 0 && b > 0 && c > 0) {
cout << 'a = ' << a << ' b = ' << b << ' c = ' << c << endl;
cout << '三角形的面積 = ' << triangle(a, b, c) << endl;
cin >> a >> b >> c;
if (a <= 0 || b <= 0 || c <= 0) {
throw 1;
}
}
} catch (double) {
cout << '這三條邊不能構成三角形...' << endl;
} catch (int) {
cout << '邊長小于或等于0...' << endl;
}
return 0;
}
~
10.2 命名空間和頭文件命名規(guī)則
命名空間:一個由程序設計者命名的內(nèi)存區(qū)域。程序設計者可以根據(jù)需要指定一些有名字的命名空間,將各命名空間中聲明的標識符與該命名空間標識符建立關聯(lián),保證不同命名空間的同名標識符不發(fā)生沖突。
1.帶擴展名的頭文件的使用
在C語言程序中頭文件包括擴展名.h,使用規(guī)則如下面例子
#include <stdio.h>
2.不帶擴展名的頭文件的使用
C++標準要求系統(tǒng)提供的頭文件不包括擴展名.h,如string,string.h等。
#include <cstring>
十一、STL標準模板庫
標準模板庫(Standard Template Library )中包含了很多實用的組件,利用這些組件,程序員編程方便而高效。
11.1 Vector
vector容器與數(shù)組類似,包含一組地址連續(xù)的存儲單元。對vector容器可以進行很多操作,包括查詢、插入、刪除等常見操作。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> nums;
nums.insert(nums.begin(), 99);
nums.insert(nums.begin(), 34);
nums.insert(nums.end(), 1000);
nums.push_back(669);
cout << '\n當前nums中元素為: ' << endl;
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << ' ' ;
cout << nums.at(2);
nums.erase(nums.begin());
nums.pop_back();
cout << '\n當前nums中元素為: ' << endl;
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << ' ' ;
return 0;
}
~
11.2 list容器
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> number;
list<int>::iterator niter;
number.push_back(123);
number.push_back(234);
number.push_back(345);
cout << '鏈表內(nèi)容:' << endl;
for (niter = number.begin(); niter != number.end(); ++niter)
cout << *niter << endl;
number.reverse();
cout << '逆轉(zhuǎn)后的鏈表內(nèi)容:' << endl;
for (niter = number.begin(); niter != number.end(); ++niter)
cout << *niter << endl;
number.reverse();
return 0;
}
~
11.3 stack
**例子:**利用棧進行進制轉(zhuǎn)換
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
int num = 100;
cout << '100的八進制表示為:';
while (num) {
st.push(num % 8);
num /= 8;
}
int t;
while (!st.empty()) {
t = st.top();
cout << t;
st.pop();
}
cout << endl;
return 0;
}
~
11.4 queue
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> qu;
for (int i = 0; i < 10; i++)
qu.push(i * 3 + i);
while (!qu.empty()) {
cout << qu.front() << ' ';
qu.pop();
}
cout << endl;
return 0;
}
~
11.5 優(yōu)先隊列priority_queue
#include <iostream>
#include <queue>
#include <functional>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
priority_queue<int> pq;
srand((unsigned)time(0));
for (int i = 0; i < 6; i++) {
int t = rand();
cout << t << endl;
pq.push(t);
}
cout << '優(yōu)先隊列的值:' << endl;
for (int i = 0; i < 6; i++) {
cout << pq.top() << endl;
pq.pop();
}
return 0;
}
~
11.6 雙端隊列deque
push_back();
push_front();
insert();
pop_back();
pop_front();
erase();
begin();
end();
rbegin();
rend();
size();
maxsize();
~
11.7 set
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main() {
set<string> s;
s.insert('aaa');
s.insert('bbb');
s.insert('ccc');
if (s.count('aaa') != 0) {
cout << '存在元素aaa' << endl;
}
set<string>::iterator iter;
for (iter = s.begin(); iter != s.end(); ++iter)
cout << *iter << endl;
return 0;
}
~
11.8 map
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, int> m;
m['aaa'] = 111;
m['bbb'] = 222;
m['ccc'] = 333;
if (m.count('aaa')) {
cout << '鍵aaa對應的值為' << m.at('aaa') << endl;
cout << '鍵aaa對應的值為' << m['aaa'] << endl;
}
return 0;
}
完結撒花~~~??????????
博主就是在復習的過程中順便總結下知識點,以便以后查看學習。
注:本博客僅供學習和參考,內(nèi)容較多,難免會有錯誤,請大家多多包涵。如有侵權敬請告知。
參考資料《C++面向?qū)ο蟪绦蛟O計》陳維興、林小茶編著
|