算术操作符:+
-
*
/
%
移位操作符:<<
>>
位操作符:&
|
^
赋值操作符:=
+=
-=
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
+
-
*
/
%
注意/
操作符:
对于整型的除法运算结果依然是整数
对于浮点型的除法是, 如果有一个是浮点型, 结果就是浮点型
#include
int main()
{
int a = 7 % 2;//7/2...1
int b = 7 / 2;
printf("%d\n", a);//1
printf("%d\n", b);//1
return 0;
}
//% 取模操作符的两端必须是整数
移位操作符: 移动的是二进制位
<<
左移操作符
>>
右移操作符
注: 移位操作符的操作数只能是 整数
关于计算机中的 进制
对于整数的二进制表示有3种: 原码, 反码, 补码.
如果是正的整数, 它的原码、反码、补码要相同
如果是负的整数, 它的原码、反码、补码是要计算的.
比如数字7.
0000 0000 0000 0000 0000 0000 0000 0111 - 原码
0000 0000 0000 0000 0000 0000 0000 0111 - 反码
0000 0000 0000 0000 0000 0000 0000 0111 - 补码
数字-7
1000 0000 0000 0000 0000 0000 0000 0111 - 原码
1111 1111 1111 1111 1111 1111 1111 1000 - 反码( 原码的符号位不变,其他位按位取反就是反码)
1111 1111 1111 1111 1111 1111 1111 1001 - 补码( 反码+1就是补码)
整数在内存中存储的是补码.
int main()
{
int a = 7;
int b = a << 1;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
运行结果:
下面是a为负数的情形, 以int a = -7;
为例:
注: 移位操作符只能用在整数.
右移操作符分为两种: 算术移位和逻辑移位.
算术移位: 右边丢弃, 左边补原符号位;
逻辑移位: 右边丢弃, 左边补0.
int main()
{
int a = 7;
int b = a >> 1;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
VS2019采用算术右移.
下面是a为负数的情形, 以int a = -7;
为例:
注意: 对于移位运算符,不要移动负数位,这个是标准未定义的。
&
- 按(2进制)位与
|
- 按(2进制)位或
^
- 按(2进制)位异或 - 相同为0,相异为1
int main()
{
int a = 3;
int b = -5;
int c = a & b;
int d = a | b;
int e = a ^ b;
printf("c=%d\n", c);
printf("d=%d\n", d);
printf("e=%d\n", e);
return 0;
}
0000 0000 0000 0000 0000 0000 0000 0011 - 3的补码
1000 0000 0000 0000 0000 0000 0000 0101 - -5的原码
1111 1111 1111 1111 1111 1111 1111 1010 - -5的反码
1111 1111 1111 1111 1111 1111 1111 1011 - -5的补码
于是:
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 - &运算
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 - |运算 - 补码
1111 1111 1111 1111 1111 1111 1111 1010 - 反码
1000 0000 0000 0000 0000 0000 0000 0101 - 原码
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 - ^运算 - 补码
1111 1111 1111 1111 1111 1111 1111 0111 - 反码
1000 0000 0000 0000 0000 0000 0000 1000 - 原码
不能创建临时变量(第三个变量),实现两个数的交换。
原来我们允许创建临时变量的时候可以这么实现两个数的交换:
int main()
{
int a = 3;
int b = 5;
int c = 0;
printf("交换前:a=%d b=%d\n", a, b);
c = a;
a = b;
b = c;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
那么要满足题意的代码如下:
int main()
{
int a = 3;
int b = 5;
//这种方法会有溢出的问题
printf("交换前:a=%d b=%d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
但是, 理论上讲, 这种方法会出现问题.
a是int类型, 而int类型又有它自己的最大值, b也是int类型, 有它自己的最大值.
那么它们就有自己的上限.
假设a放的值非常大, 但是没有超出它的最大值, b也是一样,
此时a+b的和就会超出最大值, 再赋给a就有可能不是真正的a了, a就会栈溢出, 导致后续的运算结果出错.
我们来看最没有问题的代码:
int main()
{
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a ^ b;//a=3^5
b = a ^ b;//3^5^5 --> b=3
a = a ^ b;//3^5^3 --> a=5
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
注意这里使用了异或运算符的特点.
3^3 = 0 -> a^a=0 任何数和它本身异或, 结果是0.
011
011
000
0^5=5 -> 0^a = a 任何数和0异或都是它本身.
000
101
101
3^3^5 = 5
3^5^3 = 5
异或操作符支持交换律
011
101
3^3 = 110
011
3^3^5 = 101
求一个整数存储在内存中的二进制中1的个数
翻译题意就是: 求补码的二进制中1的个数
比如int a = 3;
它的二进制为00000000000000000000000000000011
我们进行a&1操作
00000000000000000000000000000011
00000000000000000000000000000001
结果
00000000000000000000000000000001
可以知道, 可以通过&1的方式来得到一个数的二进制位的最后一位是什么.
3&1得到的结果是1, 说明他的最低位是1.
那么当我们统计完这最后一位是什么之后, 我们可以通过右移来让每一位都来到最低位, 然后再通过&1的方式来得到一个数的二进制位的最后一位是什么, 这样就能得到二进制位1的个数
>> <<
#include
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
for(i=0; i<32; i++)
{
if( num & (1 << i) )
count++;
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}
赋值操作符可以更新变量的数据, 并且它是可以连续使用的, 但是不建议写连续赋值的代码.
int b = 0;
b = 20;//赋值
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
int a = 3;
a = a + 5;
a += 5;
a = a >> 1;
a >>= 1;
在C语言中,单目操作符是只对一个操作数进行操作的操作符,而双目操作符是对两个操作数进行操作的操作符。
举个例子,单目操作符包括取地址操作符&
(用于获取变量的地址)、递增操作符++
(用于增加变量的值)、递减操作符--
(用于减少变量的值)等等。这些操作符只需要一个操作数。
双目操作符包括加法操作符+
、减法操作符-
、乘法操作符*
、除法操作符/
等等。这些操作符需要两个操作数来执行相应的操作。
例如,a++
是一个单目操作符,它将变量a
的值递增1。而a + b
是一个双目操作符,它将变量a
和b
的值相加。
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
//! 逻辑反操作
//C语言中0表示假,非0表示真
int flag = 3;
//flag为真,进入if
if (flag)
{}
//flag为假,进入if
if(!flag)
{}
//& 取地址
int a = 10;
printf("%p\n", &a);
int* p = &a;//p就是指针变量
//sizeof 操作数的类型长度(以字节为单位)
//计算的是变量所占内存空间的大小,单位是字节
//计算类型所创建的变量占据空间的大小,单位是字节
int arr[5] = {0};
printf("%d\n", sizeof(arr));//20
int a = 10;
//int n = sizeof(a);//计算的是a所占内存的大小,单位是字节
int n = sizeof(int);
printf("n=%d\n", n);//4
//~ 对一个数的二进制按位取反
int a = 0;
//0000 0000 0000 0000 0000 0000 0000 0000 - 补码
//1111 1111 1111 1111 1111 1111 1111 1111 -> ~a
//1111 1111 1111 1111 1111 1111 1111 1110 - -1得到反码
//1000 0000 0000 0000 0000 0000 0000 0001 - 再按位取反得到原码
//-1
printf("%d\n", ~a);//-1
int b = 3;
//0000 0000 0000 0000 0000 0000 0000 0011
//1111 1111 1111 1111 1111 1111 1111 1100 - 补码
//1111 1111 1111 1111 1111 1111 1111 1011 - 反码
//1000 0000 0000 0000 0000 0000 0000 0100 - 原码
//-4
printf("%d\n", ~b);
//* 间接访问操作符(解引用操作符)
int a = 10;
int* p = &a;
*p = 20;
printf("%d\n", a);
//(类型) 强制类型转换
int a = (int)3.14;
int main()
{
int a = 0;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//ok
//printf("%d\n", sizeof int);//err
return 0;
}
sizeof是操作符,不是函数
strlen是库函数,是用来求字符串长度
关系操作符是用于比较两个值之间的关系的C语言操作符。它们通常返回一个布尔值(真或假),表示比较的结果。
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
注意: 在编程的过程中== 和=不小心写错,导致的错误。
int a = 5;
int b = 10;
if (a < b) {
// 执行这个代码块,因为a小于b
printf("a小于b\n");
} else {
// 如果条件不成立,执行这个代码块
printf("a不小于b\n");
}
在上述示例中,<
是一个关系操作符,用于比较变量a
和b
的值,根据比较结果执行不同的代码块。
if ("abc" == "abcdef")//这样写是在比较2个字符串的首字符的地址
{}
//两个字符串比较相等应该使用strcmp
逻辑操作符是用于在布尔表达式中执行逻辑运算的C语言操作符。它们通常用于组合多个条件以生成更复杂的条件。
&&
(逻辑与,AND):当两个条件都为真时,返回真(1),否则返回假(0)。||
(逻辑或,OR):当至少一个条件为真时,返回真(1),只有两个条件都为假时才返回假(0)。!
(逻辑非,NOT):对一个条件取反,如果条件为真则返回假(0),如果条件为假则返回真(1)。int x = 5;
int y = 10;
if (x < 10 && y > 5) {
// 执行这个代码块,因为x小于10且y大于5
printf("x小于10且y大于5\n");
}
if (x > 10 || y < 5) {
// 执行这个代码块,因为x大于10或y小于5
printf("x大于10或y小于5\n");
}
if (!(x < 10)) {
// 执行这个代码块,因为对x < 10 取反得到假
printf("x不小于10\n");
}
在上述示例中,&&
和||
是逻辑操作符,用于组合不同的条件,以便在if
语句中执行不同的代码块。而!
操作符则用于取反一个条件。
&&
左边为假,右边就不计算了
||
左边为真,右边就不计算了
条件操作符,也称为三元运算符,是C语言中的一种特殊操作符,用于在一个表达式中根据条件的真假选择不同的值。条件操作符具有以下语法形式:
condition ? expression_if_true : expression_if_false
condition
是一个条件表达式,如果它的值为真(非零),则返回 expression_if_true
的值,否则返回 expression_if_false
的值。
例如:
int x = 10;
int y = 20;
int max;
// 使用条件操作符选择最大值
max = (x > y) ? x : y;
// 等效的 if-else 语句
/*
if (x > y) {
max = x;
} else {
max = y;
}
*/
printf("最大值是:%d\n", max);
在上述示例中,条件操作符 (x > y) ? x : y
用于比较 x
和 y
的值,如果 x
大于 y
,则选择 x
作为最大值,否则选择 y
。这是一种紧凑的方式来根据条件选择不同的值。
逗号表达式是C语言中的一种特殊表达式,它允许你在一个表达式中使用逗号 ,
来分隔多个子表达式,并按顺序依次计算这些子表达式,最终返回最后一个子表达式的值。逗号表达式的一般形式如下:
expression1, expression2, expression3, ..., expressionN
,
用于分隔不同的子表达式。逗号表达式通常用于一些特定的情况,例如在 for
循环的表达式部分,或者在需要在一个语句中执行多个操作并返回最后一个操作结果的情况下。
以下是一个示例:
int x = 5, y = 10, z;
z = (x++, y++, x + y); // 逗号表达式,计算x++,然后y++,最后计算x + y,将结果赋给z
printf("x = %d, y = %d, z = %d\n", x, y, z);
在上述示例中,逗号表达式 (x++, y++, x + y)
先计算 x++
和 y++
,然后计算 x + y
,最后将结果赋给变量 z
。在输出中,x
和 y
的值都增加了,而 z
的值是 x + y
的结果。
下标引用操作符是用于访问数组和其他类似数据结构的元素的操作符。在C语言中,下标引用操作符采用方括号 []
来实现。它允许您通过指定元素的索引来访问数组中的特定元素。
下标引用操作符的一般形式如下:
array[index]
array
是数组的名称或指向数组的指针。index
是要访问的元素的索引,通常是一个整数表达式。例如,考虑以下示例:
int numbers[5] = {1, 2, 3, 4, 5};
// 使用下标引用操作符访问数组元素
int thirdNumber = numbers[2]; // 访问索引为2的元素,即第三个元素
在上述示例中,numbers[2]
使用下标引用操作符访问了数组 numbers
中索引为2的元素,即值为3的元素。这样,thirdNumber
变量就被赋值为3。
需要注意的是,C语言中的数组索引通常从0开始,因此第一个元素的索引是0,第二个元素的索引是1,以此类推。
函数调用操作符是一种特殊的操作符,用于调用函数并传递参数给它。在C语言中,函数调用操作符采用圆括号 ()
来表示,用于标识函数的名称以及传递给函数的参数列表。
函数调用操作符的一般形式如下:
function_name(arguments)
function_name
是要调用的函数的名称。arguments
是传递给函数的参数列表,可以是零个或多个参数,用逗号 ,
分隔。例如,考虑以下示例:
#include
// 声明一个函数
int add(int a, int b) {
return a + b;
}
int main() {
int result;
// 调用函数add,并传递两个参数
result = add(5, 3);
printf("5 + 3 = %d\n", result);
return 0;
}
在上述示例中,函数调用操作符 (5, 3)
用于调用 add
函数,并传递两个参数 5 和 3 给该函数。add
函数计算它们的和,并将结果返回给 result
变量。然后,printf
函数用于打印结果。
函数调用是C程序中的重要部分,它使您能够执行各种任务,包括数据处理、输入输出、数学计算等。函数的名称和参数列表定义了您要执行的操作。
要访问一个结构的成员,需要使用成员访问操作符 .
(点号)来引用结构变量的成员名称。结构成员访问操作符的一般形式如下:
structure_variable.member_name
structure_variable
是包含要访问成员的结构变量的名称。member_name
是要访问的结构成员的名称。以下是一个示例:
#include
// 定义一个结构
struct Point {
int x;
int y;
};
int main() {
// 声明一个结构变量并初始化
struct Point p1;
p1.x = 10;
p1.y = 5;
// 访问结构成员并打印它们的值
printf("p1.x = %d\n", p1.x);
printf("p1.y = %d\n", p1.y);
return 0;
}
在上述示例中,我们首先定义了一个名为 Point
的结构,该结构有两个成员 x
和 y
。然后,在 main
函数中,我们声明了一个名为 p1
的结构变量,并分别使用成员访问操作符 .
来设置和访问结构的成员 x
和 y
的值。
通过这种方式,可以有效地组织和操作数据,将相关的数据项存储在一个结构中,并使用成员访问操作符来访问它们。这在构建复杂的数据结构和数据记录时非常有用。
访问一个结构的成员时,还可以使用箭头操作符 ->
。这个操作符通常用于访问指向结构体的指针的成员。结构体的成员可以是变量、其他结构体或其他数据类型。
下面是箭头操作符的一般形式:
pointer_to_structure->member
pointer_to_structure
是指向结构体的指针。member
是要访问的结构体成员的名称。示例代码:
#include
// 定义一个结构体
struct Point {
int x;
int y;
};
int main() {
// 创建一个Point类型的结构体变量
struct Point myPoint;
myPoint.x = 10;
myPoint.y = 20;
// 创建指向结构体的指针
struct Point *ptr = &myPoint;
// 使用箭头操作符访问结构体的成员
printf("x坐标:%d\n", ptr->x); // 输出:x坐标:10
printf("y坐标:%d\n", ptr->y); // 输出:y坐标:20
return 0;
}
在上述示例中,我们定义了一个名为 Point
的结构体,其中包含 x
和 y
两个成员。然后,我们创建了一个 Point
类型的结构体变量 myPoint
和一个指向结构体的指针 ptr
。使用箭头操作符 ptr->x
和 ptr->y
可以访问结构体 myPoint
的成员。