1. 友元类
(1 )一个类成为另一个类的友元,这两个类应该存在某种关系,既不是公有继承的is-a关系,也不是包含has-a关系。打个比方,遥控器和电视机,遥控器可以改变电视机的状态,可以说遥控器(Remote类)作为电视机(TV类)的一个友元。
下面语句使Remote成为友元类:
friend class Remote;
友元声明可以位于公有/私有/保护部分,其所在位置无关紧要。
15chapter.h 文件
#ifndef _TV_H_
#define _TV_H_
class TV
{
public:
friend class Remote;
enum{Off, On};
void onoff(){state = (state == On)? On:Off;}
void chanup();
void chandown();
TV(int s = Off, int c = 2):state(s),channel(s){}
void setting() const;
private:
int state;
int channel;
};
class Remote//友元类
{
private:
int state;
public:
Remote(int m = TV::On):state(m){}
void chanup(TV & t){t.chanup();}
void chandown(TV & t){t.chandown();}
void onoff(TV & t){t.onoff();}
void setchan(TV & t, int c){t.channel = c;}
};
#endif
15chapter.cpp文件
#include<iostream>
#include"15chapter.h"
void TV::chandown()
{
if(channel > 1)
{
channel--;
}
else
{
channel = 60;
}
}
void TV::chanup()
{
if(channel < 60)
{
channel++;
}
else
{
channel = 1;
}
}
void TV::setting() const
{
using std::cout;
using std::endl;
cout<<"TV is "<<(state == Off?"OFF":"ON")<<endl;
cout<<"channel = "<<channel<<endl;
}
test.cpp 文件
#include<iostream>
#include"15chapter.h"
int main()
{
TV s;
s.setting();
s.onoff();
s.chanup();
s.setting();
Remote grey;
grey.chanup(s);
grey.onoff(s);
grey.setchan(s, 50);
s.setting();
while(1);
return 0;
}
这个练习主要表明,类友元是一种自然用语,用于表示一些关系。如果不使用某些形式的友元关系,则必须将TV类的私有部分设置为公有的。
(2)友元成员函数
上例中,Remote的chanup(),onoff(),都是调用TV类的公有实现,意味着这些方法不是真正需要作为友元,唯一直接访问TV成员的是Remote::setchan(),因此它是唯一需要作为友元的方法。
在TV类中将其声明为友元:
class TV
{
friend void Remote::setchan(TV & t, int c);
}
不过,编译器能够处理这条语句,他必须知道Remote的定义。因此,运用前向声明:
class TV;//forward declaration
class Remote{...};
class TV{...};
不能如下排列:
class Remote;
class TV{...};
class Remote{...};
原因在于,在编译器TV类的声明中看到Remote的一个方法被声明为TV类的友元之前,应该先看到Remote类的声明和setchan()方法的声明。
那么,同样的问题,尽管前向声明class TV,但是由于Remote的方法都是内联函数并调用TV的方法,这样编译的时候,编译器并不知道TV类有哪些方法,还是照样会报错。解决的办法是把Remote只包含方法声明,并将实际的定义房子TV类之后。当编译器到达真正的方法定义时,他已经读取了TV类的声明,并拥有了编译这些方法的所需信息。头文件修改如下:
#ifndef _TV_H_
#define _TV_H_
class TV;
class Remote//友元类
{
private:
int state;
public:
Remote(int m = 0):state(m){}
//方法声明
void chanup(TV & t);
void chandown(TV & t);
void onoff(TV & t);
void setchan(TV & t, int c);
};
class TV
{
public:
//friend class Remote;
friend void Remote::setchan(TV & t, int c);
enum{Off, On};
void onoff(){state = (state == On)? On:Off;}
void chanup();
void chandown();
TV(int s = Off, int c = 2):state(s),channel(s){}
void setting() const;
private:
int state;
int channel;
};
//方法的定义放到TV类后面
inline void Remote::chanup(TV & t){t.chanup();}
inline void Remote::chandown(TV & t){t.chandown();}
inline void Remote::onoff(TV & t){t.onoff();}
inline void Remote::setchan(TV & t, int c){t.channel = c;}
#endif
(3)其他友好关系
共同友元,假设一个函数需要访问两个类的私有数据,可以把这个函数看成是一个类的成员函数,同时是另一个类的友元,不过有时候将函数作为两个类的友元更为合理。假定有一个Probe类和一个Analyzer类,两个类都有内部时钟,并且希望他们能够同步,如下代码片段:
class Analyzer //forward declaration
class Probe
{
friend void sync(Analyzer& a, const Probe& p);
friend void sync(Probe& p, const Analyzer& a);
...
};
class Analyzer
{
friend void sync(Analyzer& a, const Probe& p);
friend void sync(Probe& p, const Analyzer$ a);
...
};
//define the friend functions
inline void sync(Analyzer& a, const Probe& p)
{
...
}
inline void sync(Probe& p, Analyzer& a)
{
...
}
2.嵌套类
(1)在另一个类中声明的类被称为嵌套类(nested class).对类进行嵌套与包含并不相同.包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效.
class Quene
{
class Node
{
public:
Item item;
Node* next;
Node(const Item & i):item(i),next(0){}
};
...
};
嵌套类定义了构造函数,假设在外部定义的话,需要使用两次作用域解析操符来完成:
Quene::Node::(const Item & i):item(i),next(0){}
(2)嵌套类和访问权限
声明位置 | 包含它的类是否可以使用 | 派生类是否可以使用 | 外部世界是否可以使用 |
私有部分 | 是 | 否 | 否 |
保护部分 | 是 | 是 | 是 |
公有部分 | 是 | 是 | 是,通过类限定符来使用 |
(3)模板中的嵌套
小程序小试牛刀,将Queue类定义为模版,同时包含嵌套类
//头文件
#ifndef _QUENETP_H_
#define _QUENETP_H_
template<class Item>
class QueueTP
{
private:
enum{Q_SIZE = 10};
class Node
{
public:
Item item;
Node* next;
Node(const Item & i):item(i),next(0){}
};
Node* front;
Node* rear;
int items;
const int qsize;
QueueTP(const QueueTP & q):qsize(0){}
QueueTP & operator=(const QueueTP & q){return *this;}
public:
QueueTP(int qs):qsize(qs),front(0),rear(0),items(0){}
~QueueTP();
bool isEmpty() const
{
return items == 0;
}
bool isfull() const
{
return items == qsize;
}
int queuecount() const
{
return items;
}
bool enqueue(const Item & item);
bool dequeue(Item &item);
};
template<class Item>
QueueTP<Item>::~QueueTP()
{
Node * temp;
while(front != 0)
{
temp = front;
front = front->next;
delete temp;
}
}
template<class Item>
bool QueueTP<Item>::enqueue(const Item & item)
{
if(isfull())
return false;
Node *node = new Node(item);
if(node == NULL)
return false;
items++;
if(front == 0)
front = node;
else
rear->next = node;
rear = node;
return true;
}
template<class Item>
bool QueueTP<Item>::dequeue(Item & item)
{
if(front == 0)
return false;
item = front->item;
items--;
Node* temp = front;
front = front->next;
delete temp;
if(items == 0)
rear = 0;
return true;
}
#endif
//test.cpp
#include<iostream>
#include<string>
#include"quene.h"
int main()
{
using namespace std;
QueueTP<string>cs(5);
string temp;
while(!cs.isfull())
{
cout<<"please enter your name.you will be served in the order of arrival.\n name:";
cin>>temp;
cs.enqueue(temp);
}
cout<<"the queue is full.process begins\n";
while(!cs.isEmpty())
{
cs.dequeue(temp);
cout<<"Now process "<<temp<<"...\n";
}
while(1);
return 0;
}
3.异常
(1)异常机制
异常提供了将控制权从程序的一部分传递到另一部分的途径,对异常的处理有3个组成部分
a.引发异常 ---------->(throw,抛出异常)
b.捕获有处理程序的异常 ----------->(catch,捕获异常)
c.使用try块 ------------->(try块,异常可能被激活的代码块)
#include<iostream>
double hmean(double a, double b);
int main()
{
double x, y, z;
std::cout<<"Enter two numbers:";
std::cin>>x>>y;
try //可能产生异常的代码块
{
z = hmean(x, y);
}
catch(const char* s)//捕获
{
std::cout<<s<<std::endl;
}
while(1);
return 0;
}
double hmean(double a, double b)
{
if(a == -b)
{
throw "bad hean() argument: a = -b not allowed!!";//抛出异常,字符串传给catch做参数
}
else
{
return 1.0;
}
}
(2)将对象用作异常类型
引发异常的函数通常将传递一个对象,可以使用不同的异常类型来区分不同的函数在不同的情况下引发的异常.对象可以携带信息,catch块可以根据这些信息来决定采取什么样的措施.
如果异常规范中的括号为空,则表明该函数不会引发异常.
double simple(double z) throw() //does't throw an exception
定义两个对象用于捕获异常,请看下面的例子
头文件
#include<iostream>
using namespace std;
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0):v1(a),v2(b){}
void mesg();
};
inline void bad_hmean::mesg()
{
cout<<"hmean("<<v1<<","<<v2<<"):"<<"invalid arguments: a = -b \n";
}
class bad_gmean
{
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0):v1(a),v2(b){}
const char * mesg();
};
inline const char * bad_gmean::mesg()
{
return "gmean() arguments should be >= 0\n";
}
测试文件
#include<iostream>
#include"exc_mean.h"
#include<cmath>
double hean(double a, double b)throw(bad_hmean);
double gean(double a, double b)throw(bad_gmean);
int main()
{
using namespace std;
double x, y, z;
cout<<"Enter two numbers:";
while(cin>>x>>y)
{
try
{
z = hean(x, y);
cout<<"harmonic mean of"<<x<<" and "<<y<<" is:"<<z<<endl;
cout<<"geometric mean of"<<x<<" and "<<y<<" is:"<<gean(x, y)<<endl;
}
catch(bad_hmean & hg)//捕获异常对象
{
hg.mesg();
cout<<"Try Again!!";
continue;
}
catch(bad_gmean & gg)//捕获异常对象
{
cout<<gg.mesg();
break;
}
}
cout<<"Bye!";
while(1);
return 0;
}
double hean(double a, double b)throw(bad_hmean) //声明
{
if(a == -b)
{
throw bad_hmean(a, b); //抛出异常对象
}
return 2.0 * a * b /(a+b);
}
double gean(double a, double b)throw(bad_gmean) //声明
{
if(a < 0 || b < 0)
{
throw bad_gmean(a, b); //抛出异常对象
}
return sqrt(a * b);
}
(3)堆栈解退 和 其他异常特性
A.假设try块没有直接调用引发异常的函数,而是调用了对引发异常的函数进行调用的函数,则程序将从引发异常的函数跳到try块和处理程序的函数,这设计到堆栈解退(unwinding the stack).假设现在函数由于出现异常而终止,则程序也将释放堆栈中的内存,但不会在释放堆栈的第一个返回地址后停止,而是继续释放堆栈,直到找到一个位于try块中的返回地址.随后控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句,这个过程被称为堆栈解退.
程序进行堆栈解退以回到能够捕获异常的地方时,将释放堆栈中的自动存储型变量,如果变量是类对象,将为该类对象调用析构函数.
B.如果有一个异常类继承层次结构,排列catch块时,应将基类排在最底部,其余的派生类按顺序往上排列.
C.catch(...){...};表示可以捕获任何异常.
(4) exception类
exception头文件定义了它,C++可以把它用作其他异常类的基类.代码可以引发exception异常,也可以将exception作为基类.它有一个what()虚拟成员函数,返回一个字符串.
class bad_hmean : public std::exception
{
public:
const char* what(){return "bad mesg"}
};
try{...}
catch(std::exception & e)
{
cout<<e.what()<<endl;
}
(5)stdexcept异常类
头文件stdexcept定义了logic_error 和 runtime_error,他们都以共有的方式从exception派生而来.
logic_error典型的逻辑错误,又可以派生出
A.domain_error: 定义域,假如输入的值没有在指定的区域时,将引发domain_error
B.invalid_error:给函数传递一个意料外的值
C.length_error:没有足够的空间来执行所需的操作
D.out_of_bound:指示索引错误,常见于数组.
runtime_error异常系列描述了在运行期间发生但难以预计和防范的错误.
A.rang_over---->计算结果可能不再函数允许的范围之内,但没有发生上溢或者下溢错误,这种情况可以使用他.
B.underflow_error----->在浮点计算中,存在浮点类型可以表示的最小非0值,计算结果小于这个值将导致下溢错误.
C.overflow_error----->超过了某种类型能够表示的最大数量级时,发生上溢错误.
以上,不同的类名让我们可以分别处理每种异常.
(6)bad_alloc异常和new
使用new时,可能出现内存分配问题,C++提供两种方式,
A.让new无法成功申请是返回一个空指针
B.让new引发bad_alloc异常,头文件new.h中包含它,是从exception派生而来.VS2012下,是这个.
#include<iostream>
#include<new>
#include<cstdlib>
using namespace std;
struct Big
{
double stuff[20000];
};
int main()
{
Big * pb;
try
{
cout<<"Trying to get a big block of memory:\n";
pb = new Big[10000];
cout<<"Got past the new request:\n";
}
catch(bad_alloc & ba)
{
cout<<"Caught the exception!\n";
cout<<ba.what()<<endl;
exit(EXIT_FAILURE);
}
if(pb != 0)
{
pb[0].stuff[0] = 4;
cout<<pb[0].stuff[0]<<endl;
}
else
cout<<"pb is null pointer\n";
delete [] pb;
while(1);
return 0;
}
(7)异常,类,继承,迷失方向未捕获---->//先掠过
A.可以从一个异常类派生出另一个.
B.在类定义中嵌套异常类声明来组合异常.
C.这种嵌套声明本身可被继承,还可用作基类.
D.异常引发后,未捕获异常默认情况下会导致程序终止,程序首先调用terminate(),默认情况下,调用abort()函数.我们可以指定terminate()应调用的函数来修改terminate()这种行为.可调用set_terminate()函数来制定.
(8)异常 和 动态内存分配
A.正常的例子
void test1(int n)
{
string mesg("i am seaice");
....
if(oh_no)
{
throw exception();
}
....
return;
}
B.导致内存泄漏的例子
void test2(int n)
{
double * ar = new double[n];
...
if(oh_no)
{
throw exception();
}
....
delete [] ar;
return;
}
当然这种内存泄漏可以避免,需要在catch捕获异常的时候,释放内存.不过,这将增加疏忽和产生其他错误的机会.
4.RTTI (Runtime Type Identification)
RTTI只适用于包含虚函数的类,原因在于只有对于这种类层次结构,才应该将派生类对象的地址赋给基类指针.
(1)如果可能的话,dynamic_cast操作符将使用一个指向基类的指针来生成一个指向派生类的指针,否则,该操作符返回0,空指针.
记住:如果指向的对象(*pt)的类型为Type或者是从Type直接或间接派生而来的类型则表达式为:
dynamic_cast<Type*>(pt)
将指针pt转换为Type类型的指针,否则,结果为0.
#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
class Grand
{
private:
int hold;
public:
Grand(int h = 0):hold(h){}
virtual void speak() const {cout<<"I am a grand class\n";}
virtual int value() const {return hold;}
};
class Superb : public Grand
{
public:
Superb(int h = 0):Grand(h){}
void speak() const {cout<<"I am a Superb class\n";}
virtual void say() const {cout<<"I hold the superb value of\n"<<value()<<"\n";}
};
class Supestb :public Superb
{
private:
char ch;
public:
Supestb(int h = 0, char c = 'A'):Superb(h),ch(c){}
void speak() const {cout<<"I am a Supesb class\n";}
void say() const {cout<<"I hold the character "<<ch<<"and the integer "<<value()<<"!\n";}
};
Grand* getone();
int main()
{
srand(time(0));
Grand* pg;
Superb* ps;
for(int i = 0; i < 5; i++)
{
pg = getone();
pg->speak();
if(ps = dynamic_cast<Superb*>(pg))//检查pg可否可以安全转换为Superb指针
{
ps->say();
}
}
while(1);
return 0;
}
Grand* getone()
{
Grand* p = NULL;
switch(rand() % 3)
{
case 0:
p = new Grand(rand() % 100);
break;
case 1:
p = new Superb(rand() % 100);
break;
case 2:
p = new Supestb(rand() % 100, 'A');
break;
}
return p;
}
(2)typeid操作符用于判断两个对象是否为同种类型.
A.接受类名参数
B.结果为对象的表达式
Typeid 返回 对type_info对象的引用.
Typeid(Supestb) == typeid(*pg),返回true or false,假如pg = NULL,将引发bad_typeid异常.
如果发现扩展的if else 语句系列中使用了typeid,则应考虑使用虚函数和dynamic_cast.下面这样的例子,就应该改为上面的例子:
if(typeid(Supestb) == typeid(*pg))
{
....
}
else if(typeid(Superb) == typeid(*pg))
{
.....
}
.....
5. 类型转换操作符C++添加4种类型转换操作符
(1)dynamic_cast ------>前面已经介绍过
(2)const_cast ------->用来移除变量的const或volatile限定符。对于const变量,我们不能修改它的值,我们来看看这个转换符到底怎么用
#include<iostream>
using namespace std;
int main()
{
const int cons = 21;
const int* pc = &cons;
int * m = const_cast<int*>(pc);
*m = 7;
cout<<"cons = "<<cons<<",*pc = "<<*pc<<", m = "<<*m<<endl;
cout<<"cons:"<<&cons<<endl;
cout<<"pc:"<<pc<<endl;
cout<<"m:"<<m<<endl;
/*
cons = 21,*pc = 7, m = 7
cons:0028FE50
pc:0028FE50
m:0028FE50
*/
return 0;
}
从这里我们可以看出,const变量就是const,不管怎么转换变化,定义为cosnt的值都不会变。不然,const也没有意义了。对于const数据我们应该保证,绝对不对const数据进行重新赋值。但是既然const是定义为不变的值,我们为什么还需要这个去cosnt操作符呢?
原因可能是
A.我们调用了一个参数不是const的函数,而我们传进去的实际参数确实是const.因此便需要cosnt_cast去除限定。
#include<iostream>
using namespace std;
void print(int* c)
{
cout<<*c<<endl;
}
int main()
{
const int cons = 21;
//print(&cons);//error
print(const_cast<int*>(&cons));
return 0;
}
B.我们定义了一个非const变量,但用const指针去指向他,想要修改是我们只有const指针,那么这时候就需要这个const_cast去cosnt了。请看下面例子,const变量cons依旧不变,cons1 改变了。
#include<iostream>
using namespace std;
void print(const int* c)
{
int *pc;
pc = const_cast<int*>(c);
*pc = 100;
cout<<"c = "<<*c<<endl;
}
int main()
{
const int cons = 21;
int cons1 = 9;
print(&cons);
print(&cons1);
cout<<"cons = "<<cons<<endl;
cout<<"cons1 = "<<cons1<<endl;
/*
c = 100
c = 100
cons = 21
cons1 = 100
*/
return 0;
}
(3)static_const<type-name>(expression)
仅当type_name和expression可以相互转换时,转换才是合法的。例如基类High,子类Low,这两个相互转换都是合法的,但是转换到其他类型是不合法的。
High h;
Low l;
High* h = static_cast<High*>(&l);//valid
Low* l = static_cast<Low*>(&h);//valid,but not safe
看出似乎只有继承关系是可以被static_cast所接受的。实际上,static_cast真正的用处并不在指针和引用上,而在基础类型和对象的转换上。基于基础类型和对象的转换
正式其他三种所办不到的。可以使用他将double转为int,将float转为long 等等。
(4)reinterpret_cast ----->天生危险的类型转换,用于处理无关类型之间的转换。
reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。
IBM的C++指南里倒是明确告诉了我们reinterpret_cast可以,或者说应该在什么地方用来作为转换运算符:
从指针类型到一个足够大的整数类型
从整数类型或者枚举类型到指针类型
从一个指向函数的指针到另一个不同类型的指向函数的指针
从一个指向对象的指针到另一个不同类型的指向对象的指针
从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针
不能将函数指针转换为数据指针。
参考:http://www.cnblogs.com/ider/archive/2011/07/30/cpp_cast_operator_part3.html
使用范例:
struct dat{short a; short b};
long value = 0x23ac78ba;
dat *pd = reinterpret_cast<dat*>(&value);
cout<<pd->a;//display first 2 bytes of value;
这样的转换依赖于实现的底层编程技术,是不可移植的。