C++自修入門實境秀、C++ Primer 5版研讀秀 47/ ~v7 類別 7.5.1. Constructor Initializer Li...





4:50 參見練習7.30 頁278

7.3.4. Friendship Revisited

再談一談朋友關係

7.3.4重訪朋友關係

43:39

除了函式之外,類別也可以互為朋友

還可以宣告一個在它之前已經定義好的類別中的成員函式作為它的朋友

a friend function can be defined inside the class body. Such functions are implicitly inline .

friend函式也能定義在類別主體內。這種函式隱含是inline的。

50:20

在類別內定義的何必還要是「朋友」呢?不就是成員函式了嗎?難道是宣告不在該類別內?然若不在該類別內宣告,該類別又如何能取用(access)那個外部的函式呢?只因為二者有朋友關係?

53:20

it can declare specific member functions of another (previously defined) class as friends.

這裡是宣告,而前述是定義!

54:40

Friendship between Classes

頁280

It is important to understand that friendship is not transitive.

1:24:00 朋友的朋友沒有權限,一定要本人認可才行。就如臉書分享。朋友與「朋友的朋友」是有不同的權限的。

transitive就是推己及人、愛屋及烏的「及」。

遞移性(transitive)

夠文言了吧!文言文用在哪裡?知道了吧!

用在學術術語的命名或譯名也。

Note:

Each class controls which classes or functions are its friends.

Note:每個類別都控制哪些類別或函式是其朋友。



可見朋友關係是用在函式與類別上的

1:50:18

Making A Member Function a Friend



頁281

1:58:30

定義和宣告之間的依存關係

To accommodate interdependencies among the declarations and definitions.

accommodate:周顧



Overloaded Functions and Friendship

多載函式、重載函式與朋友關係

2:20:40

Friend Declarations and Scope

2:25:00在做朋友宣告前,朋友的兩造是不需要先經過宣告的(即「幽靈人口」也可以存在朋友關係),可是要用到這個朋友函式時,它一定是已經過一般宣告過的函式才能被用(就不能是幽靈人口了)可見「宣告」與「調用」是不同層面上的意義

朋友函式的定義可以在該朋友的類別中,但其宣告一定要在該類別外,畢竟朋友不是「內人」「家人」(一家人)而是「外人」。



頁282

scope範疇就是有效性或可見度

2:38:10使用一個函式,這個函式就一定要是已經宣告了,可是宣告一個函式為朋友,該函式卻未必要先宣告了才能這樣做。

練習7.32

2:40:40 以下編譯時有錯誤,再研究: 4:20:00

LNK1169 one or more multiply defined symbols found

LNK2005 public: void __cdecl Window_mgr::clear(unsigned __int64) (?clear@Window_mgr@@QEAAX_K@Z) already defined in prog1.obj

Screen.h:

#pragma once

#ifndef Screen_H

#define Screen_H

#include<string>

#include<vector>

#include<iostream>

using namespace std;

class Screen;//要先宣告,Window_mgr 中的 Screen 才「看得見」

class Window_mgr {

vector<Screen>Screens;

public:// Window_mgr::clear must have been declared before class Screen 頁281

typedef vector<Screen>::size_type ScreenIndex;

void clear(ScreenIndex);

friend Screen;

};



class Screen

{

public:

typedef string::size_type pos;

Screen() = default;

Screen(pos w,pos h,char c ) :w(w),h(h),contents(h*w,c){}

//Screen(pos w, pos h, char c) :w(w), h(h), contents{ c }{}//字元作為字串型別的初始器,原來是用{}

Screen(pos w,pos h,string s ) :w(w),h(h),contents(s){}

Screen(pos w,pos h,pos ctrBlanks ) :w(w),h(h),contents(ctrBlanks,' '){}



inline char get() { if(curser<contents.size()) return contents[curser]; }

string get(pos startposition,pos length) const; //不能用inline

void some_member() const;



Screen& set(char);//之所以要傳回對Screen的參考,是為了set move display等方法可以連動地用

Screen& set(pos,pos,char);

Screen& move(pos,pos);

Screen& display(ostream& os) { do_display(os); return *this; }

const Screen& display(ostream& os)const { do_display(os); return *this; }

pos size();

private:

mutable size_t access_ctr=0; // may change even in a const object

// other members as before

pos curser = 0;

pos w = 0, h = 0;

string contents;

void do_display(ostream& os)const { os << contents; }

friend void Window_mgr::clear(ScreenIndex);

};



void Window_mgr::clear(ScreenIndex si) {

Screens={ Screen(21,18,'a') };

//vector<Screen>Screens{ Screen(21,18,'a') };

Screen& s=Screens[si];

s.contents = string(s.w * s.h, ' ');

//Screens[si].contents = string(Screens[si].w * Screens[si].h, ' ');

}



#endif // !Screen_H





3:29:10

7.4. Class Scope

藉由範疇運算子來存取一個類別中的型別成員(type member),而一般的資料成員(data member)、成員函式,則是藉由成員存取運算子(點運算子)來存取的。

可見類別內之成員可分為3大類:

1.資料成員(data member)

2.成員函式

3.型別成員(type member)

3:34:10

3:37:10

Scope and Members Defined outside the Class

「定義在類別外的成員」和「範疇」的關係

範疇與定義在類別外的成員

頁283

4:00:00

練習7.33

4:7:10

Screen::pos Screen::size()

{

return w*h;

}

7.4.1. Name Lookup and Class Scope

7.4.1名稱查找與類別範疇



name lookup (the process of finding which declarations match the use of a name)



In the programs we’ve written so far,

在我們目前見過的程式中

名稱查找就是先從block裡頭找,找不到再找其外層的scope,否則就是錯誤

4:33:50

Note Member function definitions are processed after the compiler processes all of the declarations in the class.

Name Lookup for Class Member Declarations

4:36:00

頁284

4:39:50如果類別成員用了一個不在類別內可以見到的名稱,那麼編譯器就會在該類別所在的範疇中去查找該名稱。

4:46:10

編譯順序:在整個類別都處理完後,才會處理成員函式的body

4:46:59

Type Names Are Special

4:48:00

內嵌範疇可以重新訂義一個外包範疇已使用過的名稱,但若是一個類別它的成員已使用了一個外包範疇的型別名稱,該名稱在其類別內就不能再重新定義了。所以這裡才說「Type Names Are Special」即指型別名稱一旦經其成員用過後,就不得再在其類別內重新定義之。

頁285

即使給一樣的定義也一樣不能,就好像一個常值不能被更動其值,而如果一我們賦予它一個和它現有值相同的值,也是一樣不容許的!

Tip

Definitions of type names usually should appear at the beginning of a class. That way any member that uses that type will be seen after the type name has already been defined.

5:1:00

Normal Block-Scope Name Lookup inside Member Definitions

成員定義內的一般區塊範疇名稱查找

在成員函式本體內用到的名稱,會用以下方式來解析、比對:

1. 先由成員函式內部宣告中找

2. 再由成員函式所在類別中找(所有成員都納入考量)

3. 類別中再找不到,就在成員函式定義前的範疇中找 look for a declaration that is in scope before the member function definition.



頁286

5:17:00一個函式的範疇包括它的參數列在內:

A function’s parameters are in the function’s scope.



照前一般的規則先找到的對象會遮蔽後來才能找到的對象:

In this case, the height parameter hides the member named height . If we wanted to override the normal lookup rules, we can do so:

如果想要覆寫這樣的規則,我們可以:

// good practice: don't use a member name for a parameter or other local variable

void Screen::dummy_fcn(pos ht) {

cursor = width * height; // member height

}

5:41:00

After Class Scope, Look in the Surrounding Scope

類別範疇找完後如果還是沒找到的話,就在類別所在範疇中繼續尋找

類別範疇之後,就在外圍範疇中尋找



5:50:00

如何利用 Visual Studio Code及Notepad++來快速導覽程式碼

Notepad++有功能表View下有「Document Map」和「Function List」及「Fold All」(Alt+0)等可運用



5:55:00

Adobe Acrobat DC如何顯示選取文字後的快顯功能表

「在文字選取範圍顯示「快速動作」」



6:0:59

However, the object in the outer scope is hidden by our member named height . If we want the name from the outer scope, we can ask for it explicitly using the scope operator:

要利用一個已被類別成員遮蔽的外部名稱,可以用範疇運算子來調用。

如果是global全域的,就用範疇運算子,前面不加任何指名(類別)就是了

void Screen::dummy_fcn(pos height) {

cursor = width * ::height;// which height? the global one

}

6:7:20

可見所謂的遮蔽hide,其實只是預設情形下不開放使用而已,一樣可藉由範疇運算子來資格修飾(qualification)指名來用

6:10:59

頁287

Names Are Resolved Where They Appear within a File

名稱會在檔案中它們出現處被解析

練習7.34

6:22:30

凡用到「pos」的都不認得了。

練習7.35

6:38:00

typedef string Type;

Type initVal();

class Exercise {

public:

typedef double Type;

Type setVal(Type);

Type initVal();

private:

int val;

};

Exercise::Type Exercise::setVal(Type parm) {

val = parm + initVal();

return val;

}

可見一個類別外部已有的名稱,在其成員尚未使用此名稱時,類別內部仍可對其重新定義



頁288

6:43:00

7.5. Constructors Revisited

再談談建構器(constructor)

7.5再訪建構器

Constructors are a crucial part of any C++ class.

建構器是任何C++類別的關鍵部分。

建構器對C++的類別而言是很緊要的!非常關鍵重要的部分

前面有提過建構器在C++中是很複雜的:

Constructors are a surprisingly complex topic.

建構器是出乎意料複雜的主題。

(頁262)

出乎意料→超乎想像

主題→課題

6;53:49

7.5.1. Constructor Initializer List

7.5.1建構器初始器串列

指定(assignment)和初始化(initialization)是不相同的

7:5:00

資料成員(data member)的型別,決定了是初始化還是指定,此中差異是否有關係。

7:7:00

Constructor Initializers Are Sometimes Required

建構器的初始器有時候是不可省略的

只要是常值const或參考(reference)型別的成員都必須被初始化,而不僅僅是被指定



可見必須被初始化而不是定義後再指定值給它的有:

1. const (因為常值一經宣告後,就不能再指定其值,可指定值的是變數,不是常數)

2. reference(因為參考是別人的影子,沒有個體,不初始化的話,不知是什麼東西)

3. 成員所屬的型別(其類別)若沒有預設初始器,則這樣的成員就必須被初始化

Note :

We must use the constructor initializer list to provide values for members that are const , reference, or of a class type that does not have a default constructor.(頁289)



頁289

ConstRef::ConstRef(int ii)

{ // assignments:

i = ii; // ok

ci = ii; // error: cannot assign to a const

ri = i; // error: ri was never initialized

//不能使用一個未經初始化的參考(型物件)

//未經初始化的參考是不能被指定值的

}

By the time the body of the constructor begins executing, initialization is complete.

建構器的主體開始執行的時候,初始化就完成了。



7:32:20

Order of Member Initialization

成員初始化的順序,只要不存在以成員作為初始器,則不須考慮其初始化的順序



類別成員是照著它們在類別中出現的前後來依序被初始化的:

Members are initialized in the order in which they appear in the class definition:

通常初始化的順序並不重要,但是如果有一個成員需要經由另一個成員來初始化的話,那麼,順序就不能紊亂了!

7:44:00

頁290

7:47:00

養成好習慣:可以的話,盡量避免用一個成員去初始化另一個成員。而可以盡量照著類別中定義的順序來撰寫建構器初始器串列(constructor initializer list)。

7:51:00

寧願用建構器的參數來作為類別成員的初始器,也不要用其他類別成員來作為某個類別成員的初始器

只要避免用上另一個成員作為某個成員的初始器,則我們就不必考慮初始化的順序

Default Arguments and Constructors

建構器和預設引數(default argument)



// defines the default constructor as well as one that takes a string argument

//定義預設建構器和接受一個string引數的建構器

定義一個預設建構器就好像帶了一個string引數的建構器。



8:15:00

凡是類別內部public的部分,都是介面(interface)

頁291

8:19:00

預設建構器(不帶參數的,頁264:不接受任何引數)可藉由一般建構器為其所有參數提供了預設引數(default argument)來達成,就不必另外定義。

因為有了預設引數(default argument),就可以完全省略引數傳遞,效力就等同於預設建構器了;而一個類別的預設建構式有且僅能有一個。

8:28:08

練習7.36

struct X {

X(int i, int j) : base(i), rem(base% j) { }

int base,rem ;//沒有倒過來前程式仍可執行,但結果是出乎意料的,因為rem用了尚未定義的base來初始化,rem的結果也就是未定義的(undefined)

};

或改為:

struct X {

X(int i, int j) : base(i), rem(i% j) { }

int rem,base ;//改用建構器的參數來初始化,就不必考慮成員的先後了

};

練習7.37

8:41:18

#include<string>

#include <iostream>

using namespace std;

class Sales_data {

string bookNo;

unsigned units_sold=0;

double revenue=0.0;

public:

// defines the default constructor as well as one that takes a string argument

Sales_data(std::string s = "") : bookNo(s) { }

// remaining constructors unchanged

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); }

// remaining members as before

friend istream& read(istream&, Sales_data&);

friend ostream& print(ostream& os, const Sales_data& sales_data);

};



istream& read(istream&, Sales_data&);



istream& read(istream& is, Sales_data& sales_data) {//要改變引數值,參數一定要是參考,將引數用傳址(參考)方式傳遞

//decltype(cin)& read(istream is ,Sales_data sales_data) {

is >> sales_data.bookNo >> sales_data.units_sold >> sales_data.revenue;

return is;

}



ostream& print(ostream& os, const Sales_data& sales_data) {

os << sales_data.bookNo << '\t' << sales_data.units_sold << '\t'

<< sales_data.revenue ;

return os;

}



Sales_data first_item(cin);//Sales_data(std::istream& is) { read(is, *this); }



int main() {

Sales_data next;//Sales_data(std::string s = "") : bookNo(s) { } ;下同

Sales_data last("9-999-99999-9");



print(cout,first_item); cout << endl;

print(cout,next); cout << endl;

print(cout, last); cout << endl;

}

8:45:40 上面沒錄到的看臉書直播第186集

練習7.38

8:51:30預設引數(default argument)和建構器(constructor)的關係,只要建構器的參數都有了預設引數,則該建構器就成了預設建構器,而預設建構器是不能有二個以上的——只能有一個預設建構器

#include<string>

#include <iostream>

using namespace std;

class Sales_data {

string bookNo;

unsigned units_sold=0;

double revenue=0.0;

public:

// defines the default constructor as well as one that takes a string argument

Sales_data(std::string s ) : bookNo(s) { }

// remaining constructors unchanged

Sales_data(std::string s, unsigned cnt, double rev) :

bookNo(s), units_sold(cnt), revenue(rev* cnt) { }

Sales_data(std::istream& is = cin) { read(is, *this); }

// remaining members as before

friend istream& read(istream&, Sales_data&);

friend ostream& print(ostream& os, const Sales_data& sales_data);

};



istream& read(istream&, Sales_data&);



istream& read(istream& is, Sales_data& sales_data) {//要改變引數值,參數一定要是參考,將引數用傳址(參考)方式傳遞

//decltype(cin)& read(istream is ,Sales_data sales_data) {

is >> sales_data.bookNo >> sales_data.units_sold >> sales_data.revenue;

return is;

}



ostream& print(ostream& os, const Sales_data& sales_data) {

os << sales_data.bookNo << '\t' << sales_data.units_sold << '\t'

<< sales_data.revenue ;

return os;

}



Sales_data first_item(cin);//Sales_data(std::istream& is = cin) { read(is, *this); }



int main() {

Sales_data next;//Sales_data(std::istream& is = cin) { read(is, *this); }

Sales_data last("9-999-99999-9");//Sales_data(std::string s ) : bookNo(s) { }



print(cout,first_item); cout << endl;

print(cout,next); cout << endl;

print(cout, last); cout << endl;

}



練習7.39

8:57:30

答案已見前一題練習7.38

練習7.40

9:1:10

Choose one of the following abstractions (or an abstraction of your own choosing). Determine what data are needed in the class. Provide an appropriate set of constructors. Explain your decisions.

選擇下列的抽象層(abstractions)之一(或你自己找的抽象層)。判斷該類別中需要什麼資料。提供適當的一組建構器。解釋你的決策。

(a) Book (b) Date (c) Employee

(d) Vehicle (e) Object(各種物品) (f) Tree(有各種樹種,或木本植物)

abstractions在此當翻作「抽象概念」,不應翻成「抽象層」(翻成「抽象層」中文使用者誰看得懂?)

在上述的各種,選擇出一種,來作為類別的設計對象,設計此抽象類別中,需要怎麼樣的資料成員(data member),並提供對此資料成員適當的一組建構器。解釋您這麼設計的緣故。

在生活中的各個具體事物抽象化後都有一個「類別」概念,可作為囊括這類相關物件object對象的總概念或共通特徵。

資料在此就類似屬性的概念。

同一類別中,需要哪些屬性,即需要哪些資料成員(data member)。如「person」會有「name」與「address」的屬性(資料)

前面有討論過所謂的「Data」在此翻成「資料」是極其不當的。中文語境中,不會用這個「資料」概念來表示這裡想表達的Data的意指。這裡的「data」一樣是翻成「內容」「東西」或「屬性」「特性」才適合。如:

(a) Book 書要有大小、售價、作者、出版者、裝訂方式、書號……

(b) Date 年、季、月、星期、日、午前、午後、時、刻、分、秒、毫秒;時差……

(c) Employee 姓名、身分證字號、電話、地址、學歷、職務、家屬……

(d) Vehicle 幾輪的、動力(柴油、汽油、發電)、載客量、價錢……

(e) Object(各種物品) 食衣住行,太抽象了、太廣泛了,應予窄化、具體限制化

(f) Tree(有各種樹種,或木本植物) 灌木or喬木、常綠……

資料成員就是屬性元素,需要有哪些屬性、哪些元素



所以這個搞資訊的中文程度差,用字遣詞多辭不達意,難怪外人想學者,視為畏途。



7.5.2. Delegating Constructors

7.5.2委派建構器

又是一個文言文! 9:41:00

留言

熱門文章