C语言学习
目录
前言
一、算术操作符
二、移位操作符
2.1 左移操作符
2.2 右移操作符
三、位操作符
3.1 按位与操作符 &
3.2 按位或操作符 |
3.3 按位异或操作符 ^
四、赋值操作符
五、单目操作符
5.1 逻辑反操作符!
5.2 正值+、负值-操作符
5.3 取地址操作符&
5.4 计算操作数的类型长度sizeof
5.5 按位取反操作符 ~
5.6 ++和--操作符
5.7 解引用操作符 *
5.8 强制类型转换操作符()
5.9 sizeof和数组
六、关系操作符
七、逻辑操作符
八、条件操作符
九、逗号表达式
十、下标引用、函数调用和结构成员
10.1 [ ] 下标引用操作符
10.2 ( ) 函数调用操作符
10.3 结构成员
总结
本文介绍C语言操作符,有以下内容:算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、函数调用和结构成员。
+ - * / %
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. / 操作符:如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
代码示例:
#include
int main()
{
int a = 1 / 2;
printf("a=%d\n", a);
return 0;
}
运行结果:
a=0
代码示例1:
#include
int main()
{
double b = 1.0 / 2;
printf("b=%f\n", b);
return 0;
}
运行结果:
b=0.500000
代码示例2:
#include
int main()
{
double b = 1 / 2.0;
printf("b=%f\n", b);
return 0;
}
运行结果:
b=0.500000
3. % 操作符计算的是整除后的余数,% 操作符的两个操作数必须为整数。
代码示例:
#include
int main()
{
int c = 7 % 2;
printf("c=%d\n", c);
return 0;
}
运行结果:
c=1
- << 左移操作符
- >> 右移操作符
注:移位操作符的操作数只能是整数。
移位操作符移动的是二进制位。
整数的二进制表示有3种:
正的整数的原码、反码、补码相同。
负的整数的原码、反码、补码是要计算的。
示例:
7的原码、反码、补码:
-7的原码、反码、补码:
整数在内存中存的是补码。
移位操作符移动的是存在内存中的补码。
移位规则: 左边抛弃、右边补0。
正的整数左移示例:
对7左移
补码:0000 0000 0000 0000 0000 0000 0000 0111
左移:0000 0000 0000 0000 0000 0000 0000 1110
左移后内存中存的还是补码,因为正整数的原码和补码相同,所以对7左移后的值十进制为14。
代码验证:
#include
int main()
{
int a = 7 ;
int b = a << 1;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
运行结果:
a=7
b=14
变量b得到了变量a左移1位之后的变化,但变量a自身是不变的。
负的整数左移示例:
对-7左移
补码:1111 1111 1111 1111 1111 1111 1111 1001
左移:1111 1111 1111 1111 1111 1111 1111 0010
左移后内存中存的还是补码,因为负整数的原码和补码之间需要计算。
补码:1111 1111 1111 1111 1111 1111 1111 0010 (左移1位后)
反码:1111 1111 1111 1111 1111 1111 1111 0001 补码-1
原码:1000 0000 0000 0000 0000 0000 0000 1110 符号位不变,其他位按位取反。
计算出-7左移后的值十进制为-14。
代码验证:
#include
int main()
{
int a = -7 ;
int b = a << 1;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
运行结果:
a=-7
b=-14
因为二进制数的每一位都是2的指数幂,所以左移1位后,只要没有发生数据溢出,值就会变为原来的2倍,如果变量为无符号整形或正整数,表达式 a << n 会将a的所有位左移n位,运算结果位a×。
移位规则:
- 逻辑移位:左边补0,右边丢弃。
- 算术移位:左边补原值的符号位,右边丢弃。(绝大多数编译器采用算术移位,所以示例都为算数移位结果)
正的整数右移示例:
对7右移
补码:0000 0000 0000 0000 0000 0000 0000 0111
左移:0000 0000 0000 0000 0000 0000 0000 0011
右移后内存中存的还是补码,因为正整数的原码和补码相同,所以7左移后的值十进制为3。
代码验证:
#include
int main()
{
int a = 7 ;
int b = a >> 1;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
运行结果:
a=7
b=3
负的整数右移示例:
对-7右移
补码:1111 1111 1111 1111 1111 1111 1111 1001
左移:1111 1111 1111 1111 1111 1111 1111 1100
右移后内存中存的还是补码,因为负整数的原码和补码之间需要计算。
补码:1111 1111 1111 1111 1111 1111 1111 1100 (左移1位后)
反码:1111 1111 1111 1111 1111 1111 1111 1011 补码-1
原码:1000 0000 0000 0000 0000 0000 0000 0100 符号位不变,其他位按位取反。
计算出-7右移后的值十进制为-4。
代码验证:
#include
int main()
{
int a = -7 ;
int b = a >> 1;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
运行结果:
a=-7
b=-4
因为二进制数的每一位都是2的指数幂,所以右移1位后,只要没有发生数据溢出,值就会变为原来的二分之一,如果变量为无符号整形或正整数,表达式 a >> n 会将a的所有位右移n位,运算结果位a÷。
警告:对于移位运算符,不要移动负数位,这个是标准未定义的。
- & //按(2进制)位与
- | //按(2进制)位或
- ^ //按(2进制)位异或 —— 相同为0,相异为1
注:他们的操作数必须是整数。
& :两者都为1时结果为1。
代码示例:
#include
int main()
{
int a = -5;
int b = 3;
int c = a & b;
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
return 0;
}
运行结果:
a=-5
b=3
c=3
先计算出-5和3的二进制位补码
-5二进制位
原码:1000 0000 0000 0000 0000 0000 0000 0101
反码:1111 1111 1111 1111 1111 1111 1111 1010
补码:1111 1111 1111 1111 1111 1111 1111 1011
3二进制位
补码:0000 0000 0000 0000 0000 0000 0000 0011
用补码进行按位与计算:
1111 1111 1111 1111 1111 1111 1111 1011 -5补码
0000 0000 0000 0000 0000 0000 0000 0011 3补码
0000 0000 0000 0000 0000 0000 0000 0011 -5 & 3 的补码
因为符号位为0,为正整数,所以原码与补码相同,对应十进制值为3。
| :只要有一个为1,结果就为1。
代码示例:
#include
int main()
{
int a = -5;
int b = 3;
int c = a | b;
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
return 0;
}
运行结果:
a=-5
b=3
c=-5
用补码进行按位或计算:
1111 1111 1111 1111 1111 1111 1111 1011 -5补码
0000 0000 0000 0000 0000 0000 0000 0011 3补码
1111 1111 1111 1111 1111 1111 1111 1011 -5 | 3 的补码
因为符号位为1,为负整数,所以原码需由补码计算得出。
1111 1111 1111 1111 1111 1111 1111 1011 补码
1111 1111 1111 1111 1111 1111 1111 1010 反码
1000 0000 0000 0000 0000 0000 0000 0101 原码
计算得出原码的十进制值为-5。
^ :相同为0,相异为1。
代码示例:
#include
int main()
{
int a = -5;
int b = 3;
int c = a ^ b;
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
return 0;
}
运行结果:
a=-5
b=3
c=-8
用补码进行按位异或计算:
1111 1111 1111 1111 1111 1111 1111 1011 -5补码
0000 0000 0000 0000 0000 0000 0000 0011 3补码
1111 1111 1111 1111 1111 1111 1111 1000 -5 ^ 3 的补码
因为符号位为1,为负整数,所以原码需由补码计算得出。
1111 1111 1111 1111 1111 1111 1111 1000 补码
1111 1111 1111 1111 1111 1111 1111 0111 反码
1000 0000 0000 0000 0000 0000 0000 1000 原码
计算得出原码的十进制值为-8。
编程题:不能创建临时变量(第三个变量),实现两个数的交换。
方法1代码示例:
#include
int main()
{
int a = 5;
int b = 3;
printf("a=%d ", a);
printf("b=%d\n", b);
a = a + b;//变量a中存了a+b的和
b = a - b;//a+b的和8减去变量b的值3就是变量a的初值5,
//把5存在变量b中,此时变量a中的值还是a+b的和8
a = a - b;//a+b的和8减去此时变量b的值5,就是变量b的初值3,
//把3存在变量a中,此时变量a中的值是3
printf("a=%d ", a);
printf("b=%d\n", b);
return 0;
}
运行结果:
a=5 b=3
a=3 b=5
但是这种方法会有溢出的问题。
方法2代码示例:
#include
int main()
{
int a = 5;
int b = 3;
printf("a=%d ", a);
printf("b=%d\n", b);
a = a ^ b;//a = 5 ^ 3
b = a ^ b;//b = 5 ^ 3 ^ 3 -> b=5
a = a ^ b;//a = 5 ^ 3 ^ 5 -> a=3
printf("a=%d ", a);
printf("b=%d\n", b);
return 0;
}
运行结果:
a=5 b=3
a=3 b=5
两个相同的数按位异或的结果为0;0和任何整数按位异或的结果为整数本身。
a = b 就是把b的值赋给a,在 = 左侧必须是变量,不能是常量或表达式。
复合赋值符:+= -= *= /= %= >>= <<= &= |= ^=
下方每个代码框中的第1句使用复合赋值操作符的代码意思和它下一句代码意思相同。
a += 3;
a = a + 3;
a >>= 3;
a = a >> 3;
a &= 3;
a = a & 3;
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
首先来明确一些定义:
!作用:把真变假,假变真。
C语言中0为假,非0为真。
代码示例:
#include
int main()
{
int flag = 5;
//if条件判断:非0为真,0为假
if(flag)//flag为真,进入if
{
printf("真\n");
}
if(!flag)//flag为假,进入if
{
printf("假\n");
}
return 0;
}
代码示例:
#include
int main()
{
int a = +8;
int b = +a;
int c = -a;
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
int e = -9;
int f = +e;
int g = -e;
printf("e=%d\n", e);
printf("f=%d\n", f);
printf("g=%d\n", g);
return 0;
}
运行结果:
a=8
b=8
c=-8
e=-9
f=-9
g=9
&操作符可以取出变量在内存中的首地址。
代码示例:
#include
int main()
{
int a = 6;
printf("%p\n", &a);
int* p = &a;//p就是指针变量
return 0;
}
运行结果:
000000AAD799FA74
sizeof是一个操作符。
代码示例:
#include
int main()
{
//计算变量所占内存空间的大小,单位是字节。
int a = 7;
int n1 = sizeof(a); //计算变量a所占内存空间的大小
printf("n1=%d\n", n1);
//计算类型所创建的变量占据空间的大小,单位是字节。
int n2 = sizeof(int);
printf("n2=%d\n", n2);
//计算整个数组所占内存空间的大小,单位是字节。
int arr[10] = { 0 };
printf("arr[10]=%d\n", sizeof(arr));
return 0;
}
运行结果:
n1=4
n2=4
arr[10]=40
sizeof使用细节注意
代码示例:
#include
int main()
{
int a = 3;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//可以这样使用
printf("%d\n", sizeof int);//不可以这样使用
return 0;
}
区分:
~0 对0按位取反:
补码:0000 0000 0000 0000 0000 0000 0000 0000 0的原码、反码、补码都相同
取反:1111 1111 1111 1111 1111 1111 1111 1111
按位取反后内存中存的还是补码,负整数的原码和补码之间需要计算。
补码:1111 1111 1111 1111 1111 1111 1111 1111 (0按位取反后)
反码:1111 1111 1111 1111 1111 1111 1111 1110 补码-1
原码:1000 0000 0000 0000 0000 0000 0000 0001 符号位不变,其他位按位取反。
计算出0按位取反后的值十进制为-1。
代码验证:
#include
int main()
{
int a = 0;
printf("~a=%d\n", ~a);
return 0;
}
运行结果:
~a=-1
~5 对5按位取反
代码示例:
#include
int main()
{
int a = 5;
printf("~a=%d\n", ~a);
return 0;
}
运行结果:
~a=-6
~5
补码:0000 0000 0000 0000 0000 0000 0000 0101 5的原码、反码、补码都相同
取反:1111 1111 1111 1111 1111 1111 1111 1010
按位取反后内存中存的还是补码,负整数的原码和补码之间需要计算。
补码:1111 1111 1111 1111 1111 1111 1111 1010 (5按位取反后)
反码:1111 1111 1111 1111 1111 1111 1111 1001 补码-1
原码:1000 0000 0000 0000 0000 0000 0000 0110 符号位不变,其他位按位取反。
计算出0按位取反后的值十进制为-6。
对某个数的二进制任意一位进行置1或清0的改动:
代码示例:
#include
int main()
{
int a = 12;
a |= (1 << 4);
printf("a=%d\n", a);
a &= (~(1 << 4));
printf("a=%d\n", a);
return 0;
}
运行结果:
a=28
a=12
对12二进制的第五位置1
0000 0000 0000 0000 0000 0000 0000 1100 12补码
0000 0000 0000 0000 0000 0000 0001 0000 修改第五位的数可由1移位获得
0000 0000 0000 0000 0000 0000 0001 1100 12与上数按位或,得到指定修改位置1
第五位置1后的数十进制为28
对28二进制的第五位清0
0000 0000 0000 0000 0000 0000 0001 1100 28补码
0000 0000 0000 0000 0000 0000 0001 0000 修改第五位的数可由1移位获得
1111 1111 1111 1111 1111 1111 1110 1111 对1移位后的数按位取反
0000 0000 0000 0000 0000 0000 0000 1100 28与上数按位与,得到指定修改位置清0
第五位清0后的数十进制为12
- 前置++,先++,后使用;
- 后置++,先使用,再++;
- 前置--,先--,后使用;
- 后置--,先使用,再--;
代码示例:
#include
int main()
{
int a = 6;
int b = ++a;//前置++,先++,后使用
//a = a+1; b = a;
printf("a=%d\n", a);
printf("b=%d\n", b);
int c = 6;
int d = c++;//后置++,先使用,再++
//c = d; c = d+1;
printf("c=%d\n", c);
printf("d=%d\n", d);
return 0;
}
运行结果:
a=7
b=7
c=7
d=6
代码示例:
#include
int main()
{
int a = 6;
int b = --a;//前置--,先--,后使用
//a = a-1; b = a;
printf("a=%d\n", a);
printf("b=%d\n", b);
int c = 6;
int d = c--;//后置--,先使用,再--
//c = d; c = d-1;
printf("c=%d\n", c);
printf("d=%d\n", d);
return 0;
}
运行结果:
a=5
b=5
c=5
d=6
代码示例:
#include
int main()
{
int a = 6;
int* p = &a;
*p = 20;
printf("a=%d\n", a);
return 0;
}
运行结果:
a=20
上面程序中*p就等价于变量a。
强制类型转换是把变量从一种类型转换为另一种数据类型。
代码示例:
#include
int main()
{
int a = (int)3.14;
printf("a=%d\n", a);
return 0;
}
运行结果:
a=3
(int)可以强制将带小数点的数转换为整型。
代码示例:
#include
void test1(int arr[])
{
printf("test1(arr[])=%d\n", sizeof(arr));
}
void test2(char ch[])
{
printf("test2(ch[])=%d\n", sizeof(ch));
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("arr[10]=%d\n", sizeof(arr));
printf("ch[10]=%d\n", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
运行结果:
arr[10]=40
ch[10]=10
test1(arr[])=8
test2(ch[])=8
sizeof(数组名) 可得出整个数组在内存中的大小。
数组传参,传递的是数组首元素的地址。所以test1函数和test2函数中sizeof操作符读到的是数组首元素地址在内存中的大小。
>>=<<=!= 用于测试“不相等”== 用于测试“相等”
使用过程中注意==和=
代码示例:
#include
int main()
{
if (3 == 5)//可以进行数是否相等的判断
{
printf("相等");
}
if ("abc" == "abcdef")//这样写实际上是在比较2个字符串的首字符的地址,
//不是比较两个字符串是否相等
{
printf("相等");
}
//比较两个字符串是否相等应该使用strcmp库函数
return 0;
}
&& 逻辑与:如果两边操作数都为真,则结果为真;
如果两边操作数有一个为假,则结果为假。
|| 逻辑或:如果两边操作数有一个为真,则结果为真;
如果两边操作数都为假,则结果为假。
代码示例:
#include
int main()
{
int a = 0 && 2;
int b = 1 && 2;
int c = 0 || 2;
int d = 1 || 2;
int e = 0 || 0;
printf("0 && 2 = %d\n", a);
printf("1 && 2 = %d\n", b);
printf("0 || 2 = %d\n", c);
printf("1 || 2 = %d\n", d);
printf("0 || 0 = %d\n", e);
return 0;
}
运行结果:
0 && 2 = 0
1 && 2 = 1
0 || 2 = 1
1 || 2 = 1
0 || 0 = 0
代码示例:
#include
int main()
{
int a = 1 & 2;
int b = 1 && 2;
int c = 1 | 2;
int d = 1 || 2;
printf("1 & 2 = %d\n", a);
printf("1 && 2 = %d\n", b);
printf("1 | 2 = %d\n", c);
printf("1 || 2 = %d\n", d);
return 0;
}
运行结果:
1 & 2 = 0
1 && 2 = 1
1 | 2 = 3
1 || 2 = 1
总结:逻辑与和逻辑或只关注真假,不关注二进制位。
编程题:
1.写一个判断闰年的函数。
能被4整除,并且不能被100整除,但能被400整除是闰年
代码示例:
#include
int is_lear_year(int y)
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int a = 0;
scanf("%d", &a);
if(is_lear_year(a))
{
printf("是闰年!");
}
else
{
printf("不是闰年!");
}
return 0;
}
运行结果:
2023
不是闰年!
2024
是闰年!
2.分析代码运行结果
代码1:
#include
int main()
{
int i = 0, a = 0, b = 2, c = 3;
i = a++ && ++b && c++;
printf("i = %d\na = %d\nb = %d\nc = %d\n",i, a, b, c);
return 0;
}
运行结果:
i = 0
a = 1
b = 2
c = 3
分析:
i = a++ && ++b && c++;
所以表达式是:i = 0 && ++b && c++; 因为逻辑与时只要有0,结果就为0,所以 0&&++b,左边操作数为0,右边不用算也知道结果为0。实际上代码运行时也是这么处理的,右边的++b也不会运算。同理c++也不会运算。所以变量b和c的值不变。
代码2:
#include
int main()
{
int i = 0, a = 0, b = 2, c = 3;
i = a++ || ++b || c++;
printf("i = %d\na = %d\nb = %d\nc = %d\n", i, a, b, c);
return 0;
}
运行结果:
i = 1
a = 1
b = 3
c = 3
分析:
i = a++ || ++b || c++;
所以表达式是:i = 0 || 3 || c++; 因为逻辑或只要有一个为真,结果就为真,所以 0 || 3结果为真,c++不用算也知道最后结果为真。实际上代码运行时也是这么处理的,最右边的c++也不会运算。所以变量c的值不变。
总结:
&& 左边为假,右变不计算。
|| 左边为真,右变不计算。
exp1 ? exp2 : exp3
代码1:
if (a > 5)
b = 3;
else
b = -3;
代码2:
(a > 5) ? (b = 3) : (b = -3);
代码3:
b = ((a > 5) ? 3 : -3);
代码1、2、3等价。
exp1 , exp2 , exp3 , …expN
代码示例:
#include
int main()
{
int a = 5;
int b = 3;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("c=%d\n", c);
return 0;
}
运行结果:
c=14
表达式 (a > b, a = b + 10, a, b = a + 1) 从左向右依次执行,a=3+10,a=13,b=13+1,最后b=14是整个表达式的最后一个表达式的结果,所以整个表达式的结果也为14。
代码1:
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
代码1用逗号表达式可以简化为:
while (a = get_val(), count_val(a), a > 0)
{
//业务处理
}
下标引用操作符的操作数:一个数组名 + 一个索引值
代码示例:
int arr[10] = { 0 };//创建数组
arr[9] = 10;//实用下标引用操作符。
arr[9] 的两个操作数是arr和9。
思考:访问数组时 arr[9] 中两个操作数的位置调换运行时是否会报错?
代码示例:
#include
int main()
{
int arr[10] = { 0 };
arr[9] = 10;
9[arr] = 6;
return 0;
}
调试的监视窗口:
从单步调试的监视窗口来看arr[9] 中两个操作数的位置调换运行时不但不会报错,还可以正常赋值。由此进一步验证 [ ] 就是一个操作符。
arr[9] 等价于 *(arr+9)
由加法交换律可知:*(arr+9) 等价于 *(9+arr)
由于 arr[9] 可等价于 *(arr+9)
所以 *(9+arr)可等价于9[arr]
但实际写代码时,数组不可以像9[arr]这样写。
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
函数调用操作符至少有一个操作数。
代码示例:
#include
//函数定义
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 2;
int b = 3;
//函数调用
int c = Add(a, b);//()函数调用操作符,操作数:Add、a、b
printf("%d\n", c);
return 0;
}
运行结果:
5
. 结构体 . 成员名-> 结构体指针 -> 成员名
代码示例1:
#include
struct Panda
{
//成员
char name[30];
int age;
char sex[10];
char birth[20];
};
print(struct Panda* pHua)
{
printf("%s %d %s %s\n", (*pHua).name, (*pHua).age, (*pHua).sex, (*pHua).birth);
printf("%s %d %s %s\n", pHua->name, pHua->age, pHua->sex, pHua->birth);
//结构体指针变量->成员名
}
int main()
{
struct Panda Hua = { "HeHua",3,"mu","2020.07.04" };
printf("%s %d %s %s\n", Hua.name, Hua.age, Hua.sex, Hua.birth);
//结构体对象.成员名
print(&Hua);
return 0;
}
运行结果:
HeHua 3 mu 2020.07.04
HeHua 3 mu 2020.07.04
HeHua 3 mu 2020.07.04
总结:(*px).成员名 等价于 px->成员名
以上就是今天要讲的内容,本文介绍了C语言操作符的使用。