C语言 操作符详解

简介: C语言 操作符详解

文章目录

前言

在之前学习的过程中,有大致了解过一些操作符,但还有一些操作符没见过,所以这篇文章将会把操作符都过一遍。本文章所有代码依赖的平台:(Visual Studio2017 && Windows)

一、算术操作符

 +   -  *  /  %

算术操作符这里加减乘除就不过多的了解了

这里需要注意的有2点:

a. / 和 %的区别

#include<stdio.h>
int main()
{
  int a1 = 3 / 5;//取商
  int a2 = 3 % 5;//取余
  printf("a1 = %d\na2 = %d\n", a1, a2);//a1 = 0; a2 = 3;
  return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
int main02()
{
  int a = 5 / 3.0;//ok
  //int a = 5 % 3.0;//err
  return 0;
}

b. 小数除法

#include<stdio.h>
int main()
{
  //观察a1、a2、a3的结果
  float a1 = 3.0 / 5;
  float a2 = 3 / 5.0;
  float a3 = 3.0 / 5.0;
  printf("a1 = %.1f\na2 = %.1f\na3 = %.1f\n", a1, a2, a3);//a1,a2,a3 = 0.6
  return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//当执行上面上面这段程序时 - warning:从double到float截断 
//因为直接写6.0(没有定义的情况)这种小数时,编译器会默认为double类型
//更适合的写法:
//float a1 = 3.0f / 5f;
//double a2 = 3.0 / 5;

小结:

  • / 和 % 都是执行除法运算,但是 / 算的是商,%算的是余数
  • 除了 % 操作符之外 ,其它的操作符可以用于整数和浮点数;% 操作符的2个操作数必须为整数
  • / 操作符如果2个操作数都为整数,执行整数除法;而只要有任何1个操作数为float类型或doble类型,执行的是小数除法

二、移位操作

<<  >>

<<:左移操作符

#include<stdio.h>
int main()
{
  int a = 2;
  //把a的二进制位向左移动1位
  int b = a << 1;
  printf("b = %d\n", b);//4
  //注意在对a进行移位后,并不会改变a本身
  printf("a = %d\n", a);//2
  return 0;
}


`>>:右移操作符:

1.算术右移:右边丢弃,左边补原符号位

2.逻辑右移:右边丢弃,左边补0

/***********************************************************************

目的:验证当前机器是算术右移还是逻辑右移

分析:在右移操作符下,正数的算术右移和逻辑右移是一样的。所以这里采用负数来验证。(对于移位操作时,移动的是二进制补码:而正数的原反补相同,移动原码 = 移动补码;而负数需要转换为补码再移动)

平台:Visual studio 2017 && windows

*************************************************************************/

整数的二进制表示形式:

原码:直接根据数值写出的二进制序列就是原码(相当于屏幕上能看到的数)

反码:原码的符号位不变,其它位按位取反

补码:反码+1 (内存中的存储形式)

#include<stdio.h>
int main()
{
  int a = -1;
  //把a的二进制位向右移动1位
  //由些可见,当前编译器采用的是算术右移
  int b = a >> 1;
  printf("b = %d\n", b);//-1
  return 0;
}


注意:

int a = 10;

int b = a << -5; // err,这是标准未定义的语法。属于垃圾代码

小结:

  • 移位操作符移动的是二进制位
  • 左移操作符遵循左边丢弃,右边补0的原则
  • 右移操作符则有2个情况:1.算术右移(右边丢弃,左边补原符号位)  2.逻辑右移(右边丢弃,左边补0)
    经验证当前机器使用算术右移

三、位操作符

&   |  ^

#include<stdio.h>
//&:按(二进制位)位与 -> 对应的二进制位有1个或2个为0,则为0;否则为1
int main()
{
  int a = 3;
  int b = 5;
  //...011 -> 3
  //...101 -> 5
  //...001 -> 1
  int c = a & b;
  printf("c = %d\n", c);//1
  return 0;
}
//|:按(二进制位)位或-> 对应的二进制位有1个或2个为1,则为1;否则为0
int main02()
{
  int a = 3;
  int b = 5;
  //...011 -> 3
  //...101 -> 5
  //...111 -> 7
  int c = a | b;
  printf("c = %d\n", c);//7
  return 0;
}
//^:按(二进制)位异或 -> 对应的二进制位,相同为0,相异为1
int main03()
{
  int a = 3;
  int b = 5;
  //...011 -> 3
  //...101 -> 5
  //...110 -> 6
  int c = a ^ b;
  printf("c = %d\n", c);//6
  return 0;
}

小结:

  • &:按(二进制位)位与:对应的二进制位,同真为真,其余为假
  • | :按(二进制位)位或:对应的二进制位,同假为假,其余为真
  • ^:按(二进制)位异或 :对应的二进制位,相同为0,相异为1
  • 它们的操作数必须为整数

四、赋值操作符

=  +=  -=  *=  /=  %=  >>=  <<=  &=  |=  ^=

1.连续赋值

int main()
{
  int a = 10;
  int x = 0;
  int y = 20;
  //a = x = y + 1;//连续赋值语法支持,但不建议这样写
  //这样写可读性更高
  x = y + 1;
  a = x;
  return 0;
}

2.复合赋值,这里就介绍1个,其它的复合赋值都是相同的用法

int main()
{
  int a = 10;
  a = 100;
  //a = a + 100;
  a += 100;//复合赋值 -> 同a = a + 100
  return 0;
}

3.注意:=是赋值操作符

    ==等于

五、单目操作符

!  -  +  &  sizeof  ~  –  ++  *  (类型)

注:单目操作符只有一个操作数

#include<stdio.h>
//!:非,即真为假,假为真
int main()
{
  int flag = 5;
  if(flag)
    printf("hehe\n");
  if(!flag)
    printf("haha\n");
  return 0;
}
//-----------------------------------------------------------------------------
//-:负
int main02()
{
  int a = 10;
  a = -a;
  printf("%d\n", a);//-10
  return 0;
}
//-----------------------------------------------------------------------
//sizeof:计算变量所占内存的大小(单位:字节)
int main03()
{
  int a = 10;
  //sizeof的三种形式:
  printf("%d\n", sizeof(a));
  printf("%d\n", sizeof(int));
  printf("%d\n", sizeof a);//有的人会认为sizeof是一个函数:函数名()。这种写法说明了sizeof是一个操作符
  //printf("%d\n", sizeof int);//err
  return 0;
}
//sizeof计算数组大小
int main04()
{
  int arr[10] = {0};
  printf("%d\n", sizeof(arr));
  printf("%d\n", sizeof arr);
  printf("%d\n", sizeof(int [10]));//sizeof(数据类型);对于数组的类型把数组名去掉,剩下的就是数据类型
  return 0;
}
//观察下面程序,分析结果?
int main05()
{
  short s = 5;
  int a = 10;
  printf("%d\n", sizeof(s = a + 2));//2
  printf("%d\n", s);//5
  return 0;
  //sizeof括号中放的表达式是不参与运算的
}
//-----------------------------------------------------------------------
//~:按(二进制位)位取反
int main06()
{
  int a = -1;
  //10000000 00000000 00000000 00000001 - 原
  //11111111 11111111 11111111 11111110 - 反
  //11111111 11111111 11111111 11111111 - 补
  //按位取反(包括符号位):
  //00000000 00000000 00000000 00000000
  int b = ~a;
  printf("%d\n", b);//0
  printf("%d\n", a);//-1 - 并不会改变a
  return 0;
}
//++:自增
int main07()
{
  int a1 = 10;
  int b1 = a1++;//后置++,先使用,再++
  printf("a1 = %d\n", a1);//11
  printf("b1 = %d\n", b1);//10
  //++++++++++++++++++++++++++++++++++++++++++++
  int a2 = 10;
  int b2 = ++a2;//前置++,先++,后使用
  printf("a2 = %d\n", a2);//11
  printf("b2 = %d\n", b2);//11
  return 0;
  //++++++++++++++++++++++++++++++++++++++++++++++
  //--也是如此
  int a3 = 10;
  printf("%d\n", a3--);//10
  printf("%d\n", a3);//9
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++
//对于自增或自减,可能会见到很多一些没有意义的代码,甚至是错误的:
int main08()
{
  int a = 1;
  int b = (++a) + (++a) + (++a);
  printf("%d\n", b);
  return 0;
  //结果(已验)
  //在VS2017下结果是12
  //在Linux_Ubuntu下结果是10
}
//--------------------------------------------------------
//&:取地址
//*:指针
int main09()
{
  int a = 10;
  printf("%p\n", &a);//取a的地址以十六进制打印
  int* pa = &a;//将a的地址存放于pa,pa就是一个指针变量,pa的类型就是int*
  *pa = 20;//这里的* - 解引用操作符或间接访问操作符;这里就是通过pa存的地址找到a,并把a赋值20
  printf("%d\n", 20);//20;
  return 0;
  //这里注意区别&:按位与;&:取地址。当&有2个操作数时,它就是按位与;当&仅有1个操作数时,它就代表取地址
}
//--------------------------------------------------------
//(类型):强制类型转换
int main10()
{
  int a1 = 3.14;//warning:从double类型转换到int可能会丢失数据
  int a2 = (int)3.14;//将double类型的数据强转为int 
  return 0;
}

六、关系操作符

 >   >=   <   <=   !=   ==

对于关系操作符这里没啥可讲的,相对简单。值的注意的是:

1.==是判断相等;而=是赋值

2.==不能比较2个字符串相等

七、逻辑操作符

&&   ||

#include<stdio.h>
int main()
{
  int a = 3;
  int b = 0;
  if(a && b)//同真为真,其余为假。这里只要有1个为假,则为假
  {
    printf("hehe\n");
  }
  if(a || b)//同假为假,其余为真。这里只要有1个为真,则为真
  {
    printf("haha\n");
  }
  return 0;
}

八、条件操作符

exp1 ? exp2 :exp3

注:也被称为三目操作符

#include<stdio.h>
int main()
{
  int a = 3;
  int b = 0;
  if(a > 5)
    b = 1;
  else 
    b = -1;
  //上面的if...else语句使用三目操作符只要一句代码:
  //三目操作符:表达式1的结果为真,则执行表达式2,否则执行表达式3
  b = (a > 5 ? 1 : -1);
  return 0;
}

九、逗号表达式

exp1, exp2, exp3, … expN

#include<stdio.h>
int main()
{
  int a = 3;
  int b = 5;
  int c = 0;
  //逗号表达式:从左向右依次计算,但是整个表达式的结果是最后一个表达式的结果,而最后一个表达式的结果可能会受到前面表达式的影响
  int d = (c = 5, a = c + 3, b = a - 4, c += 5);
  printf("%d\n", d);//10
  return 0;
}

十、下标引用、函数调用和结构成员

[ ]   ()   .   ->

#include<stdio.h>
//[]:下标引用操作符,有两个参数:第一个是数组名,第二个是元素大小或索引值
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,10};//这里的[]里面是元素个数
  printf("%d\n", arr[4]);//数组名[索引值] -> 可以访问数组元素
  return 0;
}
//--------------------------------------------------------
//():函数调用操作符,接受一个或多个操作数:第一个操作数是函数名,剩余的操作数是传递给函数的参数
//定义函数
int Add(int x, int y)
{
  return x + y;
}
int main01()
{
  int a = 10;
  int b = 20;
  //调用函数
  int ret = Add(a, b);//这里的()就是函数调用操作符
  printf("%d\n", ret);
  return 0;
}
//--------------------------------------------------------
//.:
//->
//这里先介绍结构体:struct:
//定义结构体类型
struct Book
{
  char name[20];
  char id[20];
  int price;
};
int main02()
{
  //对于生活中的一些事物,我们要怎样描述它的信息:(这些信息又是不同的类型的,有字符串,整型等等。这时C语言提供了struct结构体关键字,它可以囊括不同的数据类型)
  //书:书名、书号、作者、定价、书号
  //人:名字、年龄、性别
  //初始化结构体:通过之前的了解我们知道初始化变量:int a = 10 -> 类型 + 变量 = 值 -> 所以这里结构体的类型就是struct Book
  struct Book b = {"C语言", "cc202005011", "55"};
  //打印结构体成员
  //1.使用.来访问结构体成员
  printf("书名:%s\n", b.name);//通过(变量+.+成员名)可以访问结构体成员
  printf("书号:%s\n", b.id);
  printf("定价:%d\n", b.price);
  printf("-------------分割线-------------\n");
  //2.使用*和.来访问结构体成员
  struct Book* pb = &b;//存储结构体的地址
  printf("书名:%s\n", (*pb).name);
  printf("书号:%s\n", (*pb).id);
  printf("定价:%d\n", (*pb).price);
  printf("-------------分割线-------------\n");
  //3.使用->来访问结构体成员
  printf("书名:%s\n", pb -> name);//通过(指针变量 -> 成员名)可以访问结构体成员
  printf("书号:%s\n", pb -> id);
  printf("定价:%d\n", pb -> price);
  return 0;
}

十一、操作符的优先级

在程序设计中也是有操作符的优先级的,同加减乘除,优先级高的先算。

影响表达式求值的有三个因素:

1.操作符的优先级

2.操作符的结合性(优先级相同的情况下,结合性决定了运算顺序)

3.是否控制求值顺序(比如&&,如果左边为假,则整体为假)

以下整理了C语言中操作符的优先级:

注:N\A是无结合性

  L-R是从左向右

  R-L是从右向左

1、一些有问题的表达式

此类表达式没有办法确定唯一的计算路径,未来应该避免这类的表达式

a * b + c * d + e * f;

这个表达式执行的顺序可能就有2种:


c + --c;

比如:c = 3时:

同上,操作符的优先级只能决定–的运算在+的运算的前面,但是我们不知道,+操作符的左操作数的获取是在右操作数的之前还是之后的值,所以是有歧义的。


此代码来自《C和指针》

int main()

{

  int i = 10;

  i = i-- - --i * (i = -3) * i++ + ++i;

  printf(“i = %d\n”, i);

  return 0;

}

经作者的验证:相同的代码在不同的编译器下跑出的结果大不相同


int fun ()

{

  static int count = 1;//注意count通过static修饰后,下一次进来时并不会销毁

  return ++count;

}

int main()

{

  int answer;

  answer = fun() - fun() * fun();

  printf("%d\n", answer);

  return 0;

}

在VS2017下的结果为2 - 3 * 4 = -10

3、… …

对于fun()函数的调用有可能会有不同的先后调用顺序


int main()

{

  int i = 1;

  int ret = (++i) + (++i) + (++i);

  printf("%d\n", ret);

}

在VS2017中是12 -> 4 + 4 + 4:

在Linux下是10 -> 3 + 3 + 4:


总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那个就是存在问题的。

十二、趁热打铁

1、交换变量,不能使用第3个变量(出自品茗股份c++笔试题)

/***********************************************************************

目的:不能创建临时变量(第3个变量),实现2个数的交换

分析:1.借助2数之和与变量的运算:

   2.使用异或:

平台:Visual studio 2017 && windows

*************************************************************************/

实现代码1:

#include<stdio.h>
int main()
{
  int a = 3;
  int b = 5;
  printf("a = %d b = %d\n", a, b);//3 5
  a = a + b;
  b = a - b;
  a = a - b;
  printf("a = %d b = %d\n", a, b);//5 3
  return 0;
}

这种写法是有问题的:当这2个数足够大时,相加可能会造成数值溢出

实现代码2:

#include<stdio.h>
int main()
{
  int a = 3;
  int b = 5;
  printf(a = %d b = %d\n", a, b);//3 5
  a = a ^ b;//以下的括号没有任何意义
  b = a ^ b;//(a ^ b) ^ b -> a
  a = a ^ b;//(a ^ b) ^ ((a ^ b) ^ b) -> b
  printf(a = %d b = %d\n", a, b);//5 3
  return 0;
}

2、二进制中的1

/***********************************************************************

目的:求一个整数存储在内存中的二进制中1的个数

分析:1.利用十进制转二进制的方法(除2反序取余法),如果余数等于1,就统计

   2.让这个数的二进制位不断和1的二进制位按位&与(总共&32次,>>31次),如果得到1则统计

   3.观察以下规律:

平台:Visual studio 2017 && windows

***********************************************************************/

实现代码1:

#include<stdio.h>
int main()
{
  int a = 13;//要求的数
  int temp = a;//拷贝一份
  int count = 0;//计数
  //...00001101
  while(temp)
  {
    if(temp % 2 == 1)
    {
      count++;
    }
    temp = temp / 2;
  }
  printf("%d存储在内存中的二进制中1的个数是%d\n", a, count);
  return 0;
}

这个代码也是有局限性的 —— 只能计算正数

实现代码2:

#include<stdio.h>
int main()
{
  int i = 0;
  int count = 0;//计数器
  int a = 13;//要求的数
  int temp = a;//拷贝一份
  //...00001101
  for(i = 0; i < 32; i++)
  {
    /*
    if(a & 1 == 1)
    {
      count++;
    }
    temp = temp >> 1;
    */
    //或者:
    if (1 == ((a >> i) & 1))
      count++;
  }
  printf("%d存储在内存中的二进制中1的个数是%d\n", a, count);
  return 0;
}

实现代码3:

#include<stdio.h>
int main()
{
  int i = 0;
  int count = 0;//计数器
  int a = 13;//要求的数
  int temp = a;//拷贝一份
  //...00001101
  while(temp)
  {
    count++;
    temp = temp & (temp - 1);
  }
  printf("%d存储在内存中的二进制中1的个数是%d\n", a, count);
  return 0;
}

3、1置0,0置1

/***********************************************************************

目的:将一个整数存储在内存中的二进制中的某一个1置为0,某一个0置为1

分析:将某一个二进制位0置1,借助左移操作符,然后将这个位或上1即可;

   将某一个二进制位1置0:

   如…00001010 -> 00000010  将这个数与上11110111即可

   需要注意的是将这个数所要变换的位与上0,其它位与1不变。而这个数通过左移操作符和按位取反得到

平台:Visual studio 2017 && windows

***********************************************************************/

实现代码:

#include<stdio.h>
int main()
{
  int a = 13;//要操作的数
  //...00001101
  //1.把a的二进制中的第5位置为1(从右至左)
  a = a | (1 << 4);
  printf("a = %d\n", a);//29
  //2.把a的二进制中的第5位置为0
  //...00011101
  //&
  //...11101111 -> 这个数如何得到?
  //...00001101
  a = a & ~ (1 << 4);
  printf("a = %d\n", a);//13
  return 0;
}

4、判断以下代码的输出结果(出自360的笔试题)


#include<stdio.h>
int main()
{
  int i = 0, a = 0, b = 2, c = 3, d = 4;
  i = a++ && ++b && d++;
  printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);//1 2 3 4
  return 0;
}

分析:a++ && ++b && d++;这里的操作符是&& -> 同真为真,其余为假。此时这里的a又是后置++,所以a为0。对于&&操作符,只要前面为假,后面则都为假,所以不会执行



#include<stdio.h>
int main()
{
  int i = 0, a = 0, b = 2, c = 3, d = 4;
  i = a++ || ++b || d++;
  printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);//2 3 3 4
  return 0;
}

分析:a++ || ++b || d++;这里的操作符是|| -> 同假为假,其余为真。此时a为0但后面的b为3,所以为真。对于||操作符,只要前面为真,后面则都为真,所以不会执行


总结:熟悉了解 && 和 || 的习性

&& 只要有一个为假,后面则为假,并且不会执行

|| 只要有一个为真,后面则为真,并且不会执行


相关文章
|
13天前
|
存储 编译器 C语言
爱上C语言:操作符详解(下)
爱上C语言:操作符详解(下)
|
25天前
|
算法 测试技术 C语言
【C语言】异或(^)操作符
【C语言】异或(^)操作符
16 0
|
1月前
|
存储 算法 程序员
【c 语言 】位操作符详解
【c 语言 】位操作符详解
36 0
|
1月前
|
存储 编译器 Linux
操作符详解【c语言】
操作符详解【c语言】
|
1月前
|
C语言
【C语言】位操作符详解
【C语言】位操作符详解
22 0
|
1月前
|
存储 C语言
【C语言】位与移位操作符详解
【C语言】位与移位操作符详解
【C语言】位与移位操作符详解
|
1月前
|
C语言
C语言------操作符的巧妙使用
C语言------操作符的巧妙使用
21 0
|
1月前
|
编译器 C语言
C语言---------对操作符的进一步认识
C语言---------对操作符的进一步认识
22 0
|
1月前
|
存储 编译器 程序员
C语言的模型玩具:结构体的使用以及操作符优先级
C语言的模型玩具:结构体的使用以及操作符优先级
|
1月前
|
存储 编译器 程序员
c语言从入门到实战——操作符详解
C语言操作符指的是程序中用来进行各种计算、逻辑和条件操作的符号或符号组合。 操作符是编程中用于执行特定操作或比较数据的符号。它们根据操作类型分为算术、比较、逻辑和位操作符。算术操作符执行加、减、乘、除等数学运算;比较操作符比较两个值的大小或相等性;逻辑操作符连接多个条件,形成更复杂的逻辑判断;位操作符则直接对整数的二进制位进行操作。了解各种操作符的特性和用法,对于编写高效、准确的代码至关重要。
54 0