C 语言复习总结记录五

ops/2024/11/27 10:56:48/

C 语言复习总结记录五

一 操作符分类

  • 算术操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号表达式
  • 下标引用、函数调用和结构成员

二 算术操作符

+    -   *   /   %

1、除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数

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

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

三 移位操作符

<< 左移操作符
>> 右移操作符注:移位操作符的操作数只能是整数

3.1 左移操作符

移位规则 :左边抛弃、右边补 0

image-20241118194336707

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 操作符的属性

复杂表达式求值有三个影响因素

  1. 操作符优先级

  2. 操作符结合性

  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 语言中数组作为形参可以接收指向数组的指针,是因为数组名在传递给函数时被自动转换为数组首元素的指针,这样可以避免数据的复制并能够通过指针对数组进行访问和修改


http://www.ppmy.cn/ops/137059.html

相关文章

代理模式 (Proxy Pattern)

文章目录 代理模式 (Proxy Pattern)原理分类优点缺点示例代码静态代理1. 定义接口2. 创建真实类3. 创建代理类4. 客户端代码输出结果 动态代理&#xff08;基于 JDK&#xff09;1. 定义接口和真实类2. 创建动态代理类3. 客户端代码输出结果 UML 类图静态代理动态代理 使用场景小…

css:转换

转换 移动 /* transform: translate(100px, 200px); */transform: translateX(100px);transform: translateY(100px); /*一个意思*/ 如果后面跟百分数的意思是移动盒子自身x/y方向长度的百分比&#xff0c;可以用作子绝父相控制盒子水平居中垂直居中 translate里的xy值是相对…

Perforce SAST专家详解:自动驾驶汽车的安全与技术挑战,Klocwork、Helix QAC等静态代码分析成必备合规性工具

自动驾驶汽车安全吗&#xff1f;现代汽车的软件包含1亿多行代码&#xff0c;支持许多不同的功能&#xff0c;如巡航控制、速度辅助和泊车摄像头。而且&#xff0c;这些嵌入式系统中的代码只会越来越复杂。 随着未来汽车的互联程度越来越高&#xff0c;这一趋势还将继续。汽车越…

AdaPipe:动态规划解决显存和GPU在LLM计算中出现气泡问题

目录 AdaPipe:动态规划解决显存和GPU在LLM计算中出现气泡问题 0-5表示不同数据 大的方块表示:管道,便于理解了想成GPU 黄色方块表示显存 Stage表示Attention和FFN layer(Projection和MLP) 重计算和分区策略:细化了Attention和FFN layer Transformer中的管道 AdaPi…

鸿蒙动画开发07——粒子动画

1、概 述 粒子动画是在一定范围内随机生成的大量粒子产生运动而组成的动画。 动画元素是一个个粒子&#xff0c;这些粒子可以是圆点、图片。我们可以通过对粒子在颜色、透明度、大小、速度、加速度、自旋角度等维度变化做动画&#xff0c;来营造一种氛围感&#xff0c;比如下…

【算法】欧几里得与拓展欧几里得算法

目录 一、欧几里得算法 二、拓展欧几里得算法 2.1 裴蜀定理 2.2 拓展欧几里得算法 2.3 例题 三、线性同余方程 3.1 概念 3.2 例题 一、欧几里得算法 欧几里得算法又称辗转相除法&#xff0c;可用于求解两个数的最大公约数 其思路&#xff1a; gcd(a, b) gcd(b, a%b…

241126学习日志——[CSDIY] [ByteDance] 后端训练营 [19]

CSDIY&#xff1a;这是一个非科班学生的努力之路&#xff0c;从今天开始这个系列会长期更新&#xff0c;&#xff08;最好做到日更&#xff09;&#xff0c;我会慢慢把自己目前对CS的努力逐一上传&#xff0c;帮助那些和我一样有着梦想的玩家取得胜利&#xff01;&#xff01;&…

Deepnote、JupyterLab、Google Colab、Amazon SageMaker、VS Code对比

功能比较 平台语言支持扩展性数据连接可视化能力DeepnotePython、R、SQL中等&#xff0c;依赖云端支持主要云平台&#xff08;BigQuery、Snowflake等&#xff09;内置仪表盘与交互图表JupyterLab多种语言&#xff0c;插件支持广泛极高&#xff0c;完全可自定义使用库&#xff…