从0开始学c语言-18-操作符详解

本文详细介绍了C语言中的各种操作符,包括算数、移位、位操作、赋值、单目操作符以及逻辑操作符等,并通过实例解析其用法。适合C语言初学者学习,帮助理解操作符的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本人0基础开始学编程,我能学会的,你也一定可以,学会多少写多少。

下载安装请从官网入手,社区版本即可,这里主要使用的软件是VS2019,图标如下。

 上一篇:从0开始学c语言-过渡-函数递归、循环语句、数组练习_阿秋的阿秋不是阿秋的博客-优快云博客

在开始之前,希望大家先看看我之前写的操作符文章,这里给上链接。(因为有重复的内容,细节可能不会在这里提及。)

从0开始学c语言-07-认识一下操作符、原码和反码和补码_阿秋的阿秋不是阿秋的博客-优快云博客

目录

1·算数操作符

2·移位操作符

左移操作符

右移操作符

补充

3·位操作符

练习1

第一种写法

第二种写法

异或的结论

练习2

画图示意

代码思路

优化

4·赋值操作符

5·单目操作符

! 逻辑反操作

 sizeof 操作数的类型长度(以字节为单位)

括号与数组类型

数组传参与sizeof

sizeof括号中的表达式不参与运算

~  对一个数的二进制按位取反

和它相似的

应用练习

 前置、后置的--、++

 &取地址和*  间接访问操作符(解引用操作符)

sizeof和它们组合的应用

 6·关系操作符

7·逻辑操作符

&&与&、||与|

提示:

8·条件操作符(三母操作符)

9·逗号表达式

小用途

10·下标引用、函数调用和结构成员


操作符

1·算数操作符

+   -   *   /   %

从0开始学c语言-07-认识一下操作符、原码和反码和补码_阿秋的阿秋不是阿秋的博客-优快云博客

详细的看上面那篇文章,这里只给上结论。

1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

2·移位操作符

<<  左移>>  右移

移位操作符的操作数只能是整数(不能<<2.3),我们这里所指的移动是移动补码的二进制位!!!

详细解释看从0开始学c语言-07-认识一下操作符、原码和反码和补码_阿秋的阿秋不是阿秋的博客-优快云博客

左移操作符

左移原则:左边不要,右边补0

右移操作符

右移分为两种:

1.逻辑移位:左边补0、右边丢弃

2. 算术移位:左边用原该值的符号位填充,右边丢弃 

计算机系统中的右移以算术移位为主


对于移位运算符,不要移动负数位,这个是标准未定义的。

例如:  

int aqiu = 10;  

num>>-1;  //这是不可以的哦!


补充

 变量b储存a变量移位后的结果,a本身不会改变。

我们把移位放在判断条件中,可以看到a本身依旧不会改变。

3·位操作符

&:按位与(有0为0)

| :按位或(有1为1)

^:按位异或(相同为0,不同为1)

注意:

1.和移位运算符一样,他们的操作数也必须是整数。也就是说不能&2.3

2.和移位运算符一样,位操作符也是对补码进行操作。

3·和移位运算符一样,别的变量储存它按位后的结果或者放在判断条件中,它本身不变

练习1

不创建第三个变量,交换两个变量的值。

第一种写法

int main()
{
	int a = 3;
	int b = 5;
	printf("a=%d,b=%d", a, b);
	a = b - a;
	b = b - a;
	a = a + b;
	printf("a=%d,b=%d", a, b);
	return 0;
}

这样虽然可以,但是如果a和b都很大的话会溢出

第二种写法

int main()
{
	int a = 3;
	int b = 5;
	printf("a=%d,b=%d", a, b);
//异或的结果同原来的其中一个值异或能得到另外一个值
	a = b ^ a; 
//这个a相当于密码
	b = b ^ a; 
//b去异或这个密码就能得到a,相当于b=b^b^a,b^b=0,0^a=a,b=a
	a = a ^ b; 
//a去异或这个密码就能得到b
	printf("a=%d,b=%d", a, b);
//用ab异或的结果可以知道ab哪些位不同,然后用a与结果异或,相同保留不同取异得到b
	return 0;
}

异或的结论

1· 任何两个相同的数 异或 为0
2· 0和任何数字 异或 是它本身

练习2

求一个整数储存在内存中的二进制1的个数

思路:一个数和1按位与的话,只有它也为1才能得到1。然后不断右移这个数,验证有多少个1。

画图示意

假设现在我们需要验证变量a=5在内存中二进制1的个数。

因为a是正数,所以原码补码相同,直接写出来便可进行运算,就像这样。

 这样就可以验证有几个1了,只要&后的结果不为0便证明有1。

思路中说的是右移a这个数,但是右移负数的话就会补充很多1,那边不便不是我们想要的结果了。

我们需要换一种思路,比如:左移1。就像图中这样。

可以看到,这样确实可以达到我们的目的,那么接下来就写代码把

代码思路

int main()
{
	int a = 5; //需要验证的数
	int count = 0; //计数
	if (a & 1) 
	{
		count++;
	}
	printf("%d\n", count);
	return 0;
}

根据上面的思路可以写出来这段代码。

但是我们需要验证32位二进制数字才算完成了任务,现在只是验证了一位。

那么就需要循环来帮助我们了,第一次不需要移位,那么初始值就为0。

第二次b需要移位1,第三次b需要移位2,一共32次,以此类推,就可以写出这段代码。

int main()
{
	int a = 5; //需要验证的数
	int b = 0; //需要左移的数
	int count = 0; //计数
	for (b = 0; b < 32; b++)
	{
		if (a & (1 << b))
			count++;
	}
	printf("%d\n", count);
	return 0;
}

优化

int main()
{
	int num = -1;
	int i = 0;
	int count = 0;//计数
	while (num)
	{
		count++;
		num = num & (num - 1);
	}
	printf("二进制中1的个数 = %d\n", count);
	return 0;
}

这种想法很难想到,我就不解释了。

4·赋值操作符

=与==不同

+=    -=   *=   /=   %=           

>>=   <<=   &=   |=   ^= 

不多讲,看从0开始学c语言-07-认识一下操作符、原码和反码和补码_阿秋的阿秋不是阿秋的博客-优快云博客

5·单目操作符

! 逻辑反操作

  1. 规定0为假,非0为真

  2. !逻辑取反就是真假互换,一般0会变为1,非0变为0。

int main()
{
	int flag = 0; //flag为假
	if (flag)
	{
		printf("1"); //不能打印,因为flag为假
	}
	//flag为假的时候进行打印
	if (!flag) //!falg把假的flag变为真,执行语句
	{
		printf("2");
	}
	return 0;
}

 sizeof 操作数的类型长度(以字节为单位)

1.siizeof是一个操作符,而不是函数(函数不能省略括号)
2.sizeof可以计算类型或者变量所占空间的大小,sizeof的单位是字节

括号与数组类型

int main()
{
	int a = 0;
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(int));
	printf("%d\n", sizeof a);
	printf("%d\n", sizeof int); //这个不行
//不能省略类型的括号
//sizeof可以省略变量的括号,说明它只是一个操作符而不是函数
//因为函数的括号不能省略
	
    int arr[10] = { 10 };
//整个数组大小
	printf("%d\n", sizeof(arr)); 
//int [10]就是数组的类型,代表整个数组
	printf("%d\n", sizeof(int[10])); 

	return 0;
}

数组传参与sizeof

void test1(int arr[])
{
	printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
	printf("%d\n", sizeof(ch));//(4)
}
int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d\n", sizeof(arr));//(1)
	printf("%d\n", sizeof(ch));//(3)
	test1(arr);
	test2(ch);
	return 0;
}

 可以看到,在数组传参的时候,传过去的是首元素的地址。

sizeof括号中的表达式不参与运算

int main()
{
	short i = 5;
	int j = 5;
	printf("%d\n", sizeof(i = j + 2));//sizeof括号中的表达式不参与运算
	printf("%d\n", i);
	return 0;
}

 sizeof算的是i的类型大小,i的值还是5。表达式的类型是看左值的类型。

为了加深理解,我们用这段代码来试试。

int main()
{
	short i = 5;
	int j = 6;
	long long p = 9;
	printf("%d\n", sizeof(i = j + 2));//sizeof括号中的表达式不参与运算
	printf("%d\n", i);
	printf("%d\n", sizeof(i+j));  //整型提升
	printf("%d\n", sizeof(p+i));  //整型提升
	printf("%d\n", sizeof(i=i+j));  
	printf("%d\n", sizeof(j=i+j));  //表达式的类型是看左值的类型
	printf("%d\n", sizeof(p=i+j));  
	return 0;
}

 这里涉及到 整型提升 新的知识点,以后再讲。

~  对一个数的二进制按位取反

和它相似的

int main()
{
	int a = 7;
	int b = ~a;
	int c = a & 1;
	int d = a << 1;
	printf("%d\n", a);
	//a不会变
}

移位操作符(<<、>>)和位操作符(&、|、^)和单目操作符~都是

1.操作数是整数

2.对补码进行操作。

别的变量储存它按位后的结果或者放在判断条件中,它本身不变

应用练习

把a=13的二进制补码1101前面的0变为1后,再变为0。

int main()
{
	int a = 13;
	//能把1101前面的0变为1
	a = a | (1 << 4);
	//00001101 a
	//00010000 1 << 4
	//00011101
	//这样就能把1101前面的0变为1
	
	//把1101前面的1变为0
//第一种
	a = a ^ (1 <<  4);
    //00011101 a
    //00010000 1 <<  4
    //00001101 

//第二种
	a = a & ~(1 << 4);	
    //00010000 1 << 4
	//11101111 ~(1 << 4)
	//00011101 a
	//00001101
	//&=~是置0 ,|= 是置1
	return 0;
}

以上这种代码属于是灵活运用操作符了,我们一定要多思考。

 前置、后置的--、++

 好好看清楚a的输出结果。

 &取地址和*  间接访问操作符(解引用操作符)

我们已经知道了地址是什么,而&就是取出地址的操作符。

*则是通过指针去访问这个地址住着的人。

int main()
{
	int a = 1;
	printf("%p\n", &a); //&取地址操作符

	int* p = &a;//p是用来存放地址的指针变量
	printf("%p\n", p);
//int*代表p是整型指针变量,且p指向的对象类型也是int
	
    *p = 2;
//*解引用操作符,间接访问操作符,通过p找到a的地址
	printf("%d\n", a);
	
    *(&a) = 9;
	printf("%d\n", a);
	//地址应该放到指针变量中
	return 0;
}

 看清楚它们之间的关系,谁是指针,谁是地址,谁又是住户?

&a是a的地址

p是指针变量

*p和*(&a)代表通过地址访问住户a。

输出结果如下

sizeof和它们组合的应用

void test1(int arr[])
{
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr[0]+arr[1]));
	printf("%d\n", sizeof(*arr));
	//传过来的是数组首元素的地址
	//而指针变量是用来存放地址的
	//所以形参int arr[]就是int*arr
	//在打印首元素地址的大小
}//也就是说传过来的是首元素的地址,然后指针是用来存放地址的
//所以形参int arr[]和int*arr就是一个意思
//你在打印的时候就是在打印首元素地址的size
void test2(char ch[])
{
	printf("%d\n", sizeof(ch));
	printf("%d\n", sizeof(ch[1]));
	printf("%d\n", sizeof(*ch)); //*ch是不包含地址的,只是在通过指针变量ch找到实参ch的地址来访问住在这里的元素
	//在打印首元素地址的size,那为什么不是1呢?
	// 我们需要知道的是,char ch[]和char*ch是一样的
	// 也就是说我们传过来的地址是存放在指针变量中的首元素地址
	//而指针变量的大小不是4就是8,这个根据系统不同而定的
}
int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 }; 
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(ch));
	char* p = ch;
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(p));
	test1(arr);
	test2(ch);
	//调试
	return 0;
}

 

 6·关系操作符

>  大于

>=  大于等于

<  小于

<=  小于等于

!=       用于测试“不相等”

==      用于测试“相等”

注意: 在编程的过程中 == 和 = 所代表的意思不一样!!!

7·逻辑操作符

&&     逻辑与
||         逻辑或

&&就是一假则假,||就是一真则真。

(真就是非0,假就是0)

&&与&、||与|

1 & 2 -----> 0
1 && 2 ----> 1
1 | 2 -----> 3
1 || 2 ----> 1

&与|是对二进制中的补码进行操作,

而&&与||是对前后表达式的结果进行操作。

int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++; //表达式1
    //i = a++||++b||d++; //表达式2
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0; 
}

分别猜测一下&&与注释掉的||表达式的输出结果是什么。

提示:

&&一假则假,碰到一个假的就不再计算。

||   一真则真,碰到一个真的就不再计算。

什么情况下才会把&&或者||连接的表达式都计算呢?

&&全部为真,或者最后一个为假。
||   全部为假,或者最后一个为真。

 

现在我改动一下表达式

 可以看到如果一开始为真,那么便不再计算后面的。

8·条件操作符(三母操作符)

exp1 ? exp2 : exp3   exp1是判断条件,为真执行exp2,为假执行exp3

exp就是表达式的意思。

9·逗号表达式

exp1, exp2, exp3, …expN   用逗号隔开的多个表达式。

从左向右依次执行。整个表达式的结果是最后一个表达式的结果
int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);
	printf("%d\n", c);
	int d = 2;
	if (a = b + 1, c = a / 2, d > 0)
	{
		printf("%d\n", a);
		printf("%d\n", c);
		printf("%d\n", d);
	}
}

可以看到,虽然表达式的结果是最后一个,但是需要从左向右计算,即使左边的判断表达式并不成立,依旧会向后计算。

小用途

a = get_val();
count_val(a);
while (a > 0) 
{
//其他语句
    a = get_val();
    count_val(a);
}

这样一段代码可以用逗号表达式优化为

while (a = get_val(), count_val(a), a>0)
{
     //其他语句
}

10·下标引用、函数调用和结构成员

1. [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
arr[9] = 10;//实用下标引用操作符。
 [ ]的两个操作数是arr和9。
2. ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
void test1()
 {
 }
 void test2(const char *str)
 {
 }
int main()
{
test1();            //实用()作为函数调用操作符。
test2("hello");//实用()作为函数调用操作符。
return 0;
}
3. 访问一个结构的成员
. 结构体 .成员名
-> 结构体 指针 ->成员名
struct Stu //结构体
{
 char name[10];
 int age;
 char sex[5];
 double score;
};
//函数1
void set_age1(struct Stu stu) 
{
 stu.age = 18; 
}
//函数2
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; 
}

操作符的学习不止于此,明天写一篇来好好用一下。

写到最后有些累了,没细说,哎,没时间了也是

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值