大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
操作符可以说是家家必备但不怎么重视的一个C语言武器,那今天我就详细讲一讲C语言当中的操作符,让大家能够很好的理解操作符并运用它们。
算数操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、结构调用和结构成员
操作符:+ - * / %
1.对于%操作符之外,其他的几个操作符可以作用于整数和浮点数。
2.对于/操作符如果两个操作数都为整数,执行整数除法,而只要有浮点数执行的就是浮点数除法。
3.%操作符的两个操作数必须为整数,返回的是整除之后的余数。
#include//2进制
//整数的2进制的表现形式,其实有三种
//原码、补码、反码
//在计算机内,内存中存储的是补码的二进制
//所以在参与移位的时候,移动的都是补码
//12 - 数值
//2进制:1100
//8进制:14
//16进制:c
//
int main() {//按照一个数的正和负直接写出它的二进制表示形式得到的就是原码
//
//正数的原码、补码、反码是相同的
//负数的原码、补码、反码需要进行计算
//负数反码 - 原码的符号位不变,其他位按位取反
//负数补码 - 反码+1/原码的符号位不变,从右往左找第一个1,这些位不变,其余位取反
//
//整形占4个字节(32个比特位)
//a - 00000000000000000000000000001010 - 原码
//a - 00000000000000000000000000001010 - 补码
//a - 00000000000000000000000000001010 - 反码
int a = 10;
//b - 10000000000000000000000000001010 - 原码
//b - 11111111111111111111111111110101 - 反码
//b - 11111111111111111111111111110110 - 补码
int b = -10;
return 0;
}
操作符的左右移移动的都是补码的二进制,因为计算机内存中存储的就是补码的二进制。关于原码、补码、反码的知识,在程序注释行里解释过了,大家要仔细看看并理解理解。
大家可以根据下面这张图来理解一下原码、补码和反码。
计算规则:左边丢弃,右边补0。
计算规则与正数一样。
1.左移操作符左移1位是原本的值进行平方操作。
2.左移后那个最左边的数就是符号位,即使是0,最后结果也是0。
3.计算规则:最高位舍弃,新的第一位做符号位,末位补0。
大家可以进入调试窗口看一看内存分布,发现-1是在内存中存储的是32个1,而展现在我们面前的是16进制数,是因为方便好看。
而当再考虑补几的时候犹豫了,到底补1呢还是补0呢?这就牵扯到算术右移与逻辑右移了!
右边丢弃,左边补原本符号位,原本符号位是什么,左边就补充什么符号位。
右边丢弃,左边补0。
移动只能移动正数哦!!!不可以移动负数,这个标准是未定义的。
(5)编译器移位运算所以编译器到底是算术还是逻辑右移呢?我们打印出来看看!!!
原来是算术右移,因为还是个负数!!!
规则:按二进制位与,有零则为零,两个同时为1才为1。
//按二进制与
//有零则为零,两个同时为1才为1
//
#includeint main() {int a = 3;
//00000000000000000000000000000011 - 原码、补码、反码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a & b;
//a - 00000000000000000000000000000011
//b - 11111111111111111111111111111011
//a&b 00000000000000000000000000000011
//
printf("%d\n", c);//3
return 0;
}
(三)按位或规则:按二进制或,对应的二进制位有1则为1,两个同时为0则为0。
//
//按位或 - 有1则为1,俩零才为零
#includeint main() {int a = 3;
//00000000000000000000000000000011 - 原码、补码、反码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a | b;
//a - 00000000000000000000000000000011
//b - 11111111111111111111111111111011
//a|b 11111111111111111111111111111011
//
printf("%d\n", c);//-5
return 0;
}
(四)按位异或规则:按二进制位异或,对应的二进制位,相同为0,相异为1。
//
//按位异或 - 相同为0,相异为1
//
#includeint main() {int a = 3;
//00000000000000000000000000000011 - 原码、补码、反码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a ^ b;
//a - 00000000000000000000000000000011
//b - 11111111111111111111111111111011
//a^b 11111111111111111111111111111000 - 补码
// 11111111111111111111111111110111 - -1操作
// 10000000000000000000000000001000 - 取反(原码)
// -8
printf("%d\n", c);//-8
return 0;
}
(五)小练习
1.题目描述不创建临时变量(第三个变量),实现两个整数的交换。
2.解题过程先来个比较能想到的代码:
//局限性:a,b如果为一个很大的数,两者相加溢出了
#includeint main() {int a = 3;
int b = 5;
printf("%d %d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("%d %d\n", a, b);
return 0;
}
这种代码有局限性,如果两个数都很大,相加导致溢出的现象怎么办呢?接下来就需要异或这操作符的辅助了!
3.代码异或操作符注意项:
#includeint main() {int a = 3;
int b = 5;
printf("%d %d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %d\n", a, b);
return 0;
}
4.弊端1.效率较低,不如使用临时变量的方法
2.可读性差
3.异或只能针对整数的操作
赋值操作符可以让你得到一个你之前不满意的值,你可以给自己的值进行重新赋值。可以连续赋值,但不是很清晰明了,不利于调试,可读性也较差,尽量使用单句赋值语句,不要一个语句多个赋值操作符。并且,一定要给变量进行初始化赋值,不赋值是很危险的,这个涉及到了函数栈帧的创建与销毁,大家可以看看这篇博客:
函数栈帧的创建与销毁
例如:
int main() {int weight = 70;
weight = 90;//不满意就赋值
double salary = 10000.9;
salary = 20000.1;//使用赋值操作符进行赋值
return 0;
}
(二)复合赋值符//复合赋值符
int main() {int a = 10;
//a=a+5;
a += 5;
int b = 12;
//b = b >>1;
b >>= 1;
return 0;
}
更加简化清晰明了。
#includeint main(){//C语言中,0表示假,非零表示真
int flag = 5;
//if (flag) { //flag如果为真,做……
// printf("hehe\n");
//}
if (!flag) {//非flag,!操作符是把假变成真,真变成假
printf("hehe\n");
}
return 0;
}
(2)小知识(布尔类型)true - 1(非零,负数也算)
falise - 0
举例(判断闰年):
#include#include_Bool is_leap_year(int y) {if ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) {return true;
}
else {return false;
}
}
int main() {int y = 0;
scanf("%d", &y);
_Bool ret = is_leap_year(y);
if (ret) {printf("yes\n");
}
else {printf("no\n");
}
return 0;
}
4.负值和正值操作符
(1)概念正值操作符一般没什么用,一般省略,负值操作符是可以改变值的正负的。
概念:把内存中的补码拿出来进行计算!!!
计算器呼出按键:先打开管理,再输入calc。
只要是个变量就可取地址,例如可以&a,但不能&3。
int main() {int a = 10;
//取出的是内存的地址,可存起来也可打印
printf("%p\n", &a);
int* pa = &a;
char ch = 'a';
char* pc = &ch;
char arr[10] = {0 };
char* arr1 = arr;
char* p1 = &arr[0];
char* p = "abcdef";
return 0;
}
(2)配合使用
&操作符拿到地址,*操作符解引用去找到它进行操作。
既是关键字,也是操作符!!!
(1)简单了解#include//函数调用的时候,要写()
//sizeof后边的括号可以省略,说明它是操作符不是函数
int main() {int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);//ok
printf("%d\n", sizeof(int));
//printf("%d\n", sizeof int);//error 类型名括号不能省略
int arr[10] = {0 };
printf("%d\n", sizeof(arr));//40,计算的是整个数组的字节大小
printf("%d\n", sizeof(int[10]));//ok
return 0;
}
(2)sizeof的特点可以打断运算!!!
原因:
文件在进行操作的时候,是通过编译,链接到test.exe,而sizeof很强大,在编译期间就已经打断赋值运算了,根据的是原本变量的类型大小进行输出的。
#include//~按位取反,只针对整数
int main() {int a = 0;
printf("%d\n", ~a);
//00000000000000000000000000000000
//11111111111111111111111111111111 - 按位取反
//10000000000000000000000000000000 - 反码
//10000000000000000000000000000001 - 补码
//-1
return 0;
}
(2)综合应用按位与、按位或、按位取反的综合应用。
#includeint main() {int a = 9;
//00000000000000000000000000001001
//00000000000000000000000000010000 |操作 1<<4
//00000000000000000000000000011001
//
//把a的二进制中的第五位改成1
a |= (1<< 4);
printf("%d\n", a);
//把a的二进制中的第五位改成0
//00000000000000000000000000011001
//11111111111111111111111111101111 &操作 ~(1<<4)
//00000000000000000000000000001001
a &= ~(1<< 4);
printf("%d\n", a);
return 0;
}
8.前置++(- -)和后置++(- -)
(1)简单认识举例:
注意:++和–是带有副作用的,它会影响自己。
比如:b=++a后,a的值会自增1;b=a+1后,a的值不会自增1。
当迫不得已的时候,可以进行强制类型转换。
这里sizeof操作符单独拿出来讲,说明sizeof的重要性。
#includevoid test1(int arr[]) {printf("%zd\n", sizeof(arr));
}
void test2(char ch[]) {printf("%zd\n", sizeof(ch));
}
int main() {int arr[10] = {0 };
char ch[10] = {0 };
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
大家看这个肯定很熟悉,我们之前讲过的按位与和按位或,这个不就是双倍吗!但大家要注意的是按位与和按位或是通过二进制计算的,而逻辑与和逻辑或只关注真和假。
两个同是真为真,只要有假即为假。
两个同是为假则为假,有一个真就是真。
//判断闰年:
//1.能被4整除但不能被100整除
//2.能被400整除
#includeint main() {int year = 2000;
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {printf("yes\n");
}
return 0;
}
2.面试题复合++和–和&&和||的操作。
表达式1的结果如果为真,则计算表达式2,不计算表达式3。
表达式1的结果如果为假,则计算表达式3,不计算表达式2。
也被称为三目操作符。
例如:
逗号表达式就是用逗号隔开的多个表达式。
逗号表达式,从左往右依次进行,整个表达式的结果是最后一个表达式的结果。
操作符:一个数组名+一个索引值
接受一个或者多个操作符:第一个操作符是函数名,剩余的操作数就是传递给函数的参数。
函数调用的时候至少要有一个操作数。
//char int float double long long short - 内置类型
//自定义类型 - 结构体、枚举、联合体
//结构体 - 自定义类型(聚合类型)
//生活中有些对象要被描述的话,不能简单的使用单个内置类型
//描述一本书:书名+定价+作者+出版社…… - 结构体(struct)来聚合它们
#include//类型
struct book {char name[20];
int price;
char author[20];
};
void Print(struct book* b1) {//结构体变量.成员名
printf("%s %d %s\n", (*b1).name, (*b1).price, (*b1).author);
}
void Print1(struct book* b2) {//结构体指针->成员名
printf("%s %d %s\n", b2->name, b2->price, b2->author);//指向成员
}
int main() {struct book b1 = {"cpp",10,"renhai" };//初始化
struct book b2 = {"cppp",20,"jiang" };//初始化
printf("%s %d %s\n", b1.name, b1.price, b1.author);
printf("%s %d %s\n", b2.name, b2.price, b2.author);
//结构体变量.成员名
//分装函数
Print(&b1);
Print1(&b2);
return 0;
}
2.例子仅仅是值传参上去是改变不了值的,而传地址上去才能改变值的大小。
#includestruct 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);
printf("%d\n", stu.age);//20
pStu->age = 20;//结构成员访问
set_age2(pStu);
printf("%d\n", stu.age);//18
return 0;
}
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
C的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的值进行相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或者unsigned int,然后才能送入CPU去执行相应的运算。
整形提升是按照变量的数据类型的符号位来提升的
(1)截断当赋值操作符右边是整数,是有32个比特位的,而char类型只能存放一个字节,也就是8个字节,所以发生截断,只保留低八位。
当截断完要进行整数相加的操作之前,a(正数)和b(正数)进行整数提升,也就是在高处补0,再进行计算
当要整型提升的那个数是负数的时候,即高位是1的时候,在前面补1,补1之后得到的数是二进制的补码,因为这是在计算机内部进行操作的,计算机内部存储的是二进制的补码,所以是补码,而想要打印出来,就需要有符号的数转换到原码进行打印。
关于char的无符号和有符号的数的范围:
解析如下:
//char short int long……
// 1 2 4
int main() {//signed char的取值范围是-128 - 127
//unsigned char的取值范围是0 - 255
//
//char =signed char 是有符号位的
char a = 3;
//截断 - 4个比特位的空间放到1个比特位的空间里(长的变成短的)
//00000000000000000000000000000011 - 原码
//char只有一个字节,也就是8个比特位,所以a里面存放的是:00000011
char b = 127;
//00000000000000000000000001111111 - 原码
//b - 01111111
char c = a + b;
//00000011
//01111111
//整型提升 - 正数整型提升高位补0
//整型提升 - char类型整型提升到int类型(短的变成长的)
//00000000000000000000000000000011
//00000000000000000000000001111111
//00000000000000000000000010000010
//放到c里面去,只能存8个比特位 - 10000010 - c
printf("%d\n", c);
//%d 是打印十进制的整数
//整型提升 - 负数整型提升高位补1
//10000010 - c
//11111111111111111111111110000010 - 补码
//10000000000000000000000001111110 - 原码 - -126
return 0;
}
3.例子
(1)练习1#includeint main()
{char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
//a发生整型提升
//0xb6 - 10110110 - 182
//a整型提升后 - 11111111111111111111111110110110 - 补码
// 10000000000000000000000001001010 - 原码 -74
if (a == 0xb6) //进入不了
printf("a");
//同理b也发生整型提升
if (b == 0xb600)//进入不了
printf("b");
//c不发生整型提升
if (c == 0xb6000000) //可以进入
printf("c");
return 0;
}
(2)练习2a,b要进行整形提升,但是c不需要整形提升。
a,b整形提升之后,变成了负数,所以表达式 a为0xb6 , b为0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真。
#includeint main()
{char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
(二)算术转换c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节,表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof© ,就是1个字节。
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
根据字节大小进行排名(从高往下递减):
解释:不同类型的数相加,需要转化为字节更大的那个数再进行相加。即:如果int类型的数加上double类型的数,那编译器会先把int类型的数转换为double类型的字节长度并进行计算(就高不就低)。
但同样有小问题,如果float类型转换为int类型是能转换的,但是存在精度丢失的问题。
(三)操作符的属性 1.概念复杂表达式的求值有三个影响的因素:
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
#include//首先确定优先级,相邻操作符按照优先级高低计算
//优先级相同的情况下,结合性才起作用
//
int main() {int a = 1;
int b = 2;
int c = 4;
int d = a * 4 + b / 3 + c;//相邻操作符才讨论优先级
int e = a + b + c;
printf("%d\n", d);
return 0;
}
优先级:
2.一些问题表达式 (1)典例1
在计算的时候,由于星号比+的优先级高,只能保证,星号的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行。
运算顺序不一样,很可能导致运算结果不一样。
操作符的优先级只能决定自减–的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
在不同的编译器上计算结果是不同的。
虽然在大多数的编译器上求得结果都是相同的。但是上述代码answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。
操作符这块的知识较为冗杂,但是只要你认真细心地去做,去思考,就能体会到很多不同的知识,这篇博客除了讲解了操作符,还跟大家讲解了在计算机内部的存储方式以及二进制的原码、补码和反码的知识,相信大家在看完以后会有很多不错的收获!!!
客官,码字不易,给个三连支持一下吧!!!
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧