目录
5.1 类型转换
(1)复杂类型
枚举:一种变量类型,用户定义了一组可能的离散值,这些值可以用人们能理解的方式使用。
结构:一种合成的变量类型,由用户定义的一组其他变量类型组成。
数组:—包含一种类型的多个变量,允许以索引方式访问各个数值。
(2)类型转换
隐式转换: 从类型A到类型B的转换可在所有情况下进行,执行转换的规则非常简单,可以让编译器执行转换。
显式转换: 从类型A到类型B的转换只能在某些情况下进行,转换规则比较复杂,应进行某种类型的额外处理。
5.1.1 隐式转换
隐式转换不需要做任何工作,也不需要另外编写代码。
隐式数值转换
序号 | 类型 | 可以安全地转换为 |
1 | byte | short, ushort, int, uint, long, ulong, float, double, decimal |
2 | sbyte | short, int, long, float, double, decimal |
3 | short | int, long, float, double, decimal |
4 | ushort | int, uint, long, ulong, float, double, decimal |
5 | int | long, float, double, decimal |
6 | uint | long, ulong, float, double, decimal |
7 | long | float, double, decimal |
8 | ulong | float, double, decimal |
9 | float | double |
10 | char | ushort, int, uint, long, ulong, float, double, decimal |
如果要把一个值放在变量中,而该值超出了变量的取值范围,就会出问题。例如,short类型的变量可以存储0-32 767的数字,而byte可以存储的最大值是255,所以如果要把short值转换为byte值,就会出问题。如果short包含的值在256和32 767之间,相应数值就不能放在byte中。
如果short类型变量中的值小于255,就应能转换这个值吗?答案是可以。具体地说,虽然可以,但必须使用显式转换。执行显式转换有点类似于“我已经知道你对我这么做提出了警告,但我将对其后果负责”。
5.1.2 显示转换
明确要求编译器把数值从一种数据类型转换为另一种数据类型时,就是在执行显式转换。因此,这需要另外编写代码,代码的格式因转换方法而异。
static void Main(string[] args)
{
byte destinationVar;
short sourceVar = 7;
destinationVar = (byte)sourceVar;
WriteLine($"sourceVar val: {sourceVar}");
WriteLine($"destinationVar val: {destinationVar}");
ReadKey();
}
运行结果:
static void Main(string[] args)
{
byte destinationVar;
short sourceVar = 7;
destinationVar = sourceVar;
WriteLine($"sourceVar val: {sourceVar}");
WriteLine($"destinationVar val: {destinationVar}");
ReadKey();
}
运行结果:
sourceVar val: 7
destinationVar val: 7
//强制转换,数据丢失
short sourceVar = 281;
byte destinationVar;
destinationVar = (byte)sourceVar;
WriteLine($"sourceVar val: {sourceVar}");
WriteLine($"destinationVar val: {destinationVar}");
运行结果:
sourceVar val: 281
destinationVar val: 25
原因分析:
看看这两个数字的二进制表示,以及可以存储在byte中的最大值255:
281 = 100011001
25 = 000011001
255 = 011111111
源数据的最左边一位丢失了。这会引发一个问题:如何确定数据是何时丢失的?显然,当需要显式地把一种数据类型转换为另一种数据类型时,最好能够了解是否有数据丢失了。如果不知道这些,就会发生严重问题。
对于为表达式设置所谓的溢出检查上下文,需要用到两个关键字——checked和unchecked。按下述方式使用这两个关键字:
checked(<expression>)
unchecked(<expression>)
//强制转换,进行溢出检查
short sourceVar = 281;
byte destinationVar;
destinationVar = checked((byte)sourceVar);
WriteLine($"sourceVar val: {sourceVar}");
WriteLine($"destinationVar val: {destinationVar}");
运行结果:
5.1.3 通过方法转换
(1)使用ToString()方法。所有类型都继承了Object基类,所以都有ToString()这个方法(转化成字符串的方法)。
int i=200;
string s=i.ToString();
//这样字符串类型变量s的值就是”200” 。
(2)通过int.Parse()方法转换,参数类型只支持string类型。
注意:使用该方法转换时string的值不能为NULL,不然无法通过转换;另外string类型参数也只能是各种整型,不能是浮点型,不然也无法通过转换 (例如int.Parse("2.0")就无法通过转换)。
int i;
i = int.Parse("100");
(3)通过int.TryParse()方法转换,该转换方法与int.Parse()转换方法类似,不同点在于int.Parse()方法无法转换成功的情况该方法能正常执行并返回0。也就是说int.TryParse()方法比int.Parse()方法多了一个异常处理,如果出现异常则返回false,并且将输出参数返回0。
int i;
string s = null;
int.TryParse(s,out i);
bool isSucess=int.TryParse("12", out i);//输出值为12;True
bool isSucess=int.TryParse("ss", out i);//输出值为0;False
bool isSucess=int.TryParse("", out i);//输出值为0;False
(4)通过Convert类进行转换,Convert类中提供了很多转换的方法。使用这些方法的前提是能将需要转换的对象转换成相应的类型,如果不能转换则会报格式不对的错误(即前提为面上要过得去例如string类型的“666”可以转换为整数型666,string类型的“666aaa”却转换不成整数型 )。
static void Main(string[] args)
{
float num1=82.26f;
int integer,num2;
string str,strdate;
DateTime mydate=DateTime.New;
//Convert类的方法进行转换
integer=Convert.ToInt32(num1);
str=Convert.ToString(num1);
strdate=Convert.ToString(mydate);
num2=Convert.ToInt32(mydate);
Console.WriteLine("转换为整型数据的值{0}",integer);
Console.WriteLine("转换为字符串{0},str");
Console.WriteLine("日期型数据转换为字符串值为{0}",strdate);
Console.ReadKey();
}
(5实现自己的转换,通过继承接口IConventible或者TypeConventer类,从而实现自己的转换。
注意:以Int类型为例,int.Parse,Convert.ToInt和int.TryParse的比较
【1】.参数和适用对象不同
int.Parse的参数数据类型只能是string类型,适用对象为string类型的数据
convert.toInt参数比较多,具体可以参见最下面的重载列表
int.TryParse的参数只能是只能是string类型,适用对象为string类型的数据
【2】.异常情况不同
异常主要是针对数据为null或者为""的情况
Convert.ToInt32 参数为 null 时,返回 0;Convert.ToInt32 参数为 "" 时,抛出异常; int.Parse 参数为 null 时,抛出异常。; int.Parse 参数为 "" 时,抛出异常。int.TryParse()方法 比int.Parse()方法多了一个异常处理,如果出现异常则返回false,并且将输出参数返回0。
【3】.返回值不同
int.TryParse与int.Parse和Convert.ToInt 在返回值的不同是返回bool类型。获取转换后的值是通过out result这个参数获取的。
序号 | 方法 | 描述 |
1 | ToBoolean | 如果可能的话,把类型转换为布尔型。 |
2 | ToByte | 把类型转换为字节类型。 |
3 | ToChar | 如果可能的话,把类型转换为单个 Unicode 字符类型。 |
4 | ToDateTime | 把类型(整数或字符串类型)转换为 日期-时间 结构。 |
5 | ToDecimal | 把浮点型或整数类型转换为十进制类型。 |
6 | ToDouble | 把类型转换为双精度浮点型。 |
7 | ToInt16 | 把类型转换为 16 位整数类型。 |
8 | ToInt32 | 把类型转换为 32 位整数类型。 |
9 | ToInt64 | 把类型转换为 64 位整数类型。 |
10 | ToSbyte | 把类型转换为有符号字节类型。 |
11 | ToSingle | 把类型转换为小浮点数类型。 |
12 | ToString | 把类型转换为字符串类型。 |
13 | ToType | 把类型转换为指定类型。 |
14 | ToUInt16 | 把类型转换为 16 位无符号整数类型。 |
15 | ToUInt32 | 把类型转换为 32 位无符号整数类型。 |
16 | ToUInt64 | 把类型转换为 64 位无符号整数类型。 |
例子
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;
WriteLine($"Implicit, -> double: {floatVal} * {shortVal} -> { doubleResult }");
shortResult = (short)floatVal;
WriteLine($"Explicit, -> short: {floatVal} -> {shortResult}");
stringResult = Convert.ToString(boolVal) +
Convert.ToString(doubleVal);
WriteLine($"Explicit, -> string: \"{boolVal}\" + \"{doubleVal}\" -> " +$"{stringResult}");
longResult = integerVal + Convert.ToInt64(stringVal);
WriteLine($"Mixed, -> long: {integerVal} + {stringVal} -> {longResult}");
ReadKey();
运行结果
Variable Conversion Examples
Implicit, -> double: 10.5 * 4 -> 42
Explicit, -> short: 10.5 -> 10
Explicit, -> string: "True" + "99.999" -> True99.999
Mixed, -> long: 67 + 17 -> 84
5.2 复杂的变量类型
5.2.1 枚举
枚举的基本类型可以是byte、sbyte、short、ushort、int、uint、long和ulong。
定义枚举
enum<typeName>
{
<value1>,
<value2>,
<value3>,
...
<valueN>
}
申明变量并赋值
<typeName><varName>;
<varName> =<typeName>.<value>;
例子:
enum orientation : byte
{
north = 1,
south = 2,
east = 3,
west = 4,
}
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;
directionString = Convert.ToString(myDirection);
WriteLine($"byte equivalent = {directionByte}");
WriteLine($"string equivalent = {directionString}");
}
运行结果:
myDirection = north
byte equivalent = 1
string equivalent = north
5.2.2 结构
结构就是由几个数据组成的数据结构,这些数据可能具有不同的类型。
根据这个结构,可以定义自己的变量类型。
定义结构
struct<typeName>
{
<memberDeclarations>
}
<memberDeclarations >部分包含变量的声明(称为结构的数据成员),其格式与前面的变量声明一样。每个成员的声明都采用如下形式:
<accessibility><type><name>;
例子:
enum orientation : byte
{
north = 1,
south = 2,
east = 3,
west = 4
}
struct route
{
public orientation direction;
public double distance;
}
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 = Convert.ToInt32(ReadLine());
}
while ((myDirection < 1) || (myDirection > 4));
WriteLine("Input a distance:");
myDistance = Convert.ToDouble(ReadLine());
myRoute.direction = (orientation)myDirection;
myRoute.distance = myDistance;
WriteLine($"myRoute specifies adirection of {myRoute.direction}" +$"and a distance of {myRoute.distance}.");
ReadKey();
}
运行结果:
1) North
2) South
3) East
4) West
Select a direction:
5
Select a direction:
7
Select a direction:
2
Input a distance:
168
myRoute specifies adirection of southand a distance of 168.
5.2.3 数组
数组有一个基本类型,数组中的各个条目都是这种类型。
数组的条目通常称为元素。
声明数组
<baseType> []<name>;
注意:<baseType >可以是任何变量类型,包括枚举和结构类型。数组必须在访问之前初始化。
数组的初始化有两种方式。
可以字面值形式指定数组的完整内容,也可以指定数组的大小,再使用关键字new初始化所有数组元素。要使用字面值指定数组,只需提供一个用逗号分隔的元素值列表,该列表放在花括号中。
int[] myIntArray = { 5, 9, 10, 2, 99 };
使用关键字new显式地初始化数组,用一个常量值定义其大小。这种方式会给所有数组元素赋予同一个默认值,对于数值类型来说,其默认值是0。也可以使用非常量的变量来进行初始化。
int[] myIntArray = new int[arraySize];
可以组合使用这两种初始化方式:
int[] myIntArray = new int[5] { 5, 9, 10, 2, 99 };
如果使用变量定义其大小,该变量必须是一个常量。如果省略了关键字const,就会失败。
const int arraySize = 5;
int[] myIntArray = new int[arraySize] { 5, 9, 10, 2, 99 };
例子
//数组定义方法一
int []myArray1 = { 2, 4, 6, 8, 10 };
//数组定义方法二
int[] myArray2 = new int[5] { 1,2,3,4,5};
//数组定义方法三
int[] myArray3 = new int[3];
myArray3[0] = 3;
myArray3[1] = 6;
myArray3[2] = 9;
数组元素遍历
数组遍历方法有两种:for循环、foreach循环
for循环:①提取数组长度;②循环遍历数组元素;
foreach循环:循环取出数组元素
foreach语法foreach (<baseType><name> in<array>) { // can use<name> for each element }
foreach (<baseType><name> in<array>)
{
// can use<name> for each element
}
解释:
这个循环会迭代每个元素,依次把每个元素放在变量<name >中,且不存在访问非法元素的危险。不需要考虑数组中有多少个元素,并可以确保将在循环中使用每个元素。
例子1:for循环
static void Main(string[] args)
{
string[] friendNames = { "Andy", "Bob", "Tony" };
int i;
WriteLine($"Here are {friendNames.Length} of my friends:");
//for循环遍历数组元素
for (i = 0; i < friendNames.Length; i++)
WriteLine(friendNames[i]);
ReadKey();
}
例子2:foreach循环
static void Main(string[] args)
{
string[] friendNames = { "Andy", "Bob", "Tony" };
int i;
WriteLine($"Here are {friendNames.Length} of my friends:");
//for循环遍历数组元素
foreach (string friendname in friendnames)
WriteLine(friendname);
ReadKey();
}
运行结果:
Here are 3 of my friends:
Andy
Bob
Tony
多维数组
<baseType>[,]<name>;
<baseType>[,,,]<name>;
例子:
double[,] hillHeight = new double[3,4];
例子
static void Main()
{
int[,] hillHeight =new int[3,4] { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
WriteLine(hillHeight[1,2]);
WriteLine(hillHeight.Length);
foreach (int k in myArray)
Write(k+"\t");
}
运行结果:
7
12
1 2 3 4 5 6 7 8 9 10 11 12
数组的数组(锯齿数组)
以使用锯齿数组(jagged array),其中每行的元素个数可能不同。为此,需要有这样一个数组,其中的每个元素都是另一个数组。也可以有数组的数组的数组,甚至更复杂的数组。但是,注意这些数组都必须有相同的基本类型。
//锯齿数组定义1
int[][] jaggedIntArray1;
jaggedIntArray1 = new int[3][] { new int[] { 1, 2, 3 }, new int[] { 1 }, new int[] { 1, 2 } };
//锯齿数组定义2
int[][] jaggedIntArray2 = { new int[] { 1, 2, 3 }, new int[] { 1 }, new int[] { 1, 2 } };
锯齿数组遍历
foreach (int[] divisorsOfInt in jaggedIntArray1)
foreach (int divisor in divisorsOfInt)
WriteLine(divisor);
5.3 字符串的处理
string类型的变量可以看成char变量的只读数组。这样,就可以使用下面的语法访问每个字符。
string myString = "A string";
char myChar = myString[1];
<string>.ToCharArray() 将字符串转换为数组
<string>.Length 获取元素个数
<string >.ToLower() 将字符串转换为小写
<string >.ToUpper() 将字符串转换为大写
例子
string myString = "A string";
char[] myChars = myString.ToCharArray();
//字符串长度
WriteLine($"myString.Length = {myString.Length}.");
//字符串转换为小写
WriteLine($"myString.ToLower() = {myString.ToLower()}");
//字符串转换为大写
WriteLine($"myString.ToUpper() = {myString.ToUpper()}");
WriteLine("myString:");
foreach (char menber1 in myString)
Write($"{menber1}\t");
WriteLine("\nmyChars:");
foreach (char menber2 in myChars)
Write($"{menber2}\t");
ReadKey();
运行结果
myString.Length = 8.
myString.ToLower() = a string
myString.ToUpper() = A STRING
myString:
A s t r i n g
myChars:
A s t r i n g
语法:
<string >.Trim() 删除输入字符串前后的空格
<string >.TrimStart() 把字符串前面的空格删掉
<string >.TrimEnd() 把字符串后面的空格删掉
<string >.PadLeft() 在字符串的左边添加空格,使字符串达到指定的长度
<string >.PadRight() 在字符串的右边添加空格,使字符串达到指定的长度
例子
string String= " Hello World ! ";
WriteLine($"String.Trim() = {String.Trim()}");
WriteLine($"String.TrimStart() = {String.TrimStart()}");
WriteLine($"String.TrimEnd() = {String.TrimEnd()}");
WriteLine($"String.PadLeft(50) = {String.PadLeft(50)}");
WriteLine($"String.PadRight(10) = {String.PadRight(10)}");
运行结果
String.Trim() = Hello World !
String.TrimStart() = Hello World !
String.TrimEnd() = Hello World !
String.PadLeft(50) = Hello World !
String.PadRight(10) = Hello World !
语法:
<string >.Split(char) 按照char将字符串切割成小的字符串
例子:
//按照空格切割字符串,形成数组。
string myString ="My name is Tony.";
char separator=' ';
string[] mywords = myString.Split(separator);
foreach (string word in mywords)
WriteLine($"{word}");
运行结果:
My
name
is
Tony.
5.4 练习
(1)下面的转换哪些不是隐式转换?
a. int转换为short
b. short转换为int
c. bool转换为string
d. byte转换为float
答案:
a 不是,int-32 位有符号整数类型;short-16 位有符号整数类型
b 是
c 不是,bool-布尔值;string-引用类型;
d 是,byte-8位无符号整数;float-32为当进度浮点型
(2)以short类型作为基本类型编写一个color枚举,使其包含彩虹的颜色加上黑色和白色。这个枚举可使用byte类型作为基本类型吗?
答案:
enum color : short
{
Red, Orange, Yellow, Green, Blue, Indigo, Violet, Black, White
}
可以,byte类型可以包含0~255之间的数字。
如果枚举项使用不同值,基于byte的枚举可以包含256项;如果给枚举项使用重复的值,就可以包含更多的项。
(3)下面的代码可以成功编译吗?为什么?
string[] blab = new string[5] blab[5] = 5th string.
答案:
无法编译代码,
原因如下:
遗漏了语句末尾的分号。
第二行尝试访问blab中不存在的第6个元素。
第二行尝试指定未包含在双引号中的字符串。
修改
string[] blab = new string[5];
blab[4] = "5th string";
(4)编写一个控制台应用程序,它接收用户输入的一个字符串,将其中的字符以与输入相反的顺序输出。
答案:
//接收输入的字符,以相反的书序输出
WriteLine("Please input a string:");
string originalString =ReadLine();
int Num = originalString.Length;
string inverseString="";
for (int i = Num - 1; i >= 0; i--)
{
Write($"{originalString[i]}");
inverseString += originalString[i];
}
WriteLine($"\ninverseString = {inverseString}");
ReadKey();
运行结果:
Please input a string:
My name is tony .
. ynot si eman yM
inverseString = . ynot si eman yM
(5)编写一个控制台应用程序,它接收一个字符串,用yes替换字符串中所有的no。
答案:
WriteLine("Enter a string: ");
string myString = ReadLine();
myString = myString.Replace("no ", "yes ");
WriteLine($"Replaced \"no\" with \"yes\":\n {myString} ");
运行结果:
Enter a string:
no that is not a girl .
Replaced "no" with "yes":
yes that is not a girl .
(6)编写一个控制台应用程序,给字符串中的每个单词加上双引号。
答案:
WriteLine("Please input a sentence:");
string myString = ReadLine();
char blankSpace = ' ';
string[] myWords = myString.Split(blankSpace);
foreach (string word in myWords)
WriteLine($"\"{word}\"");
运行结果:
Please input a sentence:
my name is tony .
"my"
"name"
"is"
"tony"
"."
5.5 本章要点
序号 | 主题 | 要点 |
1 | 类型转换 | 值可以从一种类型转换为另一种类型,但在转换时应遵循一些规则。隐式转换是自动进行的,但只有当源值类型的所有可能值都可以在目标值类型中使用时,才能进行隐式转换。也可以进行显式转换,但可能得不到期望的值,甚至可能出错 |
2 | 枚举 | 枚举是包含一组离散值的类型,每个离散值都有一个名称。枚举用enum关键字定义,以便在代码中理解它们,因为它们的可读性都很高。枚举有基本的数值类型(默认是int),可使用枚举值的这个属性在枚举值和数值之间转换,或者标识枚举值 |
3 | 结构 | 结构是同时包含几个不同值的类型。结构用struct关键字定义。包含在结构中的每个值都有名称和类型,存储在结构中的每个值的类型不一定相同 |
4 | 数组 | 数组是同类型数值的集合。数组有固定的大小或长度,确定了数组可以包含多少个值。可以定义多维数组或锯齿数组来保存不同数量和形状的数据。还可以使用foreach循环来迭代数组中的值 |