初始C# Case1
一、类型
读《Effective C#--50 Specific Ways to Improve Your C#》了解,值类型与引用类型需要明辨场合使用,在C++中,所有的类型都被我们定义为值类型,然后可以选择创建它们的引用形式;在Java中,除了预定义的数值类型和字符类型为值类型,所有的类型都是引用类型。C#是一种强类型的基础语言,在设计类型的时候就必须决定实例类型的行为。值类型不支持多态,比较适合存储供应用程序操作的数据。引用类型支持多态,应该用于定义应用程序的行为。简而言之,结构用于存储数据,类用于定义行为。
C#中具备的基本数据类型有:简单类型、枚举、结构和引用类型。
- 简单类型是系统预置的,包括:bool,sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal。
- 枚举类型是一组命名的集合,使用enum关键字声明枚举,该类型的一组成员可以是除了char以外的任何整形,默认元素为int,且以0起始,往后递增1。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Test.Green);
Console.WriteLine((int)Test.Green);
string[] str1 = Enum.GetNames(typeof(Test));
foreach (string str in str1)
{
Console.WriteLine(str);
}
byte[] b1 = (byte[])Enum.GetValues(typeof(Test));
foreach (byte b in b1)
{
Console.WriteLine(b);
}
}
}
enum Test:byte { Green=1,Yello,Blue=1,Black };
初始化enum时给其中元素赋值,打印的时候会按大小打印。在变量名后给予整型类型,会将数据转换成该类型,默认为int。
- 结构类型与引用类型,首先,class Demo的Demo程序集可以用Struct来创建,其次,它们在内存中存储的位置不同。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
static void Main()
{
Demo D1 = new Demo();
Demo D2 = D1;
D1.A = 100;
D2.A = 1;
D1.Method();
D2.Method();
}
}
class Demo
{
int _A;
public int A
{
get { return _A; }
set { _A = value; }
}
public void Method()
{
Console.WriteLine(A);
}
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
static void Main()
{
Demo D1 = new Demo();
Demo D2 = D1;
D1.A = 100;
D2.A = 1;
D1.Method();
D2.Method();
}
}
struct Demo
{
int _A;
public int A
{
get { return _A; }
set { _A = value; }
}
public void Method()
{
Console.WriteLine(A);
}
}
使用class创建D1,D2,D1把地址副本传递给了D2,D1与D2指向了同一个实例,当D2修改了实例时,D1使用Method得出的结果是修改后的结果,这是引用类型传递引用的结果。
使用struct创建D1,D2,D1把值的副本传递给了D2,D2与D1在栈中同样具备了一个A的值,D1与D2对A的修改不互相影响,这是值类型传递值的结果。
这是一个错误的实例,struct使用的范围是存储数据,class使用的范围是定义行为,是创建时需要分清使用范围。
栈与堆,在方法中,值类型存储于栈中,引用类型存储于堆中。没有强调方法中,那么值类型不一定存储于栈中。读《Accelerated C# 2008》了解,当值类型属于引用的一部分,确保引用的完整性,值类型存储于堆中,当使用时是否重新开辟一块内存空间来分开存放引用中的值与引用,这一点未深入。
引用类型与值类型存在着“装箱与拆箱”的操作,使两种那类型相互转化,首先,所有的类型都是集成自Object基类,ValueType继承自Object并重写了其中的一些方法(并未增加新方法),使它更适合值类型的使用,一个结构隐式的继承自ValueType和Object。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
static void Main()
{
/*--装箱--*/
int x = 1;
object obj1 = x;
x = x + 100;
Console.WriteLine(x);
Console.WriteLine(obj);
/*--拆箱--*/
int y = (int)obj1;//拆箱操作进行类型强制转换
}
装箱操作把obj1存放在栈中,地址指向x=1,当对栈中x进行操作时,并未影响到obj1的值,这里也说明了值类型不一定存在于栈中。
拆箱操作使用了强类型转换,类型转换包括了隐式转换,显示转换,Convert转换。
尽量减少装箱与拆箱操作,它们有一定的开销,在后来引进了泛型和集合对其进行了约束。
补充:
对于浮点数,需要在数字后面加上F,如果不加编译器则默认为double类型,浮点数是不精确的,当表示不了时,系统将调用最接近的数来代替,不是所有的十进制小数都可以转换为二进制的,需要依赖于浮点数在内存中存在表示,原理是以2为底的科学计数法,很多十进制数无法准确的表示。于是,在涉及到类似财务计算一类的要求精确运算时,可以使用decimal类型,其表达方式在数据后加m,float与decimal不可直接转换,会存在丢失数据或丢失精度的情况。然后decimal无法代替float,其范围比float要小了几十个数位级。
二、运算符
运算符是语言中相当熟悉的一环,其中&,^,|实现了二进制计算。
这里使用^来实现如何不用第三变量来实现两个整数的值互换。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program
{
static void Main()
{
int _A = 5;
int _B = 7;
_A = _A ^ _B;
_B = _A ^ _B;
_A = _A ^ _B;
Console.WriteLine("{0},{1}", _A,_B);
}
}
左移<<与右移>>运算符也是极其重要的,使用<<进行运算相当于其二进制数*2,它提供32位长度,i<<1 == i<<33 since of 33%32=1。
使用移位运算符实现进制转换。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program
{
static void Main()
{
int a = 255;
int b = 65535;
int c = 20;
string sa = ToHex(a);
string sb = ToHex(b);
string sc = ToHex(c);
Console.Read();
}
public static string ToHex(int num)
{
char[] chars = { '0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F'};
uint mark = 0x0000000F;
uint temp = 0;
string res = "";
for (int i = 0; i < 8; i++)
{
temp = (uint)num & mark;
string bit = chars[temp].ToString();
res = bit + res;
// 把要计算的数,每次右移四位,这样每次可以取4位.
num >>= 4;
}
return res;
}
}
三、控制语句
选择语句:if-else,switch-case
循环语句:while,do-while,for,foreach
跳转:break,continus,goto
异常处理:try-catch-finally
选择语句的细节部分,首先if-else和switch-case在对于等值判断的时候是可以通用的,在使用范围判断时则只能用if-else。
关于if,与C不同,C#中的条件类型只能为bool型。
关于switch,其条件变量可以为int与string类型,C#中任何case语句与default语句最后都需要有break,在实际运用中,default块必须写进程序中,在测试中可以通过default语句看出该控制语句块是否得到了处理。如果Case 1与Case 0得出同样的结果,则应这样编写:
Case A:
Case B:
Console.WriteLine();
break;
default有双重用法,一种是用于switch中当默认值,一种是用于泛型中。 因为并不知道泛型T是值类型还是引用类型,值类型初始化应该是0,而引用类型则应该是null。所以用default关键字来初始化,保证运行时初始化为正确的类型,e.g:T temp = default(T)。
循环语句的细节部分,for(;;)与while(true)功能等价。
for与foreach,这两种类型存在着不能相互转换的情况,当类型数目不明确时,则只能用foreach进行遍历;当对集合进行增,删时,需要用for循环。foreach是一种迭代器,更改集合大小时迭代器将无效。
读《Effective C#--50 Specific Ways to Improve Your C#》了解,要优先的采用foreach循环语句,对于当前与未来的C#编译器,foreach语句代码最优,程序员的开发效率也比较高。它会使用最高效的构造为数组的上下界索引、多维数组遍历、操作数转型等产生正确的代码,并且产生的是最具效率的循环结构。
关于异常处理,首先,finally中编写的一般是释放资源打开资源的语句,其次,try-catch-finally查找错误的是一种事后补救方式,它并不能限制错误的发生也不能查出错误发生情况,最后,它具备了高开销。使用throw继续抛出异常,如果用throw e的话则相等于改变了异常抛出位置到catch。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Account
{
/// <summary>
/// 表示账户的余额
/// </summary>
private decimal balance = 1000;
/// <summary>
/// 表示取款的操作.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public decimal Draw(decimal value)
{
// 只有在要账户余额大于等于取款金额时才进行操作.
if (balance >= value)
{
balance -= value;
}
else
{
// 当遇到要取款的金额大于当前的账户余额时,因为我们没有办法进行处理
// 把这个时候,我们可以抛出一个异常. 由调用方进行处理.
// 抛出异常的语法是throw 后面跟一个Exception或Exception的子类的实例.
// 在要描述异常的对象中,必须给出关于该异常的详细信息,这样用户程序员
// 才能根据异常描述信息来查找出错的原因.
throw new Exception("当前余额小于取款金额,取款操作无法进行.");
}
return balance;
}
}
public class TestThrow
{
public static void Main()
{
try
{
Account acc = new Account();
decimal b = acc.Draw(100);
b = acc.Draw(2000);
Console.WriteLine(b);
}
catch (Exception e)
{
string msg = e.Message;
}
}
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class MyException
{
public static void Excep()
{
int i = 10;
// 把有可能产生异常的代码放到try代码块中,
// 当try块中的代码在执行过程中发生了异常的时候,
// 代码的执行流程会跳转到try后面的catch块中的代码段去执行.
// 所以catch块中放置的是对异常情况进行处理的代码.
// 如果没有异常发生 ,那么就不会执行catch代码段中的代码.
// 使用catch通常称为异常捕获.
try
{
while (i != -1)
{
// 当i变为零时,程序执行到这行语句时会发
// 生一个异常.发生异常后,如果没有进行处理的话,
// 程序会异常终止,不会继续运行下去.
int value = 100 / i;
i--;
}
}
catch (Exception e)
{
string str = e.Message;
Console.WriteLine("程序运行发生了异常:" + str);
}
while (i != -1)
{
// 当i变为零时,程序执行到这行语句时会发
// 生一个异常.发生异常后,如果没有进行处理的话,
// 程序会异常终止,不会继续运行下去.
int value = 100 / i;
i--;
}
}
public static void Main()
{
// Excep();
Res(0);
}
public static int Result(int num)
{
// 约定当除法出错时,返回-1,表示除法出错.
// num 必须是大于等于零的整数.
int result = -1;
try
{
result = 100 / num;
// 有些代码段必须要在发生异常时也执行,
// 不发生异常时也执行,所以会在try,catch中出现同样的代码段.
return result;
}
catch (Exception e)
{
return result;
}
}
public static void Res(int num)
{
int result = -1;
FileStream fs = null;
try
{
fs = new FileStream(@"c:\test.txt",FileMode.Open);
// 对文件进行一些处理.
// fs.Close();
}
catch (Exception e)
{
string msg = e.Message;
}
// 不管异常是否发生都会执行finally中的语句.
// 在现实中,finally都是用来实现资源的释放,
finally
{
if (fs != null)
{
fs.Close();
}
}
}
}
以上内容补充:
is操作符用来判断一个变量的类型。
as操作符用来判断一个变量的类型,转换时成功则返回,不成功返回null。as后面只能是引用类型,因为值类型不能返回null。
using关键字
命名空间可以嵌套。使用的时候需要两个命名空间名作引用,命名空间的引入解决了代码混乱问题。
若两个命名空间下同时存在同一个类,e.g:using System.Timers;using System.Threading;两个命名空间存在Timer这个类。则需要使用using来取别名,using MyTime=System.Threading.Timer。