小结 | C语言操作符

目录:

C语言操作符分类总结:

C语言中大致分为以下几类:

  1. 算数操作符
  2. 移位操作符
  3. 位操作符
  4. 赋值操作符
  5. 单目操作符
  6. 关系操作符
  7. 逻辑操作符
  8. 条件操作符
  9. 逗号表达式
  10. 下标引用、函数调用、结构成员访问操作符

** 注意:**
其中,移位操作符和位操作符都是对二进制序列进行操作。

=具体分析=


一、算数操作符


+ - * / %

这里需要注意的是:
1.除法(/)
整数除法时,左右两边都需要为整数;
而浮点数除法时,左右两边至少有一个数为浮点数。
2.取模运算(%)
只能对整数操作。


二、移位操作符


<<左移操作符
>>右移操作符

移动规律:

左移:左边丢弃,右边补0;
右移:
1.算数右移:右边丢弃,左边补符号位(正数补0,负数补1);
2.逻辑右移:右边丢弃,左边补0;

大小的变化:

左移:每左移一位,相当于乘2;
右移:每右移一位,相当于除2。


三、位操作符


  • & 按位与 (两数二进制位都为1 —>1)
  • | 按位或 (两数二进制位有一个为1 —>1)
  • ^ 按位异或 (两数二进制位不同为1,相同为0)

关于异或的应用例题:
在一个是数组中,只有一个数字单独出现,其余都两两出现,求单独出现的数字。

int ret = 0;
for(i = 0; i < n; i++){
ret = ret ^ a[i];
}

异或(^)的特点:
1.两数相同,异或为0;
2.0与任何数异或,还是这个数。

这里有一道关于异或的题,是上一题的升级版解题思路较巧妙,有兴趣的可以看下:

题目:一个数组中只有两个数字是出现一次,其他所有数字都出现了两次,找出这两个数字,编程实现。

#include <stdio.h>

void search_one(int arr[],int len)
{
    
    int data = arr[0];
    int i = 1;
    for (; i < len; i++) {
        data ^= arr[i];
    }
    //异或结果为 data = m ^ n; (假设单独出现的两数为m、n)
    //由于m、n为单独出现的两数,所以m一定不等于n,即data一定不为0,data的二进制位至少有一位为1
    
    int flag = 1;
    for (i = 0; i < sizeof(int)*8; i++) {
        if (data & (flag<<i) ) {
            flag <<= i;
            break;//可不加break,因为用flag记录哪一位都可以
        }
        
    }
    //找出m与n相异的一位,即data二进制位为1的一位
    //用flag来记录是哪一位,在这里我们假设是第x位
    
    int dataA = 0;//组A
    int dataB = 0;//组B
    
    for (i = 0; i < len; i++) {
        if (arr[i] &flag) {
            dataA ^= arr[i];
        }
        else{
            dataB ^= arr[i];
        }
            
    }
    /*在这里我们将整个数组的数分成两组,分组条件是,第x位为1的在一组里,为0的在一组里,可得出两点结论
    1.m、n肯定不在一组里
    2.重复出现的两个数一定在一组里*/
    
    printf("%d %d\n",dataA,dataB);
    
}



int main(int argc, const char * argv[]) {
    int a[] = {1,1,2,8,2,3,3,4,4,5,6,5};
    search_one(a,12);
    
    return 0;
}

四、赋值操作符 =


赋值操作符可以与其他操作符连用,形成复合赋值符,
如:
+= , -= , *= , /= , %= , >>= , << , &= , |= , ^=

用法:
a += 2;等同于 a = a + 2;
其余类似。


五、单目操作符


  • ! 取反
  • -- 负值
  • + 正值
  • & 取地址
  • sizeof 计算字符串长度(以字节为单位)
  • ~ 对二进制取反
  • -- 前置、后置--
  • ++ 前置、后置++
  • * 间接操作符(解引用操作符)
  • (类型) 强制类型转换

单目操作符即只有一个操作元素(可以看到,前面几个操作符都有两个操作元素);

这里需要注意的是,sizeof这个操作符,
int a = 10;
sizeof (a)——这里括号可以省略,即写成 sizeof a 也可以;
但是sizeof(int)这种情况下括号不可省略;

另外和数组结合时,也需要注意:
int arr[3] = {1,2,3}
sizeof(arr) 结果为12(arr表示整个数组)
sizeof(arr+0) 结果为4(表示首元素地址)

下面再来看一段关于sizeof的代码:

int a = 1;
short s = 3;
printf("%d\n",sizeof(s=a+5));
printf(""%d\n",s);

结果为:
2
3

可能会有人有疑问,明明 s = a + 5 赋值给了s,为什么s的值还是3?
这里有一点需要记住,放在sizeof内部的表达式不参与运算,因为关于sizeof的运算是在编译期间就完成了的,而我们所讲的sizeof运算,需要在运行期间完成,即在运行前,sizeof内部的值已经被替换成了2(sizeof最终看s的类型),代码相当于

int a = 1;
short s = 3;
printf("%d\n",sizeof(2));
printf(""%d\n",s);

那s的值也就不会发生变化,输出时仍为3。


六、关系操作符


>= , <= , > , < , != , ==


七、逻辑操作符


&& 逻辑与
|| 逻辑或
需要与位运算(按位与&、按位或|)区分开来。

下面是一道关于逻辑与经典的例题:

 int i = 0 , a = 0 , b = 2 , d = 4;
    i = a++ && ++b && d++;
    printf("%d %d %d\n",a,b,d);

结果为 1 2 4
可以看到,a先参与了运算,再自增,但是参与运算时它的值为0,逻辑与(&&)操作符的运算特点是,全为1结果才为1,当出现了一个0时,后面的他将不会继续运算下去,因此,++b和d++并未实现,所以结果只有a加了1.

再看另一个操作符:

int i = 0 , a = 0 , b = 2 , d = 4;
i = a++ || ++b || d++;
printf("%d %d %d\n",a,b,d);

结果为 1 3 4
逻辑或的运算特点是,当所有值都为0时,结果才为0,当a参与运算时为0,||会继续看后面的运算,当++b的值不为0时,无论后面的值是多少,其结果已经是1了,后面的d++也就不会运算下去了。


八、条件操作符(三目操作符)


exp1 ? exp2 : exp3
当exp1的值为真时,执行exp2表达式,反之,执行exp3表达式;

例:
求a,b中最大值可以这么求
int max = a>b ? a : b;


九、逗号表达式


exp1,exp2,exp3,...,expN
从左向右依次执行,最终结果为expN。


十、下标引用、函数调用、结构成员访问操作符


  • 下标引用操作符即 [ ],经常在数组中能看到,
    例如 int arr[10] = {0};
    当赋值arr[5] = 2;相当于执行了*(arr+5)= 2;这样一个操作。

  • 函数调用操作符 (), 在函数中经常能看到。

  • 结构成员访问操作符,分为两类:.->
    当我们定义一个结构体

struct Stu{
    char name[20];
    char id[13];
    int age;
};

这时候只是定义,创造了一个类型,注意这个时候并没有给他分配空间,类似于创造了int这样一个类型。
而当我们用时,

struct Stu s;//这相当于我们 int a;(开辟了空间)
    strcpy(s.name,"张三");
  strcpy(s.id, "201606060501");
    s.age = 20;

在这里s.name中的. 就是成员访问操作符。

但是另一种情况,
struct Stu *p = &s;
如果这时候需要通过指针p来修改结构体成员的值,
则:

strcpy((*p).name,"张三");
    strcpy((*p).id, "201606060501");
    (*p).age = 20;

但是(*p).这种写法复杂繁琐,于是有了另一个操作符->,它的用法如下:

strcpy(p->name,"张三");
    strcpy(p->id, "201606060501");
    p->age = 20;

哈哈哈哈哈哈