1. 命名空间(Namespace)
命名空间存在的目是提供一种让一组名称与其他名称分隔开的封装。使得在一个命名空间中声明的类名与另一个命名空间中声明的相同的类名不冲突。
就像计算机系统中,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。
💡类似地可以用计算机系统文件夹和文件命名来理解命名空间,把不同的命名空间理解为不同的文件夹,命名空间中的类名理解为文件夹中的文件的文件名。
当需要用到某文件夹中的方法时,先using
命名空间,然后使用。相当于先打开相应文件夹,再从中打开目标文件。
就像在C#理论之一中的1.3节提及的,命名空间内包含了类,类中包含了多个方法和属性。
using System; // 包含System命名空间
namespace HelloWorldApplication // 命名空间声明,名称默认与工程名相同,当然也可以修改
{
class HelloWorld //一个类,类命可与代码文件命不同
{
static void Main(string[] args) // 主函数
{
/* 我的第一个 C# 程序*/
Console.WriteLine("Hello World");
Console.ReadKey();
}
}
}
1.1 定义命名空间
命名空间的定义是以关键字 namespace
开始,后跟命名空间的名称。
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(); // 实例化命名空间first_space中的类
second_space.namespace_cl sc = new second_space.namespace_cl(); // 实例化命名空间second_space中的类,显然类名是相同的,但来自于不同的命名空间
fc.func();
sc.func();
Console.ReadKey();
}
}
1.2 using 关键字
using 关键字表明程序使用的是给定命名空间中的名称。如在程序中使用 System 命名空间,在System 命名空间中定义了类 Console。那么可以运用下列代码:
Console.WriteLine ("Hello there");
// 也可以使用完整限定函数名
using System;
System.Console.WriteLine("Hello there");
- 自定义命名空间:
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();
}
}
运行结果:
Inside first_space
Inside second_space
1.3 嵌套命名空间
命名空间可以嵌套,即可在一个命名空间内定义另一个命名空间。
using System;
using SomeNameSpace;
using SomeNameSpace.Nested;
namespace SomeNameSpace
{
public class MyClass
{
static void Main()
{
Console.WriteLine("In SomeNameSpace");
Nested.NestedNameSpaceClass.SayHello();
Console.ReadKey();
}
}
// 命名空间内嵌套命名空间
namespace Nested
{
public class NestedNameSpaceClass
{
public static void SayHello()
{
Console.WriteLine("In Nested");
}
}
}
}
运行结果:
In SomeNameSpace
In Nested
2. 类(Class)
类是实例的抽象,是一个概念;
不同的类或对象在成员方面侧重点不同:
- 模型类或对象重在属性,如
Entity Framework
; - 工具类或对象重在方法,如
Math
、Console
; - 通知类或对象重在事件,如
Timer
2.1 类与对象及类的实例化
- 对象:也叫实例,是类经过实例化后得到的内存中的实体;
- 类的实例化举例:
- 新建一个“控制台.net framework” 项目;
- 在引用管理器中添加
System.Windows.Forms
类:
- 类Form 的实例化:
using System.Windows.Forms;
namespace ConsoleApp_CalssAndInstance
{
internal class Program
{
static void Main(string[] args)
{
// 类Form 的实例化,并将实体命名为myForm
Form myForm = new Form();
myForm.Text = "My Form";
myForm.ShowDialog();
}
}
}
// 类Form 本身不占用电脑内存空间,当其被实例化成对象后,即有了实体,即在内存空间有实体存在;
2.1 类的三大成员:属性、方法、事件
- 属性:Property,储存数值、组合起来表示类或对象当前的状态;
- 方法:Method,与C 语音的函数(function)类似;
- 事件:Event,类或对象通知其他类或对象的机制;
- 在官方MSDN 中可查看官方库的三大成员,首先在visual studio 中,选中
Form
类,点击键盘F1,进入官方介绍页面,向下拉即可找到Form
类的属性、方法、事件:
2.2 类的构造器
类的构造器是类的一个特殊成员方法,当类被定义时同步被定义,当类的对象被创建时自动执行;
- 构造器的名称与类的名称完全相同,且它没有任何返回类型。
using System;
namespace LineApplication
{
class Line
{
static void Main(string[] args)
{
Line line = new Line(); // 当类的对象被创建时自动执行构造函数
// 设置线条长度
line.setLength(6.0);
Console.WriteLine("线条的长度: {0}", line.getLength());
Console.ReadKey();
}
private double length; // 线条的长度
public Line() // 构造函数,当类被定以后,就默认被定义并对类函数进行初始化了
{
Console.WriteLine("对象已创建"); // 当需要在构造函数中添加代码时,可将其写出来(否则不必要写)
}
public void setLength(double len)
{
length = len;
}
public double getLength()
{
return length;
}
}
}
- 默认的构造器是不带参数的。带参数的构造器叫参数化构造函数(也常叫自定义构造器)。它可以实现在创建对象的同时给对象赋初始值。
using System;
namespace LineApplication
{
class Line
{
static void Main(string[] args)
{
Line line = new Line(10.0); // 类的对象被创建时,必须给对象赋初值
Console.WriteLine("线条的长度: {0}", line.getLength());
// 设置线条长度
line.setLength(6.0);
Console.WriteLine("线条的长度: {0}", line.getLength());
Console.ReadKey();
}
private double length; // 线条的长度
public Line(double len) // 参数化构造函数
{
Console.WriteLine("对象已创建,length = {0}", len);
length = len;
}
public void setLength( double len )
{
length = len;
}
public double getLength()
{
return length;
}
}
}
- 在上一个例程的基础上,可定义多一个无输入参数的构造器,则在类的对象被创建时,对象初值则不是必须赋值;
using System;
namespace LineApplication
{
class Line
{
static void Main(string[] args)
{
Line line = new Line(); // 类的对象被创建时,不是必须给对象赋初值
Console.WriteLine("线条的长度: {0}", line.getLength());
// 设置线条长度
line.setLength(6.0);
Console.WriteLine("线条的长度: {0}", line.getLength());
Console.ReadKey();
}
private double length; // 线条的长度
public Line(double len) // 参数化构造函数
{
Console.WriteLine("对象已创建,length = {0}", len);
length = len;
}
public Line() // 默认构造函数
{
Console.WriteLine("对象已创建;");
}
public void setLength(double len)
{
length = len;
}
public double getLength()
{
return length;
}
}
}
3.3 类的析构器
与构造器对应的,类的析构函数也是类的一个特殊的成员函数,当类的对象超出范围时执行。
析构函数的名称是在类名前加一个波浪号(~
)作为前缀,无返回值,也不带任何参数。
析构函数用于在结束程序前(如关闭文件、释放内存等)释放资源。析构函数不能继承或重载。
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());
}
}
}
2.4 静态成员与实例成员
- 静态(Static):类的成员,不用实例化就可以用的成员;
- 实例(非静态):对象的成员,要对实例化后才可以用的成员;
- 绑定(Binding):即编译器把一个成员与类或对象关联起来;
- 成员访问符:
.
2.4.1 静态成员变量
使用 static
关键字把类成员定义为静态。当声明一个类成员为静态时,意味着无论有多少个类的对象被创建,它们都是用同一个静态变量。
using System;
namespace StaticVarApplication
{
class StaticVar
{
public static int num; // 静态变量,默认初始化为0
public void count()
{
num++;
}
public int getNum()
{
return num;
}
}
class StaticTester
{
static void Main(string[] args)
{
StaticVar s1 = new StaticVar();
StaticVar s2 = new StaticVar();
s1.count();
s1.count();
s1.count(); // 对象s1计算了3次
s2.count();
s2.count();
s2.count(); // 对象s2计算了3次
Console.WriteLine("s1 的变量 num: {0}", s1.getNum());
Console.WriteLine("s2 的变量 num: {0}", s2.getNum());
Console.ReadKey();
}
}
}
运行结果:
s1 的变量 num: 6
s2 的变量 num: 6
2.4.2 静态成员函数
把一个成员函数声明为 static
。类的静态成员函数只能访问类的静态成员变量。
using System;
namespace StaticVarApplication
{
class StaticVar
{
public static int num;
public void count()
{
num++;
}
public static int getNum() // 静态成员函数
{
return num;
}
}
class StaticTester
{
static void Main(string[] args)
{
StaticVar s = new StaticVar();
s.count();
s.count();
s.count();
Console.WriteLine("变量 num: {0}", StaticVar.getNum());
Console.ReadKey();
}
}
}
运行结果:
变量 num: 3
2.5 继承
继承允许根据一个类来定义另一个类。前者被称为的基类(父类),后者被称为派生类(子类)。
继承的思想实现了属于(IS-A) 关系。例如,哺乳动物属于(IS-A) 动物,狗属于(IS-A) 哺乳动物,因此狗属于(IS-A) 动物,那么狗将继承哺乳动物和动物中的所有成员;
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();
}
}
}
2.5.1 基类的初始化
派生类继承基类的成员变量和成员方法。因此基类必须在派生类定义之前被定义。
如下例程,当父类和子类中同时存在同名的成员函数,而该函数在程序中被调用时,执行顺序是先执行父类成员函数,后执行子类成员函数。
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();
Console.WriteLine("成本: {0}", GetCost());
}
}
class ExecuteRectangle
{
static void Main(string[] args)
{
Tabletop t = new Tabletop(4.5, 7.5); // 创建Tabletop对象,同时对其父类、子类成员变量进行初始化
t.Display(); // 先调用父类成员函数,再调用子类成员函数
Console.ReadLine();
}
}
}
运行结果:
长度: 4.5
宽度: 7.5
面积: 33.75
成本: 2362.5
- 上例中子类的构造函数中的
: base(l, w)
实现子类对象被创建时,初始化其父类的成员变量;- 子类中的方法与父类方法同名的情况,尽量少用;
2.5.2 多重继承
多重继承即一个子类可以同时从多个父类继承行为与特征的功能。与单一继承相对,单一继承指一个子类只可以继承自一个父类。
C# 不支持多重继承。但是可以使用接口来实现多重继承。
using System;
namespace InheritanceApplication
{
// 父类1
class Shape
{
public void setWidth(int w)
{
width = w;
}
public void setHeight(int h)
{
height = h;
}
protected int width;
protected int height;
}
// 父类2,定义接口
public interface PaintCost
{
int getCost(int area);
}
// 子类
class Rectangle : Shape, PaintCost // 多重继承
{
public int getArea()
{
return (width * height);
}
public int getCost(int area)
{
return area * 70;
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle Rect = new Rectangle();
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// 打印对象的面积
Console.WriteLine("总面积: {0}", Rect.getArea());
Console.WriteLine("油漆总成本: ${0}", Rect.getCost(area));
Console.ReadKey();
}
}
}
运行结果:
总面积: 35
油漆总成本: $2450
2.6 get
和set
访问器:获取和设置字段(属性)的值
get
和set
是访问器(accessors)的关键字,它们用于定义属性的读取和写入行为,多用于保护属性;
如果是只读属性,则只有
get
没有set
;
using System;
namespace LineApplication
{
class Line
{
static void Main(string[] args)
{
Student stu1 = new Student();
stu1.Age = 10;
Console.WriteLine(stu1.Age);
}
}
class Student
{
private int age; // 私有字段
public int Age // 属性声明
{
get
{
return this.age;
}
set
{
if (value >= 0 && value <=120)
{
this .age = value;
}
else
{
throw new Exception("Age value has error.");
}
}
}
}
}
- 快速插入:
propfull
private int myVar;
public int MyProperty
{
get { return myVar; }
set { myVar = value; }
}
prop
:
public int MyProperty { get; set; }
2.7 索引器
索引器(Indexer)是类中的一个特殊成员,它允许对象以类似数组的形式来索引。索引器与属性类似,在定义索引器时同样会用到 get
和 set
访问器,不同的是,访问属性不需要提供参数而访问索引器则需要提供相应的参数。
- 索引器多用于集合、字典;
- 索引器允许重载
public class Person
{
private Dictionary<string, string> phoneNumbers = new Dictionary<string, string>(); // 定义一个字典
// 索引器,用于获取和设置电话号码
public string this[string name]
{
get
{
if (phoneNumbers.TryGetValue(name, out string phoneNumber))
{
return phoneNumber;
}
else
{
throw new KeyNotFoundException("No phone number found for the given name.");
}
}
set
{
phoneNumbers[name] = value;
}
}
}
Person person = new Person();
person["Alice"] = "123-456-7890"; // 使用索引器设置 Alice 的电话号码
string alicePhone = person["Alice"]; // 使用索引器获取 Alice 的电话号码
Console.WriteLine(alicePhone); // 输出: 123-456-7890
- 索引器重载:
using System;
namespace c.biancheng.net
{
class Demo
{
static void Main(string[] args){
Demo names = new Demo();
names[0] = "1111";
names[1] = "2222";
names[2] = "C#教程";
names[3] = "索引器";
// 使用带有 int 参数的第一个索引器
for (int i = 0; i < Demo.size; i++){
Console.WriteLine(names[i]);
}
// 使用带有 string 参数的第二个索引器
Console.WriteLine("“C#教程”的索引为:{0}",names["C#教程"]);
Console.ReadKey();
}
static public int size = 10;
private string[] namelist = new string[size];
public Demo(){
for (int i = 0; i < size; i++)
namelist[i] = "NULL";
}
public string this[int index]{
get{
string tmp;
if( index >= 0 && index <= size-1 ){
tmp = namelist[index];
}else{
tmp = "";
}
return ( tmp );
}
set{
if( index >= 0 && index <= size-1 ){
namelist[index] = value;
}
}
}
public int this[string name]{
get{
int index = 0;
while(index < size){
if(namelist[index] == name){
return index;
}
index++;
}
return index;
}
}
}
}
#运行结果:
1111
2222
C#教程
索引器
NULL
NULL
NULL
NULL
NULL
NULL
“C#教程”的索引为:2
- 快速插入:
index
public object this[int index]
{
get { /* return the specified index here */ }
set { /* set the specified index to value here */ }
}
2.8 在项目中添加第三方C# 类库(Class Library)
C# 类库(Class Library)是一组可重用的代码和功能,打包在一个单独的程序集中。它通常包含了多个类、接口、结构和其他相关资源,用于提供特定的功能或服务。
C# 类库被设计为独立于特定应用程序的通用组件,可以在多个项目中重复使用。通过将常用的功能封装在类库中,可以提高代码的可维护性、可重用性和开发效率。
C# 类库可以包含各种类型的组件,如数据访问层、业务逻辑层、工具类、UI 控件等。它们提供了面向对象的编程接口,允许其他应用程序通过引用库并实例化其中的类来使用其功能。
C# 类库可以作为独立的 .dll
(动态链接库)文件进行分发,并由其他应用程序通过引用该库来使用其中的功能。这样,开发人员可以利用已经编写和测试过的代码,减少重复编写相同功能的工作量,并提高整体应用程序的质量和可靠性。
- 在项目中添加第三方类库:
-
在 Visual Studio 中打开 Windows 窗体应用程序项目
-
在 “解决方案资源管理器” 中,右键单击项目名称,然后选择 “添加” -> “现有项”
-
在弹出的对话框中,浏览到存放 xxxxxx.cs C# 类库文件的位置,并选择它,然后点击 “添加” 按钮
-
把该文件拖动包含到项目中;
-
在代码文件的顶部添加适当的命名空间引用来使用 xxxxxx 类。在想要使用该类的代码文件的顶部,添加对应 using 指令,如
using xxxxxx;
2.9 this
参数
this 关键字表示当前实例的成员。当在类的非静态成员(包括方法、属性、索引器、事件或构造器)内部使用 this 时,实际上是在引用当前对象的实例。this 关键字主要用于以下几种情况:
- 区分实例成员和局部变量:当方法的参数名或局部变量名与类的成员名(字段、属性或方法)相同时,可以使用 this 来引用类的成员,以避免混淆。
public class MyClass
{
private int myVariable;
public void MyMethod(int myVariable)
{
this.myVariable = myVariable; // 将方法参数的值赋给类的字段
Console.WriteLine(this.myVariable); // 输出类的字段的值
}
}
- 在 C# 中,可以使用
this
关键字来表示当前对象;
using System;
namespace c
{
class Demo
{
static void Main(string[] args)
{
Website site = new Website("1","2");
site.Display();
}
}
public class Website
{
private string name;
private string url;
public Website(string n, string u){
this.name = n;
this.url = u;
}
public void Display(){
Console.WriteLine(name +" "+ url);
}
}
}
#运行结果:1 2
- 使用 this 关键字串联构造函数:
using System;
namespace c
{
class Demo
{
static void Main(string[] args)
{
Test test = new Test("1");
}
}
public class Test
{
public Test()
{
Console.WriteLine("无参构造函数");
}
// 这里的 this()代表无参构造函数 Test()
// 先执行 Test(),后执行 Test(string text)
public Test(string text) : this()
{
Console.WriteLine(text);
Console.WriteLine("实例构造函数");
}
}
}
#运行结果:
无参构造函数
1
实例构造函数
- 使用 this 关键字作为类的索引器:
using System;
namespace c
{
class Demo
{
static void Main(string[] args)
{
Test a = new Test();
Console.WriteLine("Temp0:{0}, Temp1:{1}", a[0], a[1]);
a[0] = 15;
a[1] = 20;
Console.WriteLine("Temp0:{0}, Temp1:{1}", a[0], a[1]);
}
}
public class Test
{
int Temp0;
int Temp1;
public int this[int index]
{
get
{
return (0 == index) ? Temp0 : Temp1;
}
set
{
if (0==index)
Temp0 = value;
else
Temp1 = value;
}
}
}
}
#运行结果:
Temp0:0, Temp1:0
Temp0:15, Temp1:20
- 使用 this 关键字作为原始类型的扩展方法:
using System;
namespace c
{
class Demo
{
static void Main(string[] args)
{
string str = "111111";
string newstr = str.ExpandString();
Console.WriteLine(newstr);
}
}
public static class Test
{
public static string ExpandString(this string name)
{
return name+" 22222222";
}
}
}
#运行结果:
111111 22222222
- 在链式调用中返回当前实例:
public class Builder
{
private StringBuilder sb = new StringBuilder(); // StringBuilder是C#中用于高效构建字符串的一个类,尤其是在需要频繁修改字符串内容的情况下。
public Builder Append(string text)
{
sb.Append(text);
return this; // 返回当前实例以允许链式调用
// 比如:builder.Append("Hello").Append(" ").Append("World");
}
public string Build()
{
return sb.ToString();
}
}
3. 方法/函数(Method)
一个方法(函数)是把一些相关功能的语句组织在一起,用来执行一个任务的语句块。每一个C#程序至少有一个带有 Main 方法的类。
方法是类的成员,只能位于类下面;
为了编程规范,方法的命名必须使用大驼峰法明明(如CalTheNumber
),切必须是一个动词或动词短语(方法就是为了完成某项算法);
3.1 定义/调用方法
- 定义方法:
class NumberManipulator
{
public int FindMax(int num1, int num2) // <访问修饰符> <返回值类型> <方法名>(参数列表)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
...
}
- 调用方法:
using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;
namespace CalculatorApplication
{
class NumberManipulator
{
static void Main(string[] args)
{
/* 局部变量定义 */
int a = 100;
int b = 200;
int ret;
NumberManipulator n = new NumberManipulator();
//调用 FindMax 方法
ret = n.FindMax(a, b);
Console.WriteLine("最大值是: {0}", ret);
Console.ReadLine();
}
public int FindMax(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
}
}
- 递归调用:
递归就是方法的自我调用;
using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;
namespace CalculatorApplication
{
class NumberManipulator
{
public int factorial(int num)
{
/* 局部变量定义 */
int result;
if (num == 1)
{
return 1;
}
else
{
result = factorial(num - 1) * num; // 递归
return result;
}
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
//调用 factorial 方法
Console.WriteLine("6 的阶乘是: {0}", n.factorial(6));
Console.WriteLine("7 的阶乘是: {0}", n.factorial(7));
Console.WriteLine("8 的阶乘是: {0}", n.factorial(8));
Console.ReadLine();
}
}
}
3.2 参数传递
当调用带有参数的方法时,需要向方法传递参数。
在 C# 中,有三种向方法传递参数的方式:
方式 | 描述 |
---|---|
值参数 | 参数传递的默认方式,复制参数的实际值给函数的形式参数,实参和形参使用的是两个不同内存中的值。在这种情况下,当形参的值发生改变时,不会影响实参的值,保证了实参数据的安全。 |
引用参数 | 复制参数的内存位置的引用给形式参数。在这种情况下,当形参的值发生改变时,实参的值也会随之改变。 |
输出参数 | 可返回多个值。 |
3.2.1 按值传递参数
参数传递的默认方式。在这种方式下,当调用一个方法时,会为每个值参数创建一个新的存储位置。
实际参数的值会复制给形参,实参和形参使用的是两个不同内存中的值。所以,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。
using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;
namespace CalculatorApplication
{
class NumberManipulator
{
public void swap(int x, int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
int b = 200;
Console.WriteLine("在交换之前,a 的值: {0}", a);
Console.WriteLine("在交换之前,b 的值: {0}", b);
/* 调用函数来交换值 */
n.swap(a, b);
Console.WriteLine("在交换之后,a 的值: {0}", a);
Console.WriteLine("在交换之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
运行结果:
在交换之前,a 的值:100
在交换之前,b 的值:200
在交换之后,a 的值:100
在交换之后,b 的值:200
3.2.2 按引用传递参数
引用参数是一个对变量的内存位置的引用。与值参数不同的是,它不会为这些参数创建一个新的存储位置。
在 C# 中,使用ref
关键字声明引用参数。
using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;
namespace CalculatorApplication
{
class NumberManipulator
{
public void swap(ref int x, ref int y) // 按引用传递参数
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
int b = 200;
Console.WriteLine("在交换之前,a 的值: {0}", a);
Console.WriteLine("在交换之前,b 的值: {0}", b);
/* 调用函数来交换值 */
n.swap(ref a, ref b);
Console.WriteLine("在交换之后,a 的值: {0}", a);
Console.WriteLine("在交换之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
运行结果:
在交换之前,a 的值:100
在交换之前,b 的值:200
在交换之后,a 的值:200
在交换之后,b 的值:100
3.2.3 按输出传递参数
return
语句可用于只从函数中返回一个值。而使用输出参数可从函数中返回两个值。输出参数会把方法输出的数据赋给自己,其他方面与引用参数相似。
另外对比于按引用传递参数方式来说,可提供给输出参数的变量不需要初始化赋值。当需要从一个参数没有指定初始值的方法中返回值时,输出参数特别有用。
在 C# 中,使用out
关键字声明引用参数。
using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;
namespace CalculatorApplication
{
class NumberManipulator
{
public void getValues(out int x, out int y)
{
Console.WriteLine("请输入第一个值: ");
x = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入第二个值: ");
y = Convert.ToInt32(Console.ReadLine());
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a, b; // 变量未进行初始化
/* 调用函数来获取值 */
n.getValues(out a, out b);
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.WriteLine("在方法调用之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
3.3 具名参数
具名参数(Named Arguments)允许在调用方法时通过参数名称来指定参数的值,而不是仅仅依赖参数的顺序。这增加了代码的可读性和维护性,特别是当方法具有多个参数时。
using System;
class Program
{
static void Main()
{
// 使用具名参数调用方法
DisplayPerson(firstName: "John", lastName: "Doe", age: 30);
}
static void DisplayPerson(string firstName, string lastName, int age)
{
Console.WriteLine($"First Name: {firstName}");
Console.WriteLine($"Last Name: {lastName}");
Console.WriteLine($"Age: {age}");
}
}
4.7 可选参数
可选参数(Optional Parameters)允许在定义方法时指定某些参数具有默认值,从而在调用方法时可以省略这些参数。通过使用可选参数,减少方法重载的需要,并简化代码。
可选参数必须位于所有必需参数之后。你不能在必需参数之前定义可选参数。
- 可选参数的默认值可以是任何有效的常量表达式,包括:
- 字面量(如数字、字符串)
- null(对于引用类型)
- default(T)(对于值类型,表示该类型的默认值)
- 枚举成员
- 常量表达式的结果
using System;
class Program
{
static void Main()
{
// 调用方法,省略可选参数
DisplayMessage("Hello");
// 提供所有参数的值
DisplayMessage("Hello", "World");
}
static void DisplayMessage(string text, string title = "Message") // title 参数有一个默认值 "Message",因此它是一个可选参数
{
Console.WriteLine(title + ": " + text);
}
}
# 运行结果:
Message: Hello
World: Hello
4. 访问修饰符(封装)
封装即把一个或多个项目封闭在一个物理的或者逻辑的包(package)中。在面向对象程序设计中,封装是为了防止用户对实现细节的访问。
C# 封装根据具体的需要,设置使用者的访问权限,并通过访问修饰符来实现。
访问修饰符定义了类成员(方法、变量、属性、事件等)的范围和可见性。C# 支持的访问修饰符如下:
public
:公开,所有对象都可以访问,但是需要引用命名空间;private
:私有,类的内部才可以访问;protected
:受保护的,类的内部或类的父类和子类中可以访问;internal
:内部的,同一个程序集的对象可以访问,程序集就是命名空间;new
static
:静态,静态类不能实例化;virtual
sealed
override
abstract
extern
async
注:以上访问修饰符有的可组合使用,有的不能
4.1 Public
允许本方法、变量被本类外的代码调用;
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());
}
}// Rectangle 结束
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.length = 4.5; // 访问Rectangle类的成员变量
r.width = 3.5;
r.Display(); // 访问Rectangle类的成员函数
Console.ReadLine();
}
}
}
4.2 Private
只允许本类内的代码访问类内方法、变量;
如果类的成员变量和成员函数没有指定访问修饰符,则private
就是它们的默认访问修饰符。
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());
}
}//end class Rectangle
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.Acceptdetails();
r.Display();
Console.ReadLine();
}
}
}
4.3 Protected
Protected
访问修饰符允许子类访问它的基类(父类)的成员变量和成员函数。这样有助于实现继承。
4.4 Internal
Internal
访问说明符允许一个类将其成员变量和成员函数开放给当前程序中的其他函数和对象。即带有 internal
访问修饰符的任何成员可被定义在该成员所定义的项目内的任何类或方法访问。(这里的项目是只单独的项目,而不是整个解决方案,这是Internal
和Public
的区别)
如果类没有指定访问修饰符,则internal
就是它的默认访问修饰符。
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());
}
}//end class Rectangle
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.Acceptdetails();
r.Display();
Console.ReadLine();
}
}
}
4.5 Static
在类、接口和结构体中可以使用 static 关键字修饰变量、函数、构造函数、类、属性、运算符和事件。
- 静态属性:静态属性不仅可以使用成员函数来初始化,还可以直接在类外进行初始化;
using System;
namespace c
{
class Demo
{
static void Main(string[] args)
{
Test.str = "1111"; // 在类外对类的静态属性进行初始化
Console.WriteLine(Test.str);
Test test1 = new Test();
test1.getStr();
Test test2 = new Test();
test2.getStr();
test2.setStr("2222");
test1.getStr();
test2.getStr();
}
}
public class Test
{
public static string str; // 静态属性
public void setStr(string s){
str = s;
}
public void getStr(){
Console.WriteLine(str);
}
}
}
#运行结果:
1111
1111
1111
2222
2222
- 静态函数:静态函数只能访问静态属性
using System;
namespace c
{
class Demo
{
static void Main(string[] args)
{
Test test1 = new Test();
test1.setStr("1111");
Test.getStr();
Test test2 = new Test();
test2.setStr("2222");
Test.getStr();
}
}
public class Test
{
public static string str;
public void setStr(string s){
str = s;
}
public static void getStr(){
Console.WriteLine(str);
}
}
}
#运行结果:
1111
2222
4.6 readonly
readonly
是一个访问修饰符,用于声明一个字段只能在声明时或构造函数中赋值,之后就不能再被修改。
using System;
namespace LineApplication
{
class Line
{
static void Main(string[] args)
{
Student stu1 = new Student(001);
Console.WriteLine(stu1.ID);
stu1.ID = 002; // 编译报错
}
}
class Student
{
public readonly int ID;
public readonly int Field = 42; // 在声明时初始化
public Student(int iD) // 自定义构造器
{
ID = iD;// 在实例化对象时可以对readonly 的ID 参数进行赋值,之后则再也不能修改其值
}
}
}
5. 多态
多态即同一个行为在不同的应用环境具有多个不同表现形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
在 C# 中,每个类都是多态的,因为包括用户定义类在内的所有类都继承自 Object
.
- 例1:如下图所示:从电脑发出的“打印”指令动作,不同的打印机接收,打印的效果不同。
- 再者,比如按下 F1 这个动作:
如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
如果当前在 Word 下弹出的就是 Word 帮助;
在 Windows 下弹出的就是 Windows 帮助和支持。
同一个事件发生在不同的对象上会产生不同的结果。
5.1 静态多态性
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
- 函数重载
- 运算符重载
5.1.1 方法重载(Overload)
即在同一个作用范围内的同一个函数名有多个功能定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同、顺序不同,数量不同。只有返回类型不同的函数声明不允许重载。
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;
}
}
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);
}
}
}
如下图,当在应用一个方法时,提示有19个该方法的重载,此时按下键盘上下键,即可选择;
5.1.2 运算符重载
运算符重载即重载 C# 中内置的运算符。使得程序员可使用用户自定义类型的运算符。重载运算符实质是具有特殊名称的函数,通过关键字operator
后跟运算符的符号来定义。与其他函数一样,重载运算符有返回类型和参数列表。
using System;
namespace OperatorOvlApplication
{
class Box
{
private double length; // 长度
private double breadth; // 宽度
private double height; // 高度
public double getVolume()
{
return length * breadth * height;
}
public void setLength(double len)
{
length = len;
}
public void setBreadth(double bre)
{
breadth = bre;
}
public void setHeight(double hei)
{
height = hei;
}
// 重载 + 运算符来把两个 Box 对象相加
public static Box operator +(Box b, Box c)
{
Box box = new Box();
box.length = b.length + c.length;
box.breadth = b.breadth + c.breadth;
box.height = b.height + c.height;
return box;
}
}
class Tester
{
static void Main(string[] args)
{
Box Box1 = new Box(); // 声明 Box1,类型为 Box
Box Box2 = new Box(); // 声明 Box2,类型为 Box
Box Box3 = new Box(); // 声明 Box3,类型为 Box
double volume = 0.0; // 体积
// 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);
// 把两个对象相加
Box3 = Box1 + Box2; // 应用重载的运算符 +
// Box3 的体积
volume = Box3.getVolume();
Console.WriteLine("Box3 的体积: {0}", volume);
Console.ReadKey();
}
}
}
5.1.2.1 可重载和不可重载运算符
运算符 | 描述 |
---|---|
+, -, !, ~, ++, – | 这些一元运算符只有一个操作数,且可以被重载。 |
+, -, *, /, % | 这些二元运算符带有两个操作数,且可以被重载。 |
==, !=, <, >, <=, >= | 这些比较运算符可以被重载。 |
&&, ` | |
+=, -=, *=, /=, %= | 这些赋值运算符不能被重载。 |
=, ., ?:, ->, new, is, sizeof, typeof | 这些运算符不能被重载。 |
5.2 动态多态性
C# 允许使用关键字 abstract
创建抽象类。当一个派生类继承自该抽象类时,该类的实例化即完成。抽象类包含抽象方法,抽象方法可被派生类调用。
下面是有关抽象类的一些规则:
- 不能创建一个抽象类的实例。
- 不能在一个抽象类外部声明一个抽象方法。
- 通过在类定义前面放置关键字
sealed
,将类声明为密封类。当一个类被声明为sealed
时,它不能被继承。抽象类不能被声明为sealed
.
using System;
namespace PolymorphismApplication
{
// 抽象类
abstract class Shape
{
abstract public int area(); // 抽象方法
}
// 子类(继承抽象类)
class Rectangle : Shape
{
private int length;
private int width;
public Rectangle(int a = 0, int b = 0)
{
length = a;
width = b;
}
// 子类方法继承抽象类的抽象方法
public override int area()
{
Console.WriteLine("Rectangle 类的面积:");
return (width * length);
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle r = new Rectangle(10, 7);
double a = r.area();
Console.WriteLine("面积: {0}", a);
Console.ReadKey();
}
}
}
当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法。
虚方法使用关键字 virtual
声明。
虚方法可在不同的继承类中有不同的实现。
对虚方法的调用在运行时发生。
动态多态性是通过 抽象类 和 虚方法 实现的。
- 例程1:
using System;
using System.Collections.Generic;
//父类
public class Shape
{
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }
// 虚方法
public virtual void Draw()
{
Console.WriteLine("执行基类的画图任务");
}
}
// 子类
class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("画一个圆形");
base.Draw();
}
}
// 子类
class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("画一个长方形");
base.Draw();
}
}
// 子类
class Triangle : Shape
{
public override void Draw()
{
Console.WriteLine("画一个三角形");
base.Draw();
}
}
class Program
{
static void Main(string[] args)
{
// 创建一个 List<Shape> 对象,并向该对象添加 Circle、Triangle 和 Rectangle
var shapes = new List<Shape>
{
new Rectangle(),
new Triangle(),
new Circle()
};
// 使用 foreach 循环对该列表的派生类进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法
foreach (var shape in shapes)
{
shape.Draw();
}
Console.WriteLine("按下任意键退出。");
Console.ReadKey();
}
}
运行结果:
画一个长方形
执行基类的画图任务
画一个三角形
执行基类的画图任务
画一个圆形
执行基类的画图任务
- 例程2:
using System;
namespace PolymorphismApplication
{
// 父类
class Shape
{
protected int width, height;
public Shape(int a = 0, int b = 0)
{
width = a;
height = b;
}
public virtual int area()
{
Console.WriteLine("父类的面积:");
return 0;
}
}
// 子类
class Rectangle : Shape
{
public Rectangle(int a = 0, int b = 0) : base(a, b)
{
}
public override int area()
{
Console.WriteLine("Rectangle 类的面积:");
return (width * height);
}
}
// 子类
class Triangle : Shape
{
public Triangle(int a = 0, int b = 0) : base(a, b)
{
}
public override int area()
{
Console.WriteLine("Triangle 类的面积:");
return (width * height / 2);
}
}
// 子类
class Caller
{
public void CallArea(Shape sh) // 输入参数是类Shape的派生类的对象
{
int a;
a = sh.area();
Console.WriteLine("面积: {0}", a);
}
}
// 子类
class Tester
{
static void Main(string[] args)
{
Caller c = new Caller();
Rectangle r = new Rectangle(10, 7);
Triangle t = new Triangle(10, 5);
c.CallArea(r);
c.CallArea(t);
Console.ReadKey();
}
}
}
运行结果:
Rectangle 类的面积:
面积:70
Triangle 类的面积:
面积:25
6. 接口(Interface)
接口定义了所有类继承接口时应遵循的语法规则。接口定义了语法规则 “是什么” 部分,派生类定义了语法规则 “怎么做” 部分。
接口定义了属性、方法和事件,它们都是接口的成员。接口只包含了成员的声明。成员的定义由派生类完成。接口提供了派生类应遵循的标准结构。
接口使得实现接口的类或结构在形式上保持一致。
抽象类在某种程度上与接口类似,但是,它们大多只是用在当只有少数方法由基类声明由派生类实现时。
6.1 接口定义: MyInterface.cs
接口使用 interface
关键字声明,它与类的声明类似。接口声明默认是 public 的。接口命一般都以I
开头。
using System;
// 定义一个接口
interface IMyInterface
{
// 接口成员函数声明
void MethodToImplement();
}
// 接口的实现
class InterfaceImplementer : IMyInterface // InterfaceImplementer 类实现了 IMyInterface 接口,接口的实现与类的继承语法格式类似
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
}
// 实现接口的方法 MethodToImplement() , 方法名必须与接口定义的方法名一致
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
}
6.2 接口继承: InterfaceInheritance.cs
using System;
// 接口 IParentInterface
interface IParentInterface
{
void ParentInterfaceMethod();
}
// 接口 IMyInterface,继承于 IParentInterface
interface IMyInterface : IParentInterface
{
void MethodToImplement();
}
// 接口的实现
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
iImp.ParentInterfaceMethod();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
public void ParentInterfaceMethod()
{
Console.WriteLine("ParentInterfaceMethod() called.");
}
}
运行结果:
MethodToImplement() called.
ParentInterfaceMethod() called.
*. 常用方法
*.1. 随机数 - Random()
// 实例化对象
Random randomizer = new Random();
int addend1;
addend1 = randomizer.Next(51); // 在51 内生成随机数
addend1 = randomizer.Next(1, 101);// 在1~101 内生成随机数
*.2 延时 - Sleep()
System.Threading.Thread.Sleep(1000); // 延时1s
*.3 获取数据的类型 - GetType()
var i = 3;
var j = 3.0;
var k = 3L;
Console.WriteLine(i.GetType().Name);
Console.WriteLine(j.GetType().Name);
Console.WriteLine(k.GetType().Name);
打印结果:
Int32 // 32位的整形
Double
Int64
- 查看该数据类型的最值:
uint max = uint.MaxValue;
uint min = uint.MinValue;
Console.WriteLine(max);
Console.WriteLine(min);
#运行结果:
4294967295
0
*.4 接受用户输入的值 - ReadLine()
使用 ReadLine()
函数可以接受来自用户输入的内容并将其存储到变量中;
using System;
namespace c
{
class Program {
static void Main(string[] args) {
int a, b;
Console.WriteLine("请输入第一个数字:");
a = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入第二个数字:");
b = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("{0}+{1}={2}", a, b, a+b);
}
}
}
*.5 查看对象包含的所有方法 - GetMethods()
using System;
namespace LineApplication
{
class Line
{
static void Main(string[] args)
{
Type t = typeof(int);
int counts = t.GetMethods().Length;
foreach (var method in t.GetMethods())
{
Console.WriteLine(method.Name);
}
Console.WriteLine(counts);
}
}
}
#运行结果:
CompareTo
CompareTo
Equals
Equals
GetHashCode
ToString
ToString
ToString
ToString
Parse
Parse
Parse
Parse
TryParse
TryParse
GetTypeCode
GetType
17
*.6 异常抓取 - try - catch
使用try - catch
抓取可能发生的异常,避免程序在运行过程中崩溃;
uint max = uint.MaxValue;
try
{
uint max_1 = checked(max + 1);
}
catch (InternalBufferOverflowException ex)
{
Console.WriteLine("There is overflow!");
}
*.7 字符串转换为特定数据类型 - Parse
、TryParse
方法
Parse
和TryParse
是两种用于将字符串转换为特定数据类型(如整数、浮点数、日期等)的方法。这两种方法的主要区别在于错误处理机制:Parse
方法在转换失败时会抛出异常,而TryParse
方法则通过返回bool 值来指示转换是否成功,从而避免了异常处理的需要。
parse
:一种静态方法,通常定义在数据类型的类中(例如int.Parse、double.Parse、DateTime.Parse等)。它的作用是将一个字符串参数解析为指定类型的对象。如果字符串不能被解析为有效的类型值,它会抛出一个异常(通常是FormatException)。
string numberString = "123";
int number = int.Parse(numberString);
Console.WriteLine(number); // 输出: 123
TryParse
:一种静态方法,TryParse 方法尝试将字符串转换为指定类型的对象,并返回一个布尔值来指示转换是否成功。如果转换成功,它还会通过输出参数返回转换后的值。
string numberString = "123";
int number;
bool success = int.TryParse(numberString, out number);
if (success)
{
Console.WriteLine(number); // 输出: 123
}
else
{
Console.WriteLine("转换失败");
}
- 何时使用
Parse
和TryParse
:- 如果确信字符串总是可以成功转换为目标类型,或者你愿意在转换失败时让程序抛出异常并终止,那么可以使用Parse方法。
- 如果不确定字符串是否可以被成功解析,或者想要在转换失败时采取一些特定的措施(例如,给用户显示一个错误消息或使用默认值),那么应该使用TryParse方法。这样可以避免异常处理带来的额外开销和复杂性。