目录
补码
字符类型 VS 整型
字符串
算数运算符
类型转换
分支结构
关系运算符
逻辑运算符
if语句
switch-case语句
if和switch语句嵌套
循环结构
while循环
do...while循环
for循环
continue
多学一点
for VS while
goto
条件运算符
彩蛋
第一天忘了跟大家说C环境的配置,点击这里查看C语言初学者如何配置开发环境。
计算机是用补码的形式存储整型数据的。正数的原返补都是一样的,负数的补码需要三步才能得到:
为什么会引入补码?详情见:计算机中数的表示 & 使用补码的好处
先看个例子:
#include
int main()
{char c = 'A';printf("%c = %d\n",c,c);
}
运行结果:
字符'A'为什么以整型输出时是65?还记得第一天在讲转义字符时提到的ASCII表吗,对的,这里就是通过查ASCII表查到字符'A'对应的十进制数是“65”,所以字符类型是特殊的整型。
再来看个例子:
#include
int main()
{char height = 175;printf("我的海拔是%dcm\n",height);
}
哎呀我去,不得了了,咋还长到地底下去了,我又不是胡萝卜(´•༝•`) 这又是咋回事呢?还记得第一天学C时提到char的取值范围是-128到127或0到255,为什么还有个或呢?这是因为C标准中没有规定char默认是signed还是unsigned,而是由编译系统决定它的默认限定符,signed char的取值范围是 -128 ~ 127;unsigned char的取值范围是 0 ~ 255。所以我们应该手动指定char的限定符。
C没有专门为存储字符串设计一个单独的类型,因为没必要。字符串事实上就是一串字符。所以只需要在内存中开辟一块连续空间,然后存放一串字符类型的变量即可。
#include
int main() {char words[12];words[0] = 'I';words[1] = ' ';words[2] = 'L';words[3] = 'o';words[4] = 'v';words[5] = 'e';words[6] = ' ';words[7] = 'C';words[8] = 'h';words[9] = 'i';words[10] = 'n';words[11] = 'a';/*'\0'是字符串结束的标记,不可以省掉*/words[12] = '\0';/*声明和赋值放在一块,称为初始化,[]中的数量可以省掉,编译期会自动帮我们计算*/char words2[] = {'I',' ','L','o','v','e',' ','C','h','i','n','a','\0'};/*字符串常量可以用双引号直接括起来,不必在末尾追加'\0',系统会自动追加*/char words3[] = {"I Love China"};/*字符串常量可以把大括号也省掉*/char words4[] = "I Love China";printf("%s\n",words);printf("%s\n",words2);printf("%s\n",words3);printf("%s\n",words4);return 0;
}
假设A的值是10,B的值是20
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加(双目) | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数(双目) | A - B 将得到 -10 |
* | 把两个操作数相乘(双目) | A * B 将得到 200 |
/ | 分子除以分母(双目) | B / A 将得到 2 |
% | 取模运算符,整除后的余数(双目) | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1(单目) | A++ 将得到 11 |
-- | 自减运算符,整数值减少 1(单目) | A-- 将得到 9 |
代码示例:
#include
int main() {int a = 10,b = 20;printf("a + b = %d\n",a + b);printf("a - b = %d\n",a - b);printf("a * b = %d\n",a * b);printf("b / a= %d\n",b / a);/*对于整型类型数据的除法运算,是采取直接舍弃小数部分的方式,而不是什么四舍五入。*/printf("a / b= %d\n",a / b);printf("b %% a = %d\n",b % a);/*++在前,先自增再赋值;++在后,先赋值再自增;自减同理。*/printf("a++ = %d\n",a++);//a++的值是10printf("++a = %d\n",++a);//经过上一步运算a的值就变成11了,++a在11的基础上先自增1变成12,再赋值,所以此时a的值是12printf("a-- = %d\n",a--);printf("--a = %d\n",--a);return 0;
}
需要注意的几点问题:
#include
#include
int main() {/*自动转换:两种不同类型的数据做运算,容量小的类型会自动向上转型成容量大的类型再做运算,所以3+3.0得到的是浮点型,以整型格式输出,结果肯定不对*/printf("整型输出:%d\n",3+3.0);printf("浮点输出:%f\n",3.0+3.0);/*强制转换:语法格式:(目标类型)原数据强制类型转换会损失精度*/printf("强制类型转换:%d\n",3+(int)3.6);//浮点型的3.6强转成int后变成3了}
之前我们写的代码都是顺序结构,即按代码编写顺序依次执行。但我们生活中会存在一种“如果怎样就怎样否则会怎样”的情况,所以就有了分支结构,在学习分支结构之前,我们得先了解关系运算符和逻辑运算符。
关系运算符用于比较两个数或者两个表达式的大小关系,包含>(大于)、<(小于)、=(等于)、>=(大于或等于)、<=(小于或等于)、==(等于)、!=(不等于),关系运算符的优先级低于算数运算符,结合性都是从左到右。关系运算符是双目运算符,用关系运算符将两边的变量、数据或表达式连接起来,称之为关系表达式。关系表达式的结果是一个逻辑值,或真(1)或假(0)。
#include
int main() {int a = 6,b = 8;printf("a > b结果是%d\n",a > b);printf("a = b结果是%d\n",a >= b);printf("a <= b结果是%d\n",a <= b);printf("a != b结果是%d\n",a != b);
}
逻辑运算符包含&&(与)、||(或)、!(非),优先级:!>&&>||。逻辑运算符存在短路求值,详情参见下面代码示例:
#include
int main() {int a = 6,b = 8;/*我们输入非0数,C语言就会判断为真,否则为假;C语言反馈给我们的值只有0和1,0表示假,1表示真。*//*&&:两边同为真结果才为真,否则皆为假。*/printf("a && b = %d\n",a && b);/*||:两边同为假结果才为假,否则皆为真。*/printf("(a = 0) || b = %d\n",(a = 0) || b);/*!:单目运算符,原数据为真,结果则为假,否则为真,即取反。*/printf("!0 || a }
if语句有三种形态:
#include
int main() {int age;printf("阁下贵庚:");scanf("%d",&age);if(age >=18) {printf("欢迎光临,祝您玩的开心!");}return 0;
}
#include
int main() {int age;printf("阁下贵庚:");scanf("%d",&age);if(age >=18) {printf("欢迎光临,祝您玩的开心!");} else {printf("未成年人禁止入内,谢谢配合!");}return 0;
}
#include
int main() {float score;char grade;printf("请输入学生成绩:");scanf("%f",&score);/*输入的是浮点型,以整型打印,结果为0*/printf("用户输入的成绩是(以整型格式输出):%d\n",score);if(score >=90) {grade = &#39;A&#39;;} else if(score >= 80 && score <90) {grade = &#39;B&#39;;} else if(score >= 70 && score <80) {grade = &#39;C&#39;;} else if(score >= 60 && score <70) {grade = &#39;D&#39;;} else if(score >= 0 && score <60) {grade = &#39;E&#39;;} else {printf("学生分数不能给负数,太打击学生自信心了!");return 0;}printf("该学生分数等级是%c\n",grade);return 0;
}
可以认为switch-case语句是if语句第三种形态的优化版本,因为它的语法更简洁。语法如下:
switch (表达式)
{case 常量表达式1: 语句或程序块case 常量表达式2: 语句或程序块……case 常量表达式n:语句或程序块default: 语句或程序块
}
示例代码:
#include
int main() {char grade;printf("请输入学生成绩评级:");scanf("%c",&grade);switch(grade) {case &#39;A&#39;:printf("该学生分数是90分以上。\n");/*break关键字用于跳出switch语句*/ break;case &#39;B&#39;:printf("该学生分数是80分以上。\n");break;case &#39;C&#39;:printf("该学生分数是70分以上。\n");break;case &#39;D&#39;:printf("该学生分数是60分以上。\n");break;case &#39;E&#39;:printf("该学生分数未及格。\n");break;/*default语句是可选的,即可以不写,不过最好写上*/ default:printf("输出成绩评级不合法,请输入(&#39;A&#39;/&#39;B&#39;/&#39;C&#39;/&#39;D&#39;/&#39;E&#39;)。\n");break;}return 0;
}
可以看出我们在switch语句中增加了break关键字用于跳出switch语句,如果不加break,switch语句就会从匹配到的case开始执行,一直执行到default才会退出,这种现象叫做“case穿透”。switch语句中的 case和 default 事实上都是“标签”,用来标志一个位置而已。当 switch 跳到某个位置之后,就会一直往下执行,所以我们这里还需要配合一个 break语句,让代码在适当的位置跳出 switch。
来看一个利用case穿透的示例:
#include
/*根据用户输入的月份,匹配相应的季节
*/
int main() {/*月份*/int month;/*季节*/char* season;printf("请输入月份:");scanf("%d",&month);switch(month) {case 3:case 4:case 5:season = "春季";break;case 6:case 7:case 8:season = "夏季";break;case 9:case 10:case 11:season = "秋季";break;case 12:case 1:case 2:season = "冬季";break;default:printf("输出月份不合法。\n");return 0;}printf("输出的月份对应%s。\n",season);return 0;
}
#include
/*根据用户输入的月份,匹配相应的季节
*/
int main() {/*月份*/int month;/*季节*/char* season;printf("请输入月份:");scanf("%d",&month);if(month >= 1 && month <= 12) {switch(month) {case 3:case 4:case 5:season = "春季";break;case 6:case 7:case 8:season = "夏季";break;case 9:case 10:case 11:season = "秋季";break;case 12:case 1:case 2:season = "冬季";break;default:break;}printf("输出的月份对应%s。\n",season);} else {printf("输出的月份不合法!\n");}return 0;
}
if语句也可以与if嵌套,大家自行练习。
语法:while(逻辑表达式){循环体} ------ 只要逻辑表达式的结果为真,就会一直循环执行循环体中的语句。
/*统计用户输入的字符个数:
*/
#include
int main() {int count = 0;printf("请输入你想说的话:");/*getchar()------从标准输入流中获取字符*/while(getchar() != &#39;\n&#39;) {count++;}printf("您总共输入了%d个字符。\n",count);
}
语法:do{循环体}while(逻辑表达式); ------ 先会执行一次循环体,然后进入while,只要逻辑表达式为真,就会一直循环执行。do...while循环语句不是很常用。
语法格式:
for (表达式1; 表达式2; 表达式3){循环体;
}
表达式1是初始化循环变量;表达式2是循环的条件,满足条件就会执行循环体;最后执行表达式3,表达式3是改变循环变量的值。下面是一个判断用户输入的自然数是否是素数的案例:
/*判断用户输入的数字是否是素数:<1>质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数。<2>0和1既不是质数也不是合数,最小的质数是2。
*/
#include
int main() {/*接收用户输入的自然数*/int num;/*标记是否是素数,默认不是素数*/_Bool isPrimer = 1;printf("请输入一个自然数:");scanf("%d",&num);if(num == 0 || num == 1) {printf("您输入的%d既不是素数也不是合数~\n",num);return 0;}/*已知2和3是素数,所以这里只对大于3的自然数做处理*/if(num > 3) {int i;/*能整除一个数的数,肯定小于或等于被除数的一半,所以这里取 num/2 */for(i = 2; i <= num / 2; i++) {if(num % i == 0) {isPrimer = 0;/*只要找到一个可以整除num的除数就跳出循环*/break;}}}if(isPrimer)printf("您输入的自然数%d是素数\n",num);elseprintf("您输入的自然数%d不是素数\n",num);return 0;
}
另外,for 语句的表达式1,表达式2和表达式3都可以按需省略(但分号不能省):
C99允许在 for语句的表达式1中定义变量,比如如下代码:
for (int i=0, int j=10; i
增加这个新特性的原因主要是考虑到循环通常需要一个循环变量(计数器),而这个循环变量只需要在for循环中有效即可。所以在表达式1的位置定义的循环变量,作用域仅限于循环中,出了循环,它就无效了。
ps:循环结构同意可以嵌套,如下中的for循环嵌套案例,先执行内层循环,再执行外层循环。
/*打印九九乘法表
*/
#include
int main() {int i,j;for(i=1; i<10; i++) {for(j=1; j<=i; j++) {printf("%d * %d = %d\t",j,i,j*i);}printf("\n");}return 0;
}
continue与break都是用于跳出循环,continue是跳出本轮循环继续下一轮循环,break是跳出所在的那一层循环(当循环嵌套时如果要跳出整个循环可以借助goto语句,其他场景慎用goto)。
/*去掉用户输入的空格
*/
#include
int main() {char ch;while((ch = getchar()) != &#39;\n&#39;) {if(ch == &#39; &#39;) {continue;}putchar(ch);}putchar(&#39;\n&#39;);return 0;
}
for语句和 while语句执行过程是有区别的,它们的区别在于出现 continue语句时。在 for语句中,continue语句跳过循环的剩余部分,直接回到调整循环变量部分。在 while语句中,由于调整部分是循环体的一部分,因此 continue语句会把它也跳过,稍不注意就会出现问题。比如如下案例中:使用while改造for循环实现的功能,结果死循环了{T_T}
/*for VS while
*/
#include
int main() {int i,j;for(i = 0,j = 10; i<10; i++,j--) {if(i > j) {continue;}}printf("for循环执行完成后i和j的值分别是:%d、%d\n",i,j);/*使用while改造上面的for循环 */ i = 0;j = 10;while(i<10) {if(i > j) {continue;}i++;j--;}printf("while循环执行完成后i和j的值分别是:%d、%d\n",i,j);return 0;
}
goto用于跳到指定标签的位置。
/*goto语句使用案例
*/
#include
int main() {int i = 6;while (i++) {if (i > 8) {goto Label;}}Label:printf("第%d次发牢骚:\n从来表白多白表,\n自古情书难书情,\n笑谈年少多少年,\n常与生人道人生。 \n", i);return 0;
}
条件运算符是C语言中唯一一个三目运算符,语法格式:exp1 ? exp2 : exp3; exp1是逻辑表达式,如果结果为真,则取exp2的值,否则取exp3的值。其实它就是为了简化 if...else...这种简单的语句。
/*条件运算符简化if-else语句,实现求两个数中的最大值。
*/
#include
int main() {int i = 6,j = 8,max;if(i > j)max = i;elsemax = j;/*使用条件表达式简化上述语句*/max = i > j ? i:j;printf("最大值是%d。\n",max);return 0;
}
<1>思考如下代码运行结果是什么,并解释说明。
#include
int main()
{
int a = 3;
printf("%d,%d\n",a++,a++);
printf("\n%d",a);return 0;
}
<2>还记得上一篇博文留的思考题吗?就是关于 unsigned short j = -1; 输出结果为什么是65535。这个问题就跟我们文章开头讲的补码有关系了,CPU运算整型是通过补码运算的,unsigned short 占用两个字节(16位),所以此时 -1的补码是1111 1111 1111 1111,转换成十进制就是65535。
你要学会猥琐发育,不鸣则已,一鸣惊人!