C 语言复习总结记录五
一 操作符分类
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
二 算术操作符
+ - * / %
1、除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
2、对于 / 操作符如果两个操作数都为整数,执行整数除法,而只要有浮点数执行的就是浮点数除法
3、% 操作符的两个操作数必须为整数,返回的是整除之后的余数
三 移位操作符
<< 左移操作符
>> 右移操作符注:移位操作符的操作数只能是整数
3.1 左移操作符
移位规则 :左边抛弃、右边补 0
3.2 右移操作符
首先右移运算分两种:
int num = -1; //内存中 num 的补码如下
11111111111111111111111111111111 //32 个 1
1、逻辑移位 :左边用 0 填充,右边丢弃
//左边补 0, 右边移出
01111111111111111111111111111111 //一个0 31 个 1
2、算术移位:左边用原该值的符号位填充,右边丢弃
//左边补符号位 1, 右边移出
01111111111111111111111111111111 //32 个 1
移位操作的变量自身不改变
对 char 类型数据进行移位运算时会对它的 ASCII 码值进行操作。
对 byte、short 或者 char 类型数据进行移位操作时,会先把它们整型提升为 int 后再进行运算。
左移运算符,每移动一位,相当于扩大 2 倍。移动几位相当于乘以 2 的几次方
右移运算符,每移动一位,相当于缩小 2 倍。移动几位相当于除以 2 的几次方
//对于移位运算符,不要移动负数位,这个是标准未定义的
int num = 1;
num >> -1; //error
四 位操作符
位操作符有三个,操作数必须是整数
& //按位与
| //按位或
^ //按位异或
练习
#include <stdio.h>
int main()
{int num1 = 1; //0000 0001int num2 = 2; //0000 0010num1 & num2; //0000 0000num1 | num2; //0000 0011num1 ^ num2; //0000 0011return 0;
}
不创建临时变量,实现两个数的交换
void swap(int* a, int* b)
{*a = *a ^ *b;*b = *a ^ *b;*a = *a ^ *b;
}
求一个整数存储在内存中的二进制中 1 的个数
十进制转二进制: 除 2 取余法 (短除法)
每次将整数部分除以 2,余数为该位上的数,而商继续除以 2,这个步骤一直持续下去,直至商为 0 为止9 / 2 = 4 余 1
4 / 2 = 2 余 0
2 / 2 = 1 余 0
1 / 2 = 0 余 1然后把所有余数按相反的顺序排列,即得到二进制数 1001
//方式一
#include <stdio.h>
int main()
{int num = 10;int count= 0; //计数while(num) //如果数很大, 循环耗时会很久{if(num % 2 == 1)count++;num = num/2;}printf("二进制中 1 的个数 = %d\n", count);return 0;
}//方式二
#include <stdio.h>
int main()
{int num = -1;int i = 0;int count = 0;//计数for(i=0; i<32; i++) //这里必须循环 32 次, 但不是所有输入的数值都很大, 如何让循环次数等于整数中 1 的个数呢?{if( num & (1 << i) )count++; }printf("二进制中1的个数 = %d\n",count);return 0;
}//方式三
#include <stdio.h>
int main()
{int num = -1;int i = 0;int count = 0;//计数while(num){count++;num = num&(num-1); //a & (a - 1) : 去掉整数二进制最末尾的 1}printf("二进制中1的个数 = %d\n",count);return 0;
}
五 赋值操作符
int weight = 160;
weight = 140;
double salary = 10000.0;
salary = 20000.0; //使用赋值操作符赋值//赋值操作符连续使用
int a = 10;
int x = 0;
int y = 20;
a = x = y + 1; //连续赋值
复合赋值符
+= 加等于
-= 减等于
*= 乘等于
/= 除等于
%= 取模等于
>>= 右移等于
<<= 左移等于
&= 与等
|= 或等
^= 异或等
int x = 10;
x = x + 10;
x += 10; //复合赋值, 其它复合赋值符同理, 代码更简洁
六 单目操作符
6.1 单目操作符介绍
~ 对一个数的二进制按位取反
- 负值
+ 正值
! 逻辑反操作
& 取地址
sizeof 操作数的类型长度(以字节为单位)
-- 前置、后置 --
++ 前置、后置 ++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
#include <stdio.h>
int main()
{int a = -10;int *p = NULL;printf("%d\n", !2); //0printf("%d\n", !0); //1a = -a;p = &a;printf("%d\n", sizeof(a));printf("%d\n", sizeof(int)); //函数调用printf("%d\n", sizeof a); //单目操作符printf("%d\n", sizeof int); //不支持return 0;
}
sizeof 求变量(类型)所占空间的大小
6.2 sizeof 和 数组
数组作为形参传递的是其头指针
#include <stdio.h>
void test1(int arr[])
{printf("%d\n", sizeof(arr)); // 8 (64 位的平台)
}void test2(char ch[])
{printf("%d\n", sizeof(ch)); // 8
}int main()
{int arr[10] = {0};char ch[10] = {0};printf("%d\n", sizeof(arr)); // 4 * 10printf("%d\n", sizeof(ch)); // 1 * 10test1(arr);test2(ch);return 0;
}
//++和--运算符
//前置++和--
#include <stdio.h>
int main()
{int a = 10;int x = ++a; //a = 11, x = 11int y = --a; //a = 10, y = 10return 0;
}//后置++和--
#include <stdio.h>
int main()
{int a = 10;int x = a++; // x = 10, a = 11int y = a--; // y = 11, a = 10return 0;
}
七 关系操作符
不要发生 == 和 = 的误写
>
>=
<
<=
!= 用于测试 不相等
== 用于测试 相等
八 逻辑操作符
&& 只要左侧表达式为真,右侧表达式将不会再执行
&& 逻辑与
|| 逻辑或
区分 逻辑与 和 按位与 ,逻辑或 和 按位或;一个是位操作符一个是逻辑操作符
1 & 2 -----> 0
1 && 2 -----> 1
1 | 2 -----> 3
1 || 2 -----> 1
#include <stdio.h>
int main()
{int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++ && ++b && d++;//i = a++ || ++b || d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d); //1, 2 ,3, 4return 0;
}
九 条件操作符
也叫三目运算符
exp1 ? exp2 : exp3//小练习
if (a > 5) b = 3;
else b = -3;
//转换成条件表达式
a > 5 ? b = 3 : b = -3;//使用条件表达式实现找两个数中较大
a > b ? a : b;
十 逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。从左向右依次执行。整个表达式的结果是最后一个表达式的结果
//代码1
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1); // c = 13//代码2
if (a = b + 1, c = a / 2, d > 0) //if (d > 0)//代码3 -- 抽取相同的处理逻辑
a = get_val();
count_val(a);
while (a > 0)
{ //业务处理a = get_val();count_val(a);
}//如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a > 0)
{//业务处理
}
十一 下标引用、函数调用和结构成员
[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10]; //创建数组
arr[9] = 10; //下标引用操作符
[] 的两个操作数是 arr 和 9
() 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include <stdio.h>void test1()
{printf("hehe\n");
}void test2(const char *str)
{printf("%s\n", str);
}int main()
{test1(); //函数调用操作符test2("hello bit."); //函数调用操作符return 0;
}
访问一个结构成员
. 结构体.成员名
-> 结构体指针->成员名
#include <stdio.h>
struct Stu
{char name[10];int age;char sex[5];double score;
};
void set_age1(struct Stu stu)
{stu.age = 18;
}
void set_age2(struct Stu* pStu)
{pStu->age = 18;//结构成员访问
}
int main()
{struct Stu stu;struct Stu* pStu = &stu;//结构成员访问stu.age = 20;//结构成员访问set_age1(stu);pStu->age = 20;//结构成员访问set_age2(pStu);return 0;
}
十二 表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
12.1 隐式类型转换
C 的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为 整型提升
整型提升的意义:表达式的整型运算要在 CPU 的相应运算器件内执行,CPU 内整型运算器 (ALU) 的操作数的字节长度。一般就是 int 的字节长度,同时也是 CPU 的通用寄存器的长度。因此,即使两个 char 类型的相加,在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长度。
通用 CPU(general-purpose CPU)是难以直接实现两个 8 比特直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于 int 长度的整型值,都必须先转换为 int 或 unsigned int,然后才能送入 CPU 去执行运算
//实例1
char a, b, c;
...
a = b + c;
b 和 c 的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果将被截断,然后再存储于 a 中
整形提升是按照变量的数据类型的符号位来提升的
//负数的整形提升
char c1 = -1;
/*变量 c1 的二进制位(补码)中只有 8 个比特位:1111111因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为 1提升之后的结果是:11111111111111111111111111111111
*///正数的整形提升
char c2 = 1;
/*变量 c2 的二进制位(补码)中只有8个比特位:00000001因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为 0提升之后的结果是:00000000000000000000000000000001
*///无符号整形提升,高位补 0
//练习
int main()
{char a = 0xb6; // 11 * 16 + 6 = 182short b = 0xb600; // 11 * 16^3 + 6 * 16^2 + 0 + 0 = 46592int c = 0xb6000000;//182 对应的二进制序列为 0000 0000 0000 0000 0000 0000 1011 0110, 存入 char 类型, 需要发生截断 1011 0110, 在判断 a == 0xb6, 比较也是表达式, 发生整型提升, 高位补充符号位, 即为如下 1111 1111 1111 1111 1111 1111 1011 0110, 所以 if 结果为 falseif(a==0xb6)printf("a");//此处同理if(b==0xb600)printf("b");if(c==0xb6000000)printf("c"); //最终程序仅输出 creturn 0;
}
c 只要参与表达式运算,就会发生整形提升,表达式 +c 发生提升,所以 sizeof(+c) 是 4 个字节。表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但 sizeof© 就是 1 个字节
int main()
{char c = 1;printf("%u\n", sizeof(c)); //1printf("%u\n", sizeof(+c)); //4printf("%u\n", sizeof(-c)); //4return 0;
}
12.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为 寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算
警告:但是算术转换要合理,要不然会有一些潜在的问题
float f = 3.14;
int num = f; //隐式转换,会有精度丢失
12.3 操作符的属性
复杂表达式求值有三个影响因素
-
操作符优先级
-
操作符结合性
-
是否控制求值顺序
相邻操作符先执行哪个,取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性
操作符优先级
操作符 描述 用法示例 结果类型 结合性 是否控制求值顺序
() 聚组 (表达式) 与表达式同 N/A 否
() 函数调用 rexp(rexp,...,rexp) rexp L-R 否
[ ] 下标引用 rexp[rexp] lexp L-R 否
. 访问结构成员 lexp.member_name lexp L-R 否
-> 访问结构指针成员 rexp->member_name lexp L-R 否
++ 后缀自增 lexp ++ rexp L-R 否
-- 后缀自减 lexp -- rexp L-R 否
! 逻辑反 ! rexp rexp R-L 否
~ 按位取反 ~ rexp rexp R-L 否
+ 单目,表示正值 + rexp rexp R-L 否
- 单目,表示负值 - rexp rexp R-L 否
++ 前缀自增 ++ lexp rexp R-L 否
-- 前缀自减 -- lexp rexp R-L 否
* 间接访问 * rexp lexp R-L 否
& 取地址 & lexp rexp R-L 否
sizeof 取其长度,以字节表示 sizeof rexp sizeof
(类型) 类型转换 (类型) rexp R-L 否
* 乘法 rexp * rexp rexp L-R 否
/ 除法 rexp / rexp rexp L-R 否
% 整数取余 rexp % rexp rexp L-R 否
+ 加法 rexp + rexp rexp L-R 否
- 减法 rexp - rexp rexp L-R 否
<< 左移位 rexp << rexp rexp L-R 否
>> 右移位 rexp >> rexp rexp L-R 否
> 大于 rexp > rexp rexp L-R 否
>= 大于等于 rexp >= rexp rexp L-R 否
< 小于 rexp < rexp rexp L-R 否
<= 小于等于 rexp <= rexp rexp L-R 否
== 等于 rexp == rexp rexp L-R 否
!= 不等于 rexp != rexp rexp L-R 否
& 位与 rexp & rexp rexp L-R 否
^ 位异或 rexp ^ rexp rexp L-R 否
| 位或 rexp | rexp rexp L-R 否
&& 逻辑与 rexp && rexp rexp L-R 是
|| 逻辑或 rexp || rexp rexp L-R 是
? : 条件操作符 rexp ? rexp : rexp rexp N/A 是
= 赋值 lexp = rexp rexp R-L 否
+= 以...加 lexp += rexp rexp R-L 否
-= 以...减 lexp -= rexp rexp R-L 否
*= 以...乘 lexp *= rexp rexp R-L 否
/= 以...除 lexp /= rexp rexp R-L 否
%= 以...取模 lexp %= rexp rexp R-L 否
<<= 以...左移 lexp <<= rexp rexp R-L 否
>>= 以...右移 lexp >>= rexp rexp R-L 否
&= 以...与 lexp &= rexp rexp R-L 否
^= 以...异或 lexp ^= rexp rexp R-L 否
|= 以...或 lexp |= rexp rexp R-L 否
, 逗号 rexp,rexp rexp L-R 是
一些 问题表达式
// 表达式的求值部分由操作符的优先级决定
// 表达式1
a*b + c*d + e*f
代码 1 在计算的时候,由于 * 比 + 的优先级高,只能保证,* 的计算是比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早执行。所以表达式的计算机顺序就可能是:
a * b
c * d
a * b + c * d
e * f
a * b + c * d + e * f//或
a * b
c * d
e * f
a * b + c * d
a * b + c * d + e * f
//表达式2
c + --c;
同上,操作符的优先级只能决定自减 – 的运算在 + 的运算的前面,但是我们并没有办法得知,+ 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的
//代码3-非法表达式
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;return ++count;
}int main()
{int answer;answer = fun() - fun() * fun();printf( "%d\n", answer); //输出 -10return 0;
}
虽然在大多数的编译器上求得结果都是相同的。但上述代码 answer = fun() - fun() * fun(); 我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定
//代码5
#include <stdio.h>
int main()
{int i = 1;int ret = (++i) + (++i) + (++i);printf("%d\n", ret); printf("%d\n", i); //4return 0;
}
//尝试在linux 环境gcc编译器(12),VS2013环境下都执行(10),看结果
这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的。因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。
总结:如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的
关于数组传参的一些补充
在 C 语言中,数组和指针之间有着紧密的关联。当我们将数组作为形参传递给函数时,实际上传递给函数的是数组的指针,而不是数组本身。这是因为,数组名在传递给函数时会被自动转换为指向数组首元素的指针。即 C 语言里数组不能作为函数的参数。如果函数参数写成了数组类型的形式,将会一律视为指针类型
C 语言中的数组实际上是由元素的连续内存块组成的,而数组名代表的是数组的首元素的内存地址。因此,传递数组名给函数时,将传递数组首元素的指针给函数
通过将数组作为指针传递给函数,可以避免在函数调用时进行大量的数据复制,而直接传递数组的内存地址,节省了时间和空间。 在函数内部,通过指针的方式可以访问数组的所有元素,因为数组的内存布局是连续的。函数可以通过指针进行对数组元素的读取和修改操作。
总结 C 语言中数组作为形参可以接收指向数组的指针,是因为数组名在传递给函数时被自动转换为数组首元素的指针,这样可以避免数据的复制并能够通过指针对数组进行访问和修改