C++自修入門實境秀、C++ Primer 5版研讀秀 38/ ~v7類別-Defining a Function to Return “Thi...
preprocessor macro
Preprocessor facility that behaves like an inline function. Aside from assert , modern C++ programs make very little use of preprocessor macros.
5:00
() operator 函式呼叫運算子
Call operator. Executes a function. The name of a function or a
function pointer precedes the parentheses, which enclose a (possibly empty)
comma-separated list of arguments.
const
point 關鍵概念就是只要不想被改動,就用const來說明或註記 意思其實就是「readonly」
頁253
Chapter 7. Classes
8:00
Constructors Revisited 再訪建構器
14:00類別就是用來定義我們自己的資料型別
In C++ we use classes to define our own data types.
需要哪一種型別的資料,就用class來定義
類別(class)就像一面鏡子,把我們想要解決問題的構想給反射、映照出來:
By defining types that mirror concepts in the problems we are trying to solve, we can make our programs easier to write, debug, and modify(調適、調整).
mirror ⑦先抓動詞
所以它真的是一個藍圖,藉由class反射出我們內心的想法、做法、想要什麼……人家看了也能夠一目瞭然
資料抽象化的重要性
importance of data abstraction
資料抽象化(data abstraction)
這個data應該不宜只直翻成「資料」,因為中文語境下,data只是靜態的一堆資料物質而已,不包括動態的操作行為。
抽象化才能形成、組成一個概念(concept)供給其他情境套用與運用 49:00
「implementation、perform」即「interface、abstraction」的對立面(即若產品與藍圖的關係)
頁254
35:00
The fundamental ideas behind classes are data abstraction and encapsulation .
類別的基本概念就是資料抽象化(data abstraction)封裝
封裝的基本概念就是控制存取權(封什麼?裝什麼?⑧找對主詞 存取權)⑧找對主詞 什麼東東的存取權? 類別的實作的存取權:
Encapsulation enforces the separation of a class’ interface and implementation.
其實1:6:00封裝是將介面與實作分離的機制
A class that is encapsulated hides its implementation—users of the class can use the interface but have no access to the implementation.
Data abstraction is a programming (and design) technique that relies on the separation of interface and implementation .
資料抽象化是藉由「分離介面和實作」來完成的
也就是從實作中抽離出介面(框架、大綱、模型、模式……)來
介面的定義:
The interface of a class consists of the operations that users of the class can execute.
一個類別的介面是由使用類別者可以進行的操作來組成的
51:30
The implementation includes the class’ data members, the bodies of the functions that constitute the interface, and any functions needed to define the class that are not intended for general use.
⑦先抓動詞 are 可見 ⑧找對主詞 不是class,而是functions(∵ 是複數) 59:59
實作的內容、元素:
1. 類別成員
2. 組成介面的函式的本體/主體(body即有定義了)
3. 定義該類別所需且不作一般用途的函式(不作為通用的函式)
以及定義該類別所需但不提供一般用途的任何函式。
一個類別若是用上了資料抽象化(data abstraction)與封裝,就未定義的了一個抽象的資料型別:
A class that uses data abstraction and encapsulation defines an abstract data
type.
使用資料抽象化和封裝的類別會定義一個抽象資料型別(abstract data type)。
資料抽象化(data abstraction)是就理論上泛論如何將資料抽象化(即將介面與實作分離開來),而封裝是指對一個具體的類別,要如何把它的介面與實作分離開來。
而具備了這樣的資料抽象化與封裝技術的類別,實際上就是定義了一個抽象資料型別(abstract data type)
1:18:00
這裡designer與 use(user)是相對的
設計者的本分是去考量這樣抽象資料型別(abstract data type)的類別要如何去實作;而使用者並不需要操心這個(這樣型別實際是如何實作的):
In an abstract data type, the class designer worries about how the class is implemented. Programmers who use the class need not know how the type works. They can instead think abstractly about what the type does.
worries about 這裡該翻成「在乎、該重視的」
使用者反而應該是要去抽象地思考這樣的型別會做些什麼事。
在一個抽象資料型別中,類別設計者擔心類別如何實作。使用該類別的程式設計師不需要知道型別實際上的運作方式。,而取而代之,他們會該是去抽象地(abstractly)思考型別所做的事。
什麼叫「抽象地思考」?其實就是指「去理解、明白」,就是在寫程式時不用打出來的部分。就是這個抽象資料型別(abstract data type)的實作是不需要使用者去實作的,而是知道它怎麼運作,你怎麼使用便可。1:32:00
1:29:00 1:34:30
7.1. Defining Abstract Data Types
7.1定義抽象資料型別
The Sales_item class that we used in Chapter 1 is an abstract data type. We use a Sales_item object by using its interface (i.e., the operations described in § 1.5.1 (p. 20)). We have no access to the data members stored in a Sales_item object.
其實我們今天使用一個產品,也實際上僅只是使用到它的「介面(interface)」而已。它實際上內部是如何操作的,我們幾乎都是一無所知,就如前面所談一個使用者並不需要去操心的一樣。像一個遙控器,我們按下一個按鈕後會發生什麼事,我們可以想像,但實際上背後、幕後、內地裡是如何運算的,我們並無所知,只能想像,或去理解。即使我們通盤了解了,也無法參與。有且唯有設計這個遙控器的,才能實作它的實際運作方式及其內容。裡面的內容是我們使用者無法去過問的。
資料成員(data member)或這裡的「data」翻成「內部成員」內部的成員或「資賦成員」或「稟賦成員」或「本有成員」本來就有的成員比較好。
這個「資料」也不是中文語境中的,而是指一切在類別中的「東西」、裡頭的「內容」。
「成員」是描述它們是在類別中。
這個「資料」其實要用文言來理解就可以,資是存在於其類別內部的資產(有的東西),而「料」就是那些內賦、稟賦的「東西」。這個資,就像「資源」「資本」「資優」的「資」一義。
甚至可以翻成「資本成員」或許更適當!
不要誤以為是資料庫、文件、文獻那種的資料。其實資料庫裡的資料,此二字也可用文言來分析才是、也才精切。
可見「資料」二字在學習電腦科學相關的地方要用文言來一個字一個字地去體認才好理解精切!
「文言」不要想成「文謅謅」,而是對「語文」或「文字」有更精切、道地的認識與知識。
2:8:00這個data是指存在類別裡頭的,就像data是存放在資料庫裡的一樣,是取這個意思。∴ data就是「放在裡面」。
Our Sales_data class (§ 2.6.1 , p. 72 ) is not an abstract data type. It lets users of the class access its data members and forces users to write their own operations. To make Sales_data an abstract type, we need to define operations for users of Sales_data to use. Once Sales_data defines its own operations, we can encapsulate (that is, hide) its data members.
可見是不是抽象資料型別(隔離內部成員型別),取決於這個類別型別有沒有它自己的的操作/運算(operations)。
所以封裝的前提是在一個類別具備了它自己的運算/操作之後。
2:18:50
7.1.1. Designing the Sales_data Class
普通的函式就是具名函式:
ordinary (named) functions
就是有函式名稱的
2:25:00 data和member在這裡的區別應只是在data猶如「東西」,泛指一切在類別中的都可,而member則一定要是自成一個單位(unit)的,如欄位、變數、函式,才叫作member。
在C++中,我們藉由定義一個類別來定義出我們自己的資料型別。(頁72)
這個資料則資給計算機處理的材料的意思。
所以您說,要不要當作文言文來閱讀、理解呢? ②單字想複詞
也就是計算機運算的資源材料,就叫資料。
這個「資」就是「談資、資助」的資
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};//(頁72)
由{};可知struct其實還是一種述句,所以結尾處才需要用個分號:
結束類別主體的右大括號(close curly)後面必須跟著一個分號(semicolon)。分號之所以 是必要的,是因為我們可以在類別主體後定義變數:(頁72)
struct Sales_data { /* ... */ } accum, trans, *salesptr;
//等效的,不過是定義這些物件比較好的方式
struct Sales_data { /* ... */ };
Sales_data accum, trans, *salesptr;
分號標示著宣告器串列(list of declarators,通常是空的)的結尾。(頁72-73)
類別定義的尾端忘記放上分號是新手程式設計師常犯的錯誤。(頁73)
頁255
Key Concept: Different Kinds of Programming Roles
2:59:00
關鍵概念:不同種類的程式設計角色
3:8:00
Programmers tend to think about the people who will run their applications as users. Similarly a class designer designs and implements a class for users of that class. In this case, the user is a programmer, not the ultimate user of the application.
終端機的終端應該也跟這個ultimate有關
總之這個「users」不僅僅只是一般不會寫程式的使用者也。 ⑧找對主詞 不要搞錯對象了!新年快樂誰快樂?我們講使用者,是怎樣的使用者啊?!
類別寫作者/設計者就是為其他程式設計師寫作/設計類別的人
3:19:10 各司其職 扮演好自己的角色
When we design the interface of a class, we should think about how easy it will be to use the class. When we use the class, we shouldn’t think about how the class works.
⑦先抓動詞 to use the class 「it」是代這件事
這是不定詞作主詞之例
④倒序重組 → to use the class will be how easy
可見類別設計的大原則就是,讓它使用起來,盡可能地容易
how the class works
這個類別實際上或內部是如何作用的,好像我們前面講的遙控器的例子,只要知道遙控器怎麼操作,有哪些功能就好,不必去管它實際是如何將我們下達的指令,成為實際作用出來的結果
3:36:00
Authors of successful applications do a good job of understanding and implementing the needs of the application’s users. Similarly, good class designers pay close attention to the needs of the programmers who will use the class. A well-designed class has an interface that is intuitive and easy to use and has an implementation that is efficient enough for its intended use.
3:38:30
Using the Revised Sales_data Class
3:40:30
As one example, we can use these functions to write a version of the bookstore program from § 1.6 (p. 24) that works with Sales_data objects rather than Sales_items:
舉個例子,我們可以使用這些函式來寫出用於Sales_data物件而非Sales_item的書店程式(§1.6):
應是翻成「用」,不是「用於」
3:52:30
頁256
4:4:00
We next assign trans to total, thus setting up to process the records for the next book in the file.
我們接著將trans指定到total,藉此設定好開始處理檔案中下本書的記錄。
⑦先抓動詞 setting up 、to process
⑤添字還原 開始
4:6:30
練習7.1
練習2.40
struct Sales_data {
std::string bookNo;
double revenue;
unsigned soldQ{1};//銷售數量
double bookSize;
const string isbn() {
return bookNo;
}
void combine(Sales_data sales_data){//記錄銷售總數
soldQ+= sales_data.soldQ;
}
};
ostream& print(ostream &os, Sales_data sales_data) {
os <<sales_data.isbn()<<'\t'<< sales_data.soldQ;
return os ;
}
istream& read(istream &is ,Sales_data &sales_data) {//要改變引數值,參數一定要是參考,將引數用傳址(參考)方式傳遞
//decltype(cin)& read(istream is ,Sales_data sales_data) {
is >>sales_data.bookNo;
return is;
}
int main() {
Sales_data total;
if (read(cin,total))
{
Sales_data trans;
while (read(cin,trans))
{
if (total.bookNo==trans.bookNo)
{
total.combine(trans);
}
else {
print(cout, total) << endl;
total = trans;
}
}
print(cout, trans)<<endl;
}
else
{
cerr<<"No data?!"<<endl;
}
5:20:40 5:22:20
7.1.2. Defining the Revised Sales_data Class
可見「Defining」和「Designing」不是一回事。
練習7.1 加上平均售價後:
struct Sales_data {
std::string bookNo;
double revenue;
unsigned soldQ{1};//銷售數量
double bookSize;
const string isbn() {
return bookNo;
}
const double avg_price() {
return revenue / soldQ;
};
void combine(Sales_data sales_data){//記錄銷售總數及營業額
soldQ+= sales_data.soldQ;
revenue += sales_data.revenue;
}
};
ostream& print(ostream &os, Sales_data sales_data) {
os <<sales_data.isbn()<<'\t'<< sales_data.soldQ<<'\t'<<sales_data.avg_price();
return os ;
}
istream& read(istream &is ,Sales_data &sales_data) {//要改變引數值,參數一定要是參考,將引數用傳址(參考)方式傳遞
//decltype(cin)& read(istream is ,Sales_data sales_data) {
is >>sales_data.bookNo>>sales_data.revenue;
return is;
}
int main() {
Sales_data total;
if (read(cin,total))
{
Sales_data trans;
while (read(cin,trans))
{
if (total.bookNo==trans.bookNo)
{
total.combine(trans);
}
else {
print(cout, total) << endl;
total = trans;
}
}
print(cout, trans)<<endl;
}
else
{
cerr<<"No data?!"<<endl;
}
}
5:48:10 這裡 implemeation和 interface怎麼區分呢?
we’ll give Sales_data another member function to return the average price at which the books were sold. This function, which we’ll name avg_price, isn’t intended for general use. It will be part of the implementation, not part of the interface.
不提供給外界用就是實作(要被封裝起來的),介面是拿來與外界接觸用的
Member functions must be declared inside the class. Member functions may be defined inside the class itself or outside the class body. Nonmember functions that are part of the interface, such as add , read , and print , are declared and defined outside the class.
6:2:20
這樣就明白了,只要定義and宣告寫在類別外部的就是介面的一部分,宣告寫在內部的(可封裝的)即實作,其定義則可在類別內或外。
Although every member must be declared inside its class, we can define a member function’s body either inside or outside of the class body.(頁257)
非成員函式(Nonmember functions)是介面的一部分
struct Sales_data {
// new members: operations on Sales_data objects
6:10:30 在這裡 operation可以理解為含有operator的述句(statement)或運算式(表達式expression) operation是抽象資料型別(abstract data type)的必要條件 抽象層、抽象化(abstraction) 資料抽象化(data abstraction)
頁257
// nonmember Sales_data interface functions
在類別裡面定義的都隱含(默認)是行內函式(inline function)
Functions defined in the class are implicitly inline (§ 6.5.2 , p. 238 ).
所謂隱含是合用函式者,即不需要在此函式前冠上 inline這個請求語
Defining Member Functions
6:37:10
Introducing this
介紹this
when we call a member function we do so on behalf of an object.當我們呼叫一個類別的成員函式,我們是藉由一個物件去呼叫它的。
我們呼叫一個成員函式時,是代表一個物件這麼做的。
藉由和 代表也是 表哥原理 看起來好像是不同的東西,其實是一樣或相通的。
When isbn refers to members of Sales_data (e.g., bookNo), it is referring implicitly to the members of the object on
which the function was called. In this call, when isbn returns bookNo, it is implicitly returning total.bookNo.
當isbn這個成員函式指向一個Sales_data的成員(比如說在這裡是bookNo),這個isbn函式其實指向的是這次呼叫它時,它所屬的物件的成員。如在這次呼叫中,isbn回傳的bookNo,其實就是total這個物件下的bookNo資料成員(data member)。isbn其實是回傳tatal這個object的bookNo,而不是回傳Sales_data的資料成員bookNo。
當isbn指涉Sales_data的成員(例如bookNo),它隱含指涉的是在其上函式被呼叫的那個物件之成員。在這個呼叫中,當isbn回傳bookNo,它是隱含地回傳total. bookNo 。
6:47:30 這裡的 implicitly翻成「不言而喻、不言自明」或「就是」「其實」才更貼切
7:4:30
原來程式在玩這個小把戲:
Member functions access the object on which they were called through an extra, implicit parameter named this . When we call a member function, this is initialized with the address of the object on which the function was invoked.
原來成員函式被呼叫時是帶有一個隱藏的參數this,一旦呼叫,它就被初始化了。它是用那個被呼叫的函式所在的物件的位址來初始化的。
可以被傳址初始化,可見this應是一種指標(pointer)型別參數。果然:
We do not have to use a member access operator to use the members of the object to which this points.(頁258)
this不但是指標,而是常值的指標(const pointer):
Because this is intended to always refer to “this” object, this is a const pointer (§ 2.4.2 , p. 62 ). We cannot change the address that this holds.(頁258)
The purpose of that const is to modify the type of the implicit this pointer.(頁258)
也就是無法改變它指向的對象object(無法改變它儲存的位址值——就是無法改變它的值;一個指標(pointer)的值,就是位址)。加了const就是唯讀之意,不想改變的值就前冠const。const pointer就是這個指標的值是不會被改變的。
可見成員函式都有一個額外的隱藏版的參數this,在呼叫這個函式時,就必須傳遞一個引數讓這個this參數被初始化,而這個傳遞引數的工作是由編譯器完成的,它會傳送被呼叫的函式所在的物件的位址,當作引數,讓這個被呼叫的成員函式的this參數,得以初始化。
就是編譯器在動這個手腳嘛:
It is as if the compiler rewrites this call as
// pseudo-code illustration of how a call to a member function is translated
Sales_data::isbn(&total)// & address of operator,這裡不是函式參數的定義,而是引數的傳遞。它是一個call,不是一個定義definition。
which calls the isbn member of Sales_data passing the address of total.
在這裡「pseudo」應翻成「假」,假借之義,於此與「偽」不同義!在這個意義上,「假」和「偽」是不同義的。
頁258
7:14:30
可以不必用成員存取運算子(.),仍是要用到箭號運算子(->)
this->bookNo;
7:28:00
Introducing const Member Functions
介紹const成員函式
The other important part about the isbn function is the keyword const that follows the parameter list. The purpose of that const is to modify the type of the implicit this pointer.
7:35:00
By default, the type of this is a const pointer to the non const version of the class type. For example, by default, the type of this in a Sales_data member function is Sales_data *const . Although this is implicit, it follows the normal initialization rules, which means that (by default) we cannot bind this to a const object (§ 2.4.2 , p. 62 ). This fact, in turn, means that we cannot call an ordinary member function on a const object.
7:48:00
If isbn were an ordinary function and if this were an ordinary pointer parameter, we would declare this as const Sales_data *const . After all, the body of isbn doesn’t change the object to which this points, so our function would be more flexible if this were a pointer to const (§ 6.2.3 , p. 213 ).
如果isbn是一個普通的函式,而this是一個普通的指標參數,我們會將this宣告為const Sales_data *const。畢竟,isbn的主體不會更改this所指的物件,因此,如果this是 對const的一個指標(§ 6.2.3 ),我們的函式就會更有彈性。
中文版掉了一個「const」。
the normal initialization rules
因為參數初始化的方式與變數一樣,記住通用的初始化規則是有幫助的。我們可以用一個非 const物件來初始化具有低層const (low-level const)的物件,但不能反過來,而一個普 通的參考必須以相同型別的物件來初始化。(頁213)
7:54:10
However, this is implicit and does not appear in the parameter list. There is no place to indicate that this should be a pointer to const . The language resolves this problem by letting us put const after the parameter list of a member function.
A const following the parameter list indicates that this is a pointer to const .
結果就是這個this經過這樣的指定後就變成一個指向常值的常值指標(const pointer to const type)。
常值成員函式(const member functions)的定義:
Member functions that use const in this way are const member functions .
const member functions 其實應該翻為:成員函式隱含的this參數是指向常值
8:17:00
Note
Objects that are const, and references or pointers to const objects, may call only const member functions.
是const的物件,以及對const物件的參考或指標只可以用來呼叫const成員函式。
常值的引數或物件,只能用來呼叫常值的成員函式,這一樣是符合「the normal initialization rules」
頁259
Class Scope and Member Functions
和在類別外定義的不一樣,類別範疇內的定義,不必依其次序。在後宣告定義的也能先在前被調用:
It is worth noting that isbn can use bookNo even though bookNo is defined after isbn . 8:35:40 As we’ll see in § 7.4.1 (p. 283), the compiler processes classes in two steps— the member declarations are compiled first, after which the member function bodies, if any, are processed. Thus, member function bodies may use other members of their class regardless of where in the class those members appear.
Defining a Member Function outside the Class
8:36:30
前面說宣告成員函式必在類別之中,而定義則不必。所以這裡就要介紹如何定義一個在類別外的成員函式。
If the member was declared as a const member function, then the definition must also specify const after the parameter list. The
在類別外的定義也一樣是要將const放在參數列(parameter list)後的
定義在類別外的成員的名稱也一定要再加上其類別的名稱。
double Sales_data::avg_price() const {
if (units_sold)
return revenue/units_sold;
else
return 0;
}
範疇運算子(::)表示了這個函式的宣告是在那個類別的範疇內的
Once the compiler sees the function name, the rest of the code is interpreted as being inside the scope of the class. Thus, when avg_price refers to revenue and units_sold, it is implicitly referring to the members of Sales_data.
這裡的「implicitly」也是翻成「其實就是」就好了。
Defining a Function to Return “This” Object
9:00:30
The object on which this function is called represents the left-hand operand of the assignment. 呼叫combine函式的那個物件,是作為指定的左運算元。The right-hand operand is passed as an explicit argument:這個引數是指combind這個函式的引數。指定的右運算元則是由呼叫combine的引數來做的。
在其上這個函式被呼叫的物件代表指定(assignment)的左運算元。右運算元是作為一個明確的引數傳入的:
9:6:00
9:12:00
頁260
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
}
前二行註解是一句話,要一起看的:將rhs的成員加入this這個物件的成員,怎麼中文版可以這樣亂翻?人家是加進「add……into」給人家翻成「加上」!
Sales_data& Sales_data::combine(const Sales_data &rhs)
{units_sold += rhs.units_sold; // 加上 rhs 的成員
revenue += rhs.revenue; // this 物件的成員
return *this; //回傳在其上這個函式被呼叫的那個物件
}
→回傳呼叫這個函式的物件。
Google翻譯都翻得比中文版好:
返回調用函數的對象
這應該是叫研究生或助理翻的!本書翻譯水平良莠不齊。
函式只有回傳參考型別,才是左值(lvalue);其均屬右值(rvalue)(頁226)
9:33:00
藉由this才能存取成員函式本身所在的「物件」。
因為this就是成員函式隱含的指向其物件本身的常值指標(pointer)型參數
Here the return statement dereferences this to obtain the object on which the function is executing.在這個return的述句中解參考(dereference)了this,來取得該函式所在所屬的物件
這裡的return述句解參考了 this來獲取函式在其上執行的那個物件。
中文一般不會先寫代名詞(其)再寫所代之物。這個「其」就是「那個物件」。
9:50:00
這裡指定combine的回傳型別為參考,(即左值(lvalue))純粹只為模仿內建的複合指定運算子(compound assignment operator)的行為,因為combine的行為運算、操作都是+=。
留言