c语言 打印任意行数的实心菱形图案
0.代码预览(分析附在后面)
0.1 第一种方法:
#include<stdio.h>
void PrintRhombus() {
int line;
printf("请输入菱形的上半行数:\n");
scanf_s("%d", &line);
//打印上半部分三角形
for (int i = 0; i < line; i++) {
for (int j = 0; j < line - i - 1; j++) { //控制打印空格的个数
printf(" ");
}
for (int j = 0; j < 2 * i + 1; j++) { //控制打印*的个数
printf("*");
}
printf("\n"); //不要忘了每行结束换行
}
//打印下半部分三角形
for (int i = 0; i < line - 1; i++) {
for (int j = 0; j < i + 1; j++) { //控制打印空格的个数
printf(" ");
}
for (int j = 2 * (line - i) - 3; j > 0; j--) { //控制打印*的个数
printf("*");
}
printf("\n"); //不要忘了每行结束换行
}
}
int main() {
//setbuf(stdout, NULL); //使用clion运行代码时要加这一行
PrintRhombus();
return 0;
}
0.2 第二种实现方法:
#include<stdio.h>
void PrintRhombus1() {
int line;
printf("请输入菱形的上半行数:\n");
scanf_s("%d", &line);
for (int i = 0; i < 2 * line - 1; i++) {
if (i < line) {
//打印上半部分三角形
for (int j = 0; j < line - i - 1; j++) { //打印空格
printf(" ");
}
for (int j = 0; j < 2 * i + 1; j++) { //打印*
printf("*");
}
printf("\n"); //不要忘了每行结束换行
} else {
//打印下半部分三角形
for (int j = 0; j < i - line + 1; j++) { //打印空格
printf(" ");
}
for (int j = 2 * (2 * line - i) - 3; j > 0; j--) { //打印*
printf("*");
}
printf("\n"); //不要忘了每行结束换行
}
}
}
int main() {
//setbuf(stdout, NULL); //使用clion运行代码时要加这一行
PrintRhombus();
return 0;
}
0.3 程序运行结果
输入一个正整数n
,即可打印一个行数为2n-1
的实心菱形:
下面示例中输入6代表菱形上半部分行数,于是输出了一个总行数11行的菱形,说明程序正常运行:
1. 前言
如果是第一次学习c语言的循环结构,很大概率会感觉循环的嵌套理解起来很吃力。下面以经典题目“打印由*
组成的菱形图案”来理解一下双层循环。
2.题目
打印任意行数的由*
组成的实心菱形图案,打印结果如图所示:
3.思路分析
面对这样的题目要求,有至少两种思路可以借鉴:
思路一:
使用两次for循环的嵌套,将菱形分成上下两个等腰三角形分别打印
如将上面的菱形分为以下两个三角形来分别打印:
![上半部分](https://img-blog.csdnimg.cn/img_convert/356b84758b434844a2961e5464fd153a.png)
![下半部分](https://img-blog.csdnimg.cn/img_convert/3a41cd040faf4693495e248590d6663d.png)
思路二:
使用根据一定的数学关系,在一次for循环的嵌套中直接打印出完整的菱形。
4. 分析如何用代码实现
4.1 分析思路一的代码实现
显然思路一需要分别实现两个三角形的打印
4.1.1 打印菱形的上半三角形
如图所示,首先来分析上面的三角形如何打印:
![上半部分](https://img-blog.csdnimg.cn/img_convert/356b84758b434844a2961e5464fd153a.png)
这个三角形是由空格和*
组成的,下表展示了行数与空格个数、*
个数的对应关系。
行数(从第0行开始算) | 空格的个数 | * 的个数 | |
---|---|---|---|
0 | 5 | 1 | 第0行有5个空格和1个* |
1 | 4 | 3 | 第1行有4个空格和3个* |
2 | 3 | 5 | 第2行有3个空格和5个* |
3 | 2 | 7 | 第3行有2个空格和7个* |
4 | 1 | 9 | 第4行有1个空格和9个* |
5 | 0 | 11 | 第5行有0个空格和11个* |
在for循环中通常使用
i
来表示行数,j
来表示列数,i
控制一共有多少行;j
来控制每一行有多少列,也就是控制每一行具体输出多少个数据,所谓空格的个数和星号*
的个数其实指的就是列数j
,因此可以把上表做如下等价替换:
表1 i
与j
的对应关系表
i 的值即行数 | 空格的个数对应的列数j | * 的个数对应的列数j |
---|---|---|
0 | 5 | 1 |
1 | 4 | 3 |
2 | 3 | 5 |
3 | 2 | 7 |
4 | 1 | 9 |
5 | 0 | 11 |
在双层for循环中确定i
和j
的循环次数是很重要的,通过上表我们可以写出一个for循环:
for ( i 控制循环 6 次 ) { //最外层循环6次,控制行数从0到5 for (j 控制循环次数从 5 到 0) { //嵌套第一个for循环,通过j控制循环次数,即打印空格的个数 打印空格; } for ( j 控制循环次数为从 1 到 11 之间的奇数) { //再并列嵌套一个for循环,通过j控制循环次数,即打印*的个数 打印*; } 打印换行; //打印完空格和*之后不要忘了打印一个换行符表示这行结束了 }
思路写好之后,再来把代码中的汉字用c语言语句或表达式代替,在此之前,首先要明确循环控制语句怎么写:
- 如果循环次数确定
有两种写法,以表示循环次数为6举例:
- 可以写成:
i = 0; i < 6; i++
或者i = 6; i > 0; i--
,用半开半闭区间表示即为:
[0,6)
或者(0,6]
这两个区间都有6个数,因此写成了这样的形式而且还有一个结论,区间左闭右开对应递增,区间左开右闭对应递减
- 也可以写成
i = 0; i <= 5; i++
或者i = 5; i >= 0; i--
,用闭区间表示为:
[0,5]
或者[0,5]
可以发现:区间长度就是循环次数,而区间的开闭表示是否带等号,有等号就是闭区间,没等号就是开区间。
- 如果循环次数有变化
根据上述结论,区间长度就是循环次数,如果循环次数有变化,用于表示循环次数的区间长度也必须有变化
那么如何使区间长度是变化的,可以让区间的一侧端点或两侧为变量,如区间
[0,a]
,a作为区间端点是可变的,如果a的值发生变化,那么区间长度也就随之变化。因此对于循环次数有变化的循环控制语句来说,重要的是确定区间的那个可变的端点。
在明白上述结论的基础上,我们可以把代码中的汉字改写为c语言语句或表达式
i
控制循环6次:
i
的控制的循环次数只与行数有关,最好确定,要打印几行就循环几次
写法一:
int i = 0; i < 6; i++ //递增通过i++来实现,i初始值赋0
这里的 6 其实就是总行数,设总行数为
line
,则有写法二:写法二:
int i =0; i < line; i++
这样写并非多此一举,如果采用这样的写法,就可以通过控制变量
line
的大小来输出任意的行数,因此我们最终用写法二替换掉“i
控制循环6次”这句话。
j
控制循环次数从5到0变化:如何实现循环次数的变化,就是确定一个可变区间,使得区间长度能随着
i
变化这里有一种思路:如果你希望让区间长度为
n
,则你可以写成[0,n)
或者(0,n]
,这样便于直观感受这样的话,我们要找的可变区间的一个端点就确定了,那就是0,现在我们只需确定另一个端点,即你所希望的区间长度。我们现在要找可变区间,那么自然是要找一个变量来表示区间长度。
我们回过头来再分析这个表:
i
的值即行数空格的个数对应的列数 j
*
的个数对应的列数j
0 5 1 1 4 3 2 3 5 3 2 7 4 1 9 5 0 11 不难发现,我们希望的
j
的值,即循环次数,也即区间长度与变量i
有相关性。通过一点代数分析,可以得到我们期望
j
值与i
值之间的关系是j = 5 - i
,这样区间的另一个端点也就确定了,最终我们得到的区间为(0, 5 - i]
,由于我们希望j
是递减的,因此写成左开右闭的形式,这个区间对应的控制语句是:
int j = 5 - i; j > 0; j--
,也就是我们可以用这条语句替换“j
控制循环次数从5到0”这句话。但这样写还不是最终结果,因为此时
j
还没有与表示总行数的变量line
产生关联。为什么要产生关联,因为我们期望用一个line
变量来控制整个菱形的打印过程。如何产生关联,在修改的过程中尽量只修改区间的可变端点,使区间保持只有一侧在动,这样更有利于理解,通过观察不难发现,可变端点
5 - i
中的5其实就是line -1
,用line -1
替换掉原来的5,把区间改写为(0,line -1 -i]
,这个区间对应的循环控制语句就是:
int j = line - 1 - i; j > 0; j--
最终我们用该语句替换掉了“
j
控制循环次数从5到0”这句话。
j
控制循环次数为从 1 到 11 之间的奇数:依照上面的思路,通过继续观察[上表](#表1
i
与j
的对应关系表)发现期望的循环次数为j = 2 * i + 1
,即j
的变化区间是[0,2 * i - 1)
用循环控制语句可以表示为int j = 0; j < 2 * i + 1; j++
,观察发现这里的j
确实与变量line
无关,因此不需要改写。直接用该语句替换“j
控制循环次数为从 1 到 11 之间的奇数”这句话。
将上面的改写到程序中把汉字替换掉,我们就得到了一段代码:
for (int i = 0; i < line; i++) { //最外层循环控制行数从0到5 for (int j = line - i - 1; j > 0;j--) { //嵌套一个for循环,通过j控制空格个数,把每一行的空格打印出来 printf(" "); } for (int j = 0; j < 2 * i + 1; j++) { //再并列嵌套一个for循环,通过j控制*的个数,把每一行的*打印出来 printf("*") } printf("\n"); //打印完空格和*之后不要忘了打印一个换行符表示这行结束了 }
到这里主体部分就完成了,补全对总行数line的声明和其他格式代码,就可以得到完整的代码:
#include<stdio.h> void PrintUp(){ //打印菱形上半部分三角形的函数 int line = 6; //定义一个变量line,存放要打印的三角形的总行数 printf("请输入要打印几行:\n"); scanf("%d", &line); //可以通过输入控制打印任意行数 for (int i = 0; i < line; i++) { //最外层循环控制行数从0到5 for (int j = line - i - 1; j > 0; j--) { //嵌套一个for循环,通过j控制空格个数,把每一行的空格打印出来 printf(" "); } for (int j = 0; j < 2 * i + 1; j++) { //再并列嵌套一个for循环,通过j控制*的个数,把每一行的*打印出来 printf("*"); } printf("\n"); //打印完空格和*之后不要忘了打印一个换行符表示这行结束了 } } int main(){ //setbuf(stdout, NULL); //如果使用clion运行程序则要加上这一行 PrintUp(); //在主函数中调用刚写的函数 return 0; }
经过测试,可以正确打印
到这里我们终于打印出了菱形的上半部分,接下来我们打印下半部分三角形
4.1.2 打印菱形的下半三角形
具体过程和上半部分基本一样,我们可以尝试先列出i与j的对应关系表,再用汉字写出代码,最后用c语言语句或表达式替换掉汉字,并添加上必要的变量声明和格式代码。
![下半部分](https://img-blog.csdnimg.cn/img_convert/3a41cd040faf4693495e248590d6663d.png)
首先尝试列出行数与空格个数、*
个数的对应关系表:
行数(从第0行开始算) | 空格的个数 | * 的个数 | |
---|---|---|---|
0 | 1 | 9 | 第0行有5个空格和1个* |
1 | 2 | 7 | 第1行有4个空格和3个* |
2 | 3 | 5 | 第2行有3个空格和5个* |
3 | 4 | 3 | 第3行有2个空格和7个* |
4 | 5 | 1 | 第4行有1个空格和9个* |
再用i
来控制和表示行数,用j
来表示和控制空格和*
的个数,实际上就是表示每一行有多少列空格和多少列*
,由此把上表转换成下表:
表2 i
与j
的对应关系表
i 的值即行数 | 空格的个数对应的列数j | * 的个数对应的列数j |
---|---|---|
0 | 1 | 9 |
1 | 2 | 7 |
2 | 3 | 5 |
3 | 4 | 3 |
4 | 5 | 1 |
列完表之后尝试用汉字写出代码思路:
for ( i 控制循环 5 次 ) { //最外层循环控制行数从0到5
for (j 控制循环次数从 1 到 5) { //嵌套第一个for循环,通过j控制空格个数,把每一行的空格打印出来
打印空格;
}
for ( j 控制循环次数为从 9 到 1 之间的奇数) { //再并列嵌套一个for循环,通过j控制*的个数,把每一行的*打印出来
打印*;
}
打印换行; //打印完空格和*之后不要忘了打印一个换行符表示这行结束了
}
接下来尝试把汉字转换成c语言表达式或语句
-
i
控制循环5次:- 写法一:
int i = 0; i < 5; i++
最简单的写法,但是这样写使得这部分代码与上面的打印菱形上三角的代码完全割裂开来,两段代码没有建立联系,而这两段代码要打印的是同一个菱形,因此希望通过修改,使两段代码产生关联。修改思路是使此处的
i
与变量line
产生关联。不难发现,
line
表示上半部分三角形的行数,而下半部分三角形的行数正好等于line - 1
,因此可以写作写法二:- 写法二:
int i = 0; i < line - 1; i++
这样写同样不是多此一举,是为了让代码之间产生关联,让一个
line
变量能够控制整个菱形的打印过程。因此采用写法二。
- 写法一:
-
j
控制循环次数从1到5:递增,采取左闭右开写法,观察得,可变端点为
i + 1
,因此区间为[0,i + 1)
,对应的控制语句写法为:
int j = 0; j < i + 1; j++
-
j
控制循环次数为从 9 到 1 之间的奇数:j
递减,采取左开右闭写法,在这里确可变区间的右端点有难度,需要一些代数技巧,我们把i
和这个j
的关系单独列一个表:i
的值即行数*
的个数对应的列数j
0 9 1 7 2 5 3 3 4 1 可以发现j随i的增大而减小
此时,不要忘记还有一个行数变量
line
,最容易想到的是用类似line - i
的式子来表示j
,原因是,随着i
的增加,line - i
在减小,而j
的值也随i
增加而减小。接着尝试配一些常数使两边相等,我们列表来观察line-i
和j
的关系:line
即上半菱形行数i
line - i
j
6 0 6 9 6 1 5 7 6 2 4 5 6 3 3 3 6 4 2 1 利用瞪眼法观察得知,
j = 2 * (line - i) - 3
,所以区间的右端点就确定了,表示j
变化范围的区间也就确定了,即:(0,2 * (line - i) - 3]
,这里面含有变量line
,这使得我们通过修改line
就能控制j
的变化区间,这与我们希望的由变量line
来控制整个菱形的打印过程是相符合的。把它改写成循环控制语句如下所示:
int j = 2 * (line - i) - 3; j > 0; j--
用该语句替换掉代码中的“j
控制循环次数为从9到1的奇数”这句话。
至此,所有的循环控制语句都被我们从文字替换成了c语言语句,替换完后长这样:
for (int i = 0; i < line - 1; i++ ) { //最外层循环控制行数从0到5
for (int j = 0; j < i + 1; j++) { //嵌套第一个for循环,通过j控制空格个数,把每一行的空格打印出来
printf(" ");
}
for (int j = 2 * (line - i) - 3; j > 0; j--) { //再并列嵌套一个for循环,通过j控制*的个数,把每一行的*打印出来
printf("*");
}
printf("\n"); //打印完空格和*之后不要忘了打印一个换行符表示这行结束了
}
这就是打印下半部分三角形的核心代码,我们将这段代码加到打印上半三角形的后面,就得到了完整的代码:
void PrintRhombus() {
int line;
printf("请输入菱形的上半行数:\n");
scanf_s("%d", &line);
//打印上半部分三角形
for (int i = 0; i < line; i++) {
for (int j = 0; j < line - i - 1; j++) { //控制打印空格的个数
printf(" ");
}
for (int j = 0; j < 2 * i + 1; j++) { //控制打印*的个数
printf("*");
}
printf("\n"); //不要忘了每行结束换行
}
//打印下半部分三角形
for (int i = 0; i < line - 1; i++) {
for (int j = 0; j < i + 1; j++) { //控制打印空格的个数
printf(" ");
}
for (int j = 2 * (line - i) - 3; j > 0; j--) { //控制打印*的个数
printf("*");
}
printf("\n"); //不要忘了每行结束换行
}
}
int main() {
setbuf(stdout, NULL); //使用clion运行需要加上这一行
PrintRhombus;
return 0;
}
至此,思路一的代码就结束了,接下来分析思路二如何用代码实现:
4.2 分析思路二的代码实现
思路二最外层只使用一个for循环来打印菱形,先用汉字来写出程序的大概结构:
仍然设菱形上半部分的行数为line
for ( i 控制循环次数为菱形的总行数 ) {
如果行数小于等于line:
//打印上半三角形
for( j 控制打印空格){
};
for( j 控制打印* ){
}
换行;
如果行数大于line:
//打印下半三角形
for( j 控制打印空格){
};
for( j 控制打印* ){
}
换行;
}
首先来分析菱形有几行,不难发现,菱形的总行数是2 * line - 1
,因此控制行数的i
的变化范围的区间长度应该是2*line - 1
,采用递增写法则为:[0,2 * line - 1)
,用c语言表示如下:
int i = 0; i < 2 * line - 1; i++
打印上半部分三角形的代码与思路一打印上半部分三角形的代码完全一样,这是因为:
控制打印上半三角形的j
只与line
和i
有关,而line
没有变,i
也和之前一样从0开始每次加一,这就使得j
的取值范围变化区间与之前完全就一样,把之前打印上半部分的代码抄过来填进去即可
for ( i 控制循环次数为菱形的总行数 ) {
如果行数小于等于line
//打印上半三角形
for (int j = line - i - 1; j > 0;j--) { //嵌套一个for循环,通过j控制空格个数,把每一行的空格打印出来
printf(" ");
}
for (int j = 0; j < 2 * i + 1; j++) { //再并列嵌套一个for循环,通过j控制*的个数,把每一行的*打印出来
printf("*")
}
printf("\n");
如果行数大于line
//打印下半三角形
for( j 控制打印空格){
};
for( j 控制打印* ){
}
换行;
}
变化发生在打印下半部分三角形:
控制打印下半三角形的j
只与line
和i
有关,而line
没有变,但i
此时已经不是从0开始每次加一,而是从i = line
,开始加一,这就使得j
的取值范围变化区间发生了变化,我们要重新改写。
同样的,仅靠空想难以得出此时j
与i
和line
的关系,还是通过列表来分析:
![下半部分](https://img-blog.csdnimg.cn/img_convert/3a41cd040faf4693495e248590d6663d.png)
注意此时i
的值从line
开始变化:
先分析第一个j
与i
的关系
控制行数的i | 空格个数的j |
---|---|
line | 1 |
line + 1 | 2 |
line + 2 | 3 |
line + 3 | 4 |
line + 4 | 5 |
不难发现,j = i - line + 1
,因此控制语句可以这么写:int j = 0; j < i - line + 1; j++
再分析第二个j与i的关系
控制行数的i | 控制* 个数的j |
---|---|
line | 9 |
line + 1 | 7 |
line + 2 | 5 |
line + 3 | 3 |
line + 4 | 1 |
此时i
递增,j
递减,因此想到尝试用类似line - i
的形式来表示j
,首先来分析line - i
与j
的关系:
line - i | j |
---|---|
0 | 9 |
-1 | 7 |
-2 | 5 |
-3 | 3 |
-4 | 1 |
还是很难看出line- i
与j
有什么关系,这时可以想到尝试让line - 1
加上一个数之后变成正数,为了不破坏j
只与line
和i
的值有关这个原则,不能随意加一个无意义的正数,可以选择加上一个line
,注意line
是菱形上半三角形的行数,在示例中line = 6
,加完之后再列表:line - i + line
也就是 2 * line - i
2 * line - i | j |
---|---|
6 | 9 |
5 | 7 |
4 | 5 |
3 | 3 |
2 | 1 |
至此,两者的关系就更加明了,即 j = 2 * (2 * line - i) - 3
,因此控制语句可以这么写:
int j = 2 * (2 * line - i) - 3; j > 0; j--
用这两个j有关的控制语句替换掉原来的语句,就得到了打印下半部分三角形的代码:
for (int j = 0; j < i - line + 1; j++) { //控制打印空格的个数
printf(" ");
}
for (int j = 2 * (2 * line - i) - 3; j > 0; j--) { //控制打印*的个数
printf("*");
}
printf("\n");
把它填在一开始的文字描述部分:
for ( i 控制循环次数为菱形的总行数 ) {
如果行数小于等于line
//打印上半三角形
for (int j = line - i - 1; j > 0;j--) { //嵌套一个for循环,通过j控制空格个数,把每一行的空格打印出来
printf(" ");
}
for (int j = 0; j < 2 * i + 1; j++) { //再并列嵌套一个for循环,通过j控制*的个数,把每一行的*打印出来
printf("*")
}
printf("\n");
如果行数大于line
//打印下半三角形
for (int j = 0; j < i - line + 1; j++) { //控制打印空格的个数
printf(" ");
}
for (int j = 2 * (2 * line - i) - 3; j > 0; j--) { //控制打印*的个数
printf("*");
}
printf("\n");
}
这样控制打印菱形的主体部分就完成了,加上变量声明和必要的格式代码,就有了思路二的完整代码:
#include<stdio.h>
void PrintRhombus1() {
int line;
printf("请输入菱形的上半行数:\n");
scanf_s("%d", &line);
for (int i = 0; i < 2 * line - 1; i++) {
if (i < line) {
//打印上半部分三角形
for (int j = 0; j < line - i - 1; j++) { //打印空格
printf(" ");
}
for (int j = 0; j < 2 * i + 1; j++) { //打印*
printf("*");
}
printf("\n"); //不要忘了每行结束换行
} else {
//打印下半部分三角形
for (int j = 0; j < i - line + 1; j++) { //打印空格
printf(" ");
}
for (int j = 2 * (2 * line - i) - 3; j > 0; j--) { //打印*
printf("*");
}
printf("\n"); //不要忘了每行结束换行
}
}
}
int main() {
//setbuf(stdout, NULL); //使用clion运行代码时要加这一行
PrintRhombus();
return 0;
}
5.总结
双重for循环的嵌套初学时可能难以理解,打印菱形是一道经典的练习题来帮助理解双重循环
重要的是明白每层循环在干什么,在本题中,首先要明白外层循环控制行数,内层循环控制要打印的空格和*的个数
然后用数学表达式来表示循环变量是如何变化的,用一句话概括就是:外层循环次数固定,因此我们引入了一个固定值line
来表示外层循环变量i
的变化过程;内层循环次数j
随i
在变化,因此我们尝试用含有i
或line
的表达式来表示j
的变化过程。
思路有了,剩下的就是求解这个含有i和line的表达式,本文使用大量的列表来辅助我们求解这个表达式,列出表格能更直观的展示数据之间的关系,简化思考过程。