©一颗斯特拉
【注】
1.标有❤️的是值得多做的题目。
2.标有II、III的是二刷、三刷的题目。
- 题目来源于C语言经典例题(菜鸟教程100例)
——2.19更新——
实例21:【循环】❤️II
题目:猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘了多少。
01程序分析:
第一次:采用逆向思维,第10天想吃的时候只剩下1个是定量。
02Bad Solution:
03Correct Solution:
【第一种】
#include<stdio.h>
int main() {
int i,x;
x=1;
for(i=9;i>=1;i--)
{
x=(x+1)*2;
}
printf("第一天共摘了多少: %d",x);
return 0;
}
【第二种】
#include<stdio.h>
int main() {
int day,x1,x2;
day=9;
x2=1;
while(day>0)
{
x1=(x2+1)*2;
x2=x1;
day--;
}
printf("第一天共摘了多少: %d",x1);
return 0;
}
【运行结果】
第一天共摘了多少: 1534
【第三种】
#include<stdio.h>
//函数返回第n天的桃子数
int num(int n){
if(n==10)
return 1;
else
return (num(n+1)+1)*2;
}
int main() {
int i;
printf("第一天共摘了多少: %d",num(1));
return 0;
}
04题目总结:
暂无
实例22:【乘法表】
使用嵌套 for 循环输出九九乘法口诀表。
【注】详见实例8
实例23:【双重循环➕打印图案】
题目:打印出如下图案(菱形)。
01程序分析:
暂无
02Bad Solution:
暂无
03Correct Solution:
#include <stdio.h>
int main()
{
int i,j,k;
for(i=0;i<=3;i++) {//一般从0下标开始
for(j=0;j<=2-i;j++) {
printf(" ");
}
for(k=0;k<=2*i;k++) {
printf("*");
}
printf("\n");
}
for(i=0;i<=2;i++) {
for(j=0;j<=i;j++) {
printf(" ");
}
for(k=0;k<=4-2*i;k++) {
printf("*");
}
printf("\n");
}
return 0;
04题目总结:
暂无
——2.21更新——
实例24:【数列求和】❤️II
题目:有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13...求出这个数列的前20项之和。
01程序分析:
第一次:先考虑如何输出这个数列,可以看到分子是前一项的分子与分母之和,分母是前一项的分子,这样必须用到一个临时存储变量。再考虑前20项怎么抓出来,应该可以用一个计数器。
02Bad Solution:
暂无
03Correct Solution:
#include<stdio.h>
int main()
{
float sum=0,i=1,j=2;
int count,temp;
for(count=1;count<=20;count++)
{
sum=sum+j/i;
temp=j;//需要开辟一个临时变量
j=i+j;
i=temp;
}
printf("前20项的和是: %f ",sum);
}
【运行结果】
前20项的和是: 32.660263
04题目总结:
1.注意除法问题。如果两个变量都是整型,则相除后得到的数也是整形。
2.浮点数
实例25:【阶乘求和】❤️
题目:求1+2!+3!+...+20!的和。
01程序分析:
第一次:和上一道题差不多,只是把累加变成了累乘。
02Bad Solution:
#include<stdio.h>
int main()
{
int i,a=1,sum=0;
for(i=1;i<=20;i++)
{
a=i*a;
sum=a+sum;
}
printf("和是: %d ",sum);
}
【运行结果】
和是: 268040729
【错误】
1.结果和每项设置为int
类型不能正确的显示结果。超出了范围。
03Correct Solution:
#include<stdio.h>
int main()
{
int i;
long double a=1,sum=0;
for(i=1;i<=20;i++)
{
a=a*i;
sum=sum+a;
}
printf("和是: %Lf ",sum);
}
【运行结果】
和是: 2561327494111820313.000000
04题目总结:
1.区分三种浮点数:
float
、 double
、long double
2.一字节表示八位,即:1byte = 8 bit;
int: 4byte = 32 bit 有符号signed范围:2^31-1 ~ -2^31即:2147483647 ~ -2147483648无符号unsigned范围:2^32-1 ~ 0即:4294967295 ~ 0
long: 4 byte = 32 bit 同int型
double: 8 byte = 64 bit 范围:1.79769e+308 ~ 2.22507e-308
long double: 12 byte = 96 bit 范围: 1.18973e+4932 ~ 3.3621e-4932
float: 4 byte = 32 bit 范围: 3.40282e+038 ~ 1.17549e-038
实例26:【递归】
题目:利用递归方法求5!。
01程序分析:
第一次:要定义一个函数,并且要在这个函数中调用自身。
02Bad Solution:
暂无
03Correct Solution:
#include<stdio.h>
double factorial(unsigned int i)
{
if(i<=1)
{
return 1;
}
return i*factorial(i-1);
}
int main()
{
int i=5;
printf("%d 的阶乘为 %f\n",i,factorial(i));
return 0;
}
【运行结果】
5 的阶乘为 120.000000
04题目总结:
1.递归
- 循环是有去无回,而递归则是有去有回(因为存在终止条件)。
- 递归指的是在函数的定义中使用函数自身的方法。C 语言支持递归,即一个函数可以调用其自身。但在使用递归时,程序员需要注意定义一个从函数退出的条件,否则会进入死循环。
-
递归函数在解决许多数学问题上起了至关重要的作用,比如计算一个数的阶乘、生成斐波那契数列,等等。
实例27:【逆序输出】❤️II
题目:利用递归函数调用方式,将所输入的5个字符,以相反顺序打印出来。
01程序分析:
第一次:
02Bad Solution:
暂无
03Correct Solution:
#include <stdio.h>
void palin(int n)
{
char next;
if(n<=1) {
next=getchar();
printf("相反顺序输出结果\40:\40");
putchar(next);
} else {
next=getchar();
palin(n-1);//到了递归出口返回时才执行下面的语句
putchar(next);
}
}
int main()
{
int i=5;
void palin(int n);
printf("请输入5个字符\40:\40");
palin(i);
printf("\n");
}
【运行结果】
请输入5个字符 : abcde
相反顺序输出结果 : edcba
04题目总结:
暂无
实例28:【递推】❤️
题目:有5个人坐在一起,问第五个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第三个人,又说比第2人大两岁。问第2个人,说比第一个人大两岁。最后问第一个人,他说是10岁。请问第五个人多大?
01程序分析:
第一次:用递推的方法,有两个过程:递推和回推。
02Bad Solution:
暂无
03Correct Solution:
#include<stdio.h>
int year(unsigned int i)
{
if(i==1)
{return 10; }
return year(i-1)+2;
}
int main()
{
printf("第5个人 %d 岁",year(5));
return 0;
}
【运行结果】
第5个人 18 岁
04题目总结:
实例29:【求位数+逆序】❤️
题目:给一个不多于5位的正整数,要求:一、求它是几位数,二、逆序打印出各位数字。
01程序分析:
第一次:递归函数为计位数函数,且可打印。
02Bad Solution:
暂无
03Correct Solution:
#include<stdio.h>
int fun(int para)
{
if(para/10==0){
printf("%d",para);
return 1;
}
printf("%d",para%10);
return fun(para/10)+1;//计一位数
}
int main()
{
int test;
printf("请输入一个不多于5位的正整数:");
scanf("%d",&test);
printf("逆序输出该数:");
int res=fun(test);
printf("\n该数是 %d 位数",res);
return 0;
}
【运行结果】
请输入一个不多于5位的正整数:1212
逆序输出该数:2121
该数是 4 位数
04题目总结:
实例30:【回文数】
题目:一个5位数,判断它是不是回文数。即12321是回文数,个位与万位相同,十位与千位相同。
01程序分析:
第一次:与实例13一样,需要取出每位上的数。只不过,这里是5位数,那里是3位数。
02Bad Solution:
暂无
03Correct Solution:
#include<stdio.h>
int main()
{
int num,n1,n2,n3,n4,n5;
printf("请输入一个五位数:");
scanf("%d",&num);
n5=num/10000;
n4=num%10000/1000;
n3=num%1000/100;
n2=num%100/10;
n1=num%10;
if(n1==n5&&n2==n4)
{
printf("是回文");
}
else{printf("不是回文");}
return 0;
}
【运行结果】
请输入一个五位数:12321
是回文
04题目总结:
1.取每位上的数的操作可以记下来。先取余后整除。
——2.22更新——
实例31:【switch】❤️
题目:请输入星期几的第一个字母来判断一下是星期几,如果第一个字母一样,则继续判断第二个字母。
01程序分析:
第1次:这个问题,用到判断中的switch
语句。需要两次判断的,在对应case
里加if
条件语句。
02Bad Solution:
暂无
03Correct Solution:
#include<stdio.h>
int main() {
char c,d;
printf("请输入第1个字母:");
scanf("%c",&c);
getchar();
switch(c) {
case 'm':
printf("星期一");
break;
case 't':
printf("请输入第2个字母:");
scanf("%c", &d);
if (d == 'u') { printf("星期二"); }
else { printf("星期四"); }
break;
case 'w':
printf("星期三");
break;
case 'f':
printf("星期五");
break;
case 's':
printf("请输入第2个字母:");
scanf("%c", &d);
if (d == 'a') {
printf("星期六");
}
else { printf("星期天"); }
break;
default : printf("错误!没有匹配的结果。");break;
}
return 0;
}
【运行结果】
请输入第1个字母:s
请输入第2个字母:a
星期六
04题目总结:
1.switch
用法总结:
- 当被测试的变量等于 case 中的常量时,case 后跟的语句将被执行,直到遇到 break 语句为止。
- 当遇到 break 语句时,switch 终止,控制流将跳转到 switch 语句后的下一行。
- 不是每一个 case 都需要包含 break。如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。
- 一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。
2.C 库函数 getchar()
int getchar(void)
从标准输入 stdin 获取一个字符(一个无符号字符)。这等同于 getc 带有 stdin 作为参数。
3.scanf()
输入后,会在末尾加一个换行符,所以为避免后面的scanf()
读取换行符,这里需要加一个getchar()
在用 %c 输入时,空格和"转义字符"均作为有效字符。
====考点1:函数====【起】====
下面几个实例都是有关函数的题目。这里先攻克函数中的一个难点,即函数参数中的传值调用和引用调用。
当时学习的时候,我还是经历了一番波折才弄懂了这两者的差别。明白了这个问题,你对内存地址的理解就很透彻了。
下面我们通过一个例子看一下这两种调用的区别。
题目:编写一个函数,交换两整数的值。
1.传值调用
#include<stdio.h>
void swap(int num1,int num2);
int main(){
int a,b;
printf("请输入a,b的值:");
scanf("%d%d",&a,&b);
printf("交换前,a的值为: %d\n",a);
printf("交换前,b的值为: %d\n",b);
swap(a,b);
printf("交换后,a的值为: %d\n",a);
printf("交换后,b的值为: %d\n",b);
return 0;
}
void swap(int num1,int num2){
int temp;
temp=num2;
num2=num1;
num1=temp;
}
【运行结果】
请输入a,b的值:1 2
交换前,a的值为: 1
交换前,b的值为: 2
交换后,a的值为: 1
交换后,b的值为: 2
【注】我们可以看到,尽管调用了交换函数swap,但是a,b的值并未改变。这是因为传值调用是把参数的实际值复制给函数的形式参数,在这种情况下,修改函数内的形式参数不会影响实际参数。
2.引用调用
#include<stdio.h>
void swap(int *num1,int *num2);
int main(){
int a,b;
printf("请输入a,b的值:");
scanf("%d%d",&a,&b);
printf("交换前,a的值为: %d\n",a);
printf("交换前,b的值为: %d\n",b);
swap(&a,&b);
printf("交换后,a的值为: %d\n",a);
printf("交换后,b的值为: %d\n",b);
return 0;
}
void swap(int *num1,int *num2){
int temp;
temp=*num2;
*num2=*num1;
*num1=temp;
}
【运行结果】
请输入a,b的值:1 2
交换前,a的值为: 1
交换前,b的值为: 2
交换后,a的值为: 2
交换后,b的值为: 1
【注】通过引用传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。
指针我们之后会讲到,这里特别注意*
的作用:
- 用来指定一个变量是指针
- 一元运算符
*
来返回位于操作数所指定地址的变量的值
实例32:【字符串中删除指定字母】❤️
题目:删除一个字符串中的指定字母,如:字符串 "aca",删除其中的 a 字母。
01程序分析:
第1次:
02Bad Solution:
#include<stdio.h>
#include<string.h>
int main()
{
char ch[100],a;
int i,j;
printf("请输入字符串:");
gets(ch);
printf("请输入要删除的字符:");
scanf("%c",&a);
for(i=0;i<strlen(ch);i++){
if(ch[i]==a){
for(j=i;j<strlen(ch);j++){
ch[j]=ch[j+1];
}
}
}
printf("删除 %c 的字符串为:",a);
puts(ch);
return 0;
}
【运行结果】
abax
请输入要删除的字符:a
删除 a 的字符串为:bx
03Correct Solution:
#include<stdio.h>
#include<string.h>
char * deleteCharacters(char *str,char charSet);
int main()
{
char a;//要删除的字母
char ch[100];//要删除的字符串
printf("请输入字符串:");
gets(ch);
printf("请输入要删除的字符:");
scanf("%c",&a);
printf("%s\n",deleteCharacters(ch,a));
return 0;
}
char *deleteCharacters(char *str,char charSet) {
if (charSet==NULL)
return str;
else if(charSet!=NULL) {
for (int i = 0; i < strlen(str); i++) {
if (str[i] == charSet) {
for (int j = i; j < strlen(str); j++)//将后面元素往前移
str[j] = str[j + 1];
}
}
return str;
}
}
【运行结果】
请输入字符串:warning: this program uses gets(), which is unsafe.
abc
请输入要删除的字符:a
bc
04题目总结:
1.在 C 语言中,字符串实际上是使用 null 字符 '\0' 终止的一维字符数组。C 编译器会在初始化数组时,自动把 '\0' 放在字符串的末尾。
2.size_t strlen(const char *str)
计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
3.char *gets(char *str)
从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
实例33:【质数】
题目:判断一个数字是否为质数。
01程序分析:
第1次:写一个判断是否是质数的函数。
02Bad Solution:
暂无
03Correct Solution:
#include<stdio.h>
int isPrime(int n)
{
if(n<=1) return 0;
for(int i=2;i<n;i++)
if(n%i==0) return 0;
return 1;
}
int main()//1表示是质数,0表示不是质数
{
printf("%d 是否质数 %d\n",1,isPrime(1));
printf("%d 是否质数 %d\n",10,isPrime(10));
printf("%d 是否质数 %d\n",19,isPrime(19));
}
【运行结果】
1 是否质数 0
10 是否质数 0
19 是否质数 1
04题目总结:
1.一个函数可返回一个值,所以当调用函数时,执行到某一返回语句后,即返回主函数。
实例35:【字符串反转】❤️
题目:字符串反转,如将字符串 "www.runoob.com" 反转为 "moc.boonur.www"。
01程序分析:
第一次:编写一个反转函数。
02Bad Solution:
暂无
03Correct Solution:
【运行结果】
04题目总结:
1.将这道题与实例32放在一起对比。
====考点1:函数====【止】====
实例36:【素数】
题目:求100之内的素数。
01程序分析:
02Bad Solution:
#include<stdio.h>
int main()
{
int tag,n=0;
for(int i=2;i<=100;i++) {
for (int j = 2; j <= i; j++) {
tag = j;
if (i % j == 0) break;
}
if (tag == i)
{
printf(" %-3d ",i);
n++;
if(n%5==0) printf("\n");
}
}
return 0;
【运行结果】
【缺陷】循环的次数多了,运行慢,可利用数学基础数论知识缩减循环次数。
03Correct Solution:
#include<stdio.h>
#include<math.h>
int main() {
int i,j,k,n = 0;
for (i = 2; i <= 100; i++) {
k=(int)sqrt(i);
for (j = 2; j <= k; j++)
if (i % j == 0) break;
if (j>k) {
printf(" %-3d ", i);
n++;
if (n % 5 == 0) printf("\n");
}
}
return 0;
}
【运行结果】
同“结果02”
04题目总结:
- 质数(prime number)又称素数,有无限个。一个大于1的自然数,除了1和它本身外,不能被其他自然数整除。
2.在for()
里定义的是局部变量,只在for
循环里面起作用。
——2.25更新——
实例37:【排序算法】❤️
题目:对10个数进行排序。
01知识储备:
第一次:
02Bad Solution:
#include<stdio.h>
#define N 10
int main() {
int i, j, a[N], min, temp;
printf("请输入10位数:");
for (i = 0; i < N; i++)
scanf("%d", &a[i]);
for (i = 0; i < N - 1; i++) {
min = i;
for (j = i + 1; j < N; j++) {
if (a[min] > a[j]) {
temp = a[min];
a[min] = a[j];
a[j] = temp;
}
}
}
printf("交换后的10位数为:");
for (i = 0; i < N; i++)
printf("%d ", a[i]);
return 0;
}
【运行结果】
请输入10位数:11 22 9 8 7 6 5 4 3 2
交换后的10位数为:2 3 4 5 6 7 8 9 11 22
【缺陷】
这种方法运行起来更复杂,交换次数更多。会频繁交换元素,只是看起来代码少一点。
03Correct Solution:
#include<stdio.h>
#define N 10
int main()
{
int i,j,a[N],temp;
printf("请输入10个数字:\n");
for(i=0;i<N;i++)
scanf("%d",&a[i]);
for(i=0;i<N-1;i++)
{
int min=i;
for(j=i+1;j<N;j++)
if(a[min]>a[j]) min=j;
if(min!=i)
{
temp=a[min];
a[min]=a[i];
a[i]=temp;
}
}
printf("排序结果是:\n");
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
【结果对比】
虽然02、03得到的结果是一样的,但是03在排序中,交换的次数更少。比如将4、3、2、1按从小打大的顺序排列。03是先找到最小那个,把它排到最前面,而02是找到比当前位置小的就交换,当然交换的次数多得多得多啦,这对电脑伤害很大。
04题目总结:
暂无
实例38:【二维数组】
题目:求一个3*3矩阵对角线元素之和
01知识储备:
二维数组
02Bad Solution:
#include<stdio.h>
int main() {
int a[3][3]={1,2,3,4,5,6,7,8,9},i,j,sum=0;
for(i=0;i<3;i++)
for(j=0;j<3;j++)
if(i==j) sum=sum+a[i][j];
printf("对角元素的和是: %d",sum);
return 0;
}
【运行结果】
对角元素的和是: 15
【缺陷】
1.算对角线元素之和可以不用嵌套循环。
03Correct Solution:
#include<stdio.h>
int main() {
int a[3][3],i,j,sum=0;
printf("请输入3*3矩阵:\n");
for(i=0;i<3;i++)
for(j=0;j<3;j++)
scanf("%d",&a[i][j]);
for(i=0;i<3;i++)
sum+=a[i][i];
printf("对角元素的和是: %d",sum);
return 0;
}
【运行结果】
请输入3*3矩阵:
1 2 3 4 5 6 7 8 9
对角元素的和是: 15
04题目总结:
暂无
实例39:【插入】❤️
题目:有一个已经排好序的数组。现输入一个数,要求按原来的规律将它插入数组中。
01程序分析:
第一次:如果比最后一个数字大,则放在最后。否则,看比哪个数字小,将大的那部分全体向后移动。
02Bad Solution:
#include<stdio.h>
int main() {
int a[6]={1,3,4,7,8},num,i,j,temp1,temp2;
printf("输入要插入的数字:");
scanf("%d",&num);
if(num>a[4]) {
a[5] = num;
}
else{
for(i=0;i<5;i++)
{
if(num<a[i]){
for(j=5;j<i;j--)
a[j]=a[j-1];
}
}
a[i]=num;
}
printf("插入后的数组为:");
for(i=0;i<6;i++)
printf("%d ",a[i]);
}
【运行结果】
输入要插入的数字:5
插入后的数组为:1 3 4 7 8 5
【错因】
没有起到插入作用。Why?
仔细分析下这个程序,发现有以下错误:
①for(j=5;j<i;j--)
中的循环条件错了,应该为for(j=5;j>i;j--)
。
② a[i]=num;
这个应该在if条件句里面,将后面的数移动了后立即插入。
03Correct Solution:
①从后往前移
#include<stdio.h>
int main() {
int a[6]={1,3,4,7,8},num,i,j,temp1,temp2;
printf("输入要插入的数字:");
scanf("%d",&num);
if(num>a[4]) {
a[5] = num;
}
else{
for(i=0;i<5;i++)
{
if(num<a[i]){
for(j=5;j>i;j--)
a[j]=a[j-1];
a[i]=num;
break;
}
}
}
printf("插入后的数组为:");
for(i=0;i<6;i++)
printf("%d ",a[i]);
}
【运行结果】
输入要插入的数字:2
插入后的数组为:1 2 3 4 7 8
②从前往后移
#include<stdio.h>
int main()
{
int a[11]={1,4,6,9,13,16,19,28,40,100};
int temp1,temp2,number,end,i,j;
printf("原始数组是:\n");
for(i=0;i<10;i++)
printf("%4d",a[i]);
printf("\n插入一个新的数字: ");
scanf("%d",&number);
end=a[9];
if(number>end)
a[10]=number;
else
{
for(i=0;i<10;i++)
{
if(a[i]>number)
{
temp1=a[i];
a[i]=number;
for(j=i+1;j<11;j++)
{
temp2=a[j];
a[j]=temp1;
temp1=temp2;
}
break;
}
}
}
for(i=0;i<11;i++)
printf("%4d",a[i]);
printf("\n");
return 0;
}
04题目总结:
1.数组的长度是在定义的时候确定的,不同编译器对于数组越界的处理也是不一样的,一般而言数组的长度是不可以改变的。
- 有一些编译器对于数组越界,是不处理,继续放后面。但是会对后面的数据进行改变。
- 还有一些编译器就是程序遇到未知错误而终止。但不管是哪一种,都需要注意数组的下标是不是越界。
实例40:【数组逆序】
题目:将一个数组逆序输出(要求逆序,不能直接从后往前输出)。
01程序分析:
第一次:将数组的第一个数与最后一个数交换,偶数个数的数列和奇数个数的数列处理方法相同。
02Bad Solution:
暂无
03Correct Solution:
#include<stdio.h>
#define N 10
int main()
{
int a[N]={1,2,3,4,5,6,7,8,9,10},i,temp;
for(i=0;i<N/2;i++) {
temp = a[i];
a[i] = a[N-i-1];
a[N-i-1] = temp;
}
printf("逆转后的序列为:");
for(i=0;i<N;i++)
printf("%d ",a[i]);
return 0;
}
【运行结果】
逆转后的序列为:10 9 8 7 6 5 4 3 2 1
04题目总结:
暂无