系列文章目录
C语言学习入门第五节————操作符详解(下)
操作符详解
- 操作符分类
- 算术操作符
- 位移操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
- 表达式求值
文章目录
一、关系操作符
> : 大于
>= : 大于等于
<: 小于
<= : 小于等于
!= : 不等于(用于测试“不相等”)
== : 相等(用于测试“相等”)
在编程的过程中 == 和 = 不小心写错,容易导致错误
二、逻辑操作符
- && : 逻辑与(并且:两个为真,才为真)
- || : 逻辑或(或者:多个中一个为真,就为真)
- ! : 逻辑非
1.&& : 逻辑与(并且:两个为真,才为真)
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
//a 和 b 都是5,打印 hehe
if (a == 5 && b == 5)
{
printf("hehe\n");
}
//a 或者 b是5 打印哈哈
if (a == 5 || b == 5)
{
printf("haha\n");
}
return 0;
}
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;//整个表达式的结果为0,因为a = 0,为加a++执行完后,不会在执行b++
//所以a = 1,b = 2
//因为前面的表达式结果为0,所以这个表达式的结果也为0
//所以d++也不执行,d = 4,i= 0
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);//
return 0;
}
2. || : 逻辑或(或者:多个中一个为真,就为真)
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 4;
i = a++ || ++b || c++;//因为a = 0,为假。++b后b = 3,为真。
//所以此时a = 1,b = 3,整体结果为真,即为1
//因为c++左边已经为真,所以c++不执行
//所以c = 4,i = 1
printf("a = %d\nb = %d\nc = %d\n", a, b, c);
return 0;
}
3.判断闰年( * )
//判断闰年
#include <stdio.h>
int main()
{
int y = 0;
scanf("%d", &y);
//1. 能被4整除,并且不能被100整除
//2. 能被400整除是闰年
if ( ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
{
printf("闰年\n");
}
else
{
printf("不是闰年");
}
return 0;
}
三、条件操作符(三目操作符,有三个操作数)
exp1 —— ? —— exp2 —— : —— exp3
(表达式1) (表达式2) (表达式3)
真 --> 计算 --> 不计算
==> exp2 是整个表达式的结果
exp1 —— ? —— exp2 —— : —— exp3
假 --> 不计算 --> 计算
==> exp3 是整个表达式的结果
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
if (a > 5)
{
b = 3;
}
else
{
b = -3;
}
//第一种写法:
(a > 5) ? (b = 3) : (b = -3);
//第二种写法:
b = ((a > 5) ? (3) : (-3));
return 0;
}
四、逗号表达式( * * * )
逗号表达式:用逗号隔开的多个表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果
exp1 , exp2 , exp3 , …expN
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//从左向右依次执行,整个表达式的结果是最后一个表达式的结果
printf("%d\n", a);//所以a = 12
printf("%d\n", b);//b = 13
printf("%d\n", c);//c = b = 13
return 0;
}
五、下标引用,函数调用和结构函数
1. [] : 下标引用操作符
操作数:一个数组名 + 一个索引值
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5 };
// 下标: 0 1 2 3 4
//数组的起始是有下标的,下标是从0开始的
printf("%d\n", arr[4]);
return 0;
}
2.(): 函数调用操作符
接受1个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int c = Add(6, 9); // 函数调用操作符
printf("%d", c);
return 0;
}
3.访问一个结构的成员
. : 结构体.成员名
#include <stdio.h>
//创建一个结构体(类似java中的类)
struct Book //使用struct创建结构体
{
//定义成员
char name[30]; //书名
char author[20]; //作者
float price; //价格
}; //结尾要加分号
int main()
{
//一本书:
struct Book b1 = { "C语言详解", "王五", 66.5f };
//另一本书:
struct Book b2 = { "数据结构", "李四", 66.5f };
printf("%s %s %.1f\n", b1.name, b1.author, b1.price);
// . --> 访问结构体成员:结构体变量.成员名
printf("%s %s %.1f\n", b2.name, b2.author, b2.price);
return 0;
}
-> : 结构体指针->成员名:
#include <stdio.h>
//创建一个结构体(类似java中的类)
struct Book //使用struct创建结构体
{
//定义成员
char name[30]; //书名
char author[20]; //作者
float price; //价格
}; //结尾要加分号
//写一个打印结构体变量信息的函数
void Print(struct Book* p)
// * 表示 p 是指针变量
// struct Book 表示 指针变量的 类型
{
//第一种方法:
printf("%s %s %.1f\n", (*p).name, (*p).author, (*p).price);
//第二种方法:
printf("%s %s %.1f\n", p->name, p->author, p->price);
// 结构指针 -> 成员名
}
int main()
{
//一本书:
struct Book b1 = { "C语言第一课", "张三", 66.5f };
//另一本书:
struct Book b2 = { "数据结构第一课", "李四", 66.5f };
//写一个打印结构体变量信息的函数
Print(&b1);
Print(&b2);
return 0;
}
六、表达式求值
- 表达式求值的顺序一部分是由操作符的优先级和结合性决定。
- 有些表达式的操作数在求值的过程中可能需要转换为其它类型。
1 隐式类型转换(整型提升)( * * * * * )
- C语言的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。
- 为了获得这个精度,表达式中的字符(char)和短整型(short)操作数在使用之前被转换为普通整型(char为1字节,int为4字节),这种转换称为整型提升,这里的转换只是计算时临时转换一下,变量本身不会转换类型。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器(register)的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose
CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能由这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整形值,都必须先转换为
int 或 unsigned int ,然后才能送入CPU去执行运算。
整型提升是按照变量的数据类型的符号位来提升的
(一个有符号的数字,在二进制序列里面就把最高位规定为符号位)
#include <stdio.h>
int main()
{
// 存放时截断,使用时整型提升
char c1 = 5;
//00000000000000000000000000000101 -- 整型5的32为bit位(四个字节:32位)
//00000101 -- char类型5的8为bit位(一个字节:8位)(截断)
//00000101 -- c1
char c2 = 127;
//00000000000000000000000001111111 - 整型
//01111111 -- c2
char c3 = c1 + c2;
//计算时要整型提升:整型提升是按照变量的数据类型的符号位来提升的
//char类型是有符号的char,此时高位就是它的符号位,为 0,所以 补 0 到 32位,
//
//00000101 --》00000000000000000000000000000101
// 相加
//01111111 --》00000000000000000000000001111111
// 从 char 变成 int
//得 00000000000000000000000010000100 -- 32位
//
// 要把32位放进8位得char中,再进行截断
//00000000000000000000000010000100 --》10000100
//所以 c3 = 10000100
// 这时打印 c3 ,会打印 -124
//原因:
// %d - 以10进制的形式打印有符号的整数
// 因为 c3 是 char 类型的,不是 %d 要的整型,所以这里也要进行整型提升
// c3 = 10000100 ,char类型是有符号的char,此时高位就是它的符号位,为 1
// 所以 补 1 到 32位,
// (无符号整形提升,直接高位补0)
// 10000100 --》
// 11111111111111111111111110000100 -- 补码
// 11111111111111111111111110000011 -- 反码(补码 - 1)
// 10000000000000000000000001111100 -- 原码(反码按位取反)
// 得 打印时得 -124
printf("%d\n", c3);
return 0;
}
证明 整形提升 的存在:
有符号数字和无符号数字:
10010100,如果这是有符号数字,最高位就是符号位,不是有效数字如果这是无符号数字,最高位就是有效数字,表示 2的7次方
(截断:以char类型为例,截断会直接获取二进制序列最低的8位,即一个字节,其它位全部丢弃)
#include <stdio.h>
//%d - 以10进制的形式打印有符号的整数
//%u - 以10进制的形式打印无符号的整数
int main()
{
char c = 1;//char类型占一个字节
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));//%u打印无符号 c是有符号的,要转换为无符号整数,整形提升为四个字节
printf("%u\n", sizeof(-c));//%u打印无符号 c是有符号的,要转换为无符号整数,整形提升为四个字节
return 0;
}
2.算术转换( * * * * * )
如果某个操作符的各个操作数属于不同的类型(>=4个字节),那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换(向上转换),这里的转换只是计算时临时转换一下,变量本身不会转换类型。
如果某个操作数的类型在下面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告: 算术转换要合理,要不然会有一些潜在的问题,如精度丢失。
3.操作符的属性
杂表达式的求值有三个影响的因素:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
七、sizeof 和 strlen 的对比( * * * * )
sizeof 是 操作符 ;strlen 是 库函数
sizeof 计算的是占用内存的大小,单位是字节,不关注内存中存放的具体数据
strlen 是求字符串长度的,只能针对字符串,会寻找字符串中的 \0,统计的是字符串中 \0 之前出现的字符的个数
使用函数完成整型函数的打印、元素逆置、初始化
//实现函数init() 初始化数组为全0
//实现print() 打印数组的每个元素
//实现reverse() 函数完成数组元素的逆置。
//要求:自己设计以上函数的参数,返回值。
#include <stdio.h>
//实现print() 打印数组的每个元素
void print(int* arr, int sz)
{
//输出:
int j = 0;
for (j = 0; j < sz; j++)
{
printf("%d ", arr[j]);
}
//换行:
printf("\n");
}
//实现reverse() 函数完成数组元素的逆置。
void reverse(int* arr, int sz)
{
int i = 0;
for (i = 0; i < (sz / 2); i++)
//循环条件???
//6个元素调3次,5个元素掉两次
//sz/2 肯定够用
{
int* left = arr + i;
int* right = arr + sz - 1 - i;
// 使用指针移位,类似下标,易混
// 让左右指针慢慢往中间靠
int tmp = *left;
*left = *right;
*right = tmp;
// 获取指针值是用 *
}
}
//实现函数init() 初始化数组为全0
void init(int* arr, int sz)
{
//arr:首元素地址 ; sz为几就循环几次
int i = 0;
for (i = 0; i < sz; i++)
{
int* tmp = arr + i; //创建指针变量存放指针
*tmp = 0; //取指针中的值,并赋为0
}
}
int main()
{
int arr[5] = { 1,2,3,4,5 };
int sz = sizeof(arr) / sizeof(arr[0]); //元素个数
//实现print() 打印数组的每个元素
print(arr, sz);
//实现reverse() 函数完成数组元素的逆置。
reverse(arr, sz);
print(arr, sz);
//实现函数init() 初始化数组为全0
init(arr, sz);
print(arr, sz);
return 0;
}