数据类型
- 值
- 引用
- 指针
- 可空类型
值
值类型的值存在栈上,引用类型的栈上存的是地址,值是在堆上。
传参或赋值,会发生拷贝而不是引用。
常量
- 整数:10,20
- 浮点:10.0,20u
- 字符:‘a’
- 字符串:“abc”
- 自定义常量:使用const关键字
const int a = 10;
变量
与C++基本相同。不同点:
- decimal:128位,精确的十进制类型,有符号
- sbyte:8位,整数,有符号
- byte:在C++和C#中都是8位无符号整数
decimal a;
sbyte b;
byte c;
枚举与结构体
都是值类型
引用
引用类型不包含存储在变量中的实际数据,使用多个变量时,引用类型指向一个内存位置。
传参或者赋值时,按引用,不会发生拷贝生成新的副本。
值类型的值存在栈上,引用类型的栈上存的是地址,值是在堆上。
(没有int& a = b
这种C++里的引用符号 “&”!)
-
对象类:可以存放任何类型,因为是.Net中所有类的基类。
/*装箱、拆箱*/ Object Obj; // Object类型是所有类的基类 MyObject b = new MyObject(1); MyObject c; Obj = b; // 装箱 c = (MyObject)Obj; // 拆箱:c此时为b的别名 c = Obj as MyObject; // 拆箱:使用"as"进行强制转换,因为使用C、C++中的"(Type1)Type2的变量"转换如果失败,则会抛出错误,而使用“as”则失败时会返回null,不会出错。
-
数组
int[] a = {1,2,3,4,5}; // 编译器将自动转化为下面这条语句 int[] a = new int[5]{1,2,3,4,5}; // 标准赋值方法 int[] a = new int[]{1,2,3,4,5}; // ke'sheng int[] b = a; // 此时b与a指向同一片空间,修改任意一个同步改变
-
动态类:可以存放任何类型,并且在运行时才会动态赋值
dynamic a = "string"; // 编译器不会检查成员的有效性,直到运行时才会检查成员调用、参数等的有效性 Console.WriteLine(a.ToUpper()); Console.WriteLine(a.toupper()); Console.WriteLine(a.Foo(10,DateTime.Now));
-
String类
String a = @"ab\cde"; // "@"可以将转义字符"\"当普通字符 String a = "ab\\cde"; // 上面的等价方法,要多写个"\"
-
自定义引用类型
-
class:类
-
interface:接口
-
delegate:委托
-
可空类型
int a; // a默认为0
int? a; // a默认为空,即null
int? a = null; // a赋值为空
int? a = new int?(); // a赋值为空
int b = a ?? 10; // 如果a为空,则b赋值为10
a ?? = b; // 如果a为空,则a赋值为b
指针
与C、C++相同。
类型转换
隐式类型转换和显式类型转换
float a = 10.0000001;
double b = a; // 隐式转换:低精度向高精度,不会损失精度
int c = a; // 隐式转换:高精度向低精度,损失精度
int d = (int)a; // 显示转换:高精度向低精度,损失精度
装箱与拆箱
Object
类是所有类的基类,所以它可引用任何类型的对象,它的对象也可转换为任何类型的对象
Object Obj; // Object类型是所有类的基类
MyObject b = new MyObject(1);
MyObject c;
Obj = b; // 装箱
c = (MyObject)Obj; // 拆箱:c此时为b的别名
c = Obj as MyObject; // 拆箱:使用"as"进行强制转换,因为使用C、C++中的"(Type1)Type2的变量"转换如果失败,则会抛出错误,而使用“as”则失败时会返回null,不会出错。
强制类型转换与as
、is
关键字
使用C、C++中的()
进行强制类型转换时,如果转换失败则会直接抛出错误,所以**as
与is
**转换更方便
as
:转换成功则返回转换后的对象,失败则返回**null
**is
:测试是否可以转换成功,如果可以成功则进行转换并返回true
,如果失败则不转换并返回**false
**
Son son = new Son();
Father father = new Father();
Person person = new Person();
Son son = father as Son; // as:父类强制转换为子类,注意子类无法强制转换为父类,cast成功则返回成功后的对象,失败则返回bull
if(person is Son converted_person) // is:如果person对象能转换为Son类型,则强制转换并命名转换后的对象别名为converted_person
{
converted_person.PrintClassName();
/*语句*/
}
基本语法
- 运算符
- 判断
- 循环
运算符
与C、C++相同。
判断
与C、C++相同。
循环
与C、C++基本相同。不同点:
-
范围for循环
foreach(Object a in MyObject){}
方法
传引用与传值
**ref
**关键字
**ref
**关键字可以使得值类型:
- 发生函数调用时,通过形参可直接修改实参,即形参成为实参的引用
- 函数返回时,不传值而传引用
注意:使用ref关键字,必须提前对要传入函数的参数进行初始化。
ref int Function(ref int param)
{
param *= 2;
num = param + 2;
return ref num;
}
// 使用
int a = 10;
Function(a); // 传引用:a = 20
ref int b = ref Function(a); // 返引用:b = 42,此时b就是Function方法中局部变量num的别名,且num的生存周期延长
// 注意
int c = ref Function(a); // 返值:此时的b是局部变量num的复制,原本的num已经被析构或释放
in
关键字
按值传递,就是C#默认的传递方式,但如果显式声明了in
,则在传入的函数内部不能对值进行再次赋值。
out
关键字
按引用传递,和ref
作用相同,但是如果使用了out
则不用在传入函数前对变量进行初始化。也就是说并不会在传入函数前,占用一个内存空间,同样在传入前也未分配地址。
基本数据结构
- 数组
- 枚举
- 结构体
数组
一维数组
/*创建和初始化*/
int[] a = {1,2,3,4,5}; // 指定内容:创建并赋值为1,2,3,4,5
int[] b = new int[5]; // 指定长度:创建并初始化为5个长度的int数组(默认每个为0)
int[] c = new int[5]{6,7,8,9,10} //指定长度和内容:创建并初始化为6,7,8,9,10
int[] d = new int[]{11,12,13,14,15} //指定内容:创建并初始化为11,12,13,14,15
/*赋值*/
a[0] = 1;
b[1] = 1;
/*访问*/
int e = a[0];
二维数组
/*创建和初始化*/
int[,] a = {{1,2},{3,4}};
int[,] b = new int[,];
int[,] c = new int[2,2]{{1,2},{3,4}};
int[,] d = new int[,]{{1,2},{3,4}};
/*赋值*/
a[0,0] = 1;
b[1,2] = 1;
/*访问*/
int c = a[0,0];
交错数组
交错数组本质是个一维的数组,它是由不同长度的数组组成的数组。
int[][] a = new int[2][]{new int[]{1,2}, new int[]{1,2,3}}; // a是由两个不同长度的一维数组组成的
参数数组与params
关键字
-
不能确定要传递给函数作为参数的参数数目,可用将形参定义为参数数组的方法
-
在使用数组作为形参时,C#提供了 params 关键字,使调用数组为形参的方法时,既可以传递数组实参,也可以传递一组数组元素params 关键字
class ParamArray { public int AddElements(params int[] arr) { int sum = 0; foreach (int i in arr) { sum += i; } return sum; } } class TestClass { static void Main(string[] args) { ParamArray app = new ParamArray(); int sum = app.AddElements(512, 720, 250, 567, 889); Console.WriteLine("总和是: {0}", sum); Console.ReadKey(); } }
枚举
/*创建*/
enum Day{Mon, Tue, Wed, Thu, Fri, Sat, Sun}; // Dat.Mon的值为0,Day.Tue的值为1,...
/*使用*/
a = Day.Mon; // a赋值为0
b = Day.Sun; // b赋值为6
结构体
与C、C++中的结构体,还有C#中的类不同:
- 类是引用类型,它在堆中分配空间,栈中保存的只是引用;结构是值类型,它在栈中分配空间。
- 结构可带有方法、字段、索引、属性、运算符方法和事件。
- 结构可实现一个或多个接口。
- 结构可定义构造函数,但不能定义析构函数、无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
- 当使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
- 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
- 结构不能继承其他的结构或类。
- 结构不能作为其他结构或类的基础结构。
- 结构成员不能指定为abstract、virtual 或 protected,但可以指定为public、private
- 结构成员不能在结构体里赋值,这点和C++相同;
细节差异:
- 由于结构是值类型,并且直接存储数据,因此在一个对象的主要成员为数据且数据量不大的情况下,使用结构会带来更好的性能。
- 因为结构是值类型,因此在为结构分配内存,或者当结构超出了作用域被删除时,性能会非常好,因为他们将内联或者保存在堆栈中。当把一个结构类型的变量赋值给另一个结构时,对性能的影响取决于结构的大小,如果结构的数据成员非常多而且复杂,就会造成损失
结构和类的适用场合分析:
- 当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些;
- 对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低;
- 在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
- 大多数情况下,目标类型只是含有一些数据,或者以数据为主。
struct Books
{
private string title; // 成员变量不能在定义时赋初值,但是class可以
private string author; // 域(方法和成员变量)只能指定为public、private
private string subject;
private int book_id;
public Books(string str) // 构造函数,必须带参数,但类可以不带参数。没有析构函数,但类有。(注意:结构体和类的构造函数都没有返回值。)
{
}
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);
}
}; // 不要忘记加上分号
面向对象
类
定义方法与C++相同
封装
C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。
-
public:所有对象都可以访问;
-
private:对象本身在对象内部可以访问;
-
protected:只有该类对象及其子类对象可以访问
-
internal:同一个程序集的对象可以访问,即在该成员所定义的应用程序内的任何类或方法访问。
-
protected internal:访问限于当前程序集或派生自包含类的类型。
-
sealed:密封该类,使得它不能被继承。
注意:抽象类不能密封
继承
- C#不支持多重继承,即一个派生类只能有一个父类,但是它可以有一个父类和多个接口
派生类
<访问修饰符> class <基类>
{
...
}
class <派生类> : <基类>
{
...
}
派生类的实例化
-
在创建一个派生类对象时,首先会先创建一个父类对象,再创建派生类对象
-
在派生类的成员初始化列表使用
base()
对父类对象进行初始化class Son : Father { private string hair; public Son(string hair) : base(hair) { } }
**new
与override
**关键字
在派生类中,通过使用new
和override
关键字声明方法,可以隐藏或者重写基类中的同名方法
-
override
:在派生类用来重写基类中的同名方法,可以实现多态- 不能重写非虚方法和静态方法;
- 只能重写用virtual、abstract、override修饰的方法;
- 不能使用修饰符 new、static、virtual 或 abstract 来修改 override 方法。
-
new
:在派生类用来隐藏基类中的同名方法,也就是说在派生类中“看不到”基类中的方法- 如果要在派生类中隐藏(不是重写)基类中的方法,而没有使用new关键字,编译时会出现一个警告,提示如果是要隐藏基类中的方法,请使用new关键字;
- 派生类可以隐藏基类中的虚方法,也可以隐藏基类中的普通方法;
- 如果在派生类中用private来修饰new方法,那么只在该派生类中隐藏了基类中的方法,在该派生类之外,相当于没有隐藏基类中的方法;
- 如果在派生类中隐藏了基类中的方法,在该派生类的派生类中,将延续对该派生类对基类方法的隐藏。
构造
与C++不同之处:
- 无委托构造、移动构造函数
与C++相同之处:
- 无返回值
- 只能声明为public
- 有初始化列表,但只能使用**
this()
和base()
**语句
class Son : Father
{
private string m_hair;
public Son(string hair) : base(hair)
{
this.m_hair = hair;
}
public Son() : this("black"){}
}
Son son = new Son("blue"); // 先创建Father的对象,并初始化为blue;再创建Son的对象son,初始化为blue;
Son son = new Son(); // 调用Son的无参构造函数,无参构造函数通过初始化列表的this()语句又产生了对有参构造的调用
实例构造函数
使用public
声明的构造函数
私有构造函数
使用private
声明的构造函数。可以禁止类进行实例化,比如Math
类,它不需要实例化,我们一般使用Math
类中的静态方法
静态构造函数
使用static
声明的构造函数(无访问修饰符和参数)。它任何对静态成员进行初始化。
- 当创造第一个实例时,首先会执行静态构造函数。创造第二个实例时,不会再调用
- 当直接使用类的静态方法和静态成员变量时,首先会执行静态构造函数
- 无法显式调用
复制构造函数
class Person
{
private string m_sName;
public Person(string name) // 构造函数
{
m_sName = name;
}
public Person(Person previousPerson) // 复制构造函数
{
this.m_sName = previousPerson.m_sName;
}
}
Person A = new Person("LiMing");
Person B = new Person(A); // 调用复制构造函数:B的m_sName此时为“LiMing”
析构
与C++不同之处:
- 没有delete关键字
与C++相同之处:
- 无返回值
- 无参数
- 无public、private声明
namespace MySpace
{
class Father // 类
{
public Father() // 构造函数
{
eye = "big"; // 初始化成员变量
}
~Father() // 析构函数
{
eye.Clear(); // 清空成员变量
}
private string eye;
}
}
抽象类与接口
抽象类
抽象类不是一个具体的类,比如人。人是一个抽象的概念,所有人都有头,手和脚,但具体的一个人这些部位的形状、长度、颜色等都不一样。
-
抽象类无法实例化一个对象(可以声明,无法new)
// Person是抽象类,Son派生于Person Person Xiaoli = new Person(); // 错误!!!无法实例化抽象类!!! // 用派生类声明和构造 Son XiaoMing = new Son(); XiaoMing.MyName(); // 输出“小明” // 用抽象类声明,用派生类构造 Person XiaoHua = new Son(); XiaoHua.MyName(); // 输出“小华”
-
其子类只有完全重写所有方法或纯虚函数后,才能实例化,否则该子类仍然是一个抽象类
-
抽象类同样有构造函数和析构函数,但只有完全重写所有纯虚函数后的子类在实例化时可调用父抽象类的构造与析构函数
注意:构造函数不能声明为虚函数,如果要使用多态特性,析构函数则必须声明为虚函数!
C++中抽象类
成员函数含有纯虚函数的类(不是完全都是纯虚函数)
class Person // 抽象类
{
public:
// 纯虚函数定义方法:virtual <返回类型> <函数名称>() = 0;
virtual void run() = 0; // 纯虚函数。如果不写 = 0,则run()只是一个虚函数,而不是纯虚函数
virtual void walk() = 0; // 纯虚函数
virtual void myPrint() // 虚函数
{
std::cout<<"我是人类"<<std::endl;
}
}
class Father: public Person // 该子类没有全部重写基类所有纯虚函数,依然是一个抽象类,无法实例化
{
public:
void run() // 虚函数。重写了纯虚函数后,它就成了虚函数
{
std::cout<<"我能跑"<<std::endl;
}
}
class Son: public Father // 该子类重写了基类所有纯虚函数(其父类已经重写了walk()纯虚函数),可以实例化一个对象
{
public:
Son(std::string name)
{
std::cout<<"子对象创建成功"<<std::endl; // 当执行"Son* son = new Son"时,输出"子对象创建成功"
}
void walk() // 虚函数。重写了基类的纯虚函数后,它就成了虚函数
{
std::cout<<"我能走"<<std::endl;
}
void myPrint() // 虚函数。基类的虚函数被重写后,依然为虚函数
{
std::cout<<"我是人类"<<std::endl;
}
}
C#中的抽象类
使用abstract
修饰的类
- C#允许把类、属性和函数声明为
abstract
- 当一个类中如果有属性、方法被声明为
abstract
时,这个类也要被声明为abstract
// 鸟的抽象类
abstract class Bird // 含有抽象属性和方法,就一定是抽象类
{
// 鸟速度的属性
public double Speed { get; set; }
// 鸟体重的属性
public abstract double Weight { get; set; }
// 鸟飞翔的抽象方法
public abstract void Fly();
}
// 创建麻雀的类,继承自鸟
class Sparrow : Bird // 继承了抽象类,此时必须要求实现抽象属性和方法
{
// 麻雀体重的属性
public override double Weight { get; set; }
// 麻雀飞翔的方法
public override void Fly()
{
Console.WriteLine("麻雀飞翔~");
}
}
// 实例化
// 用派生类声明和构造
Sparrow sparrow = new Sparrow();
sparrow.Fly(); // 输出“麻雀飞翔”
// 用抽象类声明,用派生类构造
Bird bird = new Sparrow();
bird.Fly(); // 同样输出“麻雀飞翔”
接口
接口定义了所有类继承接口时应遵循的语法合同。接口定义了语法合同 “是什么” 部分,派生类定义了语法合同 “怎么做” 部分。
C++中接口的实现方法
C++没有接口的概念,C++一般用纯虚函数来构造一个函数全为纯虚函数的抽象类,称之为虚基类来实现接口功能。
-
成员由纯虚函数和虚析构函数构成
在使用接口类的指针访问接口类的子类的实例时,当对接口类的指针做delete时,如果接口类的析构函数不是虚析构函数的话,将只会调用接口类的析构函数,接口类的子类的析构函数将不会被调用,内存泄露将会产生,所以接口类的析构函数必须定义成虚析构函数。
-
虚析构函数要提供默认实现
接口类的析构函数是纯虚析构函数的话,接口类的子类将被迫必须提供析构函数的实现,这样对接口类的子类不友好,所以接口类析构函数要提供默认实现
class Test { public: virtual ~Test() {}; // 虚析构函数默认实现 virtual ~Test() = default; // C++11支持的虚析构函数默认实现方法 virtual ~Test() = 0 {}; // 纯*虚析构函数的默认实现(写法很奇怪,但是确实是标准的) }
-
成员函数必须声明为public
如果声明为protected和private,则没有了接口的意义,即统一编程标准和规范
-
不要显示的定义构造函数,但也不能用
Test() = delete
和Test(const Test&) = delete
来禁止构造函数隐式生成子类实例化时会先调用基类构造函数,如果基类没有显示定义构造函数,则编译器会隐式生成
-
最好不要有成员变量,但可以有静态常量(static、const、enum)
class Person // 接口
{
public:
// 纯虚函数定义:virtual <返回类型> <函数名称>() = 0;
virtual void run() = 0; // 如果不写 = 0,则run()只是一个虚函数,而不是纯虚函数
virtual void walk() = 0;
virtual void sit() = 0;
virtual ~Person() = default;
static const std::string name;
}
class Father: public Person // 该子类没有全部重写所有纯虚函数,只是一个抽象类,无法实例化一个对象
{
public:
void run()
{
std::cout<<"我能跑"<<std::endl;
}
void walk()
{
std::cout<<"我能走"<<std::endl;
}
}
class Son: public Father // 该子类重写了所有纯虚函数,可以实例化一个对象
{
public:
void sit()
{
std::cout<<"我能坐"<<std::endl;
}
}
C#中的接口
使用interface
来定义
-
接口只能包含方法,属性,索引器和事件的声明
-
接口不能有构造函数,也不能有字段,接口也不允许运算符重载
-
接口定义中不允许用public、protected、internal、private修饰符声明成员,接口成员都是公有的
-
接口成员不能有new、static、abstract、override、virtual修饰符
当一个接口实现一个接口,这2个接口中有相同的方法时,可用new关键字隐藏父接口中的方法。
-
接口可以彼此继承,其方式和类的继承方式相同
接口解决了C#里面类只能同时继承一个基类的问题:有了接口之后,一个类可以同时继承一个基类和若干接口
namespace MySpace
{
interface Animal // 两个方法
{
void run();
void walk();
}
interface Person: Animal // 三个方法
{
void speak();
}
public abstract class Father: Person //
{
void run();
}
public class XiaoMing: Father
{
void speak();
}
}
多态
静态多态
函数重载
根据所传入参数的类型和个数,调用相应同名不同参的函数
运算符重载
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;
}
public static bool operator == (Box lhs, Box rhs)
{
bool status = false;
if (lhs.length == rhs.length && lhs.height == rhs.height
&& lhs.breadth == rhs.breadth)
{
status = true;
}
return status;
}
public static bool operator !=(Box lhs, Box rhs)
{
bool status = false;
if (lhs.length != rhs.length || lhs.height != rhs.height
|| lhs.breadth != rhs.breadth)
{
status = true;
}
return status;
}
public static bool operator <(Box lhs, Box rhs)
{
bool status = false;
if (lhs.length < rhs.length && lhs.height
< rhs.height && lhs.breadth < rhs.breadth)
{
status = true;
}
return status;
}
public static bool operator >(Box lhs, Box rhs)
{
bool status = false;
if (lhs.length > rhs.length && lhs.height
> rhs.height && lhs.breadth > rhs.breadth)
{
status = true;
}
return status;
}
public static bool operator <=(Box lhs, Box rhs)
{
bool status = false;
if (lhs.length <= rhs.length && lhs.height
<= rhs.height && lhs.breadth <= rhs.breadth)
{
status = true;
}
return status;
}
public static bool operator >=(Box lhs, Box rhs)
{
bool status = false;
if (lhs.length >= rhs.length && lhs.height
>= rhs.height && lhs.breadth >= rhs.breadth)
{
status = true;
}
return status;
}
public override string ToString()
{
return String.Format("({0}, {1}, {2})", length, breadth, height);
}
}
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
Box Box4 = new 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);
// 使用重载的 ToString() 显示两个盒子
Console.WriteLine("Box1: {0}", Box1.ToString());
Console.WriteLine("Box2: {0}", Box2.ToString());
// Box1 的体积
volume = Box1.getVolume();
Console.WriteLine("Box1 的体积: {0}", volume);
// Box2 的体积
volume = Box2.getVolume();
Console.WriteLine("Box2 的体积: {0}", volume);
// 把两个对象相加
Box3 = Box1 + Box2;
Console.WriteLine("Box3: {0}", Box3.ToString());
// Box3 的体积
volume = Box3.getVolume();
Console.WriteLine("Box3 的体积: {0}", volume);
//comparing the boxes
if (Box1 > Box2)
Console.WriteLine("Box1 大于 Box2");
else
Console.WriteLine("Box1 不大于 Box2");
if (Box1 < Box2)
Console.WriteLine("Box1 小于 Box2");
else
Console.WriteLine("Box1 不小于 Box2");
if (Box1 >= Box2)
Console.WriteLine("Box1 大于等于 Box2");
else
Console.WriteLine("Box1 不大于等于 Box2");
if (Box1 <= Box2)
Console.WriteLine("Box1 小于等于 Box2");
else
Console.WriteLine("Box1 不小于等于 Box2");
if (Box1 != Box2)
Console.WriteLine("Box1 不等于 Box2");
else
Console.WriteLine("Box1 等于 Box2");
Box4 = Box3;
if (Box3 == Box4)
Console.WriteLine("Box3 等于 Box4");
else
Console.WriteLine("Box3 不等于 Box4");
Console.ReadKey();
}
}
}
动态多态
通过virtual
关键字来实现动态多态
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();
}
}
/**************
运行后将输出:
画一个长方形
执行基类的画图任务
画一个三角形
执行基类的画图任务
画一个圆形
执行基类的画图任务
按下任意键退出。
****************/
其他
命名空间(Namespace)
命名空间定义
namespace A
{
class a
{}
namespace B
{
class b
{}
}
}
命名空间使用
使用using
指令。
-
using
指令:引入命名空间using A; using A.B;
-
using static
指令:指定无需指定类型名称即可访问其静态成员的类型using static System.Math; var = PI; // 直接使用System.Math.PI
-
起别名
using Project = PC.MyCompany.Project;
-
using
语句:将实例与代码绑定using (Font font3 = new Font("Arial", 10.0f), font4 = new Font("Arial", 10.0f)) { // Use font3 and font4. } // 外部不可用font3, font4 // 因为代码段结束时,自动调用font3和font4的Dispose方法(Winform),释放实例。
using 语句可确保调用Dispose,即使 using 块中发生异常也会调用
预处理器
指令表
指令 | 用途 |
---|---|
#define | 它用于定义一系列成为符号的字符。 |
#undef | 它用于取消定义符号。 |
#if | 它用于测试符号是否为真。 |
#else | 它用于创建复合条件指令,与 #if 一起使用。 |
#elif | 它用于创建复合条件指令。 |
#endif | 指定一个条件指令的结束。 |
#line | 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。 |
#error | 它允许从代码的指定位置生成一个错误。 |
#warning | 它允许从代码的指定位置生成一级警告。 |
#region | 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。 |
#endregion | 它标识着 #region 块的结束。 |
使用
-
预处理器指令的用途
在程序调试和运行上有重要的作用。
-
比如预处理器指令可以禁止编译器编译代码的某一部分,如果计划发布两个版本的代码,即基本版本和有更多功能的企业版本,就可以使用这些预处理器指令来控制。在编译软件的基本版本时,使用预处理器指令还可以禁止编译器编译于额外功能相关的代码。
-
另外,在编写提供调试信息的代码时,也可以使用预处理器指令进行控制。总的来说和普通的控制语句(if等)功能类似,方便在于预处理器指令包含的未执行部分是不需要编译的。
#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 语句可以提醒自己执行某个操作。
#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.*");
-
#region
和**#endregion
**#region 和 #endregion 指令用于把一段代码标记为有给定名称的一个块,如下所示:
#region Member Field Declarations int x; double d; #endregion
这看起来似乎没有什么用,它不影响编译过程。这些指令的优点是它们可以被某些编辑器识别,包括 Visual Studio .NET 编辑器。这些编辑器可以使用这些指令使代码在屏幕上更好地布局。
-
#line
#line指令可以用于改变编译器在警告和错误信息中显示的文件名和行号信息,不常用。
如果编写代码时,在把代码发送给编译器前,要使用某些软件包改变输入的代码,就可以使用这个指令,因为这意味着编译器报告的行号或文件名与文件中的行号或编辑的文件名不匹配。#line指令可以用于还原这种匹配。也可以使用语法#line default把行号还原为默认的行号:
#line 164 "Core.cs" // 在文件的第 164 行 // Core.cs, before the intermediate // package mangles it. // later on #line default // 恢复默认行号
-
#pragma
#pragma 指令可以抑制或还原指定的编译警告。与命令行选项不同,#pragma 指令可以在类或方法级别执行,对抑制警告的内容和抑制的时间进行更精细的控制
#pragma warning disable 169 // 取消编号 169 的警告(字段未使用的警告) public class MyClass { int neverUsedField; // 编译整个 MyClass 类时不会发出警告 } #pragma warning restore 169 // 恢复编号 169 的警告
-
异常
异常类
C# 异常是使用类来表示的。
- C# 中的异常类主要是直接或间接地派生于
System.Exception
类。System.ApplicationException
和System.SystemException
类是派生于System.Exception
类的异常类。 System.ApplicationException
类支持由应用程序生成的异常。所以程序员定义的异常都应派生自该类。System.SystemException
类是所有预定义的系统异常的基类。
下表列出了一些派生自 System.SystemException
类的预定义的异常类:
异常类 | 描述 |
---|---|
System.IO.IOException | 处理 I/O 错误。 |
System.IndexOutOfRangeException | 处理当方法指向超出范围的数组索引时生成的错误。 |
System.ArrayTypeMismatchException | 处理当数组类型不匹配时生成的错误。 |
System.NullReferenceException | 处理当依从一个空对象时生成的错误。 |
System.DivideByZeroException | 处理当除以零时生成的错误。 |
System.InvalidCastException | 处理在类型转换期间生成的错误。 |
System.OutOfMemoryException | 处理空闲内存不足生成的错误。 |
System.StackOverflowException | 处理栈溢出生成的错误。 |
异常处理
try
{
// 引起异常的语句
}
catch( ExceptionName e1 )
{
// 错误处理代码
}
catch( ExceptionName e2 )
{
// 错误处理代码
}
// .....
catch( ExceptionName eN )
{
// 错误处理代码
}
finally
{
// 要执行的语句
}
自定义异常
用户自定义的异常类是派生自 ApplicationException 类
using System;
namespace UserDefinedException
{
class TestTemperature
{
static void Main(string[] args)
{
Temperature temp = new Temperature();
try
{
temp.showTemp(); // 抛出一个TempIsZeroException对象,消息为"Zero Temperature found"
}
catch(TempIsZeroException e) // 捕获抛出的TempIsZeroException对象
{
Console.WriteLine("TempIsZeroException: {0}", e.Message);
}
Console.ReadKey();
}
}
}
public class TempIsZeroException: ApplicationException // 自定义异常类
{
public TempIsZeroException(string message): base(message)
{
}
}
public class Temperature // 温度类
{
int temperature = 0;
public void showTemp()
{
if(temperature == 0)
{
throw (new TempIsZeroException("Zero Temperature found")); // 抛出一个TempIsZeroException对象,消息为"Zero Temperature found"
}
else
{
Console.WriteLine("Temperature: {0}", temperature);
}
}
}