衡庐浅析·C语言程序设计·第二章·运算符及其优先级关系

       本文适用于大学的期中期末考试、专升本(专接本、专插本)考试、408等考研预科。如有相关题目疑问或建议欢迎在评论区进行互动。

       转载请标明出处。


本章我们将介绍运算符的相关知识。在C语言学习的初级阶段,我们仅需要掌握以下十二种运算符,即可在习题作业中表现得游刃有余,它们分别是:算数运算符、关系运算符、逻辑运算符、赋值运算符、条件运算符、逗号运算符、指针运算符、位操作运算符、自增自减运算符,以及特殊运算符、强制类型转换运算符、求字节数运算符。

我们以目录的形式,将这些运算符的优先级关系整理为表格,方便大家快速定位查阅。

C运算符的优先级(目录)
优先级运算符名称或含义使用形式结合方向说明
1[]数组下标数组名[常量表达式]从左到右
()圆括号(表达式)/函数名(形参表)
.成员选择(对象)对象.成员名
->成员选择(指针)对象指针->成员名
2-负号-表达式从右到左单目运算符
(类型)强制类型转换(数据类型)表达式
++自增++变量名/变量名++单目运算符
--自减--变量名/变量名--单目运算符
*取值运算符*指针变量单目运算符
&取地址运算符&变量名单目运算符
!逻辑非!表达式单目运算符
~按位取反~表达式单目运算符
sizeof长度运算符sizeof(表达式)
3/表达式/表达式从左到右双目运算符
*表达式*表达式双目运算符
%余数(取模)整型表达式/整型表达式双目运算符
4+表达式+表达式从左到右双目运算符
-表达式-表达式双目运算符
5<<左移变量<<表达式从左到右双目运算符
>>右移变量>>表达式双目运算符
6>大于表达式>表达式从左到右双目运算符
>=大于等于表达式>=表达式双目运算符
<小于表达式<表达式双目运算符
<=小于等于表达式<=表达式双目运算符
7==等于表达式==表达式从左到右双目运算符
!=不等于表达式!=表达式双目运算符
8&按位与表达式&表达式从左到右双目运算符
9^按位异或表达式^表达式从左到右双目运算符
10|按位或表达式|表达式从左到右双目运算符
11&&逻辑与表达式&&表达式从左到右双目运算符
12||逻辑或表达式||表达式从左到右双目运算符
13  ?  :条件运算符表达式1?表达式2:表达式3从右到左三目运算符
14=赋值运算符变量=表达式从右到左
/=除后赋值变量/=表达式
*=乘后赋值变量*=表达式
%=取模后赋值变量%=表达式
+=加后赋值变量+=表达式
-=减后赋值变量-=表达式
<<=左移后赋值变量<<=表达式
>>=右移后赋值变量>>=表达式
&=按位与后赋值变量&=表达式
^=按位异或后赋值变量^=表达式
|=按位或后赋值变量|=表达式
15,逗号运算符表达式,表达式,……从左到右顺序运算

注[1]:同一优先级下,运算次序由结合方向所决定。

注[2]:带有小括号的标题跳转有误,请手动前往或点击左侧大纲目录。

1.1  数组下标  []

在C语言中,对于一个数组,我们常常这样来定义:

数据类型 数组名[常量表达式];  //常量表达式必须是整型

数组的下标从0开始。

举个例子:

int a[10];  //定义一个整型数组a,该数组从a[0]开始,到a[9]结束,共十个元素

对于数组的详细解读我们后面会单写一个章节,在这里我们仅需要了解它作为“第一优先级”的成员,其优先级是凌驾于其他符号之上的。

1.2  圆括号  ()

圆括号在C语言中常常有以下三种用法:

函数调用:使用圆括号来调用函数,并传入参数。

在解决复杂问题的时候,我们往往不能只用一个main函数就编写完整个C语言代码,这时候就需要定义多个类型的函数,并对它们进行来回的调用以达到目的。同样的,我们会在后面的章节单独学习函数的相关知识,这里我们仅仅举一个在函数调用中使用圆括号的例子。

int secondary(int b)  //secondary为被调函数
{
    return b;  //返回b的值
}
 
int main()  //main为主调函数
{
    int a = 5;
}

控制运算符优先级:使用圆括号可以强制控制或改变运算符的优先级和结合性。

举个例子:

int x = 10, y = 20;
int sum = x + y * 2;  //默认乘法优先
int sum_with_parentheses = (x + y) * 2;  //指定加法优先

控制运行流程:用于条件语句如if、while、for等,以及在函数中定义作用域。

举个例子:

int i;
scanf(" %d ", &i );
if( ( i > 0 ) && ( i < 10 ) )  //如果输入的i的值大于0且小于10,则执行花括号内的语句
  {
    printf( "这是一个位于0到10之间的数字\n" );
  }

1.3  成员选择(对象)  .

成员运算符在结构体中常常使用,如:

struct manager mgr1, *p;
p = &mgr1;  //p是指向结构体变量的指针,此时的*p即等价于stu1
mgr1.id = 202477;  //管理员1的id为202477,格式为:结构体变量名.成员名;

等价于:

(*p).id = 202477;  //管理员1的id为202477,格式为:(*指针名).成员名;

等价于:

p->id = 202477;  //管理员1的id为202477,格式为:指针名->成员名;

1.4  成员选择(指针)  ->

指向运算符的相关举例已举例于1.3末。

2.1  负号  -

即表示相关数据的负值,通常有以下两种用法:

放在变量前:

int num = 5;
int negNum = -num;

放在常量前:

int num;
num = -5;

2.2  强制类型转换  (类型)

使用圆括号将需要转换的数据类型括起来,作为强制类型转换的运算符。例如:

(int) 2.71;  //将浮点数2.71强制转换为整数类型
(int) 2.71 + 3.14;  //将浮点数2.71强制转换为整数类型,再加上浮点数3.14,结果为浮点数5.14
(int) (2.71 + 3.14);  //将浮点数2.71与浮点数3.14相加,得到结果为浮点数5.85后,再进行强制转换,最终结果为5

其是将小数部分进行截断处理,即结果为2,而非四舍五入的3。

而在C语言中,除了强制类型转换外,还存在一种自动转换机制,其规则如图所示:

2.3  自增  ++

使单个变量的值加一,一般用法有前缀、后缀之分:

++变量名  //前缀
变量名++  //后缀

这里是初学者经常犯错的地方之一,也是各大类型的基础考卷所需要考察的“基本功”之一,注意辨别并体会以下这个例子:

int m = 2,n;
m++;  //即 m = m + 1,m的值为3
++m;  //即 m = m + 1,m的值为3
n = m++;  //即 n = m; m = m + 1,先操作再自增,n的值为2,m的值为3
n = ++m;  //即 m = m + 1; n = m,先自增再操作,n的值为3,m的值为3

我们可以记忆这样一个口诀:变量相连即操作,变量不连自增/减

其中是否相连可以以等号两边为变量还是自增/减运算符作为判断。

2.4  自减  --

类似的,自减也同样有前缀、后缀之分:

--变量名  //前缀
变量名--  //后缀

同样适用于2.3所提到的例子:

int m = 2,n;
m--;  //即 m = m - 1,m的值为1
--m;  //即 m = m - 1,m的值为1
n = m--;  //即 n = m; m = m - 1,先操作再自增,n的值为2,m的值为1
n = --m;  //即 m = m - 1; n = m,先自增再操作,n的值为1,m的值为1

2.5  取值运算符  *

取值运算符用于取得指针指向的变量的值,例如:

int x = 10;
int *p;
p = &x;  //使用取值运算符获取x的值
int y = *p;  //*p 将获取指针 p 指向的内存地址中的值,也就是变量 x 的值
printf("x的值是: %d\n", x);  //程序将打印出 x 的值是 10
printf("y的值是: %d\n", y);  //程序将打印出 y 的值是 10

2.6  取地址运算符  &

取址运算符用于获取操作数的内存地址。

基本的取址操作例如:

int x = 10;  //定义一个整型变量x并初始化为10
int *p;  //定义一个指向整型的指针p
p = &x;  //使用取址运算符 & 来获取 x 的内存地址并将其存储在 p 中
printf("Address of x: %p", &x);  //使用 printf函数 打印出 x 的地址

或者我们可以用其取得数组的地址:

int arr[5] = {1, 2, 3, 4, 5};  //定义一个有5个元素的数组arr并初始化
int (*p)[5];  //定义一个指向有5个整型元素的数组的指针p
p = &arr;  //使用取址运算符 & 来获取 arr 的内存地址并将其存储在 p中 
printf("Address of arr: %p", &arr);  //使用 printf函数 打印出 arr 的地址

类似的,我们也可以这样来取得指针的地址:

int x = 10;  //定义一个整型变量x并初始化为10
int *p = &x;  //定义一个指向整型的指针 p 并使用取址运算符 & 来获取 x 的内存地址
int **pp;  //定义一个指向整型指针的指针pp
pp = &p;  //使用取址运算符 & 来获取 p 的内存地址并将其存储在 pp 中
printf("Address of p: %p", &p);  //使用 printf函数 打印出 p 的地址

2.7  逻辑非 !

其作用是对其操作数的逻辑状态进行反转,如果操作数为真,那么逻辑非运算符的结果就为假。如果操作数为假,那么逻辑非运算符的结果就为真。且它只能对一个操作数进行操作

使用逻辑非运算符可以对变量进行操作:

int a = 10;
if(!a) printf("a is false\n");
else printf("a is true\n");

/*变量a是一个非零值,因此,其逻辑值为真。运用逻辑非运算符 ! 后,结果为假。*/

使用逻辑非运算符也可以对常量进行操作:

int a = 0;
if(!0) printf("0 is false\n");
else printf("0 is true\n");

/*常量0本身的逻辑值就是假。运用逻辑非运算符 ! 后,结果仍然为假。*/

我们甚至可以使用逻辑非运算符对函数返回值进行操作: 

int isEven(int num)
{
  return (num % 2 == 0);
}
 
int main() 
{
  int num = 4;
  if(!isEven(num)) printf("%d is odd\n", num);
  else printf("%d is even\n", num);
  return 0;
}

/*函数isEven()会判断一个数是否为偶数,如果是偶数,则返回真,否则返回假。逻辑非运算符 ! 会将这个返回值反转,从而得出结果。*/

2.8  按位取反  ~

按位取反运算符可以用来对一个二进制数按位取反,即将二进制数的每一位(0变为1,1变为0)。

值得注意的是:

按位取反运算符是单目运算符,只需要一个操作数。

对无符号整数进行按位取反操作,结果会保留在无符号整数的范围内。

对有符号整数进行按位取反操作,可能会因为符号位的变化而导致结果不是预期的

例如:

直接使用按位取反运算符:

unsigned int num = 10;
unsigned int result = ~num;
printf("%u\n", result);

 使用按位取反运算符进行一系列操作

unsigned int num = 10;
unsigned int result;
result = ~num;
result &= 0x0F;  //取反后,与0x0F进行与操作,实际上是取反后保留后四位
printf("%u\n", result);

使用按位取反运算符和循环实现取反:

unsigned int num = 10;
unsigned int result = 0;
int i;
for (i = 0; i < 32; i++) 
  {
      if ((num & (1 << i)) == 0) 
        {
          result |= (1 << i);
        }
  }
printf("%u\n", result);

2.9  长度运算符  sizeof

长度运算符用于获取数组、字符串、指针的长度,其返回的是指定类型或对象的长度(大小),单位是字节。

使用sizeof运算符获取数组长度:

int arr[] = {1, 2, 3, 4, 5, 6};
printf("The length of the array is: %lu\n", sizeof(arr)/sizeof(arr[0]));

/*使用sizeof运算符来获取整个数组的大小和一个元素的大小,通过除法将其转换为元素的数量,从而得到数组的长度。*/

使用sizeof运算符获取字符串长度:

char str[] = "Hello, World!";
printf("The length of the string is: %lu\n", sizeof(str)/sizeof(str[0]) - 1);

值得注意的是,在字符串的长度计算中,我们需要减去1,因为 \0 是字符串结束的标志。

使用sizeof运算符获取指针长度:

int *ptr;
printf("The length of the pointer is: %lu\n", sizeof(ptr));

/*使用sizeof运算符来获取指针的大小,在32位机器上,指针的长度通常是4个字节,在64位机器上,指针的长度通常是8个字节。*/

但sizeof运算符有一个弊端,即不能用于求得动态分配的内存的大小,例如使用malloc或calloc函数分配的内存。

3.1  除  /

区别于数学上的除法,若双目(两个运算对象)均为int,则除法为整除(进行截断处理),例如:

1/2 = 0;
1.0/2 = 0.5;
1/2.0 = 0.5;  //若两边有任意一个浮点数,则结果也是浮点数,即不整除

3.2  乘  *

类似于数学上的乘法,但注意乘法符号不能用数学中的 × 或 · 代替,且不能省略。

3.3  余数(取模)  %

取模运算符是用来获取两个整数相除之后的余数。例如:

1 % 2 = 1;
-10 % 3 = -1;
10 % -3 = 1;
'a' % 'A' = 32;

注意:求余(取模)时双目必须是整型,且结果的符号于被求余数的符号一致

且0 % 任何数都为0,小 % 大 = 小本身。

4.1  加  +

类似于数学上的加法

4.2  减  -

类似于数学上的减法

5.1  左移  <<

用于将一个表达式的值向左移动指定的位数。其中左移一位相当于乘以2,左移两位相当于乘以4,以此类推。

左移正数:

int a = 1;
printf("%d\n", (a << 2));  //输出 8

 左移负数:

int a = -1;
printf("%d\n", (a << 2));  //输出 -8

/*将变量a(一个负数)的值左移两位,在二进制表示中,负数以补码形式存储,即左移一个负数实际上是在移动其绝对值的二进制表示,并在高位插入0。所以输出结果为-8。*/

 左移超过int位数的值:

int main() {
int a = 1;
printf("%d\n", (a << 33));  //输出 0

/*将变量a的值左移33位。在32位系统中,int类型的值只能存储在32位中,所以,左移超过这个数的位数实际上是没有意义的,输出结果为0。*/

 左移时溢出:

unsigned int a = 1073741823;  //二进制表示为 01111111111111111111111111111111
printf("%u\n", (a << 1));  //输出 2147483644

/*将一个非常大的数左移一位。由于是无符号整数,左移一位会在低位插入0,并且自动舍弃最高位的1。所以,输出结果为2147483644。*/

逻辑左移是将一个数的二进制表示形式向左移动指定的位数,在右边补零。逻辑左移不会改变数字的符号位。例如:

unsigned int num = 1;  //二进制表示为 00000001
int shift = 2;  //左移2位// 逻辑左移2位
num = num << shift;  //输出结果,二进制表示为 00000100
printf("%u\n", num); 

/*定义一个无符号整数num,初始值为1(二进制表示为00000001)。定义另一个整数shift,表示要左移的位数,这里是2。然后使用<<运算符对num进行逻辑左移操作,最后输出移位后的结果。*/

算数左移和逻辑左移在效果上是一样的:它们都将数值的二进制表示向左移动指定的位数,并且在右侧用零来填充。例如:

int num = 4;  //二进制表示为 0000 0100 (对于大多数系统,int是32位或64位,但这里只显示最低有效位)
int shift = 2;   //算数左移2位
num = num << shift;  //输出结果,二进制表示为 0001 0000 (十进制为16)
printf("%d\n", num);

/*定义一个有符号整数num,初始值为4(二进制表示为0000 0100),将它左移2位,得到的结果是16(二进制表示为0001 0000)。*/

5.2  右移  >>

与左移相对,同样存在右移的操作。

逻辑右移:符号位不变,空出的位以0填充。

算术右移:符号位不变,空出的位以符号位的值填充。

int a = -1;  //二进制表示为 1111 1111
int b = a >> 1;  //逻辑右移1位,结果为 011111111 (十进制为 127)
int c = a >> 16;  //逻辑右移16位,结果为 11111111111111111111111111111111 (十进制为 2147483647)
int d = -2;  //二进制表示为 1111 1110
int e = d >> 1;  //算术右移1位,结果为 101111111 (十进制为 126),符号位保持不变
printf("b = %d, c = %d, e = %d\n", b, c, e);

/*定义两个有符号整数a和d,它们的二进制表示中都包含一个1。使用逻辑右移运算符(>>)对a进行操作,使用算术右移运算符(>>)对d进行操作。可以看到当逻辑右移时,空出的位以0填充;当算术右移时,空出的位依然保持原有的符号位的值。*/

6.1  大于  >

比较两个值的大小,如果左侧的值大于右侧的值,结果为真(!0),否则结果为假(0)。

6.2  大于等于  >=

比较两个值的大小,如果左侧的值大于或等于右侧的值,结果为真(!0),否则结果为假(0)。

6.3  小于  <

比较两个值的大小,如果左侧的值小于右侧的值,结果为真(!0),否则结果为假(0)。

6.4  小于等于  <=

比较两个值的大小,如果左侧的值小于或等于右侧的值,结果为真(!0),否则结果为假(0)。

7.1  等于  ==

用于比较两个操作数是否相等,如果相等则返回1(真),如果不相等则返回0(假)。

注意:

在C语言中 = 是赋值, == 是判断左右两边是否相等。

7.2  不等于  !=

用于比较两个操作数是否不相等,如果不相等则返回1(真),如果相等则返回0(假)。

在C语言中,真一般表示为1或!0,假一般表示为0。

8.1  按位与  &

用于对两个整数执行按位与操作,即比较两个整数的二进制表示,并对每一位执行逻辑与操作。如果两个整数在某个位置上的二进制位都是1,则结果在该位置上的二进制位也是1;否则,结果是0。

9.1  按位异或  ^

用于比较两个二进制数值的不同位。当两个对应的二进制位相异时结果为1,相同时结果为0。

10.1  按位或  |

用于两个整数对应的二进制位执行或操作。如果其中一个数的某一位为1,或者两个数的相应位都为1,那么结果的对应位就为1。否则,结果的对应位就为0。

11.1  逻辑与  &&

当两个操作数中有一个为假(0)时,其结果为假。如果两个操作数都为真(!0),则结果为真。

即:

真 && 真 结果为真

假 && 假 结果为假

真 && 假 结果为假

假 && 真 结果为假

12.1  逻辑或  ||

当两个操作数中有一个为真(!0)时,其结果为真。如果两个操作数都为假(0),则结果为假。

即:

真 || 真 结果为真

假 || 假 结果为假

真 || 假 结果为真

假 || 真 结果为真

特别注意的是,逻辑与和逻辑或存在短路现象!且短路机制凌驾于所有优先级之上!

在 x && y 运算中,当 x 为假时, y 将不再运算。

在 x || y 运算中,当 x 为真时, y 将不再运算。

例如:

int a = 2,b = 1,c = 3,d = 4,t = 9;
a < b && (t = c > d);  //假。后面短路不再计算,t值为9
(a > b++) || (t = c++ > --d);  //真。结合性自左向右,前面结果为真,后面短路不再计算,b值为2.其他值不变
(a > b++) && (t = c++ > --d);  //假。后面结果为真假,c值为2,d值为3,t值为0

13.1  条件运算符  ?  :

条件运算符的一般形式为:表达式1?表达式2:表达式3

这里,表达式1是条件表达式,表达式2和表达式3是两个表达式。条件运算符的执行流程是:先计算条件表达式1的值,如果表达式1的值为真,则执行并返回表达式2的值,如果表达式1的值为假,则执行并返回表达式3的值。

14.1  赋值运算符  =

用于将右侧的表达式的值赋给左侧的操作数。

初学阶段需要掌握以下八类:

基本赋值:

int a = 10;  //将10赋值给整型变量a
double b = 20.5;  //将20.5赋值给双精度浮点型变量b
char c = 'c';  //将字符'c'赋值给字符变量c

将一个变量的值赋给另一个变量:

int x = 10;
int y = x;  //将变量x的值赋给变量y

复合赋值运算符:

int a = 10;
a += 5;  //等价于a = a + 5,a的值变为15
a -= 3;  //等价于a = a - 3,a的值变为12
a *= 2;  //等价于a = a * 2,a的值变为24
a /= 2;  //等价于a = a / 2,a的值变为12
a %= 3;  //等价于a = a % 3,a的值变为0

解引用赋值:

int array[5] = {1, 2, 3, 4, 5};
int *ptr = array;
*(ptr + 1) = 10;  //将10赋值给ptr指向的地址的下一个元素,即array[1]

结构体赋值:

struct Student
{
  char name[20];
  int age;
};

struct Student stu1 = {"Tom", 18};
struct Student stu2;
stu2 = stu1;  //将结构体stu1的值赋给结构体stu2

枚举类型赋值:

enum Day {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
 
enum Day today;
today = Mon;  //将枚举类型中的Mon赋值给枚举变量today

指针赋值:

int x = 10;
int *ptr;
ptr = &x;  //将变量x的地址赋值给指针ptr

返回值赋值:

int func(int a)
{
  return a * 2;
}
 
int result;
result = func(5);  //将函数func返回的值赋值给变量result

14.2  除后赋值  /=

复合赋值的一种,将左操作数除以右操作数,并将结果赋值给左操作数。

14.3  乘后赋值  *=

复合赋值的一种,将左操作数乘以右操作数,并将结果赋值给左操作数。

14.4  取模后赋值  %=

复合赋值的一种,将左操作数对右操作数除求余(取模)后,再将结果赋值给左操作数。

14.5  加后赋值  +=

复合赋值的一种,将左操作数加上右操作数,并将结果赋值给左操作数。

14.6  减后赋值  -=

复合赋值的一种,将左操作数减去右操作数,并将结果赋值给左操作数。

14.7  左移后赋值  <<=

复合赋值的一种,变量值根据表达式值所规定的位数进行左移,此变量的值不变。

14.8  右移后赋值  >>=

复合赋值的一种,变量值根据表达式值所规定的位数进行右移,此变量的值不变。

14.9  按位与后赋值  &=

复合赋值的一种,将其左操作数和右操作数按位与,然后将结果赋值给左操作数。

14.10  按位异或后赋值  ^=

复合赋值的一种,将其左操作数和右操作数按位异或,然后将结果赋值给左操作数。

14.11  按位或后赋值  |=

复合赋值的一种,将其左操作数和右操作数按位或,然后将结果赋值给左操作数。

15.1  逗号运算符  ,

将两个或多个表达式连接起来。使用逗号运算符时,从左到右计算每个表达式,但整个表达式的值取最右边的表达式的值。


《衡庐浅析·C语言程序设计·第二章·运算符及其优先级关系》部分到这里就结束了,下一个章节我们将会学习C语言中三种基本结构的相关知识,正式迈入C语言程序设计的大门,敬请期待,也欢迎大家在评论区进行互动!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值