自学C语言——操作符详解

接上一篇:自学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类型的整数类型(如charshort)会自动提升为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

具体规则如下:

  1. 如果两个操作数的类型相同:则不需要进行转换,直接进行运算。
  2. 如果两个操作数的类型不同
    • 首先进行整形提升(前面已经介绍过,小于int类型的会提升为int类型)。
    • 然后,如果提升后类型仍不同,将较低类型的操作数转换为较高类型的操作数,直到两个操作数类型相同。

——————————End—————————— 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

混迹网络的权某

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值