• C语言 操作符


    一、算数操作符

    +				// 加
    - 				// 减
    * 				// 乘
    / 				// 除
    %				//取模
    
    • 1
    • 2
    • 3
    • 4
    • 5

    程序清单:

    #include 
    
    int main() {
    
    	int a = 10 / 3;
    	printf("%d\n", a);
    
    	float b1 = 10.0 / 3; // double / int
    	printf("%f\n", b1);
    
    	float b2 = 10 / 3.0; // int / double
    	printf("%f\n", b2);
    
    	int c = 10 % 3;
    	printf("%d\n", c);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出结果:

    1-1

    注意事项:

    ① 对于除法操作符,如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

    ② 观察第二个输出结果,实际上由于 double / int,所以产生的是 double 类型,那么在以格式化 "%f " 输出时,就会发生自 double 向 float 截断。(C语言 默认使用 double 类型)

    1-2

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

    二、移位操作符

    <<				// 左移
    >> 				// 右移
    
    • 1
    • 2

    注意:

    ① 移位操作符针对的是数据在内存中的二进制补码。
    ② 移位操作符的操作数只能是整数。

    int a = 3 << 1 			// √
    int b = 3.5 << 1 		// error
    int c = 3 << 1.5		// error
    int d = -3 << 1			// √
    
    • 1
    • 2
    • 3
    • 4

    1. 数据在内存中的存储

    计算机在存储数据的时候是以二进制存储的。二进制有多少位,根据数据的类型决定。比如 int 类型,即 4 字节,即 32 位,那么就有 32 个 0或1 的二进制数据。

    ① 整数的二进制有三种形式:原码、反码、补码。正整数的原、反、补码是相同的;但负整数的原、反、补码则需要计算。(原码符号位不变,其他位按位取反即可变成反码;反码再 +1 即可变成补码)

    ② 最终,整数在内存中存储的是补码的二进制。

    ③ 对于有符号整数来说,最高位表示符号位,0表示正号,1表示负号,此时在原码、反码、补码的转换过程中,符号位不能改变;对于无符号数来说,最高位也表示数据位。

    ④ printf 格式化输出的是数据的原码。

    2. 左移操作符

    程序清单:

    #include 
    
    int main() {
    
    	int a1 = 5;
    	int b1 = a1 << 1;
    	printf("a = %d, b = %d\n", a1, b1); // 5, 10
    
    	int a2 = -5;
    	int b2 = a2 << 1;
    	printf("a = %d, b = %d\n", a2, b2); // -5, -10
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    分析左移的过程:

    5 << 1,5 的原、反、补码相同。

    1-3

    -5 << 1,左移操作符对 -5 的补码进行操作。

    1-4

    总结:

    ① 左移操作符相当于为原数据乘以 2.
    ② 左移对数据的补码的二进制进行操作:左边丢弃,右边补0.
    ③ 左移不会对原数据进行直接改变。

    如下:a 左移过后,把值赋给了 b,则 b 变成了 10,但 a 还是 5.

    int a = 5;
    int b = a << 1; // a = 5, b = 10
    
    • 1
    • 2

    3. 右移操作符

    程序清单:

    #include 
    
    int main() {
    
    	int a1 = 5;
    	int b1 = a1 >> 1;
    	printf("a = %d, b = %d\n", a1, b1); // 5, 2
    
    	int a2 = -5;
    	int b2 = a2 >> 1;
    	printf("a = %d, b = %d\n", a2, b2); // -5, -3
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    分析右移的过程:

    5 >> 1,5 的原、反、补码相同。

    1-5

    -5 >> 1,右移操作符对 -5 的补码进行操作。

    1-6

    总结:

    ① 针对于正整数时,右移操作符相当于为原数据除以 2;针对于负整数时,不确定。

    ② 右移对数据的补码的二进制进行操作。它分为两种情况。
    a. 算数右移:右边丢弃,左边补原符号位。
    b. 逻辑右移:右边丢弃,左边补0.
    一个程序到底是算数右移还是逻辑右移,取决于编译器的使用,例如上面的程序就是放在 VS 编译器下运行的,所以它就采取了算数右移,我的分析过程也是如此。

    ③ 同样地,右移不会对原数据进行直接改变。

    三、位操作符

    &				// 按位与
    |				// 按位或
    ^				// 按位异或
    
    • 1
    • 2
    • 3

    注意:

    ① 位操作符同样针对的是数据在内存中的二进制补码。
    ② 位操作符的操作数只能是整数。

    1. 按位与

    & 规则:两个位都为1,则结果为1;其中一位为0,则结果为0.

    #include 
    
    int main() {
    
    	int a = 3;
    	int b = -5;
    	int c1 = a & b;
    	printf("%d\n", c1); // 3
    	
    	return 0;
    }
    
    //00000000 00000000 00000000 00000011   -> 3的原、反、补码
    
    //10000000 00000000 00000000 00000101   -> 5的原码
    //11111111 11111111 11111111 11111010	-> 5的反码
    //11111111 11111111 11111111 11111011	-> 5的补码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    1-7

    2. 按位或

    | 规则:两个位都为0,则结果为0;其中一位为1,则结果为1.

    #include 
    
    int main() {
    
    	int a = 3;
    	int b = -5;
    	int c2 = a | b;
    	printf("%d\n", c2); // -5
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1-8

    3. 按位异或

    ^ 规则:同为0;异为1.

    #include 
    
    int main() {
    
    	int a = 3;
    	int b = -5;
    	int c3 = a ^ b;
    	printf("%d\n", c3); // -8
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1-9

    异或的两个规律

    a ^ a = 0
    0 ^ a = a
    
    • 1
    • 2

    4. 位操作符的应用

    应用1

    写一个程序,用来交换两个数。

    方法一:

    #include 
    
    int main() {
    
    	int a = 3;
    	int b = 5;
    	printf("%d, %d\n", a, b); 
    	
    	int tmp = a;
    	a = b;
    	b = tmp;
    	printf("%d, %d\n", a, b); 
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    方法二:

    #include 
    
    int main() {
    
    	int a = 3;
    	int b = 5;
    	printf("%d, %d\n", a, b); 
    
    	a = a + b;
    	b = a - b; // a+b-b => b = a
    	a = a - b; // a+b-a => a = b
    	printf("%d, %d\n", a, b); 
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    方法三:

    #include 
    
    int main() {
    
    	int a = 3;
    	int b = 5;
    	printf("%d, %d\n", a, b); 
    
    	a = a ^ b;
    	b = a ^ b; // a^b^b => a^0 => b = a
    	a = a ^ b; // a^b^a => 0^b => a = b
    	printf("%d, %d\n", a, b); 
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    统一输出结果:

    1-10

    总结:

    ① 方法一是创建一个新的变量来实现两数交换的,它最常用、效率高、可读性高。方法二和方法三则没有创建新的变量,虽然看似更高效,但也带来了缺点。

    ② 方法二,我们知道 int 类型是有范围的,当两数相加相减时超出了 int 类型的范围,就会产生截断效果,所以在极端的情况下,这并不合理。

    ③ 方法三,异或本身对于操作数的要求就是必须为整数,所以对于两个浮点数的交换,也并不合理。

    ④ 综上所述,如果不是面试问到或者题目问到这样的两数交换,我们还是采用方法一,因为程序要么错,要么对,不能模棱两可。

    应用2

    写一个程序,求一个整数存储在内存中的二进制中1的个数。

    #include 
    
    int main() {
    
    	int a = 13;
    	int count = 0;
    
    	for (int i = 0; i < 32; i++) {
    		int result = (a >> i) & 1;
    		if (result == 1) { // 某一位结果为1,代表是二进制的值为1
    			count++;
    		}
    	}
    	printf("整数 %d 在内存中二进制为1的个数为:%d\n", a, count); // 
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    输出结果:

    2-1

    思路: 让底层的二进制补码右移的同时,按位与1. 与的结果为 1,则说明当前二进制位是 1.

    2-2

    四、赋值操作符

    = 
    += 
    -= 
    *= 
    /= 
    &= 
    ^= 
    |= 
    >>= 
    ><<=
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    五、单目操作符

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

    单目操作符,顾名思义,它只有一个操作数。

    sizeof 操作符的使用

    程序清单:

    #include 
    
    void test1(int arr[]) // int* arr
    {
    	printf("%d\n", sizeof(arr));
    }
    
    void test2(char ch[]) // char* arr
    {
    	printf("%d\n", sizeof(ch));
    }
    
    int main()
    {
    	int arr[10] = { 0 };
    	char ch[10] = { 0 };
    	printf("%d\n", sizeof(arr)); // 40
    	printf("%d\n", sizeof(ch));  // 10
    	test1(arr); // 4/8
    	test2(ch);  //4/8
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    输出结果:(32 位)

    2-3

    总结:

    ① sizeof 是一个操作符,不是一个函数。
    ② sizeof 用来求类型 / 变量在内存中储存的大小。
    ③ sizeof 在操作于数组时,需要明白的是针对于整个数组,还是针对于函数接收数组的形参;前者计算的是整个数组内元素所占内存的大小,后者是计算一个指针变量的所占内存的大小。

    自增、自减

    #include 
    
    int main() {
    
    	int a = 1;
    	int b = a++; // b = a; a = a + 1;
    	int c = ++a; // a = a + 1; c = a;
    
    	printf("%d\n", a); 
    	printf("%d\n", b);
    	printf("%d\n", c);
    
    	return 0;
    }
    
    // 输出结果:
    // 3
    // 1
    // 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注意事项:

    ① 自增分为前置与后置,++前置表示:先自增,后使用;++后置表示:先使用,后自增。(自减也是如此)

    自增自减会对当前操作的变量直接生效,也就是说,底层存储的二进制也被修改了。

    ③ 在日常程序中,自增自减正常使用即可。以前在学校的时候,C语言 期末考试会考那些逻辑非常怪的题目,其中就有多个自增自减放在一起使用的,其实没有必要深究,因为一个好的程序压根就不会那么写。

    六、关系操作符

    注意在字符串比较的时候,不能使用双等号作为比较,它需要 strcmp 字符串函数来操作两个字符串。

    >
    >=
    <
    <=
    != 				// 用于测试“不相等”
    == 				// 用于测试“相等”
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    七、逻辑操作符

    && 				// 逻辑与
    || 				// 逻辑或
    
    • 1
    • 2

    程序清单:

    #include 
    
    int main()
    {
    	int i = 0, a = 0, b = 2, c = 3;
    	i = a++ && ++b && c++;
    
    	int j = 0, x = 1, y = 2, z = 3;
    	j = x++ || ++y || z++;
    
    	printf("a = %d, b = %d, c = %d\n", a, b, c);
    	printf("x = %d, y = %d, z = %d\n", x, y, z);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出结果:

    2-4

    注意事项:

    ① 逻辑与表示的 " 两者都 ",所以当前者为否的时候,后面就不计算了。
    ② 逻辑或表示的 " 两者任意一个 ",所以当前者为真的时候,后面就不计算了。

    八、条件操作符

    a ? b : c 
    // a 成立,执行 b,否则执行 c
    
    • 1
    • 2

    程序清单:

    #include 
    
    int main() {
    
    	int a = 3;
    	int b = 5;
    	int c = 0;
    	
    	if (a > b) {
    		c = a;
    	}else {
    		c = b;
    	}
    
    	printf("%d\n", c);	// 5
    
    	c = a > b ? a : b;
    	printf("%d\n", c);	// 5
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    九、逗号表达式

    result = exp1, exp2, exp3...
    // 从左向右依次执行,result 结果为最右边表达式的结果。
    
    • 1
    • 2

    程序清单:

    #include 
    
    int main() {
    
    	int a = (3, 5, 7); // a = 7
    	printf("a = %d\n", a); 
    
    	int x = 1;
    	int y = 2;
    	int z = (x > y, x = y + 1, y = x + 1); // z = y
    	printf("x = %d, y = %d, z = %d\n", x, y, z);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出结果:

    2-5

    十、其他操作符

    []            // 下标引用操作符
    ()			  // 函数调用操作符
    .			  // 结构体变量.成员名
    ->			  // 结构体指针变量->结构体成员
    
    • 1
    • 2
    • 3
    • 4

    1. 下标引用操作符

    程序清单:

    #include 
    
    int main() {
    	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    	printf("%d\n", arr[5]);
    	printf("%d\n", 5[arr]); // 这样写不会出错,但没有人这么写
    	
    	return 0;
    }
    
    // 输出结果:
    // 6
    // 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意事项:

    在我们平时写出 arr[5] 这样的代码时,看上去很平常,但实际上 [ ] 确实是一个操作符,arr 和 5 是它的两个操作数。

    2. 函数调用操作符

    swap(a, b);
    print();
    
    • 1
    • 2

    注意事项:

    在我们平时写出上面那样的代码时,看上去也很平常,但实际上 () 确实是一个操作符。例如:

    第一个 () 有三个操作数,swap、a、b.
    第二个 () 只有一个操作数:print.

    3. 结构体成员访问操作符

    程序清单:

    #include 
    
    struct Student
    {
    	char name[20];  // 名字
    	int age;		// 年龄
    	int studentID;  // 学号
    };
    
    int main() 
    {
    	struct Student student1 = {"Jack", 18, 32};  
    	struct Student student2 = {"Bruce", 20, 05};
    	printf("%s %d %d\n", student1.name, student1.age, student1.studentID);
    
    	struct Student* ps1 = &student1;
    	printf("%s %d %d\n", (*ps1).name, (*ps1).age, (*ps1).studentID); // 先解引用再访问
    	printf("%s %d %d\n", ps1->name, ps1->age, ps1->studentID);
    	
    	return 0;
    }
    
    // 输出结果:
    // Jack 18 32
    // Jack 18 32
    // Jack 18 32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    可持久化数据结构(待补)
    Source Insight 工具栏图标功能介绍
    【LeetCode-简单题】977. 有序数组的平方
    谈谈对面向对象的理解
    防止电脑自动锁屏
    EarlyStopping
    NumPy和Pandas中的广播
    Mybatis缓存
    LeetCode·每日一题·952.按公因数计算最大组件大小·并查集
    网络编程:协议拆分练习(通过协议控制机械臂 TFTP传输文件)
  • 原文地址:https://blog.csdn.net/lfm1010123/article/details/127735892