目录

补码

 字符类型 VS 整型

字符串

算数运算符

类型转换

分支结构

关系运算符

逻辑运算符

if语句

switch-case语句 

if和switch语句嵌套

循环结构

while循环

do...while循环

for循环

continue

多学一点

for VS while

goto

条件运算符

彩蛋


第一天忘了跟大家说C环境的配置,点击这里查看C语言初学者如何配置开发环境。

补码

计算机是用补码的形式存储整型数据的。正数的原返补都是一样的,负数的补码需要三步才能得到:

  1. 先把该数的绝对值转换成二进制形式,也就是该数的源码 ------ 源码就是原来的样子
  2. 再把数值位按位取反,得到反码 ------ 反码就是反着干,0变1,1变0;
  3. 最后将第2步的值加一就得到了补码 ------ 补码就是补1。
    偷学C语言第二天_#include

为什么会引入补码?详情见:计算机中数的表示 & 使用补码的好处

字符类型 VS 整型

先看个例子:

#include<stdio.h>
int main()
{
	char c = 'A';
	printf("%c = %d\n",c,c);
}

运行结果:
偷学C语言第二天_数据_02

字符'A'为什么以整型输出时是65?还记得第一天在讲转义字符时提到的ASCII表吗,对的,这里就是通过查ASCII表查到字符'A'对应的十进制数是“65”,所以字符类型是特殊的整型。

再来看个例子:

#include<stdio.h>
int main()
{
	char height = 175;
	printf("我的海拔是%dcm\n",height);
}

 偷学C语言第二天_补码_03

哎呀我去,不得了了,咋还长到地底下去了,我又不是胡萝卜(´•༝•`) 这又是咋回事呢?还记得第一天学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语句有三种形态:

  1. if(逻辑数据){//业务代码} ;
    #include<stdio.h>
    int main() {
    	int age;
    	printf("阁下贵庚:");
    	scanf("%d",&age);
    
    	if(age >=18) {
    		printf("欢迎光临,祝您玩的开心!");
    	}
    
    	return 0;
    }

     

  2. if(逻辑数据){//业务代码}else{//业务代码};
    #include<stdio.h>
    int main() {
    	int age;
    	printf("阁下贵庚:");
    	scanf("%d",&age);
    
    	if(age >=18) {
    		printf("欢迎光临,祝您玩的开心!");
    	} else {
    		printf("未成年人禁止入内,谢谢配合!");
    	}
    
    	return 0;
    }

     

  3. 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。

你要学会猥琐发育,不鸣则已,一鸣惊人!