Classes
12.1. 類的定義和聲明 Class Definitions and Declarations
類定義
大師僅僅寫(xiě)了這么一個(gè)類定義的簡(jiǎn)單的例子,卻有這么多的東西可以說(shuō)說(shuō)啊
class Sales_item {
public:
// operations on Sales_item objects
double avg_price() const;
bool same_isbn(const Sales_item &rhs) const
{ return isbn == rhs.isbn; }
// default constructor needed to initialize members of built-in type
Sales_item(): units_sold(0), revenue(0.0) { }
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
double Sales_item::avg_price() const
{
if (units_sold)
return revenue/units_sold;
else
return 0;
}
|
· Member: data, functions or type definitions.
· 構(gòu)造函數(shù)Constructors初始化:
// default constructor needed to initialize members of built-in type
Sales_item(): units_sold(0), revenue(0.0) { }
|
這種寫(xiě)法是初始化內(nèi)置數(shù)據(jù)類型。
· const:表示functions不會(huì)修改數(shù)據(jù)成員。這可以用在查詢或者判斷類型的functions上。
· 成員函數(shù)member functions必須在類的內(nèi)部聲明,成員函數(shù)的定義是否放在類定義里面是可選的。如果放在類里面,那么
數(shù)據(jù)抽象和封裝Data Abstraction and Encapsulation
類背后蘊(yùn)涵的基本思想是數(shù)據(jù)抽象和封裝。(The fundamental ideas behind classes are data abstraction and encapsulation.)數(shù)據(jù)抽象這種技術(shù)依賴于接口和實(shí)現(xiàn)的分離。(Data abstraction is a programming (and design) technique that relies on the separation of interface and implementation.)
多么精煉的話,包含了面向?qū)ο蟮纳羁毯x。大師啊。
類的用戶(user of class)是誰(shuí)?程序員programmer。user of class就如同user of application。
類的用戶最關(guān)心啥?最關(guān)心的是接口。
關(guān)于類定義的更多內(nèi)容 more on class definitions
使用Typedef簡(jiǎn)化類
class Screen {
public:
// interface member functions
typedef std::string::size_type index;
private:
std::string contents;
index cursor;
index height, width;
};
|
注意:
typedef是放在public部分的,這樣Screen的用戶都能夠使用這個(gè)typedef。
成員函數(shù)可被重載Member Functions May Be Overloaded
顯式指定 inline 成員函數(shù)Explicitly Specifying inline Member Functions
成員函數(shù)定義為inline。這個(gè)定義即可以在函數(shù)聲明里,也可以在類定義之外。在類聲明和類定義里把函數(shù)指定為inline都是合法的。(It is legal to specify inline both on the declaration and definition.)
類聲明與類定義
類定義一般要放在頭文件中。
類聲明后,即使沒(méi)有類定義,也可以作為引用或者指針來(lái)使用:
class LinkScreen {
Screen window;
LinkScreen *next;
LinkScreen *prev;
};
|
LinkScreen定義完成之前,我們就定義了指向LinkScreen類型的指針。
這里引入了兩個(gè)基本感念:
前向聲明forward declaretion:
class Screen; //definition of the Screen class
|
這就是一個(gè)前向聲明。表示Screen是一個(gè)類類型(class type)。
不完全類型incompete type:在前向聲明之后,類定義之前Screen就是一個(gè)不完全類型。意思是說(shuō)知道Screen是個(gè)類型,但是不知道這個(gè)類型包括那些成員(members)。不完全類型只能用在有限的幾種方式,它可以用來(lái)定義這種類型的指針或者是引用,也可以用在把這種類類型作為形參或者返回值類型的函數(shù)聲明。(An incomplete type may be used to define only pointers or references to the type or to declare (but not define) functions that use the type as a paremeter or return type.)
在創(chuàng)建某個(gè)類類型的對(duì)象前,這個(gè)類類型必須完整定義。這樣編譯器餐能夠知道要為這種類型的對(duì)象保留多大的存儲(chǔ)空間。
12.2 隱含的this指針 The Implicit this Pointer
何時(shí)使用this?
簡(jiǎn)單的說(shuō),就是當(dāng)我們需要引用整個(gè)對(duì)象而不是對(duì)象中的成員的時(shí)候。最常見(jiàn)的情況是在返回值是被調(diào)用的對(duì)象的引用的函數(shù)里。(when we need to refer to the object as a whole rather than to a member of the object. The most common case where we must use this is in functions that return a reference to the object on which they were invoked.)
this是指針,因此如果返回值是當(dāng)前對(duì)象的引用,那么返回語(yǔ)句一定是:
剛開(kāi)始看到這個(gè)的時(shí)候,總是和Java搞混,因?yàn)?/span>Java是沒(méi)有指針,所以返回的只能是this,顯式使用的時(shí)候也是this。
從const成員函數(shù)里返回*this
這里有兩個(gè)規(guī)則:
- 對(duì)于一般的非const成員函數(shù),this的類型是一個(gè)const pointer。這意味我們可以修改this指向的對(duì)象的值,但是不能修改this保存的地址。(In an ordinary nonconst member function, the type of this is a const pointer to the class type. We may change the value to which this points but cannot change the address that this holds.)
- 對(duì)于const成員函數(shù),this的類型是一個(gè)指向const 對(duì)象的const pointer。這意味我們既不可以修改this指向的對(duì)象的值,也不能修改this保存的地址。(In a const member function, the type of this is a const pointer to a const class-type object. We may change neither the object to which this points nor the address that this holds.)
基于const的重載
可以定義兩個(gè)操作,一個(gè)是const,另一個(gè)不是,這也算是重載。
class Screen {
public:
// interface member functions
// display overloaded on whether the object is const or not
Screen& display(std::ostream &os)
{ do_display(os); return *this; }
const Screen& display(std::ostream &os) const
{ do_display(os); return *this; }
private:
// single function to do the work of displaying a Screen,
// will be called by the display operations
void do_display(std::ostream &os) const
{ os << contents; }
// as before
};
|
這樣const對(duì)象使用const成員函數(shù);非const對(duì)象可以使用任一成員函數(shù),不過(guò)最好還是使用非const的成員函數(shù)。
對(duì)比下面的例子(基于指針形參是否指向const的函數(shù)重載):
Record lookup(Account&);
Record lookup(const Account&); // new function
|
可變數(shù)據(jù)成員Mutable Data Members
即使對(duì)象是const,有時(shí)我們也希望其中的某些數(shù)據(jù)成員是可以修改的,這就引出了mutable。
可變數(shù)據(jù)成員絕不是const,即使對(duì)象是const。這樣const的成員函數(shù)也是可以修改可變數(shù)據(jù)成員的。
定義:
class Screen {
public:
// interface member functions
private:
mutable size_t access_ctr; // may change in a const members
// other data members as before
};
|
12.3 類的作用范圍
12.4構(gòu)造函數(shù)Constructors
構(gòu)造函數(shù)不能聲明為const。
建議的方法:使用構(gòu)造函數(shù)初始化式Constructor Initializer。
// recommended way to write constructors using a constructor initializer
Sales_item::Sales_item(const string &book):
isbn(book), units_sold(0), revenue(0.0) { }
|
Constructor Initializer
以下是正確的寫(xiě)法:
// recommended way to write constructors using a constructor initializer
Sales_item::Sales_item(const string &book):
isbn(book), units_sold(0), revenue(0.0) { }
|
但是這種寫(xiě)法乍一看似乎和下面不使用Constructor Initializer的效果一樣:
// legal but sloppier way to write the constructor:
// no constructor initializer
Sales_item::Sales_item(const string &book)
{
isbn = book;
units_sold = 0;
revenue = 0.0;
}
|
NO,NO,不一樣。后一種寫(xiě)法那就像郭德綱相聲說(shuō)的房都燒沒(méi)了,就剩下個(gè)防盜門,于謙還開(kāi)門進(jìn)去。isbn的初始化是在構(gòu)造函數(shù)執(zhí)行之前完成的。上面這個(gè)寫(xiě)法先用缺省的string的構(gòu)造函數(shù)初始化isbn,在構(gòu)造函數(shù)的函數(shù)體執(zhí)行時(shí),isbn又重新賦值為book。沒(méi)效率啊!但是對(duì)于內(nèi)置類型來(lái)說(shuō),兩種方式無(wú)論是在結(jié)果上還是在性能上又都是沒(méi)有區(qū)別的。
構(gòu)造函數(shù)的執(zhí)行分成兩個(gè)步驟:
- 初始化階段,類的數(shù)據(jù)成員是在這個(gè)階段初始化的。
- 一般計(jì)算階段,這就是指構(gòu)造函數(shù)函數(shù)體的執(zhí)行。
在計(jì)算階段前初始化階段就開(kāi)始了。(Initialization happens before the computation phase begins.)
在一些特定的情況下必須使用Constructor Initializer,這些特殊的情況包括:
- 數(shù)據(jù)成員沒(méi)有缺省的構(gòu)造函數(shù)
- 數(shù)據(jù)成員是const或者是引用
for example:
// legal but sloppier way to write the constructor:
// no constructor initializer
class ConstRef {
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
// no explicit constructor initializer: error ri is uninitialized
ConstRef::ConstRef(int ii)
{ // assignments:
i = ii; // ok
ci = ii; // error: cannot assign to a const
ri = i; // assigns to ri which was not bound to an object
}
|
正確寫(xiě)法:只有用constructor initializer才有機(jī)會(huì)對(duì)const和引用進(jìn)行初始化。
ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }
|
成員初始化順序
成員的初始化順序就是成員的定義的順序。第一個(gè)定義的成員第一個(gè)初始化,然后是下一個(gè),以此類推。(The order in which members are initialized is the order in which the members are defined. The first member is initialized first, then the next, and so on.)
類類型的數(shù)據(jù)成員的初始化Initializers for Data Members of Class Type
Constructor Initializer初始化式使用類的某一個(gè)構(gòu)造函數(shù)來(lái)完成。
// alternative definition for Sales_item default constructor
Sales_item(): isbn(10, '9'), units_sold(0), revenue(0.0) {}
|
缺省構(gòu)造函數(shù)
只有在類沒(méi)有定義任何一個(gè)構(gòu)造函數(shù)時(shí),編譯器才會(huì)自動(dòng)生成缺省的構(gòu)造函數(shù)。
隱式類類型轉(zhuǎn)換 Implicit Class-Type Conversions
12.5. 友元Friends
友元就是允許其它特定的類和方法來(lái)訪問(wèn)它的非public的成員。(The friend mechanism allows a class to grant access to its nonpublic members to specified functions or classes.)
友元定義:
class Screen {
// Window_Mgr members can access private parts of class Screen
friend class Window_Mgr;
// ...rest of the Screen class
};
|
友元不是類的成員,但是他們是類的接口的組成部分。(even if they are not members of the class, they are "part of the interface" to the class.)
友元可以是:
l 類
l 類的成員函數(shù)
class Screen {
// Window_Mgr must be defined before class Screen
friend Window_Mgr&
Window_Mgr::relocate(Screen::index r, Screen::index c,
Screen& s);
// ...rest of the Screen class
}
|
l 一般的非成員函數(shù)
友元聲明和范圍
友元聲明和友元定義之間存在的相互依賴關(guān)系使得我們小心地構(gòu)造類。再看上面的例子:
Window_Mgr必須要的Screen前定義。但是成員方法relocate要在Screen定義后才能定義。靠,這就像是先有蛋還是先有雞一樣擰巴。
class Screen {
// Window_Mgr must be defined before class Screen
friend Window_Mgr&
Window_Mgr::relocate(Screen::index r, Screen::index c,
Screen& s);
// ...rest of the Screen class
}
|
12.6. static 類成員static Class Members
這句話是對(duì)static class member的最重要的解釋:
靜態(tài)數(shù)據(jù)成員獨(dú)立于類的任何對(duì)象而存在,每個(gè)靜態(tài)數(shù)據(jù)成員是和類相關(guān)的對(duì)象,而不是和類的對(duì)象相關(guān)。(a static data member exists independently of any object of its class; each static data member is an object associated with the class, not with the objects of that class.)
使用靜態(tài)數(shù)據(jù)成員的好處
l static成員的名字的作用范圍是類,因此可以避免和其它的類的成員或者全局對(duì)象的名字的沖突。
l 強(qiáng)制進(jìn)行封裝。static成員可以是private的,而全局對(duì)象是不可以的。
l 如果把static成員關(guān)聯(lián)到某個(gè)特殊的類上,這樣易于閱讀代碼。
靜態(tài)成員函數(shù)static member function
在靜態(tài)成員函數(shù)(static member function)中是不能使用this指針的。這其實(shí)很好理解,因?yàn)?/span>this指針指向的是對(duì)象,而靜態(tài)成員函數(shù)并不屬于任何的對(duì)象。
靜態(tài)數(shù)據(jù)成員static data members
靜態(tài)數(shù)據(jù)成員可以是任何的類型:const,引用reference,數(shù)組,指針類。
class Account {
public:
// interface functions here
void applyint() { amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
std::string owner;
double amount;
static double interestRate; //這是聲明
static double initRate();
};
// define and initialize static class member,這是定義
double Account::interestRate = initRate();
|
以上是一個(gè)完整的static的定義,要注意以下幾點(diǎn):
1. 靜態(tài)數(shù)據(jù)成員要在類定義體之外來(lái)定義。(static data members must be defined (exactly once) outside the class body.)
2. 靜態(tài)成員不能通過(guò)構(gòu)造函數(shù)進(jìn)行初始化,而是在定義的時(shí)候就完成了初始化。(static members are not initialized through the class constructor(s) and instead should be initialized when they are defined.)
3. 關(guān)鍵字static只是用在類體內(nèi)的成員聲明,而當(dāng)在類的外面的對(duì)靜態(tài)數(shù)據(jù)成員進(jìn)行定義時(shí),就不要再使用關(guān)鍵字static。(The static keyword, however, is used only on the declaration inside the class body. Definitions are not labeled static.)
整型類型的靜態(tài)數(shù)據(jù)成員可以在類定義體內(nèi)直接定義,只要是初始化值是常量表達(dá)式。
a const static data member of integral type can be initialized within the class body as long as the initializer is a constant expression
period的定義就是這樣:
class Account {
public:
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
static const int period = 30; // interest posted every 30 days
double daily_tbl[period]; // ok: period is constant expression
};
// definition of static member with no initializer;
// the initial value is specified inside the class definition
const int Account::period;
|
注意:雖然period在類定義體內(nèi)完成了初始化,但是還是要在類定義之外定義數(shù)據(jù)成員。(When a const static data member is initialized in the class body, the data member must still be defined outside the class definition.)
靜態(tài)成員不是類對(duì)象的一部分
也正因?yàn)槿绱耍瑢?duì)于非靜態(tài)成員的非法的使用方式,對(duì)于靜態(tài)成員來(lái)說(shuō)就是合法的。例如:靜態(tài)成員的類型可以是它所屬類的類型。非靜態(tài)數(shù)據(jù)成員被嚴(yán)格限制只能是它所屬類的對(duì)象的引用或者是指針。(the type of a static data member can be the class type of which it is a member. A nonstatic data member is restricted to being declared as a pointer or a reference to an object of its class:)
class Bar {
public:
// ...
private:
static Bar mem1; // ok
Bar *mem2; // ok
Bar mem3; // error
};
|