C++文件
例:從文件income. in中讀入收入直到文件結(jié)束,
并將收入和稅金輸出到文件tax. out。
#include<iostream>
using namespace std;
const int cutoff = 6000;
const float rate1 = 0.3;
const float rate2 = 0.6;
int main()
{
ifstream infile;
ofstream outfile;
int income,tax;
infile.open("income.in")
outfile.open("tax.out")
while( infile>>income){
if( income<cutoff)
tax = rate1 * income;
else
tax = rate2 * income;
outfile<< "Income = "<< income
<< "greenbacks\n"
<< "Tax = " << tax
<< "greenbacks\n";
}
infile.close();
outfile.close();
return 0;
}
檢查文件是否成功打開
ifstream infile;
infile.open("scores.dat")
if (infile)
//...
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
int main()
{
ifstream infile;
ofstream outfile;
infile.open("in.txt");
outfile.open("out.txt");
int num1,num2,num3=0;
if(infile && outfile)
{
while(infile >> num1 >> num2 >> num3){
outfile << setw(2)<< num1<<" "<<num2<<" "<<num3<<" "<<num1+num2+num3<<endl;
}
}
infile.close();
outfile.close();
return 0;
}
常量
C++中的const變量能在任何常數(shù)可以出現(xiàn)的地方使用,例如數(shù)組的大小、case標(biāo)號中的表達(dá)式。
const int Size = 100;
float a[Size];
bool data type
C++新增bool類型,取值true 或false。用來表示真假。
所有的關(guān)系操作符、相等操作符和邏輯操作符現(xiàn)在都
產(chǎn)生bool類型的結(jié)果值,而不是int型。
在需要bool類型的地方,整數(shù)和指針表達(dá)式仍然是允
許的
默認(rèn)情況下,bool表達(dá)式輸出時真值輸出1,假值輸出0.
操作符boolalpha可用來將bool表達(dá)式輸出或輸入為false 或true的形式。
操作符noboolalpha可用來將bool表達(dá)式輸出或輸入0或1的形式。
bool flag;
flag = (3<5);
cout<<flag<<'\n';
cout<<boolalpha<<flag<<'\n';
1
true
Structure
C++中的結(jié)構(gòu)體和C語言結(jié)構(gòu)體不同。定義結(jié)構(gòu)體變量時可以不加struct關(guān)鍵字
struct Point{
double x,y;
};
Point p1,p2;
C++中的結(jié)構(gòu)體除了包含數(shù)據(jù)成員,還可以包含函數(shù)。
struct Point{
double x,y;
void setVal(double,double);
};
p.x = 3.14159;
p.y = 0.0;
p.setVal(4.11,-13.090);
在C++中,類和結(jié)構(gòu)的唯一區(qū)別是缺省情況下,結(jié)構(gòu)中的所有東西都是Public而類中的所有東西都是Private的.
string 類型
C++提供string類型來替代C語言中以null為結(jié)尾的char數(shù)組。
使用string類型必須包含頭文件string
有了string類型,程序員不再需要關(guān)心存儲的分配,也無需處理復(fù)雜的null結(jié)束字符,這些操作將由系統(tǒng)自動處理。
實例:
#include<string>
using namespace std;
string s1;
string s2="Bravo";
string s3=s2;
string s4(10,'x');
變量s1,已經(jīng)定義但沒有進行初始化, 默認(rèn)值為空串
變量s2的初始值是C風(fēng)格的字符串“Bravo”
變量s3用s2初始化,因此s2和s3都代表字符串Bravo
變量s4的初始化為10個x。
轉(zhuǎn)換為C風(fēng)格的字符串:利用函數(shù)c_str返回一個指向char類型的數(shù)組的指針
實例:
存放輸入文件名的變量filename的數(shù)據(jù)類型是string
調(diào)用ifstream的open函數(shù)時,需要一個C風(fēng)格的字符串
string filename = "infile.dat";
ifstream infile;
infile.open( filename.c_str() );
求字符串長度,使用函數(shù)length
string s = "Ed Wood";
cout << "Length = " << s.length() <<'\n';
輸出為Length = 7.
string的輸入輸出
<<用來輸出string類型的字符串
string s1;
string s2 = "Bravo";
string s3 = s2;
string s4(10, 'x');
cout<<s1<<'\n'
<<s2<<'\n'
<<s3<<'\n'
<<s4<<'\n';
輸出為
Bravo
Bravo
xxxxxxxxxx
用來輸入string類型的字符串,其默認(rèn)的動作是忽略空格,然后讀取存儲字符直到文件結(jié)束或遇到另外一個空格。任何空格都不存儲。
string s;
cout << "Enter a string:";
cin >>s;
輸入
Ed Wood
則s的內(nèi)容為Ed。
注意:在定義后,s實際上長度為0。在讀入字符串Ed后,它的長度為2。系統(tǒng)自動提供了充足的存儲空間來存儲這個長度為2的字符串。
函數(shù)getline:用來讀入一整行到string類型的變量中去。第一個參數(shù)是輸入流,第二個參數(shù)是string類型的變量。
該函數(shù)從輸入流中讀入字符,然后將它們存儲到string變量中,直到出現(xiàn)以下情況為止:
讀入了文件結(jié)束標(biāo)志。
都到了一個新行,該新行將從流中移除,但沒有存儲到變量中。
到達(dá)字符串的最大長度允許值。
如果getline沒有讀入字符,它將返回false,該條件可用于判斷文件是否結(jié)束以終止應(yīng)用程序
實例:
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
int main()
{
string buff;
ifstream infile;
ofstream outfile;
cout<<"Input file name:";
cin>>buff;
infile.open(buff.c_str());
cout<<"Output file name:";
cin>>buff;
outfile.open(buff.c_str());
while(getline(infile,buff))
outfile<<buff<<"\n\n";
infile.close();
outfile.close();
return 0;
}
輸出信息的行距是輸入信息行距的兩倍。
賦值:操作符=可用來進行string類型字符串的賦值
操作符左邊必須是一個string類型的字符串,右邊可以是一個string字符串,也可以是C風(fēng)格的字符串或僅僅是一個char字符。
字符串的連接:操作符+和+=可用來進行字符串的連接。
操作符+
string + string
string + "xxxxx" 或 "xxxxx" + string
string + 'x' 或 'x' + string
而+=,則左邊必須是string字符串,右邊可以是一個
string字符串、C風(fēng)格的字符串或一個char字符。
–string += string
–string += “xxxx”
–string += ‘x’
函數(shù)
應(yīng)用:通過&來標(biāo)記,用來為存儲器提供別名
int x;
int &ref = x;
//分配了一個int單元,它擁有兩個名字:x和ref
x=3;或ref=3;都將3存到int單元
C++默認(rèn)的調(diào)用方式和C語言一樣,都是傳值調(diào)用。如果用&指定一個函數(shù)參數(shù)為引用參數(shù),則為引用調(diào)用,引用參數(shù)將實際的實參傳給函數(shù),而不是實參的一個拷貝。
#include<iostream>
using namespace std;
void swap(int &,int &);
int main()
{
int i=7,j=-3;
swap(i,j);
cout<<"i="<<i<<'\n'
<<"j="<<j<<'\n';
return 0;
}
void swap(int &a,int &b)
{
int t;
t = a;
a = b;
b = t;
}
函數(shù)原型 void swap(int &,int &)指定swap的參數(shù)是通過引用傳遞的。
在swap被調(diào)用后,swap函數(shù)體中的a和b直接對應(yīng)main函數(shù)中的i和j的存儲空間。函數(shù)swap并不是對i,j的拷貝進行操作,而是直接操作i和j本身。
函數(shù)重載:函數(shù)名相同,參數(shù)個數(shù)或參數(shù)類型不一樣。
重載函數(shù)通常用來對具有相似行為而數(shù)據(jù)類型不同的操作提供一個通用的名稱。編譯器通過將實參類型與同名函數(shù)的參數(shù)表進行匹配,以決定應(yīng)該調(diào)用哪個函數(shù)。
#include<iostream>
#include<iomanip>
using namespace std;
void print(int a);
void print(double a);
int main()
{
int x = 8;
double y = 8;
print(x);
print(y);
return 0;
}
void print(int a){
cout<<a<<'\n';
}
void print(double a)
{
cout<<showpoint<<a<<'\n';
}
函數(shù)簽名:C++要求重載的函數(shù)具有不同的簽名。
函數(shù)簽名包括:函數(shù)名。參數(shù)的個數(shù)、數(shù)據(jù)的類型和順序。
為保證函數(shù)的唯一性,函數(shù)必須擁有獨一無二的簽名。
返回值類型不是函數(shù)簽名的一部分,所以函數(shù)不能通過返回值類型加以區(qū)分。
new和delete操作符
new new[] delete 和 delete[]操作符用于動態(tài)分配和釋放存儲空間(例如程序運行的時候)
操作符new分配一個空間;
new[]分配一個數(shù)組;
delete釋放由new分配的單一空間;
delete[]釋放由new[]分配的數(shù)組;
與C語言中的函數(shù)malloc calloc 和free不同
new、new[] delete delete[]是內(nèi)建的操作符,而malloc calloc free是庫函數(shù)。new和delete是關(guān)鍵字
new操作符根據(jù)請求分配的類型推斷返回類型和需要分配的字節(jié)數(shù)。
給定聲明
int *int_ptr;
通常使用如下方式為int_ptr分配存儲空間;
int_ptr = new int;
如果分配成功,則int_ptr指向所分配的存儲單元。
delete操作符用于釋放由new分配的存儲空間。如果int_ptr指向一個由new分配的單一int單元,則可以這樣釋放它
delete int_ptr;
new[]操作符用于動態(tài)分配一個數(shù)組
int *int_ptr;
int_ptr = new int[100];//請求分配100個int類型單元
如果分配成功,則int_ptr指向第一個int單元的地址
delete[]操作符用于釋放由new[]分配的存儲空間。如果int_ptr指向一個由new[]分配的int數(shù)組單元,則我們可以這樣釋放它:
delete [] int_ptr;
類
C++中,一個類就是一種數(shù)據(jù)類型。
標(biāo)準(zhǔn)C++定義了一些內(nèi)建類,例如string
通過創(chuàng)建自己的類,程序員可以對C++語言進行擴展。
通過類聲明可以創(chuàng)建一個類,而且可將這個類當(dāng)作數(shù)據(jù)類型來使用。
類和對象
類聲明:描述了封裝在該類中的數(shù)據(jù)成員和成員函數(shù)。
class Human{
///...data members and methods go here
};
class是個關(guān)鍵字,Human稱為類標(biāo)簽
通過類聲明創(chuàng)建一個數(shù)據(jù)類型,類標(biāo)簽是該數(shù)據(jù)類型的標(biāo)識符或名字。
類聲明中花括號后的分號不可少。
對象定義:從面向?qū)ο蟪绦蛟O(shè)計角度看,C++中以一個類作為數(shù)據(jù)類型定義的變量就是對象。
Human maryLeakey;// 如下語句定義了Human的一個對象maryLeakey
對象數(shù)組 Human latvians[365000];
C++的信息隱藏機制
三個關(guān)鍵字:
private:可用來隱藏類的數(shù)據(jù)成員和成員函數(shù)
public:用來暴露類的數(shù)據(jù)成員和成員函數(shù)
protected
面向?qū)ο笤O(shè)計的靈魂就是使用private隱藏類的實現(xiàn),使用public暴露類的接口。
定義一個Person類
接口:包含兩個公有成員函數(shù)setAge和getAge
實現(xiàn):一個unsigned 類型的數(shù)據(jù)成員age
class Person{
public:
void setAge(unsigned n);
unsigned getAge() const;
private:
unsigned age;
};
private成員和public成員可以在類聲明中交叉出現(xiàn)。
Person類的客戶(指Person類的對象的使用者)可通過調(diào)用公有成員函數(shù)setAge和getAge來請求Person類提供服務(wù)
Person類的客戶不能訪問屬于類實現(xiàn)部分的私有數(shù)據(jù)成員age
成員選擇符
class Person{
public:
void setAge(unsigned n);
unsigned getAge() const;
private:
unsigned age;
};
int main()
{
Person boxer;
boxer.setAge(27);
//...remainder of main's body
對象的使用者只能訪問類的公有成員(數(shù)據(jù)成員或成員函數(shù))
類范圍
類的私有成員僅能由類的成員函數(shù)訪問,即具有類范圍性質(zhì)。
類的公有成員擁有公有范圍性質(zhì),可以在類之外進行訪問。
在C++中,用關(guān)鍵字class聲明的類,其類成員在默認(rèn)情況下作為私有成員處理,具有類范圍性質(zhì)。
關(guān)鍵字class和struct的區(qū)別
使用class關(guān)鍵字或struct關(guān)鍵字都可以創(chuàng)建類
如果使用class關(guān)鍵字,類成員在默認(rèn)狀態(tài)下是私有的。
而是用struct關(guān)鍵字,類成員在默認(rèn)狀態(tài)下是公有的。
類成員函數(shù)定義
在類聲明之外定義、在類聲明之中進行定義(inline)
class Person{
public:
void setAge(unsigned n);
unsigned getAge() const;
private:
unsigned age;
};
//define Person's setAge
void Person::setAge(unsigned n){
age = n;
}
//define Person's getAge
unsigned Person::getAge() const{
return age;
}
在類聲明之外進行定義,為避免重名,在定義成員函數(shù)時使用了域解析符::
在類聲明之中進行定義(inline)
class Person{
public:
void setAge(unsigned n){ age = n;}
unsigned getAge() const{return age;}
private:
unsigned age;
};
通過在進行成員函數(shù)聲明的時候使用inline關(guān)鍵字,可將原本定義在類聲明之外的成員函數(shù)強制變成內(nèi)聯(lián)函數(shù)。
class Person{
public:
inline void setAge(unsigned n);
inline unsigned getAge() const;
private:
unsigned age;
};
//define Person's setAge
void Person::setAge(unsigned n){
age = n;
}
//define Person's getAge
unsigned Person::getAge() const{
return age;
}
在程序中使用類
關(guān)鍵步驟:類聲明,對象定義,客戶服務(wù)請求
#include<iostream>
using namespace std;
class Person{
public:
void setAge(unsigned n){age = n;}
unsigned getAge() const{ return age;}
private:
unsigned age;
};
int main()
{
Person p; //create a single person
Person stooges[3]; //create an array of Persons
p.setAge(12);
//set the stooges' age
stooges[0].setAge(45);
stooges[1].setAge(46);
stooges[2].setAge(44);
//print four ages
cout<<p.getAge()<<'\n';
for(int i=0;i<3;i++)
cout << stooges[i].getAge() << '\n';
return 0;
}
在程序中使用類
通常將類聲明放到.h中,這樣在使用時通過#include將類聲明包含進來。
如可將Person類的聲明放到person.h文件中
通常將成員函數(shù)的定義放到.cpp中
一般不要將成員函數(shù)定義放在.h中,因為頭文件通過#include被多個不同的文件所包含的話可能出現(xiàn)函數(shù)重復(fù)定義錯
實例程序:堆棧類
問題:創(chuàng)建一個支持int型的壓入和彈出操作的堆棧類。
公有成員:
對stack對象進行初始化。
檢查stack為空,或已滿。
將整數(shù)壓入到stack中。
從stack里彈出整數(shù)。
不移出任何元素,將stack的內(nèi)容輸出到標(biāo)準(zhǔn)輸出。
私有成員:
一個用于打印錯誤信息的私有成員函數(shù)。
三個私有數(shù)據(jù)成員(top、數(shù)據(jù)數(shù)組、dummy_val)
#include<iostream>
using namespace std;
class Stack{
public:
enum{ MaxStack = 5};
void init(){ top = -1;}
void push(int n) {
if(isFull()){
errMsg("Full stack. Can't push.");
return;
}
arr[++top] = n;
}
int pop() {
if( isEmpty()){
errMsg("Empty stack. Popping dummy value.");
return dummy_val;
}
return arr[top--];
}
bool isEmpty(){ return top<0;}
bool isFull() { return top>=MaxStack -1;}
void dump() {
cout<<"Stack contents, top to bottom:\n";
for(int i=top;i>=0;i--)
cout<<'\t'<<arr[i]<<'\n';
}
private:
void errMsg(const char* msg) const{
cerr<< "\n*** Stack operation failure:"<<msg<<'\n';
}
int top;
int arr[MaxStack];
int dummy_val;
};
int main()
{
Stack s1;
s1.init();
s1.push(9);
s1.push(4);
s1.dump(); // 4 9
cout << "Popping"<<s1.pop()<<'\n';
s1.dump(); //9
s1.push(8);
s1.dump(); // 8 9
s1.pop();s1.pop();
s1.dump();//empty
s1.pop();//still empty
s1.dump(); //ditto
s1.push(3);
s1.push(5);
s1.dump();//5 3
//push two too manny to test
for(unsigned i = 0;i<Stack::MaxStack;i++)
s1.push(1);
s1.dump(); //1 1 1 5 3
return 0;
}
效率和健壯性
- 通過引用來傳遞和返回對象
- const類型參數(shù)的對象引用
- const成員函數(shù)
- 對成員函數(shù)進行重載以便處理兩種類型的字符串
通過引用來傳遞和返回對象
對象可以采用傳值方式或引用方式進行對象的傳遞和返回。一般來說應(yīng)該采用引用方式進行對象的傳遞和返回,而不要采用傳值的方式來進行。因為通過傳值方式來傳遞和返回對象時會降低效率并將面臨對象間的拷貝操作,從而使數(shù)據(jù)增大,浪費內(nèi)存。
從效率上看,傳遞一個指向?qū)ο蟮闹羔樋墒盏脚c引用方式相同的效果,但引用方式的語法要簡練得多。
#include<iostream>
using namespace std;
class C{
public:
void set(int n){num = n;}
int get() const{return num;}
private:
int num;
};
void f(C&);
C& g();
int main()
{
C c1,c2;
f(c1); // pass by reference
c2 = g(); // return by reference
cout<< c2.get() <<'\n';
return 0;
}
void f(C& c){
c.set(-999);
cout<<c.get()<<'\n';
}
C& g(){
static C c3;// NB:static, not auto
c3.set(123);
return c3;
}
output:
-999
123
const類型參數(shù)的對象引用
通常,如果一個對象通過引用方式傳到函數(shù)f中,而函數(shù)f又不會通過修改對象的數(shù)據(jù)成員的值改變該對象的狀態(tài),那么,最好將f的參數(shù)標(biāo)記為const,可以預(yù)防對參數(shù)的誤寫,同時有些編譯器還可對這種情況進行一些優(yōu)化。
如下例:將函數(shù)setName的string類型參數(shù)n標(biāo)記為const,表明setName不會改變n,只是將n賦值給數(shù)據(jù)成員name。
class C{
public:
void setName(const string& n) {name = n;}
// ... other public members
private:
string name;
};
const成員函數(shù)
如果一個成員函數(shù)不需要直接或間接(通過調(diào)用其它的成員函數(shù)來改變其對象狀態(tài))地改變該函數(shù)所屬對象的任何數(shù)據(jù)成員,那么最好將這個成員函數(shù)標(biāo)記為const。
如下例,由于get成員函數(shù)不需要改變類C的任何數(shù)據(jù)成員,因此將get成員函數(shù)標(biāo)記為const。
class C{
public:
void set(int n) {num = n;}
int get() const {return num;}
private:
int num;
};
const 成員函數(shù)
定義一個const成員函數(shù)時,const關(guān)鍵字出現(xiàn)在參數(shù)列表與其函數(shù)體之間。
由于get成員函數(shù)不更改任何數(shù)據(jù)成員,因此這種類型的函數(shù)被稱為只讀函數(shù)。將成員函數(shù)標(biāo)記為const可以預(yù)防對該函數(shù)所屬對象的數(shù)據(jù)成員的誤寫,同時有些編譯器還可對這種情況進行一些優(yōu)化。
一個const成員函數(shù)僅能調(diào)用其它const成員函數(shù),因為const成員函數(shù)不允許直接或間接地改變對象的狀態(tài),而調(diào)用非const成員函數(shù)可能會間接改變對象的狀態(tài)
class C{
public:
void m1(int x) const{
m2(x); //*** error: m2 not const
}
void m2(int x) { dm = x;}
private:
int dm;
};
const成員函數(shù)
const關(guān)鍵字三種不同用法示例:
- 在成員函數(shù)set中,因為set不該變string類型參數(shù)n,n被標(biāo)為const。
- 成員函數(shù)get返回數(shù)據(jù)成員name的一個const型引用,此處的const表明誰也不能通過這個引用來修改數(shù)據(jù)成員name的值。
- 成員函數(shù)get本身被標(biāo)記為const,因為get不會改變類C唯一的數(shù)據(jù)成員name的值。
class C{
public:
void set( const string& n) {name = n;}
const string& get() const{ return name;}
private:
string name;
};
const返回
- 某函數(shù)如果采用const返回,則其返回值只能賦給一個const類型的局部變量。
- 如果該const返回值是一個類的指針或者引用的話,則不能用該指針或引用調(diào)用該類的non-const成員函數(shù),因為這些函數(shù)可能會改變該類的數(shù)據(jù)成員的值。
class Foo{
public:
/*
* Modifies m_widget and the user may modify the reurned widget.
*/
Widget *widget();
/*
* Does not modify m_widget but the user may modify the returned widget.
*/
Widget *widget() const;
/*
* Modifies m_widget, but the user may not modify the returned widget.
*/
const Widget *cWidget();
/*
* Does not modify m_widget and the user may not modify the returned widget.
*/
const Widget *cWidget() const;
private:
Widget *m_widget;
};
int main()
{
Foo f;
Widget *w1 = f.widget(); //fine
Widget *w2 = f.cWidget(); //error -"cWidget()"
// returns a const value
// and "w2" is not const
const Widget *w3 = f.cWidget(); //fine
return 0;
}
對成員函數(shù)進行重載以便處理兩種類型的字符串
class C{
public:
void set( const string & n) {name = n;}
void set( const char* n) { name = n;}
const string& get() const { return name;}
private:
string name;
};
C c1;
string s1("Who's Afraid of Virginia Woolf?");
c1.set(s1); // string argument
C c2;
c2.set( "What, me worry?"); //const char*
構(gòu)造函數(shù)和析構(gòu)函數(shù)
有些函數(shù)比較特殊,在調(diào)用它們時不需要顯式地提供函數(shù)名,編譯器會自動地調(diào)用它們。
類構(gòu)造函數(shù)(class constructor, 可以有多個)和類析構(gòu)函數(shù)(class destructor,最多一個)就是這種類型的函數(shù),通常編譯器會自動調(diào)用這兩個函數(shù)而不需要我們顯式地發(fā)出調(diào)用動作。
構(gòu)造函數(shù):是一種與類名相同的成員函數(shù)。當(dāng)創(chuàng)建類的一個實例時(例如,定義一個類的變量時),編譯器會自動地調(diào)用某個合適的構(gòu)造函數(shù)。下面的例子中,三個成員函數(shù)是構(gòu)造函數(shù),都有著與類相同名稱的Person,而且沒有返回值類型。
class Person{
public:
Person();// constructor
Person( const string & n); // constructor
Person( const char* n); //constructor
void setName( const string& n);
void setName( const char* n);
const string& getName() const;
private:
string name;
};
構(gòu)造函數(shù)不能有返回類型,因此void Person();是錯誤的。
一個類可以擁有多個構(gòu)造函數(shù),可以對構(gòu)造函數(shù)進行重載。但每個構(gòu)造函數(shù)必須擁有不同的函數(shù)簽名。
上個例子中,三個構(gòu)造函數(shù)具有不同的函數(shù)簽名。
第一個沒有參數(shù)(默認(rèn)構(gòu)造函數(shù)),第二個參數(shù)類型是const string引用(帶參數(shù)溝槽函數(shù)),第三個的參數(shù)類型是C風(fēng)格字符串const char*(帶參數(shù)構(gòu)造函數(shù))。
構(gòu)造函數(shù)的使用:
#include "Person.h" //class declaration
int main()
{
Person anonymous; //default constructor
Person jc("J.Coltrane"); // parameterized constructor
//...
}
當(dāng)創(chuàng)建一個對象時,構(gòu)造函數(shù)會被編譯器自動調(diào)用。程序員不需要調(diào)用構(gòu)造函數(shù)。構(gòu)造函數(shù)主要用來對數(shù)據(jù)成員進行初始化,并負(fù)責(zé)其他一些在對象創(chuàng)建時需要處理的事務(wù)。構(gòu)造函數(shù)對提高類的健壯性有重要的作用。
之前的stack類沒有構(gòu)造函數(shù),為保證一個stack正確運行,top成員必須初始化為-1.雖然stack提供了init成員函數(shù)來完成這個初始化任務(wù),但程序員可能會在創(chuàng)建一個stack對象之后忘了調(diào)用init成員函數(shù)而出錯。可以通過為stack類增加一個默認(rèn)構(gòu)造函數(shù),這樣當(dāng)定義一個stack對象時,編譯器自動調(diào)用其默認(rèn)構(gòu)造函數(shù),默認(rèn)構(gòu)造函數(shù)再調(diào)用init成員函數(shù):
class Stack{
Stack() {init();} //ensures initialization
//...
};
構(gòu)造函數(shù)最大的特點是:函數(shù)名與類名相同,沒有返回類型。
除此之外,構(gòu)造函數(shù)的行為與其他函數(shù)相同,也可完成如賦值、條件測試、循環(huán)、函數(shù)調(diào)用等功能。
構(gòu)造函數(shù)既可以在類聲明之中定義,也可在類聲明之外定義。
下例子中,將默認(rèn)構(gòu)造函數(shù)定義為inline類型,將帶參數(shù)構(gòu)造函數(shù)定義放到類聲明之外。
class Person{
public:
Person() {name = "Unknown";}
Person( const string& n);
Person( const char* n);
void setName( const string& n);
void setName( const char* n);
const string& getName() const;
private:
string name;
};
Person::Person( const string& n){
name = n;
}
Person::Person( const char* n){
name = n;
}
對象數(shù)組與默認(rèn)構(gòu)造函數(shù):
如果C是一個類,可以定義任意維數(shù)的C對象數(shù)組;
如果C擁有默認(rèn)構(gòu)造函數(shù),數(shù)組中每個C對象都會調(diào)用默認(rèn)構(gòu)造函數(shù)。
#include<iostream>
using namespace std;
unsigned count = 0;
class C{
public:
C() { cout<<"Creating C"<< ++ count<<'\n';}
};
C ar[1000];
本例輸出為
Creating C1
Creating C2
...
Creating C999
Creating C1000
通過構(gòu)造函數(shù)約束對象的創(chuàng)建
C++程序員常常會將部分構(gòu)造函數(shù)設(shè)計為私有成員,將另一部分設(shè)計為公有成員,以確保在創(chuàng)建對象時進行正確的初始化。
一個私有構(gòu)造函數(shù)與普通的私有成員函數(shù)一樣,擁有類范圍屬性,因而不能再類之外進行調(diào)用。
提供私有的默認(rèn)構(gòu)造函數(shù)
class Emp{
public:
Emp( unsigned ID) {id = ID;}
unsigned id; //unique id number
private:
Emp();//*** declared private for emphasis
//...
};
int main(){
Emp elvis;//***** ERROR: Emp() is private
Emp cher(111222333);// OK, Emp(unsigned) is public
//...
}
不提供默認(rèn)構(gòu)造函數(shù):
class Emp{
public:
Emp( unsigned ID) {id = ID;}
unsigned id; //unique id number
private:
//...
};
Emp elvis; //*** ERROR: no public default constructor
當(dāng)編譯器在類聲明中找不到任何構(gòu)造函數(shù)時,才會生成一個公有的默認(rèn)構(gòu)造函數(shù)。如果一個類已經(jīng)顯式地聲明了任何構(gòu)造函數(shù),編譯器不生成公有的默認(rèn)構(gòu)造函數(shù)。
拷貝構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù) 構(gòu)造函數(shù)分為兩組:1、默認(rèn)構(gòu)造函數(shù),不帶參數(shù) 2、帶參數(shù)構(gòu)造函數(shù),需要參數(shù)。
在帶參數(shù)構(gòu)造函數(shù)中,有兩類很重要的構(gòu)造函數(shù):
拷貝構(gòu)造函數(shù):創(chuàng)建一個新的對象,此對象是另外一個對象的拷貝品。
轉(zhuǎn)型構(gòu)造函數(shù):用于類型間的轉(zhuǎn)換,只有一個參數(shù)。
拷貝構(gòu)造函數(shù)的原型
必須是引用: Person( const Person&);
Person( Person&);
下面的原型是錯誤的 Person( Person);
拷貝構(gòu)造函數(shù)可以有多于一個的參數(shù),但是第一個以后的所有參數(shù)都必須有默認(rèn)值。例如:
Person( const Person& p, bool married = false);
如果類的設(shè)計者不提供拷貝構(gòu)造函數(shù),編譯器會自動生成一個。它完成如下操作:將源對象所有的數(shù)據(jù)成員的值注意賦值給目標(biāo)對象相應(yīng)的數(shù)據(jù)成員。
例如:將定類Person沒有定義拷貝構(gòu)造函數(shù),盡管對象orig和clone擁有不同的存儲空間,但相應(yīng)的數(shù)據(jù)成員具有相同的值。
Person orig("Dawn Upshaw");
Person clone(orig);
什么時候應(yīng)該為一個類設(shè)計一個拷貝構(gòu)造函數(shù)呢?
答:如果一個類包含指向動態(tài)存儲空間指針類型的數(shù)據(jù)成員,則就應(yīng)為這個類設(shè)計拷貝構(gòu)造函數(shù)。
class Namelist {
public:
Namelist() {size = 0;p=0}
Namelist( const string [], int);
void set( const string&, int);
void set( const char*, int);
void dump() const;
private:
int size;
string* p;
};
Namelist:: Namelist( const string s[], int si) {
p = new string [size = si];
for (int i = 0 ;i < size ;i++)
p[i] = s[i];
}
上例中,沒有為類Namelist定義拷貝構(gòu)造函數(shù),則下例中定義d2時將會調(diào)用編譯器提供的拷貝構(gòu)造函數(shù),將的
的數(shù)據(jù)成員拷貝到d2
int main()
{
string list[] = {"Lab","Husky","Collie"};
Namelist d1(list,3);
d1.dump();//Lab,Husky,Collie
Namelist d2(d1);
d2.dump();//Lab, Husky,Collie
d2.set("Great Dane",1);
d2.dump();//Lab, Great Dane, Colli
d1.dump(); //***** Caution: Lab, Great Dane, Collie
return 0;
}
這時,指針d1.p和指針d2.p將指向同一塊存儲空間
潛在危險:操作d1時可能會改變d2的內(nèi)容,反之亦
然。
為了避免發(fā)生潛在錯誤,為Namelist類設(shè)計一個滿足要求的拷貝構(gòu)造函數(shù)。
Namelist:: Namelist(const Namelist& d)
{
p = 0;
copyIntoP(d);
}
void Namelist::copyIntoP( const Namelist& d) {
delete [] p;
if (d.p != 0) {
p = new string[ size = d.size] ;
for( int i=0; i<size; i++)
p[i] = d.p[i];
}
else {
p = 0;
size = 0;
}
}
禁止對象拷貝
原因:我們知道,采用傳值方式將對象傳遞給一個函數(shù)或者返回一個對象時,將進行對象的拷貝操作。但有些對象很大,比如設(shè)計一個Windows類,如果進行對象間拷貝的話,非常費空間和時間。因此需要一種機制,能夠禁止這種情況的發(fā)生。
措施:通常采用將拷貝構(gòu)造函數(shù)設(shè)計成私有成員的方式,將禁止對象間的拷貝操作。
禁止對象拷貝:
class C{
public:
C();
private:
C( C&);
};
void f(C); //*** call by value
C g(); //*** return by value
int main(){
C c1,c2;
f( c1); //***** ERROR C(C&) is private!
c2 = g(); //***** ERROR C(C&) is private!
//...
}
void f(C cobj) { /*...*/}
C g() {/*...*/}
上個例子中,將類C的拷貝構(gòu)造函數(shù)的聲明放在private區(qū),這樣main函數(shù)中對f的調(diào)用將導(dǎo)致一個嚴(yán)重錯誤,因為試圖將c1以傳值方式傳遞給函數(shù)f。
要改正這個錯誤,我們就需要修改f,將其參數(shù)類型改為類C的引用:void f( C& cobj) {/.../} // ok, call by reference
main 函數(shù)對g的調(diào)用同樣導(dǎo)致一個嚴(yán)重錯誤,因為函數(shù)g以傳值方式返回一個C對象,就需要類C擁有一個公有的拷貝構(gòu)造函數(shù),但類C的拷貝構(gòu)造函數(shù)是私有的。
要避免這個錯誤,必須讓g返回類C的引用:
C& g() {/.../} //ok,return by reference
轉(zhuǎn)型構(gòu)造函數(shù)
轉(zhuǎn)型構(gòu)造函數(shù)是一個單參數(shù)的構(gòu)造函數(shù)。它可以將一個對象從一種數(shù)據(jù)類型(由參數(shù)指出)轉(zhuǎn)換為另一種數(shù)據(jù)類型(該構(gòu)造函數(shù)所屬的類)
class Person{
public:
Person() { name = " Unknown"; } //default
Person( const string& n) {name = n;} //convert
Person( const char* n) { name = n;} //convert
//...
private:
string name;
};
int main()
{
Person soprano( "Dawn Upshaw");
//...
}
轉(zhuǎn)型構(gòu)造函數(shù)可替代函數(shù)重載機制,假設(shè)函數(shù)f的參數(shù)類型為Person對象: void f(Person p);// declaration
如果以一個string作為參數(shù)來調(diào)用f:
string s = "Turandot";
f(s); //string, not Person
只要Person類擁有一個將string轉(zhuǎn)型為Person的轉(zhuǎn)型構(gòu)造函數(shù),那么編譯器就在string 對象s上調(diào)用它,以此來構(gòu)造一個Person對象作為f的參數(shù)。
我們稱上例中的Person類的轉(zhuǎn)型構(gòu)造函數(shù)支持隱式類型轉(zhuǎn)換,也就是說,該構(gòu)造函數(shù)采用隱藏方式將一個string轉(zhuǎn)型為一個Person。之所以說它是隱式的,是因為這個轉(zhuǎn)型動作由編譯器來完成,不需要編程人員提供一個明確的轉(zhuǎn)型操作。
隱式類型轉(zhuǎn)換提供了方便,但有時會導(dǎo)致一些無法預(yù)料到的錯誤,而這些錯誤往往細(xì)微得難以察覺。在這種時候,可以關(guān)閉這種因轉(zhuǎn)型構(gòu)造函數(shù)的存在而導(dǎo)致的隱式類型轉(zhuǎn)換動作,以保證程序的正確性。C++提供的關(guān)鍵字explicit可以用來關(guān)閉系統(tǒng)的隱式類型轉(zhuǎn)換功能。
class Person{
public:
// convert constructor marked as explicit
explicit Person( const string& n) {name = n;}
//...
};
void f( Person s) { /* note: f expects a Person... */}
int main(){
Person p("foo"); //convert constructor used
f ( p); //ok p is a Person
string b = "bar";
f( b ); //***** ERROR: no implicit type conversion
return 0;
}
構(gòu)造函數(shù)初始化程序
對const類型的數(shù)據(jù)成員進行初始化時不能直接賦值,如下列賦值操作是錯誤的。
class C{
public:
C() {
x = 0; // ok,x not const
c = 0; //***** ERROR: c is const
}
private:
int x; //nonconst data member
const int c; // const data member
};
對const類型的數(shù)據(jù)成員進行初始化時必須為構(gòu)造函數(shù)添加一個初始化列表
class C{
public:
C() : c(0) { x = -1;}
private:
int x;
const int c;// const data member
};
構(gòu)造函數(shù)的初始化段由一個冒號:開始,緊跟在冒號之后的是需要進行初始化的數(shù)據(jù)成員,然后是由一對小括號括起來的初始值。
初始化列表僅在構(gòu)造函數(shù)中有效,不能用于其他函數(shù)。構(gòu)造函數(shù)的初始化列表可以初始化任何數(shù)據(jù)成員( const or non const)
但const類型的數(shù)據(jù)成員只能在初始化列表里初始化,而不能用其他辦法進行初始化。
class C {
public:
C() : c( 0 ), x( -1 ) {} //empty body
private:
int x;
const int c;//const data member
};
構(gòu)造函數(shù)與操作符 new 和 new[]
當(dāng)使用動態(tài)方式為一個對象分配存儲空間時,C++操作符new和new[]比C函數(shù)malloc和calloc做的更好。因為操作符new和new[]在分配存儲空間的同時,還會調(diào)用相應(yīng)的構(gòu)造函數(shù),而malloc和calloc無法完成這個任務(wù)。
#include<cstdlib> // for malloc and calloc
class Emp {
public:
Emp() { /*...*/}
Emp ( const char* name) {/*...*/}
//...
};
int main()
{
Emp* elvis = new Emp(); //default
Emp* cher = new Emp(" Cher"); //convert
Emp* losOfEmps = new Emp[1000]; //default
Emp* foo = malloc ( sizeof ( Emp) ); // no constructor
//...
return 0;
}
析構(gòu)函數(shù)
創(chuàng)建類的對象時,會自動調(diào)用某個合適的構(gòu)造函數(shù)。同樣,當(dāng)對象被摧毀時,也會自動調(diào)用一個析構(gòu)函數(shù)。
對象的摧毀出現(xiàn)在如下兩種情況:
以某個類作為數(shù)據(jù)類型的變量超出其作用范圍。
用delete操作符刪除動態(tài)分配的對象。
與構(gòu)造函數(shù)一樣,析構(gòu)函數(shù)也是一個成員函數(shù)。
對于類C,其析構(gòu)函數(shù)的原型為: ~C();
由于析構(gòu)函數(shù)不帶參數(shù),因此不能被重載,這樣每個類只能擁有一個析構(gòu)函數(shù)。
與構(gòu)造函數(shù)一樣,析構(gòu)函數(shù)也沒有返回類型,所以void ~C();是錯誤的。
#include<iostream>
#include<string>
using namespace std;
class C{
public:
C() { //default constructor
name = "anonynous";
cout<<name<<" constructing.\n";
}
C(const char *n) { //parameterized constructor
name = n;
cout<< name<< " constructing.\n";
}
~C() { cout<<name<<" destructing.\n";}
private:
string name;
};
int main() {
/* 1 */ C c0("hortense"); //parameterized constructor
{
/* 2 */ C c1; //default constructor
/* 3 */ C c2("foo"); // parameterized constructor
cout<<'\n';
/* 4 */ } //c1 and c2 destructors called
/* 5 */ C *ptr = new C(); //default constructor
/* 6 */ delete ptr; //destructor for the ptr object
/* 7 */ return 0; //c0 destructor called
return 0;
}
console:
hortense constructing.
anonynous constructing.
foo constructing.
foo destructing.
anonynous destructing.
anonynous constructing.
anonynous destructing.
hortense destructing.
構(gòu)造函數(shù)和析構(gòu)函數(shù)小結(jié)
在創(chuàng)建對象時,類的構(gòu)造函數(shù)負(fù)責(zé)完成初始化和其它相關(guān)操作。
析構(gòu)函數(shù)在對象摧毀時完成相應(yīng)的清理工作(例如將構(gòu)造函數(shù)分配的資源釋放掉)。
建議為每個帶有數(shù)據(jù)成員的類設(shè)計一個默認(rèn)構(gòu)造函數(shù),如果需要,也要設(shè)計其他構(gòu)造函數(shù)和析構(gòu)函數(shù)。
類數(shù)據(jù)成員和類成員函數(shù)
類成員:成員屬于類本身,而不屬于類的某個對象。
對象成員或?qū)嵗蓡T:屬于對象的成員。前面講過的都是對象成員。
使用關(guān)鍵字static可以創(chuàng)建一個類成員。
類數(shù)據(jù)成員
聲明晶態(tài)成員的語法:
class Task {
public:
static unsigned getN() const (return n;}
//...
private:
static unsigned n; // count of Task objects
//...
};
Task類的數(shù)據(jù)成員n與Task類本身相關(guān),與任何Task對象無關(guān)。
由于n是static的,它對整個Task類而言只有一個,而不是每個Task對象都有一個n。
可以利用n來確定當(dāng)前存在的Task對象的數(shù)量。
Task ( const string & ID) {
setID( ID);
logFile = "log.dat";
setST();
ft = st; //no duration ye
n++; //another Task created
}
~Task() {
logToFile();
n--; // another Task destroyed
}
類成員與對象成員實例:
類數(shù)據(jù)成員除了必須在類聲明內(nèi)部用static進行聲明外,還必須在類外進行定義。
定義時可以指定初始值。缺省情況下初始化為0.
class Task {
public:
//...
private:
static unsigned n; // count of Task objects
//...
};
unsinged Task::n = 0; //define static data member
static 數(shù)據(jù)成員不會影響該類及其對象的sizeof。如下例中表達(dá)式sizeof(C)和sizeof(c1)的值都是16。
class C {
unsigned long dm1;
double dm2;
static unsigned long dm3; //does not impact sizeof (C)
static double dm4; //does not impact sizeof(C)
};
類成員函數(shù)
除了static數(shù)據(jù)成員,類還可以有static成員函數(shù)。
class Task {
public:
static unsigned getN() const {return n;}
//...
private:
static unsigned n; //count of Task objects
//...
};
靜態(tài)成員函數(shù)只能訪問其他的static成員,包括數(shù)據(jù)成員和成員函數(shù)。而非靜態(tài)成員函數(shù)既可以訪問static成員,也可以訪問非靜態(tài)成員。
static成員函數(shù)既可以是inline函數(shù),也可以是非inline函數(shù)
class Task {
public:
static unsigned get() {
setST(); //*****ERROR: NOT static!
st = time ( 0 ); //***** ERROR: not static!
return n; // ok,n is static
}
//...
};
訪問static數(shù)據(jù)成員和static成員函數(shù)的方式:
通過對象來訪問;直接通過類來訪問(推薦)
class C{
public :
static int sVar;
static void sMeth();
//...
};
int main(){
C c1;
c1.sMeth(); //through an object
C::sMeth(); //directly and preferred
unsigned x = c1.sVar; //through an object
unsigned y = C::sVar; //directly and preferred
//...
}
在成員函數(shù)內(nèi)定義靜態(tài)變量
成員函數(shù)內(nèi)的局部變量可以是static的。如果將成員函數(shù)內(nèi)的某個局部變量定義為靜態(tài)變量,該類的所有對象在調(diào)用這個成員函數(shù)時將共享這個變量。
class C{
public:
void m() ; //object method
private:
int x; //object data member
};
void C::m() {
static int s = 0; //*****Caution: 1 copy for all objects
cout << ++s << '\n';
}
int main() {
C c1,c2;
c1.m() //output 1
c2.m(); //output 2
c1.m(); //output 3
return 0;
}
上個例子中,在成員函數(shù)m中定義了一個static變量s,由于s定義在程序塊內(nèi),它擁有程序塊范圍,因此它只能在m內(nèi)部訪問。換句話說,該變量只有在函數(shù)內(nèi)部才有效。每調(diào)用m一次,s就會相應(yīng)地增加一次。
因為m是C的成員函數(shù),所以C的所有對象都共享這個靜態(tài)局部變量。這樣,不同對象對m的每一次調(diào)用訪問的都是同一個s。
相反,對于非靜態(tài)局部變量x來說,每個C對象都擁有一個x。
指向?qū)ο蟮闹羔?br>
對象或?qū)ο笠檬褂贸蓡T選擇操作符,來訪問對象的成員。要通過指針來訪問成員,必須使用指針操作符->
在C++中,指向?qū)ο蟮闹羔樦饕糜趦蓚€方面:
指向?qū)ο蟮闹羔樋梢宰鳛閰?shù)傳遞給函數(shù),或通過函數(shù)返回。
使用操作符new和new[]動態(tài)創(chuàng)建對象,然后返回一個指向該對象的指針。
常量指針this
this是一個關(guān)鍵字,this指針只能出現(xiàn)在類的非靜態(tài)成員函數(shù)中。它指向調(diào)用該成員函數(shù)的那個對象。靜態(tài)成員函數(shù)中不能出現(xiàn)this指針。
this指針不是對象的一部分,所以不影響對象的大小。
當(dāng)一個非靜態(tài)成員函數(shù)被某對象調(diào)用時,編譯器將該對象的地址作為一個隱含的參數(shù)傳給該成員函數(shù),例如下面的函數(shù)調(diào)用:
myDate.setMonth(3);可以看作
setMonth(&myDate,3);
在成員函數(shù)內(nèi)部,可以通過this指針來獲取對象的地址。
大多數(shù)情況下,this指針都是隱含使用的。但也可以顯式地使用this指針來調(diào)用類的成員。例如:
void Date::setMonth( int mn) {
month = mn;
this->month = mn;
(*this).month = mn;
}
上述三條語句是等價的。
this指針通常用來從成員函數(shù)中返回當(dāng)前對象。
return *this;
this指針有時也用來避免自引用
if (&Object != this)
this指針是一個常量,它不能作為賦值、遞增、遞減等運算的目標(biāo)對象。此外this只在非static成員函數(shù)中用才有效。
與普通函數(shù)相比,靜態(tài)成員函數(shù)由于不與任何對象相聯(lián)系,因此它不具有this指針。由于沒有this指針的額外開銷,因此靜態(tài)成員函數(shù)與類的全局函數(shù)相比速度上會有少許的增長。
繼承
C++允許從任何已存在的類派生新類,所派生的類被稱為派生類(derived class),又稱為子類。而已存在的用于派生新類的類被稱為基類(base class),又稱為父類。
在C++中,一個派生類既可以從一個基類派生,也可以從多個基類派生。
從一個基類派生類的繼承被稱為單繼承。
從多個基類派生類的繼承被稱為多繼承。
C++中繼承的語法:
單繼承語法:
class 派生類名 : 訪問控制 基類名
{
數(shù)據(jù)成員和成員函數(shù)聲明
};
多繼承語法:
class 派生類名 : 訪問控制 基類名1,訪問控制 基類名2,... 訪問控制 基類名n
{
數(shù)據(jù)成員和成員函數(shù)聲明
};
類繼承舉例:
單繼承:
class Bear : public ZooAnimal
{ ... };
多繼承:
class Panda : public Bear, public Endagered
{ ... };
其中:
public: 訪問控制關(guān)鍵字,指明繼承方式是公有繼承。
當(dāng)一個類通過公有繼承方式從基類繼承時,基類中的公有成員在派生類中也是公有的。
不指明繼承方式關(guān)鍵字public時,編譯器會默認(rèn)繼承方式為private或protected。
":"用于建立基類與派生類的層次結(jié)構(gòu)。
基類:C++提供的或用戶自定義的類。
派生類中可以定義數(shù)據(jù)成員和成員函數(shù),此外,還繼承基類所有成員。
class Pen{
public:
enum int {Off, On};
void set_status( ink );
void set_location(int ,int);
private:
int x;
int y;
ink status;
};
class CPen : public Pen {
public:
void set_color( int );
private:
int_color;
};
繼承機制下的私有成員
基類的所有私有成員僅在基類中可見,而在派生類中是不可見的。
但派生類對象會為基類中的所有私有成員分配空間。
如上個例子中,CPen類從Pen類繼承了數(shù)據(jù)成員x,y和status,盡管這些成員在CPen類中是不可見的,但無論何時創(chuàng)建CPen類的對象時,該對象都將獲得相應(yīng)的存儲空間來保存x,y和status等數(shù)據(jù)成員。
盡管在派生類中不能直接訪問基類的私有成員,但可以通過間接的方式(調(diào)用從基類繼承來的公有成員函數(shù))進行。
如上個例子中,x,y和status可以通過成員函數(shù)set_location和set_status進行訪問。
class Point {
public:
void set_x( int x1 ) { x = x1; }
void set_y( int y1 ) { y = y1; }
int get_x() const {return x;}
int get_y() const {return y;}
private:
int x;
int y;
};
class Intense_point : public Point {
public:
void set_intensity( int i ) { intensity = i; }
int get_intensity() const { return intensity;}
private:
int intensity;
};
改變訪問限制:
使用using聲明可以改變成員在派生類中的訪問限制。例如:基類中的公有成員一般情況下被繼承為公有成員,但使用using聲明可將其改為私有成員(或保護成員)
class BC { //base class
public:
void set_x( float a ) { x = a;}
private:
float x;
};
class DC : public BC { //derived class
public:
void set_y(float b) { y = b; }
private:
flaot y;
usign BC::set_x;
};
這樣就無法直接通過DC類的任何對象調(diào)用set_x:
int main() {
DC d;
d.set_y(4.31); //OK
d.set_x(-8.03); // ***** ERROR:set_x is private in DC
//...
}
如果基類的某個公有成員函數(shù)在繼承類中不適合,則可以通過using聲明將其轉(zhuǎn)變?yōu)樗接谐蓡T函數(shù),從而使它在派生類中隱藏起來。
例如,“有序元素類”從“無序元素類”派生而來,基類“無序元素類”中的某些成員函數(shù)可能并不適合“有序元素類”。
如基類中某個成員函數(shù)的算法為:在一個無序表中的任意位置插入一個元素。這個算法顯然不符合有序表的處理要求。這樣的成員函數(shù)就應(yīng)通過using聲明將其在派生類中隱藏起來。
名字隱藏
如果派生類添加了一個數(shù)據(jù)成員,而該成員與基類中的某個數(shù)據(jù)成員同名,新的數(shù)據(jù)成員就隱藏了繼承來的同名成員。
保護成員
除了私有和公有成員, C++還提供了保護成員。在沒有繼承的情況下,保護成員和私有成員類似,只
在該類中可見。在公有繼承方式下,保護成員和私有成員具有不同性
質(zhì):
- 基類的保護成員在派生類中是可見的。
-
而基類的私有成員在派生類中是不可見的。
image.png
派生類可對從基類繼承來的保護成員進行訪問,也就是說保護成員在派生類中是可見的。但派生類不能訪問一個基類對象的保護成員,因為基類對象屬于基類,不屬于派生類。
采用public派生
基類的私有成員,在派生類中不可見,基類的私有成員只能被基類的其他成員函數(shù)訪問(除friend函數(shù))。
基類的保護成員,在派生類可見,基類的保護成員除了能被基類的其他成員函數(shù)訪問外,還能被類層次結(jié)構(gòu)中的所有成員函數(shù)訪問。
基類的公有成員,在派生類可見,可被任何函數(shù)訪問。
一般來說,應(yīng)避免將數(shù)據(jù)成員設(shè)計為保護類型
而是采用私有數(shù)據(jù)成員與相應(yīng)保護型訪問函數(shù)結(jié)合的模式,便于實現(xiàn)數(shù)據(jù)隱藏(復(fù)雜數(shù)據(jù)成員例外)。
繼承機制下的構(gòu)造函數(shù)
當(dāng)創(chuàng)建一個派生類對象時,基類的構(gòu)造函數(shù)被自動調(diào)用,用來對派生類對象中的基類部分進行初始化,并完成其他一些相關(guān)事務(wù)。
如果派生類定義了自己的構(gòu)造函數(shù),則由該構(gòu)造函數(shù)負(fù)責(zé)對象中“派生類添加部分”的初始化工作。
當(dāng)基類構(gòu)造函數(shù)的功能對派生類而言不夠用的時候,派生類必須定義自己的構(gòu)造函數(shù)。
可以在派生類的構(gòu)造函數(shù)中調(diào)用其基類的構(gòu)造函數(shù)(前提是基類擁有構(gòu)造函數(shù))
多米諾骨牌效應(yīng):在一個層次很深的類層次結(jié)構(gòu)中,創(chuàng)建一個派生類對象將導(dǎo)致派生鏈中的所有類的構(gòu)造函數(shù)被逐一調(diào)用。
如果基類擁有構(gòu)造函數(shù)但沒有默認(rèn)構(gòu)造函數(shù),那么派生類的構(gòu)造函數(shù)必須顯式地調(diào)用基類的某個構(gòu)造函數(shù)。
一般來說,最好為基類提供一個默認(rèn)構(gòu)造函數(shù),不但可以避免出現(xiàn)上述問題,而且并不妨礙派生類構(gòu)造函數(shù)去調(diào)用基類的非默認(rèn)構(gòu)造函數(shù)。
假設(shè)基類擁有默認(rèn)構(gòu)造函數(shù),而其派生類也定義了一些構(gòu)造函數(shù),不過派生類的任何構(gòu)造函數(shù)都沒有顯式地調(diào)用基類的某個構(gòu)造函數(shù)。在這種情況下,當(dāng)創(chuàng)建一個派生類對象時,基類的默認(rèn)構(gòu)造函數(shù)將被自動地調(diào)用。
以“DC類從BC 類派生”為例,總結(jié)如下:
- 若DC有構(gòu)造函數(shù)而BC沒有,當(dāng)創(chuàng)建DC類的對象時,DC的相應(yīng)構(gòu)造函數(shù)被自動調(diào)用。
- 若DC沒有構(gòu)造函數(shù)而BC有,則BC必須擁有默認(rèn)構(gòu)造函數(shù)。只有這樣,當(dāng)創(chuàng)建DC類的對象時,才能自動執(zhí)行BC的默認(rèn)構(gòu)造函數(shù)。
- 若DC有構(gòu)造函數(shù),且BC有默認(rèn)構(gòu)造函數(shù),則創(chuàng)建DC類的對象時,BC的默認(rèn)構(gòu)造函數(shù)會自動執(zhí)行,除非當(dāng)前被調(diào)用的派生類構(gòu)造函數(shù)在其初始化段中顯式地調(diào)用了BC的非默認(rèn)構(gòu)造函數(shù)。
- 若DC和BC都有構(gòu)造函數(shù),但BC沒有默認(rèn)構(gòu)造函數(shù),則DC的每個構(gòu)造函數(shù)必須在其初始化段中顯式地調(diào)用BC的某個構(gòu)造函數(shù)。只有這樣,當(dāng)創(chuàng)建DC的對象時,BC的構(gòu)造函數(shù)才能獲得執(zhí)行機會。
在創(chuàng)建派生類對象時,必須顯式地或隱式地執(zhí)行其基類的某個構(gòu)造函數(shù),因為有時候,派生類的構(gòu)造函數(shù)可能會依賴基類的構(gòu)造函數(shù)來完成一些必要的操作。例如,依賴基類構(gòu)造函數(shù)來完成部分?jǐn)?shù)據(jù)成員的初始化。
繼承機制下的析構(gòu)函數(shù)
在類的層次結(jié)構(gòu)中,構(gòu)造函數(shù)按基類到派生類的次序執(zhí)行,析構(gòu)函數(shù)則按派生類到基類的次序執(zhí)行,因此,析構(gòu)函數(shù)的執(zhí)行次序和構(gòu)造函數(shù)的執(zhí)行次序是相反的。
由于每個類至多只有一個析構(gòu)函數(shù),因此對析構(gòu)函數(shù)的調(diào)用不會產(chǎn)生二義性,這樣在析構(gòu)函數(shù)中不必顯式地調(diào)用其他析構(gòu)函數(shù),這一點和構(gòu)造函數(shù)的調(diào)用規(guī)則是不同的。
構(gòu)造函數(shù)回顧
- 在派生類的對象中,由基類中聲明的數(shù)據(jù)成員和函數(shù)成員所構(gòu)成的封裝體稱為基類子對象。
- 基類子對象由基類中聲明的構(gòu)造函數(shù)進行初始化。
- 構(gòu)造函數(shù)不能被繼承,所以,一個派生類的構(gòu)造函數(shù)必須通過調(diào)用基類的某個構(gòu)造函數(shù)來初始化基類子對象。
- 派生類的構(gòu)造函數(shù)只負(fù)責(zé)初始化在派生類中聲明的數(shù)據(jù)成員。