C#是一种强类型语言,每个变量和常量都有一个类型,每个表达式也有一个类型。C#提供了多种数据类型,包括内置的数值类型、布尔类型、字符类型,以及用户自定义的结构类型、枚举类型、类类型、接口类型等。本文将介绍C#中的三个重要话题:值类型和引用类型的区别,类型转换的方法和注意事项,以及字符串类型的特点和用法。
值类型和引用类型的区别
值类型和引用类型是C#中两个主要的类别。值类型的变量包含了类型的实例,而引用类型的变量包含了对类型实例的引用。这意味着在赋值或传递参数时,值类型会复制实例,而引用类型只会复制引用。例如:
// 值类型
int x = 10; // x包含了10这个值
int y = x; // y也包含了10这个值,x和y是独立的
y = 20; // y的值改变为20,不影响x
Console.WriteLine(x); // 输出10
Console.WriteLine(y); // 输出20
// 引用类型
string s1 = "Hello"; // s1包含了对"Hello"这个字符串实例的引用
string s2 = s1; // s2也包含了对"Hello"这个字符串实例的引用,s1和s2指向同一个实例
s2 = "World"; // s2的引用改变为指向"World"这个字符串实例,不影响s1
Console.WriteLine(s1); // 输出Hello
Console.WriteLine(s2); // 输出World
值类型可以分为以下几种:
- 结构类型:用于封装数据和相关功能,例如内置的数值类型、字符类型、布尔类型等,以及用户自定义的结构。
- 枚举类型:由一组命名常数定义,表示一个选择或选择组合,例如System.DayOfWeek。
- 可为null值类型:表示其基础值类型的所有值及额外的null值,例如int?。
引用类型可以分为以下几种:
- 类类型:用于定义对象,包含数据成员和方法成员,例如System.String、System.Object等,以及用户自定义的类。
- 接口类型:用于定义一组抽象的方法和属性规范,由实现它们的类提供具体功能,例如System.IDisposable、System.IComparable等。
- 数组类型:用于存储同一种元素类型的固定长度或可变长度序列,例如int[]、string[,]等。
- 委托类型:用于表示可执行代码的引用,可以作为参数传递或作为返回值返回,例如System.Action、System.Func等。
## 类型转换的方法和注意事项
在C#中,有时需要将一个数据类型转换为另一个数据类型。例如,在数学运算中需要将整数转换为浮点数,在多态性中需要将基类转换为派生类,在字符串处理中需要将数字转换为字符串等。C#提供了以下几种方法进行数据转换:
- 隐式转换:由于这种转换始终会成功且不会导致数据丢失,因此无需使用任何特殊语法。示例包括从较小整数类型到较大整数类型的转换以及从派生类到基类的转换的情况。例如:
// 隐式转换
int i = 10; // i是int类型
long l = i; // l是long类型,可以存储int类型的任何值
Animal a = new Dog(); // a是Animal类型,Dog是Animal的派生类
// 显式转换(强制转换)
double d = 3.14; // d是double类型
int j = (int)d; // j是int类型,需要强制转换,可能丢失小数部分
Dog d = (Dog)a; // d是Dog类型,需要强制转换,可能在运行时失败
-用户定义的转换:用户可以定义自己的类型之间的隐式转换和显式转换,通过使用implicit和explicit关键字来重载转换运算符。例如:
// 用户定义的转换
class Celsius
{
public double Temperature { get; set; }
public Celsius(double temp) { Temperature = temp; }
// 隐式转换从Celsius到Fahrenheit
public static implicit operator Fahrenheit(Celsius c)
{
return new Fahrenheit((9.0 / 5.0) * c.Temperature + 32);
}
// 显式转换从Celsius到Kelvin
public static explicit operator Kelvin(Celsius c)
{
return new Kelvin(c.Temperature + 273.15);
}
}
class Fahrenheit
{
public double Temperature { get; set; }
public Fahrenheit(double temp) { Temperature = temp; }
}
class Kelvin
{
public double Temperature { get; set; }
public Kelvin(double temp) { Temperature = temp; }
}
class TestTemperature
{
static void Main()
{
Celsius c = new Celsius(37);
Fahrenheit f = c; // 隐式转换
Kelvin k = (Kelvin)c; // 显式转换
Console.WriteLine(#34;Celsius: {c.Temperature}, Fahrenheit: {f.Temperature}, Kelvin: {k.Temperature}");
}
}
// 输出:Celsius: 37, Fahrenheit: 98.6, Kelvin: 310.15
-使用帮助类进行转换:如果要在不兼容的类型之间进行转换,例如整数和日期,或者十六进制字符串和字节数组,可以使用一些帮助类来实现。例如System.BitConverter, System.Convert, System.Int32等。例如:
// 使用帮助类进行转换
int n = 1234567890;
byte[] bytes = BitConverter.GetBytes(n); // 将整数转换为字节数组
string hex = BitConverter.ToString(bytes); // 将字节数组转换为十六进制字符串
Console.WriteLine(hex); // 输出:D2-02-96-49
string s = "123";
int m = Convert.ToInt32(s); // 将字符串转换为整数
Console.WriteLine(m); // 输出:123
string t = "FF";
int x = Int32.Parse(t, System.Globalization.NumberStyles.HexNumber); // 将十六进制字符串解析为整数
Console.WriteLine(x); // 输出:255
在进行类型转换时,需要注意以下几点:
- 不要假设类型转换总是成功的,要使用try-catch语句来处理可能发生的异常,或者使用TryParse等方法来尝试转换并检查返回值。
- 不要滥用隐式转换和用户定义的转换,以免造成代码的可读性和可维护性下降。
- 不要忽略类型转换可能导致的精度损失或溢出问题,要根据实际情况选择合适的数据类型和转换方式。
- 不要对null值进行类型转换,否则会引发NullReferenceException异常。
## 字符串类型的特点和用法
字符串是值为文本的String类型对象。文本在内部存储为Char对象的依序只读集合。在C#字符串末尾没有null终止字符;因此,一个C#字符串可以包含任何数量的嵌入的null字符('\0')。字符串的Length属性表示其包含的Char对象数量,而非Unicode字符数。要访问字符串中的各个Unicode码位,请使用StringInfo对象。
string与System.String
在C#中,string关键字是String的别名。因此,String和string是等效的(虽然建议使用提供的别名string),因为即使不使用using System;,它也能正常工作。String类提供了安全创建、操作和比较字符串的多种方法。此外,C#语言重载了部分运算符,以简化常见字符串操作。
声明和初始化字符串
可以使用各种方法声明和初始化字符串,如以下示例中所示:
// 声明但不初始化
string message1;
// 初始化为null
string message2 = null;
// 初始化为空字符串
// 使用Empty常量而不是字面量""
string message3 = System.String.Empty;
// 使用普通字符串字面量初始化
string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";
// 使用逐字字符串字面量初始化
string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";
// 如果你喜欢,可以使用System.String
System.String greeting = "Hello World!";
// 在局部变量(即方法体内)中
// 你可以使用隐式类型
var temp = "I'm still a strongly-typed System.String!";
// 使用const string来防止'message4'被
// 用来存储另一个字符串值
const string message4 = "You can't get rid of me!";
// 只有在从char*、char[]或sbyte*创建字符串时才使用String构造函数。
// 有关详细信息,请参阅System.String文档。
char[] letters = { 'A', 'B', 'C' };
string alphabet = new string(letters);
不要使用new运算符创建字符串对象,除非使用字符数组初始化字符串。使用Empty常量值初始化字符串,以新建字符串长度为零的String对象。长度为零的字符串文本表示法是“”。通过使用Empty值(而不是null)初始化字符串,可以减少NullReferenceException发生的可能性。尝试访问字符串前,先使用静态IsNullOrEmpty(String)方法验证字符串的值。
字符串的不可变性
字符串对象是“不可变的”:它们在创建后无法更改。看起来是在修改字符串的所有String方法和C#运算符实际上都是在新的字符串对象中返回结果。在下面的示例中,当s1和s2的内容被串联在一起以形成单个字符串时,两个原始字符串没有被修改。+=运算符创建一个新的字符串,其中包含组合的内容。这个新对象被分配给变量s1,而分配给s1的原始对象被释放,以供垃圾回收,因为没有任何其他变量包含对它的引用。
// 声明但不初始化
string message1;
// 初始化为null
string message2 = null;
// 初始化为空字符串
// 使用Empty常量而不是字面量""
string message3 = System.String.Empty;
// 使用普通字符串字面量初始化
string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";
// 使用逐字字符串字面量初始化
string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";
// 如果你喜欢,可以使用System.String
System.String greeting = "Hello World!";
// 在局部变量(即方法体内)中
// 你可以使用隐式类型
var temp = "I'm still a strongly-typed System.String!";
// 使用const string来防止'message4'被
// 用来存储另一个字符串值
const string message4 = "You can't get rid of me!";
// 只有在从char*、char[]或sbyte*创建字符串时才使用String构造函数。
// 有关详细信息,请参阅System.String文档。
char[] letters = { 'A', 'B', 'C' };
string alphabet = new string(letters);
字符串的比较和排序
字符串的比较和排序是一个复杂的话题,因为不同的场景可能需要不同的规则和逻辑。在C#中,有两种基本的比较方式:序号比较和语义比较。
序号比较是按照字符串中每个字符的二进制值进行比较,不考虑语言或区域性的规则。序号比较通常用于测试字符串是否相等,或者在不需要区分大小写或区域性的情况下对字符串进行排序。序号比较可以分为区分大小写和不区分大小写两种。
语义比较是按照字符串中每个字符的语言含义进行比较,考虑区域性或文化的规则。语义比较通常用于对字符串进行排序,或者在需要区分大小写或区域性的情况下测试字符串是否相等。语义比较可以分为当前区域性和固定区域性两种。
C#提供了多种方法来进行字符串的比较和排序,例如:
- String.Equals方法用于测试两个字符串是否相等,可以指定StringComparison参数来选择不同的比较方式。
- String.Compare方法用于比较两个字符串在排序顺序中的相对位置,返回一个整数值,可以指定StringComparison参数来选择不同的比较方式。
- String.CompareTo方法用于比较当前字符串对象与另一个字符串或对象的字符串表示形式,在排序顺序中的相对位置,返回一个整数值。
- String.Contains方法用于判断当前字符串是否包含指定的子字符串,返回一个布尔值。
- String.IndexOf方法用于查找指定字符或子字符串在当前字符串中第一次出现的索引位置,返回一个整数值,可以指定开始搜索的位置和StringComparison参数来选择不同的比较方式。
- String.LastIndexOf方法用于查找指定字符或子字符串在当前字符串中最后一次出现的索引位置,返回一个整数值,可以指定开始搜索的位置和StringComparison参数来选择不同的比较方式。
- String.StartsWith方法用于判断当前字符串是否以指定的子字符串开头,返回一个布尔值,可以指定StringComparison参数来选择不同的比较方式。
- String.EndsWith方法用于判断当前字符串是否以指定的子字符串结尾,返回一个布尔值,可以指定StringComparison参数来选择不同的比较方式。
下面是一些示例代码:
// 序号比较
string s1 = "Hello";
string s2 = "hello";
bool b1 = s1.Equals(s2); // false
bool b2 = s1.Equals(s2, StringComparison.Ordinal); // false
bool b3 = s1.Equals(s2, StringComparison.OrdinalIgnoreCase); // true
int i1 = s1.CompareTo(s2); // -32
int i2 = String.Compare(s1, s2, StringComparison.Ordinal); // -32
int i3 = String.Compare(s1, s2, StringComparison.OrdinalIgnoreCase); // 0
// 语义比较
string s3 = "Stra?e";
string s4 = "Strasse";
bool b4 = s3.Equals(s4); // false
bool b5 = s3.Equals(s4, StringComparison.CurrentCulture); // false
bool b6 = s3.Equals(s4, StringComparison.InvariantCulture); // true
int i4 = s3.CompareTo(s4); // 1
int i5 = String.Compare(s3, s4, StringComparison.CurrentCulture); // 1
int i6 = String.Compare(s3, s4, StringComparison.InvariantCulture); // 0
// 其他方法
string s5 = "Hello World";
bool b7 = s5.Contains("World"); // true
int i7 = s5.IndexOf('o'); // 4
字符串的拼接和分割
字符串的拼接和分割是指将多个字符串连接成一个字符串,或者将一个字符串按照一定的规则切分成多个子字符串。在C#中,有多种方法可以实现字符串的拼接和分割,例如:
- +和+=运算符用于简单地将两个或多个字符串连接起来,例如:
string s1 = "Hello";
string s2 = "World";
string s3 = s1 + " " + s2; // "Hello World"
s1 += " "; // "Hello "
s1 += s2; // "Hello World"
- 字符串插值用于在一个字符串字面量中插入其他变量或表达式的值,例如:
string name = "Alice";
int age = 25;
string s4 = #34;Hello, {name}. You are {age} years old."; // "Hello, Alice. You are 25 years old."
- String.Format方法用于根据指定的格式字符串和参数列表创建一个新的字符串,例如:
string name = "Bob";
int age = 30;
string s5 = String.Format("Hello, {0}. You are {1} years old.", name, age); // "Hello, Bob. You are 30 years old."
- StringBuilder类用于在循环或其他性能敏感的场景中动态地构建字符串,例如:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++)
{
sb.Append(i.ToString());
}
string s6 = sb.ToString(); // "0123456789"
- String.Concat方法用于将一个字符串数组或集合中的所有元素连接成一个字符串,例如:
string[] words = {"The", "quick", "brown", "fox"};
string s7 = String.Concat(words); // "Thequickbrownfox"
- String.Join方法用于将一个字符串数组或集合中的所有元素使用指定的分隔符连接成一个字符串,例如:
string[] words = {"The", "quick", "brown", "fox"};
string s8 = String.Join(" ", words); // "The quick brown fox"
- String.Split方法用于将一个字符串按照指定的字符数组或字符串数组作为分隔符切分成一个子字符串数组,例如:
string s9 = "The quick brown fox jumps over the lazy dog.";
string[] words = s9.Split(' '); // {"The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog."}
string[] sentences = s9.Split('.'); // {"The quick brown fox jumps over the lazy dog", ""}
本文暂时没有评论,来添加一个吧(●'◡'●)