目录
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用,函数调用,结构成员
表达式求值
隐式类型转换
算术转换
操作符的属性
练习题
代码仓库
算术操作符
加(+),减(-),乘(*),除(/),取模(%)
- 除法:
- 整数除法(除号两端都是整数就执行整数除法)
- 浮点数除法(除号两端只要有一个是小数就执行浮点数除法)
- 除数不能为0
- 取模:
- 得到整除后的余数
- 两个操作数必须都是整数
移位操作符
左移操作符(<<),右移操作符(>>)
- 移位移动的是补码的二进制序列
- 移位操作符的操作数只能是整数
- 整数的二进制有三种表示形式,原码,反码,补码
根据正负直接写出的二进制序列就是原码 正整数的原反补一样,负整数原反补需要计算 二进制最高位是符号位,符号位1表示负数0表示正数一个整型是4个字节=32个bit位 int a = 15; 00000000 00000000 00000000 00001111 //原码 00000000 00000000 00000000 00001111 //反码 00000000 00000000 00000000 00001111 //补码最高位是符号位,1表示负数0表示正数 int b = -15; 10000000 00000000 00000000 00001111 //原码 反码:符号位不变其他按位取反 11111111 11111111 11111111 11110000 //反码 补码:反码+1 11111111 11111111 11111111 11110001 //补码整数在内存中存储的是补码 计算的时候也是使用补码计算
- 右移分为算术右移,逻辑右移
算术右移:右边丢弃,左边补原来的符号位 逻辑右移:右边丢弃,左边直接补0 C语言没有明确规定用哪个,一般编译器采用算术右移
- 左移:左边丢弃,右边补0
- 移动负数位是标准未定义行为
位操作符
按位与(&),按位或(|),按位异或(^)
- 操作二进制位
int a = 3; int b = -5; a的补码:00000000 00000000 00000000 00000011 b的补码:11111111 11111111 11111111 11111011按二进制位与(&):对应二进制位有0为0,两个同时1才为1 a & b: 00000000 00000000 000000000 00000011 按二进制位或(|):对应二进制位有1为1,两个同时0才为0 a | b:11111111 11111111 11111111 11111011按二进制位异或(^):对应二进制位相同为0,相异为1 a ^ b:11111111 11111111 11111111 11111000计算结果是补码,输出需要转为原码 原码 = 补码-1然后符号位不变取反
面试题:不能创建临时变量(第三个变量),实现两个整数的交换。
//方法1 int a = 3; int b = 5; a = a + b; b = a - b; a = a - b; //缺陷:假如a或b特别大,那么a+b就可能超出整型范围发生截断//方法2 a = a ^ b; b = a ^ b; a = a ^ b;//异或特点: //a ^ a = 0 //a ^ 0 = a //a ^ b = b ^ a 异或支持交换律
赋值操作符
- 赋值(=)
int x = 1; int y = 2; int z = 3; //也可以连续赋值 x = y = z = 4;
- 复合赋值符,+=,-=,*=,/=,%=,>>=,<<=,&=,|=,^=
单目操作符
- 逻辑反操作(!)
- 负值(-)
- 正值(+)
- 取地址(&),解引用操作符(*)应用于指针
int a = 10; int* pa = &a; //取出a的地址,pa是指针变量 *pa = 20; //通过pa中存放的地址,找到指向的空间(内容)
- sizeof 不是函数,是操作符,计算的是类型创建变量的大小,单位是字节
int a = 10; printf("%d\n", sizeof(a)); //4 printf("%d\n", sizeof a); //4 可以不带括号 int arr[10]; printf("%d\n", sizeof(arr)); //40
- ~ :对一个数的二进制位按位取反
- 后置++(后置-- 同理)
int a = 1; int b = a++; //b = a, a = a + 1 先使用,后++
前置++(前置-- 同理)
int a = 1; int b = ++a; //a = a + 1,b = a 先++,后使用
- 强制类型转换:(类型)
int a = (int)3.14;
关系操作符
- >, >=, <, <=, !=, ==
逻辑操作符
- 逻辑与(&&),逻辑或(||)
- && 左边为假右边就不计算了
- || 左边为真右边就不计算了
- &&,||,!,如果计算结果为真,使用1表示
条件操作符
- e1 ? e2 : e3
int a = 0; int b = 0;if(a>5) {b = 3; } else {b = -3; }//转成条件操作符 b = (a>5) ? 3 : -3;
逗号表达式
- 从左向右计算,整个表达式的结果是最后一个表达式的结果
int a = 1; int b = 2; int c = (a>b, a=b+10, a, b=a+1); //13 //例子2 a = get(); count(a); while(a>0) {a = get();count(a); } //改成逗号表达式 while(a=get(), count(a), a>0) {}
下标引用,函数调用,结构成员
- 下标引用操作符 [ ]
int arr[10]; arr[3] = 4; //arr和3是操作数
- 函数调用操作符()
int len = strlen("abc"); //操作数是函数名和参数
- 访问结构成员,结构体.成员名,结构体指针->成员名
表达式求值
- 表达式在计算的过程中有哪些类型转换?有些表达式的操作数在求值的过程中可能需要转换为其他类型,类型转换有整型提升,算术转换
- 表达式的求值顺序是怎么样的?表达式求值的顺序一部分是由操作符的优先级和结合性决定
隐式类型转换
- C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符(char)和短整型(short)操作数在使用之前被转换为普通整型,这种转换称为整型提升
- 整型提升的意义:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
- 如何进行整体提升呢?
char c1 = 5; //00000000 00000000 00000000 00000101 这是5的整型 //00000101 存入char发生截断char c2 = 127; //00000000 00000000 00000000 01111111 //01111111//整形提升是按照变量的数据类型的符号位来提升的,前面补符号位 char c3 = c1 + c2 //00000101 c1 //00000000 00000000 00000000 00000101 c1整型提升 //01111111 c2 //00000000 00000000 00000000 01111111 c2整型提升 //整型提升后相加 //00000000 00000000 00000000 10000100 //10000100 c3发生截断printf("%d\n", c3); //%d:10进制的形式打印有符号的整数,遇到char需要整型提升 //1111111 11111111 11111111 10000100 c3整型提升,前面符号位补齐,这里是补码,转成原码然后打印
算术转换
- 如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
//下面的层次体系称为寻常算术转换。 //从上往下依次排列,下面类型遇到上面类型则需要转为上面类型long doubledoublefloatunsigned long intlong intunsigned intint
操作符的属性
- 复杂表达式的求值有三个影响的因素,操作符的优先级,操作符的结合性,是否控制求值顺序。
- 相邻操作符优先级高的先算,低的后算
- 相邻操作符优先级相同的情况下,看结合性
C语言中的运算符优先级可以分为多个级别,从高到低依次为:
- 括号类操作符(如
()
、[]
、->
、.
)。- 单目运算符(如
!
、\~
、++
、--
、-
)。- 算术运算符(如
+
、-
、*
、/
、%
)。- 关系运算符(如
<
、>
、==
、!=
、<=
、>=
)。- 逻辑运算符(如
&&
、||
、!
)。- 条件运算符(如
? :
)。- 赋值运算符(如
=
、+=
、-=
、*=
、/=
、%=
、%=
)。- 逗号运算符(
,
)。
左结合性(Left-to-right)
大多数二元操作符都是左结合的,这意味着它们从左到右结合。常见的左结合操作符包括:
算术操作符:
+
,-
,*
,/
,%
关系操作符:
<
,<=
,>
,>=
相等操作符:
==
,!=
位操作符:
&
,|
,^
,<<
,>>
逻辑操作符:
&&
,||
逗号操作符:
,
a - b + c // 先计算 a - b,然后再计算结果 + c
右结合性(Right-to-left)
一些操作符是右结合的,这意味着它们从右到左结合。常见的右结合操作符包括:
赋值操作符:
=
,+=
,-=
,*=
,/=
,%=
等三元操作符:
? :
一元操作符:
!
,~
,++
,--
,+
(正号),-
(负号),*
(指针解引用),&
(取地址),sizeof
类型转换操作符:
(type)
a = b = c // 先计算 b = c,然后再计算 a = (b = c)
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的
练习题
答:A
答:a=9, b=23, c=8 (b=++c, c++, ++a, a++)整体是一个逗号表达式
写一个函数,返回参数二进制中1的个数
//思路:先取出第一位比较,然后右移一位
int get_one(int _num)
{int count = 0;for (int i = 0; i < 32; i++) if ((_num>>i) & 1) count++;return count;
}int main()
{int num;scanf("%d", &num);printf("%d\n", get_one(num));return 0;
}
//方法2
//n = n & (n-1) 这个表达式会让n的二进制最右边的1消失
int get_one(int _num)
{int count = 0;for (; _num != 0; _num = _num & (_num - 1), count++);return count;
}int main()
{int num;scanf("%d", &num);printf("%d\n", get_one(num));return 0;
}
获取一个整数二进制序列中所有偶数位和奇数位并分别打印
//思路:假设第一位是奇数位,第一位右移0位和1相与打印,第三位右移2位...,第5位右移4位...,第31位右移30位...
//偶数位:第2位右移1位...,第4位右移3位...,第32位右移31位...
int main()
{int n;scanf("%d", &n);for (int i = 30; i >= 0; i -= 2) printf("%d ", (n >> i) & 1);printf("\n");for (int i = 31; i > 0; i -= 2) printf("%d ", (n >> i) & 1);return 0;
}
输出两个int整数m和n的二进制序列中有多少个位不同
//思路:先异或,再算有几个1
int main()
{int m, n, count = 0;scanf("%d %d", &m, &n);int ret = m ^ n;while (ret = ret & (ret - 1)) count++;count++;printf("%d\n", count);return 0;
}
答:D,这个代码有问题,因为不能确定唯一的计算路径,不同的编译环境有不同的结果
下面代码的结果是?
答:>
解析:第一个点,全局变量和静态变量如果不初始化默认是0,
第二个点,sizeof的返回值类型是size_t也就是unsigned int,而 i 的类型是int,i 和 sizeof 比较 i 的类型会被算术转换为 unsigned int,此时 i 是 -1,所以 i 变成一个很大的正整数。
答:D
有序序列合并_牛客题霸_牛客网
输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。
//思路:用i, j分别遍历两个数组,将i, j对应的元素进行比较,小的输出然后往后遍历
int main() {//输入第一行int n, m;scanf("%d %d", &n, &m);//输入第二行,变长数组int n_arr[1000];for (int i = 0; i < n; i++) scanf("%d ", &n_arr[i]);//输入第三行int m_arr[1000];for (int i = 0; i < m; i++) scanf("%d ", &m_arr[i]);//合并int i = 0, j = 0;while (i < n && j < m){//谁小谁输出if (n_arr[i] < m_arr[j]) printf("%d ", n_arr[i++]);else printf("%d ", m_arr[j++]);}//走到这里是有一个数组输出完了,直接输出另一个数组剩下的数if (i == n) while (j < m) printf("%d ", m_arr[j++]);else while (i < n) printf("%d ", n_arr[i++]);return 0;
}
有序序列判断_牛客题霸_牛客网
输入一个整数序列,判断是否是有序序列,有序,指序列中的整数从小到大排序或者从大到小排序(相同元素也视为有序)。
代码仓库
Operator/Operator/main.c · 林宇恒/code_c - 码云 - 开源中国 (gitee.com)