# include<iostream>
using namespace std;
float function(float x){ //被积函数
return 4.0/(1+x*x);
};
class inte_algo{ //定积分基类
protected:
float a,b; //积分区间
int n; //积分区间的等分数
float h,sum; //步长和积分值
public:
inte_algo(float left,float right,int steps){//构造函数
a=left;
b=right;
n=steps;
h=(b-a)/n;
sum=0.0;
};
virtual void integrate(void);//求定积分的虚函数,可设计为纯虚函数
};
class rectangle:public inte_algo{//矩形法积分类
public:
rectangle(float left,float right,int steps):inte_algo(left,right,steps){};
void integrate(void);
};
class ladder:public inte_algo{//梯形法积分类
public:
ladder(float left,float right,int steps):inte_algo(left,right,steps){};
void integrate(void);
};
class simpson:public inte_algo{//辛普逊法积分类
public:
simpson(float left,float right,int steps):inte_algo(left,right,steps){ };
void integrate(void);
};
void inte_algo::integrate(void){//若在基类中设计为纯虚函数,则这段代码应删除
cout<<sum<<endl;
}
void rectangle::integrate(void){
float a1=a;
for(int i=0;i<n;i++){
sum+=function(a1);
a1+=h;
}
sum *=h; //sum=(f(a)+f(a+h)+f(a+2h)+...+f(a+(n-1)*h))*h
cout<<sum<<endl;
}
void ladder::integrate(void){
float a1=a;
sum=(function(a)+function(b))/2.0;
for(int i=1;i<n;i++){
a1+=h;
sum+=function(a1);
}
sum*=h; //sum=(f(a)+2f(a+h)+2f(a+2h)+...+2f(a+(n-1)h)+f(b))h/2
cout<<sum<<endl;
}
void simpson::integrate(void){
sum=function(a)+function(b);
float s=1.0,a1=a;
for(int i=1;i<n;i++){
a1+=h;
sum+=(3.0+s)*function(a1); //系数4,2交替,使用3±1实现
s=-s;
}
sum*=h/=3.0; //sum=(f(a)+4f(a+h)+2f(a+2h)+4f(a+3h)+2f(a+4h)+...+2f(a+(n-2)h+4f(a+(n-1)h)+f(b))h/3
cout<<sum<<endl;
}
void main(void){
rectangle rec(0.0,1.0,10);
ladder lad(0.0,1.0,10);
simpson sim(0.0,1.0,10);
inte_algo *p1=&rec, *p2=&lad, *p3=∼//使用基类指针指向各派生类
p1->integrate();
p2->integrate();
p3->integrate();//虚函数的作用可体现出来,调用的分别是各派生类的虚函数
}
要使程序更加通用,可以将被积函数抽象出 来,以便能够接受任意的函数作为参数进行 数值积分的计算。以下是一个示例程序: import numpy as np def integrate(f, a, b, n): x=np.linspace(a,b,n+1) y=f(x) dx =(b-a)/n integral = dx (np.sum(y)-0.5y[0]- 0.5y[-1]) return integral #测试函数 def f(x): return x**2-2x + 1 # 调用函数进行数值积分 result = integrate(f, 0,1,100) print(result) 在这个程序中,integrate
函数接受四个参 数:f
表示被积函数,a
和b
表示积分区 间的端点,n
表示将积分区间分成多少个小 矩形(即精度)。 使用时,只需要定义被积函数并调用 integrate
函数即可完成数值积分的计算。 例如上述示例中,测试的是 Sf(x)=x^2-2x + 1S 在 S[0,1]S 上的数值积分。
#include<stdio.h>
#include<stdarg.h>
int average(int n, ...) //该函数的功能为:求出任意个数参数的平均值
{
int i = 0;
int sum = 0;
va_list arg; //即:char* arg;
va_start(arg, n); //即:arg = (char*)&n + 4;
//初始化arg为位置参数列表中的第一个参数的地址
for(i=0; i<n; i++)
{
sum += va_arg(arg, int); //即: sum += (*(int *)(arg += 4) - 4);
//此时已经将arg重新赋值为可变参数列表中第二个参数的地址,
//但是此处保留的仍然是上一个参数的地址,然后对保留地址进行
//强制类型转换之后解以用得到内容(参数)
}
return sum/n;
va_end(arg); //即:arg = char* 0; //把arg置为NULL
}
int main()
{
int a = 10;
int b = 20;
int c = 30;
int avg1 = average(2, a, b);
int avg2 = average(3, a, b,c);
printf("avg1 = %d\n",avg1);
printf("avg2 = %d\n",avg2);
return 0;
}
上述代码的执行结果:。
可以看到函数中定义出了几个之前未见过的符号,我们转到定义看看到底是什么:
typedef char * va_list; //类型重定义:将char* 定义为va_list
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end //这三个都是#define 定义的符号,不清楚是什么,再次转到定义:
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap) ( ap = (va_list)0 )
//原来是三个宏,对其中不明白的符号再次转到定义:
#define _ADDRESSOF(v) ( &(v) )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//仍然是两个宏
哦,这时候先进行替换,方便理解:
va_list arg; 相当于 char* arg; //arg是个字符指针呀
va_start(arg, n); 相当于 _crt_va_start(arg, n);相当于(arg = (va_list)_ADDRESSOF(n) + _INTSIZEOF(n))
相当于 (arg = (char*)&n + ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
其中 ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) ;求出n的字节数,然后向上取整为4的倍数(换句话说: _INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍)
所以在上述代码中相当于 arg = (char*)&n + 4;
//初始化arg为位置参数列表中的第一个参数的地址(如调用这个函数时用的参数为(int 3, 1,2,6),那么此时arg指向的是1所在的地址)
va_arg(arg, int); 相当于 _crt_va_arg(arg,int)
相当于 ( *(int *)((arg += _INTSIZEOF(int)) - _INTSIZEOF(int)) )
所以在上述代码中相当于 *(int *)(arg += 4) - 4;
//此时已经将arg重新赋值为可变参数列表中第二个参数的地址,但是此处保留的仍然是上一个参数的地址,然后对保留地址进行强制类型转换之后解以用得到内容(参数)
va_end(arg); 相当于 (arg = (va_list)0)
所以在上述代码中相当于 arg = char* 0; //把arg置为NULL
回到原来代码中进行替换。
为了更好的理解,我们把该程序的反汇编拷贝出来解析一下:(仅以int avg1 = average(2, a, b);本次调用为例)
int main() //调用main函数之前已经调用了其他函数
{
01154770 push ebp //将当前ebp的地址压栈到当前的栈顶,每次压栈esp都更新
01154771 mov ebp,esp //将当前的ebp移动到当前esp的位置处
01154773 sub esp,0FCh //将esp向低地址处移动OFCh,为main()开辟空间
01154779 push ebx //将寄存器ebx压入栈顶
0115477A push esi //将寄存器esi压入栈顶
0115477B push edi //将寄存器edi压入栈顶
0115477C lea edi,[ebp-0FCh] //将为main开辟空间时的esp的地址加载进入edi中
01154782 mov ecx,3Fh //ecx中放入3Fh
01154787 mov eax,0CCCCCCCCh //eax中放入0CCCCCCCCh
0115478C rep stos dword ptr es:[edi] //从edi中所存的地址处开始,用0CCCCCCCCh始化3Fh次
0115478E mov ecx,offset _FC008D77_test@c (0115C003h)
01154793 call @__CheckForDebuggerJustMyCode@4 (01151212h)
int a = 10;
01154798 mov dword ptr [a],0Ah //创建变量,dword ptr [a]中放入0Ah
int b = 20;
0115479F mov dword ptr [b],14h //创建变量,dword ptr [b]中放入14h
int c = 30;
011547A6 mov dword ptr [c],1Eh //创建变量,dword ptr [c]中放入1Eh
int avg1 = average(2, a, b); //调用函数前先进行传参(参数列表中从右向左)
011547AD mov eax,dword ptr [b] //eax中放入dword ptr [b]的内容
011547B0 push eax //调用average函数前将要用的形参放入寄存器并压栈
011547B1 mov ecx,dword ptr [a] //eax中放入dword ptr [b]的内容
011547B4 push ecx //调用average函数前将要用的形参放入寄存器并压栈
011547B5 push 2 //将参数2也压栈
011547B7 call _average (01151398h) //开始调用average函数(将此地址进行压栈/保护现场)
011547BC add esp,0Ch //esp回到原来的位置,将开辟的栈帧回收
011547BF mov dword ptr [avg1],eax //将eax中存的平均值放入avr1中准备打印
int average(int n, ...)
{
01151810 push ebp
01151811 mov ebp,esp
01151813 sub esp,0E4h
01151819 push ebx
0115181A push esi
0115181B push edi
0115181C lea edi,[ebp-0E4h]
01151822 mov ecx,39h
01151827 mov eax,0CCCCCCCCh
0115182C rep stos dword ptr es:[edi] //average()函数的栈帧开辟以及初始化过程,同main()
0115182E mov ecx,offset _FC008D77_test@c (0115C003h)
01151833 call @__CheckForDebuggerJustMyCode@4 (01151212h)
int i = 0;
01151838 mov dword ptr [i],0 //创建局部变量i并初始化为0;
int sum = 0;
0115183F mov dword ptr [sum],0 //创建局部变量sum并初始化为0;
va_list arg;
va_start(arg, n);
01151846 lea eax,[ebp+0Ch] //将当前的ebp+0Ch处的地址存放到eax中
01151849 mov dword ptr [arg],eax //将eax中的内容赋值给arg
for (i = 0; i < n; i++)
0115184C mov dword ptr [i],0 //进入循环,先把i用0赋值
01151853 jmp average+4Eh (0115185Eh) //跳转到0115185Eh处,执行eax,dword ptr [i]
01151855 mov eax,dword ptr [i] //由0115187Bh跳转过来
01151858 add eax,1 //执行++
0115185B mov dword ptr [i],eax //并重新赋值给i,继续进行循环
0115185E mov eax,dword ptr [i] //由01151853h跳转过来,将此时的i加载到eax中
01151861 cmp eax,dword ptr [n] //eax的值与变量n的值进行比较
01151864 jge average+6Dh (0115187Dh)
{
sum += va_arg(arg, int); //即: sum += (*(int *)(arg += 4) - 4);
01151866 mov eax,dword ptr [arg]
01151869 add eax,4
0115186C mov dword ptr [arg],eax //此时arg存放的是下一个参数(第3个参数)的地址
0115186F mov ecx,dword ptr [arg]
01151872 mov edx,dword ptr [sum]
01151875 add edx,dword ptr [ecx-4] //sum的值加上一次arg所指地址处的内容(第2个参数)
01151878 mov dword ptr [sum],edx //将求的和继续放在sum中
}
0115187B jmp average+45h (01151855h) //跳转到0115185Eh处,执行准备执行i++
return sum / n;
0115187D mov eax,dword ptr [sum] //将for循环结束之后的sum放入eax中
01151880 cdq
01151881 idiv eax,dword ptr [n] //用eax中的数值除以变量n的内容,得到平均值
01151884 jmp average+7Dh (0115188Dh)
va_end(arg); //即:arg = char* 0; //把arg置为NULL
01151886 mov dword ptr [arg],0
}
0115188D pop edi
0115188E pop esi
0115188F pop ebx //弹出栈顶的各种寄存器
01151890 add esp,0E4h //回收为average开辟的栈帧
01151896 cmp ebp,esp
01151898 call __RTC_CheckEsp (0115121Ch)
0115189D mov esp,ebp
0115189F pop ebp
011518A0 ret //最终将栈顶的元素取出作为一个地址跳转到该地址处回到main()函数)
再以一个简单的图表示一下:
int avg1 = average(2, a, b); 该语句调用average() 函数时,其参数列表中有三个参数,而且是从右向左依次压栈的。
因为参数列表中最左侧的参数表示的就是该参数之后参数的个数,所以压栈顺序它在函数栈帧的最上方。当调用函数时,读取这个参数就能在此基础上明确参数的总个数,在利用for循环和arg指针就能准确的完成该函数的功能。
所以average(); 函数不管有几个参数都可以按照上面的函数栈帧的运行规律正确读取参数个数并成功完成功能。
对于优化代码使其更加通用的需求,可以考虑使用函数指针。函数指针是一种指向函数的指针变量,它可以作为参数传递给其他函数或返回另一个函数。通过函数指针,我们可以将数值积分的计算方式作为参数传递给一个通用的计算函数,实现对任意被积函数进行数值积分的计算。
以下是一个基于函数指针实现数值