文章目录
第五章操作符
本章将主要介绍:
1.各种操作符的讲解
2.表达式求值
1.操作符的分类
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
条件操作符
逻辑操作符
逗号表达式
下标引用、函数调用及结构成员
2.算术操作符
c
+ - * / %
%操作符的两个操作数必须是整数,返回的是整除之后的余数。
3.移位操作符
c
<< 左移操作符
>> 右移操作符
注意:移位操作符的操作数只能为整数。
3.1左移操作符
移位规则:
左边被抛弃,右边会随之补0
c
int a = 10;
a << 1;
00000000 00000000 00000000 00001010 //a在内存中的二进制 32位
00000000 00000000 00000000 00010100 //a左移一位后的结果
3.2右移操作符
移位规则:
1.算术右移
右边丢弃,左边补原符号位
2.逻辑右移
右移丢弃,左边补0
注:不要移动负数位,这个是标准未定义的。
4.位操作符
c
& //按位与
| //按位或
^ //按位异或
它们的操作数必须是整数
一道面试题:
不能创建临时变量(第三个变量),实现两个数的交换。
c
#include <stdio.h>
int main()
{
int a = 10; //00000000 00000000 00000000 00001010
int b = 20; //00000000 00000000 00000000 00010100
a = a ^ b; //00000000 00000000 00000000 00011110
b = a ^ b; //00000000 00000000 00000000 00001010 a^b^b
a = a ^ b; //00000000 00000000 00000000 00010100 a^b^a
//3^3 011 ^ 011 -> 000
//0^3 000 ^ 011 -> 011
printf("a=%d,b=%d\n",a,b);
return 0;
}
练习:
求一个整数存储在内存中的二进制中1的个数。
c
#include <stdio.h>
int main()
{
int a = 13;
int n = 1;
int count = 0;
for (int i = 0;i < 32;i++)
{
if (n & (a >> i))
{
count++;
}
}
printf("%d", count );
return 0;
}
5.赋值操作符
c
int height = 160;
height = 180; //如果对值不满意,可以赋值
int weight = 70;
weight = 50;
//赋值操作符可以连续使用,如:
int x = 10;
int y = 5;
int z = 2;
z = y = x + 5; //连续赋值
//同样的语义:
y = x + 5;
z = y;
5.1复合赋值符
+=
-=
*=
%=
/=
》=
《=
&=
|=
^=
c
int a = 50;
a = a + 5;
a += 5; //符合赋值
//与其他运算符一样的道理,这样更加简洁。
6.单目操作符
6.1介绍
c
+ //正值
- //负值
! //逻辑反
& //取地址
sizeof //操作数的类型长度(单位为字节)
~ //对一个数进行二进制按位取反
++ //前置,后置++
-- //前置,后置--
* //间接反问操作符
(类型) //强制类型转换
c
int a = 10;
printf("%d\n", sizeof(a)); //4
printf("%d\n", sizeof(int)); //4
printf("%d\n", sizeof a); //4
printf("%d\n", sizeof int); //语法错误
sizeof可以求变量(类型)所占空间的大小。
6.2 sizeof和数组
7.关系操作符
c
<
<=
>
>=
!= //判断是否不相等
== //判断是否相等
注意:=为赋值,==为判断相等,在编程过程中,不要写错了。
8.逻辑操作符
c
&& //逻辑与
|| //逻辑或
区分逻辑与和按位与
区分逻辑或和按位或
c
1 & 2 ---> 0
1 && 2 --> 1
1 | 2 ---> 3
1 || 2 --> 1
一道面试题:
c
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
//i = a++||++b||d++; //输出结果为:1 3 3 4
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
//输出结果为:1 2 3 4
//后置++,先使用再++,a为假后面不管为什么都为假,所以后面没有参与运算。
return 0;
}
9.条件操作符
c
exp1 ? exp1 : exp2
c
int a = 5;
int b = 10;
int c = 0;
if (b > a)
{
c = b;
}
else
{
c = a;
}
c = a > b ? a : b;
10.逗号表达式
c
exp1,exp2,exp3,....expn
逗号表达式:是用逗号隔开的多个表达式,它是从左往右依次执行,整个表达式的最后结果是最后一个表达式的结果。
c
//代码1
int a = 1;
int b = 3;
int c = 0;
c = (++a, b + a, b, a + 4);
printf("%d",c); //C为6
//代码2
if(a = b/2,b = a+2, a>0)
//代码3
a = get();
count(a);
while (a > 0)
{
a = get();
count(a);
}
//使用逗号表达式,改写成:
while (a = get(), count(a), a > 0)
{
a = get();
count(a);
}
11.下标引用、函数调用和结构成员
1.[]下标引用操作符
操作数:一个数组名 + 一个索引值
c
int arr[3] = { 0 }; //创建个数组
arr[2] = 2; //引用下标操作符进行赋值
//[]的两个操作数分别为arr和3.
2.()函数调用操作符
接受一个或者多个操作符,第一个操作符为函数名,剩余的操作数为传递给函数的参数。
c
#include <stdio.h>
void Test()
{
printf("Hello ");
}
void Test2(char *Str)
{
printf("%s", Str);
}
int main()
{
Test();
Test2("World");
return 0;
}
3.访问结构成员
. 结构体.成员名
-> 结构体指针->成员名
c
#include <stdio.h>
struct Book
{
char name[20];
double price;
char id[20];
};
int main()
{
struct Book b = { "C语言", 58.00, "C15156206"};
struct Book *pb = &b;
/*printf("书名是:<<%s>>,价格是:%lf, 书本编号是:%s\n", b.name, b.price, b.id);*/
/*printf("书名是:<<%s>>,价格是:%lf, 书本编号是:%s\n", (*pb).name,(*pb).price,(*pb).id);*/
printf("书名是:<<%s>>,价格是:%lf, 书本编号是:%s\n", pb->name, pb->price, pb->id);
return 0;
}
12.表达式求值
表达式的求值顺序由操作符的优先级和结合性决定。
有些函数表达式求值的时候需要转换成其他类型。
12.1隐式类型转换
C语言的整型算术运算总是至少以缺省整型类型的精度进行运算的。
为了获得精度,表达式中的字符和短整型操作数在使用之前会先转为普通整型,这种转换就被称为整型提升。
整型提升有什么意义?
c
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
c
char a, b, c;
c = a + b;
//b与c先提升为普通整型,再进行加法运算
//运算完成后,结果将被截断,然后再存储在a中
如何进行进行整型提升:
按照变量的数据类型的符号位来进行提升的。
c
char a = 3;
//00000000 00000000 00000000 00000011
//00000011 - a
char b = 127;
//00000000 00000000 00000000 01111111
//01111111 - b
char c = a + b;
//00000000 00000000 00000000 00000011
//00000000 00000000 00000000 01111111
//00000000 00000000 00000000 10000010 //逢二进一
//10000010 - c
//11111111 11111111 11111111 10000010 //补码
//11111111 11111111 11111111 10000001 //反码 补码-1
//10000000 00000000 00000000 01111110 //原码 反码取反
//-126
//a和b都是char类型,都没有达到一个int的大小,这里就要整型提
printf("%d\n", c); //-126
///无符号整形提升,高位补0
例子:
c
//例1
int main()
{
char a = 0xc5;
short b = 0xc500;
int c = 0xc5000000;
if (a == 0xc5)
printf("A");
if (b == 0xc500)
printf("B");
if (c == 0xc5000000)
printf("C");
return 0;
//例子中的a和b都需要进行整型提升,c不需要。
//a,b进行了整型提升后变成了负数,所以表达式a == 0xc5,b == 0xc500的结果是假,c不进行整型提升,则表达式c == 0xc5000000的结果为真。
//程序运行结果为C。
c
//例2
int 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)/(-c)是四个字节,sizeof©是一个字节。
12.2 算术转换
如果某个操作符的各个操作数不属于相同的类型,那么除非其中一个操作数转换成另一个操作数的类型,否则将无法执行操作。以下的层次体系称为寻常算术转换。
c
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面列表中排名较低,那首先要转换为另外一个操作数的类型再执行运算。
c
float x = 3.1415;
int y = x; //隐式转换,会丢失精度
//算术转换要合理,不然会有一些问题
12.3操作符的属性
复杂表达式的求值会受到三个因素的影响。
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序
两个相邻的操作符的执行先后,取决于它们的优先级。如果优先级相同,取决于它们的结合性。
有一些问题表达式:
c
//表达式的求值部分由操作符的优先级决定。
a*b + c*d + e*f
注释:该代码在计算的时候,由于*比+的优先级高,只能保证,的计算比+早,但优先级不能决定第三个比第一个+早执行
所以表达式的计算顺序可能是:
c
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
c
c + --c;
注释:操作符的优先级只可决定--的运算在+的运算前面,但并没有办法得
知,+操作符的左操作数的获取是在右操作数之前还是之后求值,所以结果不可预测,是有歧义的。
c
//非法表达式
int main()
{
int a = 10;
a = a-- - --a * ( a = -3 ) * a++ + ++a;
printf("a = %d\n", a);
return 0;
}
该表达式在不同的编译器有不同的结果
值 | 编译器 | |
---|---|---|
42 | Microsoft C 5.1 | |
36 | Dec VAX/VMS | |
21 | Turbo C/C++ 4.5 | |
---86 | IBM PowerPC AIX 3.2.5 | |
... | ... |
c
int f()
{
static int count = 1;
return ++count;
}
int main()
{
int a;
a = f() - f() * f();
printf( "%d\n", a);//输出-10
return 0;
}
这个代码也存在一定的问题。
虽然在大多数的编译器上得到的结果都是相同的。但是a = f() - f() * f();中我们只能通过操作符的优先级先算乘法,
再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。
c
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
//vs 12 4
//gcc 10 4
}
相同的代码缺产生了不同的结果
该代码中第一个 + 执行的时候,第三个++是否在执行,这个是不确定的,因为依靠操作符的优先级和结合性是没法取决定第一个 + 和第三个++ 的执行先后顺序。
总结:写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那么该表达式存在一些问题的。
练习:
1.创建一个整型数组,完成对数组进行以下操作:
实现函数init() 初始化数组全为0
实现print() 打印数组的每一个元素
实现reverse() 函数完成数组元素的逆置
c
#include <stdio.h>
void init(int arr[], int sz)
{
for (int i = 0;i < sz; i++)
{
arr[i] = 0;
}
}
void print(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void reverse(int arr[], int sz)
{
int left = 0;
int right = sz - 1;
int tmp = 0;
while (left < right)
{
tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
reverse(arr, sz);
print(arr, sz);
init(arr, sz);
print(arr, sz);
return 0;
}
2.交换数组
将数组A中的内容和数组B中的内容进行交换。(数组大小一样)
c
#include <stdio.h>
void swap(int arr1[],int arr2[], int sz)
{
int tmp = 0;
for (int i = 0;i < sz;i++)
{
tmp = arr1[i];
arr1[i] = arr2[i];
arr2[i] = tmp;
}
}
void print(int arr1[], int arr2[], int sz)
{
for (int i = 0;i < sz;i++)
{
printf("%d ", arr1[i]);
}
printf("\n");
for (int i = 0;i < sz;i++)
{
printf("%d ", arr2[i]);
}
}
int main()
{
int arr1[] = { 3,13,23,33,43,53,63,73,83,93 };
int arr2[] = { 2,12,22,32,42,52,62,72,82,92 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
swap(arr1, arr2, sz);
print(arr1, arr2, sz);
return 0;
}
作业:
1.统计二进制中1的个数
写一个函数返回参数二进制中1的个数。
如:15 0000 1111 4个1
c
#include <stdio.h>
//int Number(int b)
//{
// int count = 0;
// while (b)
// {
// if (b % 2 == 1)
// {
// count++;
// }
// b = b / 2;
// }
// return count;
//}
int Number(int b)
{
int count = 0;
int i = 0;
for (i = 0;i < 32;i++)
{
if(((b >> i) & 1 )== 1)
{
count++;
}
}
return count;
}
int main()
{
int a = -1;
//10000000 00000000 00000000 00000001 原码
//11111111 11111111 11111111 11111110 反码
//11111111 11111111 11111111 11111111 补码
int ret = Number(a);
printf("%d\n", ret);
return 0;
}
2.求两个数二进制中不同位的个数
编程实现:两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?
输入:1999 2999 输出:6
c
#include <stdio.h>
int Number(int n)
{
int count = 0;
while (n)
{
n= n & (n - 1);
count++;
}
return count;
}
int main()
{
int a = 0;
int b = 0;
int i = 0;
int count = 0;
scanf("%d %d", &a, &b);
/*for (i = 0;i < 32;i++)
{
if (((a >> i) & 1) != ((b >> i) & 1))
{
count++;
}
}*/
int ret = a ^ b;
count = Number(ret);
printf("%d\n", count);
return 0;
}
3.打印整数二进制的奇数位和偶数位
获取一个整数二进制序列中所有的偶数位和奇数位,分别打印二进制序列
c
#include <stdio.h>
int main()
{
int a = 0;
int i = 0;
scanf("%d", &a);
//偶数
for (i = 31;i >= 1;i -= 2)
{
printf("%d ",((a>>i) & 1));
}
printf("\n");
//奇数
for (i = 30;i >= 0;i -= 2)
{
printf("%d ", ((a >> i) & 1));
}
return 0;
}
4.交换两个变量(不创建临时变量)
不允许创建临时变量,交换两个整数的内容
c
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
printf("%d %d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %d\n",a, b);
return 0;
}
上一章:C语言入门学习 --- 4.数组
配套练习:
C语言练习题110例(一)
C语言练习题110例(二)
C语言练习题110例(三)
C语言练习题110例(四)
C语言练习题110例(五)
C语言练习题110例(六)
C语言练习题110例(七)
C语言练习题110例(八)
C语言练习题110例(九)
C语言练习题110例(十)
C语言练习题110例(十一)