類的基本思想是數據抽象和封裝。
數據抽象是一種依賴於接口和實現分離的編程技術;類的接口包括用戶所能執行的操作,類的實現包括類的數據成員、負責接口實現的函數體以及定義類所需的各種是有函數
定義抽象數據類型
設計Sales_data類
Sales——data的接口應該包含以下功能
- 一個isbn成員函數,用於返回對應的isbd編號
- 一個combine成員函數,用於將一個Sales_data對象加到另一個對象上
- 一個名爲add的函數,執行兩個Sales_data對象的加法
- 一個read函數,將數據從istream讀入到Sales_data對象中
- 一個print函數,將Sales_data對象的值輸出到ostream
(1)使用改進的Sales_data類
- 首先,我們看看應該如何使用上面的這些接口函數
Sales_data total;
if(read(cin,total)){
Sales_data trans;
while(read(cin,trans)){
if(total.isbn()==trans.isbn())
total.combine(trans);
else{
print(cout,total)<<endl;
total=trans;
}
}
print(cout,total)<<endl;
}
else{
cerr<<"No data?!"<<endl;
}
定義改進的Sales_data類
struct Sales_data{
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;
};
Sales_data add(const Sales_data&,const Sales_data&);
std::ostream &print(std::ostream&,const Sales_data&);
std::istream &read(std::isteam&,Sales_data&);
(1)定義成員函數
(2)引入this
- 實際上,調用一個成員函數,是在爲某個對象調用它。
- 在成員函數 內部,任何類成員的訪問都被看作this的隱式引用,直接使用bookNo,形同於this->bookNo
(3)引入const成員函數
- const的作用是修改隱式this指針的類型
- C++的做法是允許把const關鍵字放在成員函數的參數列表之後,此時,緊跟在參數列表後面的const表示this是一個指向常量的指針。像這樣使用const的成員函數被稱作常量成員函數
常量對象,以及常量對象的引用或指針都只能調用常量成員函數
(4)類作用域和成員函數
(5)在類的外部定義成員函數
double Sales_data::avg_price() const{
if(units_sold)
return revenue/units_dold;
else
return 0;
}
- 一旦編譯器看到改函數名,就知道其剩餘代碼是位於類的作用域內的
(6)定義一個返回this對象的函數
- 函數combine的設計初衷類似於複合賦值+=,調用改函數的對象代表賦值運算符左側對象,右側對象則通過顯式的實參被傳入函數
Sales_data& Sales_data::combine(const Sales_data &rhs){
units_sold+=rhs.units_sold;
revenue+=rhs.revenue;
return *this;
}
當我們的交易處理程序調用如下的函數時,
total.combine(trans);
total的地址被綁定到隱式的this參數上,而rhs綁定到了trans上
定義類相關的非成員函數
一般來說,如果非成員函數是類接口的組成部分,則這些函數的聲明應該與類在同一個頭文件內
(1)定義read和print函數
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;
}
- 使用IO類引用是因爲其IO類屬於不能被拷貝的類型,因此我們只能通過引用來傳遞它們
(2)定義add函數
Sales_data add(const Sales_data &lhs,const Sales_data &rhs){
Sales_data sum=lhs;
sum.combine(rhs);
return sum;
}
構造函數
(1)合成的默認構造函數
(2)某些類不呢依賴於合成的默認函數
(3)定義Sales_data的構造函數
struct Sales_data{
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 &);
std::string isbn() const{ retrun bookNo;}
Sales_data& combine(const Sales_data&);
double avg_price() const;
std::string bookNo;
unsigned units_sold=0;
double revenue=0.0;
};
(4)=default的含義
(5)在類的外部定義構造函數
Sales_data::Sale_data(std::istream &is){
read(is,*this);
}
拷貝、賦值和折構
訪問控制與封裝
class Sales_data{
public:
Saels_data()=default;
Sales_data(const std::string &s.unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n) { }
Sales_data(const std::stirng &s):bookNo(s) { }
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;
};
(1)使用class或struct關鍵字
- class默認權限爲private
- struct默認權限爲public
友元
- 類可以允許其他類或者函數訪問它的非公有成員,方法是令其他類或者函數成爲它的友元
class Sales_data{
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&);
public:
Sales_data()=default;
Sales_data(const std::string &s.unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n) { }
Sales_data(std::istream&);
Sales_data(const std::stirng &s):bookNo(s) { }
std::string isbn() const { return bookNo;
Sales_data &combine(const Sales_data&);
private:
std::string bookNo;
unsigned units_sold=0;
double revenue=0.0;
};
Sales_data add(const Sales_data&,const Sales_data&);
std::istream &read(std::istream&,Sales_data&);
std::osteam &print(std::ostream& const Sales_data&);
類的其他特性
類成員再探
(1)定義一個類型成員
(2)Screen類的成員函數
class Screen{
public:
typedef std::string::size_tyoe pos;
Screen()=default;
Screen(pos ht,pos wd,char c):height(ht),width(wd),contents(ht * wd,c){ }
char get()const{
return contents[cursor];
}
inline char get(pos ht,pos wd) const;
Screen &move(pos r,pos c);
private:
pos cursor=0;
pos height=0,width=0;
std::string contents;
};
(3)令成員你作爲內聯函數
inline
Screen &Screen::move(pos r,pos c){
pos row=r*width;
cursor=row+c;
return *this;
}
char Screen::get(pos r,pos c) const{
pos row=r*width;
return contents[row+c];
}
(4)重載成員函數
(5)可變數據成員
- 一個可變數據成員永遠不會是const,即使它是const對象的成員
- 小例子:我們將給Screen添加一個名爲access_ctr的可變成員,通過它我們可以追蹤每個Screen的成員函數被調用了多少次
class Screen{
public:
void some_member() const;
private:
mutable size_t access_ctr;
};
void Screen::some_memeber() const{
++access_ctr
}
(6)類數據成員的初始值
class Window_mgr{
private:
std::vector<Screen> screens{Screen(24,80,' ')};
}
當我們提供一個類內初始值時,必須比符號=或者花括號表示
返回*this的成員函數
class Screen {
public:
Screen &set(char);
Screen &set(pos,pos,char);
};
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;
}
- set成員返回值時調用set的對象的引用
- 返回引用的函數是左值的,意味着這些函數返回的是對象本身而非副本
把一系列這樣的操作鏈接在一起:
myScreen.move(4,0).set('#');
//這些操作將在同一個對象上執行`
- 如果我們令move和set返回Screen而非Screen&,則上述語句的行爲將大不相同(相當於對返回值進行拷貝,然後不改變myScreen)
(1)從const成員函數返回*this
一個const成員函數如果以應用的形式返回*this,那麼他的返回類型將是一個常量引用
Screen mySCreen;
// 如果display返回常量引用,則調用set將引發錯誤
myScreen.display(cout).set('*');
(2)基於const的重載
建議:對於公共代碼使用私有功能函數
類類型
- 每個類定義了唯一的類型。對於兩個類來說,即使它們的成員完全一樣,這兩個類也是兩個不同的類型
(1)類的聲明
友元再探
(1)類之間的友元關係
class Screen{
friend class Window_mgr;
}
class Window_mgr{
public:
using ScreenIndex=std::vector<screen>::size_tyoe;
void clear(ScreenIndex);
private:
std::vector<Screen> screens{Screen(24,80,'')};
};
void Window_mgr::clear(ScreenIndex i){
Screen &s=screens[i];
s.centents=string(s.height * s.width,' ');
}
(2)令成員函數作爲友元
class Screen{
friend void Window_mrg::clear(ScreenIndex);
}
(3)函數重載和友元
(4)友元聲明和作用域
類的作用域
(1)作用域和定義在類外部的成員