目录
第一天忘了跟大家说C环境的配置,点击这里查看C语言初学者如何配置开发环境。
补码计算机是用补码的形式存储整型数据的。正数的原返补都是一样的,负数的补码需要三步才能得到:
- 先把该数的绝对值转换成二进制形式,也就是该数的源码 ------ 源码就是原来的样子
- 再把数值位按位取反,得到反码 ------ 反码就是反着干,0变1,1变0;
- 最后将第2步的值加一就得到了补码 ------ 补码就是补1。
为什么会引入补码?详情见:计算机中数的表示 & 使用补码的好处
字符类型 VS 整型先看个例子:
#include<stdio.h>
int main()
{
char c = 'A';
printf("%c = %d\n",c,c);
}
运行结果:
字符'A'为什么以整型输出时是65?还记得第一天在讲转义字符时提到的ASCII表吗,对的,这里就是通过查ASCII表查到字符'A'对应的十进制数是“65”,所以字符类型是特殊的整型。
再来看个例子:
#include<stdio.h>
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没有专门为存储字符串设计一个单独的类型,因为没必要。字符串事实上就是一串字符。所以只需要在内存中开辟一块连续空间,然后存放一串字符类型的变量即可。
- 声明字符串的语法:char 变量名[字符的数量];
- 赋值:事实上就是对这一块连续空间里边的每一个字符变量进行赋值。我们通过索引号来获得每个字符变量的空间,语法格式:变量名[索引号] = 字符;
#include<stdio.h> 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<stdio.h>
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++的值是10
printf("++a = %d\n",++a);//经过上一步运算a的值就变成11了,++a在11的基础上先自增1变成12,再赋值,所以此时a的值是12
printf("a-- = %d\n",a--);
printf("--a = %d\n",--a);
return 0;
}
需要注意的几点问题:
- 因为键盘上没有乘号和除号两个按键,所以用星号(*)和斜杠(/)代替,几乎所有编程语言都是如此。
- 对于整数间的除法是采取直接舍弃小数部分的方式,而不是什么四舍五入。
- 对于浮点数间的除法则能获得一个相对逼近结果的值(如果除不尽或位数特别多的话)。
- 百分号(%)取模(求余)运算符要求两边的操作数必须都是整数,其结果也是整数。
- 双目单目指的是运算符有几个操作数。
#include<stdio.h>
#include<math.h>
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<stdio.h>
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);
printf("a != b结果是%d\n",a != b);
}
逻辑运算符
逻辑运算符包含&&(与)、||(或)、!(非),优先级:!>&&>||。逻辑运算符存在短路求值,详情参见下面代码示例:
#include<stdio.h>
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 < b = %d\n",!0 || a < b);
/*
短路求值:只有当第一个操作数或表达式结果不能确定时,才会对第二个数据进行求值操作
*/
int c = (a = 0) && (b = 2);
printf("a = %d,b = %d\n",a,b);
int d = (a = 1) || (b = 6);
printf("a = %d,b = %d\n",a,b);
return 0;
}
if语句
if语句有三种形态:
- if(逻辑数据){//业务代码} ;
#include<stdio.h> int main() { int age; printf("阁下贵庚:"); scanf("%d",&age); if(age >=18) { printf("欢迎光临,祝您玩的开心!"); } return 0; }
- if(逻辑数据){//业务代码}else{//业务代码};
#include<stdio.h> int main() { int age; printf("阁下贵庚:"); scanf("%d",&age); if(age >=18) { printf("欢迎光临,祝您玩的开心!"); } else { printf("未成年人禁止入内,谢谢配合!"); } return 0; }
- if(逻辑数据){//业务代码}else if(逻辑值){//业务代码}else if(逻辑值){//业务代码}...else{//业务代码}。
#include<stdio.h> int main() { float score; char grade; printf("请输入学生成绩:"); scanf("%f",&score); /*输入的是浮点型,以整型打印,结果为0*/ printf("用户输入的成绩是(以整型格式输出):%d\n",score); if(score >=90) { grade = 'A'; } else if(score >= 80 && score < 90) { grade = 'B'; } else if(score >= 70 && score < 80) { grade = 'C'; } else if(score >= 60 && score < 70) { grade = 'D'; } else if(score >= 0 && score < 60) { grade = 'E'; } else { printf("学生分数不能给负数,太打击学生自信心了!"); return 0; } printf("该学生分数等级是%c\n",grade); return 0; }
switch-case语句
可以认为switch-case语句是if语句第三种形态的优化版本,因为它的语法更简洁。语法如下:
switch (表达式)
{
case 常量表达式1: 语句或程序块
case 常量表达式2: 语句或程序块
……
case 常量表达式n:语句或程序块
default: 语句或程序块
}
示例代码:
#include<stdio.h>
int main() {
char grade;
printf("请输入学生成绩评级:");
scanf("%c",&grade);
switch(grade) {
case 'A':
printf("该学生分数是90分以上。\n");
/*break关键字用于跳出switch语句*/
break;
case 'B':
printf("该学生分数是80分以上。\n");
break;
case 'C':
printf("该学生分数是70分以上。\n");
break;
case 'D':
printf("该学生分数是60分以上。\n");
break;
case 'E':
printf("该学生分数未及格。\n");
break;
/*default语句是可选的,即可以不写,不过最好写上*/
default:
printf("输出成绩评级不合法,请输入('A'/'B'/'C'/'D'/'E')。\n");
break;
}
return 0;
}
可以看出我们在switch语句中增加了break关键字用于跳出switch语句,如果不加break,switch语句就会从匹配到的case开始执行,一直执行到default才会退出,这种现象叫做“case穿透”。switch语句中的 case和 default 事实上都是“标签”,用来标志一个位置而已。当 switch 跳到某个位置之后,就会一直往下执行,所以我们这里还需要配合一个 break语句,让代码在适当的位置跳出 switch。
来看一个利用case穿透的示例:
#include<stdio.h>
/*
根据用户输入的月份,匹配相应的季节
*/
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;
}
if和switch语句嵌套
#include<stdio.h>
/*
根据用户输入的月份,匹配相应的季节
*/
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循环
语法:while(逻辑表达式){循环体} ------ 只要逻辑表达式的结果为真,就会一直循环执行循环体中的语句。
/*
统计用户输入的字符个数:
*/
#include<stdio.h>
int main() {
int count = 0;
printf("请输入你想说的话:");
/*getchar()------从标准输入流中获取字符*/
while(getchar() != '\n') {
count++;
}
printf("您总共输入了%d个字符。\n",count);
}
do...while循环
语法:do{循环体}while(逻辑表达式); ------ 先会执行一次循环体,然后进入while,只要逻辑表达式为真,就会一直循环执行。do...while循环语句不是很常用。
for循环
语法格式:
for (表达式1; 表达式2; 表达式3){
循环体;
}
表达式1是初始化循环变量;表达式2是循环的条件,满足条件就会执行循环体;最后执行表达式3,表达式3是改变循环变量的值。下面是一个判断用户输入的自然数是否是素数的案例:
/*
判断用户输入的数字是否是素数:
<1>质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数。
<2>0和1既不是质数也不是合数,最小的质数是2。
*/
#include<stdio.h>
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);
else
printf("您输入的自然数%d不是素数\n",num);
return 0;
}
另外,for 语句的表达式1,表达式2和表达式3都可以按需省略(但分号不能省):
- for ( ; 表达式2; 表达式3)
- for (表达式1; 表达式2; )
- for (表达式1; ; )
- for ( ; ; )
- ……
C99允许在 for语句的表达式1中定义变量,比如如下代码:
for (int i=0, int j=10; i < j; i++, j--){
printf("%d\n", i);
}
增加这个新特性的原因主要是考虑到循环通常需要一个循环变量(计数器),而这个循环变量只需要在for循环中有效即可。所以在表达式1的位置定义的循环变量,作用域仅限于循环中,出了循环,它就无效了。
ps:循环结构同意可以嵌套,如下中的for循环嵌套案例,先执行内层循环,再执行外层循环。
/*
打印九九乘法表
*/
#include<stdio.h>
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
continue与break都是用于跳出循环,continue是跳出本轮循环继续下一轮循环,break是跳出所在的那一层循环(当循环嵌套时如果要跳出整个循环可以借助goto语句,其他场景慎用goto)。
/*
去掉用户输入的空格
*/
#include<stdio.h>
int main() {
char ch;
while((ch = getchar()) != '\n') {
if(ch == ' ') {
continue;
}
putchar(ch);
}
putchar('\n');
return 0;
}
多学一点
for VS while
for语句和 while语句执行过程是有区别的,它们的区别在于出现 continue语句时。在 for语句中,continue语句跳过循环的剩余部分,直接回到调整循环变量部分。在 while语句中,由于调整部分是循环体的一部分,因此 continue语句会把它也跳过,稍不注意就会出现问题。比如如下案例中:使用while改造for循环实现的功能,结果死循环了{T_T}
/*
for VS while
*/
#include<stdio.h>
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用于跳到指定标签的位置。
/*
goto语句使用案例
*/
#include <stdio.h>
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 <stdio.h>
int main() {
int i = 6,j = 8,max;
if(i > j)
max = i;
else
max = j;
/*使用条件表达式简化上述语句*/
max = i > j ? i:j;
printf("最大值是%d。\n",max);
return 0;
}
彩蛋
<1>思考如下代码运行结果是什么,并解释说明。
#include <stdio.h>
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。
你要学会猥琐发育,不鸣则已,一鸣惊人!