QT快速入门、三点求圆心实现详解

在编程中,会经常用到数学计算,所以C++将常用的数学计算,例如求正余弦等,封装成函数(正是我们在3.2 数学计算中学习到的),我们只需要写入简单的语句就可以执行所需要的功能,这正是函数的意义。在这一章的学习,我们会建立起模块化的思维,小的功能模块我们会封装成函数,而一些大的功能模块,则封装成类。

刚开始进行函数的编写,会感觉有些难度。其实,只要稍稍多做一些函数编写的训练的时候,就可以很容易找到其中的规律。这一章会有大量的练习实践,勤动手是学好这一章内容的关键。

7.1 函数

写一个求中点函数,输入两个点坐标——Point1,Point2,求出Point1与Point2的中点坐标。参考表7-1中的程序。如何来完成这个编程要求,先看一下函数的编写规范。

#include<iostream>
#include<cmath>
using namespace std;
 
struct Point{
double x;
double y;
};
 
 
Point GetMidpoint(Point P1, Point P2)
{
Point Midpoint={0,0};
Midpoint.x = (P1.x + P2.x )/2;
Midpoint.y = (P1.y + P2.y )/2;
return Midpoint;
}
 
int main()
{
Point Point1,Point2;
cout<<"请输入两个点的坐标"<<endl;
cin>>Point1.x>>Point1.y;
cin>>Point2.x>>Point2.y;
 
Point Point_MidP1P2;
Point_MidP1P2=GetMidpoint(Point1,Point2);
cout<<"中点坐标为"<<Point_MidP1P2.x<<','<<Point_MidP1P2.y<<endl;
system("pause");
return 0;
}

表7-1 求中点函数程序

函数的代码应该放在主函数前面(如果想放在后面需要声明,在7.2三点求圆心会讲到如果将函数的实现放在主函数后面,应该如何进行声明),具体如何实现该函数请看7.1.1小节。

第27行代码对函数GetMidPoint进行了调用,并将求得的结果赋值给了Point_MidP1P2结构体变量,函数的使用可以回顾3.2数学计算这一节。区别只在于3.2数学计算中的函数是C++提供给我们的,而函数GetMidPoint是我们自己编写的。

测试程序,输入两个坐标为(3,5),(5,10)的坐标点,程序自动计算中点坐标值并显示,如图7-1所示。

 

图7-1 求中点函数测试结果示意图

7.1.1 函数的编写规范

首先,先看一下函数的编写规范,函数定义的格式为

函数返回值的类型名 函数名(参数1类型 参数1名称, 参数2类型  参数2名称, ...)

{

函数体

}

要求中点坐标,首先回顾上一章将的内容——结构体,先定义名为Point的结构体类型,并定义名为Point1,Point2,Point3的结构体变量,结构体类型为Point :

struct Point{

double x;

double y;

};

然后开始进行求中点的函数的编写,根据易懂的命名方式,将该功能的函数名起名为GetMidpoint,最终求得的结果为一个点,所以原函数返回值的类型名为Point,共有两个参数,都为Point类型。

Point GetMidPoint(Point P1, Point P2)

第一个Point为函数返回值的类型名,即最终我们想要求得结果的类型,这里我们要求中点坐标,所以最终的结果就是一个Point类型;空格后紧接着是函数名GetMidPoint;小括号内是求中点函数的参数,共有两个参数,并且都为Point类型,而P1和P2则是参数1名称,以及参数2名称。

接下里开始在大括号内进行函数体的编写。

Point Midpoint={0,0};

Midpoint.x = (P1.x + P2.x )/2;

Midpoint.y = (P1.y + P2.y )/2;

return Midpoint;

先定义了Point类型的名为Midpoint的结构体变量,并赋初值为0,0;然后求得点Midpoint的x坐标值以及y坐标值;函数最终的结果需要使用关键字return进行返回。

(当然,如果定义函数返回值的类型名为void的话,即函数什么都不返回,最后的return语句不需要编写;如果某个函数只执行某项功能,需要返回任何值得时候,就可以这样编写)

7.1.2 形式参数与实在参数

形式参数是在定义函数时放在函数名称之后的括号中的参数。在表7-1的程序中11行代码中的P1,P2为形式参数。在函数的实现部分,该函数还没有被调用,系统不对形式参数分配内存;函数被调用时(程序的第27行为调用函数的代码),系统对形式参数进行内存分配,在函数结束后,将这部分内存释放掉。形式参数属于局部变量,其作用域限定在函数体内。

实在参数是一个具有确定值的参数。在表7-1的程序中27行代码中的Point1,Point2为实在参数。调用函数时,将实在参数的值放入形式参数的内存单元当中。

需要注意的是,实在参数的个数以及类型必须和形式参数一致,并且前后排序也要一一对应。

7.1.3 编程练习:求两点连线

编写一个函数,输入两点坐标,能够自动计算出这两点连线的斜率和截距。程序如表7-2所示。

 
#include<iostream>
#include<cmath>
using namespace std;
 
struct Point{
double x;
double y;
};
 
struct Line{
double k;
double b;
};
 
Line GetLineOfTwoPoints(Point P1,Point P2)
{
Line StraightLine={0,0};
if(P1.x==P2.x)
{
P1.x=P2.x-0.0000001;
}
StraightLine.k = (P2.y - P1.y)/ (P2.x -P1.x);
StraightLine.b = P1.y - P1.x * StraightLine.k ;
return StraightLine;
}
 
int main()
{
Point Point1,Point2;
cout<<"请输入两个点的坐标"<<endl;
cin>>Point1.x>>Point1.y;
cin>>Point2.x>>Point2.y;
Line Line1_P1P2;
Line1_P1P2=GetLineOfTwoPoints(Point1,Point2);
cout<<"两点连线的斜截式方程为y="<<Line1_P1P2.k<<"x+"<<Line1_P1P2.b<<'.' <<endl;
system("pause");
return 0;
}

表7-2 求两点连线函数程序

在初高中的时候我们通常使用斜截式来表示直线,也就是说区分不同的直线只需要一个斜率k和一个截距b即可;第10至13行定义一个名为Line的结构体类型,成员为double型的斜率k,以及double型的截距b。

15行至25行代码实现了两点求连线的函数,函数返回值的类型名为Line,函数名为GetLineOfTwoPoints,参数有两个,都是Point型,两个形式参数为P1,P2;函数体内部先定义一个Line型的结构体变量StraightLine,用来求得直线的斜率和截距。注意第18行至21行有一个if语句,该语句是为了在求斜率的时候,防止分母为0;最后将得到的直线结果返回。

在34行进行了函数的调用,实在参数为Point1和Point2,所得连线结果赋值给结构体变量Line1_P1P2。

测试程序,输入两点坐标分别为(3,6)、(2,9),最终结果如图7-2所示。

 

图7-2 求两点连线函数测试结果示意图

7.1.4 编程练习:求中垂线

编写一个函数,输入两点坐标,能够自动计算出这两点连线的中垂线的斜率和截距。根据两点求中垂线,回忆初高中的数学知识,需要先求两点的中点,然后求得两点连线的斜率,垂线的斜率与连线的斜率相乘为-1,有了过中垂线的一个点以及斜率就可以求出中垂线了。程序如表7-3所示。

#include<iostream>

#include<cmath>

using namespace std;


struct Point{

double x;

double y;

};


struct Line{

double k;

double b;

};



Point GetMidpoint(Point P1, Point P2)

{

Point Midpoint={0,0};

Midpoint.x = (P1.x + P2.x )/2;

Midpoint.y = (P1.y + P2.y )/2;

return Midpoint;

}


Line GetLineOfTwoPoints(Point P1,Point P2)

{

Line StraightLine={0,0};

if(P1.x==P2.x)

{

P1.x=P2.x-0.0000001;

}

if(P1.y==P2.y)

{

P1.y=P2.y-0.0000001;

}

StraightLine.k = (P2.y - P1.y)/ (P2.x -P1.x);

StraightLine.b = P1.y - P1.x * StraightLine.k ;

return StraightLine;

}


Line GetMidperpendicular(Point P,double k)

{

Line StraightLine={0,0};

StraightLine.k = -1/k;

StraightLine.b = P.y - StraightLine.k * P.x ;

return StraightLine;

}


int main()

{

Point Point1,Point2;

cout<<"请输入两个点的坐标"<<endl;

cin>>Point1.x>>Point1.y;

cin>>Point2.x>>Point2.y;

Point Point_MidP1P2;

Point_MidP1P2=GetMidpoint(Point1,Point2);

Line Line1_P1P2;

Line1_P1P2=GetLineOfTwoPoints(Point1,Point2);

cout<<"两点连线的斜截式方程为y="<<Line1_P1P2.k<<"x+" <<Line1_P1P2.b<<'.'<<endl;

Line Line3_P1P2_Vertical;

Line3_P1P2_Vertical = GetMidperpendicular(Point_MidP1P2,Line1_P1P2.k);

cout<<"两点的中垂线的斜截式方程为y="<<Line3_P1P2_Vertical.k<<"x+" <<Line3_P1P2_Vertical.b<<'.'<<endl;

system("pause");

return 0;

}

表7-3 求中垂线函数程序

由于要求中垂线,所以需要先求中点坐标以及两点连线的斜率,所以将直接将前面的写好的求两点中点函数以及求两点连线函数拿来使用。

需要注意的是求两点连线的第31至34行防止两点的y坐标相同;如果两点y坐标相同,求得的斜率就为0,在求中垂线的时候,连线的斜率作为分母,这样就会出错。

40至46行实现了求中垂线函数,函数返回值的类型名为Line,函数名为 GetMidperpendicular,参数有两个,一个是Point型,一个是double型,两个形式参数的名称为P,k;函数体内部先定义一个Line型的结构体变量StraightLine,用来求得直线的斜率和截距。注意两个函数的结构体变量StraightLine都是局部参数,两者是不同的。

中垂线函数实现后,开始在主函数中进行中垂线求取。首先,求得两点的中点Point_MidP1P2;然后求得两点连线Line1_P1P2;最后,将中点Point_MidP1P2以及两点连线Line1_P1P2的斜率Line1_P1P2.k作为实在参数,调用求中垂线函数GetMidperpendicular,将结果赋值给Line1_P1P2。

测试程序,输入两个点坐标为(1,1)以及(0,2),最终结果如图7-3所示。

 

图7-3 求中垂线函数测试结果示意图

7.2 编程练习:三点求圆心

两点确定一条直线,那么几个点确定一个圆呢?回忆下高中的数学知识,三个点(不在同一条直线上)确定一个圆。这一节回顾初中的数学知识,并结合学习的程序语言,对如何将给定的不在同一条直线的三点进行圆心的求解形成一个初步的思路。

7.2.1 三点求圆心思路

如图7-4所示,当给定三个坐标点(x1,y1),(x2,y2),(x3,y3),我们分别称这三个坐标点为Point1,Point2,Point3;

我们连接坐标点Point1,Point2以及Point2,Point3,这样我们有了两条线段,我们分别称这两条线段为Line1_P1P2,Line2_P2P3;

然后我们分别对这两条线段Line1_P1P2,Line2_P2P3求中垂线,获得的中垂线我们称为Line3_P1P2_Vertical,Line4_P2P3_Vertical;

最后,我们能够得到这两条中垂线的交点——Point6_CenterOfCircle。

需要注意的是,在求三点求圆心前,需要先确定输入的三个点都不相同,并且不在一条直线上。判断不在一条直线上,可以将Line1_P1P2,Line2_P2P3的斜率是否相同,如果相同则意味着三点在一条直线上。

 

图7-4 三点求圆心坐标示意图

7.2.2 程序实现

接下来我们将以上思路通过程序实现出来,如表7-4所示。

#include<iostream>

#include<cmath>

using namespace std;


struct Point{

double x;

double y;

};


struct Line{

double k;

double b;

};


bool IsSameSlope(Line Line1,Line Line2);

Line GetLineOfTwoPoints(Point P1,Point P2);

bool IsSamePoint(Point P1,Point P2);

Point GetMidpoint(Point P1, Point P2);

Line GetMidperpendicular(Point P,double k);

Point GetPointOfIntersection(Line Line1,Line Line2);

double GetDistanceOfTwoPoint(Point P1,Point P2);


int main()

{

Point Point1,Point2,Point3;

cout<<"请输入三个点的坐标"<<endl;

cin>>Point1.x>>Point1.y;

cin>>Point2.x>>Point2.y;

cin>>Point3.x>>Point3.y;

bool x;

x=IsSamePoint(Point1,Point2)||IsSamePoint(Point2,Point3)

||IsSamePoint(Point1,Point3);

if(x==1)

{

cout<<"错误,输入的三个点有相同点!"<<endl;

system("pause");

return 0;

}


Line Line1_P1P2,Line2_P2P3;

Line1_P1P2=GetLineOfTwoPoints(Point1,Point2);

Line2_P2P3=GetLineOfTwoPoints(Point2,Point3);


bool y;

y=IsSameSlope(Line1_P1P2,Line2_P2P3);

if(y==1)

{

cout<<"错误,输入的三个点共线!"<<endl;

system("pause");

return 0;

}

else

{

cout<<"这三个点能够求出圆心!"<<endl;

}


Point Point4_MidP1P2,Point5_MidP2P3;

Point4_MidP1P2 = GetMidpoint(Point1,Point2);

Point5_MidP2P3 = GetMidpoint(Point2,Point3);


Line Line3_P1P2_Vertical,Line4_P2P3_Vertical;

Line3_P1P2_Vertical = GetMidperpendicular(Point4_MidP1P2,Line1_P1P2.k);

Line4_P2P3_Vertical = GetMidperpendicular(Point5_MidP2P3,Line2_P2P3.k);


Point CenterOfCircle,StartPoint,EndPoint;

CenterOfCircle=GetPointOfIntersection(Line3_P1P2_Vertical,Line4_P2P3_Vertical);


double Radius;


Radius = GetDistanceOfTwoPoint(CenterOfCircle,Point2);


cout<<"圆心坐标为"<<CenterOfCircle.x<<", "<<CenterOfCircle.y<<endl;

cout<<"半径为"<<Radius<<endl;


system("pause");

return 0;

}


bool IsSameSlope(Line Line1,Line Line2)

{

if (Line1.k==Line2.k)

{

return 1;

}

else

{

return 0;

}

}


Line GetLineOfTwoPoints(Point P1,Point P2)

{

Line StraightLine={0,0};

if(P1.x==P2.x)

{

P1.x=P2.x-0.0000001;

}

if(P1.y==P2.y)

{

P1.y=P2.y-0.0000001;

}

StraightLine.k = (P2.y - P1.y)/ (P2.x -P1.x);

StraightLine.b = P1.y - P1.x * StraightLine.k ;

return StraightLine;

}


bool IsSamePoint(Point P1,Point P2)

{

if(P1.x==P2.x&&P1.y==P2.y)

{

return 1;

}

else

{

return 0;

}

}


Point GetMidpoint(Point P1, Point P2)

{

Point Midpoint={0,0};

Midpoint.x = (P1.x + P2.x )/2;

Midpoint.y = (P1.y + P2.y )/2;

return Midpoint;

}


Line GetMidperpendicular(Point P,double k)

{

Line StraightLine={0,0};

StraightLine.k = -1/k;

StraightLine.b = P.y - StraightLine.k * P.x ;

return StraightLine;

}


Point GetPointOfIntersection(Line Line1,Line Line2)

{

Point PointOfIntersection={0,0};

PointOfIntersection.x = (Line2.b-Line1.b)/(Line1.k-Line2.k);

PointOfIntersection.y = (Line2.k*Line1.b-Line1.k*Line2.b)/(Line2.k-Line1.k);

return PointOfIntersection;

}


double GetDistanceOfTwoPoint(Point P1,Point P2)

{

double DistanceOfTwoPoint=0;

DistanceOfTwoPoint=sqrt((P1.x-P2.x)*(P1.x-P2.x)+(P1.y-P2.y)*(P1.y-P2.y));

return DistanceOfTwoPoint;

}

表7-4 三点求圆心程序

函数放在主程序后面,需要在主函数前面声明。函数声明很简单,直接将大括号前面的

 

函数返回值的类型名 函数名(参数1类型 参数1名称, 参数2类型  参数2名称, ...)

 

加上分号“;”即可。

该程序共实现了7个函数:

bool IsSameSlope(Line Line1,Line Line2):判断两条直线的斜率是否相同,两个参数为Line型的结构体变量,如果相同返回1,不同返回0;

Line GetLineOfTwoPoints(Point P1,Point P2):求两点连线,两个参数为Point型的结构体变量,返回值为Line型结构体变量;

bool IsSamePoint(Point P1,Point P2):判断两点是否相同,两个参数为Point型的结构体变量,如果相同返回1,不同返回0;

Point GetMidpoint(Point P1, Point P2):求两点中点,两个参数为Point型的结构体变量,返回值为Point型结构体变量;

Line GetMidperpendicular(Point P,double k):求中垂线,两个参数为Point型的结构体变量和double型变量,返回值为Line型结构体变量;

Point GetPointOfIntersection(Line Line1,Line Line2):求两条直线交点,两个参数为Line型的结构体变量,返回值为Point型结构体变量;

double GetDistanceOfTwoPoint(Point P1,Point P2):求两点间距离(求半径使用,圆心到三点任意一点的距离即半径),两个参数为Point型的结构体变量,返回值为double型结构体变量。

函数实现后,在主函数中参考7.2.1三点求圆心思路对函数进行调用来完成;测试程序,输入三个点——(0,0)、(5,5)、(10,0),运行结果如图7-5所示。

 

图7-5 三点求圆心测试结果示意图

7.3 类

通过函数的编写,我们已经初步的接触到模块化设计的思维;在三点求圆心的实现中,我们一步步定义小模块(即函数)帮助我们解决三点求圆心这个问题。在程序的设计历史过程中,需要解决的问题越来越繁杂,这种编程思路会显得很适应编程需求。这个时候另一种以模块化为切入点的编程思路应用开来——面对对象编程(OOP,Object Oriented Programming)。

什么是面向对象编程?面向对象编程面向过程编程相对,面向过程编程是如流水线一般,针对要解决的问题,按照逻辑顺序一步步进行程序的设计;而面向对象编程如前文所讲,针对要解决的问题,思考可能要用到方法,将一个个方法先进行实现,然后在进行问题的解决。

C++提供了类这一功能,以胜任面对对象编程。

7.3.1 VS2010环境下类的添加

这里以三点求圆心类的实现为例,进行类的讲解。

在VS中创建一个名为CenterOfCircle的项目(名字可以不一样),然后开始添加一个类,在“解决方案资源管理器”中点击鼠标右键,依次点击“添加”,“类”,如图7-6所示。

 

图7-6 添加类示意图

出现“添加类”窗口,默认为“C++类”,无需改变,直接点击右下角的“添加”键。如图7-7所示。

 

图7-7 添加类确认示意图

此时出现新的窗口——“一般C++类向导”,此时在“类名”这一栏输入CenterOfCircle,此时,“.h文件”、“.cpp文件”两栏自动填满相应的信息,填写好后,界面如图7-8所示。

 

图7-8 “一般C++类向导”界面

点击“完成”,完成类文件的添加,然后在“解决方案资源管理”中的“头文件”以及“源文件”中会分别出现两个文件——“CenterOfCircle.h”、“CenterOfCircle.cpp”。如图7-9所示。

 

图7-9 类添加后资源方案管理界面示意图

类添加完成后,就可以进行类的编写,即分别向“CenterOfCircle.h”、“CenterOfCircle.cpp”两个文件中写入相应的代码,以完成三点求圆心类。

简单的来说以.h结尾的文件是类声明部分——以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述公有接口;以.cpp结尾的文件是类方法定义部分——用来描述如何实现类成员函数。

7.3.2 编程练习:三点求圆心类

我们通过上一节熟悉了在VS2010中类的添加,这一节我们将对三点求圆心功能进行类的实现和使用。

在数控机床等操作中,想要进行圆弧插补,需要输入的点的个数为三个,通过三个点进行圆心坐标以及圆弧走向的求取后,才能进行圆弧插补。所以这一节的内容是第8章逐点比较法圆弧插补的一个基础,请大家务必把基础打好。

首先,要判断三点在不在一条直线上。在这一节当中我们将通过计算机对给定的三个点,求这三个点所在圆的圆心、半径以及三个点行进的方向——顺时针还是逆时针。

在进行程序编写前需要首先了解一些有关类编写的相关知识。

1.构造函数与解析函数

首先,再次对类这一概念进行解释。编写三点求圆心类,我们不关心要求取的三个点到底是(0,0)、(5,5)、(10,0),或者是(1,2)、(4,5)、(11,0)等等,也就是说这个三点求圆心类是抽象的,不是具体只针对某三个具体的点能够求得最终的结果;即这个三点求圆心类可以对任意三个点都有效,都能够求得结果。

那么,三点求圆心类是如何区分不同的点的呢?这就是构造函数的作用,构造函数用来创建对象,例如(0,0)、(5,5)、(10,0)三个点,共有6个值,在求取这三个点的时候,只需要将这6个数字放入构造函数的参数中即可,构造出对应这三个点的对象;有了这个对应(0,0)、(5,5)、(10,0)三个点的对象后,就可以使用三点求圆心类下的成员函数进行圆心、半径等结果的求取了。

总而言之,类是抽象的,在使用类的时候,放入到构造函数中的参数不同来创建不同的对象,然后该对象调用类下的成员函数来得到不同的结果。

构造函数的名称和类的名称一致,但是要区分两者的区别,构造函数是类的成员函数,虽然名称一样,但构造函数出现在成员函数应该出现的位置上,在程序中很容易分别。后面的程序说明中会对此进行更详细的解释。

析构函数通常不需要特别关注,析构函数的作用是完成清理工作,在程序执行到创建的对象作用域以外的时候,需要释放内存。这个过程系统自动完成。析构函数的名称和类名相似,只在类的名称前面加上了一个波浪符号——“~”。

2.访问控制

这里介绍下面程序中会出现的两个关键字——public和private,这两个关键字描述了对类成员的访问控制。

关键字private标识的成员(无论是数据还是成员函数)不能够通过外界直接访问,只能够通过关键字public标识下的公有成员函数进行访问;这样,共有成员函数成为程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。

为了数据隐藏,通常数据都放在关键字private下,类的对外接口的成员函数放在关键字public下面。(C++还提供了一个关键字——protected,可以自行补充学习)

 

接下来,以三点求圆心类为例,看一下如何实现这个类。

a.我们先对CenterOfCirlce.h文件进行编写

 (程序说明在后面)

#pragma once

#include<cmath>


struct Point

{

double x;

double y;

};


struct Line

{

double k; //斜率

double b; //截距

};


struct Result{

bool HasSolution; //是否有解

Point StartPoint; //圆弧行进起点坐标

Point EndPoint; //圆弧行进终点坐标

Point CenterOfCircle; //三点确定的圆心坐标

bool Direction; //圆弧行进方向(顺时针还是逆时针)

double Radius; //半径长度

};


class CenterOfCircle

{

private:

Point Point1,Point2,Point3; //给定的三点

Point Point4_MidP1P2,Point5_MidP2P3; //两点的中点

Line Line1_P1P2,Line2_P2P3; //两点的连线

Line Line3_P1P2_Vertical,Line4_P2P3_Vertical; //两点连线的中垂线

Point Point6_CenterOfCircle; //圆心坐标


bool IsSamePoint(Point,Point); //判断点是否相同

Point GetMidpoint(Point,Point); //获取中点坐标

Line GetLineOfTwoPoints(Point,Point); //获取两点的连线

bool IsSameSlope(Line,Line); //判断斜率是否相同

Line GetMidperpendicular(Point,double); //获取中垂线

Point GetPointOfIntersection(Line,Line); //获取俩线交点

double GetDistanceOfTwoPoint(Point,Point); //获取两点距离

bool GetDirection(Point,Point,Point); //获取圆弧走向


public:

CenterOfCircle(double,double,double,double,double,double); //构造函数的6个参数分别三个点在x与y方向的坐标

~CenterOfCircle(void);


Result GetInformationOfCircle(); //获取最终给定三点的结果,有无解,起点,终点,圆心,走向以及半径

};

第一行代码#pragma once防止头文件重复编译。

在头文件开头,我们先包含一个头文件cmath,因为后面我们会使用函数tan()以及求平方函数sqrt()进行相关的数学计算。

结构体类型Point表示点,成员为x和y;结构体类型Line表示线,成员为斜率k和截距b。

结构体类型Result是我们最终求得的结果,其成员分别为圆弧的起点StartPoint,终点EndPoint,三点所确定的圆心坐标CenterOfCircle,圆弧走向Direction(顺时针或逆时针)以及圆的半径Radius;

还有一个bool型变量HasSolution,表示是否有解,因为要考虑到三点是否有两个相同点以及三点是否在同一条直线上的问题,这两种情况下的三个点是无法形成圆弧的。

然后我们开始对CenterOfCircle类的成员进行声明,class是关键字,表明要创建一个名为CenterOfCircle的类;其中,要求圆心的三个点分别为Point1,Point2,Point3

求圆心需要先对三点的两两连线进行中垂线的求取,在这里我们需要用到两点的中点,以及两点连线,还有两点连线线段的中垂线;

所以,我们定义Point1与Point2的中点坐标为Point4_MidP1P2,Point2与Point3的中点坐标为Point5_MidP2P3,Point1与Point2连线为Line1_P1P2,Point2与Point3连线为Line2_P2P3,求得相应的中垂线为Line3_P1P2_VerticalLine4_P2P3_Vertical

最后,我们求两条中垂线的交点即我们要求的圆心坐标——Point6_CenterOfCircle。

 

在这个过程中,我们需要用到一些简单的函数,我们把这些函数实现在CenterOfCircle类下,作为CenterOfCircle类的私有成员函数。

首先,我们要判断三个点是否有两个点相同,我们定义这个成员函数名bool IsSamePoint(Point,Point),其中两个参数是要判断的两个点,两点相同返回值为1,两点不同返回值为0;

Point GetMidpoint(Point,Point)用来获取中点坐标;

Line GetLineOfTwoPoints(Point,Point)用来获取两点连线;

bool IsSameSlope(Line,Line)判断斜率是否相同,若两两连线的斜率相同即表示三点共线,即无解;

Line GetMidperpendicular(Point,double)通过斜截式来实现中垂线的获取,两个参数分别为点与斜率;

Point GetPointOfIntersection(Line,Line)获取俩线交点坐标;

double GetDistanceOfTwoPoint(Point,Point)获取两点距离;

通过两点的向量的叉乘的正负来判断圆弧走向是顺时针还是逆时针,我们通过成果函数bool GetDirection(Point,Point,Point)来进行判断,三个参数分别为给定的三个点。

在关键字public标识下的公有成员函数中,构造函数中的6个参数分别是三个点在x与y方向的坐标;

我们通过Result GetInformationOfCircle()来获取最终给定三点的结果,结果中包括有无解,圆弧起点,圆弧终点,三点确定圆的圆心,圆弧走向以及三点确定的圆的半径;在这个函数中,我们将用到私有的成员变量与成员函数。

b.接下来我们开始对CenterOfCirlce.cpp文件进行编写

(程序说明在后面)

#include "CenterOfCircle.h"


CenterOfCircle::CenterOfCircle(double P1_x,double P1_y,double P2_x,double P2_y,double P3_x,double P3_y)

{

Point1.x=P1_x;

Point1.y=P1_y;

Point2.x=P2_x;

Point2.y=P2_y;

Point3.x=P3_x;

Point3.y=P3_y;

}


CenterOfCircle::~CenterOfCircle(void)

{

}


Result CenterOfCircle::GetInformationOfCircle()

{

Result theResult={0,{0,0},{0,0},{0,0},0,0};


if(IsSamePoint(Point1,Point2)||IsSamePoint(Point2,Point3)||IsSamePoint(Point1,Point3))

{

return theResult;

}


Line1_P1P2 = GetLineOfTwoPoints(Point1,Point2);

Line2_P2P3 = GetLineOfTwoPoints(Point2,Point3);


if (IsSameSlope(Line1_P1P2,Line2_P2P3)==1)

{

return theResult;

}

else

{

theResult.HasSolution=1;

}


Point4_MidP1P2 = GetMidpoint(Point1,Point2);

Point5_MidP2P3 = GetMidpoint(Point2,Point3);


Line3_P1P2_Vertical = GetMidperpendicular(Point4_MidP1P2,Line1_P1P2.k);

Line4_P2P3_Vertical = GetMidperpendicular(Point5_MidP2P3,Line2_P2P3.k);


theResult.CenterOfCircle = GetPointOfIntersection(Line3_P1P2_Vertical,Line4_P2P3_Vertical);


theResult.StartPoint = Point1;

theResult.EndPoint = Point3;


theResult.Radius = GetDistanceOfTwoPoint(theResult.CenterOfCircle,Point2);

theResult.Direction = GetDirection(Point1,Point2,Point3);


return theResult;

}


bool CenterOfCircle::IsSamePoint(Point P1,Point P2)

{

if ((P1.x==P2.x)&&(P1.y==P2.y))

{

return 1;

}

else

{

return 0;

}

}


Line CenterOfCircle::GetLineOfTwoPoints(Point P1, Point P2)

{

Line StraightLine={0,0};

StraightLine.k = (P2.y - P1.y)/ (P2.x -P1.x);

StraightLine.b = P1.y - P1.x * StraightLine.k ;

return StraightLine;

}


bool CenterOfCircle::IsSameSlope(Line Line1,Line Line2)

{

if (Line1.k==Line2.k)

{

return 1;

}

else

{

return 0;

}

}


Point CenterOfCircle::GetMidpoint(Point P1, Point P2)

{

Point Midpoint={0,0};

Midpoint.x = (P1.x + P2.x )/2;

Midpoint.y = (P1.y + P2.y )/2;

return Midpoint;

}


Line CenterOfCircle::GetMidperpendicular(Point P,double k)

{

Line StraightLine={0,0};

StraightLine.k = -1/k;

StraightLine.b = P.y - StraightLine.k * P.x ;

return StraightLine;

}


Point CenterOfCircle::GetPointOfIntersection(Line Line1,Line Line2)

{

Point PointOfIntersection={0,0};

PointOfIntersection.x = (Line2.b-Line1.b)/(Line1.k-Line2.k);

PointOfIntersection.y = (Line2.k*Line1.b-Line1.k*Line2.b)/(Line2.k-Line1.k);

return PointOfIntersection;

}


double CenterOfCircle::GetDistanceOfTwoPoint(Point P1,Point P2)

{

double DistanceOfTwoPoint=0;

DistanceOfTwoPoint=sqrt((P1.x-P2.x)*(P1.x-P2.x)+(P1.y-P2.y)*(P1.y-P2.y));

return DistanceOfTwoPoint;

}


bool CenterOfCircle::GetDirection(Point P1,Point P2,Point P3)

{

int CrossProduct=0;

bool Direction;

CrossProduct=(P2.x-P1.x)*(P3.y-P2.y)-(P2.y-P1.y)*(P3.x-P2.x);

if(CrossProduct<0)

{

Direction=1; //clockwise

}

else

{

Direction=0; //anticlockwise

}


return Direction;

}

在我们创建CenterOfCircle类的时候,VS2010自动给我们生成了一部分代码,在CenterOfCircle.cpp中,这写代码分别是#include "CenterOfCircle.h",构造函数及析构函数。

我们对构造函数进行改动,如CenterOfCircle.h文件中我们声明的构造函数一样,我们对CenterOfCircle(double P1_x,double P1_y,double P2_x,double P2_y,double P3_x,double P3_y)函数的参数设置为P1_x,P1_y,P2_x,P2_y,P3_x,P3_y,六个形参依次代表是三个点。

在构造函数内部我们将形参依次赋值给我们头文件声明过得私有化变量Point1.x,Point1.y,Point2.x,Point2.y,Point3.x,Point3.y,来完成对象的创建;如我们前面学习的构造函数一样,构造函数用来创建对象,创建后的对象可以调用GetInformationOfCircle()函数,获取该对象对应的解的信息——有无解,圆弧起点,圆弧终点,三点确定圆的圆心,圆弧走向以及三点确定的圆的半径。

析构函数我们不需要进行改动。

接下来我们将对这类最核心的功能进行实现,首先我们要实现这个函数,按照C++编程规 范,我们在先写这个函数求得结果的返回值类型,在头文件当中,我们定义结构体Result,我们求得的结果就放在这个结构体当中;然空格隔开,注意写上类名,表示是这个类下的函数,然后两个英文输入法状态下的冒号,最后我们把这个函数的名称写上GetInformationOfCircle(),由于对象创建由构造函数完成,我们这里可以直接使用构造函数创建对象对应的三个点Point1,Point2,Point3进行求解即可,所以不需要参数。

进入到大括号内开始对功能进行实现,首先我们先对所求结果的Result结构体创建对象theResult,并且赋初值——{0,{0,0},{0,0},{0,0},0,0};

参考头文件对Result结构体的定义,我们能够看到bool型变量HasSolution,即是否有解,我们初始化为0,即无解;第二个参数和第三个参数分别为圆弧行进起点坐标与终点坐标StartPoint和EndPoint;第四个参数CenterOfCircle,即三点确定的圆心坐标;第五个参数是圆弧行进的方向,bool型变量Direction,后面我们将顺时针方向给值为1,逆时针给值为0;最后一个参数为三点确定圆弧的半径长度Radius;

在对三个点进行处理前,我们应该首先判断两件事情,1.输入的三个点是否有相同的点;2.这三个点是否在同一条直线上;以上两种情况无法求得相应结果。

1.判断两点是否相同的函数IsSamePoint的实现

判断点与点是否相同我们通过一个函数IsSamePoint实现,然后通过调用这个函数来判断Point1,Point2,Point3两两是否相同。

以下是IsSamePoint函数的实现,返回值为bool型,返回值为1代表这两个点相同,返回值为0代表这两个点不相同。两个参数都是Point结构体类型,该结构在头文件已定义过了,可以参考头文件中Point结构体的定义。

在函数内部我们通过if语句判断实现了两点是否相同的判定,相同我们返回值为1,否则将返回0。

2.判断三个点是否在同一条直线上函数IsSameSlope的实现

判断三点是否共线实现的思路分为两步:

a)我们将Point1与Point2,Point2与Point3连接起来,得到着两条直线Line1_P1P2和Line2_P2P3;我们通过斜截式来表示直线。我们通过结构体Line来定义直线,Line结构体的两个成员分别为斜率k与截距b。

 

b)然后对这两条直线的斜率进行判定是否相等来得出三点是否共线

与函数IsSamePoint类似,我们定义一个返回值为bool型的IsSameSlope函数,参数为两个结构体类型Line,通过对两个形参的Line1与Line2的斜率k进行判断是否相等,最终得出三点是否共线。

 

回到CenterOfCircle.cpp中来,第22行到第38行代码判断了三点中是否有两点相同以及三点是否共线,如果三点中没有两个相同的点,并且也三点不共线,则有解,我们将theResult的成员HasSolution赋值为1。

接下来开始对三点所确定圆的圆心进行求解,我们分三步进行:

1.求取Point1与Point2、Point2与Point3的中点坐标;

2.求Point1与Point2所在直线Line1_P1P2的中垂线Line3_P1P2_Vertical,以及Point2与Point3所在直线Line_P2P3的中垂线Line4_P2P3_Vertical;

回忆初高中数学知识,求中垂线,我们使用斜截式进行求解。在函数GetMidperpendicular中,参数定义了两个,分别为Point结构体变量P,以及double型变量k;这两个参数分别代表所求线段的中点与斜率。

3.求两条中垂线的交点,即圆心CenterOfCircle坐标;

两条中垂线获得后我们可以通过解二元一次方程来求得交点,也就是我们最终要求的CenterOfCircle。

通过调用这些函数,在CenterOfCircle.cpp程序的39至46行实现了圆心坐标的求取;然后将Point1与Point2分别赋值给圆弧行进的起始点StartPoint与终点EndPoint。

半径的求取我们再次通过一个函数GetDistanceOfTwoPoint来实现,在程序的49行,我通过计算两点theResult.CenterOfCircle与Point2得出了半径长度。这里用到了开平方的函数sqrt,这也是为什么我们在头文件中要包含cmath的原因。

最后,还需要确定圆弧的走向。关于圆弧走向的数学证明这里不再累述,具体数学计算方法如下:设=(x1,y1),=(x2,y2),=(x3,y3),向量与的向量积(x2-x1)*(y3-y2)-(y2-y1)*(x3-x2)为正时,走向为逆时针;为负时,走向为顺时针;由于三点不在一条直线上,所以两向量叉乘不会为0。

至此,我们所求结果result结构体对应的所有成员都有了值。我们写一个main.cpp来测试以下我们写的CenterOfCircle类。

c.通过main.cpp文件进行三点求圆心类的测试

#include "CenterOfCircle.h"

#include <iostream>

using namespace std;

int main()

{

CenterOfCircle COC(0,0,5,5,10,0);

Result R1;

R1=COC.GetInformationOfCircle();

cout<<std::fixed<<R1.CenterOfCircle.x<<endl;

cout<<std::fixed<<R1.CenterOfCircle.y<<endl;

cout<<R1.Direction<<endl;

cout<<R1.Radius<<endl;

system("pause");

return 0;

}

第1行代码我们将CenterOfCircle.h包含进来,相当于我们能够使用CenterOfCircle类的功能; 进入主函数里面,我们使用CenterOfCircle类创建一个名为COC的对象,六个参数为三个点的x坐标与y坐标;

第7行定义一个名为R1的Result结构体变量,通过调用COC对象下的成员函数GetInformationOfCircle(),并将所得结果传递给R1,我们得到了点(0,0)、点(5,5)与点(10,0)确定的圆的相应信息——有无解,圆弧起点,圆弧终点,三点确定圆的圆心,圆弧走向以及三点确定的圆的半径;

第9行至12行我们使用cout函数将相应的结果输出出来。通过使用std::fixed我们禁用科学计数法,大家可以试一试对比下有无std::fixed最终结果显示的区别。

思考:如果细心的同学会发现,我们对于有些情况没有考虑进去。例如求解以下三个点(0,5),(5,5),(5,0)时,最终输出的结果并不能输出正确的结果。这是由于我们少考虑了两种情况:1.两点的连线平行于x轴;2.两点连线垂直于y轴。如何完善这个类,将以上两种情况考虑进去,进行正确的计算输出呢?可参考7.2三点求圆心这一节的内容进行程序的完善。

7.3.3 编程练习:逐点比较法直线插补类

这一小节我们对第五章的逐点比较法直线插补封装成类,以进一步加强对函数和类的掌握。创建项目后,添加类名为Interpolator的类,头文件Interpolator.h程序如表7-5所示。

 

#pragma once
#include <cmath>
#include <iostream>
using namespace std;
class Interpolator
{
private:
int m_xs;
int m_ys;
int m_xi;
int m_yi;
int m_xe;
int m_ye;
int m_Nxy;
int m_XieLvChaZhi;
int m_i;
public:
Interpolator(int,int,int,int);
~Interpolator(void);
void DoInterpolator();
};

表7-5 头文件Interpolator.h程序

将所有的数据(变量)放在关键字private下,构造函数设置了4个int型参数,分别代表插补的起始点和终点。

源文件Interpolator.cpp中的程序如表7-6所示。

#include "Interpolator.h"
 
 
Interpolator::Interpolator(int xs,int ys,int xe,int ye)
{
	m_xs=xs;
	m_ys=ys;
	m_xe=xe;
	m_ye=ye;
	cout<<"起点为"<<m_xs<<","<<m_ys<<endl;
	cout<<"终点为"<<m_xe<<","<<m_ye<<endl;
}
 
 
Interpolator::~Interpolator(void)
{
}
 
void Interpolator::DoInterpolator()
{
	m_Nxy =abs( (m_xe - m_xs)) + abs((m_ye - m_ys));
	m_i=1;
	m_xi = m_xs;
	m_yi = m_ys;
 
	for(int i=0;i<m_Nxy;i++)
	{
		m_XieLvChaZhi =(m_xe-m_xs)*(m_yi-m_ys)-(m_xi-m_xs)*(m_ye-m_ys);
		
		if(m_xe-m_xs==0)
		{
			if (m_ye-m_ys>0)
			{
				m_yi++;
			}
			else
			{
				m_yi--;
			}
			cout<<m_xi<<","<<m_yi<<endl;
		}
 
		if(m_ye-m_ys==0)
		{
			if (m_xe-m_xs>0)
			{
				m_xi++;
			}
			else
			{
				m_xi--;
			}
			cout<<m_xi<<","<<m_yi<<endl;
		}
 
		if ((m_xe-m_xs>0)&&(m_ye-m_ys>0))
		{
			if (m_XieLvChaZhi>=0)
			{
				m_xi++;
			} 
			else
			{
				m_yi++;
			}
			cout<<m_xi<<","<<m_yi<<endl;
		}
 
		if ((m_xe-m_xs<0)&&(m_ye-m_ys>0))
		{
			if (m_XieLvChaZhi<=0)
			{
				m_xi--;
			} 
			else
			{
				m_yi++;
			}
			cout<<m_xi<<","<<m_yi<<endl;
		}
 
		if ((m_xe-m_xs<0)&&(m_ye-m_ys<0))
		{
			if (m_XieLvChaZhi<=0)
			{
				m_yi--;
			} 
			else
			{
				m_xi--;
			}
			cout<<m_xi<<","<<m_yi<<endl;
		}
 
		if ((m_xe-m_xs>0)&&(m_ye-m_ys<0))
		{
			if (m_XieLvChaZhi>=0)
			{
				m_yi--;
			} 
			else
			{
				m_xi++;
			}
			cout<<m_xi<<","<<m_yi<<endl;
		}
	}
	cout<<"********************************************"<<endl;
}

表7-6 源文件Interpolator.cpp程序

首先编写构造函数,通过构造函数的四个参数(即起点坐标与中点坐标)来构造不同的对象,并且创建对象时使用输出流对象cout进行起始点以及终点的显示(10行至11行)。

成员函数DoInterpolator实现了过程点的计算以及输出显示,该部分代码原理在第5章已经有讲述,这里不再累述。

接下来,就可以通过在“源文件”中添加主函数文件进行类的测试。程序如表7-7所示。

#include "Interpolator.h"
 
int main()
{
    Interpolator chabu1(1,1,5,5);
    chabu1.DoInterpolator();
    Interpolator chabu2(2,3,-4,2);
    chabu2.DoInterpolator();
    system("pause");
    return 0;
}

表7-7 源文件main.cpp程序

可以看到测试程序里面,分别使用Interpolator类创建了两个对象——chabu1(第5行代码,参数表示起点坐标为1,1点,终点为5,5点)以及chabu2(第7行代码,参数表示起点坐标为2,3点,终点为-4,2点)。

程序测试结果如图7-10所示。

 

图7-10 逐点比较法直线插补类测试结果示意图

7.4 计算器小软件

前面我们学习了大量的C++语言的基础知识,也通过很多练习加强了对该语言应用的掌握。但大家似乎会疑问,为什么我们最终的程序实现结果是一个黑乎乎的界面,一点都不美观,感觉和我们常用的软件相差甚远。7.4与7.5两节我们将开始对Qt进行应用,做两个小软件。当然,我们还是采取由简至难的策略。这一节我们基于Qt先实现一个简易的计算器小软件,该软件能够实现对两个输入的数字进行加减乘除运算。

7.4.1 QT APPLICATION项目创建

接下来需要我们先创建一个Qt Application项目。打开VS2010,依次点击“文件”、“新建”、“项目”,然后在“新建项目”界面下,点击“Qt5 Projects”,并点击选择该项目下的“Qt Application”,更改项目名称为“Calculator”,项目名称和接下来系统默认创建的程序相关,请不要写错。如图7-11所示。

 

图7-11 Qt Application项目创建示意图

然后会自动出现Qt5 GUI Project Wizard窗口,该窗口可以设置项目所需要的组件,我们忽略设置步骤,直接点击“Finish”即可,如图7-12所示。

 

图7-12 Qt5 GUI Project Wizard窗口

其后,VS2010将自动创建一个Qt Application项目,并且自带了一部分文件以及程序。按下F5,编译并运行,显示一个什么都没有的窗口,完成第一步项目创建工作。

7.4.2 UI界面设计

前面我们已经写了很多的程序,这里我们将接触一个全新的编程模式:使用Qt Designer将界面画出来,然后自动生成对应界面的代码;当然我们可以不用管生成后的代码是什么样的,也可以很好的将目标软件实现出来。

双击“Calculator”项目下的“calculator.ui”文件,如图7-13所示。

 

图7-13 Calculator项目文件示意图

系统将自动调用Qt Designer进行界面设计。Qt Designer能够快速设置窗口需要的组件以及组件的相关属性,可以说是以“画图”的方式来进行软件设计。其界面如图7-14所示。

 

 

图7-14 Qt Designer界面示意图

Qt Designer上方如其他软件类似是菜单栏和工具栏,需要特别注意的是最左侧的“Widget Box”以及中间部分标题为“Calculator - calculator.ui”的窗口,“Widget Box”里面是软件需要的一些组件,包括我们经常接触到的按钮、标签、文本框等等。我们直接点击鼠标不松将需要的组件拖入我们的“Calculator - calculator.ui”窗口当中。我们需要两个能够输入数字并能够显示的组件,我用在“Widget Box”中查找,可找到Double Spin Box组件,我们将其拖出,放在我们的“Calculator - calculator.ui”窗口当中;然后找到Push Button组件,拖入四个到“Calculator - calculator.ui”窗口当中;如图7-15所示。

 

图7-15 包含三个Double Spin Box以及四个Push Button的窗口示意图

我们将对最左侧的两个Double Spin Box进行相应数字的输入,然后四个Push Button分别代表对这两个数字的加、减、乘、除计算功能执行,右侧的Double Spin Box将两个数字对应的计算结果显示出来。

那么,我们程序里面怎么识别我们点击了哪一个按钮呢?

我们把目光转向图??的右侧的“属性编辑器”,当点击组件的时候,右侧的“属性编辑器”将出现组件对应的属性,其中最重要的就是“objectNmae”,这个是我们写程序时所需要用到的名称,以此跟界面连接起来,实现图形交互。我们点击图??最上面的那个“PushButton”,将“objectName”由默认的名称改为“ButtonAdd”,如图7-16所示。

 

图7-16 UI文件按钮名称更改示意图

其后分别将其他三个按钮的“objectName”属性改为“ButtonReduce”、“ButtonMutiply”、“ButtonDivide”;将三个“Double Spin Box”组件的“objectName”属性改为“DoubleBoxNumber1”、“DoubleBoxNumber2”、“DoubleBoxNumber3”,同时,我们将三个“Double Spin Box”组件的“decimals”属性改为“6”,这样该组件就能显示小数点后6位的数字,同时将“minimum”和“maximum”分别改为-10000和10000,这样基本上能够满足我们的数字显示范围(这三个属性在该组件的属性编辑器较下面的位置)。

由于界面显示了四个相同Push Button,双击Push Button组件,我们将显示的内容改为“+”、“-”、“*”、“/”,如图7-17所示。

 

图7-17 已完成的简易计算器界面图

我们会发现在标题多了一个星号——“Calculator - calculator.ui*”,这是因为我们改动了界面但没有保存的缘故,我们依次点击菜单栏的“文件”,“保存”,将完成的界面保存起来。到此为止,ui界面的设计已经完成,接下来我们要编写程序,来实现计算功能。

7.4.3 计算功能程序实现

在创建Qt Application项目后,会自动生成一部分文件与相应程序。我们可以直接在系统已给定的程序基础上进行程序的编写。我们依旧先对头文件进行编写,如表7-8所示,加下划线的代码是添加或修改的。

#ifndef CALCULATOR_H
#define CALCULATOR_H
 
#include <QtWidgets/QMainWindow>
#include "ui_calculator.h"
 
class Calculator : public QMainWindow
{
    Q_OBJECT
 
    public:
    Calculator(QWidget *parent = 0);
    ~Calculator();
 
    private slots:
    void Add();
    void Reduce();
    void Mutiply();
    void Divide();
 
    private:
    Ui::CalculatorClass ui;
    double m_Number1;
    double m_Number2;
    double m_Number3;
};
 
#endif // CALCULATOR_H

表7-8 calculator.h程序

首先,我们看1、2、28行的代码,这三行是一体的,该语句为了防止头文件重复编译,类似于#pragma once,第4、5行包含了两个头文件,第一个头文件,声明我们要使用QMainWindow类,也就是说我们创建的Calculator类是一个能够实现主窗口的类;第二个头文件ui_calculator.h 表示我们包含了一个界面文件,也就是我们上文所说的Qt Designer中设计的界面文件编译后的文件。其中,第7行代码表明创建了类Calculator,并继承了QMainWindow的功能,类的继承我们会在第八章进行讲解,这里我们先跳过去;Q_OBJECT为宏,类似于我们学过的 using namespace std; 该语句使用后,方便我们后面会使用connect函数(后面会讲);接下来定义了两个公共函数,分别是构造函数和析构函数。

接下来我们看这一节最重要的部分——信号和槽机制。信号和槽机制是 Qt 的核心机制,可以让编程人员将互不相关的对象绑定在一起,实现对象之间的通信。举例来说,当用户点击我们设计的简易计算器的“+”按钮时,软件内部将执行加法功能,而用户点击这一行为发出的就是一个信号,该信号对应的要实现的功能即为槽。所有的槽函数需要在头文件中单独在“private slots:”语句下进行声明。四个函数分别对应接下来要编写的源文件中的加减乘除四个功能。

我们需要对两个数进行计算,所有我们声明两个私有变量,以及一个计算后的私有变量,对应程序的23至25行。对于22行代码,大家可以简单理解为我们前面所设计的整个界面,后面使用界面里面的组件的时候,需要先输入22行代码中所命的名字ui。

接下来我们进行源文件的编写,程序如表7-9所示,加粗部分为添加的代码部分。

#include "calculator.h"
 
Calculator::Calculator(QWidget *parent)
: QMainWindow(parent)
{
    ui.setupUi(this);
    connect(ui.ButtonAdd,SIGNAL(clicked()),this,SLOT(Add()));
    connect(ui.ButtonReduce,SIGNAL(clicked()),this,SLOT(Reduce()));
    connect(ui.ButtonMutiply,SIGNAL(clicked()),this,SLOT(Mutiply()));
    connect(ui.ButtonDivide,SIGNAL(clicked()),this,SLOT(Divide()));
}
 
Calculator::~Calculator()
{
}
 
void Calculator::Add()
{
    m_Number1=ui.DoubleBoxNumber1->value();
    m_Number2=ui.DoubleBoxNumber2->value();
    m_Number3=m_Number1+m_Number2;
    ui.DoubleBoxNumber3->setValue(m_Number3);
}
 
void Calculator::Reduce()
{
    m_Number1=ui.DoubleBoxNumber1->value();
    m_Number2=ui.DoubleBoxNumber2->value();
    m_Number3=m_Number1-m_Number2;
    ui.DoubleBoxNumber3->setValue(m_Number3);
}
 
void Calculator::Mutiply()
{
    m_Number1=ui.DoubleBoxNumber1->value();
    m_Number2=ui.DoubleBoxNumber2->value();
    m_Number3=m_Number1*m_Number2;
    ui.DoubleBoxNumber3->setValue(m_Number3);
}
 
void Calculator::Divide()
{
    m_Number1=ui.DoubleBoxNumber1->value();
    m_Number2=ui.DoubleBoxNumber2->value();
    m_Number3=m_Number1/m_Number2;
    ui.DoubleBoxNumber3->setValue(m_Number3);
}

表7-9 calculator.cpp程序

第1行代码包含相应的头文件,第3、4、6、13行代码为构造函数以及析构函数的相关代码,这部分默认给定的代码我们通常情况下无需修改。

我们重点看第7行至第10行,前面我们已经提到了信号和槽机制,这里我们将两者相连,实现该功能的函数尉connect函数,很容易找到该函数的使用规则,该函数的共有四个参数,其中第一个为界面中的组件,第二个参数对应于该组件的信号类型,第三个为this,第四个为对应的槽函数。如果想深入了解Qt、信号与槽机制、connect函数等可以学习Qt的帮助文档,学会看帮助文档是一个程序员必备的素质。

第17行至第23行我们实现了加法功能,通过Double Spin Box组件的成员函数value()来获取DoubleBoxNumber1以及DoubleBoxNumber2值,m_Number1以及m_Number2分别得到到了两个组件的值后,相加后赋值给m_Number3,,最后使用函数setvalue()将变量m_Number3的值赋给组件DoubleBoxNumber3,并显示出来。

我们能够看到无论是value()函数还是setValue()函数前面都使用了“->”符号,而不是“.”;因为Qt在编译界面文件的时候,自动将我们所用的所有组件以指针的形式进行创建(大家可以在Calcultor项目下的Generated Files文件夹中ui_calculator.h文件找到),所以ui.DoubleBoxNumber1、ui.DoubleBoxNumber2、ui.DoubleBoxNumber3都是指向类的指针,后面调用成员函数的时候用“->”符号。

其余三个函数与加法函数没有太大的区别,在此不多做解释。

最后还是下一个main.cpp文件,这个文件中的程序我们无需改动,具体的程序意义不再累述,因为Qt自身就是一个很庞大的软件项目,有余力或有兴趣的同学可以自行深入地去学习,这些年Qt因为自身的开源、免费、跨平台特性越来越受到开发者的热爱,并且应用也越来越广泛。

到这里我们就可以按下键盘上的F5键,我们就可以测试下自己编写的简易计算器是否能够如愿工作。如图7-18所示,我们分别输入“123”和“456”,然后按下按钮“/”,得到结果为“0.269737”。

 

图7-18 简易计算器测试结果图

7.5 编程练习:直线插补模拟小软件

通过使用Qt完成一个简易计算器,让我们对软件的用户交互界面实现有一个初步的认识。这一节我们完成一个直线插补模拟小软件。

首先,创建一个名为“Interpolator”的Qt Application项目;双击“Interpolator”项目下的“interpolator.ui”文件,开始设计直线插补模拟小软件的交互界面,如图7-19所示,我们这一次学习两个新的组件——Label、Text Browser。我们拖入两个Label组件,并将显示内容改为“Start Point:”和“End Point:”,Label组件的主要功能就是在界面上进行提示性显示,所以对于“objectName”以及其他属性都无需更改;然后分别在两个Label组件后面各添加两个Spin Box组件,Spin Box组件和Double Spin Box的区别在于前者所能够输入输出的数字为整数,后者则为带小数的数字,分别将两组Spin Box组件改名为“spinBox_xs”。放置一个Push Button组件,点击该按钮将触发对应的直线插补功能,并将过程点输出在我们Text Browser组件中,我们将Push Button的“objectName”改为“ButtonDoInterpolator”,Text Browser的“objectName”保持“textBrowser”不变。后面我们依旧在程序中使用这些组件的名字来实现界面交互功能。

 

图7-19 直线插补模拟小软件示意图

然后,我们开始完成头文件“interpolator.h”以及源文件“interpolator.cpp”,主程序文件“main.cpp”依旧不需要改动。经过了逐点比较法直线插补类以及计算器小软件的训练,大家可以自行尝试写一写程序,然后参考下面给出的程序内容进行比对。头文件“Interpolator.h”程序如表7-10所示。

#ifndef INTERPOLATOR_H
#define INTERPOLATOR_H
 
#include <QtWidgets/QMainWindow>
#include "ui_interpolator.h"
 
class Interpolator : public QMainWindow
{
    Q_OBJECT
 
    public:
    Interpolator(QWidget *parent = 0);
    ~Interpolator();
    private slots:
    void DoInterpolator();
    private:
    Ui::InterpolatorClass ui;
    int m_xs;
    int m_ys;
    int m_xe;
    int m_ye;
};
 
#endif // INTERPOLATOR_H

表7-10 Interpolator.h程序 

程序中我们只在原有程序的基础上增加了一个槽函数以及四个int型变量,四个int型变量代表起点和终点坐标。

然后是源文件Interpolator.cpp,如表7-11所示。在程序中,加入一个connect函数,将按钮与为一个槽函数DoInterpolator()相连接;第16行代码比表示清除掉Text Browser中的内容,然后接下来四行将Spin Box组件中的数值提取出来赋值给头文件中声明的四个int型变量——m_xs,m_ys,m_xe,m_ye;剩下的大多数代码已经多次学习,第39至第42行代码实现了过程点在界面中Text Browser组件中的显示,Qt的QString类提供了很方便的对字符串操作的接口,注意QString类与我们前面学到的输入输出流iostream类的区别;调用Text Browser组件下的append()函数即可实现,对该函数括号中参数(参数类型为QString型)的显示。想进一步了解Text Browser组件的其他成员函数以及用法可以查看Qt的帮助文档。

#include "interpolator.h"
 
 
Interpolator::Interpolator(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);
	connect(ui.ButtonDoInterpolator,SIGNAL(clicked()),this,SLOT(DoInterpolator())); 
}
 
 
Interpolator::~Interpolator()
{
}
 
 
void Interpolator::DoInterpolator()
{
	ui.textBrowser->clear();
	m_xs=ui.spinBox_xs->value();
	m_ys=ui.spinBox_ys->value();
	m_xe=ui.spinBox_xe->value();
	m_ye=ui.spinBox_ye->value();
	
	int Nxy,xi,yi,FM;
	xi = m_xs;
	yi = m_ys;
	Nxy =abs( (m_xe - m_xs)) + abs((m_ye - m_ys));
	for(int i=0;i<Nxy;i++)
	{
		FM =(m_xe-m_xs)*(yi-m_ys)-(xi-m_xs)*(m_ye-m_ys);
		if(m_xe-m_xs==0)
		{
			if (m_ye-m_ys>0)
			{
				yi++;
			}
			else
			{
				yi--;
			}
			QString str1 = QString::number(xi,'g',6);
			QString str2 = QString::number(yi,'g',6);
			QString str3 = str1+','+str2;
			ui.textBrowser->append(str3);
		}
 
 
		if(m_ye-m_ys==0)
		{
			if (m_xe-m_xs>0)
			{
				xi++;
			}
			else
			{
				xi--;
			}
			QString str1 = QString::number(xi,'g',6);
			QString str2 = QString::number(yi,'g',6);
			QString str3 = str1+','+str2;
			ui.textBrowser->append(str3);
		}
 
 
		if ((m_xe-m_xs>0)&&(m_ye-m_ys>0))
		{
			if (FM>=0)
			{
				xi++;
			} 
			else
			{
				yi++;
			}
			QString str1 = QString::number(xi,'g',6);
			QString str2 = QString::number(yi,'g',6);
			QString str3 = str1+','+str2;
			ui.textBrowser->append(str3);
		}
 
 
		if ((m_xe-m_xs<0)&&(m_ye-m_ys>0))
		{
			if (FM<=0)
			{
				xi--;
			} 
			else
			{
				yi++;
			}
			QString str1 = QString::number(xi,'g',6);
			QString str2 = QString::number(yi,'g',6);
			QString str3 = str1+','+str2;
			ui.textBrowser->append(str3);
		}
 
 
		if ((m_xe-m_xs<0)&(m_ye-m_ys<0))
		{
			if (FM<=0)
			{
				yi--;
			} 
			else
			{
				xi--;
			}
			QString str1 = QString::number(xi,'g',6);
			QString str2 = QString::number(yi,'g',6);
			QString str3 = str1+','+str2;
			ui.textBrowser->append(str3);
		}
 
 
		if ((m_xe-m_xs>0)&(m_ye-m_ys<0))
		{
			if (FM>=0)
			{
				yi--;
			} 
			else
			{
				xi++;
			}
			QString str1 = QString::number(xi,'g',6);
			QString str2 = QString::number(yi,'g',6);
			QString str3 = str1+','+str2;
			ui.textBrowser->append(str3);
		}
	}
}

表7-11 Interpolator.cpp程序

主程序文件“main.cpp”依旧无需改动,此时点击键盘F5编译并执行程序,我们以起始点(12,34)终点(56,78)为例,测试一下输出的结果,如图7-20所示。

 

图7-20 直线插补模拟小软件测试结果图

版权声明:本文为qq_36552550原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:七、函数与类——Qt快速入门、三点求圆心实现详解_Ce Ma的博客-CSDN博客

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值