一、类型转换
数据类型的转换可以分为以下两类:
隐式转换:从类型A到类型B的转换可在所有情况下进行,执行转换的规则非常简单,可以让编译器执行转换;
显式转换:从类型A到类型B的转换只能在某些情况下进行,转换的规则比较复杂,应进行某种类型的额外处理。
1.隐式转换:
任何类型A只要其取值范围完全包含在类型B的取值范围内,就可以隐式转换为类型B:
ushort destinationvar;
char sourcevar = 'a';
destinationvar = sourcevar;
short类型的变量可以存储0~32767之间的数字,而byte可以存储的最大值是255,如果要将short转到byte,那么就会产生问题,如果short中存放的值在256~32767之间,那就不能将short转到byte;如果short中存放的值在0~255之间,则可以进行转换,但是必须要进行显式转换。
2.显示转换:
当short中存放的值在0~255之间时,可以通过强制转换,将其转换为byte类型:
byte destinationvar;
short sourcevar = 7;
destinationvar = (byte)sourcevar;
此时输出的结果:sourcevar = 7;destinationvar = 7;
当short中的存放的值属于256~32767之间时,如果进行强制转换则会出现数值错误:
byte destinationvar;
short sourcevar = 281;
destinationvar = (byte)sourcevar;
此时输出的结果:sourcevar = 281;destinationvar = 25;
281 = 100011001
25 = 000011001
255 = 0111111111
由此可见在将大数强转至小数时会出现数据丢失,数据的最左侧数位丢失了。
有两种方式可以避免出现这样的问题:
其一:特别注意显示转换时的数值范围;
其二:为显式转换增加检查关键字checked或unchecked,称为溢出检查:
byte destinationvar;
short sourcevar = 281;
destinationvar = checked(byte)sourcevar;
//destinationvar = unchecked(byte)sourcevar;
checked在运行时程序会崩溃,而unchecked会和之前一样得出错误的答案。
3.用Convert命令进行显示转换:
并不是所有的字符串都可以通过Convert命令转换为double类型:
Convert.ToDouble("Numble");
//报错"Input string was not in a correct format"
为成功执行此类转换,所提供的字符串必须是数值的有效表达方式,且该数还必须是不会溢出的数,数值的有效表达方式:首先是一个可选符号(可以是加号或减号),然后是0位或多位数字,一个可选的句点,接着是一个可选的e(大小写均可以),后跟一个可选符号和一位或者多位数字,除了还可能有空格(在这个序列之前或之后),不能有其他字符。例如:-1.2451e-24。
对于Convert而言,必须要进行溢出检查,checked和unchecked关键字以及项目属性设置不起作用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
using static System.Convert;
namespace Ch05Ex01
{
class Program
{
static void Main(string[] args)
{
short shortResult, shortVal = 4;
int integerVal = 67;
long longResult;
float floatVal = 10.5F;
double doubleResult, doubleVal = 99.999;
string stringResult, stringVal = "17";
bool boolVal = true;
WriteLine("Variable Conversion Examples\n");
doubleResult = floatVal * shortVal;//在这里short会隐式转换到float,两个float相乘后转为double(隐式转换)
WriteLine($"Implicit, -> double: {floatVal} * {shortVal} -> { doubleResult }");
shortResult = (short)floatVal;//float强转为short(显示转换)
WriteLine($"Explicit, -> short: {floatVal} -> {shortResult}");
stringResult = Convert.ToString(boolVal) +
Convert.ToString(doubleVal);//ToString直接转为字符串类型,字符串类型并不限制字符串长度
WriteLine($"Explicit, -> string: \"{boolVal}\" + \"{doubleVal}\" -> " +
$"{stringResult}");
longResult = integerVal + ToInt64(stringVal);//类似与ToDouble可以直接转换字符串的数值部分
WriteLine($"Mixed, -> long: {integerVal} + {stringVal} -> {longResult}");
ReadKey();
}
}
}
二、复杂的变量类型
1.枚举
枚举相当于用户定义了一个自己需要的类型,这个类型的取值范围是用户提供的值的有限集合,定义好类型后再去用该类型声明变量。
定义枚举:
可以用enum关键字定义枚举,如下所示:
enum<typename>
{
<value1>,
<value2>,
...
<valueN>
}
接着声明这个新类型的变量:
<typename><varname>;
并对该变量进行赋值:
<varname> = <typename>.<value>;
//也可以直接写做:
<typename><varname> = <typename>.<value>;
枚举使用一个基本类型来存储,枚举类型可取的每个值都存储为该基本类型的一个值,默认情况下该类型为int,通过在枚举声明中添加类型,就可以指定其他的基本类型。
enum<typename>:<underlyingType>
{
<value1>,
<value2>,
...
<valueN>
}
枚举的基本类型可以是byte,sbyte,short,ushort,int,uint,long,ulong。
默认情况下,每个值都会根据定义的顺序(从0开始),被自动赋予对应的基本类型值。这意味着<value1>的值是0,<value2>的值是1,<value3>的值是2。
这个赋值过程可以重写,利用赋值运算符"="指定每个枚举的实际值:
enum<typename>:<underlyingName>
{
<value1> = <acturalval1>,
<value2> = <acturalval2>,
... ...
<valueN> = <acturalvalN>
}
还可以使用一个值错位另一个枚举的基础值,为多个枚举指定相同的值:
enum<typename>:<underlyingName>
{
<value1> = <acturalvalue1>,
<value2> = <value1>,
<value3>,
...
<valueN> = <acturalvalueN>
}
由于value3没有进行明确的赋值,因此value3的值应该等于上一个明确赋值的值 + 1,即value2 + 1也就是value1 + 1。
枚举类中的值与其赋值的关系并不是数值上的关系,而是类似于编号的性质,常见的枚举中并不需要对每个值进行赋值,赋值的意义在于可以通过类型转换,提取出更多的信息。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
using static System.Convert;
namespace Ch05Ex02
{
enum orientation : byte
{
north = 1,
south = 2,
east = 3,
west = 4
}
class Program
{
static void Main(string[] args)
{
//orientation myDirection = orientation.north;
//WriteLine($"myDirection = {myDirection}");
//ReadKey();
byte directionByte;
string directionString;
orientation myDirection = orientation.north;
WriteLine($"myDirection = {myDirection}");
directionByte = (byte)myDirection;//将myDirection的值转换为byte类型
directionString = Convert.ToString(myDirection);
//使用(string)进行强制类型转换是行不通的,因为需要进行的处理并不是把存储在枚举变量中的数据放在string变量中,而是更复杂一些
directionString = myDirection.ToString();//这句话与上面的表达式是等价的
WriteLine($"byte equivalent = {directionByte}");
WriteLine($"string equivalent = {directionString}");
ReadKey();
}
}
}
也可以将string转为枚举值,但是其语法稍微复杂一些,有一个特定的命令用于完成此类转换,即
(enumerationType)Enum.Pase(typeof(enumerationType), enumerationValuestring)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
using static System.Convert;
namespace Ch05Ex02
{
enum orientation : byte
{
north = 1,
south = 2,
east = 3,
west = 4
}
class Program
{
static void Main(string[] args)
{
//orientation myDirection = orientation.north;
//WriteLine($"myDirection = {myDirection}");
//ReadKey();
byte directionByte;
string directionString;
//orientation myDirection = orientation.north;
string myString = "north";
orientation myDirection = (orientation)Enum.Parse(typeof(orientation), myString);//将字符值转换为枚举值
WriteLine($"myDirection = {myDirection}");
directionByte = (byte)myDirection;//将myDirection的值转换为byte类型
directionString = Convert.ToString(myDirection);
//使用(string)进行强制类型转换是行不通的,因为需要进行的处理并不是把存储在枚举变量中的数据放在string变量中,而是更复杂一些
directionString = myDirection.ToString();//这句话与上面的表达式是等价的
WriteLine($"byte equivalent = {directionByte}");
WriteLine($"string equivalent = {directionString}");
ReadKey();
}
}
}
2.结构
结构是由几个数据组成的数据结构,这些数据可能有不同的类型,根据这个结构可以定义自己的变量类型:
定义结构:
用struct关键字定义结构,如下所示:
struct<typename>
{
<memberDeclarations>;
<memberDeclarations>;
}
<memberDeclarations>部分包含变量(称为结构的数据成员)的声明(注意分号不要漏写),其格式与前面的变量声明一致,每个成员的声明都采用如下的形式:
<accessibility><type><name>
要让调用结构的代码访问该结构的数据成员,可以对<accessibility>使用关键字public,例如:
struct route
{
public orientation direction;
public double distance;
}
定义结构类型后,就可以定义该结构类型的变量:
route myRoute;
还可以通过句点字符访问这个组合变量中的数据成员:
myRoute.direction = orientation.north;
myRoute.distance = 2.5;
结构和枚举一样,其声明均在主体Main函数以外,在名称空间中声明route结构及其使用的orientation枚举。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
using static System.Convert;
namespace Ch05Ex03
{
enum orientation : byte
{
north = 1,
south = 2,
east = 3,
west = 4
}
struct route
{
public orientation direction;
public double distance;
}
class Program
{
static void Main(string[] args)
{
route myRoute;
int myDirection = -1;
double myDistance;
WriteLine("1) North\n2) South\n3) East\n4) West");
do
{
WriteLine("Select a direction:");
myDirection = ToInt32(ReadLine());
}
while ((myDirection < 1) || (myDirection > 4));
WriteLine("Input a distance:");
myDistance = ToDouble(ReadLine());
myRoute.direction = (orientation)myDirection;
myRoute.distance = myDistance;
WriteLine($"myRoute specifies a direction of {myRoute.direction} " +
$"and a distance of {myRoute.distance}");
ReadKey();
}
}
}
3.数组
采用下述方式声明数组:
<baseType>[]<arrayName>;
其中<baseType>可以是任何变量类型,包括本章前面介绍的枚举和结构类型,数组必须在使用前进行初始化,初始化方式有以下两种:
1)用字面值指定数组:
int [] myIntArray = {5,6,7,8,9};
2)利用关键字new显式地初始化数组:
int [] myIntArray = new int[5];
这里用关键字new来显式初始化一个数组,用一个常量来定义其大小,这种方式会给所有数组元素赋一个默认值,对于数值类型来说,其默认值是0。
也可以使用非常量(arraySize不能是一个const修饰的常量)的变量来进行初始化,例如:
int [] myIntArray = new int[arraySize];
还可以根据需要组合使用这两种初始化方式:
int [] myIntArray = new int[5] {5,6,7,8,9};
使用这种方式的话,数组的大小必须与元素的个数相匹配,当数组大小与初始化提供的元素个数不同时就会编译失败。
可以使用变量来定义其大小,但是该变量必须是一个常量:
const int arraySize = 5;
int [] myIntArray = new int[arraySize] {5,6,7,8,9};
如果省略了关键字const,运行这段代码就会失败。
与其他的变量类型一样,并非必须在声明数组的代码行中初始化该数组:
int [] myIntArray;
myIntArray = new int[5] {5,6,7,8,9};
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace Ch05Ex04
{
class Program
{
static void Main(string[] args)
{
string[] friendNames = { "Todd Anthony", "Kevin Holton",
"Shane Laigle" };
int i;
WriteLine($"Here are {friendNames.Length} of my friends:");
//for (i = 0; i <= friendNames.Length; i++)//当符号改成<=时i可以取到3,但是friendNames[3]并不存在所以会出现报错
for (i = 0; i < friendNames.Length; i++)
{
WriteLine(friendNames[i]);
}
ReadKey();
}
}
}
使用for循环可能会存在出现访问非法元素的情况,使用foreach循环可以解决这一问题:
static void main(string[] args)
{
string[] friendNames = {"Todd Anthony","Kevin Holton","Shane Laigle"};
WriteLine($"Here are {friendsNames.Length} of my friends:");
foreach(string friendName in FriendNames)
{
WriteLine(friendName);
}
ReadKey();
}
foreach循环只读不写,只能逐个读取出数组中的值,但不能像for循环一样给数组进行赋值。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace Ch05Ex05
{
class Program
{
static void Main(string[] args)
{
string[] friendNames = { "Todd Anthony", "Kevin Holton",
"Shane Laigle", null, "" };
foreach (var friendName in friendNames)
{
switch (friendName)
{
case string t when t.StartsWith("T")://case声明了一个string类型的变量t,里面保存的是foreach中的friendName
WriteLine("This friends name starts with a 'T': " +
$"{friendName} and is {t.Length - 1} letters long ");
break;
case string e when e.Length == 0://声明了一个string类型的变量e,如果friend
WriteLine("There is a string in the array with no value");
break;
case null:
WriteLine("There was a 'null' value in the array");
break;
case var x://定义一个变量var x 用于捕获所有类型的变量
WriteLine("This is the var pattern of type: " +
$"{x.GetType().Name}");
break;
default:
break;
}
}
WriteLine();
WriteLine("=================================================");
WriteLine();
int sum = 0, total = 0, counter = 0, intValue = 0;
int?[] myIntArray = new int?[7] { 5, intValue, 9, 10, null, 2, 99 };//问号?的存在旨在让编译器知道这个int[]数组可以包含空对象
foreach (var integer in myIntArray)
{
switch (integer)
{
case 0://case 0用于检查在初始化一个整数时,其默认值是否为0(这一步必须进行)
WriteLine($"Integer number '{total}' has a default value of 0");
total++;//total是用来记录这个数组里一共有多少个元素
break;
case int value:
sum += value;//记录非0非null元素相加之和
WriteLine($"Integer number '{total}' has a value of {value}");
total++;
counter++;//用于记录一共出现了多少个非0非null的元素
break;
case null:
WriteLine($"Integer number '{total}' is null");
total++;
break;
default:
break;
}
}
WriteLine($"{total} total integers, {counter} integers with a " +
$"value other than 0 or null have a sum value of {sum}");
ReadLine();
}
}
}
4.多维数组
定义一个二维数组可以用以下的方式:
<baseType>[,] = <name>;
定义多维数组只需要增加逗号即可:
<baseType>[,,,] <name>;
声明和初始化二维数组hillHeight:
double [,] hillHeight = new doubel[3,4];
还可以使用字面值进行初始赋值:
double [,] hillHeight = {{1,2,3,4},{2,3,4,5},{3,4,5,6}};
通过字面值进行初始赋值的数组其维度与之前相同,通过隐式定义了这些维度。
利用foreach循环可以访问多位数组中的所有元素,其访问方式与访问一维数组相同:
double [,] hillHeight = {{1,2,3,4},{2,3,4,5},{3,4,5,6}};
foreach(double height in hillHeight)
{
writeLine($"{height}");
}
元素的输出顺序与赋予字面值的顺序相同。
5.数组的数组
当矩阵中的元素,每行中的元素个数不同,构成一个锯齿型的数组。需要有一个这样的数组,其中的每个元素都是另一个数组,也可以有数组的数组的数组,甚至是更复杂的数组。
声明数组的数组时,其语法要求在数组的声明中指定多个方括号:
int [][] jaggedIntArray;
初始化数组的数组有以下两种方式:
1)可以初始化包含其他数组的数组(称之为子数组),然后依次初始化子数组:
jaggedIntArray = new int[2][];
jaggedIntArray = new int[3];
jaggedIntArray = new int[4];
也可以使用上述字面值赋值的一种改进形式:
jaggedIntArray = new int [3][] {new int[] {1,2,3}, new int[] {1}, new int[] {1,2}};
也可以进行简化,把数组的初始化和声明放在同一行上:
int [][] jaggedIntArray = {new int[] {1,2,3}, new int[] {1}, new int[] {1,2}};
使用foreach对数组的数组进行循环,由于数组的数组其元素是数组,要想得到其中元素,就要用foreach进行循环的嵌套:
int [][] divisors1To10 = { new int[] {1},
new int[] {1,2},
new int[] {1,2,3},
new int[] {1,2,3,4},
new int[] {1,2,3,4,5},
new int[] {1,2,3,4,5,6},
new int[] {1,2,3,4,5,6,7},
new int[] {1,2,3,4,5,6,7,8},
new int[] {1,2,3,4,5,6,7,8,9},
new int[] {1,2,3,4,5,6,7,8,9,10}
};
foreach(int[] divisorsOfInt in divisors1To10)//先提取数组的数组里面的每一个数组
{
foreach(int divisor in divisorsOfInt)//再提取数组中的每一个元素
{
WriteLine(divisor);
}
}
6.字符串的处理
string类型的变量可以看作是char变量的只读数组,这样的话可以使用下面的语法访问每个字符:
string myString = "A string";
char myChar = myString[1];
但是这样做并不能为各个字符赋值,为了获得一个可写的char数组,可以使用数组变量的ToCharArray()命令:
string myString = "A string";
char[] myChars = myString.ToCharArray();
更多字符串相关操作P74-75
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace Ch05Ex06
{
class Program
{
static void Main(string[] args)
{
string myString = "This is a test.";
char[] separator = { ' ' };//split命令将字符串在空格处分解开来
string[] myWords;
myWords = myString.Split(separator);
foreach (string word in myWords)
{
WriteLine($"{word}");
}
ReadKey();
}
}
}