第7课:数据的基本操作
本课讨论基本类型数据的操作,包括数值类型、布尔类型,并对常用代码操作进行封装;此外,还使用tMath类封装扩展的计算方法,以及使用tRnd类封装随机数相关应用。
数值类型
在C#中,或者说.NET Framework中,数值类型主要包括整数、浮点数和实数;其中,整数的字面量默认为int类型,浮点数的字面量默认为double。对于一些常用的数据类型,可以在数字后使用字母标注数字的类型,如L或l表示long类型、F或f表示float类型、D或d表示double类型、M或m表示decimal类型等。
数值应用中,算术运算是最基本,也是非常重要的操作。C#中,算术运算包括基本的加(+)、减(-)、乘(*)、除(/)和求余数(%)运算。求余数运算时,当两个运算数都是整数时,运算结果是整数,如3/2等于1;如果运算数中有一个是浮点数时,运算结果就是浮点数,如3/2D等于1.5、5.1%2等于1.1。
不同类型数值进行运算时,会自动将取值范围小的类型转换为取值范围大的类型后再进行运算,如int和long类型数值运算的结果就是long类型。常用数值类型按取值范围从小到大分别是int、long、float、double、decimal。
位运算适用于整数类型,包括位与(&)、位或(|)、按位取反(~)、按位异或(^)、位左移(<<)和位右移(>>)运算。软件开发中,位运算在一些特殊的场景下可以提高开发和执行效率,如对标识类数据的处理,如下面的代码。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
int flag1 = 1;
int flag2 = 2;
int flag4 = 4;
int flag3 = flag1 | flag2;
//
int flag = 2;
tWeb.WriteLine(flag & flag1);
tWeb.WriteLine(flag & flag2);
tWeb.WriteLine(flag & flag3);
}
}
|
执行代码会显示0、2、2。本例,首先定义了三个标准标识数据,分别是1、2、4,可以看到它们分别是2的0、1、2次方,然后定义了flag3,它通过flag1和flag2的位或运算计算而来。
接下来的的操作,flag的数据为2,第一个输出会判断它是否满足标识1,运算结果为0;第二个和第三个输出分别判断flag是否满足2和3,运算结果为2。如果需要返回bool结果,可以在位与运算后与原数据进行对比,如下代码的运行结果为False、True和True。
C# |
int flag1 = 1;
int flag2 = 2;
int flag4 = 4;
int flag3 = flag1 | flag2;
//
int flag = 2;
tWeb.WriteLine(flag == (flag & flag1));
tWeb.WriteLine(flag == (flag & flag2));
tWeb.WriteLine(flag == (flag & flag3));
|
类型转换
前面已经讨论了数值类型在代码执行过程中可能的自动转换操作,下面,归纳一些C#和.NET Framework资源中与类型转换相关的内容。
自动转换,也称为隐式转换。一般是指在代码执行过程中,取值范围小的类型自动转换为取值范围大的类型。
强制转换,也称为显式转换。可以在需要转换类型的表达式前使用一对括号指定目标类型,比如,x定义为long类型,需要将其数据赋值为int类型的y变量,可以使用y=(int)x;语句。数值类型由取值范围大向取值范围小的类型转换时,如果数值过大会赞成溢出,从而改变实际的数据,在进行此类操作时,一定要考虑数据精度的问题。此外,两个较大的int数据相乘时很容易产生溢出,此时可以使用Math.BigMul()方法计算,方法会返回long类型数据。
将对象作为其它类型操作(父类、实现的接口或原始类型等),可以使用as关键字,需要注意,如果对象不能正确转换为目标类型,代码就会产生异常。
Parse()和TryParse()方法。基本的数值类型、布尔类型和DateTime类型中都定义了这两个转换方法。如,int.Parse(s)方法会将字符串s的内容转换为int类型,转换成功返回int数据,如果转换不成功,代码就会产生异常。int.TryParse(s,out v)方法会将字符串s的内容转换为int类型,转换成功时,输出参数v返回转换结果,方法返回true,如果s的内容不能正确转换为int类型,则方法返回false,此时v的值是0。如果转换不成功,TryParse()方法的输出参数会返回目标类型的默认值,对于数值类型,输出参数返回的结果是0,bool类型返回的结果是false,DateTime类型返回的时点为0001年1月1日0时。
Convert类定义了一系列的转换方法,如ToInt32(v)方法就是将参数v转换为Int32类型,也就是C#中的int类型。ToInt32()方法有多个重载版本,可以将多种类型的数据转换为int类型,转换成功时,方法返回转换结果,如果参数不能正确转换为int类型,则会产生异常。对于基本的数据类型,Convert类中的方法名都使用了.NET Framework中的命名,如ToInt32()、ToInt64()、ToSingle()、ToDouble()、ToDecimal()、ToString()、ToDateTime()等方法,而且,它们都有多个重载版本。
本书代码库中,对于类型转换与相关操作也进行了一些封装,以int类型为例,当需要从其它类型转换为int类型时,会有很多不同的应用场景,如:
- 无论原数据是否能够成功转换为int类型,都必须有一个可以使用的int数据(如默认值0)。
- 需要将原数据转换为用于保存到数据库中的int类型数据,如果原数据不能转换为int数据,则返回DBNull.Value值,表示数据库中的没有数据(NULL)。
- 只判断原数据是否可以转换为int类型。
这是常见的三种数据转换相关操作,在tInt类中定义了目标类型为int的操作方法,如下面的代码(/app_code/common/base/tInt.cs)。
C# |
using System;
public static class tInt
{
// 给出可用的int数据
public static int GetValue(object obj,int defVal=0)
{
int re;
if (obj != null && int.TryParse(obj.ToString(), out re))
return re;
else
return defVal;
}
// 返回用于保存到数据库的数据
public static object GetDbValue(object obj)
{
int re;
if (obj != null && int.TryParse(obj.ToString(), out re))
return re;
else
return DBNull.Value;
}
// 检查是否能转换为int类型
public static bool Check(object obj)
{
int re;
return obj != null && int.TryParse(obj.ToString(), out re);
}
//
}
|
tInt类中定义了三个方法,分别是:
- GetValue(obj,defVal)方法,其中,参数obj定义为object类型,指定需要转换为int类型的数据;参数defVal定义为int类型,当obj不能正确转换为int类型时就返回defVal指定的数据,默认为0。方法返回类型为int。
- GetDbValue(obj)方法,将参数obj转换为int类型,正确转换时返回int数据,否则返回DBNull.Value值。方法返回数据类型为object。
- Check(obj)方法,参数obj可以成功转换为int类型时返回true,否则返回false。
除了tInt类,在/app_code/common/base目录中,对常用的数值类型都有相关定义,如tLng、tSng、tDbl、tDec类。
数据精度
在数值类型的处理过程中,还需要注意精度问题,如浮点数转换为整数类型、缩短小数位数等情况。
使用强制转换将浮点数转换为整数时,会直接丢弃浮点数的小数部分,只保留整数部分,如下面的代码。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
tWeb.WriteLine((int)1.9);
tWeb.WriteLine((int)-1.9);
}
}
|
执行代码会显示1和-1,并不会对小数部分进行舍入操作。
在Math类中提供了一些转换方法,可以指定不同的转换规则,如:
- Round()方法,可以指定保留的小数位和舍入方式。
- Floor()方法,保留小于或等于参数的最大整数。
- Ceiling()方法,保留大于或等于参数的最小整数。
先来看Math.Round()方法的应用。方法中,参数一指定需要转换的浮点数(double或decimal类型),参数二指定保留的小数位数量,不指定时只返回整数部分;如下面的代码。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
tWeb.WriteLine(Math.Round(123.45)); // 123
tWeb.WriteLine(Math.Round(123.45,1)); // 123.4
tWeb.WriteLine(Math.Round(123.55, 1)); // 123.6
tWeb.WriteLine(Math.Round(123.45000001, 1)); // 123.5
}
}
|
请注意Math.Round()方法的默认舍入规则,可以简单总结为“四舍六入五取偶”。第一个输出,123.45只保留整数部分,由于第一位小数是4,所以直接舍去,转换结果为123。第二个输出中需要保留一位小数,而123.45正好位于123.4和123.5的中间值,会取最近的偶数123.4;同样的道理,第三个输出也会取最近偶数而显示为123.6。第四个输出中,舍去的部分大于中间值,所以会向前进位,输出结果为123.5。
Math.Floor()会返回小于或等于参数数据的最大整数,当参数为double类型时返回值也是double类型,当参数为decimal类型时返回值为decimal类型。下面的代码演示了Math.Floor()方法的应用。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
tWeb.WriteLine(Math.Floor(123.5)); // 123
tWeb.WriteLine(Math.Floor(-123.5)); // -124
}
}
|
Math.Ceiling()方法会返回大于或等于参数数据的最小整数,同样的,当参数为double类型时返回double类型,当参数为decimal类型时返回decimal类型。下面的代码演示了Math.Ceiling()方法的应用。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
tWeb.WriteLine(Math.Ceiling(123.5)); // 124
tWeb.WriteLine(Math.Ceiling(-123.5)); // -123
}
}
|
布尔类型
布尔类型,也称为逻辑类型,用于处理true和false值,一般用于表示“真/假”、“是/否”等类型的数据;在C#代码中,基本的布尔运算(逻辑运算)包括与运算(&&)、或运算(||)、取反运算(!)。
布尔运算通常用于条件的判断,如if语句结构、while语句结构等。在进行复杂的条件判断时,布尔运算的顺序很重要,按优先级高低分别是!、&&、||;如果条件的组合比较复杂,使用圆括号进行组织会是比较安全的方法。
软件开发中,界面、业务组件、数据库和各种数据源之间经常需要数据的交换,此时,常常需要在布尔类型和其它类型之间进行转换,如整数中的0转换为false,非0值转换为true。
本书代码库中,对一些常用的转换操作进行了封装,相关操作定义在tBool类中,如下面的代码(/app_code/common/base/tBool.cs)。
C# |
using System;
public static class tBool
{
//
public static bool GetValue(object obj)
{
if (obj == null) return false;
string s = obj.ToString().ToLower();
if (s == "" || s == "0" || s == "false" || s == "f" ||
s == "no" || s == "n" || s == "off")
return false;
else
return true;
}
// 数据库中只使用0和1值保存布尔值
public static object GetDbValue(object obj)
{
return GetValue(obj) ? 1 : 0;
}
//
public static int GetInt(this bool bl)
{
return bl ? 1 : 0;
}
//
}
|
tBool类中定义了三个方法,分别是:
- GetValue(obj)方法,将obj转换为bool类型,请注意其中的自定义转换规则,可以根据实际需要修改。
- GetDbValue(obj)方法,将obj转换为保存到数据库中的数据。这里约定在数据库中使用整数类型保存布尔数据,根据GetValue()方法中的转换规则,true值转换为1,false值转换为0,然后保存到数据库。
- GetInt(bool)方法,将bool类型数据转换为整数,true转换为1,false值转换为0。
类型判断
System命名空间中的Type类用于处理类型,其中,可以使用Name属性获取类型的直接名称,对于基本的数据类型,会返回System命名空间中定义的类型名称,如int类型对应的就是Int32结构类型。
此外,如果数据保存到object类型的对象中,使用GetType()方法可以获取原始类型,并使用Type类型中的Name属性获取类型名称,如下面的代码会显示Int32。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
int x = 10;
object obj = x;
tWeb.WriteLine(obj.GetType().Name);
}
}
|
下面的代码(/app_code/common/base/tType.cs)封装了tType类,其中的IsNumeric()方法判断参数的类型是否为数值类型。
C# |
using System;
public static class tType
{
// 判断对象类型是否为数值型
public static bool IsNumeric(object obj)
{
if (obj == null) return false;
return IsNumeric(obj.GetType());
}
// 通过类型名称判断是否为数值类型
public static bool IsNumeric(Type t)
{
if (t == null) return false;
string name = t.Name;
return name == "Int32" || name == "Int64" || name == "Double" ||
name == "Decimal" || name == "Single" || name == "UInt32" ||name == "UInt64" ||
name == "Byte" || name == "SByte" || name == "Int16" || name == "UInt16";
}
//
}
|
下面的代码演示了tType.IsNumeric()方法的应用。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
int x = 10;
object obj = "abc";
tWeb.WriteLine(tType.IsNumeric(x.GetType()));
tWeb.WriteLine(tType.IsNumeric(obj));
}
}
|
执行代码会显示True和False。
tMath类
System.Math类中已经封装了大量的数学计算资源,但也有一些常用的操作没有包含其中;本书代码库中,使用tMath类(/app_code/common/math/tMath.cs)封装自己需要的计算资源,如下面的代码。
C# |
using System;
public static class tMath
{
// 根据度数返回弧度
public static double Radian(double angle)
{
return angle / 180d * Math.PI;
}
// 根据弧度返回角度
public static double Angle(double radian)
{
return radian / Math.PI * 180d;
}
}
|
代码中定义了两个方法,分别是:
- Radian()方法,将角度值转换为弧度。
- Angle()方法,将弧度值转换为角度。
下面的代码演示了这两个方法的应用。
C# |
tWeb.WriteLine(tMath.Radian(180));
tWeb.WriteLine(tMath.Angle(Math.PI));
|
代码执行结果如图1。
图1
此外,Gcd()和Lcm()方法分别用于计算最大公约数和最小公倍数,如下面的代码。
C# |
// 最大公约数,自然数
public static ulong Gcd(ulong x,ulong y)
{
if (y == 0) return x;
ulong m = x % y;
return Gcd(y, m);
}
// 最小公倍数
public static ulong Lcm(ulong x,ulong y)
{
return x * y / Gcd(x, y);
}
|
代码中,Gcd()方法通过递归调用计算两个自然数的最大公约数,Lcm()方法则用于计算两个自然数的最小公倍数。此外,Gcd()和Lcm()方法还有uint类型数据计算的重载版本。
实际开发工作中,还可以根据需要扩展tMath类。
tRnd类
数据统计及软件开发中,随机数的相关应用是比较常见的,如验证码等。System.Random类可以创建随机整数和浮点数,在tRnd类中,我们会对常用的功能进行封装,代码封装在/app_code/common/tool/tRnd.cs文件,下面的代码是tRnd类的基本定义。
C# |
using System;
using System.Text;
public static class tRnd
{
private static Random r = new Random();
//
public static int GetInt(int min, int max)
{
// 不包含max
return r.Next(min, max);
}
//
public static int GetInt()
{
return r.Next(0, 10);
}
// 其它代码…
}
|
tRnd类定义为静态类,其中,r定义为Random类的实例,并作为生成随机数的对象。代码中首先定义了两个GetInt()方法,其中,min参数指定最小值,max参数指定最大值(不包含此数),返回数值n的范围为min≤n<max;不指定随机数范围时,会返回一位整数,即0到9的随机数。
接下来是产生随机字符和随机字符串的方法,如下面的代码。
C# |
// 返回一个小写字母
public static char GetLower()
{
return (char)GetInt(97, 123);
}
// 返回一个大写字母
public static char GetUpper()
{
return (char)GetInt(65, 91);
}
// 返回指定长度的字符串
public static string GetStr(int len = 8)
{
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++)
{
if (GetInt(0, 2) == 0) sb.Append(GetInt());
else sb.Append(GetLower());
}
return sb.ToString();
}
|
其中,GetLower()和GetUpper()方法分别返回一个小写和大写字母,GetStr()方法返回一个指定长度的随机字符串,这里只包含数字和小写字母。需要注意的是,如果需要使用唯一的字符串标识,应使用GUID,下一课会有相关讨论。
开发中,如果随机字符同时使用小写和大写字母,应注意某些字体的显示效果,小写l(L)和大写I(i)很接近,会造成用户不易识别。