类的基本思想是数据抽象和封装。
数据抽象是一种依赖于接口和实现分离的编程技术;类的接口包括用户所能执行的操作,类的实现包括类的数据成员、负责接口实现的函数体以及定义类所需的各种是有函数
定义抽象数据类型
设计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)作用域和定义在类外部的成员