C# 运算符是用于执行各种操作的符号,例如算术运算、比较运算、逻辑运算等。它们是构建 C# 表达式的基础。以下是 C# 中常用的运算符类型:
算术运算符
C# 中的算术运算符用于执行基本的数学运算
下表显示了 C# 支持的所有算术运算符。假设变量 A 的值为 10,变量 B 的值为 20
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
– | 自减运算符,整数值减少 1 | A-- 将得到 9 |
1. 加法运算符 ( + )
用途:
-
用于将两个数值相加。
-
也用于字符串连接。
int a = 5;
int b = 3;
int sum = a + b; // sum 的值为 8
string str1 = "Hello";
string str2 = "World";
string message = str1 + " " + str2; // message 的值为 "Hello World"
double a = 3.14;
double b = 2.71;
double sum = a + b; // sum 的值为 5.85
float x = 1.5f;
float y = 2.5f;
float result = x + y; // result 的值为 4.0
特殊值处理:在处理浮点数运算的结果时,要注意 NaN 和 Infinity 等特殊值。
可以使用 double.IsNaN() 和 double.IsInfinity() 等方法来检查这些特殊值。
注意事项:
-
当操作数中有一个是字符串时,
+
运算符执行字符串连接。 -
对于浮点数,
+
运算符执行浮点数加法。
2. 减法运算符 ( - )
用途:
- 用于将两个数值相减。
int a = 10;
int b = 4;
int difference = a - b; // difference 的值为 6
注意事项:
- 对于浮点数,
-
运算符执行浮点数减法。
3. 乘法运算符 ( * )
用途:
- 用于将两个数值相乘。
int a = 6;
int b = 7;
int product = a * b; // product 的值为 42
注意事项:
- 对于浮点数,
*
运算符执行浮点数乘法。
4. 除法运算符 ( / )
用于将一个数值(被除数)除以另一个数值(除数)
用途:
- 用于将两个数值相除。
int a = 15;
int b = 3;
int quotient = a / b; // quotient 的值为 5
double x = 10.0;
double y = 3.0;
double result = x / y; // result 的值为 3.333...
注意事项:
-
如果两个操作数都是整数,则结果也是整数(截断小数部分)。
-
如果其中一个操作数是浮点数,则结果是浮点数。
-
除数不能为0,否则会引发 DivideByZeroException 异常。
5. 取模运算符 ( % )
取模运算符 %
用于计算两个整数相除的余数。它的操作数必须是整数。
被除数 % 除数
用途:
-
用于计算两个数值相除的余数。
-
判断奇偶
-
偶数的定义: 偶数是可以被 2 整除的整数,余数为 0。
-
奇数的定义: 奇数是被 2 除,余数为 1 的整数。
-
int a = 17;
int b = 5;
int remainder = a % b; // remainder 的值为 2
int a = 10;
int b = 3;
int remainder1 = a % b; // remainder1 的值为 1 (10 除以 3 的余数为 1)
int c = 25;
int d = 7;
int remainder2 = c % d; // remainder2 的值为 4 (25 除以 7 的余数为 4)
// 负数取模
int e = -10;
int f = 3;
int remainder3 = e % f; // remainder3 的值为 -1 (-10 除以 3 的余数为 -1)
int g = 10;
int h = -3;
int remainder4 = g % h; // remainder4 的值为 1 (10 除以 -3 的余数为 1)
int i = -10;
int j = -3;
int remainder5 = i % j; // remainder5 的值为 -1 (-10 除以 -3 的余数为 -1)
int k = 1;
int l = 5;
int remainder6 = k % l; // remainder6 的值为 1 (1 除以 5 的余数为 1)
int m = 10;
int n = 0;
// int remainder7 = m % n; // 这会引发 DivideByZeroException 异常
// 判断奇偶
int number1 = 10;
int number2 = 7;
if (number1 % 2 == 0)
{
Console.WriteLine($"{number1} 是偶数"); // 输出:10 是偶数
}
else
{
Console.WriteLine($"{number1} 是奇数");
}
if (number2 % 2 == 0)
{
Console.WriteLine($"{number2} 是偶数");
}
else
{
Console.WriteLine($"{number2} 是奇数"); // 输出:7 是奇数
}
注意事项:
-
取模运算符只能用于整数。
-
被除数不能为0,否则会引发 DivideByZeroException 异常。
-
在 C# 中,取模运算的结果的符号与被除数的符号相同。
-
当被除数小于除数时,余数就是被除数本身。
- 递增运算符 (++)
-
定义
-
++ 运算符将变量的值增加 1。
-
它可以作为**前缀运算符****(++变量****)或后缀运算符****(变量++)**使用。
-
-
前缀递增:
++变量
:先将变量的值增加 1,然后返回增加后的值。
int x = 5;
int y = ++x; // x 现在是 6,y 现在也是 6
- 后缀递增:
变量++
:先返回变量的原始值,然后将变量的值增加 1。
int a = 5;
int b = a++; // a 现在是 6,b 现在是 5
---
- 递减运算符 ( – )
-
定义:
-
– 运算符将变量的值减少 1。
-
它可以作为前缀运算符(– 变量)或**后缀运算符****(变量–)**使用。
-
-
前缀递减:
--变量
:先将变量的值减少 1,然后返回减少后的值。
int m = 5;
int n = --m; // m 现在是 4,n 现在也是 4
- 后缀递减:
变量--
:先返回变量的原始值,然后将变量的值减少 1。
int p = 5;
int q = p--; // p 现在是 4,q 现在是 5
示例和解释
int i = 0;
Console.WriteLine(i++); // 输出 0,然后 i 变为 1
Console.WriteLine(++i); // i 变为 2,然后输出 2
int j = 5;
int result1 = j++ + 10; // result1 为 15,j 变为 6
int result2 = ++j + 10; // j 变为 7,result2 为 17
Console.WriteLine($"i: {i}, j: {j}, result1: {result1}, result2: {result2}");
输出:
0
2
i: 2,
j: 7,
result1: 15,
result2: 17
注意事项
-
++
和--
运算符只能用于变量,不能用于常量或表达式。 -
在使用这些运算符时,要特别注意前缀和后缀形式的区别,因为它们在表达式中的行为不同。
-
使用时候要注意代码的可读性,如果过于复杂的嵌套,会使代码难以阅读和维护。
c = a++: 先将 a 赋值给 c,再对 a 进行自增运算。
c = ++a: 先将 a 进行自增运算,再将 a 赋值给 c 。
c = a–: 先将 a 赋值给 c,再对 a 进行自减运算。
c = --a: 先将 a 进行自减运算,再将 a 赋值给 c。
运算符优先级
-
乘法、除法和取模运算符的优先级高于加法和减法运算符。
-
可以使用括号
()
来改变运算顺序。
int result1 = 10 + 5 * 2; // result1 的值为 20 (先算乘法)
int result2 = (10 + 5) * 2; // result2 的值为 30 (先算括号里的加法)
---
类型转换
-
在进行算术运算时,如果操作数的类型不同,C# 会自动进行类型转换。
-
通常情况下,较小的类型会自动转换为较大的类型。
-
例如,
int
会自动转换为double
。
int a = 10;
double b = 3.5;
double result = a + b; // a 会自动转换为 double,result 的值为 13.5
---
Math.DivRem
-
Math.DivRem 方法用于同时计算整数除法的商和余数。
-
它比单独使用
/
和%
运算符更高效。
int a = 17;
int b = 5;
int quotient;
int remainder;
Math.DivRem(a, b, out quotient, out remainder);
Console.WriteLine($"商:{quotient},余数:{remainder}");
// 输出:商:3,余数:2
在 C# 中,++
(递增)和 --
(递减)是算术运算符,用于增加或减少变量的值。它们可以作为前缀或后缀运算符使用,这会影响它们在表达式中的行为。
关系运算符
下表显示了 C# 支持的所有关系运算符。假设变量 A 的值为 10,变量 B 的值为 20
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
1. 相等运算符 ( == )
用途:
- 用于检查两个操作数是否相等。
int a = 5;
int b = 5;
bool isEqual = (a == b); // isEqual 的值为 true
string str1 = "Hello";
string str2 = "hello";
bool isEqualString = (str1 == str2);
// isEqualString 的值为 false,因为字符串区分大小写
注意事项:
-
对于引用类型(如对象),
==
运算符比较的是对象的引用是否相同,而不是对象的内容是否相同。-
1. 值类型和引用类型:
-
值类型
-
值类型变量直接存储数据本身。
-
例如:
int
、double
、bool
等。 -
当将一个值类型变量赋值给另一个变量时,会复制数据。
-
每个变量都拥有自己的数据副本,修改一个变量不会影响另一个变量。
-
-
引用类型
-
引用类型变量存储的是数据的内存地址(引用)。
-
例如:
string
、object
、数组、类等。 -
当将一个引用类型变量赋值给另一个变量时,会复制引用(内存地址),而不是数据本身。
-
多个变量可能引用同一个内存地址,修改其中一个变量会影响其他变量。
-
-
-
2.
==
运算符的行为:-
对于值类型
==
运算符比较的是两个变量的值是否相等。
-
对于引用类型
-
==
运算符比较的是两个变量的引用(内存地址)是否相同。 -
也就是说,它检查两个变量是否指向内存中的同一个对象。
-
-
-
3. 举例说明:
-
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Person person1 = new Person { Name = "Alice", Age = 30 };
Person person2 = new Person { Name = "Alice", Age = 30 };
Person person3 = person1;
// person3 = person1;
// 这条语句确实是将 person1 的内存地址(也就是栈上的地址)赋值给了 person3。
// person1 和 person3 都是引用变量:
// 它们都存储了堆上对象的内存地址
// 这些变量本身存储在栈上。
// 赋值操作:
// person3 = person1; 这条语句复制了 person1 变量中存储的内存地址。
// 这个内存地址指向堆上 Person 对象的实际数据
// 因此,person3 变量现在也存储了相同的内存地址,指向堆上的同一个 Person 对象。
// 结果:
// person1 和 person3 现在都指向堆上的同一个对象
Console.WriteLine(person1 == person2); // 输出:False
Console.WriteLine(person1 == person3); // 输出:True
person1 == person2 的结果为 False
虽然 person1 和 person2 具有相同的内容(Name 和 Age),但它们是内存中的两个不同对象。
person1和person2存储的是不同的内存地址。
因此,== 运算符比较的是它们的引用,结果为 False。
person1 == person3 的结果为 True:person3 = person1;
语句将 person1 的引用赋值给 person3。
因此,person1 和 person3 引用内存中的同一个对象。
person1和person3存储的是相同的内存地址。
因此,== 运算符比较的是它们的引用,结果为 True。
-
4. 比较引用类型的内容:
-
如果需要比较引用类型的内容是否相等,通常需要重写
Equals()
方法。 -
例如,
string
类型重写了Equals()
方法,因此可以使用string.Equals()
方法比较字符串的内容。
-
-
**要比较字符串的内容,可以使用****string.Equals()**方法。
2. 不相等运算符 ( != )
用途:
- 用于检查两个操作数是否不相等。
int a = 5;
int b = 3;
bool isNotEqual = (a != b); // isNotEqual 的值为 true
---
3. 大于运算符 ( > )
用途:
- 用于检查左操作数是否大于右操作数。
int a = 10;
int b = 5;
bool isGreater = (a > b); // isGreater 的值为 true
---
4. 小于运算符 ( < )
用途:
- 用于检查左操作数是否小于右操作数。
int a = 3;
int b = 7;
bool isLess = (a < b); // isLess 的值为 true
---
5. 大于等于运算符 ( >=)
用途:
- 用于检查左操作数是否大于或等于右操作数。
int a = 5;
int b = 5;
bool isGreaterOrEqual = (a >= b); // isGreaterOrEqual 的值为 true
---
6. 小于等于运算符 ( <= )
用途:
- 用于检查左操作数是否小于或等于右操作数。
int a = 3;
int b = 3;
bool isLessOrEqual = (a <= b); // isLessOrEqual 的值为 true
关系运算符的特点
-
关系运算符的结果都是布尔值(
true
或false
)。 -
关系运算符通常用于条件语句(如
if
语句)和循环语句(如while
语句)中,用于控制程序的流程。 -
关系运算符可以用于比较数值类型、字符类型和布尔类型。
-
对于引用类型,关系运算符比较的是引用,而不是对象的内容。
关系运算符的注意事项
-
在比较浮点数时,由于精度问题,不要直接使用
==
运算符。 -
应该使用一个小的误差范围来比较浮点数是否近似相等。
-
在比较字符串时,要注意字符串的大小写。
逻辑运算符
在 C# 中,逻辑运算符用于组合或反转布尔表达式,它们在条件语句和循环中起着至关重要的作用。
下表显示了 C# 支持的所有逻辑运算符。假设变量 A 为布尔值 true,变量 B 为布尔值 false,则:
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
1. 逻辑与运算符 ( && )
用途:
-
当且仅当两个操作数都为
true
时,结果才为true
。 -
也称为“短路与”运算符,即如果第一个操作数为
false
,则不会计算第二个操作数。
bool a = true;
bool b = false;
bool result1 = a && b; // result1 的值为 false
bool c = true;
bool d = true;
bool result2 = c && d; // result2 的值为 true
---
2. 逻辑或运算符 ( || )
用途:
-
当两个操作数中至少有一个为
true
时,结果为true
。 -
也称为“短路或”运算符,即如果第一个操作数为
true
,则不会计算第二个操作数。
bool a = true;
bool b = false;
bool result3 = a || b; // result3 的值为 true
bool c = false;
bool d = false;
bool result4 = c || d; // result4 的值为 false
---
3. 逻辑非运算符 ( ! )
用途:
-
用于反转操作数的布尔值。
-
如果操作数为
true
,则结果为false
;如果操作数为false
,则结果为true
。
bool a = true;
bool result5 = !a; // result5 的值为 false
bool b = false;
bool result6 = !b; // result6 的值为 true
---
4. 逻辑异或运算符 ( ^ )
用途:
-
当两个操作数中只有一个为
true
时,结果为true
。 -
如果两个操作数都为
true
或都为false
,则结果为false
。
bool a = true;
bool b = false;
bool result7 = a ^ b; // result7 的值为 true
bool c = true;
bool d = true;
bool result8 = c ^ d; // result8 的值为 false
bool e = false;
bool f = false;
bool result9 = e ^ f; // result9 的值为 false
逻辑运算符的特点
-
逻辑运算符的操作数和结果都是布尔值(
true
或false
)。 -
逻辑运算符通常用于组合多个条件,以创建更复杂的条件表达式。
-
短路运算符
&&
和||
可以提高代码的效率,因为它们可以避免不必要的计算。
逻辑运算符的注意事项
-
逻辑运算符的优先级 < 关系运算符。
-
可以使用括号**( )** 来改变运算顺序。
逻辑运算符的应用场景
-
**条件判断:**在
if
语句中,可以使用逻辑运算符组合多个条件。 -
**循环控制:**在
while
循环中,可以使用逻辑运算符控制循环的执行。 -
**数据验证:**可以使用逻辑运算符验证用户输入的数据是否满足多个条件。
位运算符
位运算符是编程中用于操作二进制位的运算符。它们直接作用于整数的二进制表示形式,执行逐位操作。
1. 按位与 ( & )
- 定义: 如果两个相应的二进制位都为 1,则结果为 1,否则为 0。
int a = 5; // 二进制:0101
int b = 3; // 二进制:0011
int result = a & b; // 二进制:0001 (十进制:1)
2. 按位或 ( | )
- 定义: 如果两个相应的二进制位中至少有一个为 1,则结果为 1,否则为 0。两个位都为0时,结果才为0
int a = 5; // 二进制:0101
int b = 3; // 二进制:0011
int result = a | b; // 二进制:0111 (十进制:7)
3. 按位异或 ( ^ )
- 定义: 如果两个相应的二进制位不同,则结果为 1,否则为 0。
int a = 5; // 二进制:0101
int b = 3; // 二进制:0011
int result = a ^ b; // 二进制:0110 (十进制:6)
4. 按位取反 ( ~ )
- 定义: 将每个二进制位取反,即 0 变为 1,1 变为 0。
int a = 5; // 二进制:0101
int result = ~a; // 二进制:1010 (十进制:-6)
- 注意: 由于整数在计算机中通常以补码形式存储,因此对正数取反会得到其负数的补码。
5. 左移 ( << )
- 定义: 将一个数的二进制位向左移动指定的位数。右侧空出的位用 0 填充。
int a = 5; // 二进制:0101
int result = a << 2; // 二进制:010100 (十进制:20)
x << y = x * 2^y
- 相当于: 将原数乘以 2 的移动位数次幂。
6. 右移 ( >> )
定义: 将一个数的二进制位向右移动指定的位数。
-
对于无符号数: 左侧空出的位用 0 填充。
-
对于有符号数: 左侧空出的位用符号位填充(正数用 0,负数用 1)。
int a = 20; // 二进制:010100
int result = a >> 2; // 二进制:000101 (十进制:5)
- 相当于: 将原数除以 2 的移动位数次幂。
应用场景
位运算符在以下场景中非常有用:
-
底层编程: 操作硬件寄存器、网络协议等。
-
性能优化: 位运算通常比算术运算更快。
-
标志和掩码: 使用二进制位来表示状态或选项。
-
数据加密和解密: 某些加密算法使用位运算。
条件运算符(?:)
-
定义
-
三元运算符
-
条件运算符是一种简洁的
if-else
语句的替代方案。 -
它允许您根据条件表达式的结果选择两个值中的一个。
-
语法:
条件表达式 ? 表达式1 : 表达式2
-
如果条件表达式为
true
,则返回表达式1
的结果;否则,返回表达式2
的结果。
-
int age = 20;
string message = (age >= 18) ? "成年人" : "未成年人";
Console.WriteLine(message); // 输出:成年人
int temperature = 25;
string weather = (temperature > 30) ? "炎热" : "舒适";
Console.WriteLine(weather); // 输出:舒适
-
应用场景:
-
简化简单的
if-else
语句。 -
在赋值语句中根据条件选择不同的值。
-
提高代码的简洁性。
-
空合并运算符
空合并运算符
??
是 C# 中一个非常有用的运算符,它提供了一种简洁的方式来处理可能为null
的变量。
1. 定义
-
空合并运算符
??
用于在变量为null
时提供一个默认值。 -
语法:
表达式1 ?? 表达式2
-
如果
表达式1
的值为null
,则返回表达式2
的结果;否则,返回表达式1
的结果。 -
简化
null
检查,并提供一种简洁的方式来为可能为null
的变量提供默认值。
2. 工作原理
?? 运算符的工作方式是:
-
首先,它评估左侧的
表达式1
。 -
如果
表达式1
的结果不是null
,则??
运算符返回表达式1
的结果,并且不再评估右侧的表达式2
。 -
如果
表达式1
的结果是null
,则??
运算符评估右侧的表达式2
,并返回表达式2
的结果。
// 由于 name 为 null,因此 displayName 被赋值为 "未知用户"
string name = null;
string displayName = name ?? "未知用户";
Console.WriteLine(displayName); // 输出:未知用户
// 由于nullableValue为null,因此value被赋值为0。
int? nullableValue = null;
int value = nullableValue ?? 0;
Console.WriteLine(value); // 输出:0
// greeting不为null,所以message被赋值为greeting的值“Hello”
string greeting = "Hello";
string message = greeting ?? "No greeting";
Console.WriteLine(message); // 输出:Hell
4. 应用场景
-
提供默认值: 当您需要为可能为
null
的变量提供默认值时,可以使用??
运算符。 -
简化
null
检查:??
运算符可以简化null
检查,使代码更简洁。 -
提高代码的健壮性: 通过使用
??
运算符,您可以确保变量始终具有一个有效的值,从而提高代码的健壮性。
5. 与空合并赋值运算符??=
的区别
-
C# 8.0 引入了空合并赋值运算符
??=
。 -
??=
运算符仅在左侧操作数为null
时才将右侧操作数的值赋给左侧操作数。 -
例如:
string text = null;
text ??= "default"; // text 现在是 "default"
---
赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
= | 按位或且赋值运算符 |
赋值运算符在编程中用于将值赋给变量。C# 提供了多种赋值运算符,包括简单的赋值和复合赋值。
简单赋值运算符 ( = )
-
定义
- 将右侧操作数的值赋给左侧操作数。
-
语法
变量 = 表达式
int x = 10;
string name = "John";
bool isTrue = true;
等号=
MyClass objectA=new MyClass();
MyClass objectB=objectA;
//引用变量的赋值 赋值操作完成后,两个变量都指向同一内存地址
objectA.val=10;
//给objectA.val赋值=10 由于objectB和objectA指向同一内存地址,所以ojbectB.val的值也为10
objectB.val=20;
//给objectB.val赋值=20 由于objectB和objectA指向同一内存地址,所以objectA.val的值也为20
复合 赋值运算符
复合赋值运算符将算术运算或位运算与赋值运算结合在一起,简化代码。
加法赋值 ( += )
-
将右侧操作数的值加到左侧操作数,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 + 表达式
int a = 5;
a += 3; // a 现在是 8 (相当于 a = a + 3)
减法赋值 ( -= )
-
从左侧操作数中减去右侧操作数的值,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 - 表达式
int b = 10;
b -= 4; // b 现在是 6 (相当于 b = b - 4)
乘法赋值 ( *= )
-
将左侧操作数乘以右侧操作数的值,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 * 表达式
int c = 2;
c *= 6; // c 现在是 12 (相当于 c = c * 6)
除法赋值 ( /= )
-
将左侧操作数除以右侧操作数的值,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 / 表达式
double d = 15;
d /= 3; // d 现在是 5 (相当于 d = d / 3)
取模赋值 ( %= )
-
将左侧操作数除以右侧操作数的余数赋给左侧操作数。
-
相当于:
变量 = 变量 % 表达式
int e = 17;
e %= 5; // e 现在是 2 (相当于 e = e % 5)
按位与赋值 ( &= )
-
对左侧和右侧操作数执行按位与运算,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 & 表达式
int f = 5; // 二进制:0101
f &= 3; // 二进制:0011,f 现在是 1 (相当于 f = f & 3)
按位或赋值 ( |= )
-
对左侧和右侧操作数执行按位或运算,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 | 表达式
int g = 5; // 二进制:0101
g |= 3; // 二进制:0011,g 现在是 7 (相当于 g = g | 3)
按位异或赋值 ( ^= )
-
对左侧和右侧操作数执行按位异或运算,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 ^ 表达式
int h = 5; // 二进制:0101
h ^= 3; // 二进制:0011,h 现在是 6 (相当于 h = h ^ 3)
-
左移赋值 ( <<= )
-
将左侧操作数左移右侧操作数指定的位数,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 << 表达式
-
int i = 2; // 二进制:0010
i <<= 3; // 二进制:10000,i 现在是 16 (相当于 i = i << 3)
-
右移赋值 ( >>= )
-
将左侧操作数右移右侧操作数指定的位数,并将结果赋给左侧操作数。
-
相当于:
变量 = 变量 >> 表达式
-
int j = 16; // 二进制:10000
j >>= 2; // 二进制:00100,j 现在是 4 (相当于 j = j >> 2)
---
其他运算符
sizeof() | 返回数据类型的大小。 | sizeof(int),将返回 4. |
---|---|---|
typeof() | 返回 class 的类型。 | typeof(StreamReader); |
& | 返回变量的地址。 | &a; 将得到变量的实际地址。 |
* | 变量的指针。 | *a; 将指向一个变量。 |
? : | 条件表达式 | 如果条件为真 ? 则为 X : 否则为 Y |
is | 判断对象是否为某一类型。 | If( Ford is Car) // 检查 Ford 是否是 Car 类的一个对象。 |
as | 强制转换,即使转换失败也不会抛出异常。 | Object obj = new StringReader(“Hello”); |
sizeof()
1. 定义
-
sizeof()
运算符用于获取值类型的大小(以字节为单位)。 -
它只能用于值类型,不能用于引用类型。
-
语法:
sizeof(类型)
2. 用法和示例
using System;
public class SizeofExample
{
public static void Main(string[] args)
{
Console.WriteLine($"sizeof(int) = {sizeof(int)}");
Console.WriteLine($"sizeof(short) = {sizeof(short)}");
Console.WriteLine($"sizeof(long) = {sizeof(long)}");
Console.WriteLine($"sizeof(float) = {sizeof(float)}");
Console.WriteLine($"sizeof(double) = {sizeof(double)}");
Console.WriteLine($"sizeof(char) = {sizeof(char)}");
Console.WriteLine($"sizeof(bool) = {sizeof(bool)}");
Console.WriteLine($"sizeof(decimal) = {sizeof(decimal)}");
Console.WriteLine($"sizeof(byte) = {sizeof(byte)}");
Console.WriteLine($"sizeof(sbyte) = {sizeof(sbyte)}");
}
}
sizeof(int) = 4
sizeof(short) = 2
sizeof(long) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(char) = 2
sizeof(bool) = 1
sizeof(decimal) = 16
sizeof(byte) = 1
sizeof(sbyte) = 1
-
此代码将输出各种值类型的大小。
-
请注意,
sizeof()
运算符只能在unsafe
上下文中使用,或者使用非托管代码。 -
即使使用
sizeof()
, 对基本数值类型,编译器在编译时就能处理,并不需要进入unsafe上下文。
3. 注意事项
-
sizeof()
运算符返回的大小可能因平台和编译器而异。 -
在 C# 中,
sizeof()
运算符通常用于与非托管代码交互或执行底层操作。
4. 为什么要注意数据类型大小
-
内存管理: 了解数据类型的大小有助于优化内存使用。
-
数据结构: 在设计数据结构时,了解数据类型的大小至关重要。
-
互操作性: 当与非托管代码或外部系统交互时,数据类型的大小很重要。
typeof()
用于获取类型的
System.Type
对象。System.Type
对象包含了类型的元数据信息,例如类型的名称、命名空间、基类、接口、属性、方法等。
定义:
-
typeof
运算符接受一个类型作为参数,并返回该类型的System.Type
对象。 -
语法:
typeof(类型)
用法及示例:
using System;
using System.IO;
public class TypeofExample
{
public static void Main(string[] args)
{
Type intType = typeof(int);
Type stringType = typeof(string);
Type streamReaderType = typeof(StreamReader);
Console.WriteLine($"intType: {intType}");
Console.WriteLine($"stringType: {stringType}");
Console.WriteLine($"streamReaderType: {streamReaderType}");
// 获取类型的元数据信息
Console.WriteLine($"StreamReader's FullName: {streamReaderType.FullName}");
Console.WriteLine($"StreamReader's Namespace: {streamReaderType.Namespace}");
Console.WriteLine($"StreamReader's BaseType: {streamReaderType.BaseType}");
}
}
intType: System.Int32
stringType: System.String
streamReaderType: System.IO.StreamReader
StreamReader's FullName: System.IO.StreamReader
StreamReader's Namespace: System.IO
StreamReader's BaseType: System.IO.TextReader
-
此代码演示了如何使用
typeof
运算符获取int
、string
和StreamReader
类型的System.Type
对象。 -
然后,它使用
System.Type
对象的属性来获取StreamReader
类型的元数据信息。
特点和应用场景
-
编译时运算符:
typeof
运算符在编译时求值,这意味着它不会在运行时产生额外的性能开销。 -
**获取类型信息:**typeof运算符主要用于获取类型的元数据信息,这些信息可以用于:
-
反射:在运行时动态地创建和调用类型的实例。
-
类型检查:确定一个对象的类型。
-
序列化和反序列化:将对象转换为字节流或从字节流还原对象。
-
-
泛型类型
typeof
运算符可以用于泛型类型。例如:typeof(List<int>)
。
-
开放泛型类型和封闭泛型类型
-
typeof(List<>)
是开放泛型类型。 -
typeof(List<int>)
是封闭泛型类型。
-
System.Type类:
-
System.Type
类是 .NET Framework(或 .NET Core/.NET 5+)中表示类型元数据的类。 -
它提供了许多属性和方法,用于获取类型的各种信息。
-
例如,
System.Type
类提供了以下属性:-
FullName
:获取类型的完整名称(包括命名空间)。 -
Namespace
:获取类型的命名空间。 -
BaseType
:获取类型的基类。 -
IsClass
:指示类型是否为类。 -
IsInterface
:指示类型是否为接口。 -
GetMethods()
:获取类型的所有公共方法。 -
GetProperties()
:获取类型的所有公共属性。
-
总结:
-
typeof
运算符是 C# 中一个非常有用的运算符,它允许你在编译时获取类型的元数据信息。 -
它通常与
System.Type
类一起使用,以执行反射、类型检查和序列化等操作。
& 运算符 (取地址运算符)
-
定义
-
&运算符返回变量的内存地址。
-
它只能应用于固定变量。
-
语法:&变量
-
unsafe
{
int a = 10;
int* ptr = &a; // ptr 现在包含变量 a 的内存地址
Console.WriteLine($"变量 a 的地址:{(long)ptr:X}"); // 输出地址的十六进制表示
}
-
在这个例子中,&a返回变量 a 的内存地址,并将其赋给指针变量 ptr。
-
为了输出地址,我们将指针转换为
long
,然后使用X
格式说明符将其格式化为十六进制。 -
应用场景:
-
获取变量的内存地址,以便传递给需要指针参数的函数。
-
在
unsafe
代码块中,直接操作内存。 -
与非托管代码交互
-
当 C# 代码需要调用使用 C 或 C++ 编写的非托管代码库时,通常需要传递变量的内存地址。
-
非托管代码使用指针来操作内存,因此需要使用
&
运算符获取变量的地址,并将其转换为指针类型。 -
例如,调用 Windows API 函数时,经常需要传递缓冲区(数组)的地址。
-
-
性能敏感的底层操作
-
在某些性能要求极高的场景下,例如图像处理、音视频处理或游戏开发,直接操作内存可以提高效率。
-
使用
&
运算符和指针,可以绕过 C# 的类型安全检查,直接访问和修改内存中的数据。
-
-
硬件编程
-
当 C# 代码需要与底层硬件交互时,例如驱动程序开发,通常需要直接操作硬件寄存器的内存地址。
-
& 运算符可以用于获取硬件寄存器的内存地址,以便进行读写操作。
-
-
固定数组
- 当在unsafe上下文中使用固定大小的数组时,&运算符可以被用来获得数组的第一个元素的地址。
-
unsafe
{
int value = 10;
int* pointer = &value; // 获取 value 的内存地址
Console.WriteLine($"Value 的地址:{(long)pointer:X}");
Console.WriteLine($"Pointer 指向的值:{*pointer}");
*pointer = 20; // 通过指针修改 value 的值
Console.WriteLine($"修改后的 Value:{value}");
}
为什么将内存地址转换为 long
-
地址表示
-
内存地址本质上是一个数值,用于标识内存中的一个特定位置。
-
在 64 位操作系统上,内存地址通常用 64 位整数表示,即
long
类型。 -
在 32 位操作系统上,内存地址通常用 32 位整数表示,即
int
类型。
-
-
输出和显示
-
为了方便查看和调试,通常将内存地址转换为十六进制字符串。
-
long
类型提供了足够的空间来存储 64 位内存地址,并可以使用X
格式说明符将其格式化为十六进制。 -
在32位系统上,也可以转换成uint。
-
-
指针运算:
-
在进行指针运算时,例如指针的加减,需要将指针转换为整数类型。
-
long
类型可以确保在 64 位系统上进行正确的指针运算。
-
* 运算符 (间接寻址运算符/解引用运算符)
-
*定义:运算符用于访问指针指向的内存位置的值。
-
它只能应用于指针变量。
-
语法:
*指针
unsafe
{
int a = 10;
int* ptr = &a;
Console.WriteLine($"指针 ptr 指向的值:{*ptr}"); // 输出 10
*ptr = 20; // 修改指针指向的内存位置的值
Console.WriteLine($"变量 a 的新值:{a}"); // 输出 20
}
-
在这个例子中,
*ptr
返回指针ptr
指向的内存位置的值(即变量a
的值)。 -
*ptr = 20;
修改了指针ptr
指向的内存位置的值,因此变量a
的值也发生了变化。 -
应用场景
-
访问指针指向的内存位置的值。
-
修改指针指向的内存位置的值。
-
在
unsafe
代码块中,直接操作内存。
-
-
注意事项
-
& 和 * 运算符只能在
unsafe
上下文中使用。 -
使用指针需要格外小心,因为错误的操作可能导致内存损坏、崩溃和安全漏洞。
-
指针操作通常用于与非托管代码交互或执行底层操作。
-
-
总结
-
&
运算符返回变量的内存地址。 -
*
运算符访问指针指向的内存位置的值。 -
这两个运算符在
unsafe
上下文中用于直接操作内存。
-
X"格式说明符用于将数值格式化为十六进制字符串。以下是关于"X"格式说明符的详细解释:
1. 定义
- "X"格式说明符用于将整数类型(如
int
、long
等)的数值转换为十六进制字符串表示形式。
它有两种形式:“X”(大写)和"x"(小写)。
-
“X”:输出大写十六进制字符(A-F)。
-
“x”:输出小写十六进制字符(a-f)。
2. 用法和示例
C#
using System;
public class HexFormatExample
{
public static void Main(string[] args)
{
int number = 255;
Console.WriteLine($"Number (Decimal): {number}");
Console.WriteLine($"Number (Hexadecimal, Uppercase): {number:X}");
Console.WriteLine($"Number (Hexadecimal, Lowercase): {number:x}");
long address = 0x1A2B3C4D5E6F7890;
Console.WriteLine($"Address (Hexadecimal): {address:X}");
}
}
在这个例子中:
-
{number:X}
将整数number
(255)格式化为大写十六进制字符串 “FF”。 -
{number:x}
将整数number
(255)格式化为小写十六进制字符串 “ff”。 -
{address:X}
将long类型的address变量格式化为大写十六进制字符串。 -
你还可以指定十六进制字符串的最小宽度。例如,
{number:X4}
将确保输出的十六进制字符串至少有 4 位,如果不足,则在前面填充零。
3. 应用场景
-
**内存地址显示:**在调试或底层编程中,经常需要显示内存地址,而十六进制是内存地址的常用表示形式。
-
**数据表示:**某些数据(例如,颜色值、硬件寄存器值)通常以十六进制表示。
-
**网络协议:**在网络协议中,某些数据字段也可能使用十六进制表示。
-
**文件格式:**在二进制文件格式中,经常使用十六进制表示数据。
4. 总结
-
"X"格式说明符提供了一种方便的方法,用于将数值转换为十六进制字符串。
-
它可以用于显示内存地址、表示数据和在各种编程场景中进行调试。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | ||
逻辑与 AND | && | 从左到右 |
逻辑或 OR | ||
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= | = |
逗号 | , | 从左到右 |
GetType
GetType()
是System.Object
类的一个方法,因此所有对象都继承了它。它用于在运行时获取对象的System.Type
对象,该对象包含了对象的元数据信息。
1. 定义
-
GetType()
方法返回一个System.Type
对象,该对象表示对象的运行时类型。 -
语法:
对象.GetType()
using System;
using System.IO;
public class GetTypeExample
{
public static void Main(string[] args)
{
string text = "Hello, world!";
int number = 42;
StreamReader reader = new StreamReader("example.txt");
Type textType = text.GetType();
Type numberType = number.GetType();
Type readerType = reader.GetType();
Console.WriteLine($"textType: {textType}");
Console.WriteLine($"numberType: {numberType}");
Console.WriteLine($"readerType: {readerType}");
// 获取类型的元数据信息
Console.WriteLine($"readerType's FullName: {readerType.FullName}");
Console.WriteLine($"readerType's Namespace: {readerType.Namespace}");
Console.WriteLine($"readerType's BaseType: {readerType.BaseType}");
reader.Close();
}
}
-
在这个例子中,
text.GetType()
返回string
类型的System.Type
对象,number.GetType()
返回int
类型的System.Type
对象,reader.GetType()
返回StreamReader
类型的System.Type
对象。 -
然后,它使用
System.Type
对象的属性来获取StreamReader
类型的元数据信息
**GetType() 与 typeof()**的区别
-
GetType()
**:**是一个实例方法,在运行时获取对象的类型。 -
适用于获取对象的运行时类型,特别是当对象的类型在编译时未知时。
-
typeof()
**:**是一个运算符,在编译时获取类型的System.Type
对象。 -
适用于获取已知类型的元数据信息。
-
语法:
typeof(类型)
应用场景
-
运行时类型检查
- 使用
GetType()
方法可以确定对象的运行时类型,并根据类型执行不同的操作。
- 使用
-
反射
GetType()
方法是反射的基础,它允许你在运行时获取对象的类型信息,并使用这些信息来动态地创建和调用类型的实例。
-
多态
- 在多态编程中,可以使用
GetType()
方法来确定对象的实际类型,并根据类型执行特定的操作。
- 在多态编程中,可以使用
-
动态加载类型
-
当你需要动态加载类型时,可以使用
GetType()
获取类型信息。
-
总结
-
GetType()
方法是一个运行时方法,用于获取对象的System.Type
对象。 -
它在运行时类型检查、反射和多态等场景中非常有用。
-
GetType()
和typeof()
都是获取System.Type
对象的方法,但它们的使用场景和求值时间不同。
as
和 is
都是用于类型检查和转换的运算符,但它们之间存在一些关键区别。
is 运算符
-
定义
-
is
运算符用于检查对象是否与给定类型兼容。 -
如果对象是指定类型或可以隐式转换为指定类型,则
is
运算符返回true
;否则,返回false
。 -
is
运算符不会引发异常。
-
object obj = "Hello";
if (obj is string)
{
Console.WriteLine("obj is a string.");
}
if (obj is int)
{
Console.WriteLine("obj is an integer."); // 不会执行
}
if (obj is object)
{
Console.WriteLine("obj is an object."); // 会执行
}
-
应用场景
-
在执行类型转换之前,使用
is
运算符可以避免InvalidCastException
异常。 -
在多态编程中,可以使用
is
运算符来确定对象的实际类型,并根据类型执行特定的操作。
-
as 运算符
-
定义
-
as
运算符用于将对象显式转换为指定类型。 -
如果对象可以转换为指定类型,则
as
运算符返回转换后的对象;否则,返回null
。 -
as
运算符不会引发异常。
-
object obj = "Hello";
string str = obj as string;
if (str != null)
{
Console.WriteLine($"str: {str}");
}
int? num = obj as int?; // num 为 null
if (num == null)
{
Console.WriteLine("obj cannot be converted to int.");
}
-
应用场景
-
as
运算符通常用于将对象转换为引用类型或可为空的值类型。 -
与
is
运算符结合使用,可以实现安全的类型转换。
-
as 和 is 的区别
-
is
运算符只进行类型检查,不进行类型转换;as
运算符进行类型转换,并在转换失败时返回null
。 -
is
运算符可以用于检查对象是否与任何类型兼容,包括值类型和引用类型;as
运算符主要用于引用类型和可为空的值类型。 -
is
运算符返回布尔值(true
或false
);as
运算符返回转换后的对象或null
。
using System;
using System.Collections;
public class AsIsExample
{
public static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add("Hello");
list.Add(42);
foreach (object item in list)
{
if (item is string)
{
string str = item as string;
Console.WriteLine($"String: {str}");
}
else if (item is int)
{
int num = (int)item; // 使用强制类型转换
Console.WriteLine($"Integer: {num}");
}
}
object obj1 = new object();
string str1 = obj1 as string; // str1 为 null
if (str1 == null)
{
Console.WriteLine("obj1 cannot be converted to string.");
}
object obj2 = "World";
if (obj2 is IDisposable)
{
IDisposable disposable = obj2 as IDisposable; // disposable 为 null,因为 string 没有实现 IDisposable
if (disposable != null)
{
disposable.Dispose();
}
}
if (obj2 is string)
{
string str2 = (string)obj2;
Console.WriteLine(str2);
}
}
}