【Amazing! C】操作符(一)

目录

前言

一、算数操作符

二、移位操作符

2.1 原码

2.2 反码

2.3 补码

2.4 左移操作符

2.5 右移操作符

三、位操作符

3.1 按(二进制)位与操作符 “&”

3.2 按(二进制)位或操作符 “|”

3.2 按(二进制)位异或操作符 “^”

四、赋值操作符

五、单目操作符

5.1 单目操作符

5.1.1 逻辑反操作 “!”

5.1.2 取地址 “&”

5.1.3 操作数的类型长度 “sizeof”和数组

5.1.4 按位取反 “~”

六、关系操作符

七、逻辑操作符

八、条件操作符

九、逗号表达式

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

10.1 下标引用操作符 “[]”

10.2 函数调用操作符 “()”

10.3 访问一个结构的成员


前言

        操作符种类较多,也十分常用,因此,需要我们重点关注并区分操作符的功能和应用,不要混淆。

一、算数操作符

    +    -    *    /    %
    加   减   乘   除   余

        +加、-减、*乘操作符可以作用于整数和浮点数:

        取余操作符%的两个操作数必须为整数,返回的是整除之后的余数。

        /操作符,如果两个操作数都是整数,执行整数除法,若至少有一个浮点数,则执行浮点数除法

#include<stdio.h>

int main()
{
	int a = 7 / 2;
	double b = 7 / 2;
	int c = 7.0 / 2;
	double d = 7 / 2;

    int e = 7 % 2;

	printf("a = %d\n", a);
	printf("b = %lf\n", b);
	printf("c = %d\n", c);
	printf("d = %lf\n", d);

    printf("e = %d\n", e);

	return 0;
}

代码运行结果为:

a = 3;
b = 3.000000;//小数点后是6个0
c = 3;
d = 3.000000;//小数点后是6个0
e = 1;

二、移位操作符

        左移        <<

        右移        >>

        移位操作符在之前没有没有提及,那么我们将通过代码展示来了解:

#include<stdio.h>

int main()
{
    int n = 7 >> 1;
    printf("%d", n);

    return 0;
}

运行代码后,结果如下:

        其中,要注意的是:

        我们看代码,7向右移位1,得出的结果为3,那么移位操作符的原理是什么呢?

        移位操作符移动的是二进制的位,移位操作符的操作数只能是整数。

        我们需要理解:整数的二进制表示形式有3种:原码、反码、补码

        对于一个整型,是4个字节,32bit;

        那么,一个整数写出二进制序列后,就是32个bit位;

        对于有符号的整数来说,最高位是符号位,符号位是1表示负数,符号位是0表示正数;

        对于无符号整数来说,没有符号位,所有位都是有效位。

2.1 原码

        按照数值的正负,直接写出的二进制序列,就是原码。

10
原码:0000 0000 0000 0000 0000 0000 0000 1010

-10
原码:1000 0000 0000 0000 0000 0000 0000 1010

        对于正的整数,原码、反码、补码相同,无需计算;对于负的整数,原码、反码、补码是需要计算的。

2.2 反码

        原码的符号位不变,其他位,按位取反。

-10
原码:1000 0000 0000 0000 0000 0000 0000 1010
反码:1111 1111 1111 1111 1111 1111 1111 0101

2.3 补码

        反码的二进制+1就是补码。

-10
原码:1000 0000 0000 0000 0000 0000 0000 1010
反码:1111 1111 1111 1111 1111 1111 1111 0101
补码:1111 1111 1111 1111 1111 1111 1111 0110

        不管是正的整数,还是负的整数,内存中存储的都是补码,因此可以说,整数在内存中存储的都是补码的二进制序列。另外,整数在计算的时候也使用补码。

 补码得到源码的途径:

1.先-1,再取反。

2.先取反,再+1。

2.4 左移操作符

        移位规则:左边丢弃右边补零

        基于上述了解,我们可以对代码做出进一步解释。

#include<stdio.h>

int main()
{
    int m =7;
    int n << 1;   //左移操作符
//            |0000 0000 0000 0000 0000 0000 0000 0111|
//           0|000 0000 0000 0000 0000 0000 0000 0111 |
//            左边的值直接丢弃,右边空出来一位直接补0		结果是14
    printf("%d", m);//7
    printf("%d", n);//14

    return 0;
}
#include<stdio.h>

int main()
{
    int m = -7;
//            |1000 0000 0000 0000 0000 0000 0000 0111|
//            |1111 1111 1111 1111 1111 1111 1111 1000|
//            |1111 1111 1111 1111 1111 1111 1111 1001|
    int n << 1;
//           1|111 1111 1111 1111 1111 1111 1111 1001 |
    printf("%d\n", m);
    printf("%d\n", n);
//            |1111 1111 1111 1111 1111 1111 1111 0010|
//            |1111 1111 1111 1111 1111 1111 1111 0001|
//            |1000 0000 0000 0000 0000 0000 0000 1110|
    return 0;

}

        此外,我们也可以发现,不论正数还是负数,左移1位,均具有乘2的效果。

2.5 右移操作符

        移位规则:

        1. 逻辑右移:左边补零右边丢弃

        2. 算数右移:左边用原该值的符号位进行填充右边丢弃

#include<stdio.h>

{
    int m = -10;
//            |1000 0000 0000 0000 0000 0000 0000 1010|
//            |1111 1111 1111 1111 1111 1111 1111 0101|
//            |1111 1111 1111 1111 1111 1111 1111 0110|
    int n >> 1;
//            | 111 1111 1111 1111 1111 1111 1111 1011|0

//
//对于逻辑右移:右边直接丢弃,左边补0;
//对于算数右移:右边丢弃,左边用原该值的符号位填充,即原来是负数,就拿1填充,原来是正数,就拿0填充。
    printf("m = %d\n", m);
    printf("n = %d\n", n);//VS编译器中采用算数右移
//            |1111 1111 1111 1111 1111 1111 1111 1011|
//            |1111 1111 1111 1111 1111 1111 1111 1010|
//            |1000 0000 0000 0000 0000 0000 0000 0101|

    return 0;
}

        此外,逻辑右移具有除以2的效果。

        值得注意的是:

        对于移位运算符,不要移动负数位,这是标准未定义的。

int num = 10;
num >> -1;//err

三、位操作符

        位操作符也是对整数进行操作的,位操作符包括:

&    //按位与
|    //按位或
^    //按位异或

3.1 按(二进制)位与操作符 “&”

        与运算:对应2进制位,只要有0则为0,两个同时为1才是1。代码如下:

#include<stdio.h>

int main()
{
    int a = 3;
//    0000 0000 0000 0000 0000 0000 0000 0011    3的补码
    int b = -5;
//    1000 0000 0000 0000 0000 0000 0000 0101    
//    1111 1111 1111 1111 1111 1111 1111 1010
//    1111 1111 1111 1111 1111 1111 1111 1011    -5的补码
    int c = a & b;
//    0000 0000 0000 0000 0000 0000 0000 0011    最高位是0,正数,所以补码和原码相同
    printf("%d\n", c);//3
    return 0;
}

        另外,按位与还有一个特点,就是可以获取二进制整数的任意一个位。如得到3的最低位。

#include<stdio.h>
int main()
{
    int a = 3;
    int b = a & 1;
//    0000 0000 0000 0000 0000 0000 0000 0011    3的补码
//    0000 0000 0000 0000 0000 0000 0000 0001    1的补码
//    0000 0000 0000 0000 0000 0000 0000 0001    a & 1 = 1

    printf("%d\n", b);//1
    return 0;
}

        因此,当我们想知道第n位是多少,只需先右移(n-1)位后,在按位与1。

3.2 按(二进制)位或操作符 “|”

        或运算:对应二进制位上有1则为1。代码如下:

#include<stdio.h>

int main()
{
    int a = 3;
//    0000 0000 0000 0000 0000 0000 0000 0011    3的补码
    int b = -5;
//    1000 0000 0000 0000 0000 0000 0000 0101
//    1111 1111 1111 1111 1111 1111 1111 1010
//    1111 1111 1111 1111 1111 1111 1111 1011    -5的补码
    int c = a | b;
//    1111 1111 1111 1111 1111 1111 1111 1011    c的补码
//    1111 1111 1111 1111 1111 1111 1111 1010
//    1000 0000 0000 0000 0000 0000 0000 0101    原码-5
    printf("%d\n", c);
    
    return 0;
}

3.2 按(二进制)位异或操作符 “^”

        异或运算:相同为假,相异为真。相同为0,相异为1。异或运算的操作数都是整型。

#include<stdio.h>

int main()
{
    int a = 3;
//    0000 0000 0000 0000 0000 0000 0000 0011    3的补码
    int b = -5;
//    1000 0000 0000 0000 0000 0000 0000 0101
//    1111 1111 1111 1111 1111 1111 1111 1010
//    1111 1111 1111 1111 1111 1111 1111 1011    -5的补码
    int c = a ^ b;
//    1111 1111 1111 1111 1111 1111 1111 1000
//    1000 0000 0000 0000 0000 0000 0000 0111    取反
//    1000 0000 0000 0000 0000 0000 0000 1000    +1

    printf("%d\n", c);//-8
    return 0;
}

        基于异或运算,我们可以知道两个条件:

a ^ a = 0;
a ^ 0 = a;

        根据上述条件,我们可以在不使用临时变量的前提下,实现两个数的交换。代码如下:

#include<stdio.h>

int main()
{
    int a = 3;
    int b = 5;

    a = a ^ b;
    b = a ^ b;//相当于带入,即b = a ^ b ^ b = a ^ 0 = a;
    a = a ^ b;//            a = a ^ b ^ a = 0 ^ b = b;

    printf("a=%d b=%d\n", a, b);
    return 0;
}

四、赋值操作符

#include<stdio.h>

int main()
{
	int a = 1;//不是赋值,是初始化
	a = 20;//赋值
	return 0;
}

        除此以外,还有复合赋值操作符。

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

五、单目操作符

5.1 单目操作符

!           逻辑反操作
-           负值
+           正值
&           取地址
sizeof      操作数的类型长度(以字节为单位)
~           对一个数的二进制按位取反
--          前置、后置--
++          前置、后置++
*           间接访问操作符(解引用操作符)
(类型)       强制类型转换

5.1.1 逻辑反操作 “!”

#include<stdio.h>

int main()
{
	int flag = 0;//0为假
    int c = !flag//1
	if (!flag)
	{
		printf("hehe\n");
	}
	return 0;
}

5.1.2 取地址 “&”

#include<stdio.h>

int main()
{
    int a = 0;
    int * p = &a;//取地址
    
    int arr[10];
    int (*pa)[10] = &arr;//数组的地址,也叫数组指针

    printf("%p\n", p);
    return 0;
}

5.1.3 操作数的类型长度 “sizeof”和数组

  • sizeof是在计算类型创建变量或者变量的大小,单位是字节
  • sizeof计算的结果是size_t类型的
  • size_t是无符号整型的
  • 对size_t类型的数据进行打印,可以用%zd
  • sizeof后面的括号在括号中写的不是类型的时候,括号可以省略,这就说明sizeof不是函数,是单目操作符
#include<stdio.h>

int main()
{
	int a = 10;
	printf("%zd\n", sizeof(a));//4//也可以写成printf("%zd\n", sizeof a);证明了sizeof是操作符而不是函数。因为函数后边的括号不能省略
	printf("%zd\n", sizeof(int));//4
	int arr[10] = { 0 };
	printf("%zd\n", sizeof arr);//40
	return 0;
}
#include<stdio.h>

void test1(int arr[])//本质是指针int* arr
{
    printf("%d\n", sizeof(arr));
}
void test2(char ch[])//本质是指针char* ch
{
    printf("%d\n", sizeof(ch));
}

int main()
{
    int arr[10] = { 0 };
    char ch[10] = { 0 };
    test1(arr);
    test2(ch);

    return 0;
}

         一个字符的地址和一个整型的地址都叫地址。是地址,在32位平台下是4个字节,在64位下是8个字节,而如果把地址存到指针变量中去,指针变量的大小也是4或8个字节

        所以,不要在门缝里看指针,把指针看扁了。

5.1.4 按位取反 “~”

        对一个数的二进制按位取反(包括符号位和数值位)。

#include<stdio.h>

int main()
{
	int a = 0;
	printf("%d\n", ~a);
	//0000 0000 0000 0000 0000 0000 0000 0000
	//1111 1111 1111 1111 1111 1111 1111 1111	内存中的补码
	//1000 0000 0000 0000 0000 0000 0000 0000
	//1000 0000 0000 0000 0000 0000 0000 0001
	//-1
	return 0;
}

         以a=10为例,二进制表示为:0000 0000 0000 0000 0000 0000 0000 1010,写一个代码把从左向右第5位取反,即0变成1,其他位不变。然后再变回10。

        思路:给第5位的0或上一个1。要求其他位不变,则第4、3、2、1位的1或上一个0,因此,或上“0000 0000 0000 0000 0000 0000 0001 0000”这个数字。再变回来,需要第5位按位与0,其他位按位与1。

#include<stdio.h>

int main()
{
	int a = 10;
	//0000 0000 0000 0000 0000 0000 0000 1010
	//0000 0000 0000 0000 0000 0000 0001 0000	//1<<4
	a |= (1<<4);
	//0000 0000 0000 0000 0000 0000 0001 1010
	//1111 1111 1111 1111 1111 1111 1110 1111	
    //相当于
    //0000 0000 0000 0000 0000 0000 0001 0000按位取反
	a &= (~(1 << 4));
	printf("%d\n", a);
	return 0;
}

六、关系操作符

        主要用来测试关系操作符两边对象的大小关系。

        <                >                <=                >=                !=                ==

         值得注意的是,在编程的过程中关系操作符“==”和赋值操作符“=”不小心写错,导致的错误。

七、逻辑操作符

&&    逻辑与
||    逻辑或

        这里,我们要区分好逻辑与(&&)和按位与(&),逻辑或(||)和按位或(|)。逻辑操作符不是对二进制位进行计算。其具有短路操作,即:

        &&    --    左边操作数如果为假,右边无需计算
        | |      --    左边操作数如果为真,右边无需计算

区分代码如下:

#include<stdio.h>

int main()
{
    int a = 1 & 2;//0
    int b = 1 && 2;//1

    int c = 1 | 2;//3
    int d = 1 || 2;//1

    return 0;
}

        我们通过代码进一步了解逻辑操作符。

#include<stdio.h>
例1:
int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++;

    printf("a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
    printf("%d\n", i);

    return 0;
}

例2:
int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ || ++b || d++;

    printf("a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
    printf("%d\n", i);

    return 0;
}

代码结果如下:

例1:
a=1
b=2
c=3
d=4
i=0
例2:
a=1
b=3
c=3
d=4
i=1

分析:

例1:

  • 赋值运算符的优先级最低。
  • a++,后置++,先使用再+1,先使用为0,表示假。
  • 根据短路操作,对于&&来说,遇到一个假,后边的表达式++b、d++就不运算了。
  • 所以,i=0,a后自增1。

 例2:

  • 赋值运算符的优先级最低。
  • a++,后置++,先使用再+1,先使用为0,表示假。
  • 未触发短路操作,逻辑或继续向后计算,++b,前置++,先自增1,后使用。
  • 此时,b=3,在逻辑或中,根据短路操作,看到不是0,必为真,后边就不用算了。
  • a自增1,b也自增1,所以i=1。

八、条件操作符

exp1 ? exp2 : exp3
真    计算    不算
假    不算    计算

        写一个代码,要求输出两个数中较大的一个:

#include<stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int m = 0;
	if (a < b)
	{
		m = a;
	}
	else
	{
		m = b;
	}
	
	printf("%d\n", m);
	return 0;
}

        结合条件操作符,我们可以对上述代码进行化简:

#include<stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int m = 0;
	m = (a > b ? a : b);

	printf("%d\n", m);
	return 0;
}

九、逗号表达式

exp1,exp2,exp3,...,expN
用逗号隔开的多个表达式
从左到右依次执行,整个表达式的结果是最后一个表达式的结果

        我们通过代码对逗号表达式做进一步了解:

#include<stdio.h>

int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);
				//0		12		12		13
	printf("%d\n", c);//13

	return 0;
}

        我们再来看这个代码:

a = get_val();
count_val(a);
while (a > 0)
{
	a = get_val();
	count_val(a);
}

        使用逗号表达式,还可以得到如下效果:

while (a = get_val(), count_val(a), a > 0)
{
	;
}

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

10.1 下标引用操作符 “[]”

        操作数:数组名+索引值

 代码如下:

#include<stdio.h>

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", arr[5]);//[]下标引用操作符,arr和5分别是左右操作数

	return 0;
}

10.2 函数调用操作符 “()”

        接收一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

        对于函数调用操作符,最少要有1个操作数,因为函数调用时候可以不传参,但不能没有函数名。

代码如下: 

#include<stdio.h>

int main()
{
	printf("hehe\n");//()函数调用操作符,printf、hehe\n是两个操作数
	return 0;
}

10.3 访问一个结构的成员

.       结构体.成员名
->      结构体->成员名

代码如下: 

/3include<stdio.h>

struct Book
{
	char name[20];
	int price;
};
//拿到结构体变量:	结构体变量.成员
//拿到结构体指针:	结构体指针->成员
void Print(struct Book* pb)//拿结构体的指针来接收
{
	printf("%s %d\n", (*pb).name, (*pb).price);//当传过去地址,再通过解引用,有点复杂了
	printf("%s %d\n", pb->name, pb->price);//如果是结构体指针,那么就可以通过->找到所指向对象的成员
}
int main()
{
	struct Book b = { "C语言指南",65 };
	printf("%s %d\n", b.name, b.price);

	Print(&b);
	return 0;
}

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值