C#与 C++异同
1 细微差异 ............................................................................................................................................................... 1
1.1 类的声明 .................................................................................................................................................... 1
1.2 同名覆盖 .................................................................................................................................................... 1
2 多重继承 ............................................................................................................................................................... 1
3 C#虚方法和 C++中的虚函数 ................................................................................................................................. 3
4 数组 ....................................................................................................................................................................... 7
5 C#中按值传递和按引用传递 ................................................................................................................................. 7
6 字符串 ................................................................................................................................................................... 9
7 抽象类 ................................................................................................................................................................... 9
7.1 抽象类的构造函数................................................................................................................................... 11
8 C#类访问控制修饰符........................................................................................................................................... 13
9 C#中的类型转换 .................................................................................................................................................. 13
10 C#委托(delegate) ................................................................................................................................................ 14
11 C#中事件(event) ................................................................................................................................................. 17
12 接口(interface) ................................................................................................................................................... 19
12.1 接口的使用 ............................................................................................................................................ 19
12.2 接口的多继承 ........................................................................................................................................ 22
12.3 接口的重新实现..................................................................................................................................... 24
12.4 接口和多态 ............................................................................................................................................ 25
12.5 接口的应用举例..................................................................................................................................... 26
12.6 接口与抽象类的比较 ............................................................................................................................. 27
13 密封 ................................................................................................................................................................... 28
13.1 密封类 .................................................................................................................................................... 28
13.2 密封方法 ................................................................................................................................................ 28
1 细微差异
1.1 类的声明
C++的类声明结束后要在最后的大括号后面用分号结尾。C#则可用可不用,往往都是省略。
1.2 同名覆盖
C++中,如果基类和派生类有相同的成员,遵守同名覆盖原则,即派生类对象访问的是派生类的同名
成员,基类对象访问的是基类同名成员。
2 多重继承
C++可以多重继承,即一个派生类可以从多个基类同时继承,而 C#只能从一个类单重继承,但可以
通过接口实现多重继承。C++中如果基类和派生类中有相同成员时,遵循同名覆盖原则,即派生类对象使
用的是派生类同名成员,如果要在派生类中使用基类被覆盖的同名成员(称为派生类和基类互相通信)或
派生类外通过派生类对象访问基类同名成员,都必须使用基类名限定,如 A::f(),举例如下:
Class A
{
Public:
void f(){};
};
Class B:A
{
Public:
void f()
{
A::f();//在派生类中访问基类成员 f(),互相通信
}
};
1
Void main()
{
A a;
A *pa;//定义一个基类型指针
B b;
pa=&b;//赋值兼容原则,基类指针可以指向派生类
a.f();//基类对象访问基类成员 f()
b.f();//派生类对象访问类 B的 f()
pa->f();//访问的是基类 A的 f()
b.A::f();//派生类对象访问基类 A的 f()
}
而 C#中在派生类访问基类被覆盖的同名成员时使用 base关键字来限定。C#还可以通过显式类型转
换来访问基类成员,如:((Parent)child).print(),其中 child 是派生类的对象,Parent 是基类名,
那么这个语句就是通过派生类对象访问基类成员。举例如下:
using System;
public class B0
{
public void fb() //基类同名方法
{
Console.WriteLine("B0's fb called!");
}
}
public class B1:B0
{
public new void fb()//派生类同名方法,new
示有意覆盖,若无会产生编译警告
{
base.fb();//相互通信,派生类中访问基类方法
Console.WriteLine("B1's fb called!");
}
}
public class C
{
public static void Main()
{
B0 b0 = new B0();
b0.fb();//访问基类方法
B0 b1 = new B1();//基类型的引用指向派生类
b1.fb();//访问基类方法
B1 b2 = new B1();
b2.fb();//访问派生类方法
((B0)b2).fb();//显式类型转换访问基类方法
Console.ReadLine();
}
}
多态性就是指在程序运行时,执行的虽然是一个调用方法的语句,却可以根据派生类对象的类型不同
完成方法的不同的具体实现。在使用虚函数实现多态时,C++派生类覆盖基类虚函数时函数名前可写
virtual,也可以不写,默认为虚函数,在 C++中,由于基类和派生类的赋值兼容原则,可以定义一个基
类指针或引用指向派生类对象,那么通过该指针或引用访问的还是基类同名成员,除非把基类同名成员定
义成虚函数,那么基类指针或引用指向派生类对象访问的是派生类同名成员。上例代码修改如下:
Class A
{
Public:
Virtual void f(){};//f()定义成虚函数
};
Class B:A
{
Public:
Virtual void f()//派生类 virtual关键字可省略,默认为虚函数
{
2
A::f();//在派生类中访问基类成员 f(),互相通信
}
};
Void main()
{
A a;
a.f();//基类对象访问基类成员 f()
B b;
b.f();//派生类对象访问类 B的 f()
b.A::f();//派生类对象访问基类 A的 f()
A *pa;//定义一个基类型指针
pa=&b;//赋值兼容原则,基类指针可以指向派生类
pa->f();//访问的是派生类 B的 f(),虚函数的作用
}
C#派生类覆盖基类虚函数必须把基类虚函数 virtual 处写成 override,如:基类中虚函数 public
virtual void f()那么派生类写成 public override void f()。上例修改如下:
using System;
public class B0//public可省略,默认为public访问修饰符
{
public virtual void fb() //基类虚方法
{
Console.WriteLine("B0's fb called!");
}
}
public class B1:B0
{
public override void fb()//派生类覆盖虚方法
{
base.fb();//相互通信,派生类中访问基类方法
Console.WriteLine("B1's fb called!");
}
}
public class C
{
public static void Main()
{
B0 b0 = new B0();//基类对象
b0.fb();//访问基类方法
B0 b1 = new B1();//基类型的引用指向派生类对象
b1.fb();//访问派生了类方法
B1 b2 = new B1();//派生类对象
b2.fb();//访问派生类方法
((B0)b2).fb();//显式类型转换访问派生类方法
Console.ReadLine();
}
}
3 C#虚方法和 C++中的虚函数
首先来观察 C++中的一段程序,如下:
#include "iostream.h"
class B0
{
public:
void f() {cout<<"B0.f"<
f();
}
void main()
{
B0 b0;//基类对象
B1 b1;
B2 b2;
b0=b1;//把派生类对象赋给基类对象,赋值兼容原则
b0.f();//输出 B0.f
b1.f();//同名覆盖原则,输出 B1.f
b2.f();//同名覆盖原则,输出 B2.f
B0 *p;//基类型指针
p=&b1;//基类指针指向派生类,赋值兼容原则
fun(p); //因为是基类指针,所以输出 B0.f
p=&b2;
p->f();//因为是基类指针,所以输出 B0.f
}
如果把上例中基类 B0的 f()函数写成虚函数,那么同样的main()运行结果不同,构成多态,修改如下:
class B0
{
public:
virtual void f() {cout<<"B0.f"<f();//因为虚函数构成多态,所以输出 B2.f
}
我们再来观察一段 C#程序,如下:
class B0
{
public void f()
{
Console.WriteLine("B0.f");
}
}
class B1:B0
{
public void f()
{
4
Console.WriteLine("B1.f");
}
}
class B2 : B1
{
public void f()
{
Console.WriteLine("B2.f");
}
}
class Program
{
static void Main(string[] args)
{
B0 b0 = new B0();//基类对象
b0.f();//输出B0.f
B0 b1 = new B1();//隐式类型转换
b1.f();//输出B0.f,和C++中类似。
B2 b2 = new B2();//派生类对象
b2.f();//输出B2.f
Console.ReadLine();
}
}
如果也把基类中的 f()函数写成虚方法,那么子类必须用 override覆盖
虚方法,构成多态,修改程序如下:
class B0
{
public virtual void f()
{
Console.WriteLine("B0.f");
}
}
class B1:B0
{
public override void f()
{
Console.WriteLine("B1.f");
}
}
class B2 : B1
{
public override void f()
{
Console.WriteLine("B2.f");
}
}
class Program
{
static void Main(string[] args)
{
B0 b0 = new B0();//基类对象
b0.f();//输出B0.f
B0 b1 = new B1();//隐式类型转换
b1.f();//输出B1.f,由于是虚方法,和C++中类似。
B2 b2 = new B2();//派生类对象
b2.f();//输出B2.f
Console.ReadLine();
}
}
如果基类方法写成虚方法,可选择使用 override重写该方法,也可以选择使用 new不重写方法。
再写一个程序演示多态,如下:
5
class Employee
{
protected string name;
public Employee() {}
public Employee(string _name)
{
name = _name;
}
public virtual void StartWork()
{
Console.Write(name+"开始工作:");
}
}
class Manager:Employee
{
public Manager(string name):base(name) {}
public override void StartWork()
{
base.StartWork();
Console.WriteLine("分配工作");
}
}
class Saler:Employee
{
public Saler(string name):base(name) {}
public override void StartWork()
{
base.StartWork();
Console.WriteLine("推销产品");
}
}
class Technolgy : Employee
{
public Technolgy(string name) : base(name) { }
public override void StartWork()
{
base.StartWork();
Console.WriteLine("技术开发");
}
}
class Program
{
static void Main(string[] args)
{
Employee[] emp=new Employee[3];
emp[0] = new Manager("张三");
emp[1] = new Saler("李四");
emp[2] = new Technolgy("王五");
foreach(Employee e in emp)
{
e.StartWork();
}
Console.ReadLine();
}
}
运行结果:
张三开始工作:分配工作
李四开始工作:推销产品
王五开始工作:技术开发
6
4 数组
在 C#中,数组实际上是对象。System.Array 是所有数组类型的抽象基类型。提供创建、操作、搜
索和排序数组的方法,因而在公共语言运行库中用作所有数组的基类。声明数组:
int[] a=new int[5];
可以同时初始化:
int[] a=new int[5] {1,2,3,4,5};
或 int[] a={1,2,3,4,5};
举例如下:
int[] a = new int[10] { 12,4,5,23,-5,21,3,0,-12,100};
for (int i = 0; i<10; i++)
{
Console.Write("{0} ",a[i]);
}
Array.Sort(a);//对数组进行排序
Array.Clear(a,0,10); //对数组元素置0
Array.Reverse(a); //对数组进行转置
for (int i = a.GetLowerBound(0); i <=a.GetUpperBound(0); i++)//数组的上下标
{
Console.Write("{0} ", a[i]);
}
5 C#中按值传递和按引用传递
C#中分为值类型变量和引用类型变量,传递值类型参数,可以通过值传递值类型参数,也可以通过
按引用传递值类型参数。
举例按值传递值类型参数如下:
Class A
{//本例没有实现交换数值的功能
static void swap(int x, int y)//按值传递
{
int t;
t = x;
x = y;
y = t;
}
Public statci void Main()
{
int a=3,b=5;
swap(a,b);//只是传递 a,b的副本,不会改变实参值
Console.WriteLine("{0},{1}",a,b);
}
结果:3,5
举例按引用传递值类型参数如下:
Class A
{
static void swap(ref int x,ref int y)//
{//按引用传递,调用函数前必须赋值
int t;
t = x;
x = y;
y = t;
}
Public statci void Main()
{
int a=3,b=5;
swap(ref a,ref b);
//传递的不是 a,b的值,是对 a,b的引用,形参 x,y也不是 int类型,是对 int类型的引用
Console.WriteLine("{0},{1}",a,b);
7
}
结果:5,3
C#中引用类型变量不直接包含其数据,包含的是对数据的引用,引用类型数据也能按值传递和按引
用传递。
举例按值传递引用类型数据如下:
class passingrefbyval
{
static void change(int[] arr)
{
arr[0]=888; // this change affects the original element.
arr = new int[5] {-3, -1, -2, -3, -4}; // this change is local.
console.writeline("inside the method, the first element is: {0}", arr[0]);
}
public static void main()
{
int[] myarray = {1,4,5};
console.writeline("inside main, before calling the method: {0}", myarray [0]);
change(myarray);
console.writeline("inside main, after calling the method: {0}", myarray [0]);
}
}
输出:
inside main, before calling the method: 1
inside the method, the first element is: -3
inside main, after calling the method: 888
在此情况下,myarray 是一个指向数组的引用,将向方法传递指向 myarray 的引用的一个副本。
我们可以这么理解,一开始 myarray 是数组的引用,通过传递,使 arr 也称为数组的引用,那么这时候
myarrayh和 arr都是同一个数组的引用,所以 change方法中 arr[0]=888;语句改变了这个数组的第
一个元素值,但是 arr = new int[5] {-3, -1, -2, -3, -4};语句分配了新的内存空间,使 arr成为新的一个数组的
引用,所以接下来在 change方法中对 arr引用的数组的操作和myarray引用的数组无关,不会影响它
的值,其实在本例中创建了两个数组,一个在Main方法中,一个在 change方法中。
举例按引用传递引用类型数据如下:
本示例除在方法头和调用中使用 ref 关键字以外,其余和上例相同。
class passingrefbyval
{
static void change(ref int[] arr)
{
arr[0]=888;
arr = new int[5] {-3, -1, -2, -3, -4};
console.writeline("inside the method, the first element is: {0}", arr[0]);
}
public static void main()
{
int[] myarray = {1,4,5};
console.writeline("inside main, before calling the method: {0}", myarray [0]);
change(ref myarray);
console.writeline("inside main, after calling the method: {0}", myarray [0]);
}
}
输出:
inside main, before calling the method: 1
inside the method, the first element is: -3
inside main, after calling the method: -3
我们可以这么理解,由于是使用 ref 按引用传递,所以传递是引用 myarray 本身,也可以说 arr 就
是myarray,都是原始数组的引用,在 change方法中,有语句 arr = new int[5] {-3, -1, -2, -3, -4};
8
使得 arr引用重新指向另一个数组,由于 arr就是myarray,所以此时myarray也指向另一个数组,所
以对 arr的操作就是对myarray的操作。
观察下面对于字符数组的交换:
public class C
{
static void swap1(string x, string y)//按值传递
{
string t;
t = x;
x = y;
y = t;
}
static void swap2(ref string x, ref string y)//按引用传递
{
string t;
t = x;
x = y;
y = t;
}
public static void Main()
{
string s1="abc";
string s2="efg";
swap1(s1, s2);
Console.WriteLine("{0},{1}",s1,s2);
swap2(ref s1,ref s2);
Console.WriteLine("{0},{1}",s1,s2);
Console.ReadLine();
}
}
输出:
abc,efg
efg,abc
6 字符串
C++处理字符串,可以使用字符数组、string类或指针来操作。如下:
char c1[]=”hello!”;
char c2[10];
string s1,s2;
s2=c1;
cin>>s1;
cin>>c2;
cout<总结:
(1) 抽象方法是隐式的虚方法
(2) 只允许在抽象类中使用抽象方法的声明
(3) 抽象方法声明没有方法体,以分号结束,实现由一个重写方法提供,该重写方法是一个非抽象类的成
员
(4) 在抽象方法声明中使用 static或 virtual关键字都是错误的
(5) 除了声明和调用语法上不同外,抽象属性的行为和抽象方法一样
(6) 在静态属性上使用 abstract修饰符是错误的
(7) 在派生类中,通过使用 override修饰符属性声明可以重写抽象类的继承属性
(8) C++中如果类中定义了纯虚函数,那么这个类就为抽象类,C#中抽象类必须用 abstract 修饰符定
义,而且抽象类中允许包含非抽象成员,但不是必须的。如:
abstract class A
{
public abstruct void F();
}
abstract class B:A
{
public void G(){}
}
class C:B
{
public override void F()
{//F的具体实现代码
}
}
(a) 抽象类 A提供了一个抽象方法 F。类 B从抽象类 A中继承,并且又提供了一个方法 G;
(b) 因为 B中并没有包含对 F的实现,所以 B也必须是抽象类。类 C是从类 B中继承,类中重载了抽
象方法 F,并且提供了对 F的具体实现,则类 C允许是非抽象的。
7.1 抽象类的构造函数
不要在抽象类中定义公共的或受保护的内部构造函数,因为 public 和 protected internal的构造函
数是用于能进行实例化的类型,由于任何情况下抽象类都不能实例化,应在抽象类中定义一个 protected
或 private的构造函数,如果在抽象类中定义一个 protected的构造函数,则在创建派生类实例时,基类
可执行初始化任务。
我们把前述例子中虚方法实现多态的例子也可以改为抽象方法实现,如下:
abstract class Employee
{
protected string name;
protected Employee() {}
11
protected Employee(string _name)
{
name = _name;
}
public abstract void StartWork();
}
class Manager:Employee
{
public Manager(string name):base(name) {}
public override void StartWork()
{
Console.WriteLine(name+"分配工作");
}
}
class Saler:Employee
{
public Saler(string name):base(name) {}
public override void StartWork()
{
Console.WriteLine(name+"推销产品");
}
}
class Technolgy : Employee
{
public Technolgy(string name) : base(name) { }
public override void StartWork()
{
Console.WriteLine(name+"技术开发");
}
}
class Program
{
static void Main(string[] args)
{
Employee[] emp=new Employee[3];
emp[0] = new Manager("张三");
emp[1] = new Saler("李四");
emp[2] = new Technolgy("王五");
foreach(Employee e in emp)
{
e.StartWork();
}
Console.ReadLine();
}
}
这样使用抽象类同样实现了多态,那么在实现多态机制时,什么时候使用虚方法实现什么时候使用抽
象类实现呢?(抽象类和虚方法的选择)一般来说,如果多态中各子类要实现一些共同功能时,倾向于使用
虚方法,可以把共同功能放在基类中,使用 base关键字调用,如果各子类实现功能不一样,那么可以使
用抽象类来构成多态机制。
虚方法和抽象方法的区别:
(1) 虚方法有一个实现部分,并且为派生类提供覆盖的选项,相反,抽象方法没有实现部分,强制派生类
覆盖方法(否则派生类不能成为具体类)
(2) 抽象方法只能在抽象类中声明,虚方法则不是
(3) 抽象方法必须在非抽象派生类中重写,虚方法则不必
(4) 抽象方法声明时不能有方法体,虚方法则可以
(5) C#用 abstract关键字来修饰一个方法,就可以将这个方法声明为纯虚的。自然,这个方法所属的类
也必须同时被标记为抽象类。请注意,抽象方法不能有任何实现代码。C++中用 virtual 函数名(参
12
数表)=0来声明纯虚函数,带有纯虚函数的类称为抽象类。
8 C#类访问控制修饰符
访问控制修饰符 类内部 子类 程序集内 程序集外
Default(默认为 private) 可以访问 × × ×
Public 可以访问 可以访问 可以访问 可以访问
Private 可以访问 × × ×
Protected 可以访问 可以访问 × ×
Internal 可以访问 可以访问 可以访问 ×
Protected internal 可以访问 可以访问 可以访问 ×
Default 指的是没有修饰符,它和 private 是一样的,也就是说成员默认省略修饰符则为 private,
带 protected修饰符的只有类内部和子类中可以访问,internal和 protected internal的区别是:如果
子类和父类不在同一个程序集中,那么子类不能访问 internal 修饰符成员,但是可以访问 protected
internal成员。
如果类前面省略访问控制修饰符,则为 internal修饰。
9 C#中的类型转换
C#中有显式和隐式类型转换,不同类型直接赋值就是隐式类型转换,如:
int a=5;//int是 int32位数值
long b=7;//long是 int64位数值
b=a;//把小范围数赋给大范围数,不会出错
a=b;//反过来隐式转换,编译通不过
如果我们采用显式转换,如:
int a=5;
long b=7;
b=(long)a;//把小范围数赋给大范围数,不会出错
a=(int)b;//显式转换,a=7,成功
但如果 b的值超过了 a所能表示的范围,那么会发生溢出,如:
int a;
long b=70000000000;
a=(int)b;//溢出,但不会报错,a的值也不等于 b
怎么样在类型转换发生溢出等异常时报错呢?我们可以用 checked和 unchecked关键字,如:
int a;
long b=70000000000;
a=checked((int)b);//溢出,报异常
我们可以捕捉异常,如下:
int a;
long b=70000000000;
try
{
a=checked((int)b);
}
catch(System.OverflowException)
{
MessageBox.Show(“发生溢出!”);
return;//发生溢出后程序不再往下执行
}
如果要对多个语句进行检查,可以使用 checked语句,如:
try
13
{
Checked
{
a=(int)b;//可以写多个语句
}
}
catch(System.OverflowException)
{
MessageBox.Show(“发生溢出!”);
return;//发生溢出后程序不再往下执行
}
如果写代码时允许发生溢出,最好把语句放到 unchecked块中。
C#中也可以进行引用类型的转换,如:
class Fruit
{
}
class Apple:Fruit
{
public int i=5;
}
class Test
{
Static void Main()
{
Fruit f=new Apple();//正确,形象理解:苹果也是水果
Type t=f.GetType();//返回 f的类型名
Console.WriteLine(t.FullName);//输出 Apple
Apple