变量
声明语法
datatype identifier;
如:
int i; //声明一个int类型的变量,但是在没有初始化之前编译器不允许使用该变量
同时声明多个
int a,b;//同时声明两个int类型的变量
错误示例
int x,bool y;
备注:如果在 一条语句中声明和初始化了多个变量,那么所有的变量都具有相同的数据类型;声明不同类型的变量,需要使用单独的语句
变量初始化
C#需要使用某个初始化对变量进行初始化,之后才能在操作中引用该变量,C#使用两种方法来确保变量在使用前进行了初始化
- 变量是类或者结构中的字段,如果没有显示初始化,创建这些变量时,默认值就是.NetFramework类型默认值(引用类型为null,值类型为0)
- 方法的局部变量在代码中必须被显式初始化,之后才能使用,否则编译器会产生异常
C#中实例化一个引用对象需要使用new关键字,把该引用指向存储在堆上的一个对象
类型推断
类型推断使用var关键字,编译器可以根据变量的初始化值""推断"变量的类型,规则如下
- 变量必须初始化,否则编译器就没有推断变量类型的依据
- 初始化器不能为空
- 初始化器必须放在表达式中
- 不能把初始化器设置为一个对象,除非在初始化器中创建了一个新对象
var name="wang";
常量
常量是其值在使用过程中不会发生的变量,在声明和初始化变量时,在变量前加上关键字const.就可以把该变量指定为一个常量
常量具有以下特点
- 常量必须在声明时初始化,指定其值后,就不能改写了
- 常量的值必须能在编译时用于计算,因此不能从一个变量中提取的值来初始化常量.如果需要,应该使用只读字段
- 常量总是静态的,但是不必(实际上,是不允许)在常量声明中包含修饰符static
const string name="wang";
预定义数据类型
C#中把数据类型分为两种
- 值类型
- 引用类型
从概念上看,其区别是值类型直接存储其值,而引用类型存储对值的引用;这两种数据类型存储在内存中的不同地方,值类型存储在堆栈中,而引用类型存储在托管堆中
string str=null; //引用类型可以初始化值为null,表示不引用任何对象
如果将引用设置为null,就不能对它调用任何非静态的成员函数或字段,这么做会在运行期间抛异常;CLR会定期删除不能访问的对象,把他们占用的内存返回给操作系统,通过垃圾回收器实现
C#中的基本预定义类型并没有内置于C#语言中,而是内置于.NET Framework中,比如声明一个int类型变量时,实际上是声明.NET结构System.Int32的一个实例,这表示在语法上,可以把所有的基本数据类型看成支持某些方法的类,C#中所有的数据类型都以与平台无关的方式定义,以备将来C#和.NET迁移到其他平台上
C#中的预定义值类型主要有:整型/浮点类型/布尔类型/字符类型
- 整型:C#支持8个预定义整数类型,int/short/byte/long/uint/ushort/sbyte/ulong,C#中所有的整数类型的变量都能被赋予十进制或十六进制的值,后者需要使用0X前缀;如果对一个整数是int/uint/long/ulong没有任何显式的声明,则该变量默认为int类型
- 浮点类型:float数据类型用于较小的浮点数,因为要求的精度较低,double数据类型比float数据类型大,提供的精度也大一倍;如果在代码中对某个非整数值硬编码,则编译器一般假定该变量是double,如果想指定该值为float,可以在其后加上字符F(或f);C#中提供了一种专用类型进行财务计算,这就是decimal类型,精度上来看:float<double<decimal.要注意的是:decimal类型不是基本类型,所以在计算是可能会存在装/拆箱操作,会有性能损失.要把数字指定为decimal类型,而不是double/float或整型,可以在数字后加上字符M(或m)
- 布尔类型:bool类型包含值true/false.bool值和整数值不能相互隐式转换
- 字符类型:为了保存单个字符的值,C#支持char数据类型,表示一个16位的(Unicode)字符,char类型的字面量是用单引号括起来的,如果把字符放在双引号中,编译器会把它们看成字符串,从而产生错误
C#中的预定义引用类型主要有:object/string
- object:C#中,object是所有类型的基类,所有的内置类型和用户自定义类型都是从它派生而来;可以用object引用绑定任何子类型的对象;object类型实现了一般用途的基本方法,包括Equals()/GetType()/ToString()等常用方法
- string:String对象被分配在堆上而不是栈上,因此当把一个字符串变量赋予另一个字符串时,会得到内存中对同一个字符串的两个引用,但是string类型与引用类型的常见行为有一些区别,字符串是不可改变的,修改其中一个字符串,就会创建一个全新的string对象,而另一个字符串不发生任何变化
static void Main(string[] args) { string a = "1"; string b = a; Console.WriteLine("a:" + a); Console.WriteLine("b:" + b); a = "2"; Console.WriteLine("a:" + a); Console.WriteLine("b:" + b); Console.ReadKey(); } 输出: a:1 b:1 a:2 b:1
流程控制语句
If/Switch/goto(已不推荐使用)
循环机制
for/while/do...while/foreach()可以迭代集合中的每一项,要使用集合对象,就必须支持IEnumerable接口.foreach不能改变集合中各项的值
枚举enum
C#中,enum
是值类型数据类型。枚举用于声明命名整数常量的列表,可以直接在命名空间,类或结构中使用enum
关键字定义
- 枚举用于为每个常量指定一个名称,以便可以使用其名称引用常量整数;默认情况下,枚举的第一个成员的值为 0,每个连续的枚举成员的值增加 1.
- 枚举可以包括数字数据类型的命名常量,例如
byte
,sbyte
,short
,ushort
,int
,uint
,long
或ulong
- 枚举不能与字符串类型一起使用
Enum
是一个抽象类,包含用于枚举的静态帮助器方法
Enum method | Description |
---|---|
Format | 将指定的枚举类型值转换为指定的字符串格式。 |
GetName | 返回指定枚举的指定值的常量的名称。 |
GetNames | 返回指定枚举的所有常量的字符串名称数组。 |
GetValues | 返回指定枚举的所有常量值的数组。 |
object Parse(type, string) | 将一个或多个枚举常量的名称或数值的字符串表示形式转换为等效的枚举对象。 |
bool TryParse(string, out TEnum) | 将一个或多个枚举常量的名称或数值的字符串表示形式转换为等效的枚举对象。返回值表示转换是否成功。 |
enum Color { Red, Green, Blue }
预处理器指令
#region/#endregion
指令用于把一段代码标记为有给定名称的一个块#define/#undef
结合#if/#elif/endif
实现条件编译- …
#define debug using System; namespace CSharp.Study.Test { class Program { static void Main(string[] args) { #if debug Console.WriteLine("debug"); #else Console.WriteLine("other"); #endif } } }
只读字段
常量的概念是一个包含不能修改的值的变量,但是,有时可能需要一些变量,其值不应改变,但在运行之前其值是未知的,C#为这种情形提供了另一种类型的变量:只读字段。readonly
关键字比const
灵活,允许把一个字段设置为常量,但还需要执行一些计算,以确定它的初始值。其规则是可以在构造函数中给只读段赋值,但不能在其他地方赋值。只读字段还可以是一个实例字段,而不是静态字段,类的每个实例可以有不同的值。与const
字段不同,如果要把只读字段设置为静态,就必须显式声明它
//实例只读字段 readonly double taxRate; //静态字段字段 static readonly double taxRate1; BasicsTest() { //只读字段可以在声明时赋值,也可以在构造函数中赋值 taxRate = 0.8; } static BasicsTest() { //静态只读字段在类的静态构造函数中赋值 taxRate1 = 0.9; }
常量和字段字段区别
const
字段只能在声明语句中初始化,而且必须初始化,初始化之后在任何地方都不能改变;readonly
字段既可以在声明时初始化,也可以在构造函数中改变它的值:如果是实例只读字段
,可以在实例构造函数
中改变它的值,如果是静态只读字段
,则可以在静态构造函数
中改变它的值const
字段的值必须在编译时决定,编译完成之后它的值就被替换为字面量;readonly
字段的值可以在运行时决定,可以在不同的构造函数中设置不同的值const
总是像静态字段,在类的外部要通过"类名.常量名"的方式访问;readonly
字段既可以是静态字段,也可以是实例字段。const
在内存中没有存储位置,而readonly
字段在内存中有存储位置。
类型转换
C#
是一门强类型语言,对类型要求比较严格,但是在一定的条件下是可以相互转换的,如将int
型数据转换成double
型数据。C#
允许使用两种转换方式:隐式转换和显式转换
隐式转换
隐式转换是系统默认的,不需要加以声明就会自动执行隐式类型转换,在隐式转换过程,编译器无需对转换进行详细检查就能够安全的执行.隐式类型转换是从低精度数值类型=>高精度数值类型
int a = 10; double b = a;//自动隐式类型转换
显式转换
将高精度值=>低精度进行数据转换时,可能会丢失数据,这时候需要使用显式转换,并且要考虑到可能出现算术溢出;显式转换需要明确指出指定要转换的类型
注意:显式转换可能导致错误,进行这种转换时编译器会对转换进行溢出检测,如果有溢出说明转换失败,表示源类型不是一个合法的目标类型无法进行类型转换
强制类型转换会造成数据精度丢失
double a = 10; int b = (int)a;//显式将double类型转换为int
可空类型数据转换=>非可空类型或者另一个可空类型,其中可能会丢失数据,就必须使用显式类型转换,并且如果从可空类型转换为非可空类型时.且变量值为null
.就会抛出InvalidOperationException
异常
int? a = null; int b = (int)a; //System.InvalidOperationException:“可为空的对象必须具有一个值。
通过方法进行转换
ToString()
C#中的类型基类都继承自Object
类,所以都可以使用ToString()
来转换成字符串
int a = 10; string s = a.ToString();
int.Parse方法()
用于将string
类型参数转换为int
,需要注意:string
类型参数不能为null
,并且也只能是各种整型,不能是浮点型
string a = "2"; string b = "2.6"; string c = null; int a1 = int.Parse(a);//正常 int a2 = int.Parse(b);//错误:输入字符串格式错误 int a3 = int.Parse(c);//值不能为null
int.TryParse方法()
该方法与int.Parse()
方法类似,不同点在于int.Parse()
方法无法转换成功时会抛出异常.而int.TryParse()
方法在无法进行转换时会返回false
,int.TryParse()
方法需要一个out
类型的参数,如果转换成功,out
参数的值就是正常转换的值,否则,返回false
string a = "2"; string b = "2.6"; string c = null; int i; bool a1 = int.TryParse(a,out i);//转换成功,i=2 bool a2 = int.TryParse(b, out i);//转换失败,a2=false bool a3 = int.TryParse(c, out i);//转换失败,a3=false
通过Convert
类进行转换 Convet
类提供了多种类型的转换
string a = "2"; int a1 = Convert.ToInt32(a);
Convert
类常用的类型转换方法
方法 | 说明 |
---|---|
Convert.ToInt32() | 转换为整型(int) |
Convert.ToChar() | 转换为字符型(char) |
Convert.ToString() | 转换为字符串型(string) |
Convert.ToDateTime() | 转换为日期型(datetime) |
Convert.ToDouble() | 转换为双精度浮点型(double) |
Conert.ToSingle() | 转换为单精度浮点型(float) |
通过继承接口IConventible
或者TypeConventer
类,实现自定义转换
使用 as 运算符转换
使用AS
操作符转换,但是AS
只能用于引用类型和可为空的类型。使用as
有很多好处,当无法进行类型转换时,会将对象赋值为NULL
,避免类型转换时报错或是出异常。C#
抛出异常在进行捕获异常并进行处理是很消耗资源的,如果只是将对象赋值为NULL
的话是几乎不消耗资源的(消耗很小的资源)
object o = "abc"; string s = o as string; //执行第一次类型兼容性检查,并返回结果 if (s != null) Console.WriteLine("转换成功!"); else Console.WriteLine("转换失败!");
装箱和拆箱
装箱和拆箱在值类型和引用类型之间架起了一座桥梁,使得任何value-type
的值都可以转换为object
类型的值,反过来转换也可以
装箱
装箱是指将一个值类型的数据隐式地转换成一个对象类型(object
)的数据。执行装箱操作时不可避免的要在堆上申请内存空间,并将堆栈上的值类型数据复制到申请的堆内存空间上,这肯定是要消耗内存和 cpu 资源的。注意:在执行装箱转换时,也可以使用显式转换
int i = 0; object obj = i; //装箱:值类型转换为引用类型
拆箱
拆箱是指将一个对象类型的数据显式地转换成一个值类型数据。拆箱过程是装箱的逆过程,是将存储在堆上的引用类型值转换为值类型并赋给值类型变量。拆箱操作分为两步:一是检查对象实例,确保它是给定值类型的一个装箱值;而是将该值从实例复制到值类型变量中
int i = 0; object obj = i; //装箱:值类型转换为引用类型 int j = (int)obj; //拆箱:引用类型转换为值类型
装箱拆箱注意事项
- 装箱操作可以隐式进行,但拆箱操作必须显示
- 在装箱的时候,并不需要显示类型转换。但在拆箱时需要类型转换
- 这是因为在拆箱时对象可以被转换为任何类型
- 装什么拆什么
- 装箱就是要在托管堆重开辟空间,不但要装数值而且还要装类型
- 所以说装什么拆什么,也就是用什么值类型装箱,就要用什么值类型拆箱
比较对象相等性
比较引用类型相等
ReferenceEquals:
虚拟Equals:
静态Equals:
比较运算符(==):
比较值类型相等
常见运算符
三元运算符
if...else
的简化形式,首先判断一个条件,如果为真,返回第一个值.为假返回后一个值
int a = 3; bool result = a > 10 ? true : false;//a>10?如果大于返回true否则返回false
checked
和unchecked
如果把代码块标记为checked
,CLR
就会执行栈溢出检测,如果要禁止栈溢出,则可以把代码标记unchecked
//byte类型最大取值255 byte a = 255; checked { a++; } //这里如果不加checed.++后输出0(不会抛异常,但会丢失数据,溢出的位会被舍弃,所以值为0),加上后会抛出栈溢出异常 Console.WriteLine(a);
is
is
运算符可以检测对象是否与特定类型兼容,兼容表示对象是该类型或者派生自该类型,转换规则如下:
- 1.检查对象类型的兼容性,并返回结果 true(false)
- 2.不会抛出异常
- 3.如果对象为 null,刚返回 false
object o = "abc"; if (o is string) //执行第一次类型兼容性检查 { string s = (string)o; //执行第二次类型兼容性检查,并转换 Console.WriteLine("转换成功!"); } else { Console.WriteLine("转换失败!"); }
as
as
转换规则如下:as
- 1.检查对象类型的兼容性,并返回转换结果,如果不兼容则返回 null
- 2.不会抛出异常
- 3.如果结果判断为空,则强制执行类型转换将抛出 NullReferenceException 异常
object o = "abc"; string s = o as string; //执行第一次类型兼容性检查,并返回结果 if (s != null) Console.WriteLine("转换成功!"); else Console.WriteLine("转换失败!");
sizeof
sizeof
运算符可以确定栈中值类型需要的长度(单位为字节)
Console.WriteLine(sizeof(int));//4个字节 Console.WriteLine(sizeof(byte));//1个字节
typeof
返回一个表示特定类型的Systen.Type
对象
Console.WriteLine(typeof(int));//System.Int32 Console.WriteLine(typeof(byte));//System.Byte
可空类型和运算符
在 C# 2.0 中出现了可空类型,允许值类型也可以为空null
,可空类型的实现基于 C#泛型。需要注意的是:在程序中使用可空类型就必须考虑null
值在各种运算符一起使用的影响,通常可空类型与一元或二元运算符一起使用时,如果一个操作数为null
或两个操作数为null
.结果就是null
int? a = null; int? c = a + 4; //c=null
空合并运算符
空合并运算符??
提供了快捷方式,处理可控类型和引用类型时表示null
可能的值,需要注意:只能针对引用类型处理,规则是
- 如果第一个操作数不是
null
,值就等于第一个操作数的值 - 如果第一个操作数是
null
,值就等于第二个操作数的值
int? a = null; int b; b = a ?? 10;//第一个操作数是null,值为第二个操作数.10 a = 3; b = a ?? 10;//第一个操作数不是null,值为第一个操作数.3
关键字Keywords
C#包含保留字,对编译器有特殊意义。这些保留字称为“关键字”。关键字不能用作变量,类,接口等的名称(标识符),关键字不能用作标识符(变量名,类,接口等)。但是,它们可以与前缀“@”一起使用。例如,class 是保留关键字,因此不能用作标识符,但可以使用@class。有关关键字的更多信息,访问 MSDN