-
#1.定義抽象數(shù)據(jù)類(lèi)型
- 1.1 設(shè)計(jì)Sales_data類(lèi)
- 1.2 定義改進(jìn)的Sales_data類(lèi)
- 1.3 定義類(lèi)相關(guān)的成員函數(shù)
- 1.4 構(gòu)造函數(shù)
- 1.5 拷貝、賦值和析構(gòu)
-
#2.訪問(wèn)控制和封裝
- 2.1 友元
-
#3.類(lèi)的其他特性
- 3.1 類(lèi)成員再探
- 3.2 返回*this的成員函數(shù)
- 3.3 類(lèi)類(lèi)型
- 3.4 友元再探
-
#4.類(lèi)的作用域
- 4.1 名字查找和類(lèi)的作用域
-
#5.構(gòu)造函數(shù)再探
- 5.1 構(gòu)造函數(shù)初始值列表
- 5.2 委托構(gòu)造函數(shù)
- 5.3 默認(rèn)構(gòu)造函數(shù)的作用
- 5.4 隱式的類(lèi)類(lèi)型轉(zhuǎn)換
- 5.5 聚合類(lèi)
- 5.6 字面值常量類(lèi)
- #6.類(lèi)的靜態(tài)成員
類(lèi)的基本思想是數(shù)據(jù)抽象和封裝,數(shù)據(jù)抽象是一種依賴(lài)于接口和實(shí)現(xiàn)分離的編程技術(shù)蒲犬。
封裝實(shí)現(xiàn)了類(lèi)的接口和實(shí)現(xiàn)分離莺琳。封裝后的類(lèi)隱藏了實(shí)現(xiàn)細(xì)節(jié)囚似,類(lèi)的用戶(hù)只能使用接口而無(wú)法訪問(wèn)實(shí)現(xiàn)部分倒堕。
類(lèi)要想實(shí)現(xiàn)數(shù)據(jù)抽象和封裝,需要首先定義一個(gè)抽象數(shù)據(jù)類(lèi)型钧舌。
#1. 定義抽象數(shù)據(jù)類(lèi)型
要想定義抽象數(shù)據(jù)類(lèi)型,我們需要定義一些操作以供類(lèi)的用戶(hù)使用涎跨。一旦類(lèi)定義了自己的操作洼冻,我們就可以封裝它的數(shù)據(jù)成員。
1.1 設(shè)計(jì)Sales_data類(lèi)
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
1.2 定義改進(jìn)的Sales_data類(lèi)
定義和聲明成員函數(shù)的方式和普通函數(shù)類(lèi)似差不多隅很。成員函數(shù)的聲明必須在類(lèi)的內(nèi)部撞牢,它的定義則既可以在類(lèi)的內(nèi)部也可以在類(lèi)的外部。作為接口組成部分的非成員函數(shù)外构,它們的定義和聲明都在類(lèi)的外部普泡。
struct Sales_data {
//新成員:關(guān)于Sales_data對(duì)象的操作
std::string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data &);
double avg_price() const;
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
//Sales_data的非成員接口函數(shù)
Sales_data add(const Sales_data &,const Sales_data &);
std::ostream &print(std::ostream &,const Sales_data &);
std::istream &read(std::istream &,Sales_data &);
==定義在類(lèi)內(nèi)部的函數(shù)都是隱式的inline函數(shù)。==
定義成員函數(shù)
所有的成員都必須在類(lèi)的內(nèi)部聲明审编,但是成員函數(shù)體可以定義在類(lèi)內(nèi)也可以定義在類(lèi)外撼班。以Sales_data的成員函數(shù)isbn為例來(lái)引出this指針:
std::string isbn() const {
return bookNo;
}
isbn函數(shù)返回Sales_data的數(shù)據(jù)成員bookBo。
引入this
成員函數(shù)通過(guò)一個(gè)名為this的額外的隱式參數(shù)來(lái)訪問(wèn)調(diào)用它的那個(gè)對(duì)象垒酬。當(dāng)我們調(diào)用一個(gè)成員函數(shù)時(shí)砰嘁,用請(qǐng)求該函數(shù)的對(duì)象地址初始化this件炉。例如,如果調(diào)用:
total.isbn();
則編譯器負(fù)責(zé)把total的地址傳遞給isbn的隱式形參this矮湘≌迕幔可以等價(jià)地認(rèn)為編譯器將該調(diào)用重寫(xiě)成了如下形式:
Sales_data::isbn(&total);
其中,調(diào)用Sales_data的isbn成員時(shí)傳入了total地址缅阳。在成員函數(shù)內(nèi)部磕蛇,我們可以直接使用調(diào)用該函數(shù)的對(duì)象的成員,而不須通過(guò)成員訪問(wèn)運(yùn)算符來(lái)做到這一點(diǎn)十办,因?yàn)閠his所指向的正是這個(gè)對(duì)象秀撇。任何對(duì)類(lèi)的成員的直接訪問(wèn)都被看作this的隱式引用。對(duì)于我們來(lái)說(shuō)向族,this形參是隱式定義的呵燕。我們可以把isbn定義成如下形式:
std::string isbn() const {
return this->bookNo;
}
因?yàn)閠his的目的總是指向這個(gè)對(duì)象,所以this是一個(gè)常量指針件相。
引入const成員函數(shù)
isbn函數(shù)的另一個(gè)關(guān)鍵之處是緊隨參數(shù)列表之后的const關(guān)鍵字再扭,這里,const關(guān)鍵字的作用是修改隱式this指針的類(lèi)型夜矗。默認(rèn)情況下泛范,this的類(lèi)型是指向類(lèi)類(lèi)型非常量版本的常量指針。C++語(yǔ)言允許我們把const關(guān)鍵字放在成員函數(shù)的參數(shù)列表之后侯养,此時(shí)敦跌,緊跟在參數(shù)列表后面的const表示this是一個(gè)指向常量的指針。像這樣使用const的成員函數(shù)被稱(chēng)作常量成員函數(shù)逛揩。
//偽代碼柠傍。說(shuō)明隱式的this指針如何使用的
//下面代碼是非法的,因?yàn)槲覀儾荒茱@式的定義自己的this指針
//謹(jǐn)記此處的this是一個(gè)指向常量的指針辩稽,因?yàn)閕sbn是一個(gè)常量成員
std::string Sales_data::isbn(const Sales_data *const this) const {
return this->bookNo;
}
==常量對(duì)象惧笛,以及常量對(duì)象的引用或指針都只能調(diào)用常量成員函數(shù)。==
類(lèi)作用域和成員函數(shù)
編譯器分兩步處理類(lèi):首先編譯成員的聲明逞泄,然后再是函數(shù)體患整。因此,成員函數(shù)體可以隨意使用類(lèi)的其他成員而無(wú)須在意這些成員的出現(xiàn)的次序喷众。
在類(lèi)的外部定義成員函數(shù)
當(dāng)我們?cè)陬?lèi)的外部定義成員函數(shù)時(shí)各谚,成員函數(shù)的定義必須與它的聲明匹配,同時(shí)到千,類(lèi)外部定義的成員的名字必須包含它所屬的類(lèi)名:
double Sales_data::avg_price() const {
if (units_sold) {
return revenue / units_sold;
}else {
return 0;
}
}
定義一個(gè)返回this對(duì)象的函數(shù)
Sales_data &Sales_data::combine(const Sales_data &rhs) {
units_sold += rhs.units_sold; //把rhs的成員加到this對(duì)象的成員上
revenue += rhs.revenue;
return *this; //返回調(diào)用該函數(shù)的對(duì)象
}
1.3 定義類(lèi)相關(guān)的非成員函數(shù)
我們定義非成員函數(shù)的方式和其他函數(shù)一樣昌渤,通常把函數(shù)的聲明和定義分離開(kāi)來(lái)。如有函數(shù)在概念是屬于類(lèi)但是不定義在類(lèi)中憔四,則它一般應(yīng)與類(lèi)聲明在同一個(gè)頭文件內(nèi)膀息。
定義read和print函數(shù)
std::istream &read(std::istream &is, Sales_data &item) {
double price = 0.0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
std::ostream &print(std::ostream &os, const Sales_data &item) {
os << item.isbn() << " " << item.units_sold
<< " " << item.revenue << " " << item.avg_price();
return os;
}
定義add函數(shù)
Sales_data add(const Sales_data &lhs, const Sales_data &rhs) {
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
1.4 構(gòu)造函數(shù)
每個(gè)類(lèi)都分別定義了它的對(duì)象被初始化的方式般眉,類(lèi)通過(guò)一個(gè)或幾個(gè)特殊的成員函數(shù)來(lái)控制其對(duì)象的初始化過(guò)程,這些函數(shù)叫做構(gòu)造函數(shù)潜支。構(gòu)造函數(shù)的任務(wù)是初始化類(lèi)對(duì)象的數(shù)據(jù)成員甸赃,無(wú)論何時(shí)只要類(lèi)的對(duì)象被創(chuàng)建,就會(huì)執(zhí)行構(gòu)造函數(shù)冗酿。
構(gòu)造函數(shù)的名字與類(lèi)名相同埠对。和其他函數(shù)不一樣的是,構(gòu)造函數(shù)沒(méi)有返回類(lèi)型裁替;構(gòu)造函數(shù)不能被聲明成const鸠窗。
合成的默認(rèn)構(gòu)造函數(shù)
類(lèi)通過(guò)一個(gè)一個(gè)特殊的構(gòu)造函數(shù)來(lái)控制默認(rèn)初始化過(guò)程,這個(gè)函數(shù)叫做默認(rèn)構(gòu)造函數(shù)胯究。默認(rèn)構(gòu)造函數(shù)無(wú)須任何實(shí)參。編譯器創(chuàng)建的構(gòu)造函數(shù)又被稱(chēng)為合成的默認(rèn)構(gòu)造函數(shù)躁绸。
==只有當(dāng)類(lèi)沒(méi)有聲明任何構(gòu)造函數(shù)時(shí)裕循,編譯器才會(huì)自動(dòng)生成默認(rèn)構(gòu)造函數(shù)。==
某些類(lèi)不能依賴(lài)于合成的默認(rèn)構(gòu)造函數(shù)
對(duì)于一個(gè)普通的類(lèi)來(lái)說(shuō)净刮,必須定義它自己的默認(rèn)構(gòu)造函數(shù)剥哑,原因有三:
- 只有當(dāng)類(lèi)沒(méi)有聲明任何構(gòu)造函數(shù)時(shí),編譯器才會(huì)自動(dòng)地生成默認(rèn)構(gòu)造函數(shù)淹父。一旦在類(lèi)中定義了其他構(gòu)造函數(shù)株婴,除非我們?cè)俣x默認(rèn)構(gòu)造函數(shù),否則類(lèi)沒(méi)有默認(rèn)構(gòu)造暑认。
- 對(duì)于某些類(lèi)而言困介,合成的默認(rèn)構(gòu)造可能執(zhí)行錯(cuò)誤的操作。如果類(lèi)中的內(nèi)置類(lèi)型或復(fù)合類(lèi)型的對(duì)象被默認(rèn)初始化蘸际,它們的值將是未定義的座哩。
- 編譯器有時(shí)候不能為類(lèi)合成默認(rèn)的構(gòu)造函數(shù)。例如:類(lèi)中包含了一個(gè)其他類(lèi)型的成員粮彤,而這個(gè)成員沒(méi)有默認(rèn)構(gòu)造函數(shù)根穷。
定義Sales_data的構(gòu)造函數(shù)
struct Sales_data {
//C++11新標(biāo)準(zhǔn)中,如果我們需要默認(rèn)的行為导坟,可以在參數(shù)列表后寫(xiě)上=default來(lái)要求編譯器生成默認(rèn)構(gòu)造
Sales_data() = default;
Sales_data(const std::string &s) :bookNo(s) {}
Sales_data(const std::string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p*n) {}
Sales_data(std::iostream &);
std::string isbn() const {
return bookNo;
}
Sales_data &combine(const Sales_data &);
double avg_price() const;
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
=default的含義
在C++11新標(biāo)準(zhǔn)中屿良,如果我們需要默認(rèn)的行為,可以通過(guò)在參數(shù)列表后寫(xiě)上=default來(lái)要求編譯器生成構(gòu)造函數(shù)惫周。
構(gòu)造函數(shù)初始值列表
Sales_data(const std::string &s) :bookNo(s) {}
Sales_data(const std::string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p*n) {}
這兩個(gè)定義中出現(xiàn)了新的部分尘惧,即冒號(hào)以及冒號(hào)和花括號(hào)之間的代碼,其中花括號(hào)定義了函數(shù)體闯两。我們把新出現(xiàn)的部分稱(chēng)為構(gòu)造函數(shù)初始值列表褥伴。它負(fù)責(zé)為新創(chuàng)建的對(duì)象的一個(gè)或幾個(gè)數(shù)據(jù)成員賦初值谅将。構(gòu)造函數(shù)初始值是成員名字的一個(gè)列表,每個(gè)名字后面緊跟括號(hào)括起來(lái)的(或者在花括號(hào)內(nèi)的)成員初始值重慢。不同成員初始化通過(guò)逗號(hào)分隔開(kāi)來(lái)饥臂。
在類(lèi)的外部定義構(gòu)造函數(shù)
與其他幾個(gè)構(gòu)造函數(shù)不同,以istream為參數(shù)的構(gòu)造函數(shù)需要執(zhí)行一些實(shí)際操作似踱。在它的函數(shù)體內(nèi)隅熙,調(diào)用了read函數(shù)以給數(shù)據(jù)成員賦以初值:
Sales_data::Sales_data(std::iostream &is) {
read(is, *this);
}
當(dāng)我們?cè)陬?lèi)的外部定義構(gòu)造函數(shù)時(shí),必須指明該構(gòu)造函數(shù)是哪個(gè)類(lèi)的成員核芽。因此囚戚,需要指定具體作用域。
1.5 拷貝轧简、賦值和析構(gòu)
除了定義類(lèi)的對(duì)象如何初始化之外驰坊,類(lèi)還需要控制拷貝、賦值和銷(xiāo)毀對(duì)象時(shí)發(fā)生的行為哮独。
某些類(lèi)不能依賴(lài)于合成的版本
盡管編譯器能替我們合成拷貝拳芙、賦值和銷(xiāo)毀的操作,但是必須清楚的一點(diǎn)是皮璧,對(duì)于某些類(lèi)來(lái)說(shuō)合成的版本無(wú)法正常工作舟扎。
#2. 訪問(wèn)控制與封裝
c++語(yǔ)言中,使用訪問(wèn)說(shuō)明符加強(qiáng)類(lèi)的封裝性:
- 定義在public說(shuō)明符之后的成員在整個(gè)程序內(nèi)可被訪問(wèn)悴务,public成員定義類(lèi)的接口睹限。
- 定義在private說(shuō)明符之后的成員可以被類(lèi)的成員函數(shù)訪問(wèn),但是不能被使用該類(lèi)的代碼訪問(wèn)讯檐,private部分封裝了類(lèi)的實(shí)現(xiàn)細(xì)節(jié)羡疗。
使用class或struct關(guān)鍵字
使用class和struct定義類(lèi)唯一的區(qū)別就是默認(rèn)的訪問(wèn)權(quán)限。使用struct關(guān)鍵字别洪,則定義在第一個(gè)訪問(wèn)說(shuō)明符之前的成員是public顺囊;如果使用class關(guān)鍵字,則成員是private的蕉拢。
==使用class和struct定義類(lèi)唯一的區(qū)別就是默認(rèn)的訪問(wèn)權(quán)限特碳。==
2.1 友元
類(lèi)可以允許其他類(lèi)或者函數(shù)訪問(wèn)它的非公有成員,方法是令其他類(lèi)或者函數(shù)成為它的友元晕换。如果類(lèi)想把一個(gè)函數(shù)作為它的友元午乓,只需要增加一條以friend關(guān)鍵字開(kāi)始的函數(shù)聲明即可:
class Sales_data {
//為Sales_data的非成員函數(shù)所做的友元聲明
friend Sales_data add(const Sales_data &, const Sales_data &);
friend std::ostream &print(std::ostream &, const Sales_data &);
friend std::istream &read(std::istream &, Sales_data &);
public:
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(n*p) {}
Sales_data(const std::string &s) :bookNo(s) {}
Sales_data(std::iostream &);
std::string isbn() const {
return bookNo;
}
Sales_data &combine(const Sales_data &);
private:
double avg_price() const {
return units_sold ? revenue / units_sold : 0;
}
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
//Sales_data接口的非成員接口的聲明
Sales_data add(const Sales_data &, const Sales_data &);
std::ostream &print(std::ostream &, const Sales_data &);
std::istream &read(std::istream &, Sales_data &);
==一般來(lái)說(shuō),最好在類(lèi)定義開(kāi)始或結(jié)束前的位置集中聲明友元闸准。==
友元的聲明
友元的聲明僅僅指定了訪問(wèn)的權(quán)限益愈,而非一個(gè)通常意義上的函數(shù)聲明。如果我們希望用戶(hù)能夠調(diào)用某個(gè)友元函數(shù),那么我們必須在友元聲明之外再專(zhuān)門(mén)對(duì)函數(shù)進(jìn)行一次聲明蒸其。
#3. 類(lèi)的其他特性
3.1 類(lèi)成員再探
定義一個(gè)類(lèi)型成員
class Screen {
public:
typedef std::string::size_type pos; //pos為類(lèi)型成員
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
用來(lái)定義類(lèi)型的成員必須先定義后使用敏释。
Screen類(lèi)的成員函數(shù)
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default;
Screen(pos ht,pos wd,char c):height(ht),width(wd),
contents(ht * wd,c){}
char get() const { //讀取光標(biāo)處的字符
return contents[cursor]; //隱式內(nèi)聯(lián)
}
inline char get(pos ht,pos wd) const; //顯示內(nèi)聯(lián)
Screen &move(pos r,pos c);
private:
pos cursor = 0;
pos height = 0,width = 0;
std::string contents;
};
令成員作為內(nèi)聯(lián)函數(shù)
在類(lèi)中,常有一些規(guī)模較小的函數(shù)適合于被聲明成內(nèi)聯(lián)函數(shù)摸袁。定義在類(lèi)內(nèi)部的函數(shù)是自動(dòng)inline的钥顽。我們也可以在類(lèi)的外部用inline關(guān)鍵字修飾函數(shù)的定義:
inline Screen &Screen::move(pos r,pos c) {
pos row = r * width; //計(jì)算行的位置
cursor = row + c; //在行內(nèi)將光標(biāo)移動(dòng)到指定的列
return *this; //以左值的形式返回對(duì)象
}
char Screen::get(pos r,pos c) const { //在類(lèi)的內(nèi)不聲明成inline
pos row = r * width; //計(jì)算行的位置
return contents[row + c]; //返回給定列的字符
}
重載成員函數(shù)
和非成員函數(shù)一樣,成員函數(shù)也可以被重載靠汁。只要函數(shù)之間在參數(shù)的數(shù)量和\或類(lèi)型上有區(qū)別就行蜂大。
可變數(shù)據(jù)成員
有時(shí)會(huì)發(fā)生這樣一種情況,我們希望修改類(lèi)的某個(gè)數(shù)據(jù)成員蝶怔,即使是在一個(gè)const成員函數(shù)內(nèi)奶浦。可以通過(guò)在變量的聲明中加入mutable關(guān)鍵字做到這一點(diǎn)踢星。
一個(gè)可變數(shù)據(jù)成員永遠(yuǎn)不會(huì)是const澳叉,即使它是const對(duì)象的成員。因此沐悦,一個(gè)const成員可以改變一個(gè)可變成員的值耳高。
class Screen {
public:
void some_member() const;
private:
mutable size_t access_ptr; //即使在一個(gè)const對(duì)象內(nèi)也能被修改
};
void Screen::some_member() const {
++access_ptr;
}
3.2 返回*this的成員函數(shù)
class Screen {
public:
typedef std::string::size_type pos;
Screen &set(char);
Screen &set(pos,pos,char);
private:
pos cursor;
pos height = 0, width = 0;
std::string contents;
};
inline Screen &Screen::set(char c) {
contents[cursor] = c;
return *this;
}
inline Screen &Screen::set(pos r, pos col, char ch) {
contents[r*width + col] = ch;
return *this;
}
和move操作一樣,我們的set成員的返回值是調(diào)用set的對(duì)象的引用所踊。返回引用的函數(shù)是左值的,意味著這些函數(shù)返回的是對(duì)象本身而非對(duì)象的副本概荷。
從const成員函數(shù)返回*this
==一個(gè)const成員函數(shù)如果以引用的形式返回*this秕岛,那么它的返回類(lèi)型將是常量引用。==
基于const的重載
通過(guò)區(qū)分成員函數(shù)是否是const的误证,我們可以對(duì)其進(jìn)行重載继薛。
class Screen {
public:
//根據(jù)對(duì)象是否是const重載了display函數(shù)
Screen &display(std::ostream &os){
do_display(os);
return *this;
}
const Screen &display(std::ostream &os) {
do_display(os);
return *this;
}
private:
//該函數(shù)負(fù)責(zé)顯示Screen的內(nèi)容
void do_display(std::ostream &os) const {
os << contents;
}
};
3.3 類(lèi)類(lèi)型
每個(gè)類(lèi)定義了唯一的類(lèi)型。對(duì)于兩個(gè)類(lèi)來(lái)說(shuō)愈捅,即使它們的成員完全一樣遏考,這兩個(gè)類(lèi)也是兩個(gè)不同的類(lèi)型。例如:
struct First {
int memi;
int getMem();
};
struct Second {
int memi;
int getMem();
};
First obj1;
Second obj2 = obj1; //錯(cuò)誤:obj1和obj2的類(lèi)型不同
==即使兩個(gè)類(lèi)的成員列表完全一致蓝谨,它們也是不同的類(lèi)型灌具。對(duì)于一個(gè)類(lèi)來(lái)說(shuō),它的成員和其他任何類(lèi)的成員都不是一回事兒譬巫。==
類(lèi)的聲明
就像可以把函數(shù)的聲明和定義分離開(kāi)來(lái)一樣咖楣,我們也能僅僅聲明類(lèi)而暫時(shí)不定義它:
class Screen; //Screen類(lèi)的聲明
這種聲明有時(shí)被稱(chēng)為前向聲明,對(duì)于類(lèi)型Screen來(lái)說(shuō)芦昔,在它聲明之后定義之前是一個(gè)不完全類(lèi)型诱贿。
3.4 友元再探
類(lèi)還可以把其他類(lèi)定義成友元,也可以把其他類(lèi)的成員函數(shù)定義成友元。此外珠十,友元函數(shù)能定義在類(lèi)的內(nèi)部料扰,這樣的函數(shù)是隱式內(nèi)聯(lián)的。
類(lèi)之間的友元關(guān)系
class Screen {
//Window_mgr的成員可以訪問(wèn)Screen類(lèi)的私有部分
friend class Window_mgr;
};
如果一個(gè)類(lèi)指定了友元類(lèi)焙蹭,則友元類(lèi)的成員函數(shù)可以訪問(wèn)此類(lèi)包括非公有成員在內(nèi)的所有成員晒杈。
每個(gè)類(lèi)負(fù)責(zé)控制自己的友元類(lèi)和友元函數(shù)。
令成員函數(shù)作為友元
除了令整個(gè)類(lèi)作為友元之外壳嚎,還可以只為類(lèi)的某個(gè)成員函數(shù)提供訪問(wèn)權(quán)限桐智。
class Screen {
//Window_mgr::clear必須在Screen類(lèi)之前被聲明
friend void Window_mgr::clear(ScreenIndex);
};
函數(shù)重載和友元
盡管重載函數(shù)的名字相同,但它們?nèi)匀皇遣煌暮瘮?shù)烟馅。因此说庭,如果一個(gè)類(lèi)想把一組重載函數(shù)聲明成它的友元,它需要對(duì)這組函數(shù)中的每一個(gè)分別聲明:
//重載的storeOn函數(shù)
extern std::ostream &storeOn(std::ostream &,Screen &);
extern BitMap &storeOn(BitMap &,Screen &);
class Screen {
//storeOn的ostream版本能訪問(wèn)Screen對(duì)象的私有部分
friend std::ostream &storeOn(std::ostream &,Screen &);
//...
};
友元聲明和作用域
類(lèi)和非成員函數(shù)的聲明不是必須在它們的友元之前聲明郑趁。友元聲明的作用是影響訪問(wèn)權(quán)限刊驴,它本身并非普通意義的聲明。
struct X {
friend void f(); /*友元函數(shù)可以定義在函數(shù)的內(nèi)部*/
X() { f(); }; //錯(cuò)誤:f還沒(méi)有被聲明
void g();
void h();
};
void X::g() { return f(); } //錯(cuò)誤:f還沒(méi)有被聲明
void f(); //聲明那個(gè)定義在X中的函數(shù)
void X::h() { return f();} //正確:現(xiàn)在f的聲明在作用域中了
#4. 類(lèi)的作用域
每個(gè)類(lèi)都會(huì)定義它自己的作用域寡润。在類(lèi)的作用域之外捆憎,普通的數(shù)據(jù)和函數(shù)成員只能由對(duì)象、引用或者指針使用成員訪問(wèn)運(yùn)算符來(lái)訪問(wèn)梭纹。對(duì)于類(lèi)類(lèi)型成員則使用作用域運(yùn)算符訪問(wèn)躲惰。
4.1 名字查找與類(lèi)的作用域
在目前為止,我們編寫(xiě)的程序中变抽,名字查找的過(guò)程比較直截了當(dāng):
- 首先础拨,在名字所在的塊中尋找其聲明的語(yǔ)句,只考慮在名字的使用之前出現(xiàn)的聲明绍载。
- 如果沒(méi)找到诡宗,繼續(xù)查找外層作用域。
- 如果最終沒(méi)有找到匹配的聲明击儡,則程序報(bào)錯(cuò)塔沃。
對(duì)于定義在類(lèi)外部的成員函數(shù)來(lái)說(shuō),解析其中名字的方式和上述查找規(guī)則有所區(qū)別阳谍。類(lèi)的定義分兩步處理: - 首先蛀柴,編譯成員的聲明。
- 直到類(lèi)全部可見(jiàn)后才編譯函數(shù)體矫夯。
==編譯器處理完類(lèi)中的全部聲明后才會(huì)處理成員函數(shù)的定義名扛。==
用于類(lèi)成員聲明的名字查找
這種兩階段的處理方式只使用與成員函數(shù)中使用的名字。如果某個(gè)成員的聲明使用了類(lèi)中尚未出現(xiàn)的名字茧痒,則編譯器將在定義該類(lèi)的作用域中繼續(xù)查找肮韧。例如:
typedef double Money;
string bal;
class Account {
public:
Money balance() {
return bal;
}
private:
Money bal;
//...
};
當(dāng)編譯器看到balance函數(shù)的聲明語(yǔ)句時(shí),它將在Account類(lèi)的范圍內(nèi)尋找對(duì)Money的聲明。如果沒(méi)找到弄企,編譯器會(huì)接著到Account的外層作用域中查找超燃。
類(lèi)型名要特殊處理
一般來(lái)說(shuō),內(nèi)層作用域可以重新定義外層作用域中的名字拘领,即使該名字已經(jīng)在內(nèi)層作用域中使用過(guò)意乓。然而在類(lèi)中,如果成員使用了外層作用域中的某個(gè)名字约素,而該名字代表一種類(lèi)型届良,則類(lèi)不能在之后重新定義該名字:
typedef double Money;
class Account {
public:
Money balance() { //使用外層作用域的Money
return bal;
}
private:
typedef double Money; //錯(cuò)誤:不能重新定義Money
Money bal;
};
#5. 構(gòu)造函數(shù)再探
5.1 構(gòu)造函數(shù)初始值列表
如果沒(méi)有在構(gòu)造函數(shù)的初始值列表中顯式地初始化成員,則該成員將在函數(shù)體之前執(zhí)行默認(rèn)初始化圣猎。例如:
//Sales_data構(gòu)造函數(shù)的一種寫(xiě)法士葫,雖然合法但是比較草率:沒(méi)有使用構(gòu)造函數(shù)初始值
Sales_data::Sales_data(const string &s,unsigned cnt,double price) {
bookNo = s;
units_sold = cnt;
revenue = cnt * price;
}
構(gòu)造函數(shù)的初始值有時(shí)必不可少
如果成員是const或者引用的話,必須將其初始化送悔。類(lèi)似的慢显,當(dāng)成員屬于某種類(lèi)類(lèi)型且該類(lèi)沒(méi)有定義默認(rèn)構(gòu)造函數(shù)時(shí),也必須將這個(gè)成員初始化欠啤。例如:
class ConstRef {
public:
ConstRef(int ii) : i(ii),ci(ii),ri(i) {}
private:
int i;
const int ci;
int &ri;
};
和其他常量對(duì)象或者引用一樣荚藻,成員ci和ri都必須被初始化。因此洁段,如果我們沒(méi)有為它們提供構(gòu)造函數(shù)初始值的話將引發(fā)錯(cuò)誤:
ConstRef::ConstRef(int ii) {
//賦值
i = ii; //正確
ci = ii; //錯(cuò)誤:不能給const賦值
ri = ii; //錯(cuò)誤:ri沒(méi)被初始化
}
//正確:顯示地初始化引用和const成員
ConstRef::ConstRef(int ii) :i(ii),ci(ii),ri(i) {}
成員初始化順序
成員的初始化順序與它們?cè)陬?lèi)定義中的出現(xiàn)順序一致:第一個(gè)成員先被初始化应狱,然后第二個(gè),依次類(lèi)推祠丝。構(gòu)造函數(shù)初始值列表中初始值的前后位置關(guān)系不會(huì)影響實(shí)際的初始化順序疾呻。
class X {
private:
int i;
int j;
public:
//i的值為undefined,i在j之前初始化
X(int val) :j(val), i(j) {}
};
==最好令構(gòu)造函數(shù)的初始值順序和成員聲明的順序保持一致。而且如果可能的話纽疟,盡量避免使用成員初始化其他成員。==
默認(rèn)實(shí)參和構(gòu)造函數(shù)
Sales_data默認(rèn)構(gòu)造函數(shù)的行為與只接受一個(gè)string實(shí)參的構(gòu)造函數(shù)差不多憾赁。唯一的區(qū)別是接受string實(shí)參的構(gòu)造函數(shù)使用這個(gè)實(shí)參初始化bookNo污朽,而默認(rèn)構(gòu)造函數(shù)使用string的默認(rèn)構(gòu)造函數(shù)初始化bookNo。
class Sales_data {
public:
//定義默認(rèn)構(gòu)造函數(shù)龙考,令其與只接受一個(gè)string實(shí)參的構(gòu)造函數(shù)功能相同
Sales_data(std::string s = ""):bookNo(s) {}
Sales_data(std::string s,unsigned cnt,double rev):
bookNo(s),units_sold(cnt),revenue(rev*cnt) {}
Sales_data(std::istream &is) {
read(is,*this);
}
};
==如果一個(gè)構(gòu)造函數(shù)為所有參數(shù)都提供了默認(rèn)實(shí)參蟆肆,則它實(shí)際上也定義了默認(rèn)構(gòu)造函數(shù)。==
5.2 委托構(gòu)造函數(shù)
C++11新標(biāo)準(zhǔn)擴(kuò)展了構(gòu)造函數(shù)初始值的功能晦款,使得我們可以定義所謂的委托構(gòu)造函數(shù)炎功。
一個(gè)委托構(gòu)造函數(shù)使用它所屬類(lèi)的其他構(gòu)造函數(shù)執(zhí)行它自己的初始化過(guò)程,或者說(shuō)它把它自己的一些職責(zé)委托給了其他構(gòu)造函數(shù)缓溅。
class Sales_data {
public:
//非委托構(gòu)造函數(shù)使用對(duì)應(yīng)的實(shí)參初始化成員
Sales_data(std::string s,unsigned cnt,double price):
bookNo(s),units_sold(cnt),revenue(cnt*price) {}
//其余構(gòu)造函數(shù)全部委托給另一個(gè)構(gòu)造函數(shù)
Sales_data():Sales_data("", 0, 0) {}
Sales_data(std::string s):Sales_data(s,0,0) {}
Sales_data(std::istream &is):Sales_data() {
read(is,*this);
}
};
5.3 默認(rèn)構(gòu)造函數(shù)的作用
當(dāng)對(duì)象被默認(rèn)初始化或值初始化時(shí)自動(dòng)執(zhí)行默認(rèn)構(gòu)造函數(shù)蛇损。默認(rèn)初始化在以下情況下發(fā)生:
- 當(dāng)我們?cè)趬K作用域內(nèi)不使用任何初始值定義一個(gè)非靜態(tài)變量或數(shù)組時(shí)。
- 當(dāng)一個(gè)類(lèi)本身含有類(lèi)類(lèi)型的成員且使用合成的默認(rèn)構(gòu)造函數(shù)時(shí)。
- 當(dāng)類(lèi)類(lèi)型的成員沒(méi)有在構(gòu)造函數(shù)初始值列表顯式的初始化時(shí)淤齐。
值初始化在以下情況發(fā)生:
- 在數(shù)組初始化的過(guò)程中如果我們提供的初始值數(shù)量少于數(shù)組的大小時(shí)股囊。
- 當(dāng)我們不使用初始值定義一個(gè)局部靜態(tài)變量時(shí)。
- 當(dāng)我們通過(guò)書(shū)寫(xiě)表達(dá)式顯示地請(qǐng)求值初始化時(shí)更啄,其中T是類(lèi)型名稚疹。
class NoDefault {
public:
NoDefault(const std::string&);
};
struct A {
NoDefault my_mem; //默認(rèn)情況下my_mem是public的
};
A a; //錯(cuò)誤:不能為A合成構(gòu)造函數(shù)
struct B {
B(){} //錯(cuò)誤:b_member沒(méi)有初始值
NoDefault b_member;
};
==在實(shí)際中,如果定義了其他構(gòu)造函數(shù)祭务,那么最好也提供一個(gè)默認(rèn)構(gòu)造函數(shù)内狗。==
使用默認(rèn)構(gòu)造函數(shù)
Sales_data obj(); //錯(cuò)誤:聲明一個(gè)函數(shù)而非對(duì)象
Sales_data obj2; //正確:obj2是一個(gè)對(duì)象而非函數(shù)
5.4 隱式的類(lèi)類(lèi)型轉(zhuǎn)換
如果構(gòu)造函數(shù)只接受一個(gè)實(shí)參,則它實(shí)際上定義了轉(zhuǎn)換為此類(lèi)類(lèi)型的隱式轉(zhuǎn)換機(jī)制义锥,有時(shí)我們把這種構(gòu)造函數(shù)稱(chēng)為轉(zhuǎn)換構(gòu)造函數(shù)柳沙。
只允許一步類(lèi)類(lèi)型轉(zhuǎn)換
編譯器只會(huì)自動(dòng)地執(zhí)行一步類(lèi)型轉(zhuǎn)換。例如缨该,因?yàn)橄旅娴拇a隱式地使用了兩種轉(zhuǎn)換規(guī)則偎行,所以它是錯(cuò)誤的:
//錯(cuò)誤:需要用戶(hù)定義的兩種轉(zhuǎn)換:
//(1)把“9-999-99999-9”轉(zhuǎn)換成string
//(2)再把這個(gè)臨時(shí)的string轉(zhuǎn)換成Sales_data
item.combine("9-999-99999-9");
如果想完成上述調(diào)用,可以顯示地把字符串轉(zhuǎn)換成string或者Sales_data對(duì)象:
//正確:顯示地轉(zhuǎn)換成string贰拿,隱式地轉(zhuǎn)換成Sales_data
item.combine(string("9-999-99999-9"));
//正確:隱式地轉(zhuǎn)換成string蛤袒,顯示地轉(zhuǎn)換成Sales_data
item.combine(Sales_data("9-999-99999-9"));
抑制構(gòu)造函數(shù)定義的隱式轉(zhuǎn)換
在要求隱式轉(zhuǎn)換的程序上下文中,我們可以通過(guò)將構(gòu)造函數(shù)聲明為explicit加以阻止:
class Sales_data {
public:
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p*n) {}
explicit Sales_data(const std::string &s): bookNo(s) {}
explicit Sales_data(std::istream &);
private:
std::string bookNo;
unsigned units_sold;
double revenue;
};
此時(shí)膨更,沒(méi)有任何構(gòu)造函數(shù)能用于隱式地創(chuàng)建Sales_data對(duì)象妙真,之前的兩種用法都無(wú)法通過(guò)編譯:
item.combine(null_book); //錯(cuò)誤:string構(gòu)造函數(shù)是explicit的
item.combine(cin); //錯(cuò)誤:istream構(gòu)造函數(shù)是explicit的
關(guān)鍵字explicit只對(duì)一個(gè)實(shí)參的構(gòu)造函數(shù)有效。需要多個(gè)實(shí)參的構(gòu)造函數(shù)不能用于執(zhí)行隱式轉(zhuǎn)換荚守,所以無(wú)須將這些構(gòu)造函數(shù)指定為explicit的珍德。只能在類(lèi)內(nèi)聲明構(gòu)造函數(shù)時(shí)使用explicit關(guān)鍵字,在類(lèi)外部定義時(shí)不應(yīng)重復(fù):
//錯(cuò)誤:explicit關(guān)鍵字只允許出現(xiàn)在類(lèi)內(nèi)的構(gòu)造函數(shù)聲明處
explicit Sales_data::Sales_data(istream& is) {
read(is,*this);
}
explicit構(gòu)造函數(shù)只能用于直接初始化
發(fā)生隱式轉(zhuǎn)換的一種情況是當(dāng)我們執(zhí)行拷貝形式的初始化時(shí)矗漾。此時(shí)锈候,我們只能使用直接初始化而不能使用explicit構(gòu)造函數(shù):
//正確:直接初始化
Sales_data item1(null_book);
//錯(cuò)誤:不能將explicit構(gòu)造函數(shù)用于拷貝形式的初始化過(guò)程
Sales_data item2 = null_book;
==當(dāng)我們用explicit關(guān)鍵字聲明構(gòu)造函數(shù)時(shí),它將只能以直接初始化的形式使用敞贡。而且泵琳,編譯器將不會(huì)在自動(dòng)轉(zhuǎn)換的過(guò)程中使用該構(gòu)造函數(shù)。==
為轉(zhuǎn)換顯示地使用構(gòu)造函數(shù)
盡管編譯器不會(huì)將explicit的構(gòu)造函數(shù)用于隱式轉(zhuǎn)換過(guò)程誊役,但是我們可以使用這樣的構(gòu)造函數(shù)顯示地強(qiáng)制進(jìn)行轉(zhuǎn)換:
//正確:實(shí)參是一個(gè)顯示構(gòu)造的Sales_data對(duì)象
item.combine(Sales_data(null_book));
//正確:static_cast可以使用explicit的構(gòu)造函數(shù)
item.combine(static_cast<Sales_data>(cin));
5.5 聚合類(lèi)
聚合類(lèi)使得用戶(hù)可以直接訪問(wèn)其成員获列,并且具有特殊的初始化語(yǔ)法形式。當(dāng)一個(gè)類(lèi)滿足如下條件時(shí)蛔垢,我們說(shuō)它是聚合的:
- 所有成員都是public的击孩。
- 沒(méi)有定義任何構(gòu)造函數(shù)。
- 沒(méi)有類(lèi)內(nèi)初始值鹏漆。
- 沒(méi)有基類(lèi)巩梢,也沒(méi)有virtual函數(shù)创泄。
例如,下面類(lèi)是一個(gè)聚合類(lèi):
strcuct Data {
int ival;
string s;
};
5.6 字面值常量類(lèi)
除了算術(shù)類(lèi)型且改、引用和指針外验烧,某些類(lèi)也是字面值類(lèi)型。和其他類(lèi)不同又跛,字面值類(lèi)型的類(lèi)可能含有constexpr函數(shù)成員碍拆。這樣的成員必須符合constexpr函數(shù)的所有要求,它們是隱式const的慨蓝。
數(shù)據(jù)成員都是字面值類(lèi)型的聚合類(lèi)是字面值常量類(lèi)感混。如果一個(gè)類(lèi)不是聚合類(lèi),但它符合下述要求礼烈,則它也是一個(gè)字面值常量類(lèi):
- 數(shù)據(jù)成員都必須是字面值類(lèi)型弧满。
- 類(lèi)必須含有一個(gè)constexpr構(gòu)造函數(shù)。
- 如果一個(gè)數(shù)據(jù)成員含有類(lèi)內(nèi)初始值此熬,則內(nèi)置類(lèi)型成員的初始值必須是一條常量表達(dá)式庭呜;或者如果成員屬于某種類(lèi)類(lèi)型,則初始值必須使用成員自己的constexpr構(gòu)造函數(shù)犀忱。
- 類(lèi)必須使用析構(gòu)函數(shù)的默認(rèn)定義募谎,該成員負(fù)責(zé)銷(xiāo)毀類(lèi)的對(duì)象。
constexpr構(gòu)造函數(shù)
盡管構(gòu)造函數(shù)不能是const的阴汇,但是字面值類(lèi)型的構(gòu)造函數(shù)可以是constexpr函數(shù)数冬。事實(shí)上,一個(gè)字面值常量類(lèi)必須至少提供一個(gè)constexpr構(gòu)造函數(shù)搀庶。
class Debug {
public:
constexpr Debug(bool b = true) :hw(b),io(b),other(b) {}
constexpr Debug(bool h,bool i,bool o) :
hw(h),io(i),other(o) {}
constexpr bool any() {
return hw||io||other;
}
void set_io(bool b) {
io = b;
}
void set_hw(bool b) {
hw = b;
}
void set_other(bool b) {
hw = b;
}
private:
bool hw;
bool io;
bool other;
};
constexpr構(gòu)造函數(shù)必須初始化所有數(shù)據(jù)成員拐纱,初始值或者使用constexpr構(gòu)造函數(shù),或者是一條常量表達(dá)式哥倔。
#6. 類(lèi)的靜態(tài)成員
有的時(shí)候類(lèi)需要它的一些成員與類(lèi)本身直接相關(guān)秸架,而不是與類(lèi)的各個(gè)對(duì)象保持關(guān)聯(lián)。
聲明靜態(tài)成員
我們通過(guò)在成員的聲明之前加上關(guān)鍵字static使得其與類(lèi)關(guān)聯(lián)在一起咆蒿。和其他成員一樣东抹,靜態(tài)成員可以是public的或private的。靜態(tài)數(shù)據(jù)成員的類(lèi)型可以是常量蜡秽、引用府阀、指針缆镣、類(lèi)類(lèi)型等芽突。
class Account {
public:
void caculate() {
amount += amount * interestRate;
}
static double rate() {
return interestRate;
}
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
類(lèi)的靜態(tài)成員存在于任何對(duì)象之外,對(duì)象中不包含任何與靜態(tài)數(shù)據(jù)成員有關(guān)的數(shù)據(jù)董瞻。
類(lèi)似的寞蚌,靜態(tài)成員函數(shù)也不與任何對(duì)象綁定在一起田巴,它們不包含this指針。作為結(jié)果挟秤,靜態(tài)成員函數(shù)不能聲明成const的壹哺,而且我們不能在static函數(shù)體內(nèi)使用this指針。這一限制即適用于this的顯式使用艘刚,也對(duì)調(diào)用非靜態(tài)成員的隱式使用有效管宵。
使用靜態(tài)成員
我們使用作用域運(yùn)算符直接訪問(wèn)靜態(tài)成員:
double r;
r = Account::rate(); //使用作用域運(yùn)算符訪問(wèn)靜態(tài)成員
定義靜態(tài)成員
和其他的成員函數(shù)一樣,我們既可以在類(lèi)的內(nèi)部也可以在內(nèi)的外部定義靜態(tài)成員函數(shù)攀甚。當(dāng)在類(lèi)的外部定義靜態(tài)成員時(shí)箩朴,不能重復(fù)static關(guān)鍵字,該關(guān)鍵字只出現(xiàn)在類(lèi)內(nèi)部的聲明語(yǔ)句:
void Account::rate(double newRate) {
interestRate = newRate;
}
==和類(lèi)的所有成員一樣秋度,當(dāng)我們指向類(lèi)外部的靜態(tài)成員時(shí)炸庞,必須指明成員所屬的類(lèi)名。static關(guān)鍵字則只出現(xiàn)在類(lèi)內(nèi)部的聲明語(yǔ)句中荚斯。==
靜態(tài)成員的類(lèi)內(nèi)初始化
通常情況下埠居,類(lèi)的靜態(tài)成員不應(yīng)該在類(lèi)的內(nèi)部初始化。然而事期,我們可以為靜態(tài)成員提供const整數(shù)類(lèi)型的類(lèi)內(nèi)初始值滥壕,不過(guò)要求靜態(tài)成員必須是字面值常量類(lèi)型的constexpr。
class Account {
public:
static double rate() {
return interestRate;
}
static void rate(double);
private:
static constexpr int period = 30; //period是常量表達(dá)式
double daily_tbl[period];
};