接上一篇:自学C语言——函数递归
操作符的分类
- 算数操作符:+、-、*、/、%
- 位移操作符:<< >>(二进制相关)
- 位操作符:&、|、^(二进制相关)
- 赋值操作符:=、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=
- 单目操作符:!、++、--、&、*、+、-、~、sizeof、(类型)
- 关系操作符:>、>=、<、<=、==、!=
- 逻辑操作符:&&、||
- 条件操作符:?、:
- 逗号表达式:,
- 下标引用:[ ]
- 函数调用:()
- 结构成员访问:. 、->
二进制和进制转换
十进制15的其他进制表示形式:
2进制:1111
8进制:17
16进制:F
//16进制的数值之前写:0x ——>0xF printf 15
//8进制的数值之前写:0 ——> 017 printf 15
10进制中满10进1,10进制中都是由0~9数字组成的。其他进制同理,10用A/a表示,以此类推。
二进制:满2进1,2进制由0和1组成......
2进制转10进制:
把二进制数每一位上数字乘以该位对应的权,然后相加。
10进制转2进制:
用2整除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为小于1时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。
2进制转8进制3个一组
2进制转16进制4个一组
原码、反码、补码
整数的2进制表示方法有三种,即原码、反码和补码
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当作符号位,剩余的都是数值位。
整型是四个字节==32bit位,去掉最高位符号位,还有31位
符号位都是用0表示”正“,用1表示“负”。
整数分为有符号的整数和无符号的整数,有符号的整数分为正整数和负整数。
正整数的原码、反码、补码都相同。
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位一次按位取反就可以得到反码。
补码:反码+1就得到补码。
int n = -7;
原码:1 0000000000000000000000000000111
反码:1 1111111111111111111111111111000
补码:1 1111111111111111111111111111001
对于整型来说:数据存放内存中其实存放的是补码。
在计算机系统中,数值一律用补码来表示和储存。原因:是用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器 1+1➡1-(-1) );此外,补码与原码相互转换,其运算过程相同,不需要额外的硬件电路。
位移操作符
<<左移操作符
>>右移操作符
注:位移操作符的操作数只能是整数。
左移操作符
移位规则:左边抛弃,右边补0
#include <stdio.h>
int main()
{
int n = 12;
//00000000 00000000 00000000 00001100
int m = n << 1;//左移-移动的是二进制序列
//00000000 00000000 00000000 00011000
printf("%d\n", m);
printf("%d\n", n);
return 0;
}
//m=24,n=12
int main()
{
int a = -10;
//10000000 00000000 00000000 00001010//原码
//11111111 11111111 11111111 11110101//反码
//11111111 11111111 11111111 11110110//补码
int b = a << 1;//左移-移动的是二进制序列
//11111111 11111111 11111111 11101100//补码
//10000000 00000000 00000000 00010011//反码
//10000000 00000000 00000000 00010100//原码
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
//a=-10,b=-20
左边丢1位,右边补1位(补0)
右移操作符
位移规则(取决于编译器决定,大部分的编译器采用的是算数右移)
逻辑右移:左边用0填充,右边丢弃
算数右移:左边用原该值的符号位填充,右边丢弃(负数用1填充,正数用0填充)
int main()
{
int a = -10;
//10000000 00000000 00000000 00001010//原码
//11111111 11111111 11111111 11110101//反码
//11111111 11111111 11111111 11110110//补码
int b = a >> 1;//左移-移动的是二进制序列
//11111111 11111111 11111111 11111011//补码
//10000000 00000000 00000000 00000100//反码
//10000000 00000000 00000000 00000101//原码
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
//a=-10,b=-5
对于移位运算符,不要移动负数位,这个是标准未定义的。也不要超出位移的范围
int n = 0;
n >> -1;//error
int n = 0;
n << 50;//error
位操作符:&、|、^、~
& ——按位与
| ——按位或
^ ——按位异或
~ ——按位取反
操作数必须是整数,位——二进制位
对比:
&&——逻辑与(并且)
||——逻辑或(或者)
int main()
{
int a = -5;
//10000000 00000000 00000000 00000101
//11111111 11111111 11111111 11111010
//11111111 11111111 11111111 11111011//补码
int b = 13;
//00000000 00000000 00000000 00001101//原反补
int c = a & b;
//对应的二进制位进行与运算,有0为0,同1为1
//00000000 00000000 00000000 00001001//补码
printf("%d\n", c);
return 0;
}
//c=9
int main()
{
int a = -5;
//10000000 00000000 00000000 00000101
//11111111 11111111 11111111 11111010
//11111111 11111111 11111111 11111011//补码
int b = 13;
//00000000 00000000 00000000 00001101//原反补
int c = a | b;
//对应的二进制位进行或运算,有1为1,同0为0
//11111111 11111111 11111111 11111111//补码
//10000000 00000000 00000000 00000000//反码
//10000000 00000000 00000000 00000001//原码
printf("%d\n", c);
return 0;
}
//c=-1
int main()
{
int a = -5;
//10000000 00000000 00000000 00000101
//11111111 11111111 11111111 11111010
//11111111 11111111 11111111 11111011//补码
int b = 13;
//00000000 00000000 00000000 00001101//原反补
int c = a ^ b;
//对应的二进制位进行异或运算,相同为0,不同为1
//11111111 11111111 11111111 11110110//补码
//10000000 00000000 00000000 00001001//反码
//10000000 00000000 00000000 00001010//原码
printf("%d\n", c);
return 0;
}
//c=-10
int main()
{
int a = 0;
//00000000 00000000 00000000 00000000//原码
//11111111 11111111 11111111 11111111//反码
//10000000 00000000 00000000 00000001//补码
printf("%d\n", ~a);//~a 就是对a进行按位取反
return 0;
}
//c=-1
例:不能创建临时变量(第三个变量),实现两个整数的交换
int main()
{
int a = 10;
int b = 20;
printf("a=%d,b=%d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("a=%d,b=%d\n", a, b);
return 0;
}
//弊端:如果a和b的值过大,会造成溢出,无法实现互换
int main()
{
int a = 10;
int b = 20;
printf("a=%d,b=%d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a=%d,b=%d\n", a, b);
return 0;
}
//异或运算——相同为0,相异为1
//int a = 3;
//int b = 5;
//a^a=0
//a^0=a任何一个数异或0都等于他本身
例:求一个整数存储在内存中的二进制中1的个数
int main()
{
int n = 13;
int count = 0;
while (n)
{
if (n % 2 == 1)
count++;
n = n / 2;
}
printf("%d\n", count);
return 0;
}
//输出:3
//n不可以小于0
int main()
{
int n = -1;
int count = 0;
//15
//00000000 00000000 00000000 00001111
//00000000 00000000 00000000 00000001
int i = 0;
for (i = 0; i < 32; i++)
{
if (((n >> i) & 1) == 1)
count++;
}
//与运算
//00000000 00000000 00000000 00000001
printf("%d\n", count);
return 0;
}
例:写一个代码,判断n是否是2的次方数
int main()
{
int n = 0;
scanf("%d", &n);
if (n & (n - 1) == 0)
{
printf("yes\n");
}
else
{
printf("no\n");
}
return 0;
}
二进制位置0或者置1
#include<stdio.h>
//编写代码将13二进制序列的第5位修改为1,然后再改回0
//13的2进制序列:00000000000000000000000000001101
//将第五位改成1:00000000000000000000000000011101
//再改回0:00000000000000000000000000001101
int main()
{
int n = 13;
n |= (1 << 4);
printf("%d\n", n);//29
//00000000000000000000000000001101
//00000000000000000000000000010000
//00000000000000000000000000000001
//1<<4
n &= (~(1 << (5 - 1)));//~按位取反
//00000000000000000000000000011101
//00000000000000000000000000000001
//00000000000000000000000000010000
//11111111111111111111111111101111
printf("%d\n", n);//13
return 0;
}
单目操作符
单目操作符:
!、++、--、&、、*、+、-、~、sizeof、(类型)
单目操作符的特点是只有一个操作数,在单目操作符中&和*在指针中会用到
逗号表达式
exp1,ecp2,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;
}
——————————————————————————————
int main()
{
int a = 3;
int b = 0;
int c = 0;
int d = 0;
if (a = b + 1, c = a / 2, d > 0)
{
//如果d>0就执行
}
return 0;
}
下标访问[ ]、函数调用( )
[ ]下标引用操作符
操作数:一个数组名+一个索引值(下标)
int arr[10];//创建数组
arr[9] = 10;//使用下标引用操作符
[]的两个操作数是arr和9
int main()
{
int arr[10] = { 1,2,3,4,5,6 };
printf("%d\n", arr[5]);//6
return 0;
}
函数调用操作符
接受一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给哈数的参数。
int Add(int x, int y)
{
return(x + y);
}
void test()
{
}
int main()
{
Add(2, 3);//()函数调用操作符
//()的操作数不固定,Add、2、3
printf("hehe");//()函数调用操作符
//()的操作数不固定,printf、"hehe"
test();
//()的操作数不固定,test
//()至少有一个操作符
return 0;
}
结构成员访问操作符
结构体
C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设我想描述一个人的基本信息,这是单一的内置类型是不行的。
C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型。
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚至是其他结构体。
结构的声明
struct tag//命名
{
member-list;//成员列表
}
variable-list;//变量列表
描述一个学生
struct stu
{
char name[20];//名字
int age;//年龄
float score;//成绩
}s7, s8, s9;//全局变量(可省略)
struct stu s4;//全局变量
int main()
{
struct stu s1;//局部变量
struct stu s2;//局部变量
struct stu s3;//局部变量
return 0;
}
结构体变量的定义和初始化
struct stu
{
char name[20];//名字
int age;//年龄
float score;//成绩
}s7, s8, s9;//全局变量
struct stu s4;//全局变量
int main()
{
struct stu s1 ={"张三",20,95.5f };//结构体变量初始化
struct stu s2 = { .age = 18,.score = 98.5f,.name = "tom" };//用.XXX可以乱序
return 0;
}
——————————————————————————————————————————
struct B
{
char c;
int m;
};
struct stu
{
char name[20];
int age;
struct B bb;//结构体嵌套
float score;
};
struct stu s4;//全局变量
int main()
{
struct stu s1 = { "张三",20,{'q',100},95.5f };//结构体变量初始化
struct stu s2 = { .age = 18,.score = 98.5f,.name = "tom" ,.bb = {'q',20} };//用.XXX可以乱序
printf("%s\n", s1.name);//张三
printf("%d\n", s1.age);//20
printf("%c\n", s1.bb.c);//q
printf("%d\n", s1.bb.m);//100
printf("%s %d %c %d %f", s1.name, s1.age, s1.bb.c, s1.bb.m, s1.score);//张三 20 q 100 95.500000
return 0;
}
结构成员访问操作符
结构体成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数。
#include<stdio.h>
struct Point
{
int x;
int y;
}p = {1,2};
int main()
{
printf("x:%d y:%d\n", p.x, p.y);//x:1 y:2
return 0;
}
使用方式:结构体变量.成员名
结构体成员的间接访问
有时候得到的不是一个结构体变量,而是得到了一个指向结构体的指针。
#include<stdio.h>
struct Point
{
int x;
int y;
};
int main()
{
struct Point p = { 3,4 };
struct Point* ptr = &p;
ptr->x = 10;
ptr->y = 20;
printf("x:%d y:%d\n", ptr->x, ptr->y);//x:10 y:20
return 0;
}
使用方式:结构体指针->成员名
操作符的属性:优先级、结合性
优先级
优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。(从高到低排列)
#include <stdio.h>
int main() {
int a = 2, b = 3, c = 4;
int result = a + b * c; // 先计算乘法,再计算加法
printf("结果: %d\n", result);
return 0;
}
初等操作符
- 形式:
()
、[]
、->
、.
- 说明:
()
用于分组表达式、函数调用;[]
用于数组下标访问;->
用于通过指针访问结构体成员;.
用于通过结构体变量访问成员。
单目操作符
- 形式:
!
、~
、++
、--
、+
、-
、*
、&
、(类型)
- 说明:
!
是逻辑非;~
是按位取反;++
和--
分别是自增和自减;+
和-
作为单目操作符时表示正负;*
用于指针解引用;&
用于取地址;(类型)
用于强制类型转换。
算术操作符
- 形式:
*
、/
、%
、+
、-
- 说明:
*
、/
、%
的优先级高于+
和-
。*
是乘法,/
是除法,%
是取余,+
是加法,-
是减法。
移位操作符
- 形式:
<<
、>>
- 说明:
<<
是左移,>>
是右移。
关系操作符
- 形式:
<
、<=
、>
、>=
、==
、!=
- 说明:
<
、<=
、>
、>=
用于比较大小;==
和!=
用于判断相等和不相等。
逻辑操作符
- 形式:
&&
、||
- 说明:
&&
是逻辑与,||
是逻辑或。
条件操作符
- 形式:
? :
- 说明:这是唯一的三目操作符,语法为
条件? 表达式1 : 表达式2
。
赋值操作符
- 形式:
=
、+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、&=
、^=
、|=
- 说明:
=
是基本赋值操作符,其他的是复合赋值操作符。
逗号操作符
- 形式:
,
- 说明:用于将多个表达式连接成一个表达式,从左到右依次计算,整个逗号表达式的值是最后一个表达式的值。
结合性
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,则根据运算符是左结合还是右结合,决定执行顺序。
初等操作符
- 操作符:
()
、[]
、->
、.
- 结合性:左结合性
struct Example
{
int value;
};
int main()
{
struct Example ex;
struct Example *ptr = &ex;
ex.value = 10;
int result = ptr->value + 5; // 先通过 ptr->value 访问成员,再做加法
return 0;
}
单目操作符
- 操作符:
!
、~
、++
、--
、+
、-
、*
、&
、(类型)
- 结合性:右结合性
int main()
{
int a = 5;
int b = -++a; // 先进行自增操作 ++a,再取负
return 0;
}
算术操作符
- 乘法、除法、取余操作符
- 操作符:
*
、/
、%
- 结合性:左结合性
- 操作符:
int main()
{
int a = 10, b = 2, c = 5;
int result = a / b * c; // 先计算 a / b,再乘以 c
return 0;
}
加法、减法操作符
- 操作符:
+
、-
- 结合性:左结合性
int main()
{
int a = 10, b = 2, c = 5;
int result = a - b + c; // 先计算 a - b,再加上 c
return 0;
}
移位操作符
- 操作符:
<<
、>>
- 结合性:左结合性
int main()
{
int a = 2;
int result = a << 1 >> 2; // 先左移 1 位,再右移 2 位
return 0;
}
关系操作符
- 操作符:
<
、<=
、>
、>=
、==
、!=
- 结合性:左结合性
int main()
{
int a = 10, b = 20, c = 30;
int result = (a < b) == (b < c); // 先判断 a < b 和 b < c,再判断两个结果是否相等
return 0;
}
逻辑操作符
- 逻辑与操作符
- 操作符:
&&
- 结合性:左结合性
- 操作符:
int main()
{
int a = 1, b = 0, c = 1;
int result = a && b && c; // 从左到右依次判断
return 0;
}
逻辑或操作符
- 操作符:
||
- 结合性:左结合性
int main()
{
int a = 0, b = 1, c = 0;
int result = a || b || c; // 从左到右依次判断
return 0;
}
条件操作符
- 操作符:
? :
- 结合性:右结合性
int main()
{
int a = 10, b = 20, c = 30;
int result = a > b? a : b > c? b : c; // 先计算 b > c? b : c,再计算 a > b? a : (...)
return 0;
}
赋值操作符
- 操作符:
=
、+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、&=
、^=
、|=
- 结合性:右结合性
int main()
{
int a = 5, b = 3;
a += b -= 2; // 先计算 b -= 2,再计算 a += (...)
return 0;
}
逗号操作符
- 操作符:
,
- 结合性:左结合性
int main()
{
int a = 1, b = 2, c;
c = (a++, b++, a + b); // 先执行 a++,再执行 b++,最后计算 a + b 并赋值给 c
return 0;
}
表达式求值
整型提升
C语言中整形算数运算总是至少以缺省(默认)整形类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整形,这种转换成为整形提升。
整形提升是一种隐式类型转换,指的是在表达式计算时,小于int
类型的整数类型(如char
、short
)会自动提升为int
类型。下面从整形提升的原因、规则、示例等方面进行详细介绍。
整形提升的原因
- CPU 运算效率:大多数计算机的 CPU 在进行整数运算时,是以
int
类型为基本单位处理的。将小于int
类型的数据提升为int
类型,可以提高运算效率,减少硬件设计的复杂度。 - 表达式计算的准确性:在表达式计算中,统一操作数的类型能避免数据丢失和计算结果出错。
整形提升的规则
- 有符号类型:有符号的小于
int
类型的整数,按照符号位进行扩展。如果符号位是0
,则在高位补0
;如果符号位是1
,则在高位补1
。 - 无符号类型:无符号的小于
int
类型的整数,在高位补0
。
算数换算
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行。
在 C 语言中,算术转换是指在表达式中不同类型的操作数进行运算时,为了保证运算的准确性和一致性,会将操作数转换为一种公共类型的过程。下面详细介绍算术转换的规则、目的以及示例。
算术转换的目的
- 统一操作数类型:在进行算术运算时,CPU 通常要求操作数具有相同的类型。算术转换就是为了将不同类型的操作数转换为一种公共类型,以便进行运算。
- 避免数据丢失:通过将低精度类型转换为高精度类型,可以避免在运算过程中出现数据丢失的情况。
算术转换的规则
C 语言中的算术转换遵循以下一般规则(从低到高的类型转换顺序):
char
/ short
-> int
-> unsigned int
-> long
-> unsigned long
-> long long
-> unsigned long long
-> float
-> double
-> long double
具体规则如下:
- 如果两个操作数的类型相同:则不需要进行转换,直接进行运算。
- 如果两个操作数的类型不同:
- 首先进行整形提升(前面已经介绍过,小于
int
类型的会提升为int
类型)。 - 然后,如果提升后类型仍不同,将较低类型的操作数转换为较高类型的操作数,直到两个操作数类型相同。
- 首先进行整形提升(前面已经介绍过,小于
——————————End——————————