我的c#学习笔记
- 纯属自用, 笔者有c语言基础, 所以记笔记的时候会略过c语言相关内容. 如果你有学习c#的需要, 请参照菜鸟教程里的c#教程.
- 持续更新.
- (脏话)这东西被我学的越来越盘根错节了, 之前的内容会经常性的进行补正. 真的好(脏话)难顶. 好不容易学到多态了开始补继承的锅.
我的第一个c#程序
下面编写一个能够输出Hello, World!的程序.
using System;
namespace HelloWorldApplication{
class HelloWorld{
static void Main(string[] args){
Console.WriteLine("Hello, World!");
Console.ReadKey();
}
}
}
通过这个简单的程序,我们可以看到c#的一些特性.
- c#的程序一般以using开头. using System;的内涵是包含一个System的命名空间. 一般而言,程序中会有很多using语句.
- 下一行是namespace声明. namespace 中包含了一系列的类.
- 下一行是class声明. 在类中,可以声明所用到的数据和方法
- 下一行定义的Main方法是c#程序的入口.
- 与c语言不同, c#是一个面向对象的语言. 也就是说, 其所有行为皆是由某个对象发出的. 如果我们想要在控制台输出语句, 则需要命令控制台(Console)为我们输出一行字符.
- 最后一行是让控制台读入字符, 防止运行结束后自动关闭.
下面是一个略微复杂一点的c#程序.
using System;
namespace RectangleApplication {
class Rectangle {
double length;
double breadth;
public void SetDetails() {
length = 4;
breadth = 3.5;
}
public double GetArea() {
return length * breadth;
}
public void DisplayArea() {
Console.WriteLine("The Area is: {0}.", GetArea());
}
}
class RectangleApplication {
static void Main(string[] args) {
Rectangle r = new Rectangle();
r.SetDetails();
r.DisplayArea();
Console.ReadLine();
}
}
}
c#的数据类型
1. 值类型
c#中的常用的值类型与c相似,只是c中的long long是c#中的long. c#中的无符号整形不再写作unsigned int而写作uint.
如果想要知道某个类型的在某个特定平台上的尺寸,则可以使用sizeof,例如:sizeof(int)
.
2.引用类型
值类型的变量只需要一段单独的内存, 用于储存实际的数据. 而引用类型分为两段内存,一段是位于堆中的数据, 第二段是一个在栈中的引用,它指向堆中数据的位置.
内置的引用类型有三种:
- object类型 是所有数据类型的终极基类, 所以可以被分配任何其他类型的值. 在分配值之前需要进行类型转换.
- dynamic 也可以储存任何数据类型的值, 但与object不同,这些值的转化是在运行时产生的,(而object的类型转换是在编译时产生的).
- string类型.允许为string类型的变量分配任何字符串.
此外还有一些用户自定义的引用类型,我们在稍后的章节讨论.
3.指针类型
语法和功能都同c中的指针. 能够储存另一种类型的地址.
4. 引用类型的变量和指针类型有什么不同?
可能有人发现了引用变量和指针变量功能上比较相似, 它们有什么区别呢? 笔者也很好奇! 于是查资料得到了以下答案:
指针类型和引用类型之间的区别在于指针是指向内存中的某个位置的, 而引用是指向内存中的某个对象. 所以引用类型比指针类型要更安全. 比如,你现在使用
int* p1 = GetAPointer();
获得了一个指针. 虽然指针p1被声明为指向int类型的指针, 但实际上你并不能保证GetAPointer()到底返回的是一个什么东西, 也许它真的是一个指向int的指针,也许是指向char的指针,甚至有可能是空的,或者指向了一旦被修改就会出严重问题的数据.
然而, 引用在这方面就比指针安全. 例如, 你使用
string str = GetAString();
获得了一个引用,则这个引用要么没有指向任何对象,要么指向一个有效的字符串.
c#中的数据转换
隐式类型转换
当将一个较小范围内的数据类型转换为一个较大范围内的数据类型时, 直接赋值即可. 编译器会自动完成类型转换. 此类隐式类型转换保证为安全的, 不会丢失数据.
显式类型转换
当将一个较大范围内的数据类型转换为一个较小范围内的数据类型时, 需要使用强制数据转换运算符进行转换. 这里似乎使用的转换方法是截去高位.
此外, 还提供类型转换的方法供使用. 方法的名字很好记, 都是"To"+"类型名(首字母大写)".
下面是一些类型转换的例子.
using System;
namespace DataConvertApplication {
class DataConvert {
sbyte a;
short b;
double c;
static void Main(string[] args) {
DataConvert obj = new DataConvert();
obj.b = 256;
obj.a = (sbyte)obj.b;
obj.c = 123.123;
Console.WriteLine("{0},{1},{2}", obj.a, obj.b, obj.c.ToString());
}
}
}
c#中的变量与常量
c#的变量部分和c相似,不再赘述.
c#的常量增添了以下内容:
- 每一个整形常量后可以有U(Unsigned)和L(Long)做后缀, 顺序任意.
- 在字符串常量前加上
@
符号, 产生的字符串常量将把转义字符\普通字符.
c#中的运算符
绝大多数运算符与C语言相同. 这里着重介绍不同的.
typeof()
运算: 用于返回某个class的类型.
is
运算: 判断对象是否为某一类型.
as
运算: 强制类型转换, 转换失败也不会抛出异常.
下面是一个示例:
using System;
namespace OperatorApplication {
class Operation {
static void Main(string[] args) {
int a = -100;
Type type = typeof(int);
Console.WriteLine("{0}",type.FullName);
Console.WriteLine("if a refer to int type?{0}",a is int);
}
}
}
C#的封装
终于写到一点C语言没有的东西了! 笔者狂喜!
人类的科学之所以可以长足发展, 全仰仗与封装的伟大思想. 所谓封装, 是指将细节封闭在一个逻辑的包中, 这样你下次需要调用这一功能的时候就可以忽略其中细节而使用它. 举个例子: 当我们在小学低年级第一次学习乘法口诀的时候, 我们拿出9组小棍, 每组有6个, 我们数了一遍得知有54个小棍. 这时我们的老师就要求我们记住一个结论: . 这样我们下次计算的时候,直接调用这个结果即可, 而不用重新数一遍小棍. 这就是一种封装.
封装在计算机中也是一种伟大的思想: 我们今天可以使用各种方便的编程语言,而不需要细究其背后的汇编语言如何运转, 以及某个逻辑门的亮灭.
我们在此处谈论的封装, 是为了防止程序对于细节的过多访问. 我们使用修饰符来实现.
一个访问修饰符定义了一个类成员的可见性( 类成员有成员变量和成员函数 ). 修饰符共有五种
- public: 所有外部的类皆可以访问该成员.
- private: 仅有同一个类的成员才可以访问该成员.
- protect: 该类的成员, 以及其子类的成员可以访问该成员. 这有助于实现继承.
- internal: 同一个应用程序里面的类可以访问该成员.
- protect internal: 就是protect + internal.
下面来看这几种访问修饰符的示例:
using System;
namespace AccessSpecifierrApplication {
class Class1 {
public int a = 3;
public int b = 4;
public int GetArea() {
return a * b;
}
}
class Class2 {
private int a = 3;
private int b = 4;
public int GetLength() {
return a + b;
}
}
class Application {
static void Main(string[] args) {
Class1 t1 = new Class1();
Console.WriteLine("{0},{1}", t1.a, t1.b);
Console.WriteLine("{0}", t1.GetArea());
Class2 t2 = new Class2();
//Console.WriteLine("{0},{1}", t2.a, t2.b);
//you can not excute above sentence because t2.a is private.
Console.WriteLine("{0}", t2.GetLength());
//t2.GetLength is public.
}
}
}
C#的方法
其实就是C语言中的函数
C#中定义方法的语法如下:
<Access Specifier> <Return Type> <Method Name>(Parameter List)
{
Method Body
}
要注意现在可以在方法前面加上<Access Specifier>
了. 默认是private.
在c#中, 可以直接只用方法名来调用同一个类里的方法. 也可以使用类的示例来调佣别的类里的public方法(例子如上).
对于方法的参数, 除了最普通的值参数之外, 还有引用参数和输出参数
-
引用参数
- 引用参数是对变量内存的引用. 就和如上已经提过的引用类型差不多.
- 想要使用引用参数, 则在声明和调用方法时的参数最前端加上
ref
.
-
输出参数
- 输出参数在返回值时, 会将方法中的对应参数赋值给自己. 也就是和引用参数差不多( )
- 想要使用引用参数, 则在声明和调用方法时的参数最前端加上
out
.
-
两者有什么差别?
- 引用参数在被引用时要求已经被赋过值,但是输出参数则没有这方面的要求. 所以在需要在函数里被赋值的场合里会方便不少.
c#的可空类型
在c#中,像int, double, bool等类型的变量是不可以被赋值为NULL的. 如果想要允许他们被赋值为NULL, 则要定义他们为一个可空类型. 定义语法如下:
data_type? variable_name = initial_value;
其中initial_value默认是NULL.
举个例子:
int? a = 3;
这就意味着变量a为一个可以被赋值为~之间与NULL的变量. 它现在的初值是3.
??
是一个合并运算符. 它的运算逻辑是: 若第一个值为NULL则返回第二个值,否则返回第一个值.
C#的数组
这也是一个和C语言中不太相似的概念呢.
想要声明数组, 使用下面的语法:
datatype[] arrayName;
举个例子:
int[] myArray;
这样就定义了一个int类型的数组.
声明一个数组的时候, 该数组并未在内存中初始化. 由于数组类型的变量是引用类型的变量, 所以需要使用new来创建一个数组的实例.
int [] myArray = new int[3];//create a array whose length is 3.
-
多维数组
- 多维数组又称矩形数组. 例如,您可以如下所示的声明一个int类型的二维数组和string类型的三维数组.
int [,] a; string [,,] b;
- 多维数组同样需要实例化才可以使用. 如下是一个初始化例:
int [,,] a = new int[2, 2, 2];
-
交错数组
- 交错数组是一个一维的数组. 它是数组的数组.它与多维数组相近但有一点不同,即每行的元素个数可以不一样. 如下是一个交错数组的创建与实例化.
using System; namespace ArrayTestApplication { class ArrayTest { static void Main(string[] args) { int[][] a = new int[2][]; a[0] = new int[3]; a[1] = new int[4]; //a is an array. the first element of a is an array with 3 length. the second element of a is an array with 4 length. } } }
-
传递数组给参数
- 在方法的声明中, 使用
datatype[] arrayName;
这样的格式, 在方法的调用中直接使用arrayname
传参即可.
- 在方法的声明中, 使用
-
参数数组
- 有时, 声明一个方法时, 并不能确定作为参数的数目, 这时可以使用参数数组传递位置数量的参数给数组.
- 在使用数组作为形参时,C# 提供了 params 关键字,使调用数组为形参的方法时,既可以传递数组实参,也可以传递一组数组元素。params 的使用格式为:
public 返回类型 方法名称( params 类型名称[] 数组名称 )
- 下面是一个例子:
using System; namespace ArrayTestApplication { class Array { public int AddElement(params int[] elements) { int sum = 0; foreach(int i in elements) { sum += i; } return sum; } } class Test { static void Main(string[] args) { Array arr = new Array(); int sum = arr.AddElement(1, 2, 3, 4, 5); Console.WriteLine(sum); } } }
- 输出是:
15
.
C#的字符串
C#的结构体
结构体是一个用户定义的值类型的数据结构, 它使得单一变量可以储存各种数据类型的相关结构.
- 定义
c#的结构体在定义时语法与C语言并没有什么不同. 如下所示:
struct Books
{
public string title;
public string author;
public string subject;
public int book_id;
};
-
特点
- 结构可带有方法、字段、索引、属性、运算符方法和事件。
- 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
- 与类不同,结构不能继承其他的结构或类。
- 结构不能作为其他结构或类的基础结构。
- 结构可实现一个或多个接口。
- 结构成员不能指定为 abstract、virtual 或 protected。
- 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
- 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
下面是一个使用结构体的例子:
using System;
namespace StructApplication {
struct Student {
private int id;
private string name;
private int age;
public void setInfo(int id, string name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public void display() {
Console.WriteLine("{0},{1},{2}", id, name, age);
}
}
class TestStruct {
static void Main(string[] args) {
Student s1 = new Student();
Student s2 = new Student();
s1.setInfo(10000001, "HuXiaohan", 17);
s2.setInfo(10000002, "XieXiaoming", 18);
s1.display();
s2.display();
}
}
}
-
类和结构的不同:
- 类是引用类型,结构是值类型。
- 结构不支持继承。
- 结构不能声明默认的构造函数。
C#的类
当定义了一个类的时候, 我们定义了这个类的名称意味着什么.
你可以在类中定义方法和变量, 这些东西叫做这个类的成员.
类可以被实例化为一个对象. 在类被实例化后它才拥有了储存空间以及数据.
下面是定义一个类的语法:
<access specifier> class class_name
{
// member variables
<access specifier> <data type> variable1;
<access specifier> <data type> variable2;
...
<access specifier> <data type> variableN;
// member methods
<access specifier> <return type> method1(parameter_list)
{
// method body
}
<access specifier> <return type> method2(parameter_list)
{
// method body
}
...
<access specifier> <return type> methodN(parameter_list)
{
// method body
}
}
- 类的默认
<access specifier>
是internal, 而类中成员的默认<access specifier>
是private. - 如果需要访问类的成员, 在对象后使用
.
运算符.
类的成员函数与封装
类的成员函数理所当然地可以访问这个类的全部成员, 也可以在类的对象上操作.
出于对封装的需要, 我们一般不希望在类的外部可以直接访问类的成员变量. 所以我们通过类的成员函数来访问这些变量.
类的构造函数
每当创建类和结构的实例时, 将调用其构造函数. 一个类或结构可以拥有多个不同参数的构造函数.
构造函数是一种方法, 其名与类名相同, 其方法签名仅包含可选访问修饰符, 方法名和参数列表, 不包含返回类型.
如下为一个构造函数:
public class Person {
string firstname;
string lastname;
public Person(string firstname, string lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
}
不带任何参数的构造函数被称为无参构造函数. 当使用new实例化对象且不提供参数时, 将使用无参构造函数.
除非类是静态的, 否则c#编译器将提供一个无参构造函数以便类可以实例化. 通过将构造函数设置为private, 可以阻止该类被实例化.
类和结构可以定义多个构造函数, 如下所示:
using System;
namespace Application {
public class Person {
string name;
int age;
public Person() {
name = "default";
age = 0;
}
public Person(string name, int age) {
this.name = name;
this.age = age;
}
}
class Test {
static void Main(string[] args) {
Person p1 = new Person();
Person p2 = new Person("Zhang san", 12);
}
}
}
类的析构函数
析构函数的名称是在类的名称前加上一个~. 析构函数用于在结束程序是释放内存与资源. 坏消息是我没太明白, 好消息是C#的内存管理做的很好我不需要太明白(笑)
static
注意到我们每次写Main函数时都会在前面加上static关键字, 下面了解static其含义.
static意为静态的, 不变的, 也就是说由static修饰的东西只会有一个, 不会因为实例化的不同而不同.
static可以用来修饰类, 方法, 变量等等.
静态类
静态类有如下特点:
- 不能被实例化. 你可以通过类名加上
.
运算符来访问静态类的成员. - 其成员也必须全为静态的.
- 密封, 不可被继承.
- 不能包含实例构造函数(但可以包含静态构造函数.)
静态类大约相当于一个成员全为静态的拥有private构造函数的类. 使用静态类的好处是编译器会保证不存在这个类的实例. 在只需要对输入输出函数进行操作, 而不需要访问或者设置类内部的任何成员时, 可以方便的将类声明为静态的.
静态成员
非静态类可以包含静态的成员. static在方法的返回类型前, 变量的类型前声明.
静态成员有如下特点:
- 不必对类进行实例化就可访问
- 按类名进行访问(而不是实例名)
静态的方法不可以访问任何对象的实例化变量.
虽然不可以声明static const
, 但是可以使用访问静态成员的方法访问const变量.
下面是一些例子:
public static class PrintClass {
public static int PrintNum;
static PrintClass() {
PrintNum = 0;
}
public static void Print(string text) {
Console.WriteLine(text);
}
}
c#继承
继承允许我们通过一个类来定义另一个类.
比如说我们现在以及有一个Animal(动物)类, 如果想要创建一个Mammal(哺乳动物)类, 我们并不需要重新编写所有的成员变量以及方法, 因为哺乳动物属于动物, 所以这Animal中有的性质Mammal中同样也具有, 所以在编写Mammal类时, 让其继承已有的成员即可. 在这个例子中, Animal被称为基类, Mammal被称为派生类.
c#只支持单一继承. 也就是说一个类不可以继承自两个类. 也并不是所有基类成员都可以被继承, 静态构造函数和实例构造函数并不可以被继承.
可以在派生类中通过base来调用基类的构造函数与方法. 以下是示例:
using System;
public class Rectangle {
protected int width;
protected int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int CalArea() {
return width * height;
}
public void Display() {
Console.WriteLine("宽度是:{0}", width);
Console.WriteLine("高度是:{0}", height);
}
}
class TableTop : Rectangle {
int cost;
public TableTop(int width,int height):base(width,height) {
}
public void SetCost() {
this.cost = CalArea() * 70;
}
public new void Display() {
base.Display();
Console.WriteLine("花费是:{0}", cost);
}
}
class Application {
static void Main(string[] args) {
TableTop t1 = new TableTop(3, 4);
t1.SetCost();
t1.Display();
}
}
继承时, 子类一定会隐式调用父类的无参构造函数. 如果父类没有提供无参构造函数时, 则会报错. 也可以显示的调用父类的有参构造函数.
具体语法如下所示:
using System;
class Parent1 {
protected string str;
public Parent1() {
Console.WriteLine("如果出现了这句话代表父类1无参函数被调用.");
}
public Parent1(string str) {
Console.WriteLine(str);
}
}
class Parent2 {
public Parent2() {
Console.WriteLine("如果出现了这句话代表父类2无参函数被调用.");
}
public Parent2(string str) {
Console.WriteLine(str);
}
}
class Child1:Parent1 {
public Child1() {
Console.WriteLine("如果出现了这句话代表子类1无参函数被调用.");
}
public Child1(string str) {
base.str = str;
}
}
class Child2 : Parent2 {
public Child2(string str) : base(str) {
}
}
class Application {
public static void Main(string[] args) {
Child1 child1 = new Child1();
Console.WriteLine();
Child1 child2 = new Child1("如果没有出现这句话, 则代表父类有参函数没有被调用");
Console.WriteLine();
Child2 child3 = new Child2("Child2 只有有参函数");
}
}
运行结果:
如果出现了这句话代表父类1无参函数被调用.
如果出现了这句话代表子类1无参函数被调用.
如果出现了这句话代表父类1无参函数被调用.
Child2 只有有参函数
可以看出来, 如果没有显式地调用父类的构造函数的话, 父类的无参构造函数将会被隐式地调用. 即使调用子类的有参构造函数, 父类的有参构造函数也不会被隐式调用. 除非你显式地调用它.
c#多态性
多态性, 即使用一个接口而实现多种不同的功能.
多态性分为静态多态性和动态多态性, 如果函数的响应是在编译时发生的, 则为静态多态性, 如果函数的响应是在运行时发生的, 则称为动态多态性.
静态多态性可以由函数重载和运算符重载来实现.
函数重载
允许在同一个范围内对同名的函数有多个定义. 这些函数必须彼此不同, 可以是参数列表的长度不同, 也可以是参数的类型不同, 但不允许只有返回类型不同.
如下为一个示例:
using System;
public class Rectangle {
public int CalArea(int width, int height) {
return width * height;
}
public int CalArea(int LengthOfSide) {
return LengthOfSide * LengthOfSide;
//for square
}
}
public class Application {
static void Main(string[] args) {
Rectangle rect = new Rectangle();
Console.WriteLine(rect.CalArea(3, 4));
Console.WriteLine(rect.CalArea(3));
}
}
运算符重载
可以通过重载或者重定义c#内置的运算符, 来实现在某个类型的运算. 同正常的函数一样, 重载运算符和正常的函数一样, 有参数列表和返回类型. 它通过operator来实现.
下面是一个重载运算符的例子:
using System;
public class Box {
int width;
int height;
int length;
public void Set(int length, int width, int height) {
this.length = length;
this.width = width;
this.height = height;
}
public void Display() {
Console.WriteLine("长度是:{0}", length);
Console.WriteLine("宽度是:{0}", width);
Console.WriteLine("高度是:{0}", height);
}
public static Box operator+ (Box a,Box b) {
Box box = new Box();
box.length = a.length + b.length;
box.width = a.width + b.width;
box.height = a.height + b.height;
return box;
}
}
public class Application {
public static void Main(string[] args) {
Box a = new Box();
Box b = new Box();
a.Set(1, 2, 3);
b.Set(2, 3, 4);
Box c = a + b;
c.Display();
}
}
动态多态性
抽象类是对一系列本质相同, 但实现具体方法不同的东西的抽象.
可以使用abstract关键字来创建一个抽象类. 抽象类中没有实例. 抽象类中可以包含抽象的方法. 您被允许不在抽象类中实现它而在抽象类的具体继承中实现它. 在实现时需要在访问修饰符后面加上override.
抽象类可以继承自抽象类. 但是如果抽象类被不是抽象类的类继承, 则需要重写其所有抽象函数.
如果在类前加上sealed, 则称该类为密封的. 抽象类不可以密封.
下面为一个抽象类的例子:
using System;
public abstract class Shape {
public abstract int CalArea();
}
class Rectangle : Shape {
int length;
int width;
public void Set(int length,int width) {
this.length = length;
this.width = width;
}
public override int CalArea() {
return length * width;
}
}
public class Application {
static void Main(string[] args) {
Rectangle rect = new Rectangle();
rect.Set(2, 3);
Console.WriteLine(rect.CalArea());
}
}
要注意抽象的方法必须包含在抽象的类中, 而抽象类中可以不必包含抽象的方法.
如果定义在某个非抽象类的方法需要其继承类来实现的话, 就要用到虚方法. 虚方法使用关键字virtual实现的.
虚方法和抽象方法的另一个区别是, 虚方法需要在基类中被实现, 可以在继承类中被重写. (当然也可以不重写, 这样就会默认继承基类的这个方法.) 但抽象方法在基类中不可以被实现.
下面是一个例子:
using System;
public class Shape {
protected double x;
protected double y;
virtual public double CalArea() {
return x * y;
}
public void Display() {
Console.WriteLine(CalArea());
}
}
class Triangle : Shape {
double z;
public Triangle(double x, double y, double z) {
base.x = x;
base.y = y;
this.z = z;
}
public override double CalArea() {
double p = 1.0 / 2 *( (base.x) + (base.y) + z);
return Math.Sqrt(p * (p - base.x) * (p - base.y) * (p - z));
}
}
class Rectangle : Shape {
public Rectangle(double x, double y) {
base.x = x;
base.y = y;
}
}
class Circle : Shape {
public Circle(double x) {
base.x = x;
}
const double PI = 3.14159;
public override double CalArea() {
return PI * base.x * base.x;
}
}
class Application {
public static void Main(string[] args) {
Circle circle = new Circle(3);
Triangle triangle = new Triangle(3, 4, 5);
Rectangle rectangle = new Rectangle(3, 4);
circle.Display();
triangle.Display();
rectangle.Display();
}
}
注意到virtual不能与关键字static, private, abstract, override同用.
c#接口
接口本身并不实现任何功能. 接口只是一个"实现我的对象一定需要实现行为"的契约.
这么讲还是太抽象了, 来看一个很简单的接口的例子吧.
(注意C#语言的代码规范要求接口的命名以大写I开头. )
using System;
interface IMyInterface {
public void Display();
}
class InterfaceImplementer:IMyInterface {
public void Display() {
Console.WriteLine("example");
}
static void Main(string[] args) {
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.Display();
}
}
我们看到了, 接口只包含成员的声明, 至于成员的实现则交由派生类来完成.
接口可以继承. 如果一个接口继承了其它的接口, 那么最终实现的那个类需要实现被继承的所有成员.
存在一些"默认接口成员", 即, 我们可以在接口中声明他们的主体而不是等到具体的类中. 这些默认接口成员有常量, 运算符, 静态构造函数等等.
还记得我们之前在继承中提过的, c#中不允许多继承么, 但好消息是, 一个类是可以继承多个接口的! 所以我们可以用接口来模拟这种行为.
下面来看一个这样的例子:
using System;
namespace InterfaceApplication {
public interface IFly {
void Fly();
//接口中方法不必有访问修饰符
}
public interface IEat {
void Eat();
}
public class Bird:IFly,IEat {
public void Fly() {
Console.WriteLine("I can fly");
}
public void Eat() {
Console.WriteLine("I can eat");
}
}
class Application {
public static void Main(string[] args) {
Bird bird = new Bird();
bird.Fly();
bird.Eat();
}
}
}
c#命名空间
命名空间的设计是提供让一组名称与其他的名称分割开来的方式. 就像在计算机中, 同一个文件夹下面不能放两个同名的文件, 然而不同的文件夹下却可以存在两个同名文件.
在引用语句时, 使用"命名空间"+"."的方式来区别不同命名空间下的同名语句. 在不产生歧义的前提下, 您可以使用using语句略过对命名空间的引用.
c#的预处理器指令
虽然这块C语言里也有,但是笔者学C语言的时候没学会啊,在这里补课了(喜)
定义符号
和定义符号有关的预处理指令有两条:
- #define: 定义符号
- #undef: 取消定义符号
请注意, 与C语言不同, C#中的#define不能用来声明常量值.
条件编译
可以使用条件编译的指令来检查某个符号是否被定义过. 条件编译的预处理指令有四条:
- #if 打开条件编译, 仅在定义了其中指定的符号后才会编译代码.
- #elif 关闭前面的条件定义, 并根据是否定义了其中的指定符号来决定是否打开一个新的条件编译
- #else 关闭前面的条件编译, 如果if中指定的符号未被定义, 则打开条件编译.
- #endif 关闭前面的条件编译.
注意所有打开的条件编译必须使用#endif显式地关闭.
在这些条件编译的符号中, 您可以使用&&, ||, !, ==, !=来对其进行运算. 这些符号的浴衣和平常是一样的.
下面是一些使用例:
#define A
using System;
class Application {
static void Main(string[] args) {
#if (A && B)
Console.WriteLine("A and B are defined.")
#elif (A)
Console.WriteLine("A is defined.");
#else
Console.WriteLine("B is defined.");
#endif
}
}
输出:
Console.WriteLine("A is defined.");
正则表达式
正则表达式是一种文本模式, 它提供了一种灵活强大的方式来匹配查找字符串.
具体的, 请参阅我的正则表达式笔记