C语言系统化精讲(五):C语言格式化输入和运算符与表达式

2023-12-13 15:32:25

一、C语言格式化输入

初学者牢记:只要用户输入的内容和格式控制字符串匹配,就能够正确提取。在后续讲解格式化输入所演示的奇怪问题,几乎都是因为输入的内容和格式控制字符串不匹配

1.1 C语言scanf:读取从键盘输入的数据(含输入格式汇总表)

程序是人机交互的媒介,有输出必然也有输入,在 C语言系统化精讲(三):C语言变量和数据类型-上篇 一文中讲解了如何将数据输出到显示器上,本小节我们开始讲解如何从键盘输入数据。在C语言中,有多个函数可以从键盘获得用户输入:

  1. scanf():和 printf() 类似,scanf() 可以输入多种类型的数据。
  2. getchar()、getche()、getch():这三个函数都用于输入单个字符。
  3. gets():获取一行数据,并作为字符串处理。

scanf() 是最灵活、最复杂、最常用的输入函数,但它不能完全取代其他函数,大家都要有所了解。本小节我们只讲解 scanf(),其它的输入函数将在下面几个小节进行讲解。

scanf 是 scan format 的缩写,意思是格式化扫描,也就是从键盘获得用户输入,和 printf 的功能正好相反。举例:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    int a = 0, b = 0, c = 0, d = 0;
    scanf("%d", &a);  //输入整数并赋值给变量a
    scanf("%d", &b);  //输入整数并赋值给变量b
    printf("a+b=%d\n", a + b);  //计算a+b的值并输出
    scanf("%d %d", &c, &d);  //输入两个整数并分别赋值给c、d
    printf("c*d=%d\n", c * d);  //计算c*d的值并输出
    return 0;
}

运行结果(↙表示按下回车键):

1260↙
a+b=72
10 23↙
c*d=230  

从键盘输入12,按下回车键,scanf() 就会读取输入数据并赋值给变量 a;本次输入结束,接着执行下一个 scanf() 函数,再从键盘输入 60,按下回车键,就会将 60 赋值给变量 b,都是同样的道理。第 9 行代码中,scanf() 有两个以空格分隔的 %d,后面还跟着两个变量,这要求我们一次性输入两个整数,并分别赋值给 c 和 d。注意 %d %d 之间是有空格的,所以输入数据时也要有空格。对于 scanf(),输入数据的格式要和控制字符串的格式保持一致。其实 scanf 和 printf 非常相似,只是功能相反罢了:

scanf("%d %d", &a, &b);  // 获取用户输入的两个整数,分别赋值给变量 a 和 b
printf("%d %d", a, b);  // 将变量 a 和 b 的值在显示器上输出

它们都有格式控制字符串,都有变量列表。不同的是,scanf 的变量前要带一个 & 符号。 &称为取地址符,也就是获取变量在内存中的地址。数据是以二进制的形式保存在内存中的,字节(Byte) 是最小的可操作单位。为了便于管理,我们给每个字节分配了一个编号,使用该字节时,只要知道编号就可以,就像每个学生都有学号,老师会随机抽取学号来让学生回答问题。字节的编号是有顺序的,从 0 开始,接下来是 1、2、3……下图是 4G 内存中每个字节的编号(以十六进制表示):
在这里插入图片描述
这个编号,就叫做 地址(Address)。 int a; 会在内存中分配四个字节的空间,我们将第一个字节的地址称为变量 a 的地址,也就是 &a 的值。对于前面讲到的整数、浮点数、字符,都要使用 & 获取它们的地址,scanf 会根据地址把读取到的数据写入内存。我们不妨将变量的地址输出看一下:

#include <stdio.h>
int main()
{
    int a = 'F';
    int b = 12;
    int c = 452;
    printf("&a=%#x, &b=%#x, &c=%#x\n", &a, &b, &c);
    return 0;
}

运行结果如下图所示:
在这里插入图片描述
注意: 这里看到的地址都是假的,是虚拟地址,并不等于数据在物理内存中的地址。虚拟地址是现代计算机因内存管理的需要才提出的概念,我们将在后续的文章中详细讲解。

再来看一个 scanf 的例子:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    int a, b, c;
    scanf("%d %d", &a, &b);
    printf("a+b=%d\n", a + b);
    scanf("%d   %d", &a, &b);
    printf("a+b=%d\n", a + b);
    scanf("%d, %d, %d", &a, &b, &c);
    printf("a+b+c=%d\n", a + b + c);
    scanf("%d is bigger than %d", &a, &b);
    printf("a-b=%d\n", a - b);
    return 0;
}

运行结果:

10    20↙
a+b=30
100 200↙
a+b=300
56,45,78↙
a+b+c=179
25 is bigger than 11↙
a-b=14

第一个 scanf() 的格式控制字符串为 %d %d 中间有一个空格,而我们却输入了10 20,中间有多个空格。第二个 scanf() 的格式控制字符串为 "%d %d" 中间有多个空格,而我们却输入了100 200,中间只有一个空格。这说明 scanf() 对输入数据之间的空格的处理比较宽松,并不要求空格数严格对应,多几个少几个无所谓,只要有空格就行。第三个 scanf() 的控制字符串为 "%d, %d, %d" ,中间以逗号分隔,所以输入的整数也要以逗号分隔。第四个 scanf() 要求整数之间以 is bigger than 分隔。用户每次按下回车键,程序就会认为完成了一次输入操作,scanf() 开始读取用户输入的内容,并根据格式控制字符串从中提取有效数据,只要用户输入的内容和格式控制字符串匹配,就能够正确提取。本质上讲,用户输入的内容都是字符串,scanf() 完成的是从字符串中提取有效数据的过程。

连续输入: 在本小节最开始的示例代码中,我们一个一个地输入变量 a、b、c、d 的值,每输入一个值就按一次回车键。现在我们改变输入方式,将四个变量的值一次性输入,如下所示:

12 60 10 23↙
a+b=72
c*d=230

可以发现,两个 scanf() 都能正确读取。合情合理的猜测是,第一个 scanf() 读取完毕后没有抛弃多余的值,而是将它们保存在了某个地方,下次接着使用。如果我们多输入一个整数,会怎样呢?

12 60 10 23 99↙
a+b=72
c*d=230

这次我们多输入了一个 99,发现 scanf() 仍然能够正确读取,只是 99 没用罢了。如果我们少输入一个整数,又会怎样呢?

12 60 10↙
a+b=72
23↙
c*d=230

输入三个整数后,前两个 scanf() 把前两个整数给读取了,剩下一个整数 10,而第三个 scanf() 要求输入两个整数,一个单独的 10 并不能满足要求,所以我们还得继续输入,凑够两个整数以后,第三个 scanf() 才能读取完毕。从本质上讲,我们从键盘输入的数据并没有直接交给 scanf(),而是放入了缓冲区中,直到我们按下回车键,scanf() 才到缓冲区中读取数据。如果缓冲区中的数据符合 scanf() 的要求,那么就读取结束;如果不符合要求,那么就继续等待用户输入,或者干脆读取失败。注意,如果缓冲区中的数据不符合 scanf() 的要求,要么继续等待用户输入,要么就干脆读取失败,上面我们演示了 继续等待用户输入 的情形,下面我们对代码稍作修改,演示 读取失败 的情形:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    int a = 1, b = 2, c = 3, d = 4;  //修改处:给变量赋予不同的初始值
    scanf("%d", &a);
    scanf("%d", &b);
    printf("a=%d, b=%d\n", a, b);
    scanf("%d %d", &c, &d);
    printf("c=%d, d=%d\n", c, d);

    return 0;
}

运行结果:

12 60 a10↙
a=12, b=60
c=3, d=4

前两个整数被正确读取后,剩下了 a10,而第三个 scanf() 要求输入两个十进制的整数,a10 无论如何也不符合要求,所以只能读取失败。输出结果也证明了这一点,c 和 d 的值并没有被改变。这说明 scanf() 不会跳过不符合要求的数据,遇到不符合要求的数据会读取失败,而不是再继续等待用户输入。总而言之,正是由于缓冲区的存在,才使得我们能够多输入一些数据,或者一次性输入所有数据,这可以认为是缓冲区的一点优势。然而,缓冲区也带来了一定的负面影响,甚至会导致很奇怪的行为,请看下面的代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    int a = 1, b = 2;
    scanf("a=%d", &a);
    scanf("b=%d", &b);
    printf("a=%d, b=%d\n", a, b);
    return 0;
}

输入示例:

a=99↙
a=99, b=2

输入 a=99,按下回车键,程序竟然运行结束了,只有第一个 scanf() 成功读取了数据,第二个 scanf() 仿佛没有执行一样,根本没有给用户任何机会去输入数据。如果我们换一种输入方式呢?

a=99b=200↙
a=99, b=200

这样 a 和 b 都能够正确读取了。注意,a=99b=200 中间是没有任何空格的。肯定有好奇的小伙伴又问了,如果 a=99b=200 两个数据之间有空格又会怎么样呢?我们不妨亲试一下:

a=99 b=200↙
a=99, b=2

你看,第二个 scanf() 又读取失败了!在前面的例子中,输入的两份数据之前都是有空格的呀,为什么这里不能带空格呢,真是匪夷所思。好吧,这个其实还是跟缓冲区有关系,等后面大家有一定基础之后再进行深入讲解。要想破解 scanf() 输入的问题,一定要学习缓冲区,它能使你对输入输出的认识上升到一个更高的层次,以后不管遇到什么疑难杂症,都能迎刃而解。可以说,输入输出的 命门 就在于缓冲区。

输入其它数据: 除了输入整数,scanf() 还可以输入单个字符、字符串、小数等,请看下面的演示:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    char letter;
    int age;
    char url[30];
    float price;

    scanf("%c", &letter);
    scanf("%d", &age);
    scanf("%s", url); //可以加&也可以不加&
    scanf("%f", &price);
    printf("26个英文字母的最后一个是 %c。\n", letter);
    printf("我经营CSDN博客已经%d年了,网址是 %s,学习Python3入门与进阶专栏的价格是:%g。\n", age, url, price);
    return 0;
}

运行示例:

z↙
4↙
https://blog.csdn.net/xw1680↙
69.926个英文字母的最后一个是 z。
我经营CSDN博客已经4年了,网址是 https://blog.csdn.net/xw1680,学习Python3入门与进阶专栏的价格是:69.9。

scanf() 和 printf() 虽然功能相反,但是格式控制符是一样的,单个字符、整数、小数、字符串对应的格式控制符分别是 %c、%d、%f、%s。scanf() 格式控制符汇总:
在这里插入图片描述
表格中,绿色的格式控制符是 C99 新引入的。

1.2 C语言输入字符和字符串(所有函数大汇总)

C语言有多个函数可以从键盘获得用户输入,它们分别是:

  1. scanf():和 printf() 类似,scanf() 可以输入多种类型的数据。
  2. getchar()、getche()、getch():这三个函数都用于输入单个字符。
  3. gets():获取一行数据,并作为字符串处理。

scanf() 是最灵活、最复杂、最常用的输入函数,上一小节我们已经进行了讲解,本节接着讲解剩下的函数,也就是字符输入函数和字符串输入函数。

1.2.1 输入单个字符

输入单个字符当然可以使用 scanf() 这个通用的输入函数,对应的格式控制符为 %c,上一小节已经讲到了。本节我们重点讲解的是 getchar()、getche() 和 getch() 这三个专用的字符输入函数,它们具有某些 scanf() 没有的特性,是 scanf() 不能代替的。
① getchar()。 最容易理解的字符输入函数是 getchar(),它就是 scanf("%c", c) 的替代品,除了更加简洁,没有其它优势了;或者说,getchar() 就是 scanf() 的一个简化版本。下面的代码演示了 getchar() 的用法:

#include <stdio.h>
int main()
{
    char c;
    c = getchar(); //合并: char c = getchar();
    printf("c: %c\n", c);
    return 0;
}

输入示例:

@↙
c: @

② getche()。 getche() 就比较有意思了,它没有缓冲区,输入一个字符后会立即读取,不用等待用户按下回车键,这是它和 scanf()、getchar() 的最大区别。请看下面的代码:

#include <stdio.h>
#include <conio.h>
int main()
{
    char c = _getche();
    printf("c: %c\n", c);
    return 0;
}

输入示例:

@c: @

输入 @ 后,getche() 立即读取完毕,接着继续执行 printf() 将字符输出,所以没有按下回车键程序就运行结束了。注意,getche() 位于 conio.h 头文件中,而这个头文件是 Windows 特有的,Linux 和 Mac OS 下没有包含该头文件。换句话说,getche() 并不是标准函数,默认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用。注意: getche() 被 _getche() 取代。
在这里插入图片描述
③ getch()。 getch() 也没有缓冲区,输入一个字符后会立即读取,不用按下回车键,这一点和 getche() 相同。getch() 的特别之处是它没有回显,看不到输入的字符。所谓回显,就是在控制台上显示出用户输入的字符;没有回显,就不会显示用户输入的字符,就好像根本没有输入一样。回显在大部分情况下是有必要的,它能够与用户及时交互,让用户清楚地看到自己输入的内容。但在某些特殊情况下,我们却不希望有回显,例如输入密码,有回显是非常危险的,容易被偷窥。getch() 使用举例:

#include <stdio.h>
#include <conio.h>
int main()
{
    char c = _getch();
    printf("c: %c\n", c);
    return 0;
}

输入 @ 后,getch() 会立即读取完毕,接着继续执行 printf() 将字符输出。但是由于 getch() 没有回显,看不到输入的 @ 字符,所以控制台上最终显示的内容为 c: @。注意,和 getche() 一样,getch() 也位于 conio.h 头文件中,也不是标准函数,默认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用。对三个函数的总结:
在这里插入图片描述

1.2.2 输入字符串

输入字符串当然可以使用 scanf() 这个通用的输入函数,对应的格式控制符为 %s,上小节已经讲到了;本节我们重点讲解的是 gets() 这个专用的字符串输入函数,它拥有一个 scanf() 不具备的特性。gets() 的使用也很简单,请看下面的代码:

#include <stdio.h>
int main()
{
    char author[30], lang[30], url[30];
    gets(author);
    printf("author: %s\n", author);
    gets(lang);
    printf("lang: %s\n", lang);
    gets(url);
    printf("url: %s\n", url);
    return 0;
}

运行结果:

AmoXiang↙
author: AmoXiang
C-Language↙
lang: C-Language
http://www.baidu http://www.baidu↙
url: http://www.baidu http://www.baidu

gets() 是有缓冲区的,每次按下回车键,就代表当前输入结束了,gets() 开始从缓冲区中读取内容,这一点和 scanf() 是一样的。gets() 和 scanf() 的主要区别是:

  1. scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。
  2. gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束,所以,不管输入了多少个空格,只要不按下回车键,对 gets() 来说就是一个完整的字符串。

也就是说,gets() 能读取含有空格的字符串,而 scanf() 不能。

总结: C语言中常用的从控制台读取数据的函数有五个,它们分别是 scanf()、getchar()、getche()、getch() 和 gets()。其中 scanf()、getchar()、gets() 是标准函数,适用于所有平台;getche() 和 getch() 不是标准函数,只能用于 Windows。

  1. scanf() 是通用的输入函数,它可以读取多种类型的数据
  2. getchar()、getche() 和 getch() 是专用的字符输入函数,它们在缓冲区和回显方面与 scanf() 有着不同的特性,是 scanf() 不能替代的
  3. gets() 是专用的字符串输入函数,与 scanf() 相比,gets() 的主要优势是可以读取含有空格的字符串
  4. scanf() 可以一次性读取多份类型相同或者不同的数据,getchar()、getche()、getch() 和 gets() 每次只能读取一份特定类型的数据,不能一次性读取多份数据

关于缓冲区,我们将在后续的文章中展开讲解。

二、运算符与表达式

说到减肥就要运动,看似简单的运动也是能消耗热量的,那么本文的示例就来实现用行走的步数来计算消耗的热量值,具体代码如下所示:
在这里插入图片描述
从代码中可以看到,代码中的 int,printf,scanf 等函数,大家已经了解了,可能唯一看不懂的就是橙色框中的代码――运算符表达式,简单来说,它就是用来计算的,在数学中有各种运算符,例如:加、减、乘、除、大于等等。在C语言中也会用到各种运算符来计算数据。本小节就来详细讲解C语言中用到的运算符。

2.1 运算符与表达式

在C语言中,程序需要大量的运算,就必须利用表达式和运算符操纵数据,用来表示各种不同运算的符号称为运算符,而运算符和操作数组成的式子称为表达式。

2.1.1 运算符

在数学中,总会用到加、减、乘、除这四则运算,用符号表示分别是 +,-,*,÷ 同样,在C语言中也有加各种各样的运算符。例如,C语言中也有 加(+),减(-),乘(*),除(/) 当然除了这些运算符,还有其他运算符,如下表所示:

2.1.2 表达式

看到 表达式 就会不由自主地想到数学表达式,数学表达式是由数字、运算符和括号等组成,如下图所示:
在这里插入图片描述
数学表达式在数学当中是至关重要的,那么表达式在C语言中也同样重要,它是C语言的主体。在C语言中,表达式由操作符和操作数组成。根据表达式所含操作符的个数,可以把表达式分为简单表达式和复杂表达式两种,简单表达式是只含有一个操作符的表达式,而复杂表达式是包含两个或两个以上操作符的表达式,例如:

5+20;// 简单表达式
(iNumber+3)*Bate-2;// 复杂表达式

表达式本身什么事情也不做,只是返回结果值。在程序对返回的结果值不进行任何操作的情况下,返回的结果值不起任何作用。例如上面两行代码,这两个表达式只能得出结果,在表达式的左边没有常量和变量,那么,这个表达式得出的结果就不会被输出使用。表达式产生作用主要包括两种情况:

放在赋值语句的右侧
放在函数的参数中

表达式返回的结果值是有类型的。表结果值的数据类型取决于组成表达式的变量和常量的类型。

ps: 每个表达式的返回值都具有逻辑特性。如果返回值是非零的,那么该表达式返回真值,否则返回假值。通过这个特点,可以将表达式放在用于控制程序流程的语句中,这样就构建了条件表达式。

2.2 赋值运算符与赋值表达式

在开篇示例中,代码的第08行,使用了 = 这个运算符,
在这里插入图片描述
在数学中,它的含义是 等于,而在C语言中,与数学中的含义不同,它有另外的含义――赋值。 本小节就来介绍 = 在C语言的意义。

2.2.1 赋值运算符

在C语言中,= 就是赋值运算符,作用就是将一个数值赋给一个变量,如下图所示:
在这里插入图片描述
就像男孩给女孩礼物一样,女孩就相当于变量,男孩相当于赋值运算符,而礼物就相当于值。

2.2.2 赋值表达式

赋值表达式就是为变量赋值。在声明变量时,可以为其赋一个值,就是将一个常数或者一个表达式的结果赋值给一个变量,变量中保存的内容就是这个常量或者赋值语句中表达式的值。这就是为变量赋值。为变量赋值为常数的一般形式如下:

数据类型 变量名 = 常数; //举例:
char cChar = 'Z';
int iFirst = 1314;
float fPlace= 1314.520f;

赋值语句把一个表达式的结果值赋给一个变量。一般形式如下:

类型 变量名 = 表达式; //举例:float fPrice=fBase+Day*3;

这句代码得到赋值的变量 fPrice 称为左值,因为它出现的位置在赋值语句的左侧。产生值的表达式称为右值,因为它出现的位置在表达式的右侧。

注意: 这是一个重要的区别,并不是所有的表达式都可以作为左值,如常数只可以作为右值。

2.3 算术运算符与算术表达式

生活中,常常会遇到各种各样的计算,例如下图所示,某超市老板每天需要计算本日的销售金额,他就会将每种产品的销售额相加,来计算本日的总销售额,而此处的 相加 即为数学运算符的 ++ 在C语言中称为算术运算符。
在这里插入图片描述
除上图所示的 +,在开篇示例中,我们可以看到使用了 *,它也是运算符之一,那么,在C语言中除了这两种运算符,还有其他运算符吗?其实,C语言中有两个单目算术运算符、5个双目算术运算符。下面详细进行介绍。

2.3.1 算术运算符

算术运算符包括两个单目运算符(正和负),五个双目运算符,即乘法、除法、取模、加法和减法。具体符号和对应的功能如下表所示:
在这里插入图片描述
在上表中,取模运算符 % 用于计算两个整数相除得到的余数,并且取模运算符的两侧均为整数,如 7%4 的结果是3。其中的单目正运算符是冗余的,也就是为了与单目负运算符构成一对而存在的。单目正运算符不会改变任何数值,例如,不会将一个负值表达式改为正。

注意: 运算符 - 可作为减法运算符,此时为双目运算符。- 也可作负值运算符,此时为单目运算,如 -5 等;运算符 + 也是如此,当 + 作为加法运算符时,它为双目运算符,为正值运算符时,它为单目运算符。

2.3.2 算术表达式

如果在表达式中使用的是算术运算符,则将表达式称为算术表达式。下面是一些算术表达式的例子,其中使用的运算符就是之前所列出的算术运算符,代码如下:

Number=(3+5)/Rate;
Height=Top-Bottom+1;
Area=Height * Width;

需要说明的是,两个整数相除的结果为整数,如 7/4 的结果为1,舍去的是小数部分。但是,如果其中的一个数是负数时会出现什么情况呢?此时机器会采取 向零取整 的方法,即为 -1.75,取正后是1.75,取整之后是1或者2,采用向0靠拢,那么就要取1,最后结果则为-1。这种方法也称为 向零去尾,把小数点后的尾去掉 。

注意:如果用 +,-,*,/ 运算的两个数中有一个为实数,那么结果是 double 型,这是因为所有实数都按 double 型进行运算。实数的定义:
在这里插入图片描述

对取余运算的补充说明: 取余,也就是求余数,使用的运算符是 %。C语言中的取余运算只能针对整数,也就是说,% 的两边都必须是整数,不能出现小数,否则编译器会报错。另外,余数可以是正数也可以是负数,由 % 左边的整数决定:

  1. 如果 % 左边是正数,那么余数也是正数;
  2. 如果 % 左边是负数,那么余数也是负数。

示例:

#include <stdio.h>
int main() {
  printf(
      "100%%12=%d \n100%%-12=%d \n-100%%12=%d \n-100%%-12=%d \n",
      100 % 12, 100 % -12, -100 % 12, -100 % -12
  );
  return 0;
}

程序运行结果如下图所示:
在这里插入图片描述

2.3.3 自增/自减运算符

在C语言中还有两个特殊的运算符,即自增运算符 ++ 和自减运算符 -- 就像公交车的乘客数量,每上来一位乘客,乘客的数量就会增加一个,此时的乘客数量就可以使用自增运算符,而自增运算符的作用就是使变量值增加1。同样,自减运算符的作用就是使变量值减少1,例如,客车的座位,每上来一位乘客,客车的座位就会减少一个,此时座位这个变量就可以使用自减运算符。

注意:在表达式内部,作为运算的一部分,两者的用法可能有所不同。如果运算符放在变量前面,那么变量在参加表达式运算之前完成自增或者自减运算;如果运算符放在变量后面,那么变量的自增或者自减运算在变量参加了表达式运算之后完成,如下图所示:
在这里插入图片描述
常见错误:自加自减是单目运算符,因此表达式和常量不可以进行自加自减,例如:5++ 和 (a+5)++ 都是不合法的。

自增和自减的示例1:

/*================================================================
*   Copyright (C) 2023 AmoXiang All rights reserved.
*   
*   文件名称:05-test.c
*   创 建 者:AmoXiang
*   创建日期:2023年10月11日 16:56:39
*   描    述:
*
================================================================*/


#include <stdio.h>
#include <stdio.h>
int main() {
  int a = 10, b = 20;
  printf("a=%d, b=%d\n", a, b); // a=10, b=20
  ++a; //11
  --b; //19
  printf("a=%d, b=%d\n", a, b);// a=11, b=19
  a++;//12
  b--;//18
  printf("a=%d, b=%d\n", a, b);// a=12, b=18
  return 0;
}

程序运行结果如下图所示:
在这里插入图片描述
自增和自减的示例2:

/*================================================================
*   Copyright (C) 2023 AmoXiang All rights reserved.
*   
*   文件名称:05-test.c
*   创 建 者:AmoXiang
*   创建日期:2023年10月11日 16:56:39
*   描    述:
*
================================================================*/


#include <stdio.h>
int main() {
  int a = 10, b = 20, c = 30, d = 40;
  int a1 = ++a, b1 = b++, c1 = --c, d1 = d--;

  // ++a先自增 后参与运算 故a=11,a1=11
  printf("a=%d, a1=%d\n", a, a1);
  //b++先参与运算 后自增 故b=21,b1=20
  printf("b=%d, b1=%d\n", b, b1);
  //下面同理
  printf("c=%d, c1=%d\n", c, c1);//29 29
  printf("d=%d, d1=%d\n", d, d1);//39 40
  return 0;
}

程序运行结果如下图所示:
在这里插入图片描述
自增和自减的示例3:

/*================================================================
*   Copyright (C) 2023 AmoXiang All rights reserved.
*   
*   文件名称:05-test.c
*   创 建 者:AmoXiang
*   创建日期:2023年10月11日 16:56:39
*   描    述:
*
================================================================*/


#include <stdio.h>
int main() {
  int a = 12, b = 1;
  // 12 - 1 ==> c = 11, b = 0
  int c = a - (b--);  // ①
  // 13 - (-1) == > 14
  int d = (++a) - (--b);  // ②
  printf("c=%d, d=%d\n", c, d);
  return 0;
}

程序运行结果如下图所示:
在这里插入图片描述

2.4 关系运算符与关系表达式

在数学中,经常会比较两个数的大小。例如,如下图所示,爸爸的身高是1.80米,儿子的身高是1.20米,很明显,爸爸的身高比儿子的身高要高。这句话中,隐含的意思用到了比较,那么这个比较就是关系运算符的一种,在C语言中,关系运算符的作用就是判断两个操作数的大小关系。
在这里插入图片描述

2.4.1 关系运算符

关系运算符包括大于、大于等于、小于、小于等于、等于和不等于,如下表所示:
在这里插入图片描述
注意: 符号 >=(大于等于)<=(小于等于) 的意思分别是大于或等于、小于或等于。

2.4.2 关系表达式

关系运算符用于对两个表达式的值进行比较,返回一个真值或者假值。返回真值还是假值取决于表达式中的值和所用的运算符。其中真值为1,假值为0,真值表示指定的关系成立,假值则表示指定的关系不正确。例如:

18>3 /*因为18大于3,所以该关系成立,表达式的结果为真值 即为1*/
18>=3/*因为18大于3,所以该关系成立,表达式的结果为真值 即为1*/
18<3 /*因为18大于3,所以该关系不成立,表达式的结果为假值 即为0*/
18<=3 /*因为18大于3,所以该关系不成立,表达式的结果为假值 即为0*/
18==3 /*因为18不等于3,所以该关系不成立,表达式的结果为假值 即为0*/
18!=3 /*因为18不等于3,所以该关系成立,表达式的结果为真值 即为1*/

关系运算符通常用来构造条件表达式,用在控制程序的流程语句中,如 if 语句是用于判断条件而执行语句块,在其中使用关系表达式作为判断条件,如果关系表达式返回的是真值则执行下面的语句块,如果为假值就不去执行,如下图所示:
在这里插入图片描述
常见错误:例如 i==3 中的 == 是合法的关系运算符,而 i=3 中的 = 不是合法的关系运算符,它是赋值运算符。示例,将关系运算符的结果输出:

#include <stdio.h>
int main() {
  char c = 'k';
  int i = 1, j = 2, k = 3;
  float x = 3e+5, y = 0.85;
  // 'a'->97 + 5 --> 102 'f' < 'k' 'a' + 5 < c==> 1
  int result_1 = 'a' + 5 < c, result_2 = x - 5.25 <= x + y;
  printf("%d, %d\n", result_1, -i - 2 * j >= k + 1);
  printf("%d, %d\n", 1 < j < 5, result_2);
  printf("%d, %d\n", i + j + k == -2 * j, k == j == i + 5);
  return 0;
}

程序运行结果如下图所示:
在这里插入图片描述

2.5 逻辑运算符与逻辑表达式

目前考驾驶证的人越来越多了,报考驾驶证也是有一定要求的,当然,考不用型号的驾驶证对年龄要求也不同,例如考A1驾照要求年龄在 26~50 岁之间,B1驾照要求年龄是 21~50 岁之内,C1驾照要求年龄是 18~70 岁之内,在数学中可以如图8所示表示这些年龄区间,而在C语言中,需要如图9所示的形式表示年龄区间。
在这里插入图片描述
如图9所示的 && 就是逻辑运算符,在C语言中,不仅有 && 一个逻辑运算符,接下来我们就来详细介绍C语言中的逻辑运算符和逻辑表达式。

2.5.1 逻辑运算符

逻辑运算符有3种,如下表所示:
在这里插入图片描述

2.5.2 逻辑表达式

已经介绍过关系运算符可用于对两个操作数进行比较,使用逻辑运算符可以将多个关系表达式的结果合并在一起进行判断。其一般形式如下:

表达式 逻辑运算符 表达式

举例:

age > 18 && age < 70;//逻辑与运算,表示年龄大于18并且小于70
height > 155 || height < 190;//逻辑或运算,表示身高大于155cm或者小于190cm
!weight;//逻辑非运算,表示体重的非值

逻辑运算结果如下表所示:
在这里插入图片描述
逻辑与运算符和逻辑或运算符可以用于相当复杂的表达式中。一般来说,这些运算符用来构造条件表达式,用在控制程序的流程语句中,例如,在后续博文中介绍的 if,for,while 语句等。

注意:不要把逻辑与运算符 && 和逻辑或运算符 || 与后续小节要讲的位与运算符 & 和位或运算符 | 混淆。

逻辑运算符举例:

#include <stdio.h>
int main() {
  char c = 'k';
  int i = 1, j = 2, k = 3;
  float x = 3e+5, y = 0.85;
  printf("%d,%d\n", !x * !y, !!!x); // 0, 0
  //i && j ==> 2 x||2-3 ==> 只要有一个不为0 就返回1
  // i < j && x < y ==> (i<j) && (x<y) ==> 1 && 0 ==> 0
  printf("%d,%d\n", x || i && j - 3, i < j && x < y); // 1
  printf("%d,%d\n", i == 5 && c && (j = 8), x + y || i + j + k);
  return 0;
}

程序运行结果如下图所示:
在这里插入图片描述

2.6 位运算符与位表达式

学C语言可能总会听见,int占多少字节,char占多少字节,可能会有很多人疑问,字节是什么意思?那么在C语言中,说到字节就不得不说位。位是计算机存储数据的最小单位。一个二进制位可以表示两种状态(0和1),多个二进制位组合起来便可表示多种信息。

一个字节通常是由8位二进制数组成,当然有的计算机系统是由16位组成,本文中提到的一个字节指的是由8位二进制组成的。如下图所示,8位占一个字节,16位占两个字节。
在这里插入图片描述
了解了字节和位的关系,本小节我们就来详细的介绍位运算符和位运算表达式。

2.6.1 位运算符

C语言既具有高级语言的特点,又具有低级语言的功能,C语言和其他语言的区别是完全支持按位运算,而且也能像汇编语言一样用来编写系统程序,这是C语言的特色之处。下表所示为C语言提供的位运算符:
在这里插入图片描述

2.6.2 位运算表达式

假设有两个变量 m 和 n,用来保存逻辑结果,m 和 n 可以进行下表所示的4种逻辑运算。其中 1 表示 true(真),0 表示 false(假)。
在这里插入图片描述

说明:按位与,遇0则0;按位或,遇1则1;按位异或相同为1,不同为0。不要将逻辑运算符和位运算符混淆,逻辑运算符返回的值是真假值,位运算符是二进制运算的运算符,最终计算的是一个数值。

上表介绍了按位与、按位或、取反以及按位异或,接下来介绍左移和右移。左移 运算符 << 是双目运算符。其功能是把 << 左边的运算数的各二进位全部左移若干位,由 << 右边的数指定移动的位数,高位丢弃,低位补0。例如,m<<2 即把 m 的各二进制位向左移动两位。假设 m=39,那么 m 在内存中的存放情况如下图所示:
在这里插入图片描述
若将 m 左移两位,则在内存中的存储情况如下图所示。m 左移两位后由原来的39变成了156。
在这里插入图片描述
右移运算符 >> 也是双目运算符。其功能是把 >> 左边的运算数的各二进制位全部右移若干位,>> 右边的数指定移动的位数。例如 m=30 在内存中的存储情况如下图所示:
在这里插入图片描述
m>>3 即 30 右移3位变成3,其存储情况如下图所示:
在这里插入图片描述
以上是正数移位,负数和正数在计算机存储形式不同,同样使用右移讲解负数移位,例如 m = -30,首先写出 -30,取绝对值,即 30 的二进制数,然后在这基础上按位取反,得到的二进制码再加一,得到下图所示的二进制数,这个数值就是 -30 在内存中存储的情况。
在这里插入图片描述
表达式 m>>3 的含义是将 -30 右移 3 位,结果是 -4,,其存储情况如下图所示:
在这里插入图片描述

2.7 逗号运算符与逗号表达式

提到逗号,它在我们心中是分隔符的作用,例如有这样一句话:救救我舅舅不救我舅舅我舅舅就没救了,这句话看起来(读起来)有点晕,到底是救舅舅,还是不救舅舅,这时候,可以加上逗号,改为 救救我舅舅,不救我舅舅,我舅舅就没救了,如果这样写就能明确意思了。那逗号在这里起到了一个断句的作用,在C语言中,逗号不仅可以用作分隔符,还可以用在表达式中。逗号表达式的一般形式如下:

表达式1,表达式2,,表达式n

逗号表达式的求解过程是:先求解表达式1,再求解表达式2,一直求解到表达式n,整个逗号表达式的值是表达式n的值。 逗号表达式称为顺序求值运算符。就像数学中常常求几何问题一样,需要按顺序写解题步骤。例如下面使用逗号运算符的代码:

Value=1+3,1+2,15+27;

上面语句中 Value 所得到的值为4,而非 42。由于赋值运算符的优先级比逗号运算符的优先级高,因此先执行赋值的运算。如果要先执行逗号运算,则可以使用括号运算符,代码如下:

Value=(1+3,1+2,15+27);

这句代码最终的结果是 42,因为使用了括号运算符,因此先计算括号内的表达式,然后再执行赋值的运算。

2.8 复合赋值运算符

复合赋值运算符是C语言一种缩写形式,可使得变量操作的描述方式更为简洁。例如 *= 复合,如下图所示:
在这里插入图片描述
如果在程序中为一个变量赋值,代码如下:

Value=Value*3;

这一行语句是对一个变量进行赋值操作,值为这个变量本身与一个整数常量3相乘的结果值。使用复合赋值运算符可以实现同样的操作。例如上面的语句可以改写成如下语句:

Value*=3;

这种描述更为简洁。关于上面两种实现相同操作的语句,赋值运算符和复合赋值运算符的区别在于:

  1. 为了简化程序,使程序精练
  2. 为了提高编译效率。

下表是其他复合赋值运算符:
在这里插入图片描述

2.9 优先级和结合性

在数学中,就规定了优先级,如果没有规定,就会出现争先恐后先计算情况。数学中规定:有括号先计算括号里的,没有括号先计算乘除再计算加减。那在C语言中,有这么多运算符,也需要进行规定,如下表所示是各个运算符的优先级和结合性,优先级1代表级别最高,优先级15表示级别最低。
在这里插入图片描述
当一个表达式中出现多个运算符时,C语言会先比较各个运算符的优先级,按照优先级从高到低的顺序依次执行;当遇到优先级相同的运算符时,再根据结合性决定先执行哪个运算符:如果是左结合性就先执行左边的运算符,如果是右结合性就先执行右边的运算符。C语言的运算符众多,每个运算符都具有优先级和结合性,还拥有若干个操作数,为了方便记忆和对比,我们在上表中将它们全部列了出来。对于没有学到的运算符,大家不必深究,一带而过即可,等学到时再来回顾。

至此今天的学习就到此结束了,笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习C语言的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!


在这里插入图片描述

????好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
????如果我的博客对你有帮助、如果你喜欢我的博客内容,请 点赞评论收藏 一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
?编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了 关注 我哦!

文章来源:https://blog.csdn.net/xw1680/article/details/134046719
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。