本章实际是上分析怎么去设计一个类,设计一个类应该需要包含的基本元素,下面我从如下几个方面做总结。
- class的声明,定义,以及声明后的主体。
- class的构造函数 Constructors 和 析构函数 Destructors。
- 成员种类和成员初始化,multable 和const。
- this指针
- firend友联
- 打造一个 function object 需要重载常见的运算符有哪些
1:class的声明,定义以及声明后的主体。
class的前置声明(forward declaration)
class的声明是以关键词 class开始,其后紧接着一个名称如:class Stack;
这个声明只是将class 名称告诉编译器,并未提供此 class的任何其他信息(如class支持的操作行为以及所包含的 data members等)。
前置声明使我们可以进行类指针(class pointer)的定义,或以此class作为数据型别。
Stack *pt = 0 // 定义一个类指针
void process(const Stack&) // 以Stack作为数据型别
calss的定义
在定义实际的Stack class object 或取用 Stack的任何一个 member之前,必须先定义 class本身,class定义的主体是这样的:
class Stack { public: // 声明一下函数 bool push(const string&); bool pop(string &elem); bool peek(string &elem); bool empty(); bool full(); // 定义size函数 int size() { return _stack.size(); } private: vector<string> _stack; }
member functions的声明和定义
所有 member functions都必须在class主体内进行声明,适于是否需要同时定义,可以由程序员自由决定。
- 如果在class主体内定义,那么这个函数就自定是 内联函数 ,比如:size()函数
- 如果在class主体外定义,那么就需要使用特殊的语法,目的就是在于分辨该函数究竟属于哪一个 class,如果希望该函数为 inline,那么应该在最前面指定关键系 inline。
// 内联函数 inline bool Stack::empty() { return _stack.empty(); } // 非内联函数 bool Stack::pop(string& elem) { if(empty()) return false; elem = _stack.back(); _stack.pop_back(); return true; }
上述: Stack::empty() 就是告诉编译器: empty()是 Stack class的一个 member ,class名称之后的两个冒号(Stack::) 是所谓的 class scope resolution(类范围决议)运算符。
总结:
- 对于inline 函数而言,定义于class主体或者主体外,其实并没有什么分别。
- class 的定义式及其inline member function通常应该放在与class同名的头文件中如: Stack class 的定义和 empty()函数的声明应该放在 Stack.h头文件中。
- 如果用户想要使用Stack,那么直接 include Stack.h 头文件即可。
2:Constructors构造函数和 Destructors析构函数
Constructors
构造函数的名称必须与class名称相同,语法规定:constructors不应指定返回型别,也不需要返回值,它可以被重载,例如:
class Triangular { public: // 一组重载的构造函数 Triangular(); // 默认构造函数 Triangular(int len); Triangular(int len =1,int beg_pos = 1); private: int _len; itn _beg_pos; }
member initialization
通过构造函数直接初始化
Triangular::Triangular() { _len = 1; _beg_pos = 1; }
成员初始表(member initialization list)
member initialization list :是紧接在参数表最后的冒号后面,以逗号分隔的列表,其中欲赋值给member的数值被置于 member名称后面的小括号内
Taiangular::Triangular(const Triangular& rhs):_len(rhs._len), _beg_pos(rhs._beg_pos) { }
Destructors
- 析构函数是与构造函数对立的,所谓析构函数是用户自定义的一个 class member,一旦某个class提供有 destructor,当其objects结束生命时,便会自动调用 destructor处理善后,所以析构函数主要用来释放:构造函数中或者对象声明周期中配置的资源。
- 析构函数有着严格的名称规定:class 名称再加上"~"前导符号,它绝对不会有返回值,也没有任何参数,所以也绝不会有重载。
class Matrix { public: Matrix(int row, int col):_row(row),_col(col) { // 构造函数进行资源的配置 _pmat = new double(row*col); } ~Matrix() { // 析构函数进行资源的释放 delete[] _pmat; } private: int _row, _col; double* _pmat; } int main(){ Matrix mat(4,4); }
编译器解释:
- 编译器会在 mat 被定义出来的下一刻,暗暗施行 Matrix constructor,于是,_pmat被初始化为一个指针,指向程序自由空间(free store)中的一块内存,代表一个具有16个double元素的数组,在语句区段释放之前(main函数出栈之前)编译器又会暗暗施行 Matrix Destructor,于是释放_pmat所寻址的那块具有16个double元素的数组,Matrix 用户不需要知道内存管理的细节。
- 但是 Destructor并非绝对有必要,比如:Triangular 为例,2个 data member都是以储值方式存放,这些 member data在class object被定义后便已存在,并在 class Triangular结束其生命周期被释放,因此 Triangular Destructor并没有什么事情需要做,所以:我们没有必要非得提供 Destructor不可,事实上,是否需要定义 析构函数是C++学习的难点之一。
3: const不可变与mutable可变
const关键字修饰的函数
- 被const关键字修饰的函数的一个重要作用就是为了能够保护类中的成员变量。
- 该函数可以使用类中的所有成员变量,但是不能修改他们的值。
const关键字修饰的类
- const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针 和对象引用也是一样。
- const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。
class Person {
public:
Person();
~Person();
int getAge() const;
int getCallingTimes() ;
int age33;
mutable int age44 = 2;
private:
int age;
int age22;
char* name;
float score;
mutable int m_nums;
};
Person::Person(){
m_nums = 0;
}
Person::~Person(){}
int Person::getAge() const
{
std::cout<< "calling the method"<<std::endl;
m_nums++;
age = 4;
return m_nums;
}
int Person::getCallingTimes() {
age = 4;
return m_nums;
}
void test(const Person& p) {
p.age33 = 2;
p.age44 = 6;
p.getCallingTimes();
p.getAge();
}
mutable:
- mutalbe的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。
- 在C++中,mutable也是为了突破const的限制而设置的。
- 被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。
比如:上述代码中,我只想修改 m_nums的值,但是 age的值不能修改,所以只需要给 m_nums变量增加 mutable修饰符即可。
4: this指针
this指针扮演这样一种角色:一种可以指向 tr1(调用者)对象的功能。
#include<iostream>
#include<string>
using namespace std;
class Triangular{
public:
Triangular(int len ,int bp);
Triangular(int len);
Triangular& operator=(const Triangular &rhs);
Triangular& copy(const Triangular &rhs);
void printf(Triangular& tr);
private:
int _length; // number of elements
int _beg_pos; // beginning position of range
mutable int _next; // next element to iterator over
};
void Triangular::printf(Triangular& tr){
cout<< "_length: "<< tr._length
<< " _beg_pos: "<< tr._beg_pos
<< " _next: "<< tr._next << endl;
}
// 重载 operator=操作符
Triangular& Triangular::operator=(const Triangular &rhs)
{
if(this != &rhs){
_length = rhs._length;
_beg_pos = rhs._beg_pos;
_next = 1;
}
return *this;
}
// 设计一个 copy函数
Triangular& Triangular::copy(const Triangular &rhs){
_length = rhs._length;
_beg_pos = rhs._beg_pos;
_next = rhs._next;
return *this; // this 指向函数copy的调用者
}
Triangular::Triangular(int len,int beg_pos)
: _length(len >0? len:1),
_beg_pos(beg_pos>0 ? beg_pos:1)
{
_next = _beg_pos;
}
Triangular::Triangular(int len){
_length = len;
}
int main(){
Triangular tr1(2);
Triangular tr2(3,4);
tr1.copy(tr2);
tr1.printf(tr1);
return 0;
}
在本例中,this指向 对象 tr1 ,那么它是怎样办到的了 ?
其实它的内部工作过程是这样的:编译器会自动将 this 指针加到每一个 member function的参数列表中,于是 copy函数可以转换为下面形式:
// 伪代码 member function 转换后的结果
Triangular& Triangular::copy(Triangular *this, const Triangular &rhs)
{
this->_length = rhs._length;
this->_beg_pos = rhs._beg_pos;
this->_next = rhs._next;
}
// 所以 copy函数调用就转换成下面这个方式
copy(&tr1, th2);
5: 静态类成员
静态成员变量
还记得吗?我们曾经利用一个容器container来存储 Fibonacci数列元素,这个容器是以局部静态变量(local static vector)实现的,
- 现在我们的 class也只需要唯一的容器来存储数列,关键字 static再次解决了这个问题。
- static data member用来表示唯一的,可共享的 member,它可以在同一类的所有对象中被访问。
- 对 class而言,static data member只有唯一的一份实体,因此我们必须在程序代码文件中提供其清楚的定义。这种定义看起来很像全局对象(global object)的定义,唯一的差别就是:名称必须附上 class scope运算符。
vector<int> Triangular::_elems;
// 如下这份定义,我们声明 _elems是 Triangular class的一个 static data member
class Triangular {
public:
// ......
private:
static vector<int> _elem;
}
静态成员函数 (static member Function)
- 一般情况下,member function 必须通过其类的某个对象来调用,这个对象会被绑定至该member function的 this指针,通过存储于每个对象的this 指针, member function才能够访问存储于每个对象中的 non-static data member。
- 但是,static member function可以在这种“与任何对象都毫无瓜葛”的情形之下被调用。
- 即使我们在 class 主体外进行 static member function定义,也可以无需加 static , 只需要在声明的时候加上即可。
6:class运算符的重载
- 为了说明如何对 class 进行运算符重载操作,我们以实现一个 iterator class 为例。
- 我们为 iterator class 定义 !=、*、++ 运算符,可以像定义 member function 那样来定义
- 运算符函数看起来很像普通函数,唯一差别是它不用指定名称,只需在运算符前加上关键字 operator即可。
运算符重载规则:
不可以引入新的运算符。除了: . .* :: ?: 四个运算符,其他的运算符皆可被重载
运算符的操作数(operand) 个数不可改变,每个二元运算符都需要两个操作数,每个一元运算符都需要恰好一个操作数,因此。我们无法定义出一个 equality运算符,并令它接受两个以上或两个以下的操作数
运算符的优先级(precedence)不可改变,例如:除法的优先级永远高于加法
运算符函数的参数列表中,必须至少有一个参数为 class类型,也就是说,我们无法为诸如指针之类的 non-class类型,重新定义其原已存在的运算符,当然也无法引进新运算符。
运算符定义规则
运算符定义的方式有两种
1: 像 member function一样
2: 也可以像 non-member function一样
6: friend友联(合作关系必须建立在友谊的基础之上)
定义:
任何 class 都可以将其他 function或 class 指定为朋友(friend), 而所谓的 friend 具备了 与class member function 相同的访问权限。
- 有元函数:
在当前类外定义,可以是不属于任何类的非成员函数,也可以是其他类的成员函数。
- 有元类:
将一个类声明为另一个类的 朋友,这就是友元类,并且友元类中的所有成员函数都是另一个类的有元函数。(例如将 B类声明为 类A的友元类,那么类B中的所有成员函数都是类A的友元函数,所以类B中成员函数可以访问类A所有成员,包括 public, protected, private属性。)
6.1 : 全局函数作为友元函数
// 全局函数,作为友元函数
// --------------------------------友元函数------------------------------------------
class Student{
public:
Student(char *name, int age, float score);
// 声明非成员友元函数 (就是说 类Student 有一个朋友 show , 这个朋友show对 类Student访问权限如同类Student的member function)
friend void show(Student *pstu);
private:
char *m_name;
int m_age;
float m_score;
};
// 使用列表的方式来实例化参数
Student::Student(char *name,int age,float score):
m_name(name),m_age(age),m_score(score){
}
// 有元函数 ----非成员函数
void show(Student *pstu)
{
cout<< pstu->m_name << "的年龄是:"<< pstu->m_age <<",成绩是:"<< pstu->m_score<< endl;
}
void test_frient_fun(){
Student stu("小明",15,90.6);
show(&stu);
}
6.2 : 类侧成员函数作为友元函数
// --------------------------------友元函数------------------------------------------
class Address;
class Student{
public:
Student(char *name, int age, float score);
// 声明非成员友元函数 (就是说 类Student 有一个朋友 show , 这个朋友show对 类Student访问权限如同类Student的member function)
friend void show(Student *pstu);
// 成员函数
void show2(Address *addr);
private:
char *m_name;
int m_age;
float m_score;
};
class Address{
private:
char *m_province; // 省份
char *m_city; // 城市
char *m_district; // 区
public:
Address(char *province, char *city, char *district);
friend void Student::show2(Address *addr);
};
// 使用列表的方式来实例化参数
Student::Student(char *name,int age,float score):
m_name(name),m_age(age),m_score(score){
}
Address::Address(char *province,char *city, char *district){
m_province = province;
m_city = city;
m_district = district;
}
// 有元函数 ----非成员函数
void show(Student *pstu)
{
cout<< pstu->m_name << "的年龄是:"<< pstu->m_age <<",成绩是:"<< pstu->m_score<< endl;
}
// 友元函数----成员函数
void Student::show2(Address *addr){
cout<< m_name <<" 年龄是:" << m_age<< ",成绩是: "<< m_score <<endl;
cout << ",家庭住址: "<< addr->m_province << "省: "<< addr->m_city<< "市:"<< addr->m_district << "区"<< endl;
}
void test_frient_fun(){
Student stu("小明",15,90.6);
show(&stu);
}
void test_frient2_fun(){
Student stu("小张",15,100);
Address addr("湖北","武汉","江夏");
stu.show2(&addr);
}
6.3:友元类
- 在 类 Triangular 中声明 一个友元类 Triangular_iterator, 这样Tiangular_iterator 就可以使用 Triangular 的私有属性如:_elems集合。
- 在 prog2程序中,类Triangular 通过 beg() 得到 起始Triangular_iterator 迭代器 , Triangular.end() 得到 结束迭代器 Triangular_iterator 。 然后我们对 vector<int> _elems进行迭代,得到集合中每个元素并打印
#include<iostream>
#include<string>
#include<vector>
using namespace std;
class Triangular_iterator
{
public:
Triangular_iterator(int index):_index(index-1){
}
// 需要重载iterator 迭代器常见的操作符
bool operator!=(const Triangular_iterator& ) const;
int operator*() const;
bool operator==(const Triangular_iterator& ) const;
Triangular_iterator& operator++();
private:
int _index;
};
class Triangular{
friend class Triangular_iterator;
public:
Triangular(int len = 1, int bp =1);
Triangular(const Triangular&);
Triangular& operator=(const Triangular &rhs);
int elem(int pos)const;
static void gen_elements(int length);
typedef Triangular_iterator iterator;
Triangular_iterator begin() const{
cout << "begin: "<< _beg_pos<<endl;
return Triangular_iterator(_beg_pos);
}
Triangular_iterator end()const{
cout << "end: "<< _beg_pos+_length<<endl;
return Triangular_iterator(_beg_pos+_length);
}
private:
int _length;
int _beg_pos;
mutable int _next;
static vector<int> _elems;
};
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);
}
inline Triangular_iterator& Triangular_iterator::
operator++(){
++_index;
return *this;
}
vector<int> Triangular::_elems;
void Triangular::gen_elements(int len){
if (_elems.size() < len)
{
int ix = _elems.size()?_elems.size()+1:1;
for(; ix <= len ;++ix){
_elems.push_back(ix);
}
cout << "vector 的元素是:"<< endl;
for(int i = 0; i<_elems.size();i++){
cout<<_elems[i] << " ";
}
cout <<endl;
}
}
int Triangular::
elem(int pos)const {
return _elems[pos-1];
}
inline int Triangular_iterator::
operator*()const {
// error: 'std::vector<int> Triangular::_elems' is private within this context
// 需要将 Triangular_iterator 声明为 Triangular的友元类
// cout<< "operator*() "<< _index;
return Triangular::_elems[_index];
}
Triangular::Triangular(int len,int beg_pos)
:_length(len >0 ? len :1),
_beg_pos(beg_pos >0 ? beg_pos :1)
{
_next = beg_pos;
int elem_cnt = _beg_pos + _length;
if(_elems.size() < elem_cnt)
gen_elements(elem_cnt);
}
void prog2(){
Triangular tri(5,0);
Triangular::iterator it = tri.begin();
Triangular::iterator end_it = tri.end();
while(it != end_it){
cout << *it << endl;
++it;
}
}
int main(){
prog2();
return 0;
}
6.4: 重载 copy assignment operator运算符
7:实现一个function object
- 所谓 function object 其实是一种 “提供有 function call运算符”的 class。
- function call 运算符可接受任意个数的参数:零个,一个,两个或者更多。
- 它可以在函数调用操作符(即:小括号()) 左侧使用 对象类型
- 函数对象有下面几种类型
- 函数或函数引用(需要隐式转换)
- 函数指针
- <functional> 头文件中定义的所有函数对象 (如:less ,greater)
- <function>头文件中一些函数的返回值
- lambda表达式
- 重载了函数调用运算符()的类的对象
class LessThan{
public:
LessThan(int val):_val(val){}
int comp_val()const {return _val;} // 基数的读取
void comp_val(int val){_val = val;} //基数的写入
// 重载operator() 操作符
bool operator()(int val)const;
private:
int _val;
};
inline bool LessThan::
operator()(int value)const{
return value < _val;
}
int count_less_than(const vector<int> &vec,int comp){
// 构造函数传入指定的值(需要比较的基值)
LessThan lt(comp);
int count = 0;
for(int ix = 0; ix < vec.size(); ++ix){
// 调用函数对象 operator()操作符
if(lt(vec[ix]))
++count;
}
return count;
}
void prog3(){
int ia[10] = {17,2,35,14,89,12,356,1,2,3};
vector<int> vec(ia,ia+10);
int comp_val = 20;
cout << "Number of elements less than"
<< comp_val<< " are "
<< count_less_than(vec,comp_val)<<"个元素"<< endl;
}
int main(){
prog3();
return 0;
}
// 打印结果
Number of elements less than20 are 7个元素