类
访问控制与封装
class 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(const std::string &s) : bookNo(s) {}
Sales_data(std::istream &);
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;
};
class 和 struct 都可以定义类,当我们希望定义的类的所有成员是 public 的时,使用 struct;反之,如果希望成员是 private 的,使用 class。
友元
类可以允许其它类或者函数访问它的非公有成员,方法是令其它类或者函数成为它的友元(friend)。
class Sales_data {
public:
// 为 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 &);
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p * n) {}
Sales_data(const std::string &s) : bookNo(s) {}
Sales_data(std::istream &);
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;
};
Sales_data add(const Sales_data &, const Sales_data &);
std::istream &read(std::istream &, Sales_data &);
std::ostream &print(std::ostream, const Sales_data &);
友元声明只能出现在类定义的内部,友元不是类的成员也不受它所在区域访问控制级别的约束。
友元声明仅仅指定了访问权限,而非一个通常意义上的函数声明,需要在友元声明之外再专门对函数进行一次声明。
友元类
如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员的所有成员。
class Screen {
// Window_mgr 的成员可以访问 Screen 类的私有部分
friend class Window_mgr;
// Screen 类的剩余部分
}
友元关系不存在传递性,Window_mgr 有自己的友元,这些友元不具有访问 Screen 的特权。
成员函数作为友元
class Screen {
// Window_mgr::clear 必须在 Screen 类之前被声明
friend void Window_mgr::clear(ScreenIndex);
// Screen 类的剩余部分
}
隐式的类类型转换
如果构造函数知接收一个实参,则它实际上定义了转换为此类类型的隐式转换规则,也称为转换构造函数。
string null_book = "9-999-99999-9";
// 构造一个临时的 Sales_data 对象
// 该对象的 bookNo 等于 null_book,其它都是默认值
item.combine(null_book);
抑制构造函数定义的隐式转换。将构造函数声明为 explicit 加一阻止:
class Sales_data {
public:
Sales_data() = default;
explicit Sales_data(const std::string &s) : bookNo(s) { };
}
拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数时拷贝构造函数。
class Foo {
public:
Foo(); // 默认构造函数
Foo(const Foo&); // 拷贝构造函数
}
拷贝构造函数通常用于:
- 通过使用另一个同类型的对象初始化新创建的对象
- 复制对象把它作为参数传递给函数
- 复制对象,并从函数返回这个对象
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
析构函数
析构函数释放对象使用的资源,并销毁对象的非 static 数据成员。
class Foo {
public:
~Foo(); // 析构函数
}
对一个给定类,只会有唯一一个析构函数。
class with pointer members 必须由拷贝构造和拷贝赋值,否则会有内存泄漏等问题
*String.h*
class String {
public:
explicit String(const char *cstr = nullptr);
String(const String &str); // 拷贝构造
String &operator=(const String &str); // 拷贝赋值
~String(); // 析构函数
char *get_c_str() const { return m_data; }
private:
char *m_data;
};
*String.cpp*
inline
String::String(const char *cstr) {
if (cstr) {
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
} else {
m_data = new char[1];
*m_data = '\0';
}
}
String::String(const String &str) {
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
String &String::operator=(const String &str) {
if (this == &str) {
return *this;
}
// 先删除原先的数据
delete[] m_data;
// 重新申请内存
m_data = new char[strlen(str.m_data) + 1];
// 拷贝
strcpy(m_data, str.m_data);
return *this;
}
String::~String() {
delete[] m_data;
}
OOP
面向对象程序设计(object-oriented programming)的核心思想是数据抽象、继承和动态绑定。
继承
基类
class Quote {
public:
std::string isbn() const;
virtual double net_price(std::size_t n) const; // 虚函数
};
派生类
#include "Quote.h"
class Bulk_quote : public Quote { // Bulk_quote 继承了 Quote
public:
double net_price(std::size_t n) const override;
};
派生类必须在其内部对所有重新定义的虚函数进行声明。
纯虚函数(pure virtual)
在基类中实现纯虚函数的方法是在函数原型后加 =0:
virtual void funtion1()=0
含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类(abstract base class)。