#edu017. 第 3 节 利用顺序、循环、分支绘图

第 3 节 利用顺序、循环、分支绘图

本节利用顺序、循环、分支控制画笔绘制图形,快速高效掌握顺序、循环、分支三种基本结构。

pen.fd(length) 可以控制画笔前进 lengthlength 单位,pen.rt(angel) 控制画笔右转 angelangel 度,画笔经过的地方就会留下线条,“前进100 - 右转90度”做 44 次就可以绘制一个正方形。

那么程序就很容易实现:

int main()
{
    pen.fd(100);     //C/C++中";"是语句结束标志
  	pen.rt(90);
  	pen.fd(100);
  	pen.rt(90);
  	pen.fd(100);
  	pen.rt(90);
  	pen.fd(100);
  	pen.rt(90);
    return 0;
}

同理,绘制正 66 边形,控制画笔”前进-右转“ 66 次就可以了,只不过右转角度为 6060 度(注意画笔旋转是正 nn 边形的外角,正 nn 边形外角为:180(n2)180n=360n180-\frac{(n-2)*180}{n}=\frac{360}{n}).但是重复次数过多,这种方式就无能为力了(比如重复十万次),最好就是利用循环。

C++ 中最简单的循环控制方式就是 while()()() 中就是条件,条件成立执行循环体,条件不成立,跳出循环,执行过程如下图:

while循环

while循环

循环体是单个语句:

while循环

循环体是单个语句

循环体是多个语句,多个语句用大括号括起来,构成一个整体,想一想如果去掉大括号会怎么样?

while循环

循环体是多个语句

while(true) 条件始终为真,循环就会一直执行下去,执行后可以绘制出正 44 边形,但是程序无法停止;我们可以利用 pen.show()pen.show() 显示画笔, pen.speed(110)pen.speed(1-10) 降低画笔速度,可以清晰看到画笔一直“前进-右转”。

int main()
{
	pen.show();    //显示画笔
	pen.speed(5);  //括号中填1-10,数字越大画笔越快
	while(true)
	{
		pen.fd(100);
		pen.rt(90);
	}
    return 0;
}

如何精准的控制循环进行 44 次?

如果有一个计数器,记录循环次数,那么就可以精准控制循环次数了,能够存储数字的计数器在程序设计中就是变量,如何得到一个变量了?

“数据类型 变量名” 就是定义变量的格式,例如定义了一个名字为 ii 的变量: int i;

有了变量,让变量记录循环的次数,就可以精准控制循环次数了。首先,让变量有一个明确的初值,例如 i=1i=1,循环体执行 11 次,变量增大 11,循环体执行 44 次后,变量 ii 就为 55,需要跳出循环,那么循环条件就为 i4i \le 4 , 对应关键代码就为:

int i=1;
while(i<=4)
{
	pen.fd(100);
	pen.rt(90);
	i=i+1;      //'='是赋值运算符,后面表达式的值赋给前面的变量i
}

想一想:记录变量 ii 为什么需要给初值,初值一定要赋值为 11 吗?

例1:绘制一个周长为 1000 的正 nn 边形。

【分析】:我们可以定义一个变量 nn,存储边数,定义变量 ii 控制循环次数,循环 nn 次,那么变量 ii11 开始,循环到 nn ,循环条件就为 i<=ni<=n . 右转角度就为 180(n2)180n=360n180-\frac{(n-2)*180}{n}=\frac{360}{n} ,周长为 1000 ,那么边长就为 1000n\frac{1000}{n} ,程序如下:

int n=8;    //n存储边数
int i=1;
while(i<=n)  //循环条件为i<=n
{
	pen.fd(1000/n);
	pen.rt(360/n);   //正n变形的外角为 180-(n-2)*180/n=360/n
	i=i+1;      
}

如果可以从键盘输入数据,就可以直接从键盘上输入边数到变量 nn 中,从而避免每次都修改源程序。可以使用 cin>>n 从键盘上输入一个数值到变量 n 中, cout<<n 将变量 n 输出来。

输入: cin>>n;

输出: cout<<n;

int n;    
cin>>n;   //边数从键盘上输入
int i=1;
while(i<=n)  
{
	pen.fd(1000/n);
	pen.rt(360/n);   
	i=i+1;      
}

输入不同的整数 nn ,有的时候正 nn 变形不闭合,例如当输入 1313n=13n=13 时绘制的正 1313 边形如下图:

n=13 不闭合

当n=13时不闭合

为何会出现无法闭合的情况?可以多输入几组数据,就会发现问题出在 360/n360/n处,当输入的nn能够整除360360nn变形是闭合的,否则不闭合。

运行 cout<<5/2; 发现输出结果为 22,并不是 2.52.5,发现此处发生的是整除运算。

C/C++C/C++ 中参与运算都是 intint 型整数时,中间计算都会按照 intint 类型处理,结果只会保留整数,也就是进行了向下取整(实际是向零取整),这种整除运算很有用,会在后面很多时候用到。

知道了“参与运算都是整数,中间结果也按照整数处理”原理后,我们只需要将参与运算的对象改变类型就可以解决问题,例如 cout<<5.0/2; 输出结果为 2.5, 那么就可以修改程序如下:

int main()
{
	pen.show();    //显示画笔
	pen.speed(5);  //括号中填1-10,数字越大画笔越快
	
	int n;    //n存储边数
	cin>>n;   //边数从键盘上输入
	int i=1;
	while(i<=n)  //循环条件为i<=n
	{
		pen.fd(1000.0/n);
		pen.rt(360.0/n);   //正8变形的外角为 180-(n-2)*180/n=360/n
		i=i+1;      
	}
	
	pen.hide();    //隐藏画笔
    return 0;
}

nn输入的值比较大时,例如输入100100,正100100边形接近一个圆形,利用计算机模拟出“正nn边形,nn越大,就越接近圆形”,体会小学生微元法求圆形面积的过程。

例2:绘制一个周长为 1000 的彩色的正 n 边形

分析:利用 pen.color(015)pen.color(0-15),可以改变画笔的颜色,循环时可以将循环控制变量 ii 作为颜色参数,但是参赛范围 001515,当参数 ii 超过了范围将会是默认的颜色,可以有多种方法解决这个问题:

**方法一:**利用 if()if() 条件判断,再定义一个单独表示颜色的变量,每循环一次变量自增 11,当大于 1515 时,将这个表示颜色的变量强制改为 00,重新开始计数。

ifif 语句的执行过程如下图:

if逻辑

if 逻辑过程

单语句情况:

if语句

if 单语句

多个语句情况:

if语句

if 多语句

想一想:多语句情况,去掉大括号会怎样?

参考代码如下:

int n;    
cin>>n;   
int i=1;
int col=0;  //变量col表示颜色编号
while(i<=n)  
{
	pen.color(col);
	col=col+1;
	if(col>15)col=0;
	pen.fd(1000.0/n);
	pen.rt(360.0/n);   
	i=i+1;      
}

方法二: 颜色编号的范围是 [0,15][0,15],可以利用取余数的方法,控制变量 ii1616 的余数,那么如何进行取余了?可以利用之前的整除获得余数。

整数 aa 除以整数 bb,商 qq,余数为 rr,存在等式: a=bq+ra=b*q+r,可以得到余数 r=abqr=a-b*q,那么 ii1616 的余数就等于 i16(i/16)i-16*(i/16)

当然 C/C++C/C++ 里面有直接求余数的运算符 %\% ,也可以直接 pen.color(i%16)pen.color(i\%16)

方法三: 利用随机数取余,获得随机颜色,rand()rand() 函数可以产生一个 00RAND_MAXRAND\_MAX 之间的整数值。不过默认 rand()rand() 随机种子相同,多次运行会产生相同的随机数序列,可以利用 srand(time(0))srand(time(0)) 修改随机种子以产生不同的随机数序列。

注:rand()rand()srand()srand() 出自于库 stdlib.h , time(0)time(0) 出自于库 time.h ,返回值是从 197019701111 日至今所经历的时间(以秒为单位)。

参考代码如下:

int n;    
cin>>n;   
int i=1;
srand(time(0));  //改变随机种子
while(i<=n) 
{
	pen.color(rand()%16);	//利用随机数改变边的颜色
	pen.fd(1000.0/n);
	pen.rt(360.0/n);  
	i=i+1;      
}

例3:绘制 n 个正 n 边形

subtask1subtask1: nn 个正 nn 变形围绕原点均匀分布;

subtask2subtask2: nn 个正 nn 变形在一个圆上均匀分布;

subtask3subtask3: 利用 forfor 循环实现以上程序;

分析:在例2中,利用 whilewhile 循环和变量绘制了一个正 nn 边形,如果这个过程重复 nn 次,那就可以再套一重循环,循环 nn 次;但是如果绘制完一个正 nn 边形,如果在原始位置绘制第二个,两个正 nn 边形会重合在一起,对于 subtask1subtask1 绘制完第一个正 nn 变形,让画笔旋转一个角度 360.0/n360.0/n,然后绘制第二个,就可以让 nn 个正 nn 边形围绕原点均匀分布。对于 subtask2subtask2,可以让画笔先往前移动一段距离,然后再绘制,绘制结束后,让画笔后退相同距离,这样正 nn 变形就分布在圆周上,前进后退不留下痕迹,可以使用 pen.up()pen.up() 抬笔,绘制正 nn 边形再落笔 pen.down()pen.down()

参考代码如下:

int n;
cin>>n;
int i=1;
while(i<=n)
{
	int j=1;
	while(j<=n)
	{
		pen.color(j%16);
		pen.fd(1000.0/n).rt(360.0/n);
		j=j+1;
	}
	pen.rt(360.0/n);
	i=i+1;
}

对于 subtask2subtask2 读者可自行尝试,参考代码见附件。

对于 whilewhile 控制循环,有 33 个地方很关键:

(1) int i=1; 控制变量起点;
(2) 循环条件 i<=n ;
(3) i=i+1 控制变量自增;

这三点控制着整个循环开始、结束、中间变化过程,但是当程序比较复杂,对于 whilewhile 循环阅读起来很繁琐,forfor 可以将上述三点合在一起,使得程序更加简洁。

循环 nnforfor 循环的写法 for(int i=1;i<=n;i=i+1) 循环体 , 基本在执行过程如下:

for循环

for循环执行过程

subtask3subtask3 参考程序如下:

int n;
cin>>n;
for(int i=1;i<=n;i++)
{
	for(int j=1;j<=n;j++)
	{
		pen.color(j%16);
		pen.fd(1000.0/n).rt(360.0/n);
	}
	pen.rt(360.0/n);
}

**思考:**绘制 nn 个正 nn 边形,画笔前进了多少次?


附:subtask2subtask2 参考代码:

int main()
{
	int n;
	cin>>n;
	int i=1;
	while(i<=n)
	{
		pen.up();
		pen.fd(100);
		pen.down();
		int j=1;
		while(j<=n)
		{
			pen.color(j%16);
			pen.fd(1000.0/n).rt(360.0/n);
			j=j+1;
		}
		pen.up();
		pen.bk(100);
		pen.rt(360.0/n);
		i=i+1;
	}
    return 0;
}