1.编程范式 programming paradigm
包括OO,modular paradigm
OO是面向对象编程,回顾与之相关的重要概念,类,继承,封装,数据抽象和动态绑定。
而module不同于member function,不需要去创造一个对象来使用它。
类似于泛型算法。就比如C++ stl的顺序容器并没有过度使用面向对象,而是通过一组算法库来实现对所有顺序容器和数组的操作。
又比如之后遇到的,对于利率相关的函数,比如现值,年金,终值我们不是通过构造类和OO,而是通过把它们放在一个namespace中。
2.头文件用来连接名字的声明declaration和使用,对于头文件的使用有一些细节问题,头文件一般是函数的声明,即函数原型,而函数的具体定义包含在相应的源文件中。同样地,类的定义在头文件中,类的成员函数的声明也在头文件中,但是类的成员函数一般具体定义在code file中,这时code file需要include头文件。
头文件与相应的源文件都要注意使用预处理器来防止重复包含头文件比如
#ifndef SALES_ITEM_H
#ifndef SALES_ITEM_CPP
在包含头文件时要注意,头文件一般来自于三个地方
D1: System directories
D2: Remote (‘Other’) directories
D3: Local directories
但是如果是D2,D3,那么相应的code file也要在当前目录下,并且包含时是包含头文件而不需要包含code file
// Person.hpp
// "Hello World" class. Function declarations.
// (C) Datasim Education BV 2005-2006
//
#ifndef Person HPP
#define Person HPP
#include "datasimdate.hpp" // My dates and other useful stuff
#include // Standard string class in C++
using namespace std;
class Person
{
public: // Everything public, for convenience only
// Data
string nam; // Name of person
DatasimDate dob; // Date of birth
DatasimDate createdD; // When object was created
public:
Person (const string& name,const DatasimDate& DateofBirth);
void print() const;
int age() const;
};
#endif
The body of these functions is given in the .cpp file:
// Person.cpp
// "Hello World" class
// Last Modification Dates
// 2006-2-17 DD Kick-off
// (C) Datasim Education BV 2005-2006
#include "Person.hpp"
Person::Person (const string& name, const DatasimDate& DateofBirth)
{
nam = name;
dob = DateofBirth;
createdD = DatasimDate(); // default, today
}
void Person::print() const
{ // Who am I?
cout << "\ n** Person Data **\ n";
cout << "Name: " << nam << ", Date of birth: " << dob
<< ", Age: " << age() << endl;
}
int Person::age() const
{
return int( double(DatasimDate() - dob) / 365.0);
}
The test program is defined in the current directory and is given by:
// TestPerson.cpp
// "Hello World" Testing the first C++ class
// (C) Datasim Education BV 2005-2006
#include "datasimdate.hpp" // Dates and other useful stuff
#include "Person.hpp" // Interface functions for Person
#include // Standard string class in C++
using namespace std;
int main()
{
DatasimDate myBirthday(29, 8, 1952);
string myName ("Daniel J. Duffy");
Person dd(myName, myBirthday);
dd.print();
DatasimDate bBirthday(06, 8, 1994);
string bName ("Brendan Duffy");
Person bd(bName, bBirthday);
bd.print();
return 0;
}
The output from this program is:
** Person Data **
Name: Daniel J. Duffy, Date of birth: 29/8/1952, Age: 53
** Person Data **
Name: Brendan Duffy, Date of birth: 6/8/1994, Age: 11
3.对于函数类和模板类,必须(include)包含code file而不是包含头文件
函数类的声明还是放在头文件中,而函数类的具体定义放在源文件中,这个具体定义的源文件也必须包含头文件,这与之前是非模板类也是一样的。头文件和源文件code file都要使用预处理器防止重复包含头文件。
4.Error的类型
Linkage errors occur because the bodies of functions or the initialisation of data cannot be
found. The root cause of these errors is usually:
E1: A typing error; for example, mismatch between the signature of a function as declared in
a header file and its definition in the code file
E2: You forgot to add the relevant source file to your project
An example of E1 would be in the person class where one of its member functions is declared
as:
int age(); // NO "const" in this declaration
while in the code file the function is defined as:
int Person::age() const
{
return int( double(DatasimDate() - dob) / 365.0);
}
Summarising, linker errors arise because the linker cannot find the code for a function that has
been used in some source file.
5.灵活地使用struct
Some general remarks on structs are:
They are useful as data containers All the members are public (accessibility by client code is not an issue) They can be used in conjunction with classes In some applications they are used as a building block in interface technology (for example, the Component Object Model (COM))
struct可以用类似于数组的方式进行初始化。
6.在实际编程中,我们常常从数据库加载数据,这就需要把各种数据类型转成string,或者把string转成各种数据类型。这需要通过下面的过程实现。 1)Place the data type into a string stream object
2)Convert the string stream object to a string
3)Return the new string object to the client code
The actual code is:
// Hard-coded example for starters
double myDouble = 1.0;
stringstream s;
s << myDouble;
string result = s.str();
cout << "String value is: " << result << endl;
实际中,我们把不同的类型转换为string,这时候可以通过函数模板来实现
template
string getString(const T& value)
{
stringstream s;
s << value;
return s.str();
}
// SimpleBondPricing.hpp
//
// Simple functions for interest rate calcuations.
//
// (C) Datasim Education BV 2006
//
#ifndef SimpleBondPricing HPP
#define SimpleBondPricing HPP
#include
using namespace std;
namespace Chapter3CPPBook // Logical grouping of functions and others
{
// Handy shorthand synonyms
typedef vector Vector;
// Recursive function to calculate power of a number. This
// function calls itself, either directly or indirectly
double power(double d, long n);
// Future value of a sum of money invested today
double FutureValue(double P0, long nPeriods, double r);
// Future value of a sum of money invested today, m periods
// per year. r is annual interest rate
double FutureValue(double P0, long nPeriods, double r, long m);
// Continuous compounding, i.e. limit as m -> INFINITY
double FutureValueContinuous(double P0, long nPeriods, double r);
// Future value of an ordinary annuity
double OrdinaryAnnuity(double A, long nPeriods, double r);
// Present Value
double PresentValue(double Pn, long nPeriods, double r);
// Present Value of a series of future values
double PresentValue(const Vector& prices,long nPeriods,double r);
// Present Value of an ordinary annuity
double PresentValueOrdinaryAnnuity(double A,long nPer,double r);
}
#endif
还有一个细节就是,如果我们需要的类型一般都是vector
template
Numeric compare(Numeric &A,Numeric &B){//}
9. Call by reference && Call by value 何时使用引用类型形参,三种情况,如果这时为了避免复制,那么要注意使用const.。常量成员函数是指不能改变调用该成员函数的对象。
10.静态数据成员和静态成员函数。C++中的几类特殊的内存。比如heap-based memory,这是程序运行时所占用的一块区域,自由存储区,涉及new,delete。automatic memory,这是自动对象在内存中存储的区域,随着函数的调用被创建和撤销。static memory,静态存储区域。包括四类变量:
全局变量和namespace中的变量
类的静态数据成员和成员函数
静态局部对象
回顾在之前提到静态变量时,我们就说了静态对象一般有两层含义,一是其为静态存储的,这就是指static memory,静态全局变量和全局变量都是静态存储的,区别在于前者是被当前文件访问,后者是被整个程序访问。而静态局部对象的核心在于其生命期同整个程序的生命期。
静态存储区域中的变量只可以被创建一次,且生命期与整个程序的生命期相同,在程序结束前都不会被撤销。
类的静态数据成员是类的一部分,而不是类的对象的一部分。
类的静态成员函数仅可以访问类的静态数据成员。
类的静态数据成员的初始化以及使用的例子。
class aclass{
public:
static int a;
};
int aclass::a=0; //初始化
void main(void)
{
int amain=0;
amain=aclass::a;//使用,无需定义相关类的变量而直接使用之。
}
再次强调类的静态对象是类的一部分,而不是类的对象的一部分。
1、静态数据成员在定义或说明时前面加关键字static。
2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:
<数据类型><类名>::<静态数据成员名>=<值>
这表明:
(1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。
(2) 初始化时不加该成员的访问权限控制符private,public等。
(3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。
3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。
4、引用静态数据成员时,采用如下格式:
<类名>::<静态成员名>
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
11.提高performance的一些技巧:
1)内联函数,就类似于C语言的预处理器中宏调用与函数调用的区别。回顾预处理器是程序编译过程中的第一步。宏定义是将出现名字的地方都用替换文本进行替换,注意括号的使用,#define MAX(x,y) (X>Y)?(X):(Y)。在C语言中提高效率的做法还有使用register关键字,把变量存放在寄存器中。
2)use of anonymous object,This new specifier is a hint to the compiler that it should attempt to generate code when the
above member functions are called instead of laying down the code for the function once and then calling through the usual function call mechanism (Stroustrup, 1997). This may improve performance but this is not guaranteed.
比如:
Point Point::MidPoint(const Point& p2) const
{ // Calculate the point between the two points
Point result((x+p2.x)*0.5 , (y+p2.y)*0.5 );
return result;
}
Point Point::MidPoint(const Point& p2) const
{ // Calculate the point between the two points
// Create "any old" point
return Point( (x+p2.x)*0.5 , (y+p2.y)*0.5 );
}
3)关于循环的优化,要注意减少使用局部变量,并且避免矩阵和vector的复制
12.Chapter5 pp80.操作符重载, operator overloading in C++
在C++ PRIMER中已经指出有些重载操作符作为类的成员,有些则一般不作为。类的成员都有隐含的this指针作为形参,除了类的静态成员函数,static member function仅可以访问类的静态成员。
比如赋值操作符,复合赋值操作符一般重载为类的成员。而算术操作符一般重载为非类型成员,并将它们声明为友元,使其能够访问类的私有对象。
一些常见的使用操作符重载的情形
There are several applications of operator overloading that can be used in Financial Engineering:
Operations on Date, Time and TimeStamp objects (Duffy, 2004)
Defining classes for algebraic data structures (Landin, 1969)
Matrix algebra in C++ (Duffy, 2004)
Computer graphics (Foley et al., 1990)
Expressions and expression parsers, for example composing mathematical equations at runtime
比如我们创建一个DatasimDate类型,要解决以下的问题
What is the date 100 days from now?
Compare two dates (is a date before another date?)
How many days are there between two dates?
Increment/decrement a date by one day Add a part of a year to a date, for example what is the date 6 months from now?
又比如我们要实现自定义Vector类型的点积运算,就可以重载^操作符
Vector operator ˆ (const Vector& vec) const;
回顾之前的常量成员函数,不能改变调用该函数的对象,这里重载操作符也可以视为特殊的成员函数,因此也可以声明为const
以复数的加法为例,这里还用到了之前说的增加效率的anonymous object技巧
Complex Complex::operator + (const Complex& c2) const
{ // Add two complex numbers
return Complex(x + c2.x, y + c2.y);
}
一个特殊的情形,赋值操作符的重载,assignment operator
Complex& Complex::operator = (const Complex& c)
{
// Avoid doing assign to myself
if (this == &c)
return *this;
x = p.x;
y = p.y;
return *this;
}
可以回顾一下之前提到的两类特殊的构造函数中,复制构造函数,复制构造函数除了隐含的this外只有一个形参,是对原本类型的引用。而重载赋值操作符也是。
输入和输出操作符的重载
返回类型分别是istream &和ostream &,形参为istream &,const CLASS &,ostream &,const CLASS &。也就是说重载输入或输出操作符不是类的成员,要将其声明为友元。
例子:
friend ostream& operator << (ostream& os, const Complex& cmp);
ostream& operator << (ostream& os, const Complex& cmp)
{ // Print the complex number
os << "(" << cmp.x << ", " << cmp.y << ")\ n";
return os;
}
也可以将一个类声明为友元,比如
class A
{
private: // Don’t tell others who my friends are
friend class B; // Hi B class, you my friend
// ...
};
13.C++中一些有用的类型,Array,Vector,Matrix,回顾类模板和函数模板,模板类型形参和模板非类型形参,以及模板类型推断和类型转换的问题。
且C语言规定,argv[0]是启动该程序的程序名,因此argc的值至少为1.第一个可选参数是argv[1],最后一个可选参数是argv[argc-1],且argv[argc]为空指针。
16.Chapter6 pp96 Memory Management 内存管理
有两类分配内存的方式,via the stack and via the heap
对于stack-based memory,要注意的就是变量在scope外是会自动撤消的,这与之前所说的不能返回局部对象的引用或者指针是一个意思。
int main()
{
// Define a scope
int j = 2;
cout << j << endl;
}
cout << j;
return 0;
}
这段代码是错的,因为变量j在块语句外已经被撤销了。This code will give a compiler error because the variable j does not exist outside the scope of
一般不是采用幻数+固定长度的数组,而是采用动态分配内存的方式。
动态变量与动态数组,heap-based memory,new delete的使用。
// Now create an array of options
SimpleOption* optArray;
//Now we define the array of options. Notice that the default constructor will be called ten times:
const int N = 10;
optArray = new SimpleOption[N]; // Default constructor called
//We now modify each option in the array:
for (int j = 0; j < N; j++)
{ // Member data public for convenience only
optArray[j].T = 1.0; // 1 year expiry
optArray[j].K = 100.0; // Strike price
optArray[j].print();
}
//Finally, we clean up the memory as follows (the square brackets are essential):
delete [] optArray;
回顾几种等价的表达,比如
int a[10]; //int *p=a; int *p=&a[0];
a[3]; //p[3];等价于*(p+3)
动态数组的应用,一个例子,比如我们要建立复数的数组类,名为ComplexArray
class ComlexArray{
private:
Complex* arr;
int size;
public:
ComplexArray(int size);
ComplexArray(const ComplexArray& source);
};
The body of these constructors is given by
// Constructor with size
ComplexArray::ComplexArray(int Size)
{
arr=new Complex[size];
size=Size;
}
// Copy constructor
ComplexArray::ComplexArray(const ComplexArray& source)
{
// Deep copy source
size=source.size;
arr=new Complex[size];
for (int i=0; i
virtual ∼ComplexArray();
The body of the destructor is given by:
ComplexArray::∼ComplexArray()
{
delete[] arr;
}
17.Chapter7 Functions, Namespace, Inheritance
回顾const指针以及指向const对象的指针
int const* ptr
下文包含对函数指针的介绍以及namespace的介绍,A namespace is used to group logically related C++ code.比如之前提到的interest rates相关的函数。要注意namespace中的变量也是静态存储的。回顾static memory,heap-based memory,stack-based memory要注意的问题(在scope外被撤销)
使用函数指针,并将它们作为类的成员,使用typedef简化函数指针类型的名称。
对于函数我们要知道,It must be possible to replace the code that implements a function by another block of code (this is called a Strategy pattern)
我们还要区分四类函数:
Scalar-valued function (maps a double to a double, for example)
Vector function (maps a double into a vector)
Real-valued function (maps a vector into a double)
Vector-valued function (maps a vector into a vector)
函数指针的一个例子,十分重要
void genericFunction (double myX, double myY,
double (*f) (double x, double y))
{
// Call the function f with arguments myX and myY
double result = (*f)(myX, myY);
cout << "Result is: " << result << endl;
}
在C++ Primer中提到,模板是泛型编程的基础,泛型编程是独立于特定类型的方式书写代码。而除了使用函数模板作用于不同类型的形参,而可以使用函数指针来实现泛型。比如泛型函数genericFunction,上面就是泛型函数的重要例子。
而泛型编程和OO都支持某种层面上的多态性polymorphism. 泛型编程是编译多态性。
int main()
{
double x = 3.0;
double y = 2.0;
genericFunction(x, y, add);
genericFunction(x, y, multiply);
genericFunction(x, y, subtract);
return 0;
}
A namespace is a mechanism for expressing logical grouping (Stroustrup, 1997). This mechanism
allows us to group code that belongs together into a common package as it were.
之前interest rates就是namespace的一个例子,我们再介绍如何使用namespace中的函数。两种基本的方法:
By means of qualified names
The ‘using’ declaration
The ‘using’ directive
具体而言 ,
namespace MyFunctions
{
double diffusion (double x) { return x; }
double convection (double x) { return x*x; }
}
namespace YourFunctions
{
double diffusion (double x) { return 2.0; }
double convection (double x) { return 1.0; }
}
cout << YourFunctions::convection (10.0) << endl;
using namespace MyFunctions;
cout << "Directive: \ n";
cout << convection (10.0) << endl;
cout << diffusion (2.0) << endl;
有的时候 namespace的名字太长,我们想简写它,或者说给它一个alies,就例如typedef一样,这时候可以使用
namespace YA = YourFunctions; // Define alias NS called YA
cout << YA::diffusion (2.0) << endl;
namespace StandardInterface
{
// Namespace consisting of function pointers
double (*func1) (double x);
double (*func2) (double x, double y);
}
namespace Implementation1
{
double F1 (double x) { return x; }
double F2 (double x, double y) { return x*y; }
}
namespace Implementation2
{
double G1 (double x) { return -x; }
double G2 (double x, double y) { return -x*y; }
}
StandardInterface::func1 = Implementation1::F1;
StandardInterface::func2 = Implementation1::F2;
We then can use the functions in a transparent way by using an alias:
using namespace StandardInterface;
cout << func1(2.0) << ", " << func2(3.0, -4.0) << endl;
func1 = Implementation2::G1;
func2 = Implementation2::G2;
cout << func1(2.0) << ", " << func2(3.0, -4.0) << endl;
19.Inheritance Mechanism
回顾动态绑定的两个条件,一是虚函数,二是通过对基类的引用或者指针。派生类的实例也是基类的实例。只有在运行时,才知道绑定的对象的类型,这是运行多态性。继承层次的设计。
比如Person是基类,employer是派生类,employer多几个数据成员,比如salary, retiring_age,又比如要实现派生类版本的per_print函数。
在调用虚函数时可以传递对派生类对象的引用或指针,派生类实例本身也是基类的实例,这时候程序会匹配调用派生类的函数版本。
The use of virtual keyword in C++ allows us to implement polymorphism.
20.Multiple Inheritance多重继承
class D: public Base 1, public Base 2
{
//回顾公有继承,私有继承,和protected继承,不改变派生类对基类的访问,改变的只是类的用户对于派生类成员的访问
private:
Base 2* base2;
public:
//Members here
};
21.非线性方程的解
f(x)=0 比如我们要求隐含波动率 C(sigma)-C_market=0
常见的方法:
1)Bisection Method
2)Newton's Method
3)Secant Method
4)Steffensen iteration
Bisection Method 二分法,假定f(x)在(a,b)之间有零解,f(a)f(b)<0。
Newton's Method :
X_{n+1}=X_{n}+h_{n}, h_{n}=-f(x_n)/f'(x_n)
Secant Method: 可以视为Newton方法的变形,但是需要两个初始值
X_{n+1}=X_{n}+h_{n}, h_{n}=-f_{n}*(x_{n}-x_{n-1})/(f_{n}-f_{n-1})
除了二分法外,其他方法都要设定初始值,且初始值对于方程能否找到收敛解十分重要。回顾隐含波动率的程序。
我们可以用OO的方法写NonlinearSolver,把NonlinearSolver作为父类,具体的求解方法作为派生类,比如Steffensen iteration
class NonlinearSolver{
public:
double (*myF)(double x); //NonlinearSolver类有两个数据成员,一个是函数指针,用函数名来初始化这一变量。表示f(.)
double tol; //预期的精度
public:
NonlinearSolver(double (*function)(double)) {}//
virtual double solve()=0; //这是求值的成员函数,后面可以定义派生类的不同版本,比如牛顿法,Steffensen iteration方法。这里声明为纯虚函数,指出这个函数的版本没有意义,该函数的后代版本提供了可覆盖的接口
};
class SteffensenSolver: public NonlinearSolver{
private:
double x0; //Initial Guess
double xPrevious, xCurrent;
long n; //Number of iterations
public:
SteffensenSolver(double guess,double (*myFunc)(double x))
{
x0=guess;
xPrevious=x0;
myF=myFunc;
}
double solve()
{
double tem;double hn; n=1; xPrevious=x0;
temp=myF(xPrevious);
hn=(myF(xPrevious+temp)-temp)/temp;
hn=temp/hn;
xCurrent=xPrevious-hn;
xPrevious=xCurrent;
n++;
if(::fabs(hn)<=tol)
{
return xCurrent;
}
goto L1;
}
void printStatistics() const
{
cout<<"\n Data pertaining to Steffensen's method\n";
cout<<"Value:"<
在求解隐含波动率时,我们只要先写函数C(sigma)-C_Market,再将函数名作为实参用来初始化一个Steffensen类对象,然后调用solve成员函数来求解。
double CallPrice(double sig)
{
double S=59;
double K=60;
double r=0.067;
double marketPrice=2.82;
double b=r;
double T=1;
double temp=sig*sqrt(T);
double d1=(log(S/K)+(b+(sig*sig)*0.5)*T)/temp;
double d2=d1-temp;
double calculatedValue=(S*exp((b-r)*T)*N(d1)-(K*(exp(-r*T)*N(d2));
return marketPrice-calculatedValue;
}
double guess=0.2;
SteffensenSolver Steff(guess,CallPrice);
Steff.tol=0.0001;
double resultST=steff.solve();
这也是函数指针在面向对象和类当中的应用,再回顾函数指针与genericFunction。函数指针作为类的成员。比如方程的求解。随机微分方程也是类似的,函数指针类型的数据成员可以用不同函数来进行初始化。
如果我们不考虑精度的不同要求,也不考虑迭代次数,和初始值等问题,只要传递一个函数。那么我们不用先定义类,再定义类的成员函数solver,而是直接采用solver作为成员函数,但是前者的好处是可以利用动态绑定机制来实现多方法计算非线性方程的解。
22.Chapter 8 Advanced Inheritance and Payoff——Class Hierarchies
这一部分将涉及C++中继承机制的一些高级特点。
有关generalisation/specialisation
PS:思考之前的非线性方程求解方法的继承层次设计。要注意什么时候时候面向对象和继承,回顾之前提到的不同的编程范式。是不是可以把不同的求解方法放在一个namespace里,辅助使用函数重载而不是用面向对象来实现。
一个设计好的继承层次式的我们拥有highly reusable and flexible software. 我们常常在base class中写invariant code,然后派生类继承这些invariant code。这称之为implementation inheritance,实现继承。另一种形式称之为Interface inheritance,接口继承。我们给出一个base class但是不提供任何的实现。pure virtual member functions纯虚函数的含义。
见下面的链接,接口继承与实现继承的区别
所谓接口继承,就是派生类只继承函数的接口,也就是声明;而实现继承,就是派生类同时继承函数的接口和实现。
1)声明一个纯虚函数(pure virtual)的目的是为了让派生类只继承函数接口,也就是上面说的接口继承。
2)声明非纯虚函数(impure virtual)的目的是让继承类继承该函数的接口和缺省实现。与纯虚函数唯一的不同就是其为继承类提供了缺省操作,继承类可以不实现自己的操作而采用基类提供的默认操作。
3)声明非虚函数(non-virtual)的目的是为了令继承类继承函数接口及一份强制性实现。相对于虚函数来说,非虚函数对继承类要求的更为严格,继承类不仅要继承函数接口,而且也要继承函数实现。也就是为继承类定义了一种行为。
---------
回顾malloc int(3); new double(3.0);函数的返回类型是void *
具体类和抽象类 abstract and concrete class,抽象类不是抽象类型。后者对应的概念是数据抽象,依赖于接口和实现相分离的技术。
抽象类是为了抽象和设计的目的而建立的,处于继承层次结构的上层。不能实例化,抽象类是被具体类继承用的
具体类是能够建立对象的类。
抽象类的规定
(1)抽象类只能用作其他类的基类,不能建立抽象类对象。
(2)抽象类不能用作参数类型、函数返回类型或显式转换的类型。
(3)可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性。
http://blog.csdn.net/Slience_Perseverance/article/details/20536277
-----------------------------------------------------------------------------------------------------------------------------
插一句知乎上的回答:关于OO
没什么只有OO能做到,OO更多的是给了你一种能力,一种忽略细节的能力:忽略的越多,人类有限的智力就可以容纳越多越复杂的问题,并由此提高生产效率。
任何抽象的本质都是忽略,OO刚好是其中一种。
面向对象的三板斧分别是封装,继承和多态,他用封装将问题中的数据和对数据进行处理的函数结合在了一起,形成了一个整体的对象的概念,这样更加符合人的思维习惯,更利于理解,自然在理解和抽象一些复杂系统的时候也更加容易。他用继承来应对系统的扩展,在原有系统的基础上,只要简单继承,就可以完成系统的扩展,而无需重起炉灶。他用多态来应对需求的变化,统一的接口,却可以有不同的实现。
------------------------------------------------------------------------------------------------------------------------------
23.一个使用抽象类,具体类,接口继承和实现继承的例子。比如我们为了抽象和设计的目的,定义了一个名为Payoff的抽象类,该类没有任何的数据成员。这个好处是显然的,因为这样派生类就不需要继承多余的数据成员而只要定义各自的数据成员。此外,Payoff的虚函数我们将其声明为纯虚函数,这样派生类就启用接口继承,即只继承函数的接口,也就是声明,而不继承函数的实现。
回顾析构函数一般都声明为虚函数。
对于上面的抽象类Payoff,我们可以定义非常多的派生类来继承这个抽象类,比如Spreads,Straddle,Strangles这些交易策略都有payoff,而且每个派生类的成员都不同。继承层次的设计十分重要。一定要注意抽象类的使用。
代码如下:
class Payoff
{
public:
// Constructors and destructor
Payoff(); // Default constructor
Payoff(const Payoff& source); // Copy constructor
virtual ∼Payoff(); // Destructor
// Operator overloading
Payoff& operator = (const Payoff& source);
// Pure virtual payoff function
virtual double payoff(double S) const = 0; // Spot price S
};
class CallPayoff: public Payoff
{
private:
double K; // Strike price
public:
// Constructors and destructor
CallPayoff();
CallPayoff(double strike);
CallPayoff(const CallPayoff& source);
virtual ∼CallPayoff();
// Selectors
double Strike() const; // Return strike price
// Modifiers
void Strike(double NewStrike); // Set strike price
CallPayoff& operator = (const CallPayoff& source);
// Implement the pure virtual payoff function from base class
double payoff(double S) const; // For a given spot price
};
CallPayoff::CallPayoff(const CallPayoff& source): Payoff(source)
{ // Copy constructor
K = source.K;
}
CallPayoff& CallPayoff::operator = (const CallPayoff &source)
{ // Assignment operator
// Exit if same object
if (this==&source) return *this;
// Call base class assignment
Payoff::operator = (source);
// Copy state
K = source.K;
return *this;
}
double CallPayoff::Strike() const
{
// Return K
return K;
}
void CallPayoff::Strike(double NewStrike)
{ // Set K
K = NewStrike;
}
double CallPayoff::payoff(double S) const
{ // For a given spot price
if (S > K)
return (S - K);
return 0.0;
// remark; possible to say max (S - K, 0)if you prefer
}
24.Lightweight Payoff classes
之前的Payoff classes中我们定义了一个纯虚函数并且利用接口继承,在派生类中实现了它。这么做是可行的,但是有很多的不足。
1.这样一来,不同的派生类都要定义一个新的payoff function。而不像非纯虚函数和实现继承那样,派生类可以继承该 函数的接口以及缺省实现。
2.我们一旦创造了一个实例就不能改变这个实例的类型。
我们可以采用另一种方法,见下图
我们定义一个Payoff的父类,这时该类不再是一个抽象类,Payoff类有一个指针类型的成员,
we create a single payoff class that has a pointer to what is essentially an encapsulation of a payoff function.
class Payoff
{
private:
PayoffStrategy* ps;
public:
// Constructors and destructor
Payoff(PayoffStrategy& pstrat);
// Other member functions
};
class PayoffStrategy
{
public:
virtual double payoff(double S) const = 0;
};
//A specific derived class is given by:
class CallStrategy : public PayoffStrategy
{
private:
double K;
public:
CallStrategy(double strike) { K = strike;}
double payoff(double S) const
{
if (S > K)
return (S - K);
return 0.0;
}
};
这里PayoffStrategy也是声明了一个纯虚函数,而且是抽象类,而CallStrategy作为派生类继承了纯虚函数的接口,并且给出了自己实现的操作。但是不同就在于这时的Payoff不再是抽象类,而是包含指向基类的指针作为数据成员。
上面的方法被称为Strategy Pattern。这比之前的方法更加灵活和有效。这时候我们创建的PayoffStrategy类,本身没有给予payoff函数任何意义,PayoffStrategy也不对应于 任何折扣策略,它的存在只是为了让其他类继承。使一个函数作为纯虚函数就是指出这个函数版本没有意义,该函数的后代版本提供了可以覆盖的接口。
--------------------------------------------------------------------------------------
回顾C++ Primer中我们要写一个书的基类,并定义某种打折方式的派生类。如果我们有很多的打折方式,那么不一定是让各种打折方式的派生类来继承书的基类。我们可以采用一个更灵活的做法。让一个Disc_Item类继承Base_Item类,然后再让Bulk_Item继承Base_Item类,Disc_Item添加了新的数据成员,更好地适应了不同的打折方案。C++ Primer p492.这时重构的典型例子,重构包括重定义类的层次,将操作或数据从一个类移到另一个类。
26.Super lightweight payoff functions
再次重构,这时候我们要利用函数指针,函数指针在定义类时的用途是十分重要的。(此前,我们用函数指针实现了genericFunction,这是泛型的例子。用函数指针作为namespace中的变量,然后再利用作用域操作符定义该namespace中函数指针变量的具体版本。以及我们用函数指针作为类的数据成员来实现非线性函数求解,而具体的求解则作为NonliverSolver类的成员函数。)要注意NonlinearSolver类中solver函数也是纯虚函数,该函数仅仅是为了让派生类能够继承它的接口,各种数值方法的派生类给出了solve函数的不同版本。一定一定要灵活地使用纯虚函数。再比如Disc_Item中net_price()也是纯虚函数。
比如我们要写不同的期权定价方法,那么可以定义一个抽象类OptionSolver,该类仅包含一个纯虚函数option_pricing,再让不同的求解方法作为派生类继承这个类,然后定义自己的option_pricing版本。然后再定义一个类Option,Option的数据成员是指向OptinoSolver类型的指针。
下面介绍的实现payoff类和函数的方法是用函数指针来作为类的成员实现的,有点类似于之前的NonlinearSolver类的设计:
class OneFactorPayoff
{
private:
double K;
double (*payoffFN)(double K, double S);
public:
// Constructors and destructor
OneFactorPayoff(double strike,double(*payoff)(double K,double S));
// More ...
double payoff(double S) const; // For a given spot price
};
OneFactorPayoff::OneFactorPayoff(double strike,double (*pay)(double K, double S))
{
K = strike;
payoffFN = pay;
}
double OneFactorPayoff::payoff(double S) const
// For a given spot price
return payoffFN(K, S); // Call function
}
27.要注意派生类的复制构造函数一般显示地使用基类的复制构造函数初始化对象的基类部分。赋值操作符与复制构造函数类似,如果派生类定义了自己的赋值操作符,那么该操作符必须对基类部分进行显示赋值。
具体如下:
class Base{
public:
Base(const Base &rhs);
private:
..//
};
class Bulk_Item: public Base{
Bulk_Item(const Bulk_Item &d):
Base(d) {}
};
Bulk_Item& Bulk_Item::operator=(const Bulk_Item &rhs){
if(this!=&rhs)
Base::operator=(rhs); //使用赋值操作符对基类部分进行显示赋值
}
26.C++中的struct与class很像,区别仅仅在于默认访问级别,并不同于C中的结构,回顾使用结构来实现统计输入的单词次数,struct tnode{}
STL的顺序容器都具有相应的适配器,容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。例如,stack适配器可使任何一种顺序容器以栈的方式工作。
27.模板函数也可以和非模板函数一样生命为inline,说明符放在形参表之后,返回类型之前
template inline T Min(const T&,const T&)
从函数实参确定模板实参的类型和值得过程称之为
模板实参推断。