指针、引用和虚函数
指针和引用是很好理解的,变量的引用相当于给变量取了个别名,在函数调用时,传引用是会将参数列表的值改变的。
#include <iostream>
using namespace std;
int main()
{
int m=6;
int &p=m;
int *q = &m;
cout<<&p<<endl;
cout<<q<<endl;
return 0;
}
最终输出的结果都是0x7ffdd9aff7b4,即为内存中存储整型变量m的地址。对于虚函数,它是实现多态公有继承的关键。什么是多态呢?我们的派生类是可以使用基类的方法的,但是如果只是使用基类的方法,我们是没有对基类方法作任何修改的,但是我们会遇到这种情况,我们希望一个方法在基类和派生类中的行为是不一样的,即方法的行为应取决于调用该对象的方法,这就是多态。实现多态有有种重要机制:(1)在派生类中重新定义基类的方法(2)使用虚方法。对于虚函数的介绍,这篇博客写的不错:虚函数,指针和引用
稍带提到一点,虽然 一个函数定义为虚函数,那么无论它传下多少层,都将保持为虚函数,而不必每次都加关键字virtual。但是在实际的过程中加上virtual关键字是有必要的,这有有利于代码的阅读
多态共有继承的实例
这是一个C++ Primary上简单的例子,需要开发两个类,基类Brass和派生类BrassPlus。在派生类中增加了几个私有数据maxLoan、rate和owesBank。我们不必知道这些变量的含义,我们不关心方法的实现,而是更关心多态性。两个类的定义如下:
#ifndef BRASS_H
#define BRASS_H
#include<string>
class Brass
{
private:
std::string fullName;
long acctNum;
double balance;
public:
Brass(const std::string &s = "Nullbody",long an = -1,double bal = 0.0);
void Deposit(double amt);
virtual void Withdraw(double amt);
double Balance() const;
virtual void ViewAcct() const;
virtual ~Brass() {}
};
class BrassPlus:public Brass
{
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.11125);
BrassPlus(const Brass & ba,double ml = 500,double r = 0.11125);
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; }
};
#endif // BRASS_H
这里面我们可以看到函数ViewAcct() const和Withdraw(double amt)都被定义为虚函数。也就是表明这个函数在基类和派生类中的行为是不一样,它们的实现如下:
#include "brass.h"
#include<iostream>
using namespace std;
typedef ios_base::fmtflags format;
typedef streamsize precis;
format setFormat();
void restore(format f,precis p);
Brass::Brass(const string & s ,long an,double bal)
{
fullName = s;
acctNum = an;
balance = bal;
}
void Brass::Deposit(double amt)
{
if(amt<0)
cout<<"Negative deposit not allowed; "<<"deposit is cancelled.\n";
else
balance += amt;
}
void Brass::Withdraw(double amt)
{
format initialState = setFormat();
precis prec = cout.precision(2);
if(amt<0)
cout<<"Withdraw amount must be positive; "<<"withdrawal cancelled.\n";
else if(amt<=balance)
balance -= amt;
else
cout<<"Withdrawal amount of $"<<amt<<" exceeds your balance.\n"<<"Withdrawal cancelled.\n";
restore(initialState,prec);
}
double Brass::Balance() const
{
return balance;
}
void Brass::ViewAcct() const
{
format initialState = setFormat();
precis prec = cout.precision(2);
cout<<"Client: "<<fullName<<endl;
cout<<"Account Number: "<<acctNum<<endl;
cout<<"Balance: $"<<balance<<endl;
restore(initialState,prec);
}
BrassPlus::BrassPlus(const string &s, long an, double bal, double ml, double r):Brass(s,an,bal)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
BrassPlus::BrassPlus(const Brass &ba, double ml, double r):Brass(ba)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
void BrassPlus::ViewAcct() const
{
format initialState = setFormat();
precis prec = cout.precision(2);
Brass::ViewAcct();
cout<<"Maximim loan: $"<<maxLoan<<endl;
cout<<"Owed to bank: $"<<owesBank<<endl;
cout.precision(3);
cout<<"Loan Rate: "<<100*rate<<"%\n";
restore(initialState,prec);
}
void BrassPlus::Withdraw(double amt)
{
format initalState = setFormat();
precis prec = cout.precision(2);
double bal = Balance();
if(amt<=bal)
Brass::Withdraw(amt);
else if(amt<=bal+maxLoan - owesBank)
{
double advance = amt - bal;
owesBank += advance*(1.0+rate);
cout<<"Bank advance: $"<<advance<<endl;
cout<<"Finance charge: $"<<advance*rate<<endl;
Deposit(advance);
Brass::Withdraw(amt);
}
else
cout<<"Credit limit exceeded.Transaction cancelled.\n";
restore(initalState,prec);
}
format setFormat()
{
return cout.setf(ios_base::fixed,ios_base::floatfield);
}
void restore(format f, precis p)
{
cout.setf(f,ios_base::floatfield);
cout.precision(p);
}
这里我们可以看到在定义ViewAcct()中,使用到了作用域解析运算符,假设在BrassPlus中定义ViewAcct()没有用作用域解析运算符会怎么样呢?
void BrassPlus::ViewAcct() const
{
...
ViewAcct();
...
}
这样会导致一个问题,这里会默认调用的是BrassPlus::ViewAcct(),这样就会变成一个递归调用,是一个不会终止的函数。
在我们的主函数中调用ViewAcct()方法和Withdraw()方法时,则不用显示的调用,编译器会根据你定义的对像确定自己的行为。下面就是一个例子:
#include <iostream>
#include"brass.h"
using namespace std;
int main()
{
Brass Piggy("Porcelot Pigg",381299,4000.0);
BrassPlus Hoggy("Horatio Hogg",382288,3000.00);
Piggy.ViewAcct();
cout<<endl;
Hoggy.ViewAcct();
cout<<endl;
cout<<"Depositing $1000 into the Hogg Account:\n";
Hoggy.Deposit(1000.00);
cout<<"New balance: $"<<Hoggy.Balance()<<endl;
cout<<"Withdrawing $4200 from the Pigg Account:\n";
Piggy.Withdraw(4200.00);
cout<<"Pigg account balance: $"<<Piggy.Balance()<<endl;
cout<<"Withdrawing $4200 from the Hogg Account:\n";
Hoggy.Withdraw(4200.00);
Hoggy.ViewAcct();
return 0;
}
上面是通过对象调用的,而不是通过指针或引用,没有使用虚方法的特性。由于使用的是公有继承模型,因此Brass指针既可以指向Brass对象,也可以指向BrassPlus对象。所以可以使用一个数组来表示多种类型的对象,这就是多态性(隐士的强制转换)。代码如下:
#include<iostream>
#include"brass.h"
#include<string>
using namespace std;
const int CLIENTS = 4;
int main()
{
Brass *p_clients[CLIENTS];
string temp;
long tempnum;
double tempbal;
char kind;
for(int i=0;i<CLIENTS;i++)
{
cout<<"Enter client's name: ";
getline(cin,temp);
cout<<"Enter client's account number: ";
cin>>tempnum;
cout<<"Enter opening balance: $";
cin>>tempbal;
cout<<"Enter 1 for Brass Account or 2 for BrassPlus Account: ";
while(cin>>kind&&(kind != '1' && kind != '2'))
cout<<"Enter either 1 or 2 ";
if(kind == '1')
p_clients[i] = new Brass(temp,tempnum,tempbal);
else
{
double tmax,trate;
cout<<"Enter the overdraft limit: $";
cin>>tmax;
cout<<"Enter the interest rate as a decimal fraction: ";
cin>>trate;
p_clients[i] = new BrassPlus(temp,tempnum,tempbal,tmax,trate);
}
while(cin.get()!='\n')
continue;
}
cout<<endl;
for(int i=0;i<CLIENTS;i++)
{
delete p_clients[i];
}
cout<<"Done.\n";
return 0;
}