《EssentialC++》笔记2—基于对象的编程风格

  本文主要用于笔者复习参考,很多地方写的比较粗略,还请见谅。



  一般而言,class由两部分组成:一组公开(public)的操作函数和运算符,以及一组私有的(private)实现细节。这些操作函数和运算符称为class的member function(成员函数),并代表这个class的公开接口。身为class的用户,只能访问其公开接口。这也就是我们使用string和vector的方式。

  Class的private实现细节可由member function的定义以及与此class相关的任何数据组成。

4.1 如何实现一个Class

  一般先从抽象开始。

  public可以在程序的任何地方被访问,private只能在成员函数或者是class friend内访问。

​  所有成员函数必须在class主体内进行声明。至于是否要同时进行定义,可以自由决定。

  如果要在class主体内定义,这个成员函数会被自动地视为inline函数。例如,size()即是Stack的一个inline member。要在class主体之外定义member function,必须使用特殊的语法,目的在于分辨该函数究竟属于哪个class。如果希望该函数为inline,应该在最前面指定关键字inline:

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

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

inline bool Stack::full()
{
	return _stack.size() == _stack.max_size();
}

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)
{
	if (empty())
		return false;
	for (int i = 0; i < _stack.size(); i++)
	{
		if (_stack[i] == elem)
			return true;
	}
	return false;
}

int Stack::count(const string& elem)
{
	int count = 0;
	for (int i = 0; i < _stack.size(); i++)
	{
		if (_stack[i] == elem)
			count++;
	}
	return count;
}

Stack::是在告诉编译器和程序读者,empty()是Stack的成员。

:://作用域解析运算符

  对于inline函数而言,定义在class主体内或class主体外没有啥区别。如同所有的内联函数一样,它的定义应该放在头文件中。一般class定义及其inline member function都应该放在头文件中。

  非inline member function应该在.cpp文件里头写。

4.2 构造函数和析构函数

  构造函数:名字和类名相同

  用参数或()来构造类:

class Triangular {
public:
	//Triangular();//第一种默认无参构造函数
	Triangular(int len = 1, int bp = 1);
	//第二种默认构造函数,缺省构造函数
private:
	int _length;//元素个数
	int _beg_pos;//起始位置
	int _next;//起始位置的迭代目标
};
//Triangular::Triangular()
//{
//	  _length = 1;
//	  _beg_pos = 1;
//	  _next = 0;
//}

Triangular::Triangular(int len, int bp)
{
	_length = len > 0 ? len : 1;
	_beg_pos = bp > 0 ? bp : 1;
	_next = _beg_pos - 1;
}

  这里要注意一种情况,如果成员是一个没有默认无参构造函数的对象,那么就不能用无参的构造函数来作为构造函数了,因为这时候不存在成员对象的无参构造函数,所以会报错。

在这里插入图片描述

在这里插入图片描述

这样就能过了

在这里插入图片描述

  语法就是在:后面用它的含参构造函数,之间用逗号隔开。

Triangular::Triangular():s(1,2),_name("rotaer")
{
	_length = 1;
	_beg_pos = 1;
	_next = 0;
}

  也可以直接用一个类对象初始化。

Triangular(Triangular& i):s(1,2)
	{
		_length = i._length;
		_beg_pos = i._beg_pos;
		_next = i._next;
		_name = i._name;
	}
class String {
public:
	String(int x = 1, int y = 1)
	{
		a = x;
		b = y;
	}
private:
	int a;
	int b;
};


class Triangular {
public:
	//Triangular();//默认无参构造函数
	Triangular(int len = 1, int bp = 1, string name = "rotater");
	//默认缺省构造函数
	Triangular(Triangular& i)
	{
		_length = i._length;
		_beg_pos = i._beg_pos;
		_next = i._next;
		_name = i._name;
		s = i.s;
	}
private:
	int _length;//元素个数
	int _beg_pos;//起始位置
	int _next;//起始位置的迭代目标
	string _name;
	String s;
};
//Triangular::Triangular():s(25, 26),_name("rotaer")
//{
//	_length = 1;
//	_beg_pos = 1;
//	_next = 0;
//}

Triangular::Triangular(int len, int bp, string name):s(1,1)
{
	_length = len > 0 ? len : 1;
	_beg_pos = bp > 0 ? bp : 1;
	_next = _beg_pos - 1;
	_name = name;
}

int main()
{
	Triangular t;
	Triangular s(t);
	Triangular lam(5, 4, "jiangliwen");
	return 0;
}

析构函数(destructor)

  析构函数是用户自定义的一个成员函数。一旦某个class提供有析构函数,当其对象结束生命时,便会自动调用析构函数处理善后。析构函数主要用来释放在构造函数中或对象生命周期中分配的资源。

  析构函数的名称有严格规定:class名称加上~,它没有返回值,也没有任何参数,由于其参数列表是空的,所以不可能被重载。

class Matrix {
public:
	Matrix(int row = 1, int col = 1)
	{
		_row = row > 0 ? row : 1;
		_col = col > 0 ? col : 1;
		_arr = new double[_row * _col];
	}
	~Matrix()
	{
		delete[]_arr;
	}
private:
	int _row;
	int _col;
	double* _arr;
};

  析构函数并非必要,若析构函数没什么事情好做,其实没必要写这函数。

成员逐一初始化

  一般来说,我们可以用一个对象去给另一个对象赋初值,如:

Triangular tri1(8);
Triangular tri2 = tri1;

  class的成员会被依次复制,本例中的_length、_beg_pos、_next都会从tri1依次复制到tri2,这就是默认的成员逐一初始化操作。

  但对于Matrix class来说,默认的成员逐一初始化操作并不合适,因为它是浅拷贝

如:

{
    Matrix mat(4, 4);
    //此处,构造函数发生作用
    {
        Matrix mat2 = mat;
        //此处,调用默认的成员逐一初始化操作
        //此处 mat2的析构函数发生作用
    }
    //使用mat
    //此处 mat1的析构函数发生作用
}

  默认的成员逐一初始化操作会这样:

mat2._arr = mat._arr;

  这会使得mat2的_arr和mat的_arr指向同一个对象,你在第一个括号已经释放掉了它就再在后面用mat的_mat就会出问题。

  这种情况我们可以提供一个拷贝构造函数或者重载一下分配操作符,这里我们写一下拷贝构造函数。

Matrix::Matrix(Matrix& mat)
{
	_row = mat._row;
	_col = mat._col;
	_arr = new double[_row * _col];
	for (int i = 0; i < _row * _col; i++)
	{
		_arr[i] = mat._arr[i];
	}
}

  当我们设计class的时候,必须考虑在这个class之上的默认成员逐一初始化函数是否适当。如果适当,就不需要提供拷贝构造函数,如果不适当需要我们自己在给拷贝构造函数。

4.3 const和mutable

const

  如果我们要传一个对象的常引用并且要调用对象的成员函数,这个成员函数必须不能修改对象的数据成员的值,怎么做呢,在函数后面加const修饰符,示例如下:

class test {
public:
	int length()const { return _length; };
	int beg_pos()const { return _beg_pos; };
	int elem(int pos) const;

	bool next(int& val);
	void next_reset() { _next = _beg_pos - 1; };
private:
	int _length;
	int _beg_pos;
	int _next;

	static vector<int> _ivec;
};

int test::elem(int pos) const
{
	return _ivec[pos - 1];
}

bool test::next(int& val)
{
	if (_next < _beg_pos + _length - 1)
	{
		val = _ivec[_next++];
		return true;
	}
	return false;
}

  编译器会检查每个标记为const的成员函数,看它们是否真的没有更改类成员的内容,如果更改了就会报错。

  这里有一些关于常量对象的问题,假如我们有以下代码:

class val_class {
public:
    val_class(const BigClass& v):_val(v)
    {};
    BigClass& val() const { return _val; };
private:
    BigClass _val;
}

  语法层面好像没有问题,val()函数确实没有修改_val,但是它把_val返回出去了,别人可以通过引用修改它了,我不期望这样,我希望如果你调用我的val(),它是个const标识的成员函数,你就不应该能修改返回的数据成员,解决方法是利用const的修饰也可以使函数重载。

class val_class {
public:
    val_class(const BigClass& v):_val(v)
    {};
    const BigClass& val() const { return _val; };
    BigClass& val() { return _val; };
private:
    BigClass _val;
}

  const修饰的对象,其为常量对象,所以它只能调用const修饰的函数。非const修饰的对象,其不是常量对象,所以其会调用普通的val()函数,返回一个的引用可以修改也没有关系。

mutable

  如果有这样的需求,我的某个数据成员在这个需求下必须要修改,但是我这个函数传的是对象的常引用也就是说主要表示其功能的数据没有被修改,但是有些负责迭代的对象遭到修改了,我们这时可以给这个对象加上mutable关键字,表示对这个数据成员的修改不会影响整个对象的常量性。

class test {
public:
	int length()const { return _length; };
	int beg_pos()const { return _beg_pos; };
	int elem(int pos) const;

	bool next(int& val);
	void next_reset() const{ _next = _beg_pos - 1; };
private:
	int _length;
	int _beg_pos;
	mutable int _next;

	static vector<int> _ivec;
};

4.4 this指针

  同样的成员函数,它是怎么知道谁调用的它呢?编译器会自动将this指针加到每一个成员函数的参数列表中。

tr1.copy(tr2);
//转化为
copy(&tr1, tr2);
//返回this指针指向的对象
return *this;
class Triangular {
public:
	//Triangular();//默认无参构造函数
	Triangular(int len = 1, int bp = 1)
	{
		_length = len > 0 ? len : 1;
		_beg_pos = bp > 0 ? bp : 1;
		_next = _beg_pos - 1;
	}
	Triangular(Triangular& i)
	{
		_length = i._length;
		_beg_pos = i._beg_pos;
		_next = i._next;
	}
	Triangular& copy(const Triangular& tr1);
private:
	int _length;//元素个数
	int _beg_pos;//起始位置
	int _next;//起始位置的迭代目标
};
Triangular& Triangular::copy(const Triangular& tr1)
{
	if (this != &tr1)
	{
		this->_length = tr1._length;
		this->_beg_pos = tr1._beg_pos;
		this->_next = tr1._next;
	}
	return *this;
}

4.5 静态类成员

  static data member用来表示唯一的、可共享的member,他可以在同一类的所有对象中被访问。

  就如同全局变量的使用一样,由于对class而言,static data member只有唯一的一份实体,因此我们在使用前必须在程序代码文件中提供清楚的定义。这种定义看起来很像全局变量的定义。唯一的区别是,其名称必须附上类作用区域运算符。

vector<int> Triangular::_elems;
//也可以指定初值
int Triangular::_initial_size = 8;

  要在成员函数中访问静态数据成员,如同访问一般数据成员一样就行。

  静态成员函数

  一般来说,成员函数必须通过某个对象来调用,这个对象会被绑定到该成员函数的this指针,通过存储于每个对象中的this指针,成员函数才能够访问存储于每个对象中的非静态数据成员。

  但是有些情况,我并不是要去访问非静态数据成员,我想访问的是所有对象公用一份的静态类数据成员,这时候我没有理由必须要某个对象来调用我吧,于是 static member function应运而生。

  声明时,在其返回类型前面写static。

class test {
public:
    static bool getval(int pos, int& ret);
private:
    static vector<int> _ivec;
}
//在再写他的定义的时候不用再加static了,这点数据成员
bool test:: getval(int pos, int& ret)
{
    if (pos > _ivec.size())
        return false;
    ret = _ivec[pos - 1];
    return true;
}

4.6 打造一个Iterator Class

Triangular trian(1, 8);
Triangular::iterator
    it = trian.begin(),
    end_it = trian.end();
while (it != end_it)
{
    cout << *it << ' ';
    ++it;
}

  为了能让上述代码工作,我们必须为iterator class定义!= * ++等运算符,如何做到呢?运算符重载

  因为有this指针的机制,两个操作数的运算符实际上只有一个参数,一个操作数的运算符没有参数。

class Triangular_iterator {
public:
	Triangular_iterator(int index) :_index(index - 1) {}
	bool operator==(const Triangular_iterator&) const;
	bool operator!=(const Triangular_iterator&) const;
	int operator*() const;
	Triangular_iterator& operator++();//前置++
	Triangular_iterator operator++(int);//后置++
private:
	void check_integrity() const;
	int _index;
};

  Triangular_iterator维护了一个索引值,用以索引Triangular中用来储存数列元素的那个static data member,也就是_elems,为了能让他访问Triangular的私有数据成员,Triangular必须赋予Triangular_iterator特殊的访问权限,我们在后续会用friend机制来实现这一点。

inline bool Triangular_iterator:: 
operator==(const Triangular_iterator& rhs)const
{
    return _index == rhs._index;
}

  如果运算符性质和另一个运算符相反,那么可以用后者实现出前者

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

以下为运算符重载的规则:

  • 不可以引入新的运算符。除了.、.*、::、?:四个运算符,其他运算符都可以重载。
  • 运算符的操作数的个数不可以改变
  • 运算符的优先级不可改变
  • 运算符的参数列表中,必须有一个参数是class类型,也就是说运算符重载只针对于class类型,对像指针这样的非class类型,无法对它进行运算符重载。

  运算符的定义方式,可以像成员函数一样:这时左操作数默认是this指针指的对象。

inline int Triangualr_iterator:: operator *()const
{
    check_integrity();
    return Triangular::_elem[_index];
}

  也可以像非成员函数那样,这种情况通常是默认的this指针为第一操作数并不方便的情况。

inline int operator*(const Triangular_iterator &rhs)
{
    rhs.check_integrity();
    return Triangular::_elems[_index];
}

  非成员的运算符函数的参数列表里,一定会比相应的member运算符多出一个参数,也就是this指针,对成员运算符来说,这个this指针隐式的代表左操作数。

  展开check_integrity函数,这是一个检查是否达到最大上限并且增添元素以保证能取到有效元素的函数。

inline void Triangular_iterator::check_integrity() const
{
    if (_index >= Triangular::_max_elems)
        throw iterator_overflow();
    if (_index >= Triangular::_elems.size())
        Triangular::gen_elements(_index + 1)l
}

前置++

inline Triangular_iterator& Triangular_iterator:: operator++()
{
    _index++;
    check_integrity();
    return *this;
}

  后置++本来也应该没有参数,但是为了重载这个运算符,我们就假装要求这个运算符有个参数,编译器会自动为后置版生成一个int参数(其值必为0)。

inline Triangular_iterator& Triangular_iterator:: 
operator++(int)
{
    Triangular_iterator tmp = *this;
    _index++;
    check_integrity();
    return tmp;
}

  下面我们为Triangular提供一个begin()和一个end()

class Triangular {
public:
    typedef Triangular_iterator iterator;
    Triangular_iterator begin() const
    {
        return Triangular_iterator(_beg_pos);
    }
    Triangular_iteraotr end() const
    {
        return Triangular(_beg_pos + _length);
    }
private:
    int _beg_pos;
    int _length;
    static vector<int> _elems;
};

  这样typedef以后用户再使用iterator就不会知道它是具体的那个类来实现的了。

Triangular::iterator = trian.begin();

4.7 friend

  上面的定义中,许多非成员操作符函数会直接访问Triangular的private _elems以及Triangular_iterator的private check_integrity()

  为此,我们需要operator*()声明为”朋友“。

class Triangular {
    friend int operator*(const Triangular_iterator &rhs);
};

class Triangular_iterator {
    friend int operator*(const Triangular_iterator &rhs);
}
class Triangular {
    friend int Triangular_iterator:: operator*();
    friend void Triangular_iterator:: check_integrity();
}

  这种定义我们必须先把Triangular_iterator的定义写好,不然编译器没法知道operator*()和check_integrity()是否是Triangular_iterator的成员函数。

  我们也可以把令class A和class B建立friend关系,借此让class A的成员函数都称为 class B的friend。

class Triangular {
    friend class Triangular_iterator;
    //Triangular_iterator的成员函数都成了Triangular的friend
}

  不过也不一定非需要friend来实现这个目的,我们可以提供一些public member function来访问private数据,这样你要访问我的private数据就用这些接口就行,就不需要friend了。

4.8 实现一个赋值操作符重载

class Matrix {
public:
	Matrix(int row = 1, int col = 1)
	{
		_row = row > 0 ? row : 1;
		_col = col > 0 ? col : 1;
		_arr = new double[_row * _col];
	}
	Matrix(Matrix& mat);
    Matrix& operator=(const Matrix& mat);
	~Matrix()
	{
		delete[]_arr;
	}
private:
	int _row;
	int _col;
	double* _arr;
};

Matrix::Matrix(Matrix& mat)
{
	_row = mat._row;
	_col = mat._col;
	_arr = new double[_row * _col];
	for (int i = 0; i < _row * _col; i++)
	{
		_arr[i] = mat._arr[i];
	}
}

Matrix& Matrix:: operator=(const Matrix& mat)
{
	if (this != &mat)
	{
		_row = mat._row;
		_col = mat._col;
		int elem_cnt = _row * _col;
		delete[] _arr;
		_arr = new double[elem_cnt];
		for (int i = 0; i < elem_cnt; i++)
		{
			_arr[i] = mat._arr[i];
		}
	}
	return *this;
}

4.9 实现一个函数对象

  函数对象是一种带有函数调用运算符的class。

//当不是初始化 而是已经初始化完了的的时候调用
lt(val);
//等价于
lt.operator(val);
class LessThan {
public:
	LessThan(int val) { _val = val;  }
	int out_val()const { return _val; }
	int re_val(int val) { _val = val; }
	bool operator()(int value);
private:
	int _val;
};
bool LessThan:: operator()(int value)
{
	return value < _val;
}

int count_lessthan(const vector<int>& vec, int val)
{
	LessThan lt(val);
	int count = 0;
	for (int i = 0; i < vec.size(); i++)
	{
		if (lt(vec[i]))
			count++;
	}
	return count;
}

void print_lessthan(const vector<int>& vec, int val, ostream& os = cout)
{
	LessThan lt(val);
	vector<int>::const_iterator iter = vec.begin();
	os << "小于" << lt.out_val() << "的元素有" << endl;
	while ((iter = find_if(iter, vec.end(), lt)) != vec.end())
	{
		os << *iter << ' ';
		iter++;
	}
}

int main()
{
	int a[16] = { 17, 12, 44,9,18,45,6,14,23,67,9,0,27,55,8,16 };
	vector<int> ivec(a, a + 16);
	int cmp = 16;
	cout << "小于" << cmp << "的整数个数是" 
	<< count_lessthan(ivec, cmp) << endl;
	print_lessthan(ivec, cmp);
}

4.10 重载iostream运算符

class Student {
public:
	Student() :_name("匿名"), _sex("保密")
	, _age(1), _number(1) {};
	Student(string name, string sex, 
	int age = 1, int number = 1)
	{
		_name = name;
		_sex = sex;
		_age = age > 0 ? age : 1;
		_number = number > 0 ? number : 1;
	}
	Student(Student& stu);
	string name() const
	{
		return _name;
	}
	string sex() const
	{
		return _sex;
	}
	int age() const { return _age; }
	int number() const { return _number; }
	bool operator==(Student&) const;
	bool operator!=(Student&) const;
	Student& operator= (Student&);
	//ostream& operator<<(ostream& os, const Student& stu);
	friend istream& operator>>(istream& os, Student& stu);
	friend ostream& operator<<(ostream& os, const Student& stu);
private:
	string _name;
	string _sex;
	int _age;
	int _number;
};

bool Student::operator==(Student& stu) const
{
	return _name == stu.name() && _sex == stu.sex() 
		&& _age == stu.age() && _number == stu.number();
}
bool Student::operator!=(Student& stu)const
{
	return !(*this == stu);
}

Student& Student::operator=(Student& stu)
{
	if (*this != stu)
	{
		_name = stu.name();
		_sex = stu.sex();
		_age = stu.age();
		_number = stu.number();
	}
	return *this;
}

//这里不能把它设计成一个成员函数 
//因为那样默认左操作数是student对象 得stu << cout
//看起来很怪
ostream& operator<<(ostream& os, const Student& stu)
{
	os << stu._name << ' ' << stu._sex << ' ' 
		<< stu._age << ' ' << stu._number << endl;
	return os;
}

istream& operator>>(istream& os, Student& stu)
{
	os >> stu._name;
	os >> stu._sex;
	os >> stu._age;
	os >> stu._number;
	return os;
}

Student::Student(Student& stu)
{
	*this = stu;
}

int main()
{
	Student s1("范进", "男", 55, 439849);
	//cin >> s1;
	cout << s1;
	Student s2(s1);
	Student s3;
	s3 = s1;
}

4.11 指向类成员函数的指针

  和普通函数指针的区别就是它要标注自己指的是哪个类的成员函数。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值