bensonhuangtw / cpp-primier-chinese-notes Goto Github PK
View Code? Open in Web Editor NEW本專案大部分內容為筆者從《C++ Primer ,5th Edition》閱讀後所自行整理之學習筆記(繁體中文),建議閱讀前有一點C語言的基礎知識(指標、變數、流程控制和函式等基礎概念即可)。
License: MIT License
本專案大部分內容為筆者從《C++ Primer ,5th Edition》閱讀後所自行整理之學習筆記(繁體中文),建議閱讀前有一點C語言的基礎知識(指標、變數、流程控制和函式等基礎概念即可)。
License: MIT License
以下為本章所使用的例子。
物件(object)的使用:
Sales_data total; // variable to hold the running sum
if (read(cin, total)) { // read the first transaction
Sales_data trans; // variable to hold data for the next transaction
while(read(cin, trans)) { // read the remaining transactions
if (total.isbn() == trans.isbn()) // check the isbns
total.combine(trans); // update the running total
else {
print(cout, total) << endl; // print the results
total = trans; // process the next book
}
}
print(cout, total) << endl; // print the last transaction
} else { // there was no input
cerr << "No data?!" << endl; // notify the user
}
類別(class)的架構:
struct Sales_data {
// new members: operations on Sales_data objects
std::string isbn() const { return bookNo; }
Sales_data& combine(const Sales_data&);
double avg_price() const;
// data members are unchanged from § 2.6.1 (p. 72)
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// nonmember Sales_data interface functions
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
類別所有的成員都必須在類別裡面宣告,然而成員函數可以定義於類別的body之外。
成員函式定義範例:
std::string isbn() const { return bookNo; }
當我們呼叫物件total
的成員函式isbn()
:
total.isbn()
除了7.6提到的例外之外,我們是以該物件的身分去呼叫成員函式的。當isbn
在參閱Sales_data
的成員(e.g. bookNo
)的時候,它暗中參考的是用來呼叫該函式之物件(此例中為total
)的成員,在本次呼叫中,當isbn
回傳bookNo
時,它其實回傳的是total.bookNo
。成員函式透過一個隱藏參數this來獲得呼叫函式之物件的成員,當我們呼叫一個成員函式,this
就會被呼叫該函數之物件的位址所初始化,也就是說在本例中,編譯器將total
的位址暗中傳給isbn
中的this
,可以想像成:
// pseudo-code illustration of how a call to a member function is translated
Sales_data::isbn(&total)
在成員函數中,我們可以直接參照該物件的成員而不用透過member access operator,所有對成員的直接使用其實都是暗中透過this
,也就是說當isbn
使用bookNo
的時候,它暗中使用了this
所指向的成員,就像是被寫成
std::string isbn() const { return this->bookNo; }
由於this
就是想參照該物件本身,因此this
是一個const pointer。
默認情況下,this
是一個指向nonconst
class type的const
pointer,因此當物件為const
的時候,將該物件綁定到this
是錯誤的,這代表著我們不能呼叫它一般的成員函式(因為它們的隱藏版參數this
無法被初始化),想要排除這個問題,我們希望能把this
宣告成const Sales_data *const
,然而this
是隱藏起來的,所以C++提供的解決辦法就是讓我們在函式參數列的後方加上const
,這表示說this
是一個pointer to const
,用這種方式的成員函式我們稱作為cosnt member function,也就是說我們可以把isbn
想成:
// pseudo-code illustration of how the implicit this pointer is used
// this code is illegal: we may not explicitly define the this pointer ourselves
// note that this is a pointer to const because isbn is a const member
std::string Sales_data::isbn(const Sales_data *const this)
{ return this->isbn; }
然而當我們這麼做,就代表isbn
只能讀取而無法改寫該物件的成員函數了。
Note
const
的物件或是對cosnt
物件的pointer或reference只能使用const
member functions。
當我們在類別外面定義物件時,必須包含該類別的名稱:
double Sales_data::avg_price() const {
if (units_sold)
return revenue/units_sold;
else
return 0;
}
combine
函式想要如同內建的+=
一樣運作,一般來講,當我們定義類似於內建運算子的函式時,我們應該模仿該運算子的行為,內建的+=
會回傳它左側的運算元,而且為左值,因此combine
回傳的必須是一個reference,由於它左邊的運算元是Sales_data
物件,它的回傳型別應該是Sales_data&
,因此我們透過dereference this
來獲得該物件的reference:
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
units_sold += rhs.units_sold; // add the members of rhs into
revenue += rhs.revenue; // the members of ''this'' object
return *this; // return the object on which the function was called
}
那些概念上是類別的一部份,但實際上卻定義在類別之外的函數通常都會宣告(但不定義)於和該類別同一個的標頭檔中。
// input transactions contain ISBN, number of copies sold, and sales price
istream &read(istream &is, Sales_data &item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream &print(ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();
return os;
}
有兩個值得注意的地方,首先read
和print
分別使用了reference to對應的IO class type,然而IO class是無法被複製的,因此它們被passed by reference,此外由於讀跟寫入一個stream會改變該stream,因此兩個函式都使用一般的references,而非reference to const。第二個點是print
最後並沒有換行,一般來說用來當輸出的函式會在格式上做最少的限制,這是為了讓使用者能自己決定是否要換行。
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sum = lhs; // copy data members from lhs into sum
sum.combine(rhs); // add data members from rhs into sum
return sum;
}
在第三行我們把sum
初始化為lhs
的副本,而默認下,複製一個物件會複製該物件的成員,所以說sum
的bookNo
、units_soldu
以及revenue
成員值會和lhs的一樣,接下來我們呼叫combine
把rhs
的成員值加進sum
的成員值,完成後,最終回傳sum
的副本。
constructors負責定義class物件的data member如何被初始化,它是class內部特殊的成員函數,名子與class本身一樣,但沒有回傳型別,而且constructors不能被聲明成const member function(見7.1.2)。
class用default constructor這種特殊的constructor來默認初始化,而且它沒有參數。如果我們沒在class裡面明確(explicitly)定義constructor,則編譯器會暗自(implicity)定義默認constructor,這種default constructor我們稱作synthesized default constructor,大部分的class中,synthesized default constructor會照下面的規則初始化data member:
(1) 如果有in-class initializer就用它來初始化成員。
(2) 如果沒有in-class initializer,則照default-initialize的規則初始化成員。
e.g.
Sales_data
有提供in-class initializer給units_sold
跟 revenue
,synthesized default constructor會用它們來初始化這些成員,而bookNo
則會被默認初始化成空字串。
只有很簡單的class才會依賴synthesized default constructor,否則基於下面的主要理由,class必須定義自己的default constructor:
(1) 編譯器只有在沒有其他constructor定義的情況下才會產生synthesized default constructor,如果已經有定義其他constructor則除非我們自己定義default constructor,否則不會有。
(2) 有些默認初始化會產生一些問題(見2.2.1),可能會產生未定義的值。
(3) 有些類別可能根本無法產生synthesized default constructor,比如說該類別有一個成員是class type但它根本就沒有default constructor時,那編譯器根本無法將那個成員初始化,因此需要定義default constructor。
struct Sales_data {
// constructors added
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::istream &);
// other members as before
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() = default;
其實就是在叫default constructor做synthesized default constructor會做的事情。
Warning
如果你的編譯器不支援in-class initializers,你的default constructor應該要使用下面所說的constructor initializer list
接下來看另外兩個被定義於class裡面的constructors:
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) { }
從上面的冒號與花括號之間的部分稱為constructor initializer list,它標明了該物件一個或多個data member在被創建時的初始值,其語法是成員名稱加上包含該成員初始值的括號(或花括號),如果某個成員在constructor initializer list被忽略,則會依照synthesized default constructor的規則初始化。
Sales_data::Sales_data(std::istream &is)
{
read(is, *this); // read will read a transaction from is into this object
}
如我要在class外面定義constructor,就必須標明該class的名稱,雖然在本次的定義中constructor initializer list是空的,然而該物件的成員卻仍在constructor body執行前就已經被初始化了(透過in-class initializer或默認初始化),之後才藉由read
來修改成員的值。
出現時機:
(1) copy
(a) 初始化變數(見13.1.1)
(b) 藉由pass by value傳入或回傳物件
(2) assign:使用assignment operator
(3) destroy:當物件不再存在時,例如local object在離開它被創造的block時。
如果我們沒有定義以上這些運算,則編譯器會替我們合成,舉例來說如果我們使用:
total = trans; // process the next book
則它像是在執行:
// default assignment for Sales_data is equivalent to:
total.bookNo = trans.bookNo;
total.units_sold = trans.units_sold;
total.revenue = trans.revenue;
在13章我們會學到如何定義這些運算。
使用動態記憶體管理的類別通常無法依靠編譯器提供的操作,不過如果是有用到vector
或是string
的則能正確的進行操作。
access specifiers:
public
:可供整個程式使用的部分,定義了類別的介面(interface)
private
:僅供成員函數使用,無法被類別使用者用,將實作給封裝(Encapsulation)
一個類別可以有多個access specifiers,也可以沒有,某個access specifiers的作用維持到下一個access specifiers出現為止。
e.g.
class Sales_data {
public: // access specifier added
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data(const std::string &s): bookNo(s) { }
Sales_data(std::istream&);
std::string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data&);
private: // access specifier added
double avg_price() const
{ return units_sold ? revenue/units_sold : 0; }
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
class
與struct
的唯一差別在於default access level,如果使用struct
,則在第一個access specifiers出現前所定義的成員為public
,如果使用class
,則為private
。
當我們把Sales_data
的部分成員設為private
後,read
以及print
等函式就不能被編譯了,這是因為這些函式並非Sales_data
的成員,無法使用其private
成員,為了讓一個類別能夠允許其他類別或函式使用它的非public
成員,我們將它令為friend
,即透過引進該函式的宣告並在前面加上friend
:
class Sales_data {
// friend declarations for nonmember Sales_data operations added
friend Sales_data add(const Sales_data&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
friend std::ostream &print(std::ostream&, const Sales_data&);
// other members and access specifiers as before
public:
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data(const std::string &s): bookNo(s) { }
Sales_data(std::istream&);
std::string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data&);
private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// declarations for nonmember parts of the Sales_data interface
Sales_data add(const Sales_data&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
friend
宣告只能出現在class的定義裡面(任意的位子)。
friend宣告並非一般的宣告,如果想要使用者能夠呼叫它們的話,我們必須在friend宣告之外再對函式做宣告。
Note
很多編譯器不會強制你在class裡面對某個函式做friend宣告前就必須先在class外面先宣告該函式,不過還是建議這麼做以防編譯環境改變。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.