一、算术操作符
在C语言中,算术操作符是用来执行基本数学运算的符号,包括加法、减法、乘法、除法以及取模(求余数)等。这种操作符有两个操作数,位于操作符两端的数字就是操作数,这种操作符也叫双目操作符。
+ - * / %
- 除了%之外的操作符都可以作用于整数和浮点数。
- 对于/操作符,如果两个操作数都为整数,那么执行整数除法。只有要浮点数就执行浮点数除法。
- %的两个操作数必须是整数,返回的是整除之后的余数。
#include<stdio.h>
int main()
{
int num1 = 1;
double num2 = 3.2;
int num3 = 2;
int num4 = 7;
printf("%f\n", num1 + num2);
printf("%d\n", num1 + num3);
printf("%f\n", num2 - num1);
printf("%f\n", num2 * num1);
printf("%f\n", num2 / num1);
printf("%d\n", num4 % num3);
}
运行结果为:
在计算机中执行算数运算一个不可避免的话题是操作精度问题,如果将一个浮点数和整数进行运算,结果将会是一个浮点数。浮点数可以表示小数部分而整数不可以,因此直接将一个浮点数的结果存储到整型变量中是不可行,这会导致数据丢失——浮点数的小数部分会直接被截断。
#include<stdio.h>
int main()
{
int num1 = 1;
double num2 = 3.2;
printf("%d\n", num2 / num1);//这样写对吗?
printf("%f\n", num2 / num1);
}
输出结果为:
直接讲浮点数存储到整型变量中,编译器可能会给出警告,如果想要完整的存储这个结果应当采用一个浮点数变量。如果你想将浮点数转换为整数并存储在整型变量中,可以使用强制类型转换或者像floor,ceil,round这样的函数来处理。
#include<stdio.h>
#include<math.h>
int main()
{
double k = 3.57;
int num1 = (int)k;//强制类型转换
int num2 = floor(k);//向下取整,但不会改变变量的类型
int num3 = ceil(k);//向上取整
int num4 = round(k);//四舍五入
printf("%d\n", num1);
printf("%d\n", num2);
printf("%d\n", num3);
printf("%d\n", num4);
}
输出结果为:
另外负数求模的规则是余数按照无符号整数的取模运算,但是最后的正负号取决于第一个操作数。
#include<stdio.h>
#include<math.h>
int main()
{
printf("%d\n", -10 % 3);
printf("%d\n", 10 % -3);
printf("%d\n", -10 % -3);
}
输出结果为:
二、移位操作符
在讲移位操作符之前,先简单讲讲数据在内存中的存储。整数的二进制表示方法有补码,原码,反码三种。
有符号整数的三种表示方法由符号位和数值位两部分组成。在二进制序列中,最高的以为被当作符号位,其余被当作数值位。符号位用"0"表示正,用"1"表示负。
正整数的原码、补码、反码都相同。负数的三种表示方法各不相同:
- 原码:直接将数值按照正负数的形式转换成二进制即可。
- 补码:将原码的符号位不变,其他位依次按位取反即可。
- 补码:反码+1可得到补码。
将补码按数值位取,反末尾加1可以得到原码。
int a = 1;//int型整数在内存中占4字节,也就是32bit
原码:00000000 00000000 00000000 00000001//最高位是符号位,正整数的符号位用‘0’表示
//正整数的原、反、补码都相同,所以无需转换int b = -1;
原码:10000000 00000000 00000000 00000001//最高位是符号位,负整数的符号位用‘1’表示
反码:11111111 11111111 11111111 11111110//原码符号位不变,数值位按位取反,得到反码
补码:11111111 11111111 11111111 11111111//反码+1,得到补码
在C语言中,移位操作符用于对二进制数进行左移或右移操作。这些操作符可以用来高效地实现乘法、除法、位掩码等操作。
<< >>
左移操作符(<<):
左移操作符将一个数的二进制表示向左移动指定的位数,并在右边填上0。左移操作通常相当于将数字乘以,其中 n 是移动的位数。
int a = 5; // 二进制表示为 0000 0101
int b = a << 2; // b 的值现在为 20,二进制表示为 0001 0100
右移操作符(>>):
右移操作符将一个数的二进制表示向右移动指定的位数。右移操作通常相当于将数字除以,其中n是移动的位数。有右移运算分为两种:
- 逻辑移位,左边用0填充,右边丢弃。
- 算术移位,左边用符号位数值填充,右边丢弃。
int a = 20; // 二进制表示为 0001 0100
int b = a >> 2; // b 的值现在为 5,二进制表示为 0000 0101
注意:
- 符号扩展:对于有符号整数,右移时最高位(符号位)会被复制以保持原来的符号(正数还是负数)。这意味着如果一个负数被右移,新的高位将被设置为1(表示负数)。
- 溢出问题:移位操作可能会导致溢出,尤其是在左移操作时,如果超过了整数的最大表示范围,结果将是未定义的。
- 位数限制:移位的位数不能超过整数的位宽。如果移位的位数超过了整数的位宽,结果也是未定义的。
三、位操作符
& | ^ ~
按位与操作符(&):
当&用作按位与操作符时,它会对两个操作数进行按位逻辑与操作。按位与操作符逐位比较两个操作数的补码形式,对应位置上的比特位只有都为1时,结果才为1,否则为0。
#include<stdio.h>
int main()
{
int m = 1;
int n = -1;
printf("%d\n", m & n);
//1的补码:00000000000000000000000000000001
//-1的补码:11111111111111111111111111111111
//1&-1的结果:00000000000000000000000000000001
//结果的补码是正整数,正整数的原、反、补码都相同,所以这也是结果的原码,转换成10进制就是1
}
按位或操作符(|):
当|用作按位或操作符时,它会对两个操作数进行按位逻辑或操作。按位或操作符逐位比较两个操作数的二进制表示,对应位置上的比特位只要有一个为1,结果就为1,否则为0。
#include<stdio.h>
int main()
{
int m = 1;
int n = -1;
printf("%d\n", m | n);
//1的补码:00000000000000000000000000000001
//-1的补码:11111111111111111111111111111111
//1|-1的结果:11111111111111111111111111111111
//对结果的补码进行转换得到其原码:10000000000000000000000000000001(转换成10进制就是-1)
}
按位异或操作符(^):
^操作符被称为按位异或操作符。它用于对两个整数进行按位异或操作。按位异或操作符逐位比较两个操作数的二进制表示,对应位置上的比特位只有不同才为1,相同则为0。
#include<stdio.h>
int main()
{
int m = 1;
int n = -1;
printf("%d\n", m^n);
//1的补码:00000000000000000000000000000001
//-1的补码:11111111111111111111111111111111
//1^-1的结果:11111111111111111111111111111110
//对结果的补码进行转换得到其原码:10000000000000000000000000000010(转换成10进制就是-2)
}
按位取反操作符(~):
~操作符用于对整数的二进制表示中的每一位进行取反操作,即将1变为0,将0变为1。
#include<stdio.h>
int main()
{
printf("%d\n", ~0);
//0的补码:00000000000000000000000000000000
//~0的结果:11111111111111111111111111111111
//对结果的补码进行转换得到其原码:10000000000000000000000000000001(转换成10进制就是-1)
;
}
四、赋值操作符
在变量创建的时候给它一个初始值叫做初始化,在变量创建好以后,再给一个值叫做赋值。
#include<stdio.h>
int main()
{
int a = 0;//初始化
int b;
b = 1;//赋值
a = b = 1;//赋值操作符可以连续使用
}
五、复合赋值符
+= -= *= /= %= >>= <<= &= |= ^=
#include<stdio.h>
int main()
{
int a = 0;
a += 10;//复合赋值符写法
a = a + 10;//展开写法
//其它的复合赋值符类似
}
六、单目操作符
! ~ ++ -- + - * / & sizeof (类型)
这里讲那些没有讲过的以及与前面意义不同的单目运算符。
自增操作符(++):
自增操作符分为前置++与后置++,它们共同的效果是使操作数的数值+1,他们的区别是前置++是先+1,后使用;后置++是先使用,后+1。
#include <stdio.h>
int main()
{
int a = 0;
int b = ++a;//先使a+1,再赋值给b
printf("a=%d b=%d\n", a, b);
int c = 0;
int d = c++;//先赋值给d,再自增
printf("c=%d d=%d\n", c, d);
return 0;
}
自减操作符(--):
与自增操作符的两种用法的区别类似 ,前置- -是先-1,后使用;后置- -是先使用,后-1。
#include <stdio.h>
int main()
{
int a = 1;
int b = --a;//先自减,再赋值给b
printf("a=%d b=%d\n", a, b);
int c = 1;
int d = c--;//先赋值给d,再自减
printf("c=%d d=%d\n", c, d);
return 0;
}
取地址操作符(&):
取地址操作符用于获取一个变量或表达式的内存地址。取地址操作符通常与指针一起使用,用于存储和操作变量的地址。
#include <stdio.h>
int main()
{
int a = 1;
int* p = &a;//创建一个指针变量存储a的地址
printf("%p", p);
return 0;
}
解引用操作符(*):
它用于访问通过指针指向的变量或内存区域的内容。解引用操作符*通常与指针一起使用,以获取指针所指向的数据。
#include <stdio.h>
int main()
{
int a = 1;
int* p = &a;
*p = 20;//通过解引用操作符修改指向的数据
printf("%d", *p);
return 0;
}
sizeof操作符:
sizeof是一个操作符,用于获取数据类型或变量的大小(以字节为单位)。它可以用于确定各种数据类型、结构体、联合体、数组等的大小。sizeof操作符返回的结果是一个无符号整数类型的值。
#include <stdio.h>
int main()
{
int a = 5;
double b = 0.0;
printf("%zd\n", sizeof a);//int类型的大小是4字节
printf("%zd\n", sizeof b);//double类型的大小是8字节
printf("%zd\n", sizeof(a = 8 + 5));//把8+3的值赋给a,表达式的最终结果就是a,a的类型是int
printf("%zd\n", sizeof(int));//int类型的大小是4字节
printf("%zd\n", sizeof(char));//char类型的大小是1字节
printf("%zd\n", sizeof(double));//double类型的大小是8字节
return 0;
}
注意,printf函数中的占位符%zd会根据CPU的不同,判断数据的长度。另外,sizeof后的操作数如果不是数据类,而是表达式或者变量时,是可以省略掉后边的括号的。但是由于操作符的优先级问题,有时省略掉括号会出现问题。
七、强制类型转换
强制类型转换是将一种数据类型强制转换为另一种数据类型的方法。
int a = 4.5;//a是整型变量,4.5是double类型,编译器会报错
int a = (int)4.5;//将4.5强制转换为int类型,这种强制转换只取整数部分
八、关系操作符
> >= < <= != ==
关系操作符用于比较两个值,并返回一个布尔结果(即true或false)。在C语言中,通常使用整数0表示false,非零值表示true。关系操作符的结果通常用于条件判断、循环和其他逻辑控制结构中。这里并没有很多东西可讲,只是要注意的是,注意=与==的混淆使用。
九、逻辑操作符
&& ||
逻辑与操作符&&的作用是将两个或多个条件表达式连接起来,所有条件都必须为真,最终结果才为真。如果有一个条件为假,则结果为假。
逻辑或操作符||的作用是将两个或多个条件表达式连接起来,只要有一个条件为真,最终结果就为真。只有当所有条件都为假时,结果才为假。
#include <stdio.h>
int main()
{
int a = 2;
int b = 0;
printf("%d\n", (a > 1) && (a > b));//&&两侧的表达式都为真,则整个逻辑表达式为真,用1表示
printf("%d\n", (a < 1) && (a > b));//&&左侧的表达式为假,则整个逻辑表达式为假,用0表示
printf("%d\n", (a < 1) || (a > b));//||右侧的表达式为真,则整个逻辑表达式为真,用1表示
printf("%d\n", (a > 3) || (a < b));//||两侧的表达式都为假,则整个逻辑表达式为假,用0表示
return 0;
}
十、条件操作符
exp1 ? exp2 : exp3
也叫三目操作符,它的运算逻辑是如果exp1为真,则exp2的运算结果为表达式的结果,否则exp3是表达式的结果。
#include <stdio.h>
int main()
{
int a = 1;
int b = 0;
int c = a > b ? a : b;//a>b为真,a的值是整个表达式的结果
int d = a < b ? (a + b) : 2;//a<b为假,2是整个表达式的结果
printf("%d\n", c);
printf("%d\n", d);
return 0;
}
十一、逗号表达式
exp1,exp2,....,expn
逗号表达式的效果就是从左向右依次执行表达式,但整个表达式的结果是最后⼀个表达式的结果。
#include <stdio.h>
int main()
{
int a = 2;
int b = 0;
int c = (b = b + a, a > 0, a = a * 3);
int d = (b = b + a, a > 0);
printf("%d %d %d %d",a, b, c, d);//会输出什么呢?
}
十二、下标引用、函数调用和结构成员
下标引用操作符([]):
它的操作数是一个数组名+一个索引值
int arr[10];
arr[9] = 10;
//[]的操作数是arr和9
函数调用操作符(()):
#include <stdio.h>
void test1()
{
printf("haha\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main()
{
test1();//使()作为函数调用操作符。
test2("hello world");//使用()作为函数调用操作符。
return 0;
}
结构成员访问操作符:
. ->
结构体成员可以通过操作符"."直接访问,使用方式为结构体变量.成员。
#include <stdio.h>
struct stu
{
int age;
int sex;
};
int main()
{
struct stu s;
s.age = 10;
s.sex = 1;
}
#include <stdio.h>
struct stu
{
int age;
int sex;
};
int main()
{
struct stu s;
s.age = 10;
s.sex = 1;
struct stu* p = &s;
p->age = 20;
printf("%d", p->age);
}