类和OOP

131 阅读4分钟

访问控制与封装

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

classstruct 都可以定义类,当我们希望定义的类的所有成员是 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)