类成员:
1.字段成员 字段只是类中声明的一个变量,用来在对象中存储信息。
(1).静态字段 使用static关键字修饰的就是静态字段,静态字段属于类而不属于某个类实例,对它的访问使用“类名.静态字段名” ,不能使用“对象名.静态字段名”的格式。 静态字段通常用来保存当前类的实例个数
例3-2:编写一个类,使用静态字段计算类的实例数量。
public class theclass
{ public theclass()
{
count++; //计数器增1
}
public void showcount()
{
Console.WriteLine(“实例数为:” + count );
}
private static int count=0; //显式赋初值
}
public class Test
{
public static void Main()
{
theclass class1=new theclass();
class1.showcount();
theclass class2 =new theclass();
class2.showcount();
}
}
(2).只读字段 当字段声明中包括readonly修饰符时,该字段成为只读字段。只能在声明只读字段的同时赋初值。其他任何时候都不允许为只读字段赋值。
public class Class1{
public readonly string str1=@”I am str1”;}
执行下面代码:
Class1 c1=new Class1();
Console.WriteLine(c1.str1);
//c1.str1=“Change readonlyfiled”;错误
如果在类定义中显式添加一个构造函数,可以在构造函数中改变只读字段的值。
Public class Class1
{Public readonly string str1=@”I am str1”;
public Class1(){ str1=“initialized by constructor!”;}
}
2.方法成员 方法成员的本质就是在类中声明的函数,描述类能够“做什么”。
参数传递
C#编程语言支持的参数传递方式包括:
1.值传递:方法中的变量是传入变量的一个拷贝,方法中对形参做的修改,不会影响方法外面的实参。
(1) 对于值类型数据,值传递就是传递了变量的值。
(2) 对于引用类型数据,值传递传递的是引用的值,即方法中的形参和方法外的实参将指向同一对象。因此,通过形参也能修改对象的实际内容。
- 总结1.(1)(2)就是:
简单类型(int ,double,char),enum类型,struct类型,string类型——>都是值类型
class——>引用类型
注意:string比较特殊,属于值类型。这里把s2看做实参,s看做形参。
string s = "abc";
string s2 = s;
s = "jc";
Console.WriteLine(s);
Console.WriteLine(s2);
- 代码例子:
test1类中有两个同名不同参的方法,这叫重载;
因为是test2是类,所以是引用类型的值传递,改变实参;
这里我一开始每个writeline后面都用了readline所以只出一个结果,只有我不断输入值,才会更新输出,事实上只用在静态Main里面(C#是用Main哦)最后一句放readline即可。
using System;
namespace cs
{
public class test1
{
public void valdiliver(int x)
{
x += 5;
Console.WriteLine(x);
// Console.ReadLine();
}
public void valdiliver(test2 test)
{
test.str += "changed!";
Console.WriteLine(test.str);
//Console.ReadLine();
}
}
public class test2
{
public string str = "this string";
}
public class Solution
{
static void Main(string[] args)//这是C#的Main方法
{
test1 a = new test1();
test2 b = new test2();
int c = 1;
Console.WriteLine(c);
// Console.ReadLine();
a.valdiliver(c);
Console.WriteLine(c);
//Console.ReadLine();
Console.WriteLine(b.str);
// Console.ReadLine();
a.valdiliver(b);
Console.WriteLine(b.str);
Console.ReadLine();
}
}
}
2.地址传递:方法中的变量是传入变量的一个引用,方法中对形参做的修改,也会影响方法外面的实参。
(1) 引用参数ref:由调用方法初始化参数值。
很多情况下,我们要使用参数的引用传递。引用传递是传递变量的地址,使得形参和实参指向同一内存空间,方法中对于形参的修改,实际上就是对于实参的修改。 由调用方法初始化参数值。实参、形参中ref不能省 。
(2)输出参数 out:被调用方法初始化参数值,可以不用初始化就作为参数传递给方法。
中级特性
面向对象的程序设计的三个核心概念:封装、继承、多态
1.封装
2.继承:
(1)有这四种方式:父调父;子调父:子类对象可以调父类所有方法;父调子;子调子
看一些例子:
- 子调父的方法
如果儿子有跟父亲同样的方法名,要调父类的那个方法,用base.方法名;如果子类没有,就直接用方法名。
class Car
{
public Car()
{ }
protected void f() { Console.WriteLine("aaa"); }
}
class TrashCar : Car
{
public TrashCar() { }
void f() { Console.WriteLine("bbb"); }
public void f1() { base.f(); f(); }//这里前面调父,后面调子
}
class MyApp
{
static void Main()
{
TrashCar myCar = new TrashCar();
myCar.f1();
}
}
using System;
namespace cs
{
public class test1
{
public void f()
{
Console.WriteLine("test1.....");
}
}
public class test2:test1
{
public void f()
{
Console.WriteLine("test2.....");
}
}
public class Solution
{
static void Main(string[] args)//这是C#的Main方法
{
test1 a = new test1();
a.f();
Console.ReadLine();
}
}
}
object声明对象:由于C#中System.Object类是所有类的祖先,所以可以用Object类型的引用指向所有类型的对象。
Object a = new fu();
Object b=new zi();//或者a=new zi;
a.f();
b.f();//报错
3.多态:一个函数可以从父类和子类的入口进去、多态是最大范围的对同名函数的控制
(1)声明用父类,给实例new时用子类,实际去的是父类的入口;
这个叫做继承时的多态
public class fu
{
public void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
public void f() { Console.WriteLine("zi"); }
}
static void Main(string[] args)
{
fu []a = new fu[3];
a[0]= new fu();
a[1]=new zi();
a[0].f();
a[1].f();
Console.ReadLine();
}
//输出
//fu
//fu
(2)(静态)编译时的多态:是通过重载完成的,重载(函数名相同,参数不同)。
这个就是类里写了两个同名同返回的函数,只是参数不同,所以调用函数时就根据参数不同调,而动态多态就是父类和子类各有同名同返回同参,完全一样。
(3)(动态)运行时的多态:是通过虚函数完成的。父类方法加virtual,子类方法加override表示将该方法覆盖;父类子类都加virtual就没用了
public class fu
{
public virtual void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
public override void f() { Console.WriteLine("zi"); }
}
static void Main(string[] args)
{
fu []a = new fu[3];
a[0]= new fu();
a[1]=new zi();
a[0].f();
a[1].f();
Console.ReadLine();
}
//输出
//fu
//zi
fu a = new fu();
fu b=new zi();
a.f();
b.f();
//fu zi本来用父类声明和子类给实例,从父类入口入,但是这里还是被覆盖了
所以可见覆盖和重载的区别:
相同点: 都涉及两个同名的方法。 不同点: 1.类层次 (1).重载涉及的是同一个类的两个同名方法; (2).覆盖涉及的是子类的一个方法和父类的一个方法,这两个方法同名。 2.参数和返回值 (1).重载的两个方法具有不同的参数,可以有不同返回值类型; (2).覆盖的两个方法具有相同的参数,返回值类型必需相同。
就近法则:有父子孙三级,各级都有同名同返回同参的方法;
在子类方法中访问成员(成员变量、成员方法)满足这个顺序
- 先子类局部范围找
- 然后子类成员范围找
- 然后父类成员范围找,如果父类范围还没有找到则报错。
public class fu
{
public virtual void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
public override void f() { Console.WriteLine("zi"); }
}
public class sun : zi {
public override void f() { Console.WriteLine("sun"); }
}
static void Main(string[] args)
{
sun b= new sun();
b.f();//显然输出sun
Console.ReadLine();
}
public class fu
{
public virtual void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
public override void f() { Console.WriteLine("zi"); }
}
public class sun : zi {
//public override void f() { Console.WriteLine("sun"); }
}
static void Main(string[] args)
{
sun b= new sun();
b.f();//输出zi
Console.ReadLine();
}
new:
若覆盖时没有使用virtual和override关键字,则称子类的方法隐藏了父类的方法。此时编译器报警告。若要消除掉警告,可以使用new修饰符。
不加New的话,会有警告
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cs1
{
internal class Program
{
public class fu
{
public void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
public new void f() { Console.WriteLine("zi"); }
}
public class sun : zi {
public new void f() { Console.WriteLine("sun"); }
}
static void Main(string[] args)
{
fu a = new fu();
a.f();//输出fu
sun b= new sun();
b.f();//输出sun
Console.ReadLine();
}
}
}
但要永远遵循“父声明,子给实例,从父类入口进,即调父类方法”
sealed:在虚方法上加这个修饰符;比如子某个方法sealed了,那么孙就不能再对这个方法重写(即改为虚方法)。
例:记得加virtual和override
public class fu
{
public virtual void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
public sealed override void f() { Console.WriteLine("zi"); }
}
public class sun : zi {
public override void f() { Console.WriteLine("sun"); }
}
抽象类与接口
抽象类就是把要变成抽象类的类class前面加一个abstract
接口定义:
接口成员访问方式是public,但是不能写出这个修饰符,即不允许public void f();
也不能声明出构造方法,即不能出现实例构造函数void f(){};
正确的:
interface IA//A表示这个接口的名字,I表示这是一个接口
{
void f();
}
接口上的二义性:例子
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cs1
{
interface IA//A表示这个接口的名字,I表示这是一个接口
{
void f();
}
interface IB
{
void f();
}
class A: IA,IB {
public void f() { Console.WriteLine("接口IA"); } //类中重写接口中的方法必须用public,不然做不了
void IB.f() { Console.WriteLine("接口IB"); }//显式调用IB中的方法
}
class Program
{
static void Main(string[] args) {
IA a=new A();//不需要as了
IB b = new A(); //声明接口是调用哪个的关键
a.f();//输出接口IA
b.f();//输出接口IB
Console.ReadLine();
}
}
}
总结:声明对象时的接口很重要,“继承”接口的类A中的方法更重要
但声明时更重要,实验过了,如果声明是用IA,A类中是IB.f(),那就调public f()
如果是IA.f(),先调IA.f()。如果是IB.f(),那么只有用IB接口声明对象才能调到IB接口。
new哪个类也可以改变接口怎么写
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cs1
{
interface IA//A表示这个接口的名字,I表示这是一个接口
{
void f();
}
interface IB
{
void f();
}
class A: IA,IB {
public void f() { Console.WriteLine("接口IA"); } //类中重写接口中的方法必须用public,不然做不了
void IB.f() { Console.WriteLine("接口IB"); }//显式调用IB中的方法
}
class B : IA,IB {
public void f() { Console.WriteLine("类B走的接口IA"); } //类中重写接口中的方法必须用public,不然做不了
void IB.f() { Console.WriteLine("类B走的接口IB"); }
}
class Program
{
static void Main(string[] args) {
IA a=new A();
IA b = new B();
a.f();//输出接口IA
b.f();//输出类B走的接口IA
Console.ReadLine();
}
}
}
实际问题:同学A还是同学B收作业问题
接口中一个方法就是收作业方法,两个同学作为类继承这个接口,是A还是B收作业再看
需要做一个走这个接口的通道(也可以像上面的例子中直接用a.f()还是b.f())
为什么要这个接口?因为工作中如果多写一个类多写一个方法很冗余,或者有的类不需要继承这个接口,然后你再写一个接口把这个工程都改了,能行吗
有一个Person类为什么要?就是Person类可以是A同学B同学类的父亲类,这样的话我就不用再改变new A() 还是new B() 时的语句,直接再Person类中搞(太难了不写了)
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cs1
{
interface collecthomework
{
void f();//收作业的方法
}
class A: collecthomework{
public void f() { Console.WriteLine("接口收作业,A收"); } //类中重写接口中的方法必须用public,不然做不了
}
class B : collecthomework {
public void f() { Console.WriteLine("接口收作业,B收"); } //类中重写接口中的方法必须用public,不然做不了
}
class Program
{
static void docollecthomework(collecthomework work)//执行这个通道需要搞一个做这个接口的通道
{
work.f();
}
static void Main(string[] args) {
collecthomework a= new A();
docollecthomework (a);
Console.ReadLine();
}
}
}
可以感觉到接口类似取代了基础的父类,而且比父子之间调的更便利,只要同名畅通无阻调(老师说:要学会丢弃旧方法,用新方法,比如子类的override重写和接口,都是很好的方法)
接口中的方法少些带参的
上一个例子的docollecthomework函数如果函数参数是接口数组,就很牛,因为类似子类的方法全部都在这个接口数组之下,答案都在这个数组里啊
计算不同图形的面积的例子:
public class MyApp
{
public static double SumAreas(ICalAreaAndVolumn[] array)//这个
{
double tot = 0.0;
for (int i = 0; i < array.Length; i++)
{ tot += array[i].GetArea(); }
return tot;
}
public static void Main()
{
Sphere sp = new Sphere(2);
Cylinder cd = new Cylinder(2, 10);
Cone cn = new Cone(2, 20);
ICalAreaAndVolumn[] array = { sp, cd, cn };
Console.WriteLine("total arears = {0}", SumAreas(array));
}
}