语句结构
C语言是一门结构化的程序设计语言。
C语言的语句结构,从执行流程的角度可以划分出三种基本结构:顺序结构、分支(选择)结构、循环结构。
C语言中由 ; 隔开的就是一条语句。
int main()
{
int a = 0; // 语句1
int b = 1; // 语句2
; // 空语句,也是语句
return 0; // 语句3
}
顺序结构
顺序结构就是从上往下的顺序依次执行语句的结构,是C语言最基本的结构。
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 0;
c = a;
a = b;
b = c;
printf("a=%d b=%d\n",a,b );
return 0;
}
分支(选择)结构
为了完成不同的任务,我们通常需要给一段执行代码前加上判断条件,来决定是否执行相应的代码,这种结构就成为分支(选择)结构。
if 语句
if语句分为: if、if else 、if else if … else;
if
int main(){
if(表达式) // 如果表达式为真,执行语句1
{
执行语句1;
}
// 表达式为假,继续执行下面语句
return 0;
}
举例
int main()
{
int age = 10;
if(age<18)
{
printf("未成年");
}
// 单条执行语句可以省略代码块,个人习惯使用代码块,结构清晰。
}
#include <stdio.h>
int main()
{
int age = 18;
if(age<18)
{
printf("未成年");
}
}
if else
int main()
{
if(表达式) // 如果表达式为真,执行语句1
{
执行语句1;
}
else { // 否则,执行语句2
执行语句2;
}
return 0;
}
举例
#include <stdio.h>
int main()
{
int age = 10;
if(age<18)
{
printf("未成年\n");
}
else
{
printf("成年了\n");
}
}
if else if … else
// 只能有一个语句块被执行,按顺序从上往下判断
int main()
{
if(expr1) // if expr1 is true, execute statement 1;
{
statement1;
} else if(expr2) // if expr2 is true, execute statement 2;
{
statement2;
} else if(expr3) // if expr3 is true, execute statement 3;
{
statement3;
} ... // ...
else // else ,execute statment4
{
statment4;
}
return 0;
}
举例
#include <stdio.h>
int main()
{
int age = 10;
if(age<18)
{
printf("未成年\n");
}
else if((age>=18)&&(age<28))
{
printf("青年\n");
}
// 注意:(18<=age<28) 这种写法是错误的,系统会先判断18<=age,再将判断返回的真值与28比较,为真时返回1<28,为假时返回0<28,这种判断条件永真。
else if((age>=28)&&(age<60))
{
printf("壮年\n");
}
else if((age>=60)&&(age<150))
{
printf("老年\n");
}
else
{
printf("respect\n");
}
return 0;
}
// 太多了,不截屏了
悬空else 问题
// 下面代码的输出结果?
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 0)
if (b == 2)
printf("haha\n");
else
printf("hehe\n");
return 0;
}
因此建议使用代码块{}将执行语句包裹,会使得程序的结构化更加清晰,编译器也能编译出我们想要的效果。
if 书写形式对比
例一
下面代码意思是否一样?
if(expr)
{
return x;
}
return z;
if(expr)
{
return x;
}
else
{
return z;
}
上述代码的意思是一样的,return x 会直接跳出当前函数,不会执行return 后面的语句。
因此为了不产生歧义,使用第二种书写方式的代码风格是更加清晰的。
例二
下面代码的输出结果为?
int main()
{
int a = 0;
if(a = 1)
{
printf("1\n");
}
else
{
printf("0\n");
}
return 0;
}
输出结果为1, 因为 = 是赋值操作符,等于给a重新赋值为1,条件永真。
为了避免这类错误,可以将常量放在 == 右侧,变量放在 == 左侧
int main()
{
int a = 0;
if(1 == 0) // 这样书写即使少写一个 = 代码也会报错
{
printf("1\n");
}
else
{
printf("0\n");
}
return 0;
}
练习
判断一个数是否为奇数
先分析:
- 奇数不能被2整除的整数,假设奇数x,则x%2=1;
#include <stdio.h>
int main()
{
int num = 0;
printf("请输入一个整数>:");
scanf("%d", &num);
if (1 == num % 2)
{
printf("%d is a odd number\n", num);
}
else
{
printf("%d is a even number\n", num);
}
return 0;
}
输出1~100之间的奇数
分析:
-
奇数是不能被2整除的整数,假设奇数x,则x%2=1;
-
偶数一定是2的倍数,1一定是奇数,因此从1开始查找每次+2,跳过所有偶数。
-
利用循环遍历1~100的数。
#include <stdio.h>
int main()
{
for (int i = 1; i <= 100; i += 2)
{
if (i % 2 == 1)
{
printf("%d ", i);
}
}
return 0;
}
switch语句
switch语句常用于多分支情况。
语法
switch(int_expr)
{
case const_expr1:
statement1;
case const_expr2:
statement2;
break;
case const_expr3:
statement3;
...
default :
statment4;
break;
}
-
switch语句中的表达式(int_expr)必须是整型表达式。
-
case 后面的表达式(const_expr)必须是整型常量表达式。
-
int_expr 会按照顺序结构从上往下依次对比 const_expr,哪个相等就从哪个case开始执行。
-
当执行完毕后,如果case里的执行语句含有break; 则跳出当前switch语句,如果没有break;则继续执行下面的case。
-
当所有expr与所有case的 const_expr 都不相等,如果有 default 默认执行 default 里面的语句。如果没有则结束switch语句。
-
default 的位置不影响switch语句的执行顺序。
-
分支语句中不能使用continue;
举例
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
switch (a)
{
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期天\n");
break;
default:
printf("输入值不正确\n");
break;
}
return 0;
}
如果case中没有break; 执行继续执行当前case之后的语句,直到遇到break; 或者switch语句全部执行完毕。
// 例如将说有break;删除,并输入1,查看最终的输出结果
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
switch (a)
{
case 1:
printf("星期一\n");
case 2:
printf("星期二\n");
case 3:
printf("星期三\n");
case 4:
printf("星期四\n");
case 5:
printf("星期五\n");
case 6:
printf("星期六\n");
case 7:
printf("星期天\n");
default:
printf("输入值不正确\n");
}
return 0;
}
结果如下:
练习
下面代码的输出结果为?
#include <stdio.h>
int main()
{
int n = 1;
int m = 2;
switch(n)
{
case 1: m++;
case 2: n++;
case 3:
// switch语句可以嵌套使用
switch(n)
{
case 1 : n++;
case 2 :
m++;
n++;
break;
}
case 4:
m++;
break;
default:
break;
}
printf("m = %d, n = %d\n", m,n);
return 0;
}
结果为: m = 5, n = 3
代码分析
int main()
{
int n = 1;
int m = 2;
switch(n)
{
case 1: m++; // n = 1首先进入 case 1:m++; m = 2 + 1 = 3
case 2: n++; // 接着 n++; n = 1 + 1 = 2
case 3: // 接着 进入 case 3:
switch(n)
{
case 1 : n++;
case 2 : // n = 2 进入 case 2:
m++; // m = 3 + 1 = 4
n++; // n = 2 + 1 = 3
break; // 跳出当前的这个嵌套的switch
}
case 4: // 继续执行 case 4:
m++; // m = 4 + 1 = 5
break; // 跳出当前switch循环
default:
break;
}
printf("m = %d, n = %d\n", m,n); // m = 5 n = 3
return 0;
}
循环结构
- 循环结构(语句),有规律的执行重复的操作,被重复执行的语句称为循环体。
- 循环语句中的判断表达式用于判断循环的终止条件。
- 条件为真继续循环,条件为假终止循环。
- 循环语句分为3种:while、for、do while。
while循环
先判断,当表达式为真进入循环,为假终止循环。
注意切勿使while循环的判断表达式永真,会成为死循环。
语法
while(expr)
{ // 循环体
statement;
}
举例
打印1~10
分析:
#include <stdio.h>
int main()
{
int i = 1;
while(i<=10) // 当i>10时 条件式为假,结束循环
{
printf("%d ", i);
i++;
}
return 0;
}
break和continue
break 终止整个循环
// 假如你买了5个包子,当吃到第三个时,你开始拉肚子...
int main()
{
int i = 1;
while (i <= 5)
{
printf("正在吃第%d包子\n", i);
if (3 == i)
{
printf("拉肚子了,上医院\n");
break;
}
i++;
}
return 0;
}
continue 跳出本次循环,后面的代码本次不再不执行,继续下次循环
// 假如你买了5个包子,当吃到第三个时,吃到一个虫子...
int main()
{
int i = 1;
while (i <= 5)
{
printf("正在吃第%d包子\n", i);
if (3 == i)
{
printf("有虫子,丢掉,接着吃\n");
i++;
continue;
}
i++;
}
return 0;
}
注意
continue 后面如果有对条件表达式中变量的操作,例如 ++ – 等,很容易造成死循环。
// 例如上面的代码
int main()
{
int i = 1;
while (i <= 5)
{
printf("正在吃第%d包子\n", i);
if (3 == i)
{
printf("有虫子,丢掉,接着吃\n");
// i++; 这里如果不写i++,就变成死循环
// i == 3时 跳出本次循环,i没有+1,i == 3,继续进入if语句,一直重复就变成死循环了
continue;
}
i++;
}
return 0;
}
while循环的一些实际应用
下面代码的意思是?
#include <stdio.h>
int main()
{
int ch = 0;
while((ch= getchar()) != EOF) // getchar 接收一个字符,当 ch == EOF时,终止循环。
putchar(ch); // putchar 输出一个字符
return 0;
}
这段代码的意思是当 ch != EOF时一直进行循环, 当getchar()读取到文件末尾或者是读取错误时,返回EOF,此时退出循环。
关于介绍getchar(void)、putchar(int_char)和 EOF。
getchar(void)
从标准输中接收一个无符号字符,并且一次只能接收一个字符。
返回值:
- 成功键入则返回这个键入的字符的ASCII,类型为int。
- 读取错误或者到达文件末尾则返回EOF(-1)。
- ctrl+z 在DOS中被解释为文档结束。
putchar(int_char)
输出一个字符到标准输出,输出的字符会被转为unsigned char。
返回值
- 输出成功则返回输出的字符的ASCII码值。
- 输出错误则返回EOF。
EOF
-
End of file 文件结束标志,是在stdio.h文件中被#define定义的宏常量。
-
EOF == -1,在<stdio.h> 文件中可以看到EOF被定义为-1。
-
被用于一些在头文件里的函数返回的值,标志着文件的结束或者其他一些错误的情况。
-
也被用于代表一个无效字符的值。
举例解释
int main()
{
char ch = getchar(); // 接收为字符型
printf("%d\n", ch); // 输出ASCII值
putchar(ch); // 输出为字符型
return 0;
}
- 当在键盘键入5时,getchar() 接收到的输入实际上是字符’5’。
- 而接收这个输入的变量 ch 也为字符型变量,因此直接将’5’存入变量中。
- 在打印时可以看到printf()是以十进制格式打印ch里面的值,打印结果为是字符‘5’所对应的ASCII码值。
- getchar()输出的的依然是字符型‘5‘。
int main()
{
int ch = 0;
int count = 1;
while ((ch = getchar()) != EOF)
{
printf("\n%d.--------------\n", count);
printf("ch接收到字符所对应的ASCII值%d\n", ch);
putchar(ch); // 输出为字符型 9
count++;
}
return 0;
}
-
这次使用整型类型的变量int来接收getchar()的值,getchar()接收到的值都为字符型,而ch为整型,因此ch实际接收到的值为输入字符所对应的ASCII码值。
-
并且,因为getchar()一次只能接收一个字符,所以在while的循环里会将getchar()所接收到的每一个字符和最后确认时键入回车存入的**\n**的值赋给ch,每次都会把ch之前的值给替换掉。
int main()
{
printf("%c\n",getchar()); // 返回键入的字符
return 0;
}
int main()
{
printf("%d\n",getchar()); // 返回键入的字符对应的ASCII值
return 0;
}
下面代码的意思是?
int main()
{
int ch = 0
while((ch = getchar()) != EOF)
{
if((ch < '0') || (ch>'9'))
{
continue;
}
putchar(ch); // 只打印0~9的字符
}
return 0;
}
for循环
for循环的优势
通过while循环的学习,不难发现,while循环经常配合循环变量、判断表达式和对循环变量的调整来完成一个while循环的基本操作。
//while循环存在的问题
int main()
{
int i = 0; // 循环变量初始化
while (i < 10) // 判断表达式
{
i++; // 循环变量的调整
}
return 0;
}
但是,三个部分的布局比较分散,特别实在代码量非常多时,如果想要对这三个部分进行修改,很容易出现错误。
而for循环是将这三个部分布局在一个括号()内,对其进行修改是不容易犯错,结构也更加清晰。
语法
for (初始化表达式;判断表达式;调整表达式)
// 只有第一次进入for循环时会执行初始化表达式
// 之后的循环都是对循环变量的调整和调整后对表达式的判断。
// 最后一个表达式不用加;
{
循环体;
}
初始化表达式用于初始化循环变量,判断表达式用于作为for循环的判断条件,调整表达式用于调整循环变量的值。
举例
打印1~10
#include <stdio.h>
int main()
{
int i = 0;
// 初始化; 判断; 调整
for( i = 1; i<=10; i++ )
{
printf("%d ", i);
}
return 0;
}
执行流程:
-
第一次进入for循环 执行i = 1,并判断i<=10是否为真,为真进入循环执行循环体。
-
循环体执行完毕后,来到调整表达式对i++。
-
然后继续判断i<=10是否为真,为真进入循环体。
-
…不断循环
-
直到 i <= 10 为假,结束循环。
break和continue
break在for和while中的作用一样都是终止整个循环。
coutinue在for和while中使用有些差异:
- while循环中如果调整表达式写在continue后面,那么这个调整表达式将不会执行,很容易造成死循环。
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
if (5 == i)
{
continue;
}
printf("%d ", i);
i++;
}
return 0;
}
// 1 2 3 4 死循环
- for循环中,调整表达式是每次循环必须要执行的,因此不受continue的影响。
int main()
{
int i = 0;
// 初始化; 判断; 调整
for (i = 1; i <= 10; i++)
{
if (5 == i)
{
continue;
}
printf("%d ", i);
}
return 0;
}
// 1 2 3 4 6 7 8 9 10
注意
for循环体中不要随便改变循环变量的值,防止for循环失去控制
// 这段结果的代码是?
int main()
{
int i = 0;
for(i = 0;i < 10; i++)
{
if(i = 5)
{
printf("efg");
}
printf("abc");
}
return 0;
}
// 无限打印efg的死循环
// 因为i = 5 不是判断,而是赋值
for循环的表达式建议使用前闭后开区间的写法。
int i = 0;
//前闭后开的写法
for(i=0; i<10; i++)
{}
//两边都是闭区间
for(i=0; i<=9; i++)
{}
for循环的一些特殊写法
例1
// 死循环
int main()
{
for (;;)
{
printf("abc");
}
return 0;
}
-
for循环的初始化、条件判断、调整表达式都可以省略。
-
for循环的判断部分,如果被省略,那么判断条件就是永真。
-
如果不是非常熟练,建议不要随便省略。
为什么不能随便省略?看下面例子
例2
// 下面的打印结果是?
int main()
{
int i = 0;
int j = 0;
for (; i < 10; i++)
{
for (; j < 10; j++)
{
printf("%d ", i * j);
}
}
}
从结果也能猜出来,只有 i = 0 时,进入了 j 循环打印了结果。
因为 j 的初始 在两个for循环外面,当第一个内存for循环结束时j == 10,并且当进行第二次外层for循环时,j已经等于10,j也没有被初始化,不满足内层for循环的条件,所以无法进入内层for循环。
例3
// 下面代码打印几次abc?
int main()
{
int x = 0;
int y = 0;
for (x = 0,y = 0; x<2 && y < 5; ++x, y++)
{
printf("abc\n");
}
return 0;
}
// 打印2次abc
练习
下面的for循环会循环多少次?
int main()
{
int i = 0;
int k = 0;
for (i = 0, k = 0;k = 0; i++, k++)
k++;
return 0;
}
// 0次 表达式是 k = 0,而不是 k == 0, 0代表假,不进入for循环。
do while 循环
while循环的执行流程是先判断,再根据判断选择是否进入循环体。
而do while循环的执行流程是先执行循环体,循环体执行完毕后,再来判断是否再次进入循环体。
语法
do
{
循环体;
} while(表达式);
举例
打印1~10
int main()
{
int i = 1;
do
{
printf("%d", i);
i++;
} while (i <= 10);
return 0;
}
break和continue
break和continue在do while 语句中,功能类似于在while语句中,按照上面的例子,continue在do while 语句中也会出现死循环。
练习
求n的阶乘
描述
计算n!,并输出。
分析
- n! = 1 * 2 * 3 * … * (n-1) * n=(n-1)!*n
- 创建一个变量n用来作用for循环的判断条件。
- 创建一个变量fac用来存放结果。
- 考虑到要给变量定义类型,如果n的数值太大可能放不下,因此加上一个判断条件。
代码实现
int main()
{
int n = 0;
int fac = 1;
int i = 0;
scanf("%d", &n);
if (n <= 10)
{
for (i = 1; i <= n; i++)
{
fac *= i;
}
printf("%d\n", fac);
}
else {
printf("数值过大");
}
return 0;
}
求1~10阶乘的和
描述
求1!+2!+…+10!,并输出。
分析
-
n! = (n-1)!*n
-
创建变量fac存放每次数阶乘的结果
-
创建变量sum存放阶乘相加的和。
代码实现
int main()
{
int fac = 1;
int sum = 0;
int i = 0;
for (i = 1; i <= 10; i++)
{
fac *= i;
sum += fac;
}
printf("%d\n", sum);
return 0;
}
水仙❀数
描述
求水仙花数,并输出
分析
- 十进制数中的一个三位数,它每一位数的3次幂之和等于数本身的就是水仙花数。
- 限制查找范围,创建变量x存放水仙花数,100<=x<1000。
- 求x上每一位数,创建变量h, t, o 分别存放百位 十位 个位上的数。
- 判断,如果三个数的3次幂的和等于x 则打印这个数。
代码实现
int main()
{
int x = 0,h = 0,t = 0, o = 0;
for(x = 100;x<1000;x++)
{
h = x / 100; // 商为百位数
t = x / 10 % 10; // 商为百位+十位再%10为十位数
o = x % 10; // 余数为个位数
if(((h*h*h)+(t*t*t)+(o*o*o)) == x)
{
printf("%d ", x);
}
}
return 0;
}
有序数组查找(二分查找)
描述
在一个有序数组中查找具体的数字n,并输出它的下标。
分析
- 有序数组的查找使用二分查找算法。
- 创建变量n 用于接收要查找的数字。
- 创建变量 left 接收数组第一个数的下标,创建变量 right 接收数组最后一个数的下标,创建变量 mid 接收数组中间位数的下标。
- left = 0,right = 数组长度 - 1, mid = (left + right) / 2。
- 利用mid作为数组下标号,查找数组中间位数。
- 如果大于被查找数,则范围缩小至mid的左侧,right = mid - 1, left 不变。
- 如果小于被查找数,则范围缩小至mid的右侧,left = mid + 1 , right 不变。
- mid的值需要随着 left 和 right的改变而改变,因此需要放在循环体中。
- 如果找到数字n,直接break; 终止循环。
- 如果直到 left>right,n还没有被查找到,则不存在于这个数组中。
代码实现
int main()
{
int n = 0;
scanf("%d", &n);
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int length = sizeof(arr) / sizeof(arr[0]);
int left = 0;
int right = length - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (arr[mid] > n)
{
right = mid - 1;
}
else if (arr[mid] < n)
{
left = mid + 1;
}
else
{
printf("找到了,下标为: %d\n", mid);
break;
}
}
if (left > right)
{
printf("找不到");
}
return 0;
}
//
//
- 如果用穷举法计算,有n个数则最大查找次数是n次。
- 而如果使用二分查找算法,等于n 不断的除以2,每次排除掉一半,直到除以2 = 1 ,而这中间所循环的次数 x 就是最大查找次数。2 x = n, x = log2n。
- 可以看到假如查找的个数为232, 穷举法最大查找次数是 232 次,而二分查找法的最大查找次数是32次,效率十分的高。
动态打印字符
描述
编写代码实现字符从两端移动,向中间汇聚。
// 实现效果
// w###################!
// we#################!!
// wel###############!!!
// ...
// welcome t###it !!!!!!
// welcome to#bit !!!!!!
// welcome to bit !!!!!!
分析
- 首先准备2个字符数组,str1 存放全#,str2 存放最终打印效果。
- 利用循环每次将str2下左右下标的字符赋值给str1对应下标的字符。
- 因此需要变量left和right来存放数组的下标,left = 0,right = 字符串长度 - 1。
- 如果使用sizeof计算字符串长度,因为字符串默认在结尾隐藏一个\0,则需要减去2;不过可以使用strlen() 来计算字符串长度,strlen()不会统计\0的个数。
- 每次循环left+1,right-1。
- left == right 时最后一个字符被赋值, 因此循环的判断条件为 left > right。
代码实现
#include <stdio.h>
#include <string.h>
#include <windows.h> // 使用sleep函数 需要引用头文件
#include <stdlib.h> // 使用system函数 需要引用头文件
int main()
{
char str1[] = "#####################" ;
char str2[] = "welcome to my blog!!!" ;
int left = 0;
int right = strlen(str1) - 1;
while (left <= right)
{
str1[left] = str2[left];
str1[right] = str2[right];
printf("%s\n", str1);
// 每打印一次休息1s
Sleep(1000); // sleep函数需要引用<windows.h> 头文件
// 休息一秒后清屏
if (left != right)
{ // 加这个if是为了防止最后一次被清屏
// 执行系统命令的函数, cls- 清空屏幕命令
system("cls");
// 使用system函数 需要引用头文件<stdlib.h>
}
left++;
right--;
}
return 0;
}
模拟用户登录
描述
编写代码实现,模拟用户登录情景,并且只能登录三次。只允许输入三次密码,如果密码正确则提示登录成功,如果三次均错误则退出程序。
分析
- 打印提示用户输入账号密码。
- 创建字符数组account、passwd 来接收用户输入的账户和密码。
- 利用for循环来判断如果用户输入的账户密码是否等于设定的值。
- 如果等于则提示登录成功,break; 终止循环,否则继续循环。
- 等号不可以判断字符串是否相等,应该使用库函数–strcmp()。
- 当三次均不等于结束循环,提示用户登录失败。
代码实现
#include <stdio.h>
#include <string.h>
int main()
{
char account[20] = "";
char passwd[20] = "";
int i = 0;
for (i = 0; i < 3; i++)
{
printf("请输入账号:>");
scanf("%s", account);
printf("请输入密码:>");
scanf("%s", passwd);
if ((strcmp(account,"joker") == 0) && (strcmp(passwd,"123456") == 0))
{
printf("登录成功\n");
break;
}
else
{
if (!(strcmp(account, "joker") == 0))
{
printf( "未匹配到相关账户\n" );
}
else
{
printf("密码错误\n");
}
}
}
if (3 == i)
{
printf("登录失败\n");
}
return 0;
}
strcmp(str1,str2)
string compare
判断两个字符串是否相等使用strcmp(str1,str2)函数,strcmp函数会从两个字符的第一个字符开始对比它们的ASCII码值,直到其中有字符不同或者遇到\0结束。
返回值
- 两个字符串完全相等返回0。
- 不相等的两个字符中,str1的字符比str2的字符ASCII值小,返回<0的数值。
- 不相等的两个字符中,str1的字符比str2的字符ASCII值2,返回>0的数值。