类
定义抽象数据类型->访问控制与封装->类的其他特性->类的作用域->构造函数再探->类的静态成员
类的基本思想是:数据抽象和封装
定义抽象数据类型
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.0;
};
//Sales_data的非成员接口函数
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
this
成员函数通过调用一个名为this的额外隐式参数来访问调用它的那个对象。当调用成员函数时,用请求该函数的对象地址初始化this。默认情况下,this的类型是指向类类型非常量版本的常量指针,即Sales_data *const。对于isbn() const这个函数来说,this是一个常量指针,不允许改变this中保存的地址。
返回this对象的函数:
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
...
return *this; //返回调用该函数的对象
}
const成员函数(常量成员函数)
std::string isbn() const {return bookNo;}
这里const的作用是修改隐式this指针的类型。
不能在一个常量对象上调用普通的成员函数。(因为这相当于把一个普通成员函数里的非常量this指针绑定到一个常量对象上)。常量对象,以及常量对象的指针或引用都只能调用常量成员函数。非常量对象可以调用常量函数。
常量成员函数不修改对象
在类的外部定义常量成员函数时不要忘了也要加上const
类的编译
首先编译成员的声明,然后才轮到成员的函数体。所以成员函数体可以随意使用类中的其他成员而无需在意这些成员的出现次序。
构造函数
默认构造函数:控制默认初始化过程的函数。无任何实参。编译器创建的构造函数被称为合成的默认构造函数。编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数。
注意:含有内置类型或复合类型成员的类应该在类的内部初始化这些成员,或者定义一个自己的默认构造函数。否则用户在创建类的对象时就可能得到未定义的值。因为如果定义在块中的内置类型或复合类型的对象默认初始化,则他们的值是未定义的。换句话说,如果类包含有内置类型或者复合类型的成员,则只有当这些成员全都被赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数。
类内初始值必须以=或花括号表示。
默认构造函数
Sales_data() = default;
如果 =default在类的内部,则是内联的。
这个默认构造函数有效是因为我们为内置类型提供了初始值。如果不能提供初始值,就要使用构造函数初始值列表。
构造函数初始值列表
Sales_data(const std::string &s, unsigned n, double p);
bookNo(s), units_sold(n), revenue(p*n) {}
成员初始化的顺序和他们在类中定义的顺序一样,初始值列表里的顺序不影响。除非是要用初始值列表中的一个成员来初始化另一个成员,才有顺序要求。
如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,必须通过构造函数初始值列表的方法为这些成员提供初值。
访问控制与封装
到目前为止,只给类定义了接口,但还没有封装,也就是说用户可以直达Sales_data对象内部并且控制它的具体实现细节。加强类的封装性,可以使用访问说明符。
访问说明符:public,private(可以被类的成员函数访问,不能被使用该类的代码访问)
class和struct关键字
struct和class的唯一区别是默认访问权限不一样:class的默认访问权限是private,struct是public
友元
类可以允许其他类或者函数访问它的非公有成员,方法就是令其他类或者函数成为它的友元。
class Sales_data{
friend Sales_data add(const Sales_data&, const Sales_data&); // add不是类的成员函数
....
}
Sales_data add(const Sales_data&, const Sales_data&);
注意:友元声明只能出现在类定义的内部。友元不是类的成员。
类的其他特性
类成员再探
定义类型成员:即某种类型在类中的别名
class Screen{
public:
typedef std::string::size_type pos;
//或者:using pos = std::string::size_type;
pos clear();
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
使用:
Screen::pos Screen::clear()
{...}
注意:类不能重复定义外层作用域中的某个类型名
typedef double Money;
class Account{
public:
Money balance() {return bal;}
private:
typedef double Money; // 错误,外层作用域已经定义过这个类型了
};
令成员函数作为内联函数
inlind Screen &Screen::move(pos r, pos c){...}
可变数据成员
有时候希望即使是在一个常量成员函数或常量对象内也可以修改某个数据成员,这时候需要把他声明为mutable关键字
mutable size_t access_ctr; // 在一个const对象内也能被修改
类类型
两种写法都可以:
Sales_data item1;
class Sales_data item1;
类的定义和声明可以分开:
class Screen; // 这时候他是一个不完全类型
可以定义指向这种类型的指针或引用,可以声明以不完全类型作为参数或者返回类型的函数。
友元再探
类之间的友元:
class Screen{
friend class Window_mgr;
};
class Window_mgr{
public:
void clear();
};
void Window_mgr::clear()
{
Screen &s = screen[i];
}
其他类的成员函数作为友元:
class Screen{
friend void Window_mgr::clear();
};
委托构造函数
一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程。
Sales_data() : Sales_data(“”, 0, 0) {}
Sales_data(std::istream &is): Sales_data() {read(is, *this); }
默认构造函数的作用
当对象被默认初始化或者值初始化时自动执行默认构造函数。
默认初始化:
在块作用域内不使用任何初始值定义一个非静态变量或者数组
类含有类类型的成员且使用合成的默认构造函数时
类类型的成员没有在构造函数初始值列表中显示地初始化时
值初始化:
数组初始化过程中如果提供的初始值少于数组大小
不使用初始值定义一个局部静态变量
写形如T()的表达式显式请求值初始化,T是类型名
类必须包含一个默认构造函数以便上述情况发生:
class NoDefault{
public:
NoDefault(const std::string&);
//缺少默认构造函数
};
struct A{
NoDefault my_mem;
};
A a; // 错误!不能为A合成构造函数
struct B{
B() {}
NoDefault b_member; // 错误!b_member没有初始值
}
使用默认构造函数
Sales_data obj;
错误用法: Sales_data obj(); // 错误!这是定义了一个返回值是Sales_data类型的函数
隐式的类类型转换
如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,这种构造函数称为转换构造函数。注意,只允许一步类类型转换。
隐式转换用法:
string null_book = “9-999-99999-9”
item.combine(null_book); //这里首先把null_book转换成item的类类型,然后调用item的combine函数
还用来执行拷贝形式的初始化:
Sales_data item2 = null_book
抑制构造函数定义的隐式转换explicit
explicit Sales_data(const std::string &s): bookNo(s) {}
注意:explicit关键字只能在类内声明构造函数时使用,在外部定义时不应重复。被定义了explicit关键字的函数,不能用于以上转换用法
这时候可以用static_cast使用explicit的构造函数来显式转换:
item.combine(static_cast<Sales_data>(cin));
聚合类
1) 所有成员都是public
2) 没有定义任何构造函数
3) 没有类内初始值
4) 没有基类,没有virtual函数
聚合类可以显式初始化:
Data val1 = {0, “Anna”};
类的静态成员
有时候需要他的一些成员与类本身直接相关,而不是与单个类的对象相关。这时候没有必要每个类的对象都存储这个静态成员。
声明静态成员: static
静态成员函数不包含this指针。静态成员的定义不能重复static关键字。