格式如下:
operatorop(argument_list)
例如operator+()
这将重载“+”运算符。如果A、B、C都是类D的对象就可以这样用:A=B+c
。其中,运算符左侧的对象是调用对象,右侧的则是作为参数被传递的对象。
重载限制:
假设Stonewt
类有一个构造函数Stonewt(double lbs)//可加explicit关闭隐式转换
Stonewt example//声明一个对象
example=1.1//自动(隐式)转换
example=Stonewt(1.1)//强制(显式)转换
现在我们将类对象转换成特定的数据类型,那么,反过来呢?
我们需要转换函数
注意:
class{
.
.
.
.
.
Public:
operator int() const;//转换函数//可在前面加上关键字explicit来关闭隐式转换
.
.
.
};
Stonewt::operator int() const
{
Return int (pound+0.5)
}
class{
Staic int num_strings;//静态储存类
.
.
.
.
}
它有一个特点,无论创建了多少个对象,程序都只创建一个静态类变量副本,也就是说所有的类对象共用一个静态成员。它不能在类中声明,因为这样会分配内存而类不可,不好在头文件中声明因为这会导致多次声明,最好在表示方法的文件中声明。
Such as:int stringbad::num_strings=0;
很少为人所知的是,编译器会帮我们生成许多自动生成的成员函数,从而造成一些意料之外的结果。如:复制构造函数。
复制构造函数用于将一个对象复制到新创建的对象中,它的原型通常如下:
Class_name(const Class_name &);
为什么隐式复制构造函数在一些情况下会出问题?
首先, 对于一些静态储存类,复制构造函数的执行不会对它造成改变,而有的时候正是需要
它的改变,这时候就会出问题。其次,一些类成员使用的是new初始化的,指向数据的指针,而不是数据本身。所以当对对象使用delete操作时,新构造出的函数很容易就会受到牵连。
下面给出一个复制构造函数的例子:
StringBad::StringBad(const StringBad &st)
{
Num_strings++;
Len=st.len;
Str=new char [len+1];
Std::strcpy(str,st.str);
Cout<<num_strings<<":\""<<str
<<"\"object created\n";
}
与复制构造函数类似,当存在两个已经初始化的对象,我们想将一个对象的值赋给另一个时,默认的赋值运算符就会起作用,其作用机理与复制构造函数相同,产生的问题也大致相同,解决的方法也相似,下面提供赋值运算符(进行深度复制)定义。
e.g:
StringBad & StringBad::operator=(const StringBad &st)
{
If(this==&st)
Return *this;
Delete [] str;
Len=st.len;
Str=new char[len+1];
Std::strcpy(str,st.str);
Return *this;
}
e.g:
Bool operator<(const String &st1,const String &st2)
{
if(std::strcmp(st1.str,st2.str)<0)
return true;
else
return false;
}
总的说来就是重载[]运算符,为了确保不修改数据,可以用Const char & string::operator[](int i) const
的形式来声明函数。
我们可以将成员函数声明为静态的static int Howmany(){ return num_strings;}
。这样做的后果是,不能通过对象来调用此函数,此函数也只能使用静态数据成员。
使用方法:String::Howmany()
;其中String为类名,Howmany为函数名
ps:cpp经常使用指向对象的指针
e.g:
string * favorite = new String(sayings[choice]);
可以用定位new运算符声明对象
Such as:pc1=new (buffer) JustTesting;
但要注意的是,如果是在同一块内存块上声明对象,不要使它们相互重叠而造成错误。还有就是对于用定位new运算符创建的对象要显示的调用析构函数
e.g:pc3->~justtesting();//justtesting
为pc3所指向的类的名称。
还需注意的是晚创建的应该先删除
class A:{
........
};
如何继承?
Class B:public A
{
......
};
即可,当然其中构造函数有些许的不同。
稍特殊情况:多态公有继承
即方法的行为取决于调用该方法的对象
两种方法:
Class student:private std::string,private std::valarray<double>
{
public:
.............
};
Class student:protected std::string,
Protected std::valarray<double>
{
........
};
使用保护继承时,基类的公有成员都将成为派生类的保护成员。
show()
函数中又调用基类的show()
函数,这很自然,可以减少工作量,要记得使Class Dwelling
{
public:
Virtual void showperks(int a) const;
...
};
Class Hovel:public Dwelling
{
public:
Virtual void showperks() const;
....
};
下面的showperks函数将隐藏上面的,这是很自然的。但是有时候很容易出现问题。当你想
调用上面的某个函数时,你就发现不能调用了。所以,如果要保证不出现此类问题,最好的方
法是把上一个类的虚方法的所有重载的版本在这个类中也全部写一遍。
我们已经用过public和private来控制对类成员的访问,其实还存在一种访问类别,那就是protected。protected与private较为相似。对于外部世界来说(相对的),它们都是不可访问的。但是派生类的成员可以直接访问protected中的成员。
警告:最好对类数据成员采用私有访问控制,不要使用保护访问控制;同时通过基类方法使派
生类能够访问基类数据。
抽象基类也是为了表示一种特定的关系而存在着的。比如我们有篮球和足球这两种球,它们可以独立成两个类别,但它们有很多共性,那么我们就可以使用一个叫做‘球’的类来作为抽象基类,再通过球类来派生出篮球类和足球类。
e.g
class AcctABC
{
private:
std::string fullName;
long acctNum;
double balance;
protected:
struct Formatting
{
std::ios_base::fmtflags flag;
std::streamsize pr;
};
const std::string &FullName() const { return fullName; }
long AcctNum() const { return acctNum; }
Formatting SetFormat() const;
void Restore(Formatting &f) const;
public:
AcctABC(const std::string &s = "Nullbody", long an = -1, double bal = 0.0);
void Deposit(double amt);
virtual void Withdraw(double amt) = 0;
double Balance() const { return balance; };
virtual void ViewAcct() const = 0;
virtual ~AcctABC() {}
};
class Brass :public AcctABC
{
public:
Brass(const std::string &s = "Nullbody", long an = -1,
double bal = 0.0) :AcctABC(s, an, bal) {}
virtual void Withdraw(double amt);
virtual void ViewAcct() const;
virtual ~Brass() {}
};
class BrassPlus : public AcctABC
{
private:
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const std::string &s = "Nullbody", long an = -1,
double bal = 0.0, double ml = 500,
double r = 0.10);
BrassPlus(const Brass &ba, double ml = 500, double r = 0.1);
virtual void ViewAcct() const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; };
void ResetOwes() { owesBank = 0; }
};
其中AcctABC是抽象类,Brass和BrassPlus是具体类。AcctABC包含Brass和BrassPlus类共有的所有方法和数据成员,而那些在BrassPlus类和Brass类中的行为不同的方法应被声明为虚函数。至少应有一个虚函数是纯虚函数,这样才能使AcctABC成为抽象类。抽象基类的特点,抽象基类的作用相对于公有继承的基类来说作用有点单调,它只能提供一个垫板,本身作为类并不能声明对象。并且它一定要有纯虚函数的存在,它的纯虚函数可以定义也可以不定义,如果它的派生类没有定义纯虚函数,那么它也成为了抽象基类,知道有的类定义了这些函数,那么它就成为了实体类,可以声明对象。
std::ostream&operator<<(std::ostream &os,const hasDMA & hs)
{
os<<(const baseDMA &) hs;
os<<“Style: ” <<hs.style <<endl;
Return os;
}
如上例所示,想要使用的基类的重载<<运算符的友元函数,只要加上(const baseDMA &)
即可。
使用这样的类成员,本身是另一个类的对象
e.g:
Classs student:
{
private:
String name;
Valarray<double> scores;
...
};
使用构造函数时,对于对象成员的处理:使用初始化列表,调用对象名即可而不是对象的类名。假定scores是一个对象成员,要调用该成员的方法,只需scores.function()
即可。
使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味这基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
使用构造函数:
e.g:
Student(const char *str, const double * pd,int n)
:std::string(str),ArrayDb(pd,n){}
使用私有继承时将使用类名和作用域解析运算符来调用方法。
访问基类对象(强制类型转换)
Const string &student::name() const
{
Return (const string &) *this;
}
访问基类的友元函数
用类名显示地限定函数名不适合友元函数,这是因为友元不属于类。然而,可以通过显示地转换为基类来调用正确的函数。
e.g:
Ostream &operator<<(ostream &os, const student &stu)
{
os<<“scores for ”<<(const string &)stu<<“:\n“;
.....
}
保护继承是私有继承的变体。使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。当从派生类中派生出另一个类时,私有继承和保护继承之间的主要区别便呈现出来了。使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中将变成私有方法;使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。
使用using
来重新定义访问权限
e.g
Using std::valarray
这使得valarray
可用,就像它们是student的公有方法一样
比如说有一个worker的基类,从其中派生出singer类和waiter类,至此,整个代码是没有问题的,singer和waiter都获得了worker的一些数据成员,它们统称为work。当我们从singer和waiter中派生出一个singerwaiter类时,问题就出现了。首先,singerwaiter将有两个有关于worker的数据,那么,进行赋值时要把值赋给谁呢?cpp解决了这个问题,引入了虚基类(virtual base class)
e.g:
Class singer: virtual public Worker {...};
Class wiater: public virtual worker {...};
虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。特别地使用构造函数
singerwaiter(const worker &wk, int p=0, int v=singer::other)
:worker(wk),waiter(wk,p),singer(wk,v){}
worker是虚基类,则singerwriter使用构造函数时要显式的使用worker的构造函数。还有就是方法的问题,如果singer和waiter都使用了show()方法,那么singerwaiter要使用哪个show()方法呢?
可以用作用域解析运算符来澄清编者的意图
Newwhire.singer::show();
还有一种方法可以解决这个问题
e.g
void Worker::Data() const
{
cout << "Name: " << fullname << "\n";
cout << "Employee ID: " << id << "\n";
}
void Waiter::Data() const
{
cout << "Panache rating: " << panache << "\n";
}
void Singer::Data() const
{
cout << "Vocal range: " << pv[voice] << "\n";
}
void SingingWaiter::Data() const
{
Singer::Data();
Waiter::Data();
}
void SingingWaiter::Show() const
{
cout << "Category: singing waiter\n";
Worker::Data();
Data();
}
这样,singingwaiter的show函数就能完美地完成任务了。但要记得将上述基类的data函数
设置为受保护的,这样可以放置不被当前的类对象调用。
e.g
template <class Type>
class Stack
{
private:
enum { MAX = 10 };
Type items[MAX];
int top;
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type &item);
bool pop(Type &item);
};
template <class Type>
Stack<Type>::Stack()
{
top = 0;
}
template <class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}
template <class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template <class Type>
bool Stack<Type>::push(const Type &item)
{
if (top < MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
template <class Type>
bool Stack<Type>::pop(Type &item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
上述提供了类模板的一个例子,类模板函数最好与类在同一个文件中。
使用类模板的例子:
Stack<int> kernels;
Stack<string> colonels;
如果要使用指针和涉及动态内存分配的东西,可能需要重新设计一下类模板。我们在上面已经可以看到template
的存在了,实际上,类模板可以使用的不仅仅时Typename
这一个参数,比如,它可以是template
,n可以称为时表达式参数。表达式参数有一些限制。表达式参数可以是整型、枚举、引用或指针。另外,模板代码不能修改参数的值,也不能使用参数的地址。
模板多功能性
e.g
Array< Stack<int> > asi;
Array< Array<int,5>, 10> twodee;
可以使用多个类型参数
e.g
Template
可以为类型参数提供默认值
Template
模板具体化
Template <> class SortedArray<const char char*>
{
}
这样就可以专门地为该种数据类型设计函数。
4. 部分具体化
即部分限制模板的通用性
e.g
Template
Template class Thing>
Template <typename T> void counts();
Template <typename T> void report(T &);
然后,在函数中再次将模板声明为友元。这些语句根据类模板参数的类型声明具体化。
Template <typename TT>
Class HasFriendT
{
....
Friend void counts<TT>();
Friend void report<>(HasFriendT<TT> &)
};
在写函数的时候不用再指定具体的参数类型了。
3.模板类的非约束模板友元函数
可以使用多个不同的类具体化
Template <typename T>
Class ManyFriend
{
....
Template <typename C,typename D> friend void show2(C &,D &);
};
模板别名
可以使用typedef为模板具体化指定别名:
Typedef std::array<double,12> arrd;
Typedef std::array<int,12> arri;
当然,也可以这么做:
template<typename T>
using arrtype=std::array<T,12>;
定义好以后,就可以这样使用了:
Arrtype<double> gallons;
Arrtype<int> days;
注意:Typedef const char * pc1;
与Using pc1=const char *;
是等价的
class Tv
{
public:
Friend class Remote; //Remote can access Tv private parts
};
Class Tv:
{
Friend void Remote::set_chan(Tv &t, int c);
};
Class Tv;
Class remote:{...};
Class Tv:{...};
可不可以是
Class remote;
Class Tv:{...};
Class remote: {...};
呢?答案是否定的,因为,这样做对于Tv类来说它能看到的信息是remote是一个类,但是没有得到友元成员函数的定义,所以它就很迷惑了。所以就是不可以的。
有的时候,为了方便,我们可以将一个类声明在另一个类中,这个类其实就相当于一种自定义的数据结构,而且这种类的作用域依实现而不同,是一种很好的封装的方法。
e.g:
Class example1:
{
class example2:
{
};
};
异常的出现是为了更好地检测出代码中潜在的问题,但异常也使代码变得更复杂了,可谓有一利有一弊吧。
系统有两种方式处理错误,这里简单的介绍一下
catch(...) {...}
这样它就可以捕获任何异常。terminate()
。在默认情况下,terminate()
调用abort()
函数。当然,我们也可以指定要调用的函数。#include
Using namespace std;
Void myQuit()
{
cout<<“Terminating due to uncaught excepiton\n“;
exit(5);
}
Set_terminate(myQuit);
RTTI是运行阶段类型识别(Runtime Type Identification)的简称。
cpp有三个支持RTTI的元素
1.dynamic_cast运算符
它是最常用的RTTI组件。它不能回答“指针指向的是哪类对象“这样的问题,但能够回答”是否可以安全地将对象的地址赋给特定类型的指针“这样的问题。
e.g:
Class Grand :{...};
Class superb:public grand {...};
Class magnificent:public superb{...};
Grand *pg=new grand;
Superb *pm=dynamic_cast<supurb *>(pg);
这提出了一个问题 ,指针pg的类型是否可被安全地转换为superb*
?如果可以,运算符将返回对象的地址,否则返回一个空指针。也可以将这个运算符用于引用啦,但是没有一个指示错误的空指针,所以只能用try块和catch块在检测了。
2. typeid运算符和type_info类
Typeid运算符使得能够确定两个对象是否为同种类型。它与sizeof有些相像,可以接受两种参
数。
typeid(Magnificent) == typeid(*pg)
四个类型转换运算符
Static_cast (expression)