文章目录
二、变量与数据
Covers:变量与常量、类型与运算符
变量与常量
懒得废话:
- C# 对声明和定义不作区分(在 C++ 中定义指发生了内存分配),声明时就发生了定义;
- 允许变量先定义后初始化,用
TYPE VARNAME;
定义变量 VARNAME 但不初始化; - 等号
=
是 C# 的赋值运算符; - 常量修饰符为
const
。
类型策略
C# 是强类型语言,因此数据必须有明确的静态类型;完全面向对象意味着一切都是对象,派生自 System.Object
类,包括其内置类型也是如此。
C# 中类型只有两种:值 和 引用(实际上还有指针类型,但它仅在不安全模式下可用,我们不管它):
- 值类型的对象存放在栈上;
- 引用类型的对象在栈上存放一个“指针”(并非 C/C++ 中的指针,它自动解引用,行为上与 C++ 中的引用类似),所指向的值(或引用)对象则存放在“托管堆”中,由 CLR 自动管理;
- 所有值类型的对象都具有
.ToString()
方法,可以转换为字符串;用于打印时,也可使用标准格式字符串(见后文)。
简单类型
C++ 中用于存放常规数据的内置类型,在 C# 中称为“简单类型”,是 System 命名空间下提供的若干类,它们都是值类型;所有简单类型都有一个别名,如 int
、char
等,以保持 C++ 风格的变量声明语法。
下文将一口气介绍多数常用的简单类型。
整型
整型就是整数,C# 中默认整型数据为 int 类;System 命名空间提供的所有整型类包括:
语法(别名) | 占用空间 | 取值范围 |
---|---|---|
int | 4 B = 32 bit | − 2 31 ~ 2 31 − 1 -2^{31} ~ 2^{31}-1 −231~231−1 |
uint | 4 B = 32 bit | 0 ~ 2 32 − 1 0 ~ 2^{32}-1 0~232−1 |
sbyte | 1 B = 8 bit | − 2 7 ~ 2 7 − 1 -2^{7} ~ 2^{7}-1 −27~27−1 |
byte | 1 B = 8 bit | 0 ~ 2 8 − 1 0 ~ 2^{8}-1 0~28−1 |
short | 2 B = 16 bit | − 2 15 ~ 2 15 − 1 -2^{15} ~ 2^{15}-1 −215~215−1 |
ushort | 2 B = 16 bit | 0 ~ 2 16 − 1 0 ~ 2^{16}-1 0~216−1 |
long | 8 B = 64 bit | − 2 63 ~ 2 63 − 1 -2^{63} ~ 2^{63}-1 −263~263−1 |
ulong | 8 B = 64 bit | 0 ~ 2 64 − 1 0 ~ 2^{64}-1 0~264−1 |
char | 2 B = 16 bit | (Unicode) |
C# 中,不能将整型隐式转换到 char(单字符)型,char 专用于存储一个字符数据。
浮点型
浮点型就是实数,C# 中默认浮点型数据为 double 类;System 命名空间提供的所有浮点型类包括:
语法(别名) | 占用空间 | 小数精度 |
---|---|---|
double 、后缀 d | 8 B = 64 bit | 15~16 位 |
float 、后缀 f | 4 B = 32 bit | 7 位 |
decimal 、后缀 m | 16 B = 128 bit | 28~29 位 |
**注意:**C# 中,十进制实型 decimal 与两种浮点型之间没有隐式和强制类型转换,不可同时使用在一个表达式中。
布尔型
布尔型就是逻辑量,别名为 bool
,取值只能是 true
或 false
之一,可以直接向控制台打印。
可空类型
C# 提供了一种特殊的值类型,即 可空类型(nullable type),它依附于一个主类型而存在,允许该类型的取值为 null
(表示“空、未赋值、无效”)。
在任何值类型右侧加一个问号 ?
作为其类型修饰,则表示基于该类型的可空类型,如 int?
表示“可空的 32 位整型”,其取值除 int 型的常规取值范围外,还可以取 null:
int? num = 65536;
Console.WriteLine(num);
num = null;
Console.WriteLine(num);
上面的代码输出一行“65536”和一个空行。
引用类型
C# 内置的引用类型包括对象类、动态类和字符串类。
对象类
System.Object
类是 C# 的创世类,它还有个别名叫 object
。对象类的对象(简称对象对象,听起来好奇怪)可以赋以任何其他类型的值。
给对象对象赋以其他类型的值,称为 装箱(boxing,没错你也可以翻译成拳击),在这个过程中发生了从等号右侧类型到对象类的隐式转换:
// Boxing example 1 (int converted to object):
object age = 19;
// Boxing example 2 (char converted to object):
char character = 'z';
object boxed = character;
从已装箱的对象对象中取回原来的值,称为 拆箱(unboxing),在这个过程中发生了逆向的显式转换,需要使用显式转换(见后文)来完成:
// Unboxing:
char another = (char) boxed;
对象类能够赋以任何类型的值,这并没有破坏 C# 的强类型性,因为它是一种引用类型,本质上只是一枚指针,对对象对象的类型检查发生在编译时。
动态类
.NET 4.0 引入的动态类 dynamic
是对对象类的一种特殊处理,它在编译后就是 System.Object 类的对象,但在编译期间不进行类型检查:
// Dynamic object:
dynamic name = Console.ReadLine();
// This is Ok:
name = 1.414;
在非必要时不要使用这种类型的引用。
字符串类
字符串类也是一种引用类型,可以赋值为任意长度的字符(char)的序列,为字符串对象赋值有两种方式:
// Assign to a string, method 1:
string name = "Karl Marx"
// Assign to a string, method 2:
string directory = @"C:\Dev\Program";
用 @
引导字符串时,将忽略其中的 转义字符。
类型转换
C# 的隐式类型转换 没有 C++ 那么复杂,它 只发生在安全(不发生窄化)的情况下,例如从 short 转化为 int;其他情况下需要进行显式转换。
强制类型转换
使用 类型转换运算符 (TYPE)
进行强制类型转换:
int num = 131072;
short narrow = (short) num;
Console.WriteLine("num = " + num);
Console.WriteLine("narrow = " + narrow);
上面的代码产生的输出:
num = 131072
narrow = 0
能够通过编译,但 narrow 中的数据是无效的。
ToType 方法
使用 C# 内置 System.Convert
类的“ToType”方法(Type 可以是各种内置类型,如 Double、Int64 等)来完成类型转换:
int number = Convert.ToInt32(2.71828);
上面的 ToInt32 转换是 四舍五入 的。
Console.WriteLine(Convert.ToDateTime("2021年4月14日 下午2:15"));
输出“2020/4/14 14:15:00”。
Parse 方法
内置类型的 Parse
方法可以从字符串解析出其他类型的数据,如:
bool flag = bool.Parse("true");
int number = int.Parse("233");
double pi = double.Parse("3.1416");
var time = DateTime.Parse("2021-4-14 PM 2:15");
自动类型推导
从 .NET 3.0(我们现在用的是 5.0)开始,可以使用隐式类型 var
,该类型并非弱类型,而是根据变量的初始化值,在编译时自动判断类型;使用隐式类型声明变量但不初始化,或初始化后又赋值为其他数据类型,都会导致编译错误:
var name = "Bug"; // Ok, "name" is inferred to be string
var gender; // Bad
name = 1.23; // Bad, "name" is already defined as string
实际编程中要不要使用隐式类型,取决于个人习惯或公司的代码风格规范。
运算符
C# 运算符分为 6 类:
- 算术运算符
- 关系运算符
- 布尔运算符
- 位运算符
- 赋值运算符
- 其他
算术运算符
- 四则运算:
+
、-
、*
、/
,除法的结果符合 C/C++ 的习惯:“3 / 2 返回 1,3.0 / 2 返回 1.5” - 模除:
%
,也就是取余 - 自增自减:
++
、--
,前后置的结果符合 C/C++ 的习惯:“a++ 返回 a 原值,++a 返回 a 新值”
关系运算符
- 检等运算:相等
==
,不等!=
- 不等运算:大于
>
,小于<
,不小于>=
,不大于<=
逻辑运算符
- 与:
&&
- 或:
||
- 非:
!
位运算符
- 位与:
&
- 位或:
|
- 按位异或:
^
- 按位取非:
~
- 移位:
<<
、>>
,空位补零
赋值运算符
- 简单赋值:
=
- 运算兼赋值:
+=
、-=
、*=
、/=
、%=
、&=
、|=
、^=
、<<=
、>>=
其他
-
括号:
(EXPR)
,将 EXPR 的运算优先级提高 -
三目运算:
BOOL_STATE ? IF_TRUE : IF_FALSE
-
取数据量:
sizeof(EXPR)
,返回字节数 -
强制类型转换:
(TYPE) EXPR
-
鉴型运算符:
EXPR is TYPE
,返回布尔值,反映表达式 EXPR 是否兼容于 TYPE(是否为 TYPE,或父类型是 TYPE),永不抛出异常 -
拆箱运算符:
EXPR as TYPE
,等价于EXPR is TYPE ? (TYPE) EXPR : (TYPE) null
,如果 EXPR 不兼容于 TYPE(类型不兼容或空引用),则返回 null,否则返回将 EXPR 强制转换为 TYPE 后的结果,其实就是我们前面讲过的拆箱。这里要注意 TYPE 必须为可空类型,因为 as 本身就暗示了表达式可能返回 null:int? num = 65536; object obj = num; Console.WriteLine(obj as int?);
-
空值合并运算:
EXPR ?? DEFAULT
,等价于EXPR == null ? DEFAULT : EXPR
,如果 EXPR 不为 null,则返回 EXPR,否则返回 DEFAULT
运算符优先级
C# 的运算符优先级与 C++ 基本一致,可参考 该页面。
T.B.C.