程序代写代做代考 html 第一章 熟练使用C++开发环境

第一章 熟练使用C++开发环境
学习《面向对象的程序设计》课程的过程中,同学们使用了不同的C++开发环境,有的同学在Windows下使用“老古董”VC6,或者“巨无霸”Visual Studio;有的同学在用一些轻量级的软件,比如Code::Blocks,Dev C++等,后面这两个软件提供捆绑了编译器和调试器组件(比如MinGw)的安装文件,否则需要单独安装编译工具,比如使用VS Code的同学;用“苹果”的同学可以使用Xcode全家桶;而使用Linux的同学,系统已经默认安装了g++编译器等工具链,打开各种文本编辑器神器就可以写代码了,不过需要自己手工编译源程序或者自己写makefile(或者使用Cmake等工具生成makefile)。
上面提到的一些集成了编辑、编译、调试等工具的软件称为IDE(Integrated Development Environment,集成开发环境),可以方便我们快速进行开发,是否使用IDE应该根据所开发的项目来决定。上个学期课程的实验对IDE的需求很低,不过这个学期的课程设计,如果你不使用IDE的话,可能会花大量的时间在一些基础任务上。
所以,花点时间熟悉一个你手头可以使用的IDE,将会使你顺利完成本门课程的编程工作。上学期的实验,几乎每个实验的代码都可以写到一个cpp文件中,但是真实的项目通常包括大量的各种各样的文件和资源,IDE有一个特点是把一个项目里众多的文件和资源整合在一起,并提供一系列的工具,比如编辑、编译、调试等,给开发带来方便。
我们这个课程需要开发图形用户界面程序,所以我们的第一个任务就是进一步熟悉使用你选择的开发工具,并由此决定了你要使用的图形界面库。
如果你使用Visual Studio,那么可以选择MFC,虽然这个类库面临被微软抛弃的命运,但是它毕竟使用了不完全面向对象的方法包装了Windows API,在当年也是风靡一时,已经积累了很多的资料,有问题可以方便在网上找到解决办法。对于我们要做的小项目而言完全能够满足,所以如果你安装了Visual Studio,可以运行它的installer程序里添加安装MFC模块。
不在Visual Studio的环境下,却想使用MFC几乎会让你绝望。那么你可以选择其他的图形界面库,比如Qt,Gtk,wxwidget,WPF等。这里推荐使用Qt,跨平台,学习起来也不难。你需要在download.qt.io这个网址下载安装。官方发布的版本在official_releases/qt/里。需要注意,5.15版以后,开源版只提供源码,不提供编译后的安装文件,如果你不想从源码编译Qt,可以选择稍早的版本。点击Details按钮,可以看到我们国内的镜像,下载速度更快。
不管你是使用MFC、Qt,还是其他的图形界面库,接下来你应该自学一些在这个库的基础上开发图形界面程序的知识,对于本课程设计的项目来说,主要是基本的图形绘制、读取和显示像素,网上的例子教程有很多。

第二章 统计的方法求π
目的
循环的使用和数值计算
内容
有一个正方形和它的内切圆。圆的半径为R,则正方形的边长为2R,内切圆与正方形的面积比:
= =
如果在正方形区域内随机产生大量的均匀分布的点,那么落入内切圆和正方形中的随机点个数的比值等于它们的面积之比。该比值乘以4,即为π值。

算法思路:
记住,我们在做这个实验的时候不知道pi的值到底是多少,因此不知道用多少个随机点才能够求得比较准确的pi值。我们求解的终止条件是计算的结果达到某种“满意度”,这个“满意度”在本实验中就是pi的精度。
因此,你需要设置一个合理的精度e,比如e=0.01。我们使用多轮的计算,每一轮都产生大量的随机点(比如N个),然后计算统计落入圆内的点的个数(比如M个)。重复计算多轮,每轮都把新的统计后的点数加入到计算过程中,随着轮数的增加,得到的pi值将逐渐逼近真实的pi值,相邻两轮间计算的pi值的差也将逐渐减小,直至低于设定的精度。具体如下:
第一轮N个点计算的pi值为,第二轮计算的pi值为,第三轮计算的pi值为 ……
轮数越多,使用的点越多,那么这个值理论上越接近真实的pi。那么到底需要多少轮呢?这就是我们最外层循环条件“精度”所决定的了。当新一轮的N个点加入到计算过程中,我们要判断新的pi值相对于前面一轮的pi值到底“改进”了多少,如果这个“改进”比所设的精度都小,那么就可以退出计算并返回结果了。

伪算法描述:
首先给pi一个初始值,比如100(我们不知道pi的值是多少,随便先猜一个,别告诉我你猜的是3.1415926)。还要初始化另一个变量:当前计算的pi值和上次计算的pi值之间的差residual,比如也初始化为100。当然了,你还得初始化其他一些变量,比如记录所有随机点数的累加器置零,把记录所有落入圆内点数的累加器置零。
while 差值residual > 精度e
for循环,在该循环内产生N个随机点
计算落入圆内的点的个数M;
M累加到记录所有圆内点个数的变量中;
计算新的pi值;
计算新的residual值;
输出计算的pi值

本次实验的主要内容是利用上述方法计算π,以给定的精度作为求π循环的终止条件。要求:
• 合理使用while循环和for循环;
• 设定合适的终止条件。
• 练习单步跟踪调试程序。
• 注意变量的可取值范围。

提示:
• 产生随机的点需要使用随机数生成器,在C和C++的标准库里都有提供,请自己查阅资料进行学习和使用。注意,计算机产生的随机数是伪随机数,为了避免每次运行程序产生同样的伪随机数序列,要使用合适的“种子”来初始化伪随机数产生器,这样每次运行程序会产生不同的伪随机数序列。
• 求解pi的过程要使用2层循环。最外层的循环判断是否达到终止条件,内层循环产生一定数量的随机点。
• 这个算法有两个参数需要你来设定:N和e。不合理的参数值会导致程序计算的结果不好或无法收敛。

#include

using namespace std;

int main(int argc, char* argv[])
{
// 声明所用的变量并正确初始化
// 用当前的时间作为种子初始化随机数产生器

// 编写求pi的循环
// 在你的循环体内
{
// 调用随机数产生器,产生随机二维点(两个随机数:x坐标和y坐标)
// 将随机数变换到正方形所在的区间
// 判断点是否在圆内
// 更新统计数据,求pi值
}
// 在控制台输出pi值

return 0;
}
要求
完成上述代码,并能显示正确的π值计算结果。
看看所计算的pi值的精度能达到小数点后多少位,试分析原因。
请多试验几个参数N和e的值,看看多少比较合适。N和e的值太大或太小会导致什么问题,试着分析原因。

第三章 π值的可视计算
目的
学习使用函数
了解可视计算
初步了解图形界面程序的开发和基本绘制
内容
在上一章中,我们通过统计落入正方形内切圆和正方形中的随机点的比值,来求解pi的近似值。本实验中,我们将把随机点画出来,在图形界面中进行显示,让求π的过程可视化。要求:
• 显示一幅边长为R的正方形图像(设置为0,全黑色),利用给出的画圆函数,显示出此正方形边长为R/2的内切圆(设置为255,白色),圆心位于正方形中心;
• 产生随机的二维点,将产生的随机点在图形界面中的正方形区域显示出来,该点的颜色要根据是否在圆内使用不同的颜色。我们屏幕上能够显示的点的数目毕竟有限,为了不让正方形区域很快被填满,需要设计一个机制让产生的点逐渐消失,给后面的点“腾地方”。一个简单的办法是让每个点的亮度随时间(新的随机点的产生)减弱,直至消失。
• 用合适的精度求得π值。每轮产生一定数量的点(数量通常要大一些),每次通过前n轮的数据求π值,与前面n-1轮累计数据算得的π比较,当误差小于一定数值时终止,得到最终π值,否则继续下一轮计算。计算的π值都在界面输出。
• 完成相关的辅助绘制函数,要求不适用类库现成的函数,自己设计并实现画圆的函数。

注意:
• 在本实验和今后的几个实验中,我们会接触计算机图形和图像。我们对图形图像并不陌生,我们使用的绝大部分程序都是图形界面程序,大家用手机拍摄的照片就是一幅数字图像。我们在屏幕上显示的图形和图像都是由一行行、一列列的像素组成的(称为栅格图形)。描述一个像素所用到的数值个数称为通道数(channels)。如果用三个值来描述其颜色(即3通道,分别为红绿蓝,RGB),那么图像是彩色的;如果仅用一个值来描述,那么RGB通道的值都使用这一个值,得到的这幅图像是灰度图像(类比黑白照片;黑白电视机的屏幕只有一个通道)。在本章任务中,我们用不同的颜色来绘制各种元素,如正方形、圆形、圆内的点和圆外的点。
有一个地方需要理解一下,如果一个图像是3通道的,但是像素的RGB值都是一样的,那么它和一个单通道的图像看起来一样,都是没有颜色的灰度图,但是这两个图像的大小不一样,因为内部占用的空间是差了3倍的。
• 像素值(针对单通道图像也常称为灰度值)通常以8bit无符号整数存取(unsigned char类型)。这是因为大部分显示器针对每个通道只能够显示256个不同的层级,这也是大部分图像文件存取像素信息时使用的数据类型。有些高级的图像格式选择使用更高精度的数据类型,比如12bit无符号整数、float类型,这里暂不考虑。
• 我们使用的图像的行数和列数是从零开始计数的。比如图像有100行,那么行数是从第0行到第99行,而不是从第1行到第100行。在对图像所有像素进行遍历时需要注意。
• 根据你选择的图形界面开发工具和库,学习如何在图形界面中使用绘制函数对一个像素点进行绘制。
• 完成若干函数,比如所有像素置为某像素值(背景)、在绘制区域画圆、通过将已有的点的像素值减去某值实现让该点逐渐消失的效果等。

要求
完成上述代码,动态绘制求解过程。下图是一个示例,里面的像素没有用到彩色,而只是用了灰度。

提示
使用MFC的GUI绘图功能
创建基于MFC的应用程序,在View类的OnDraw函数中完成绘制工作。

CClientDC dc(this);
CBrush bkbrush; // 创建一个画刷对象
bkbrush.CreateSolidBrush(RGB(0, 0, 0)); // 设置该画刷为黑色

// 选择使用新画刷,并保存原来的画刷
CBrush* pOrigBrush = (CBrush*)dc.SelectObject(&bkbrush);

// 把左上角的矩形涂色
CRect rc(0, 0, 800, 800);
dc.Rectangle(0, 0, rc.Width(), rc.Height());
// 恢复原来的画刷
dc.SelectObject(pOrigBrush);

// 把某个像素点涂色
dc.SetPixel(400, 400, RGB(255, 0, 0));

// 获得某个像素点的颜色
COLORREF pix = dc.GetPixel(400, 400);

// 在某个位置输出文字
CString result;
double pi = 3.14;
result.Format(_T(“计算结果pi = %f”), pi);
dc.TextOutW(900, 400, result);

使用Qt的绘图功能
新建QtWidgets项目
在mainwindow.h里,声明重写函数void paintEvent(QPaintEvent *event)
在mainwindow.cpp里,包含头文件#include ,并实现paintEvent函数:
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setPen(QPen(Qt::red, 10));
painter.drawPoint(200, 200);
painter.drawText(QPoint(10, 70), “hello” );
}

第四章 使用数组
目的
了解BMP文件的读写方法;
动态分配一维和二维数组
熟练数组的遍历和数组元素的操作;

内容
熟悉BMP文件的文件格式,练习文件的读写操作。

BMP文件格式是Windows操作系统推荐和支持的标准图像文件格式,是一种将内存或显示器的图像数据不经过压缩而直接按位存盘的文件格式,故称位图(bitmap),其扩展名为BMP。BMP图像通常有4个部分组成:文件头、信息头、颜色表、数据。

第一部分为位图文件头BITMAPFILEHEADER。位图文件头结构长度固定为14个字节,包含文件的类型、大小、位图文件保留字、位图数据距文件头的偏移量。其中WORD为无符号16位整数(unsigned short,2字节),DWORD为无符号32位整数(unsigned long,4直接)。具体结构体定义如下:
//位图文件头
typedef struct tagBITMAPFILEHEADER {
WORD bfType; //位图文件的类型,必须为0x424d 即BM两个字符的ASCII码
DWORD bfSize; //位图文件的大小,以字节为单位 包括该14字节
WORD bfReserved1; //位图文件保留字,暂时不用,一般为0
WORD bfReserved2; //位图文件保留字,暂时不用,一般为0
DWORD bfOffBits; //位图数据距文件头的偏移量,以字节为单位,即前三部分和
} BITMAPFILEHEADER;

第二部分为位图信息头BITMAPINFOHEADER,该结构也固定为40个字节,用于说明位图的尺寸、宽高、像素、分辨率、颜色表等信息。具体结构定义如下:
//位图信息头
typedef struct tagBITMAPINFOHEADER {
DWORD biSize; //本结构所占用字节数 40字节
LONG biWidth; //位图的宽度,以像素为单位
LONG biHeight; //位图的高度,以像素为单位
WORD biPlanes; //目标设备的级别,必须为1
WORD biBitCount; //每个像素所需的位数,必须是1(双色)、
//4(16色)、8(256色)或24(真彩色)之一
DWORD biCompression; //位图压缩类型,必须是 0(BI_RGB不压缩)、
//1(BI_RLE8压缩类型)
//2(BI_RLE压缩类型)之一
DWORD biSizeImage; //位图的大小,以字节为单位
LONG biXPelsPerMeter; //位图水平分辨率,每米像素数
LONG biYPelsPerMeter; //位图垂直分辨率,每米像素数
DWORD biClrUsed; //位图实际使用的颜色表中的颜色数
DWORD biClrImportant; //位图显示过程中重要的颜色数
} BITMAPINFOHEADER;

第三部分为颜色表或调色板(Palette)。有些位图需要调色板,有些位图如真彩色图(biBitCount=24)不需要调色板,它们的BITMAPINFOHEADER后面直接是位图数据。调色板实际是一个数组,共有biClrUsed个元素(如果该值为零,则有2的biBitCount次幂个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4字节。定义如下:
//位图颜色表
typedef struct tagRGBQUAD
{
BYTE rgbBlue; //蓝色的亮度(值范围为0~255)
BYTE rgbGreen; //绿色的亮度(值范围为0~255)
BYTE rgbRed; //红色的亮度(值范围为0~255)
BYTE rgbReserved; //保留,必须为0
} RGBQUAD;

第四部分就是实际的图像数据。对于真彩色图(24位位图 biBitCount=24),图像数据就是实际的RGB值;对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值。下面对2色、16色、256色和真彩色位图分别介绍:
(1)2色位图:当biBitCount=1时,用1位就可以表示该像素的颜色(0表示黑,1表示白),所以8个像素占1个字节;
(2)16色位图:当biBitCount=4时,用4为可以表示一个像素的颜色,所以2个像素占1个字节;
(3)256色位图:当biBitCount=8时,用1个字节表示1个像素,1个像素占1个字节;
(4)真彩色图:当biBitCount=24时,此时用3个字节表示1个像素,其中RGB各占1字节,由于没有颜色表,位图信息头后面是位图数据。

注意以下几点:
• 由于Windows规定一个扫描所占的字节数必须是4的倍数(即以long为单位),不足的以0填充。同时注意下面公式,计算只含位图数据的大小:
biSizeImage=(((bi.biWidth*bi.biBitCount)+31)/32*4)*bi.Height
获取文件的信息时可结合16进制数据进行查看。
• BMP图片格式的数据是从下到上、从左到右读。即文件中最先读到的图像是最下面一行的左边第一个元素,即从左下角开始存储(0,0)点,从左下角到右上角存储数据。另外,看看3通道图像每个像素的像素值的存储顺序,不是RGB,而是BGR。
• 使用C++读取BMP图片,可以自定义上述结构体,包含BMP位图的位图文件头结构、位图信息头结构、位图颜色表3个结构。使用MFC时,在其库文件wingdi.h中系统已经定义了BMP图像的结构BITMAPFILEHEADER、BITMAPINFOHEADER,可以直接在程序中直接使用。
• 如果你自定义这些结构体,你可能会掉进BITMAPFILEHEADER的坑里。用sizeof算一下这个结构体所占的字节数,你会发现结果不是14,而是16,这是由与上面第1条类似的为了效率而默认的4字节对齐原因产生的。请自行搜索“内存对齐Memory Alignment”问题学习。解决办法有3种:1)不使用自定义的结构体,在MFC下可直接使用系统库自带的结构体;2)更改对齐规则;3)不整体读该结构体,而是把该结构体内部的各个元素分别单独读取。可以参看这个帖子里收集的内容:
(https://www.cnblogs.com/jh818012/p/4245044.html)。

请编程实现:

• 图形用户界面打开一个BMP图像文件。自行学习如何通过使用文件打开对话框完成图形界面下文件名的获取。利用C++标准库进行文件的打开和读取。
• 动态分配一维数组d,将图像文件中的数据读入该数组。
• 动态分配二维数组a,将d中的数据复制到a中。
• 通过图形界面的发出指令,将数组a的元素上下翻转,即第一行变为最后一行。实现函数FlipImageUpDown,并显示翻转后的图像。
• 通过图形界面的发出指令,将数组a的元素左右翻转,即第一列变为最后一列。实现函数FlipImageLeftRight,并显示翻转后的图像。
• 通过图形界面的发出指令,设计并实现函数,将图像缩小为原来尺寸的一半,存入动态分配内存的二维数组b,并在界面显示。一个简单的做法是将a中的属于奇数行和奇数列的元素读取写入到b中。
• 将变换后的二维数组里的数据写回一维数组。
• 利用C++标准库的文件打开和写入操作,把图像变换后的结果保存成新的BMP文件。

完成上述函数的设计和实现。部分函数的声明如下:
void From1Dto2D(unsigned char **dst, unsigned char *src, int rows, int cols)
{
}

void From2Dto1D(unsigned char *dst, unsigned char **src, int rows, int cols)
{
}

void FlipImageUpDown(unsigned char **a, int rows, int cols)
{
}

void FlipImageLeftRight(unsigned char **a, int rows, int cols)
{
}

要求
完成上述代码,并能显示正确的结果图像。
注意事项
• 图像的基本组成单元用“像素”表示,例如如果图像的大小是480*511像素,表示图像的高度(行数)为480像素,宽度(列数)为511像素,;

• 本实例中加载的图像每个像素均占24bit,即每个像素在内存中占3个字节。

• 二级指针作为函数参数时,应在函数声明时指明其行列数,否则函数内部无法得知数组的维度;

• 动态分配的数组,使用完后要释放,防止内存泄漏。

• 提示:图像的大小是h行,w列,c个通道,像素值的类型是unsigned char(即在区间[0,255]内)。从文件中把图像数据先读到一维数组d,那么在d中定位第i行第j列第k个通道的像素的下标是d[?]。w*c*i + c*j + k