#《Essential C++》读书笔记# 第四章 基于对象的编程风格

0
12

基础知识

Class的定义由两部分组成:class的声明,以及紧接在声明之后的主体。主体部分由一对大括号括住,并以分号结尾。主体内的两个关键字public和private,用来标示每个块的“member访问权限”。Public member可以在程序的任何地方被访问,private member只能在member function或是class friend内被访问。

class的前置声明(forward declaration),将class名称告诉编译器,并未提供此class的任何其他信息(像class支持的操作行为及所包含的data member等)。前置声明使我们得以进行类指针(class pointer)的定义,或以此class作为数据类型。

所有member function都必须在class主体内进行声明。至于是否同时进行定义,可自由决定。如果要在class主体内定义,这个member function会自动被视为inline函数。要在class主体之外定义member function,必须使用特殊的语法,目的在于分辨该函数究竟属于哪一个class,使用双冒号“::”,即所谓的class scope resolution(类作用域解析)运算符。

对于inline函数而言,定义与class主体内或主体外,并没有什么分别。然而就像non-member-inline function一样,它也应该放在头文件中。class定义一起inline member function通常都会被放在与class同名的头文件中。non-inline member function应该在程序代码文件中定义,该文件通常与class同名,其后接着扩展名.C、.cc、.cpp或.cxx(x代表横放的+)。

初始化data member,魔法不会自动产生,编译器不会自动为我们处理。如果我们提供一个或多个特别的初始化函数,编译器就会在每次class object被定义出来时,调用适当的函数加以处理。这些特别的初始化函数称为constructor(构造函数)。Constructor的函数必须与class名称相同。语法规定,constructor不应指定返回类型,亦不用返回任何值,它可以被重载(overloaded)。最简单的constructor是所谓的default constructor,它不需要任何参数(argument)。(没有参数不需要加括号,加括号会将其解释为一个函数,这是为了兼容C所致)。

和constructor对立的是destructor。所谓destructor乃是用户自定义的一个class member。一旦某个class提供有destructor,当其object结束生命时,便会自动调用destructor处理善后。Destructor主要用来释放在constructor中或对象生命周期中分配的资源。Destructor的名称有严格规定:class名称再加上‘~’前缀。它绝对不会有返回值,也没有任何参数。由于其参数列表是空的,所以也绝不可能别重载(overloaded)。Destructor并非绝对必要,我们没有义务非得提供destructor,事实上,C++编程的最难部分之一,便是了解何时需要定义destructor而何时不需要。

当我们设计class时,必须问问自己,在此class之上进行“成员逐一初始化”的行为模式是否适当?如果答案肯定,我们就不需要另外提供copy constructor。如果答案否定的,我们就必须另行定义copy constructor,并在其中编写正确的初始化操作。如果有必要为某个class编写copy constructor,那么同样有必要为它编写copy assignment operator。

static member function 可以在“与任何对象都无瓜葛”的情形之下调用。member function 只有在“不访问任何non-static member”的条件下才能够被声明为static,声明方式是在声明之前加上关键字static。当我们在class主体外部进行member function的定义时,无需重复加上关键字static,这个规则也适用于static data member。

运算符重载规则:

  • 不可以引入新的运算符。除了“.”、“.*”、“::”、“?:”四个运算符,其他运算符皆可被重载。
  • 运算符的操作数(operand)个数不可以改变。每个二元运算符都需要两个操作数,每个一元运算符都需要恰好一个操作数。因此,我们无法定义出一个equality运算符,并令它接受两个以上或两个以下的操作数。
  • 运算符的优先级(precedence)不可改变。例如,除法的运算优先级永远高于加法。
  • 运算符函数的参数列表中,必须至少有一个参数为class类型。也就是说,我们无法为诸如指针之类的non-class类型,重新定义其原已存在的运算符,当然无法为它引进新的运算符。

对于increment递增运算符(或递减)的重载,分为前置和后置两个版本。前置版的参数列表是空的,后置版的参数列表原本也应该时空,然而重载规则要求,参数列表必须独一无二。因此,C++想出一个变通办法,要求后置版得有一个int参数。int参数从何发生,又到哪里去,编译器会自动会后置产生一个int参数(其值必为0),用户不必为此烦恼。

所谓friend,具备了与class member function相同的访问权限,可以访问class的private member。只要在某个函数原型(prototype)前加上关键字friend,就可以将它声明为某个class的friend。这份声明可以出现在class定义的任意位置上,不受private或public的影响。如果你希望将数个重载函数都声明为某个class的friend,必须明确地为每个函数加上关键字friend。

maximal munch编译规则,此规则要求,每个符号序列(symbol seuqence)总是以“合法符号序列”中最长的那个解释。因为>>是个合法的运算符序列,因此如果两个>之间没有空白,这两个符号必定会被合在一起看待。同样道理,如果我们写下a+++p,它必定会被解释为“a++ +p”。

练习题答案

练习4.1 建立Stack.h和Stack.suffix,此处的suffix是你的编译器所能接受的扩展名,或是你的项目所使用的扩展名。编写main()函数,练习操作Stack的所有公开接口,并加以编译执行。程序代码文件和main()都必须包含Stack.h:#include “Stack.h”

Stack.h
#include <string>
#include <vector>

using namespace std;

class Stack
{
public:
    bool push(const string&);
    bool pop(string& elem);
    bool peek(string& elem);
    bool empty() const { return _stack.empty(); }
    bool full() const { return _stack.size() == _stack.max_size(); }
    int size() const { return _stack.size(); }
private:
    vector<string> _stack;
};

Stack.cpp
#include "Stack.h"

bool Stack::pop(string& elem)
{
    if (empty())    return false;
    elem = _stack.back();
    _stack.pop_back();
    return true;
}

bool Stack::peek(string& elem)
{
    if (empty())    return false;
    elem = _stack.back();
    return true;
}

bool Stack::push(const string& elem)
{
    if (full()) return false;
    _stack.push_back(elem);
    return true;
}

main.cpp
#include "Stack.h"
#include <iostream>

int main()
{
    Stack st;
    string str;
    while (cin >> str && !st.full())
    {
        st.push(str);
    }
    if (st.empty())
    {
        cout << '\n' << "Oops: no strings were read -- bailing out\n ";
        return 0;
    }
    st.peek(str);
    if (st.size() == 1 && str.empty())
    {
        cout << '\n' << "Oops: no strings were read -- bailing out\n ";
        return 0;
    }
    cout << '\n' << "Read in " << st.size() << " strings!\n"
        << "The strings, in reverse order: \n";
    while (st.size())
    {
        if (st.pop(str))
            cout << str << ' ';
    }
    cout << '\n' << "There are now " << st.size()
        << " elements in the stack!\n";
}

练习4.2 扩展Stack功能,以支持find()和count()两个操作。find()会查看某值是否存在而返回true或false。count()返回某字符串的出现次数。重新实现练习4.1的main(),让它调用这两个函数。

Stack_2.h
#include <string>
#include <vector>

using namespace std;

class Stack
{
public:
    bool push(const string&);
    bool pop(string& elem);
    bool peek(string& elem);
    bool empty() const { return _stack.empty(); }
    bool full() const { return _stack.size() == _stack.max_size(); }
    int size() const { return _stack.size(); }
    bool find(const string& elem) const;
    int count(const string& elem) const;
private:
    vector<string> _stack;
};

Stack_2.cpp
#include "Stack_2.h"
#include <algorithm>

bool Stack::pop(string& elem)
{
    if (empty())    return false;
    elem = _stack.back();
    _stack.pop_back();
    return true;
}

bool Stack::peek(string& elem)
{
    if (empty())    return false;
    elem = _stack.back();
    return true;
}

bool Stack::push(const string& elem)
{
    if (full()) return false;
    _stack.push_back(elem);
    return true;
}

bool Stack::find(const string& elem) const
{
    vector<string>::const_iterator end_it = _stack.end();
    return ::find(_stack.begin(), end_it, elem) != end_it;
}

int Stack::count(const string& elem) const
{
    return ::count(_stack.begin(), _stack.end(), elem);
}

main.cpp
#include "Stack_2.h"
#include <iostream>

int main()
{
    Stack st;
    string str;
    while (cin >> str && !st.full())
    {
        st.push(str);
    }
    if (st.empty())
    {
        cout << '\n' << "Oops: no strings were read -- bailing out\n ";
        return 0;
    }
    st.peek(str);
    if (st.size() == 1 && str.empty())
    {
        cout << '\n' << "Oops: no strings were read -- bailing out\n ";
        return 0;
    }
    cout << '\n' << "Read in " << st.size() << " strings!\n";
    cin.clear();    //清除end-of-file的设定
    cout << "what word to search for? ";
    cin >> str;
    bool found = st.find(str);
    int count = found ? st.count(str) : 0;
    cout << str << (found ? " is " : "isn't ") << "in the stack.";
    if (found)
        cout << "It occurs " << count << " times\n";
}

练习4.3 考虑以下所定义的全局(global)数据:

string program_name;
string vector_stamp;
int version_number;
int tests_run;
int tests_passed;

编写一个用以包装这些数据的类。

#include <string>

using std::string;

class globalWrapper
{
public:
    static int tests_passed() { return _tests_passed; }
    static int tests_run() { return _tests_run; }
    static int version_number() { return _version_number; }
    static string version_stamp() { return _version_stamp; }
    static string program_name() { return _program_name; }

    static void tests_passed(int nval) { _tests_passed = nval; }
    static void tests_run(int nval) { _tests_run = nval; }

    static void version_stamp(const string&nstamp)
    {
        _version_stamp = nstamp;
    }

    static void version_number(int nval)
    {
        _version_number = nval;
    }

    static void program_name(const string& npn)
    {
        _program_name = npn;
    }

private:
    static string _program_name;
    static string _version_stamp;
    static int _version_number;
    static int _tests_run;
    static int _tests_passed;
};

string globalWrapper::_program_name;
string globalWrapper::_version_stamp;
int globalWrapper::_version_number;
int globalWrapper::_tests_run;
int globalWrapper::_tests_passed;

练习4.4 一份“用户概况记录(user profile)”内含以下数据:登陆记录、实际姓名、登入次数、猜过次数、猜对次数、等级——包括初级、中级、高级、专家、以及猜对百分比(可实时计算获得,或将其值储存起来备用)。请写出一个名为UserProfile的class,提供以下操作:输入、输出、相等测试、不等测试。其constructor必须能够处理默认的用户等级、默认的登陆名称(“guest”)。对于同样的名为guest的多个用户,你如何保证每个guest有他自己独有的登陆会话(login session),不会和其他人混淆?

UserProfile.h
#include <iostream>
#include <string>
#include <map>

using namespace std;

class UserProfile
{
public:
    enum uLevel
    {
        Beginner, Intermediate, Advanced, Guru
    };
    UserProfile(string login, uLevel = Beginner);
    UserProfile();

    //default memberwise initilaization和default memberwise copy已足够所需,
    //不必另行设计copy constructor或copy assignment operator,
    //也不需要destructor

    bool operator==(const UserProfile&);
    bool operator!=(const UserProfile& rhs);

    //以下函数用来读取数据
    string login()const { return _login; }
    string user_name() const { return _user_name; }
    int login_count() const { return _times_logged; }
    int guess_count() const { return _guesses; }
    int guess_correct() const{ return _correct_guesses; }
    double guess_average() const;
    string level() const;

    //以下函数用来写入数据
    void reset_login(const string& val) { _login = val; }
    void user_name(const string& val) { _user_name = val; }

    void reset_level(const string&);
    void reset_level(uLevel newlevel) { _user_level = newlevel; }

    void reset_login_count(int val) { _times_logged = val;}
    void reset_guess_count(int val) { _guesses = val; }
    void reset_guess_correct(int val) { _correct_guesses = val; }

    void bump_login_count(int cnt = 1) { _times_logged += cnt; }
    void bump_guess_count(int cnt=1) { _guesses = cnt; }
    void bump_guess_correct(int cnt = 1) { _correct_guesses = cnt; }

private:
    string _login;
    string _user_name;
    int _times_logged;
    int _guesses;
    int _correct_guesses;
    uLevel _user_level;

    static map<string, uLevel> _level_map;
    static void init_level_map();
    static string guest_login();
};

inline double UserProfile::guess_average() const
{
    return _guesses
        ? double(_correct_guesses) / double(_guesses) * 100
        : 0.0;
}

inline UserProfile::UserProfile(string login,uLevel level)
    :_login(login),_user_level(level),
    _times_logged(1), _guesses(0), _correct_guesses(0) {}

#include <cstdlib>

inline UserProfile::UserProfile()
    : _login("guest"), _user_level(Beginner),
    _times_logged(1), _guesses(0), _correct_guesses(0)
{
    static int id = 0;
    char buffer[16];

    //_itoa()是C标准库所提供的的函数,会将整数转换为对应的ASCII字符串形式
    _itoa(id++, buffer, 10);

    //针对guest,加入一个独一无二的会话标识符(session id)
    _login += buffer;
}

inline bool UserProfile::
operator==(const UserProfile& rhs)
{
    if (_login == rhs._login && _user_name == rhs._user_name)
        return true;
    return false;
}

inline bool UserProfile::operator!=(const UserProfile& rhs)
{
    return !(*this == rhs);
}

inline string UserProfile::level() const
{
    static string _level_table[] = {
        "Beginner","Intermediate","Advanced","Guru"
    };
    return _level_table[_user_level];
}

//以下难度颇高,不过恰可作为示范
map<string, UserProfile::uLevel> UserProfile::_level_map;

void UserProfile::init_level_map()
{
    _level_map["Beginner"] = Beginner;
    _level_map["Intermediate"] = Intermediate;
    _level_map["Advanced"] = Advanced;
    _level_map["Guru"] = Guru;
}

inline void UserProfile::reset_level(const string& level)
{
    map<string, uLevel>::iterator it;
    if (_level_map.empty())
        init_level_map();

    //确保level的确代表一个可识别的用户等级
    _user_level = ((it = _level_map.find(level)) != _level_map.end()) ? it->second : Beginner;
}

main.cpp
#include "UserProfile.h"
#include <iostream>

ostream& operator <<(ostream& os, const UserProfile& rhs)
{
    //输出格式,如:stanl Beginner 12 100 10 10%
    os << rhs.login() << ' '
        << rhs.level() << ' '
        << rhs.login_count() << ' '
        << rhs.guess_count() << ' '
        << rhs.guess_correct() << ' '
        << rhs.guess_average() << endl;
    return os;
}

istream& operator >>(istream& is, UserProfile& rhs)
{
    //是的,以下假设所有输入都有效,不做错误检验
    string login, level;
    is >> login >> level;
    int lcount, gcount, gcorrect;
    is >> lcount >> gcount >> gcorrect;
    rhs.reset_login(login);
    rhs.reset_level(level);
    rhs.reset_login_count(lcount+10);
    rhs.reset_guess_count(gcount);
    rhs.reset_guess_correct(gcorrect);
    return is;
}

int main()
{
    UserProfile anon;
    cout << anon;    //测试output运算符
    UserProfile anon_too;    //看看我们是否取得一份独一无二的标识符
    cout << anon_too;

    UserProfile anna("Annal", UserProfile::Guru);
    cout << anna;
    anna.bump_guess_count(27);
    anna.bump_guess_correct(25);
    anna.bump_login_count();
    cout << anna;
    cin >> anon;    //测试input运算符
    cout << anon;
    return 0;
}

练习4.5 请实现一个4*4的Martix class,至少提供以下接口:矩阵加法、矩阵乘法、打印函数print()、复合运算符+=,以及一组支持下标操作(subscripting)的function call运算符,像下面这样:

float& operator()(int row, int cloumn);
float operator()(int row, int cloumn) const;

请提供一个default constructor,可选择地接受16个数据值。再提供一个constructor,可接受一个拥有16个元素的数组。你不需要为此class 提供copy constructor,copy assignment operator、destructor。第六章重新实现Matrix class时才会需要这几个函数,用以支持任意行列的矩阵。

Matrix.h
#include <iostream>

using namespace std;

typedef float elemType;    //方便我们转为template

class Matrix
{
    //friend声明不受访问权限的影响
    //我喜欢把它们放在class一开始处
    friend Matrix operator+(const Matrix&, const Matrix&);
    friend Matrix operator*(const Matrix&, const Matrix&);

public:
    Matrix(const elemType*);
    Matrix(const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0.,
        const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0.,
        const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0.,
        const elemType = 0., const elemType = 0., const elemType = 0., const elemType = 0.);

    //不需要为Matrix提供copy constructor、destructor、
    //copy assignment operator

    //简化“转换至通用型矩阵(general Matrix)”的过程
    int rows() const { return 4; }
    int cols() const { return 4; }

    ostream& print(ostream&) const;
    void operator+=(const Matrix&);
    elemType operator()(int row, int column) const
    {
        return _Matrix[row][column];
    }
    elemType& operator()(int row, int column) 
    {
        return _Matrix[row][column];
    }

private:
    elemType _Matrix[4][4];
};

inline ostream& operator<<(ostream& os, const Matrix& m)
{
    return m.print(os);
}

Matrix operator+(const Matrix& m1, const  Matrix& m2)
{
    Matrix result(m1);
    result += m2;
    return result;
}

Matrix operator*(const Matrix& m1, const Matrix& m2)
{
    Matrix result;
    for (int ix = 0;ix < m1.rows();ix++)
    {
        for (int jx = 0;jx < m1.cols();jx++)
        {
            result(ix, jx) = 0;
            for (int kx = 0;kx < m1.cols();kx++)
            {
                result(ix, jx) += m1(ix, kx) * m2(kx, jx);
            }
        }
    }
    return result;
}

void Matrix::operator+=(const Matrix& m)
{
    for (int ix = 0;ix < 4;++ix)
    {
        for (int jx = 0;jx < 4;++jx)
        {
            _Matrix[ix][jx] += m._Matrix[ix][jx];
        }
    }
}

ostream& Matrix::print(ostream& os) const
{
    int cnt = 0;
    for (int ix = 0;ix < 4;++ix)
    {
        for (int jx = 0;jx < 4;++jx, ++cnt)
        {
            if (cnt && !(cnt % 8)) 
                os << endl;
            os << _Matrix[ix][jx] << ' ';
        }
    }
    os << endl;
    return os;
}

Matrix::Matrix(const elemType* array)
{
    int array_index = 0;
    for (int ix = 0;ix < 4;++ix)
    {
        for (int jx = 0;jx < 4;++jx)
            _Matrix[ix][jx] = array[array_index++];
    }
}

Matrix::Matrix(elemType a11, elemType a12, elemType a13, elemType a14,
    elemType a21, elemType a22, elemType a23, elemType a24,
    elemType a31, elemType a32, elemType a33, elemType a34,
    elemType a41, elemType a42, elemType a43, elemType a44)
{
    _Matrix[0][0] = a11;_Matrix[0][1] = a12;
    _Matrix[0][2] = a13;_Matrix[0][3] = a14;
    _Matrix[1][0] = a21;_Matrix[1][1] = a22;
    _Matrix[1][2] = a23;_Matrix[1][3] = a24;
    _Matrix[2][0] = a31;_Matrix[2][1] = a31;
    _Matrix[2][2] = a33;_Matrix[2][3] = a34;
    _Matrix[3][0] = a41;_Matrix[3][1] = a42;
    _Matrix[3][2] = a43;_Matrix[3][3] = a44;
}

main.cpp
#include "Matrix.h"

int main()
{
    Matrix m;
    cout << m << endl;
    elemType ar[16] = {
        1.,0.,0.,0.,0.,1.,0.,0.,
        0.,0.,1.,0.,0.,0.,0.,1.,
    };
    Matrix identity(ar);
    cout << identity << endl;

    Matrix m2(identity);
    m = identity;
    cout << m2 << endl;
    cout << m << endl;

    elemType ar2[16] = {
        1.3,0.4,2.6,8.2,6.2,1.7,1.3,8.3,
        4.2,7.4,2.7,1.9,6.3,8.1,5.6,6.6
    };
    Matrix m3(ar2);
    cout << m3 << endl;
    Matrix m4 = m3 * identity;
    cout << m4 << endl;
    Matrix m5 = m3 + m4;
    cout << m5 << endl;
    m3 += m4;
    cout << m3 << endl;
    return 0;
}

end。

“没有一个冬天不可逾越,没有一个春天不会来临。”

<

发布回复

请输入评论!
请输入你的名字