数据类型
C# 是一种 强类型语言。 这意味着我们必须声明一个变量的类型,该类型指示它将存储的值的类型,例如整数、浮点数、小数点、文本等
下面声明并初始化了不同数据类型的变量
csharp
int num = 100;
float rate = 10.2f; //浮点型要加 f
double a = 10.01d;// 双精度浮点型 加d
decimal amount = 100.50M; //decimal 固定要加m
char code = 'C';
bool isValid = true;
string name = "Steve";
DateTime dateTime = DateTime.Now;
C#主要将数据类型分为两种类型:值类型 和 引用类型。 值类型包括简单类型(如 int、float、bool 和 char)、枚举类型、结构类型和 Nullable 值类型。 引用类型包括类类型、接口类型、委托类型和数组类型
数据类型概述
值类型
类型 | .net 对应的类型 | 范围 | 默认值 |
---|---|---|---|
bool | 布尔值 | True 或 False | False |
char | 16 位 Unicode 字符 | U +0000 到 U +ffff | '\0' |
decimal | 128 位精确的十进制值,28-29 有效位数 | (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 | 0.0M |
float | 32 位单精度浮点型 | -3.4 x 1038 到 + 3.4 x 1038 | 0.0F |
double | 64 位双精度浮点型 | (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 | 0.0D |
int | 2 位有符号整数类型 | -2,147,483,648 到 2,147,483,647 | 0 |
sbyte | 8 位有符号整数类型 | -128 到 127 | 0 |
short | 16 位有符号整数类型 | -32,768 到 32,767 | 0 |
long | 64 位有符号整数类型 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 0L |
byte | 8 位无符号整数 | 0 到 255 | 0 |
ushort | 16 位无符号整数类型 | 0 到 65,535 | 0 |
uint | 32 位无符号整数类型 | 0 到 4,294,967,295 | 0 |
ulong | 64 位无符号整数类型 | 0 到 18,446,744,073,709,551,615 | 0 |
DateTime | 时间 |
总结内容如下:
描述 | 类型 |
---|---|
Unicode 字符(16 位) | char |
布尔值 | bool |
有符号整数 | sbyte(8 位) < short(16 位) < int(32 位) < long(64 位) |
正整数 | byte(8 位) < ushort(16 位) < uint(32 位) < ulong(64 位) |
浮点数 | float(32 位) < double(64 位) |
时间 | DateTime |
变量声明示例:(需要指定类型)
csharp
int age = 20;
bool isStudent = true;
float grade = 4.2;
为了让范围加一个 null
,即 变量可为 null(空) ,则需要在声明时加个 ?
,我们称其为 可空类型。可空类型表示其基础值类型正常范围内的值,再加上一个 null 值。
例如,Nullable< Int32 >,读作"可空的 Int32",可以被赋值为 -2,147,483,648 到 2,147,483,647 之间的任意值,也可以被赋值为 null 值。类似的,Nullable< bool > 变量可以被赋值为 true 或 false 或 null。
csharp
int age = null; ×(报错)
int? age = null; √(正常)
age = 20;
引用类型
引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。
换句话说,它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。内置的 引用类型有:object 、dynamic 和 string。
对象(Object)类型
对象(Object)类型 是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。
当一个值类型转换为对象类型时,则被称为 装箱 ;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱。
csharp
object obj;
obj = 100; // 这是装箱
动态(Dynamic)类型
您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。
声明动态类型,举例:
csharp
dynamic d = 20;
动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时 发生的,而动态类型变量的类型检查是在运行时发生的。对象(Object)类型更常用一些。
字符串(String)类型
字符串(String)类型 允许您给变量分配任何字符串值。字符串(String)类型是 System.String 类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:"" 和 @""
例如:
csharp
string str = "runoob.com";
string text = "This is a \"string\" in C#.";
string lines = "hello\r\n my name is \r\n malema.net";
在一个文本有多行的情况下,我们还可以用 @ 号来定义:
csharp
string lines = @"hello
my name is
malema.net";
在使用@的时候如果我们还想在字符串里面放到双引号"的话 我们会发现这个时候没有办法用转义字符。 我们必须得用 ""两个双引号来表示一个双引号
csharp
string lines = @"hello
my name is\""
malema.net";
C# string 字符串的前面可以加 @ 则将转义字符(\)当作普通字符对待
值类型和引用类型的存储方式
根据它们在内存中存储值的方式进行分类。 C# 数据类型有下面的三种分类:
- 值类型
- 引用类型
- 指针类型
栈和堆
当我们的代码执行时,内存中有两个地方用来存储这些代码。分别是栈和堆。栈和堆都用来帮助我们运行代码的,它们驻留在机器内存中,且包含所有代码执行所需要的信息。
- 栈是编译期间就分配好的内存空间,因此你的代码中必须就栈的大小有明确的定义;堆是程序运行期间动态分配的内存空间,你可以根据程序的运行情况确定要分配的堆内存的大小 存放在栈中时要管存储顺序,保持着先进后出的原则,他是一片连续的内存域,有系统自动分配和维护。
- 而堆是无序的,他是一片不连续的内存域,有用户自己来控制和释放,如果用户自己不释放的话,当内存达到一定的特定值时,通过垃圾回收器(GC)来回收。
值类型
值类型的数据是 放在栈当中 的。(因为它在定义的时候就可以知道它的具体大小了),(也叫放在自己声明的位置,要结合程序是如何运行的来理解)
值类型 参数值传递的特点
- 当您将值类型变量从一种方法传递到另一种方法时,系统会在另一种方法中创建变量的副本。 如果在一个方法中更改了值,则不会影响另一种方法中的变量。
- 同一个方法里面把一个变量赋给另一个变量,系统也是创建这个变量的副本
引用类型
与值类型不同,引用类型不直接存储其值 。 相反,它存储存储值的地址 。 换句话说,引用类型包含一个指向保存数据的另一个内存位置的指针(放在堆中)。
引用类型 传递的特点
当您将引用类型变量从一种方法传递到另一种方法时,它不会创建新副本; 相反,它传递变量的地址。 所以,如果我们在一个方法中改变了一个变量的值,它也会反映在调用方法中。 (在同一个方法中也是一样的)。
csharp
class Program
{
static void ChangeReferenceType(Student std2)
{
std2.Age = 200;
}
static void Main(string[] args)
{
Student std1 = new Student();
std1.Age = 100;
ChangeReferenceType(std1);
Console.WriteLine(std1.Age); // 200
var std2 = std1; // 改的是 std2 的值,但是 std1 的值也变了。
std2.Age = 300;
Console.WriteLine(std1.Age); // 300
}
}
public class Student
{
public int Age { get; set; }
}
类型判断
csharp
object obj = "hello";
if (obj.GetType() == typeof(string))
{
Console.WriteLine("obj is a string");
}
类型转换
C# 提供了下列内置的类型转换方法:
方法 | 描述 |
---|---|
ToBoolean | 如果可能的话,把类型转换为布尔型。 |
ToByte | 把类型转换为字节类型。 |
ToChar | 如果可能的话,把类型转换为单个 Unicode 字符类型。 |
ToDateTime | 把类型(整数或字符串类型)转换为 日期-时间 结构。 |
ToDecimal | 把浮点型或整数类型转换为十进制类型。 |
ToDouble | 把类型转换为双精度浮点型。 |
ToInt16 | 把类型转换为 16 位整数类型。 |
ToInt32 | 把类型转换为 32 位整数类型。 |
ToInt64 | 把类型转换为 64 位整数类型。 |
ToSbyte | 把类型转换为有符号字节类型。 |
ToSingle | 把类型转换为小浮点数类型。 |
ToString | 把类型转换为字符串类型。 |
ToType | 把类型转换为指定类型。 |
ToUInt16 | 把类型转换为 16 位无符号整数类型。 |
ToUInt32 | 把类型转换为 32 位无符号整数类型。 |
ToUInt64 | 把类型转换为 64 位无符号整数类型。 |
每个类型里面基本上都有一个 Parse
或者 TryParse
举例,字符串转数字:
csharp
if (int.TryParse("65", out int age))
{
// age 是 65
};
变量和常量
变量
定义:
csharp
int age = 24;
常量
变量声明前面多了个 const
定义:
csharp
const int age = 24;
代码实例:
csharp
using System;
public class ConstTest
{
class SampleClass
{
public int x;
public int y;
public const int c1 = 5;
public const int c2 = c1 + 5;
public SampleClass(int p1, int p2)
{
x = p1;
y = p2;
}
}
static void Main()
{
SampleClass mC = new SampleClass(11, 22);
Console.WriteLine("x = {0}, y = {1}", mC.x, mC.y);
Console.WriteLine("c1 = {0}, c2 = {1}",
SampleClass.c1, SampleClass.c2);
}
}
当上面的代码被编译和执行时,它会产生下列结果:
x = 11, y = 22 c1 = 5, c2 = 10
结构体 Struct
定义
为了定义一个结构体,您必须使用 struct 语句。struct 语句为程序定义了一个带有多个成员的新的数据类型。
例如,您可以按照如下的方式声明 Book 结构:
csharp
struct Books
{
public string title;
public string author;
public string subject;
public int book_id;
};
下面的程序演示了结构的用法:
csharp
using System;
using System.Text;
struct Books
{
public string title;
public string author;
public string subject;
public int book_id;
};
public class testStructure
{
public static void Main(string[] args)
{
Books Book1; /* 声明 Book1,类型为 Books */
Books Book2; /* 声明 Book2,类型为 Books */
/* book 1 详述 */
Book1.title = "C Programming";
Book1.author = "Nuha Ali";
Book1.subject = "C Programming Tutorial";
Book1.book_id = 6495407;
/* book 2 详述 */
Book2.title = "Telecom Billing";
Book2.author = "Zara Ali";
Book2.subject = "Telecom Billing Tutorial";
Book2.book_id = 6495700;
/* 打印 Book1 信息 */
Console.WriteLine("Book 1 title : {0}", Book1.title);
Console.WriteLine("Book 1 author : {0}", Book1.author);
Console.WriteLine("Book 1 subject : {0}", Book1.subject);
Console.WriteLine("Book 1 book_id :{0}", Book1.book_id);
/* 打印 Book2 信息 */
Console.WriteLine("Book 2 title : {0}", Book2.title);
Console.WriteLine("Book 2 author : {0}", Book2.author);
Console.WriteLine("Book 2 subject : {0}", Book2.subject);
Console.WriteLine("Book 2 book_id : {0}", Book2.book_id);
Console.ReadKey();
}
}
当上面的代码被编译和执行时,它会产生下列结果:
yaml
Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700
特点
- 结构可带有方法、字段、索引、属性、运算符方法和事件。
- 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
- 与类不同,结构不能继承其他的结构或类。
- 结构不能作为其他结构或类的基础结构。
- 结构可实现一个或多个接口。
- 结构成员不能指定为 abstract、virtual 或 protected。
- 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
- 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
类和结构体的区别
- 类是引用类型,结构是值类型。
- 结构 不支持继承。
- 结构不能声明默认的 构造函数。
以上实例重写
csharp
using System;
using System.Text;
struct Books
{
private string title;
private string author;
private string subject;
private int book_id;
public void setValues(string t, string a, string s, int id)
{
title = t;
author = a;
subject = s;
book_id =id;
}
public void display()
{
Console.WriteLine("Title : {0}", title);
Console.WriteLine("Author : {0}", author);
Console.WriteLine("Subject : {0}", subject);
Console.WriteLine("Book_id :{0}", book_id);
}
};
public class testStructure
{
public static void Main(string[] args)
{
Books Book1 = new Books(); /* 声明 Book1,类型为 Books */
Books Book2 = new Books(); /* 声明 Book2,类型为 Books */
/* book 1 详述 */
Book1.setValues("C Programming", "Nuha Ali", "C Programming Tutorial",6495407);
/* book 2 详述 */
Book2.setValues("Telecom Billing", "Zara Ali", "Telecom Billing Tutorial", 6495700);
Book1.display(); /* 打印 Book1 信息 */
Book2.display(); /* 打印 Book2 信息 */
Console.ReadKey();
}
}
结构和类的适用场合分析:
- 1、当堆栈的空间很有限,且 有大量的逻辑对象 时,创建类要比创建结构好一些;
- 2、对于点、矩形和颜色这样的 轻量 对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为 每个对象 分配内存,在这种情况下,使用结构的成本较低;
- 3、在表现 抽象 和 多级别 的对象层次时,类是最好的选择,因为结构不支持继承。
- 4、大多数情况下,目标类型只是含有一些数据,或者以数据为主。
枚举 Enum
枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。
C# 枚举是值类型。换句话说,枚举包含自己的值,且不能继承或传递继承。
声明 enum 变量
声明枚举的一般语法:
csharp
enum <enum_name>
{
enumeration list
};
其中,
- enum_name 指定枚举的类型名称。
- enumeration list 是一个用逗号分隔的标识符列表。
枚举列表中的每个符号代表一个整数值,一个比它前面的符号大的整数值。默认情况下,第一个枚举符号的值是 0.例如:
csharp
enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
下面的实例演示了枚举变量的用法:
csharp
using System;
public class EnumTest
{
enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
static void Main()
{
int x = (int)Day.Sun;
int y = (int)Day.Fri;
Console.WriteLine("Sun = {0}", x);
Console.WriteLine("Fri = {0}", y);
}
}
当上面的代码被编译和执行时,它会产生下列结果:
ini
Sun = 0
Fri = 5
字符串 String
在 C# 中,您可以使用字符数组来表示字符串,但是,更常见的做法是使用 string 关键字来声明一个字符串变量。string 关键字是 System.String 类的别名。
创建 String 对象
您可以使用以下方法之一来创建 string 对象:
- 通过给 String 变量指定一个字符串
- 通过使用 String 类构造函数
- 通过使用字符串串联运算符( + )
- 通过检索属性或调用一个返回字符串的方法
- 通过格式化方法来转换一个值或对象为它的字符串表示形式
下面的实例演示了这点:
csharp
using System;
namespace StringApplication
{
class Program
{
static void Main(string[] args)
{
//字符串,字符串连接
string fname, lname;
fname = "Rowan";
lname = "Atkinson";
string fullname = fname + lname;
Console.WriteLine("Full Name: {0}", fullname);
//通过使用 string 构造函数
char[] letters = { 'H', 'e', 'l', 'l','o' };
string greetings = new string(letters);
Console.WriteLine("Greetings: {0}", greetings);
//方法返回字符串
string[] sarray = { "Hello", "From", "Tutorials", "Point" };
string message = String.Join(" ", sarray);
Console.WriteLine("Message: {0}", message);
//用于转化值的格式化方法
DateTime waiting = new DateTime(2012, 10, 10, 17, 58, 1);
string chat = String.Format("Message sent at {0:t} on {0:D}",
waiting);
Console.WriteLine("Message: {0}", chat);
Console.ReadKey() ;
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
vbnet
Full Name: RowanAtkinson
Greetings: Hello
Message: Hello From Tutorials Point
Message: Message sent at 17:58 on Wednesday, 10 October 2012
String 类的属性
String 类有以下两个属性:
属性 | 描述 |
---|---|
Chars | 在当前 String 对象中获取 Char 对象的指定位置 |
Length | 在当前的 String 对象中获取字符串长度 |
String 类的方法
String 类有许多方法用于 string 对象的操作。下面的表格提供了一些最常用的方法:
方法及入参 | 返回值类型 | 描述 |
---|---|---|
Compare( string strA, string strB ) | int | 比较两个指定的 string 对象,并返回一个表示它们在排列顺序中相对位置的整数。该方法区分大小写。 |
Compare( string strA, string strB, bool ignoreCase ) | int | 比较两个指定的 string 对象,并返回一个表示它们在排列顺序中相对位置的整数。但是,如果布尔参数为真时,该方法不区分大小写 |
Concat( string str0, string str1, ... ) | string | 连接多个 string 对象 |
Contains( string value ) | bool | 判断是否包含指定 string |
Copy( string str ) | string | 创建一个与指定字符串具有相同值的新的 String 对象 |
CopyTo( int sourceIndex, char[] destination, int destinationIndex, int count ) | void | 从 string 对象的指定位置开始复制指定数量的字符到 Unicode 字符数组中的指定位置 |
EndsWith( string value ) | bool | 判断 string 对象的结尾是否匹配指定的字符串 |
Equals( string value ) | bool | 判断当前的 string 对象是否与指定的 string 对象具有相同的值 |
Equals( string a, string b ) | bool | 判断两个指定的 string 对象是否具有相同的值 |
Format( string format, Object arg0 ) | string | 把指定字符串中一个或多个格式项替换为指定对象的字符串表示形式 |
IndexOf( char / string value, int startIndex ) | int | 返回【指定 Unicode 字符 / 指定字符串】从该字符串中从 startIndex(不填则为 0) 开始搜索第一次出现的索引,索引从 0 开始 |
IndexOfAny( char[] anyOf, int startIndex ) | int | 返回某一个指定的 Unicode 字符数组中任意字符从该实例中从 startIndex(不填则为 0) 开始搜索第一次出现的索引,索引从 0 开始 |
Insert( int startIndex, string value ) | string | 返回一个新的字符串,其中,指定的字符串被插入在当前 string 对象的指定索引位置 |
IsNullOrEmpty( string value ) | bool | 指示指定的字符串是否为 null 或者是否为一个空的字符串 |
Join( string separator, string[] value ) | string | 连接一个字符串数组中的所有元素,使用指定的分隔符分隔每个元素 |
Join( string separator, string[] value, int startIndex, int count ) | string | 连接一个字符串数组中的指定位置开始的指定元素,使用指定的分隔符分隔每个元素 |
LastIndexOf( char / string value ) | int | 返回【指定 Unicode 字符 / 指定字符串】在当前 string 对象中最后一次出现的索引位置,索引从 0 开始 |
Remove( int startIndex, int count ) | string | 从当前字符串的指定位置开始移除 count(不填则表示到最后一个位置为止) 数量的字符,并返回字符串 |
Replace( char / string oldValue, char / string newValue ) | string | 把当前 string 对象中,所有指定的 Unicode 字符 / 字符串替换为另一个指定的 Unicode 字符 / 字符串,并返回新的字符串 |
Split( char[] separator, int count ) | string[] | 返回一个字符串数组,包含当前的 string 对象中的子字符串,子字符串是使用指定的 Unicode 字符数组中的元素进行分隔的。count 参数指定要返回的子字符串的最大数目(不填则为全部) |
StartsWith( string value ) | bool | 判断字符串实例的开头是否匹配指定的字符串 |
ToCharArray() | char[] | 返回一个带有当前 string 对象中所有字符的 Unicode 字符数组 |
ToCharArray( int startIndex, int length ) | char[] | 返回一个带有当前 string 对象中所有字符的 Unicode 字符数组,从指定的索引开始,直到指定的长度为止 |
ToLower() | string | 把字符串转换为小写并返回 |
ToUpper() | string | 把字符串转换为大写并返回 |
Trim() | string | 移除当前 String 对象中的所有前导空白字符和后置空白字符 |
下面的实例演示了上面提到的一些方法:
比较字符串
csharp
using System;
namespace StringApplication
{
class StringProg
{
static void Main(string[] args)
{
string str1 = "This is test";
string str2 = "This is text";
if (String.Compare(str1, str2) == 0)
{
Console.WriteLine(str1 + " and " + str2 + " are equal.");
}
else
{
Console.WriteLine(str1 + " and " + str2 + " are not equal.");
}
Console.ReadKey() ;
}
}
}
输出:
This is test and This is text are not equal.
字符串包含字符串:
csharp
using System;
namespace StringApplication
{
class StringProg
{
static void Main(string[] args)
{
string str = "This is test";
if (str.Contains("test"))
{
Console.WriteLine("The sequence 'test' was found.");
}
Console.ReadKey() ;
}
}
}
输出:
The sequence 'test' was found.
获取子字符串:
csharp
using System;
namespace StringApplication
{
class StringProg
{
static void Main(string[] args)
{
string str = "Last night I dreamt of San Pedro";
Console.WriteLine(str);
string substr = str.Substring(23);
Console.WriteLine(substr);
Console.ReadKey() ;
}
}
}
输出:
css
Last night I dreamt of San Pedro
San Pedro
连接字符串:
csharp
using System;
namespace StringApplication
{
class StringProg
{
static void Main(string[] args)
{
string[] starray = new string[]{"Down the way nights are dark",
"And the sun shines daily on the mountain top",
"I took a trip on a sailing ship",
"And when I reached Jamaica",
"I made a stop"};
string str = String.Join("\n", starray);
Console.WriteLine(str);
Console.ReadKey() ;
}
}
}
输出:
css
Down the way nights are dark
And the sun shines daily on the mountain top
I took a trip on a sailing ship
And when I reached Jamaica
I made a stop
数组 Array
C语言规定数组长度在编译时必须有明确的值,即 必须 在数组定义时 指定数组的长度 ,且一旦数组被创建,其长度就 不能再改变。(可以改变数组长度的参考 C# 进阶知识 -- ArrayList、List)
声明
在 C# 中声明一个数组,您可以使用下面的语法:
csharp
datatype[] arrayName;
其中,
- datatype 用于指定被存储在数组中的元素的类型。
- [ ] 指定数组的秩(维度)。秩指定数组的大小。
- arrayName 指定数组的名称。
初始化、赋值
使用 new 关键字来创建数组的实例
例如:
csharp
int [] marks = new int[5]
没有给数组赋值,元素则为各类型对应的默认值
您可以在声明数组的同时给数组赋值,比如:
csharp
double[] balance = { 2340.0, 4523.69, 3421.0 };
您也可以创建并初始化一个数组,比如:
csharp
int [] marks = new int[5] { 99, 98, 92, 97, 95 };
Array 类的属性
Array 类是 C# 中所有数组的基类,它是在 System 命名空间中定义。Array 类提供了各种用于数组的属性和方法。
下表列出了 Array 类中一些最常用的属性:
属性 | 描述 |
---|---|
IsFixedSize | 获取一个值,该值指示数组是否带有固定大小。 |
IsReadOnly | 获取一个值,该值指示数组是否只读。 |
Length | 获取一个 32 位整数,该值表示所有维度的数组中的元素总数。 |
LongLength | 获取一个 64 位整数,该值表示所有维度的数组中的元素总数。 |
Rank | 获取数组的秩(维度)。 |
Array 类的方法
下表列出了 Array 类中一些最常用的方法:
方法 | 描述 |
---|---|
Clear | 根据元素的类型,设置数组中某个范围的元素为 默认值 |
GetLength | 获取指定维度的数组中的元素总数 |
GetValue(Int) | 获取一维数组中指定位置的值。 |
SetValue(Object, Int) | 给一维数组中指定位置的元素设置值。索引由第 2 个参数指定。 |
GetLowerBound | 获取数组中指定维度的下界 |
GetUpperBound | 获取数组中指定维度的上界 |
Copy(Array1, Array2, Int) | 从数组的第一个元素开始 复制 某个范围的元素到另一个数组的第一个元素位置。长度由第 3 个参数指定 |
CopyTo(Array, Int) | 从当前的一维数组中复制 所有 的元素到一个指定的一维数组的指定索引位置。索引由第 3 个参数指定 |
IndexOf(Array, Object) | 搜索指定的对象,返回整个一维数组中 第一次出现的索引 |
Reverse(Array) | 逆转 整个一维数组中元素的顺序。 |
Sort(Array) | 使用数组的每个元素的 IComparable 实现来 排序 整个一维数组中的元素。 |
GetType | 获取当前实例的类型,从对象(Object)继承 |
ToString | 返回一个表示当前对象的字符串。从对象(Object)继承。 |
如需了解 Array 类的完整的方法列表,请参阅微软的 C# 文档。
下面的程序演示了 Array 类的一些方法的用法:
csharp
using System;
namespace ArrayApplication
{
class MyArray
{
static void Main(string[] args)
{
int[] list = { 34, 72, 13, 44, 25, 30, 10 };
Console.Write("原始数组: ");
foreach (int i in list)
{
Console.Write(i + " ");
}
Console.WriteLine();
// 逆转数组
Array.Reverse(list);
Console.Write("逆转数组: ");
foreach (int i in list)
{
Console.Write(i + " ");
}
Console.WriteLine();
// 排序数组
Array.Sort(list);
Console.Write("排序数组: ");
foreach (int i in list)
{
Console.Write(i + " ");
}
Console.WriteLine();
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
原始数组: 34 72 13 44 25 30 10
逆转数组: 10 30 25 44 13 72 34
排序数组: 10 13 25 30 34 44 72
运算符
算术运算符
C# 的算术运算符如下。假设变量 A 的值为 10,变量 B 的值为 20,则:
关系运算符
C# 的关系运算符如下。假设变量 A 的值为 10,变量 B 的值为 20,则:
逻辑运算符
C# 的逻辑运算符如下。假设变量 A 为布尔值 true,变量 B 为布尔值 false,则:
合并运算符
合并运算符 ??
:如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。
可以理解为三元运算符的简化形式:
csharp
num2 = num1 ?? 5.34;
num2 = (num1 == null) ? 5.34 : num1;
位运算符
C# 的位运算符如下。假设变量 A 的值为 60,变量 B 的值为 13,则:
赋值运算符
C# 的赋值运算符如下:
其他运算符
C# 的其他一些重要的运算符如下:
基本输入、输出函数
输出函数
csharp
Console.WriteLine("Hello Wrold!")
也可以先 Console.Write
再 Console.WriteLine
csharp
int[] list = { 34, 72, 13, 44, 25, 30, 10 };
Console.Write("原始数组: ");
foreach (int i in list)
{
Console.Write(i + " ");
}
Console.WriteLine();
输出:
原始数组: 34 72 13 44 25 30 10
输入函数
csharp
//读取一个字符,随便按下一个字符后终止输入操作
int n = Console.Read();
//读取一行字符,按回车结束输入操作
string str = Console.ReadLine();
判断
判断语句
C# 提供了以下类型的判断语句。
语句 | 描述 |
---|---|
if 语句 | 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 |
if...else 语句 | 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。 |
嵌套 if 语句 | 您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 |
switch...case...break 语句 | 一个 switch 语句允许测试一个变量等于多个值时的情况。 |
嵌套 switch 语句 | 您可以在一个 switch 语句内使用另一个 switch 语句。 |
三元运算符
我们已经在前面的章节中讲解了 条件运算符 ? :,可以用来替代 if...else 语句。它的一般形式如下:
csharp
Exp1 ? Exp2 : Exp3;
循环
循环类型
C# 提供了以下几种循环类型
循环类型 | 描述 |
---|---|
while 循环 | 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 |
for/foreach 循环 | 多次执行一个语句序列,简化管理循环变量的代码。 |
do...while 循环 | 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 |
嵌套循环 | 可以在 while、for 或 do..while 循环内使用一个或多个循环。 |
csharp
using System;
namespace ArrayApplication
{
class MyArray
{
static void Main(string[] args)
{
int [] n = new int[10]; /* n 是一个带有 10 个整数的数组 */
/* 初始化数组 n 中的元素 */
for ( int i = 0; i < 10; i++ )
{
n[i] = i + 100;
}
/* 输出每个数组元素的值 */
foreach (int j in n )
{
int i = j-100;
Console.WriteLine("Element[{0}] = {1}", i, j);
}
Console.ReadKey();
}
}
}
循环控制语句
循环控制语句更改执行的正常序列。当执行离开一个范围时,所有在该范围中创建的自动对象都会被销毁。 C# 提供了下列的控制语句。
控制语句 | 描述 |
---|---|
break 语句 | 终止 loop 或 switch 语句,程序流将继续执行紧接着 loop 或 switch 的下一条语句。 |
continue 语句 | 引起循环跳过主体的剩余部分,立即重新开始测试条件。 |
类相关
类 Class
当你定义一个类时,它定义了类的对象 由什么组成 及在这个对象上 可执行什么操作 。对象是类的 实例 。构成类的 方法和变量 称为类的 成员。
类的定义
类的定义是以关键字 class 开始,后跟类的名称。类的主体,包含在一对花括号内。下面是类定义的一般形式:
csharp
<access specifier> class class_name
{
// 成员变量
<access specifier> <data type> variable1;
<access specifier> <data type> variable2;
...
<access specifier> <data type> variableN;
// 成员方法
<access specifier> <return type> method1(parameter_list)
{
// method1 内容
}
<access specifier> <return type> method2(parameter_list)
{
// method2 内容
}
...
}
请注意:
- 访问标识符
<access specifier>
指定了对类及其成员的访问规则。如果没有指定,则使用默认的访问标识符。类的默认访问标识符是 internal ,成员的默认访问标识符是 private。 - 数据类型
<data type>
指定了变量的类型,返回类型<return type>
指定了返回的方法返回的数据类型。 - 如果要访问类的成员,你要使用点(.)运算符。
- 点运算符链接了对象的名称和成员的名称。
下面的实例说明了目前为止所讨论的概念:
csharp
using System;
namespace BoxApplication
{
class Box
{
public double length; // 长度
public double breadth; // 宽度
public double height; // 高度
}
class Boxtester
{
static void Main(string[] args)
{
Box Box1 = new Box(); // 声明 Box1,类型为 Box
double volume = 0.0; // 体积
// Box1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// Box1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
Console.WriteLine("Box1 的体积: {0}", volume);
// ... BoxN 同上
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
erlang
Box1 的体积: 210
BoxN 的体积: ...
成员函数和封装
类的成员函数是一个在类定义中有它的定义或原型的函数,就像其他变量一样。作为类的一个成员,它能在类的任何对象上操作,且能访问该对象的类的所有成员。
成员变量是对象的属性(从设计角度),且它们保持私有来实现封装。这些变量只能使用公共成员函数来访问。
让我们使用上面的概念来设置和获取一个类中不同的类成员的值:
csharp
using System;
namespace BoxApplication
{
class Box
{
private double length; // 长度
private double breadth; // 宽度
private double height; // 高度
public void setLength( double len )
{
length = len;
}
public void setBreadth( double bre )
{
breadth = bre;
}
public void setHeight( double hei )
{
height = hei;
}
public double getVolume()
{
return length * breadth * height;
}
}
class Boxtester
{
static void Main(string[] args)
{
Box Box1 = new Box(); // 声明 Box1,类型为 Box
Box Box2 = new Box(); // 声明 Box2,类型为 Box
double volume; // 体积
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的体积
volume = Box1.getVolume();
Console.WriteLine("Box1 的体积: {0}" ,volume);
// Box2 的体积
volume = Box2.getVolume();
Console.WriteLine("Box2 的体积: {0}", volume);
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
yaml
Box1 的体积: 210
Box2 的体积: 1560
C# 中的构造函数
类的构造函数是类的一个【特殊的成员函数】,当创建类的新对象时执行 。构造函数的名称 与类的名称完全相同,它没有返回任何类型。
下面的实例体现【创建类的新对象时构造函数会执行】:
csharp
using System;
namespace LineApplication
{
class Line
{
private double length; // 线条的长度
public Line()
{
Console.WriteLine("对象已创建");
}
public void setLength( double len )
{
length = len;
}
public double getLength()
{
return length;
}
static void Main(string[] args)
{
Line line = new Line();
// 设置线条长度
line.setLength(6.0);
Console.WriteLine("线条的长度: {0}", line.getLength());
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
对象已创建
线条的长度: 6
构造函数的应用场景包括:
- 在创建对象时,需要初始化对象的状态
- 为对象分配所需的内存空间
- 在对象创建时执行一些初始化操作,例如打开文件、连接数据库等
- 在子类中重写基类的构造函数,以便在创建子类对象时初始化基类的状态
由于默认的构造函数 没有任何参数,如果需要一个带有参数的构造函数可以有参数,这种构造函数叫做参数化构造函数。这种技术可以帮助你在创建对象的同时给对象赋初始值,具体请看下面实例:
csharp
struct Person {
public string name;
public int age;
public bool isStudent;
public string address;
}
public class PersonInfo
{
public string name;
public int age;
public bool isStudent;
public string address;
public PersonInfo(Person per)
{
name = per.name;
age = per.age;
isStudent = per.isStudent;
address = per.address;
}
}
PersonInfo p = new PersonInfo(new Person {
name = "Tom",
age = 20,
isStudent = true,
address = "China"
});
Console.WriteLine(p.name);
Console.WriteLine(p.age);
Console.WriteLine(p.isStudent);
Console.WriteLine(p.address);
当上面的代码被编译和执行时,它会产生下列结果:
arduino
Tom
20
true
China
C# 中的析构函数
类的析构函数也是类的一个【特殊的成员函数】,当类的对象超出范围时执行 。析构函数的名称是在类的名称前加上一个 波浪形(~)作为前缀,它不返回值,也不带任何参数。
下面的实例体现【在对象被销毁时析构函数执行】:
csharp
using System;
namespace LineApplication
{
class Line
{
private double length; // 线条的长度
public Line() // 构造函数
{
Console.WriteLine("对象已创建");
}
~Line() //析构函数
{
Console.WriteLine("对象已删除");
}
public void setLength( double len )
{
length = len;
}
public double getLength()
{
return length;
}
static void Main(string[] args)
{
Line line = new Line();
// 设置线条长度
line.setLength(6.0);
Console.WriteLine("线条的长度: {0}", line.getLength());
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
对象已创建
线条的长度: 6
对象已删除
析构函数的应用场景包括:
- 在对象被销毁时执行一些清理操作,例如关闭文件、释放资源等
- 在对象销毁时释放 对象所占用的内存空间
需要注意的是,C# 中的垃圾回收机制会自动释放对象所占用的内存空间,因此在大多数情况下,析构函数不是必须的。如果您需要执行一些清理操作,可以使用 IDisposable
接口和 using
语句来实现。例如:
csharp
public class MyClass : IDisposable
{
private FileStream _file;
public MyClass(string fileName)
{
_file = new FileStream(fileName, FileMode.Open);
}
public void Dispose()
{
_file.Dispose();
}
}
using (MyClass obj = new MyClass("test.txt"))
{
// 使用 obj 对象
}
在上面的示例中,我们实现了 IDisposable
接口,并在 Dispose
方法中释放了 _file
对象所占用的资源。然后,我们使用 using
语句创建了一个 MyClass
对象,并在 using
语句结束时自动调用了 Dispose
方法。
C# 类的静态成员
我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
在 C# 中,静态变量和静态成员是与类本身相关联的,而不是与类的实例相关联。它们在类的所有实例之间共享,而不是为每个实例单独存储。以下是静态变量和静态成员的一些应用场景:
静态变量
静态变量用于存储与类相关的数据,而不是与类的实例相关的数据。静态变量在所有实例之间共享,因此对一个实例所做的更改会影响其他实例。以下是静态变量的一个应用场景:
csharp
public class Counter
{
public static int count = 0;
public Counter()
{
count++;
}
}
Counter c1 = new Counter();
Counter c2 = new Counter();
Console.WriteLine(Counter.count); // 输出 2
在上面的示例中,我们使用静态变量 count
来记录 Counter
类的实例数量。每次创建一个新的 Counter
实例时,count
变量都会增加。因为 count
变量是静态的,所以它在所有实例之间共享。
静态方法
静态方法用于执行与类相关的操作,而不是与类的实例相关的操作。静态方法可以直接通过类名调用,无需创建类的实例。以下是静态方法的一个应用场景:
csharp
public class MathHelper
{
public static int Add(int a, int b)
{
return a + b;
}
}
int sum = MathHelper.Add(1, 2);
在上面的示例中,我们创建了一个名为 MathHelper
的类,其中包含一个静态方法 Add
。这个方法用于计算两个整数的和。因为 Add
方法是静态的,所以我们可以直接通过类名调用它,无需创建 MathHelper
类的实例。
总之,静态变量和静态成员在以下场景中很有用:
- 当需要在类的所有实例之间共享数据时,可以使用静态变量。
- 当需要执行与类相关的操作,而不是与类的实例相关的操作时,可以使用静态方法。
- 当需要提供一组与类本身相关的工具方法时,可以使用静态方法。如 String.Concat(str1, str2)等
封装
封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。
抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象。
C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。
一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:
- Pubilc :任何公有成员可以被外部的类访问。
- Private :只有同一个类中的函数可以访问它的私有成员。
- Protected :该类内部和继承类中可以访问。
- internal : 同一个程序集的对象可以访问。
- Protected internal :3 和 4 的并集,符合任意一条都可以访问。
范围比较:
public > protected internal > internal/protected > private
形象比喻:
假设:一个人A为父类,他的儿子B,妻子C,私生子D(注:D不在他家里)
如果我们给A的事情增加修饰符:
- public事件,地球人都知道,全公开
- protected internal事件,A,B,C,D都知道,其它人不知道(即 protected、internal 的并集)
- protected事件,A,B,D知道(A和他的所有儿子知道,妻子C不知道)
- internal事件,A,B,C知道(A家里人都知道,私生子D不知道)
- private事件,只有A知道(隐私?心事?)
Public 访问修饰符
Public 访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问。
下面的实例说明了这点:
csharp
using System;
namespace RectangleApplication
{
class Rectangle
{
// 成员变量
public double length;
public double width;
public double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.length = 4.5;
r.width = 3.5;
r.Display();
Console.ReadLine();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
长度: 4.5
宽度: 3.5
面积: 15.75
在上面的实例中,成员变量 length 和 width 被声明为 public ,所以它们可以被函数 Main() 使用 Rectangle 类的实例 r 访问。
成员函数 Display() 和 GetArea() 可以直接访问这些变量。
成员函数 Display() 也被声明为 public ,所以它也能被 Main() 使用 Rectangle 类的实例 r 访问。
Protected Internal 访问修饰符
Protected Internal 访问修饰符允许在本类,派生类或者包含该类的程序集中访问。这也被用于实现继承。
Internal 访问修饰符
Internal 访问修饰符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有 internal 访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。
下面的实例说明了这点:
csharp
using System;
namespace RectangleApplication
{
class Rectangle
{
// 成员变量
internal double length;
internal double width;
double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.length = 4.5;
r.width = 3.5;
r.Display();
Console.ReadLine();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
长度: 4.5
宽度: 3.5
面积: 15.75
在上面的实例中,请注意成员函数 GetArea() 声明的时候不带有任何访问修饰符。如果没有指定访问修饰符,则使用类成员的默认访问修饰符,即为 private。
Protected 访问修饰符
Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数。这样有助于实现继承。我们将在继承的章节详细讨论这个。更详细地讨论这个。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Private 访问修饰符
Private 访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。
下面的实例说明了这点:
csharp
using System;
namespace RectangleApplication
{
class Rectangle
{
// 成员变量
private double length;
private double width;
public void Acceptdetails()
{
Console.WriteLine("请输入长度:");
length = Convert.ToDouble(Console.ReadLine());
Console.WriteLine("请输入宽度:");
width = Convert.ToDouble(Console.ReadLine());
}
public double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.Acceptdetails();
r.Display();
Console.ReadLine();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
请输入长度:
4.4
请输入宽度:
3.3
长度: 4.4
宽度: 3.3
面积: 14.52
在上面的实例中,成员变量 length 和 width 被声明为 private ,成员函数 AcceptDetails() 和 Display() 可以访问 这些变量,但 不能 被函数 Main() 访问
由于成员函数 AcceptDetails() 和 Display() 被声明为 public ,所以它们可以被 Main() 使用 Rectangle 类的实例 r 访问。
继承
C# 中的继承允许一个类从另一个类继承成员。这有助于实现代码重用和多态,提高了代码的可读性和可维护性。
继承思想,例如:长方形、正方形、平行四边形属于四边形;牛羊狗属于哺乳类动物。
基类和派生类
假设,有一个基类 Shape,它的派生类是 Rectangle,其中使用 :
关键字表示继承:
csharp
using System;
namespace InheritanceApplication
{
// 基类
class Shape
{
public void setWidth(int w)
{
width = w;
}
public void setHeight(int h)
{
height = h;
}
protected int width;
protected int height;
}
// 派生类
class Rectangle: Shape
{
public int getArea()
{
return (width * height);
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle Rect = new Rectangle();
Rect.setWidth(5);
Rect.setHeight(7);
// 打印对象的面积
Console.WriteLine("总面积: {0}", Rect.getArea());
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
总面积: 35
方法重写覆盖
csharp
using System;
namespace RectangleApplication
{
class Rectangle
{
// 成员变量
protected double length;
protected double width;
public Rectangle(double l, double w)
{
length = l;
width = w;
}
public double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}
class Tabletop : Rectangle
{
private double cost;
public Tabletop(double l, double w) : base(l, w)
{ }
public double GetCost()
{
double cost;
cost = GetArea() * 70;
return cost;
}
public void Display()
{
base.Display(); // 调用基类 Rectangle 的 Display 方法
Console.WriteLine("成本: {0}", GetCost());
}
}
class ExecuteRectangle
{
static void Main(string[] args)
{
Tabletop t = new Tabletop(4.5, 7.5);
t.Display();
Console.ReadLine();
}
}
}
以上代码中 base
关键字代表派生类 Tabletop
的基类(父类)Rectangle
。base
关键字允许我们在派生类中访问基类的成员。在 Tabletop
类的 Display
方法中,base.Display();
语句表示调用类 Rectangle
的 Display
方法。
这样做的目的是:
- 在派生类
Tabletop
的Display
方法中保留基类Rectangle
的Display
方法的功能 - 同时在
Tabletop
类中添加新的功能(在这里是输出成本)。
当上面的代码被编译和执行时,它会产生下列结果:
yaml
长度: 4.5
宽度: 7.5
面积: 33.75
成本: 2362.5
实现多重继承
在 C# 中,类不支持多重继承,即一个类不能直接从多个基类继承。然而,可以使用接口 interface
来实现类似多重继承的功能。一个类可以实现多个接口,从而继承多个接口的成员。
以下是一个使用接口实现多重继承的示例:
csharp
public interface IFirstInterface
{
void MethodA();
}
public interface ISecondInterface
{
void MethodB();
}
public class MyClass : IFirstInterface, ISecondInterface
{
public void MethodA()
{
Console.WriteLine("MethodA from IFirstInterface");
}
public void MethodB()
{
Console.WriteLine("MethodB from ISecondInterface");
}
}
class Program
{
static void Main(string[] args)
{
MyClass obj = new MyClass();
obj.MethodA(); // 输出 "MethodA from IFirstInterface"
obj.MethodB(); // 输出 "MethodB from ISecondInterface"
}
}
在上面的示例中,我们定义了两个接口 IFirstInterface
和 ISecondInterface
,分别包含一个方法 MethodA
和 MethodB
。然后,我们创建了一个名为 MyClass
的类,它实现了这两个接口。这样,MyClass
类就继承了这两个接口的成员,实现了类似多重继承的功能。
多态性
静态多态性
运算符重载
应用场景:向量运算
csharp
using System;
public struct Vector2
{
public float x;
public float y;
// 构造函数初始化赋值向量
public Vector2(float x, float y)
{
this.x = x;
this.y = y;
}
// 向量求和
public static Vector2 operator +(Vector2 a, Vector2 b)
{
return new Vector2(a.x + b.x, a.y + b.y);
}
// 向量求差
public static Vector2 operator -(Vector2 a, Vector2 b)
{
return new Vector2(a.x - b.x, a.y - b.y);
}
// 向量乘常数
public static Vector2 operator *(Vector2 a, float scalar)
{
return new Vector2(a.x * scalar, a.y * scalar);
}
// 以直观格式输出向量
public override string ToString()
{
return $"({x}, {y})";
}
}
class Program
{
static void Main(string[] args)
{
Vector2 vec1 = new Vector2(1, 2);
Vector2 vec2 = new Vector2(3, 4);
Vector2 sum = vec1 + vec2;
Console.WriteLine($"Sum: {sum}"); // 输出 "两向量之和: (4, 6)"
Vector2 difference = vec1 - vec2;
Console.WriteLine($"Difference: {difference}"); // 输出 "两向量之差: (-2, -2)"
Vector2 product = vec1 * 2;
Console.WriteLine($"Product: {product}"); // 输出 "向量1 的 2 倍: (2, 4)"
}
}
函数重载
一个类里有多个 相同名字 但 参数的个数/类型不同 的成员函数,实例中调用这些同名函数时会根据参数的不同输出执行不同的函数
csharp
using System;
namespace PolymorphismApplication
{
public class TestData
{
public int Add(int a, int b, int c)
{
return a + b + c;
}
public int Add(int a, int b)
{
return a + b;
}
}
public class Printdata
{
public void print(int i)
{
Console.WriteLine("输出整型: {0}", i );
}
public void print(double f)
{
Console.WriteLine("输出浮点型: {0}" , f);
}
public void print(string s)
{
Console.WriteLine("输出字符串: {0}", s);
}
}
class Program
{
static void Main(string[] args)
{
TestData dataClass = new TestData();
int add1 = dataClass.Add(1, 2);
int add2 = dataClass.Add(1, 2, 3);
Console.WriteLine("add1 :" + add1);
Console.WriteLine("add2 :" + add2);
Printdata p = new Printdata();
p.print(1); // 调用 print 来打印整数
p.print(1.23); // 调用 print 来打印浮点数
p.print("Hello Runoob"); // 调用 print 来打印字符串
}
}
}
动态多态性
abstract 抽象类 是一种特殊类型的类它,不能被实例化 ,其主要目的是为其他类 提供一个基类 ,并定义这些派生类应该实现的 公共接口。抽象类可以包含抽象成员(如抽象方法、抽象属性等)和非抽象成员。
- abstract 创建【抽象类】
- abstract 创建 必须 被重写的【抽象方法】
- virtual 创建可以(非必须)被重写的【虚方法】
- override 重写抽象类中抽象方法
实例:创建一个名为 GameCharacter
的抽象基类,表示游戏中的角色。这个类包含一个抽象方法 PerformAction
,用于执行角色的行为。
csharp
public abstract class GameCharacter
{
public string Name { get; set; }
public GameCharacter(string name)
{
Name = name;
}
public abstract void PerformAction(); // 必须被重写
public virtual void PerformAction(); // 可以被重写,也可以不被重写
}
然后,我们创建两个派生类 PlayerCharacter
和 NonPlayerCharacter
,分别表示玩家角色和非玩家角色。这两个派生类重写了基类的 PerformAction
方法,为它们提供了不同的实现。
csharp
public class PlayerCharacter : GameCharacter
{
public PlayerCharacter(string name) : base(name) { }
public override void PerformAction()
{
Console.WriteLine($"{Name}是一个玩家角色");
}
}
public class NonPlayerCharacter : GameCharacter
{
public NonPlayerCharacter(string name) : base(name) { }
public override void PerformAction()
{
Console.WriteLine($"{Name}是一个 NPC 角色");
}
}
在游戏客户端中,我们可以使用多态性来创建和管理游戏角色。例如,我们可以创建一个 GameCharacter
类型的列表,用于存储玩家角色和非玩家角色。然后,我们可以遍历列表并调用每个角色的 PerformAction
方法,执行相应的行为。
csharp
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
List<GameCharacter> characters = new List<GameCharacter>
{
new PlayerCharacter("Player1"),
new NonPlayerCharacter("NPC1"),
new PlayerCharacter("Player2"),
new NonPlayerCharacter("NPC2")
};
foreach (var character in characters)
{
character.PerformAction();
}
}
}
最终输出结果:
Player1是一个真实玩家角色
NPC1是一个NPC角色
Player2是一个真实玩家角色
NPC2是一个NPC角色
接口 Interface
- 通常接口命令以 I 字母开头
- 接口(
interface
)可以帮助我们更灵活地组织代码、实现代码重用和提高可维护性
场景:玩家角色和非玩家角色有共同的行为,又有各自特定的行为
解决:共同的行为用基类写,特定的行为用接口写
首先,我们创建一个名为 BaseCharacter
的基类,表示游戏中的角色。这个基类包含一个方法 CommonAction
,用于执行角色的共同行为。
csharp
public abstract class BaseCharacter
{
public void CommonAction()
{
Console.WriteLine("Performing a common action.");
}
}
接下来,我们创建一个名为 ICharacter
的接口,表示游戏中的角色。这个接口包含一个方法 PerformAction
,用于执行角色的特定行为。
csharp
public interface ICharacter
{
void PerformAction();
}
然后,我们创建两个继承自 BaseCharacter
类并实现了 ICharacter
接口的类:PlayerCharacter
和 NonPlayerCharacter
,分别表示玩家角色和非玩家角色。这两个类实现了 ICharacter
接口的 PerformAction
方法,为它们提供了不同的实现。
csharp
public class PlayerCharacter : BaseCharacter, ICharacter
{
public void PerformAction()
{
Console.WriteLine("Performing a player action.");
}
}
public class NonPlayerCharacter : BaseCharacter, ICharacter
{
public void PerformAction()
{
Console.WriteLine("Performing a non-player action.");
}
}
在游戏客户端中,我们可以创建玩家角色和非玩家角色的对象,并调用它们的共同行为和特定行为。
csharp
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
List<ICharacter> characters = new List<ICharacter>
{
new PlayerCharacter(),
new NonPlayerCharacter(),
new PlayerCharacter(),
new NonPlayerCharacter()
};
foreach (var character in characters)
{
character.PerformAction(); // 执行特定行为
(character as BaseCharacter).CommonAction(); // 执行共同行为
}
}
}
命名空间 Namesapce
定义命名空间
命名空间 的设计目的是提供一种让一组名称与其他名称分隔开的方式。在一个命名空间中声明的类的名称与另一个命名空间中声明的相同的类的名称不冲突。类似 windows 计算机:
命名空间的用法:
csharp
using System;
namespace first_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
first_space.namespace_cl fc = new first_space.namespace_cl();
second_space.namespace_cl sc = new second_space.namespace_cl();
fc.func();
sc.func();
Console.ReadKey();
}
}
运行结果:
scss
Inside first_space
Inside second_space
using 关键字简写
using 关键字表明程序使用的是给定命名空间中的名称。例如,我们在程序中使用 System 命名空间,其中定义了类 Console。我们可以只写:
arduino
Console.WriteLine ("Hello there");
我们可以写完全限定名称,如下:
arduino
System.Console.WriteLine("Hello there");
您也可以使用 using 命名空间指令,这样在使用的时候就不用在前面加上命名空间名称。该指令告诉编译器随后的代码使用了指定命名空间中的名称。下面的代码演示了命名空间的应用。
让我们使用 using 指定重写上面的实例:
csharp
using System;
using first_space;
using second_space;
namespace first_space
{
class abc
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class efg
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
abc fc = new abc();
efg sc = new efg();
fc.func();
sc.func();
Console.ReadKey();
}
}
嵌套命名空间
命名空间可以被嵌套,即您可以在一个命名空间内定义另一个命名空间,如下所示:
arduino
namespace namespace_name1
{
// 代码声明
namespace namespace_name2
{
// 代码声明
}
}
您可以使用点(.)运算符访问嵌套的命名空间的成员,如下所示:
csharp
using System;
using SomeNameSpace;
using SomeNameSpace.Nested;
namespace SomeNameSpace
{
public class MyClass
{
static void Main()
{
Console.WriteLine("In SomeNameSpace");
Nested.NestedNameSpaceClass.SayHello();
}
}
// 内嵌命名空间
namespace Nested
{
public class NestedNameSpaceClass
{
public static void SayHello()
{
Console.WriteLine("In Nested");
}
}
}
}
运行结果:
In SomeNameSpace
In Nested
预处理器指令
C# 预处理器指令列表
下表列出了 C# 中可用的预处理器指令:
预处理器指令 | 描述 |
---|---|
#define | 它用于定义一系列成为符号的字符。 |
#undef | 它用于取消定义符号。 |
#if | 它用于测试符号是否为真。 |
#else | 它用于创建复合条件指令,与 #if 一起使用。 |
#elif | 它用于创建复合条件指令。 |
#endif | 指定一个条件指令的结束。 |
#line | 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。 |
#error | 它允许从代码的指定位置生成一个错误。 |
#warning | 它允许从代码的指定位置生成一级警告。 |
#region | 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。 |
#endregion | 它标识着 #region 块的结束。 |
预处理器用途
比如预处理器指令可以禁止编译器编译代码的某一部分,如果计划发布 两个版本 的代码,即基本版本和有更多功能的企业版本,就可以使用这些预处理器指令来控制。在编译软件的基本版本时,使用预处理器指令还可以禁止编译器编译于额外功能相关的代码。
另外,在编写提供调试信息的代码时,也可以使用预处理器指令进行控制。
总的来说和普通的控制语句(if等)功能类似,方便在于预处理器指令包含的未执行部分是不需要编译的。
csharp
#define PI
using System;
namespace PreprocessorDAppl
{
class Program
{
static void Main(string[] args)
{
#if (PI)
Console.WriteLine("PI is defined"); //PI不存在,则这条语句不编译
#else
Console.WriteLine("PI is not defined"); //PI存在,则这条语句不编译
#endif
Console.ReadKey();
}
}
}
其他预处理器指令:
#warning 和 #error:
当编译器遇到它们时,会分别产生警告或错误。如果编译器遇到 #warning 指令,会给用户显示 #warning 指令后面的文本,之后编译继续进行。如果编译器遇到 #error 指令,就会给用户显示后面的文本,作为一条编译错误消息,然后会立即退出编译。使用这两条指令可以检查 #define 语句是不是做错了什么事,使用 #warning 语句可以提醒自己执行某个操作。
csharp
#if DEBUG && RELEASE
#error "You've defined DEBUG and RELEASE simultaneously!"
#endif
#warning "Don't forget to remove this line before the boss tests the code!"
Console.WriteLine("*I hate this job.*");
异常处理
异常捕获语法
假设一个块将出现异常,一个方法使用 try 和 catch 关键字捕获异常。try/catch 块内的代码为受保护的代码,使用 try/catch 语法如下所示:
csharp
try
{
// 尝试可能引起异常的代码
}
catch( ExceptionName e1 )
{
// 错误处理
}
catch( ExceptionName e2 )
{
// 错误处理
}
catch( Exception ex ) //捕获所有
{
// 错误处理
}
finally
{
// 不管异常是否被抛出都会执行
}
异常类
- C# 异常是使用 类 来表示的
- 所有异常的来源是 Exception 类
- ApplicationException 和 SystemException 类是派生于 System.Exception 类的异常类
- ApplicationException 是自定义异常类
- SystemException 是系统内置的异常类
内置异常类
系统内置的异常类包括:
异常类 | 描述 |
---|---|
System.IO.IOException | 处理 I/O 错误。 |
System.IndexOutOfRangeException | 处理当方法指向超出范围的数组索引时生成的错误。 |
System.ArrayTypeMismatchException | 处理当数组类型不匹配时生成的错误。 |
System.NullReferenceException | 处理当依从一个空对象时生成的错误。 |
System.DivideByZeroException | 处理当除以零时生成的错误。 |
System.InvalidCastException | 处理在类型转换期间生成的错误。 |
System.OutOfMemoryException | 处理空闲内存不足生成的错误。 |
System.StackOverflowException | 处理栈溢出生成的错误。 |
ArgumentNullException | 参数不应为null时 |
自定义异常类
- 派生于
Exception
类自定义异常类 - 自定义异常类需要使用
throw
抛出异常
csharp
using System;
using System.Text.RegularExpressions;
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
public class InvalidStudentNameException : Exception
{
public InvalidStudentNameException()
{
}
public InvalidStudentNameException(string name)
: base(String.Format("不合法的名字: {0}", name))
{
}
}
class Program
{
static void Main(string[] args)
{
Student newStudent;
try
{
newStudent = new Student();
newStudent.Name = "James007";
ValidateStudent(newStudent);
}
catch (InvalidStudentNameException ex)
{
Console.WriteLine(ex.Message); // 输出→不合法的名字: James000
}
Console.ReadKey();
}
private static void ValidateStudent(Student std)
{
// 使用正则表达式进行合法校验
var regex = new Regex("^[a-zA-Z]+$");
if (!regex.IsMatch(std.Name))
{
throw new InvalidStudentNameException(std.Name);
}
else
{
Console.WriteLine("名字合法");
}
}
}