深入静态构造函数
1、面向对象
一种分析问题的方法(增强了程序的可扩展性)
面向对象的三大特性:封装,继承,多态.
封装:类与对象本身就是封装的体现。
1)属性封装了字段;
2)方法的多个参数封装成一个对象;
3)将一堆代码封装到了一个方法中;
4)装饰一些功能封装到了几个类中;
5)将一些具胡相同功能的代码封闭到了一个程序集中(exe,dll).
将一坨程序封装起来到一个程序集当中。
继承:类与类之间的关系。
好处:代码重用
2、LSP里氏替换原则
(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。
任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类
可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能
够在基类的基础上增加新的行为。
里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。
而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的
具体步骤的规范。
3、类
类的单根性、传递性、继承时函数的问题。
所有类都直接或间接继承自object.
继承中的访问修饰符问题.
1)private
2)public
3)protected
4)internal
5)protected internal
不加修饰符时:
1)类中默认是private,类本身不加修饰符默认是internal
2)结构体默认是public
3)抽象类,虚方法,必须显式public,否则子类无法重写
4)接口禁止写修饰符,它默认只能是public,否则类中无法实现
注意:以修改类的修饰符只能是public和internal
子类的访问权限不能高于父类。会造成父类成员暴露。
提示:vs2022中,尽管默认类的是interal,若不写时,vs2022在保存时会自动添加
修改符internal。同时类中成员也会自动添加private
class Father //默认是interal
{
void Show()//默认是private
{
}
}
public class Son : Father
{//错误,public高于internal
}
上面代码错误,因为父类原意是限制在当前程序集interal内部使用,结果子类玩嗨
了,用了public,意即在任何地方都可以用,由于继承原因无形中,就容易把父类成员暴
露在当前程序集之外了。所以,子类的访问权限是不能高于父类的。
类
类是模子,确定对象将会拥有的特征(属性)和行为(方法)
对象
是一个你能看得到,摸得着的具体实体--万物皆对象
类是模具,创建对象的模具,抽象的.
1)类是一种数据类型,用户自定义的数据类型
2)类组成:字段,属性,方法,构造函数等
对象是具体的,是类的具体实例。对象具有属性(特征)和方法(行为).
类中包含了数据(用字段表示)和行为(用方法,函数,功能表示,访求为一块具有名称
代码)
字段:存储数据
属性:对字段起辅助作用。如清洗数据,读写权限限制,提供更好安全性与灵活性
构造函数:初始化对象(new)
4、特别的关键字
base: 调用父类的成员(如构造函数),在子类中调用父类的重名方法
this:1当前类的对象,2调用自己的构造函数
new: 1创建对象,2特别指明子类重载,隐藏父类成员
public class Precipitation
{
public void ShowAmount()
{
}
}
public class Test : Precipitation
{
public new void ShowAmount()
{//有意隐藏子类时方法重载时,加new
}
}
virtual 标记一个方法是虚方法。
abstract 抽象类(方法)
虚方法或抽象类可以是方法或属性。但不能是字段.因字段没有方法体.
override 重写
interface 接口
partial 部分类
sealed 密封类(禁止向下继承,但本身可以继承别人 )
return 1返回值;2立即结束本次方法
break 跳出当前循环
continue 结束本次循环,返回循环条件进行判断
static 静态的
struct 结构体
enum 枚举
const 常量
模拟移动硬盘,U盘,MP3等移动存储设备,插到电脑上进行读写数据。
实现目的,让电脑具有可扩展性,无论什么都可以插入进行读写。
步骤一:统一一个父类抽象方法,下面是不同类型的子类如 mp3
步骤二:子类统一赋值到父类,作为统一的入口,
步骤三: 电脑的读写,根据统一的父类进行转换对应的子类进行读写(关键)
两种方式:
1)将父类传给两个函数;
2)写一个属性来存储插到电脑上的移动存储设备。父类类型的属性进行读写;
internal class Program
{
private static void Main(string[] args)
{
MobileDisk m = new MobileDisk();
MP3 mp3 = new MP3();
Udisk u = new Udisk();
Cpu cpu = new Cpu();
cpu.Read(m);//父类统一入口,不同子类进入
cpu.Write(mp3);
cpu.Read(u);
Console.ReadKey();
}
}
public class Cpu
{
//public Storage S { get; set; } 设置属性,在方法中使用
public void Read(Storage s)
{
s.Read();
}
public void Write(Storage s)
{
s.Write();
}
}
public abstract class Storage
{
public abstract void Read();
public abstract void Write();
}
public class MobileDisk : Storage
{
public override void Read()
{
Console.WriteLine("移动硬盘读");
}
public override void Write()
{
Console.WriteLine("移动硬盘写");
}
}
public class Udisk : Storage
{
public override void Read()
{
Console.WriteLine("优盘读");
}
public override void Write()
{
Console.WriteLine("优盘写");
}
}
public class MP3 : Storage
{
public override void Read()
{
Console.WriteLine("MP3读");
}
public override void Write()
{
Console.WriteLine("MP3写");
}
public void PlayMusic()
{
Console.WriteLine("MP3播放音乐");
}
}
1、虚方法注意的地方
1)父类中如果有方法需要让子类重写,则可以将该方法标记为Virtual.
2)虚方法在父类中必须有实现,哪怕是空实现。
3)虚方法子类可以重写(override),也可不重写.
一般类的方法重写与虚方法的重写,区别:
1)相同处:两者都是对父类方法的重写,都会隐藏父类的同名方法。
2)不同处:
a)父类对象由子类赋值而来时,虚方法调用的是子类的方法;
而一般类这时这时调用的是父类本身的方法。传递不到子类去.
b)虚方法必须是virtual-override配套使用。(override只能用在虚方法与抽象类中)
一般类直接写同名方法,或同名方法前面加new(特别标明隐藏父类同名方法)
internal class Program
{
private static void Main(string[] args)
{
Fruit f = new Fruit();
f.Show();//水果
Apple a = new Apple();
a.Show();//苹果
Orange o = new Orange();
o.Show();//橙子
Banana b = new Banana();
b.Show();//香蕉
f = new Apple();
f.Show();//苹果,虚方法,调用到子类的重写
f = new Orange();
f.Show();//水果
f = new Banana();
f.Show();//水果
Console.ReadKey();
}
}
public class Fruit
{
public virtual void Show()
{
Console.WriteLine("水果");
}
//public void Show()
//{
// Console.WriteLine("水果");
//}
}
public class Apple : Fruit
{
public override void Show()
{
Console.WriteLine("苹果");
}
}
public class Orange : Fruit
{
public void Show()//未标明new,会警告但不报错
{
Console.WriteLine("橙子");
}
}
public class Banana : Fruit
{
public new void Show()
{
Console.WriteLine("香蕉");
}
}
2、抽象方法注意的地方:
1)需要用abstract关键字标记。
2)抽象方法不能有任何方法实现。
3)抽象成员必须包含在抽象类中。
4)由于抽象成员没有任何实现,所以子类必须将抽象成员重写。
5)抽象类不能实例化。
抽象类的作用:抽象类的作用就是为了让子类继承,让子类重写。
6)抽象类中可以包括抽象成员,也可以包括有具体代码的成员。
比如抽象父类中可以用字段与属性,但它自己不能用,只能通过继承使用.
因此,这时的字段或属性,只能是public或protected。如果是private的话,
抽象类本身又不能实例化,也就无法访问这个字段或属性,同样它的子类
也无法访问private,这样这个字段或属性就成了“死人”,任何人无法访问。
此时,可以在抽象父类的public/protected属性或方法中去访问这个private
的字段,它也可以“活”过来。从而间接在子类中访问使用.
7)抽象方法不能用static修饰。
static是属性类的,具体说就是抽象父类的,静态在实例化前就可以使用了。
但是抽象方法有方法体么?静态后也无法使用。
而且静态方法一般不用于重写,已经是静态了(类中直接唯一调用使用),
重写有意义么?
抽象类与虚方法就是继承后实例化子类对象中使用,静态后,无法实现重写。
静态一般不与override,virtual联合使用。
提示:
C#静态方法属性类,类实例化前就可以使用了。
因为静态方法在类实例化前,就已经在内存中分配了固定地址位置,方便类直接
调用,所以它对于类来说静态成员的地址是固定的;而非静态成员只能在类实例
化后,才能分配内存,这个内存地址动态的,所在在实例化前无法确定非静态成
员的内存地址,也就无法使用它。
8)抽象方法有构造函数,但这构造函数没什么作用,直接忽略。
因为抽象类是不可以被直接构造的,不能实例化,所以它的构造函数没有意义。
尽管C#允许为抽象类添加构造函数,不过一点都没用处。所以不推荐给抽象类
写构造函数,因为这样做会破坏设计原则。
internal class Program
{
private static void Main(string[] args)
{
Shape c = new Circle("red", 33);
Console.WriteLine(c._color);
Console.WriteLine(c.Area());
Console.ReadKey();
}
}
public abstract class Shape
{
public string _color;//publlic
public Shape()//加上不会报错
{
}
public Shape(string color)//这个加上也不会报错
{
this._color = color;//这里加this,也不会报错.
}
public abstract double Area();
}
public class Circle : Shape
{
private double _radius;
public Circle(string color, double radius)
{
this._radius = radius;
this._color = color;//这里写到抽象父类继承过来的_color
}
public override double Area()
{
return Math.PI * _radius * _radius;
}
}
特别注意:(下面过程,可在三个构造中设置断点,观察流程走向)
先将上面三个构造Shape(),shape(string)以及子类的Circle(string,double),还
有主函数中的语句全部注释掉,着重看抽象类构造函数与其子类构造函数的关系。
1)三个构造函数,全部注释掉。不会报错,因都有隐藏的无参构造函数。
2)只激活Shape(string),报错。因为子类中没有构造函数来调用父类唯一的这个
构造函数.
3)只激活Shape()构造函数,不会报错。因为它就相当于隐式的无参构造函数。
4)激活shape()与shape(string),不会报错。因为无参构造函数shape()不会造成
子类隐式构造函数的错误。也就“掩盖”有参构造函数shape(string)的错误,
因为它没有谁来调用,它是一个“死掉”的构造函数.
5)只激活Circle(string,double)构造函数时,不会报错。因为调用它时,会自动
调用抽象父类的隐式无参构造函数,不影响构造,所以无错误。
6)激活Circle(string,double)与无参shape(),这个显式无参shape()起到了隐式
无参函数的作用,与上面5)相同,也没错误。
7)全部激活,同样因为显式无参shape()的作用,不会产生错误.
8)只激活circle(string,double)与shape(string),会报错。
因为circle构造时会调用父类构造,父类只有有参构造函数shape(string),
这个有参构造shape(string)无法接收到string类型参数。同时,因为它有存
在,父类隐式的无参函数又被覆盖,起不了作用。也就说,父类若要构造,只
能是调用有参shape(string).但这条路堵死了,无法有string过来。所以
报错。
此时也可用base传递参数到父类有参构造,也不会报错,代码如下:
public Circle(string color, double radius):base(color)
{
this._radius = radius;
this._color = color;//这里写到抽象父类继承过来的_color
}
1、什么是接口?
接口是一种规范,一种能力。如鸟会飞,飞机能会,但鸟与飞机无从父子关系,也
无法提炼出共同的父类。
此时接口就应运而生,用它来体现不同类之间,共同拥有的规则或能力,比如鸟与
飞机都会飞的能力。而且这种规则与能力是多种的,所以一个或多个类可以拥有多
个接口(也即多个能力)
2、什么时候应该使用接口???
不同类间无从属关系,也无法提炼共同父类,但都有共同的规则或能力时。
3、接口的目的是什么?
多态。此时,接口就类似充当了虚拟类的作用,它可以由继承的类来赋值。
internal class Program
{
private static void Main(string[] args)
{
Bird b = new Maque();
b.Fly();//鸟,非虚方法或抽象类,无法实现子类中的方法
Maque mq = new Maque();
mq.Fly();//麻雀,调用自身方法fly
IFlyable ifly = new Maque();
ifly.Fly();//接口, 显式调用接口方法fly
Console.ReadKey();
}
}
public interface IFlyable
{
void Fly();
}
public class Bird
{
public void Fly()
{
Console.WriteLine("鸟在飞");
}
}
public class QQ : Bird
{
public new void Fly()
{
Console.WriteLine("QQ不能飞");
}
}
public class Maque : Bird, IFlyable
{
public new void Fly()//重写父类
{
Console.WriteLine("麻雀本身能飞");
}
void IFlyable.Fly()//显式实现,不能添加public
{
Console.WriteLine("接口鸟能飞");
}
}
public class Plane : IFlyable
{
public void Fly()
{
Console.WriteLine("飞机能飞");
}
}
4、细节
1)接口中只能包含方法(属性,事件,索引器,都是方法),不能有字段。
2)接口中的成员都不能有任何实现。
3)接口不能被实例化。(静态与抽象类也不能实例化)
4)接口中的成员不能有任何访问修饰符(默认public,同理见6)条)
5)实现接口的子类必须将接口中的所有成员全都实现。(同抽象类)
6)子类实现接口的方法时,不需要任何关键字,直接实现即可。
public也不允许。而虚方法virtual-override,抽象类abstract-override
7)接口存在的意义就是为多态
5、显式与隐式实现接口
只有一个成员与接口相同,且不特别指明,则该成员是隐式实现接口。
需要特别指明与接口相同的成员,以 接口名.方法名()实现的,是显示声明。
接口中:
1)隐式声明的接口成员,用接口可以访问,也可以用类的对象访问。
2)显式声明的接口成员,只能用接口访问,不能用类的对象访问。
5、什么时候要添加引用和引入命名空间?
在一个项目中要使用另一个项目中的类的时候,可以添加引用,方便类的重用.
当前类中无法使用某些类时,应加入这些类的命名空间,以便当前类使用它们。
6、小结
接口存在的意义:多态
多态的意义:程序可扩展性。最终->节省成本,提高效率
接口解决了类的多继承问题,避免的体积庞大的问题.
接口之间可以实现多继承。
显式实现接口.
多态的作用:把不同的子类对象都当作父类来看。这样可以屏蔽子类对象之间的
的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
五、静态类
练习1:Console,Convert,Math使用时是否需要实例化?
它们是静态方法,是属性类的。也就是说程序一运行,该类的静态方法在类
还没有实例化前,就分配了固定的内存地址,已经存在,而且它在程序结束
后才消亡。实例化之前,它们就可以直接根据内存地址使用了。所以实例化
对它们没有任何意义。
练习2:声明一个静态字段都能在哪里使用?
静态类为什么不能实例化也不能继承?(抽象方法不能static)
可以利用静态字段的特征(高于实例化),每实例化一次,静态字段就记数一次,这
整个程序实例化了多少次,静态字段都可以记录,无论实例化对象已经消亡。
所以静态字段方便做全局性的统计。
基于练习1的回答,静态类已经在实例化前就分配了固定的内存地址,实例化则是
再次动态分配内存地址。两者冲突。
技巧:vs2022C#
1)类中输入字段后,按Ctrl+R,再Ctrl+E,会根据当前的字段自动生成属性。
2)在类中空行处,按Alt+Enter,会有提示,选择自动生成构造函数,根据弹出
的字段,选择后,自动生成构造函数。
3)如果是虚函数或抽象类,在子类名后继承的父类输入完成后,按Alt+Shift+F10
选择实现抽象类,抽象方法,等,将自动生成代码。
一般的类,在子类名首行输入完成后,按Alt+Shift+F10,选择构造函数,将
自动生成子类的构造函数。
作业一:
定义父亲类Father(姓lastName,财产property,血型bloodType),
儿子Son类(玩游戏PlaygGame方法),女儿类Daughter类(跳舞Dance方法),
调用父类构造函数(:base())给子类字段赋值
internal class Program
{
private static void Main(string[] args)
{
Son s = new Son("张无忌", 8888m, "AB");
s.SelfInfo();
s.PlayGame();
Daughter d = new Daughter("紫微", 6666m, "O");
d.SelfInfo();
d.Dance();
Console.ReadKey();
}
}
public class Father
{
private string _lastName;
private decimal _property;
private string _bloodType;
public string LastName { get => _lastName; set => _lastName = value; }
public decimal Property { get => _property; set => _property = value; }
public string BloodType { get => _bloodType; set => _bloodType = value; }
public Father(string lastName, decimal property, string bloodType)
{
this._lastName = lastName;
this._property = property;
this._bloodType = bloodType;
}
public void SelfInfo()
{
Console.WriteLine($"我叫{LastName},拥有{Property}元,是{BloodType}型血");
}
}
public class Son : Father
{
public Son(string lastName, decimal property, string bloodType) : base(lastName, property, bloodType)
{
}
public void PlayGame()
{
Console.WriteLine("儿子玩游戏");
}
}
public class Daughter : Father
{
public Daughter(string lastName, decimal property, string bloodType) : base(lastName, property, bloodType)
{
}
public void Dance()
{
Console.WriteLine("女儿会跳舞");
}
}
作业二:
定义汽车类Vehicle,属性(brand品牌,color颜色),方法:run。
子类卡车(Truck),属性weight载重,方法拉货。
轿车(Car),属性passenger载客数量,方法载客.
internal class Program
{
private static void Main(string[] args)
{
Vehicle v = new Vehicle("五菱杀手", "white");
v.Run();
Druck d = new Druck("五菱杀手", "white", 2.5);
d.LaHuo();
Car c = new Car("哈佛5", "black", 4);
c.CarryPassenger();
Console.ReadKey();
}
}
public class Vehicle //交通工具
{
private string _band;
private string _color;
public string Band { get => _band; set => _band = value; }
public string Color { get => _color; set => _color = value; }
public Vehicle(string band, string color)
{
this._band = band;
this._color = color;
}
public void Run()
{
Console.WriteLine("能够行驶");
}
}
public class Druck : Vehicle
{
private double _weight;
public double Weight { get => _weight; set => _weight = value; }
public Druck(string brand, string color, double weight) : base(brand, color)
{
this._weight = weight;
}
public void LaHuo()
{
Console.WriteLine($"当前卡车拉货{_weight}吨");
}
}
public class Car : Vehicle
{
private int _passenger;
public int Passenger { get => _passenger; set => _passenger = value; }
public Car(string brand, string color, int passenger) : base(brand, color)
{
this._passenger = passenger;
}
public void CarryPassenger()
{
Console.WriteLine($"当前载客{_passenger}");
}
}
作业三:
员工类,部门经理类(部门经理也是员工,所以要继承自员工类。员工有上班打卡
的方法,程序员来自员工不用打卡。用类来模拟。多态
internal class Program
{
private static void Main(string[] args)
{
Employee e1 = new Employee();
Employee e2 = new Manager();
Employee e3 = new Programmer();
e1.ClockIn();
e2.ClockIn();
e3.ClockIn();
Console.ReadKey();
}
}
public class Employee
{
public virtual void ClockIn()
{
Console.WriteLine("员工九点打卡");
}
}
public class Manager : Employee
{
public override void ClockIn()
{
Console.WriteLine("经理11点打卡");
}
}
public class Programmer : Employee
{
public override void ClockIn()
{
Console.WriteLine("程序员不用打卡"); ;
}
}
作业四:
动物Animal都有吃Eat和叫Bark的方法,狗Dog和猫Cat叫的方法不一样。
父类中没有默认的实现,所以考虑用抽象方法
internal class Program
{
private static void Main(string[] args)
{
Animal a = new Dog();
a.Eat();
a.Bark();
Animal a1 = new Cat();
a1.Eat();
a1.Bark();
Console.ReadKey();
}
}
public abstract class Animal
{
public abstract void Eat();
public abstract void Bark();
}
public class Dog : Animal
{
public override void Bark()
{
Console.WriteLine("旺旺旺");
}
public override void Eat()
{
Console.WriteLine("狗狗快吃"); ;
}
}
public class Cat : Animal
{
public override void Bark()
{
Console.WriteLine("喵喵喵");
}
public override void Eat()
{
Console.WriteLine("猫咪慢吃"); ;
}
}
技巧:
在上一行,按Shift+Enter,会新开一空行,如果上句是方法,会自动开{},鼠标
自动移到下面空行中.
作业五:
计算形状Shape(圆Cirle,矩形Square,正方形Rectangle)的面积,周长.
internal class Program
{
private static void Main(string[] args)
{
Shape s = new Circle(5);
Console.WriteLine(Math.Round(s.GetArea(), 2) + "--" + s.GetPremeter());
Shape s1 = new Square(3);
Console.WriteLine(s1.GetArea() + "--" + s1.GetPremeter());
Shape s2 = new Rectangle(3, 4);
Console.WriteLine(s2.GetArea() + "--" + s2.GetPremeter());
Console.ReadKey();
}
}
public abstract class Shape
{
public abstract double GetArea();
public abstract double GetPremeter();
}
public class Circle : Shape
{
private double _r;
public Circle(double r)
{
_r = r;
}
public override double GetArea()
{
return Math.PI * _r * _r;
}
public override double GetPremeter()
{
return 2 * Math.PI * _r;
}
}
public class Square : Shape
{
private double _side;
public Square(double side)
{
_side = side;
}
public override double GetArea()
{
return _side * _side;
}
public override double GetPremeter()
{
return 4 * _side;
}
}
public class Rectangle : Shape
{
private double _sideA;
private double _sideB;
public Rectangle(double sideA, double sideB)
{
_sideA = sideA;
_sideB = sideB;
}
public override double GetArea()
{
return _sideA * _sideB;
}
public override double GetPremeter()
{
return 2 * (_sideB + _sideA);
}
}
作业六:鸟-麻雀sparrow,鸵鸟ostrich,企鹅penguuin,鹦鹉parrot.
鸟能飞,鸵鸟、企鹅不能飞...你怎么构造面向对象。
注意:隐式接口,接口对象可以调用,类的对象也可以调用。
显式接口,只能是接口对象可以调用,类的对象不能调用。
internal class Program
{
private static void Main(string[] args)
{
IFlyable ifly = new Parrot();
ifly.Fly();//接口只有一个成员,所以只能访问fly
Parrot p = new Parrot();
p.HaveWing();//来自父类bird
p.SayHello();//来自类本身
p.Fly();//来自类本身 隐式接口成员p.Fly既可以由接口访问,也可以由类对象访问。
Sparrow s = new Sparrow();
s.HaveWing();//来自父类bird
//s.Fly();//错误,显式Fly来自接口,类对象不能直接访问,只能通过接口访问
Console.ReadKey();
}
}
public interface IFlyable
{
void Fly();
}
public class Bird
{
public void HaveWing()
{
Console.WriteLine("鸟类有两翅膀");
}
}
public class Sparrow : Bird, IFlyable
{
void IFlyable.Fly()//显式
{
Console.WriteLine("接口麻雀能飞");
}
}
public class Parrot : Bird, IFlyable
{
public void SayHello()
{
Console.WriteLine("鹦鹉能说话");
}
public void Fly()//隐式
{
Console.WriteLine("类中鹦鹉能飞");
}
}
public class Penguuin : Bird
{
}
public class Ostrich : Bird
{
}
作业七:
橡皮rubber鸭子,木wood鸭子,真实的鸭子realduck.
三个鸭子都会游泳,而橡皮鸭子和真实鸭子都会叫,只是叫声不一样,橡皮鸭子
“唧唧”叫,真实的鸭子“嘎嘎”叫,木鸭子不会叫。
internal class Program
{
private static void Main(string[] args)
{
ReadDuck r = new ReadDuck();
WoodDuck w = new WoodDuck();
RubberDuck ru = new RubberDuck();
IBarkable ib = r;
Duck d = w;
ib.Bark();
d.Swim();
Console.ReadKey();
}
}
public interface IBarkable
{
void Bark();
}
public class Duck
{
public virtual void Swim()
{
Console.WriteLine("鸭子能游泳");
}
}
public class ReadDuck : Duck, IBarkable
{
public void Bark()
{
Console.WriteLine("嘎嘎嘎"); ;
}
public override void Swim()
{
Console.WriteLine("真实鸭子能游泳");
}
}
public class RubberDuck : Duck, IBarkable
{
public void Bark()
{
Console.WriteLine("唧唧唧"); ;
}
public override void Swim()
{
Console.WriteLine("橡皮鸭子能游泳");
}
}
public class WoodDuck : Duck
{
public override void Swim()
{
Console.WriteLine("木头鸭子能游泳");
}
}
学习.Net就是学习它的无数个类库怎么用。
string,字符串可以看成字符的只读数组.
不可变特性(通过for循环,修改String中的元素,失败)
GC 垃圾回收器
程序结束时或一定时期内,GC会扫描内存,清理资源。但有些资源GC是无法处理的
比如,流Filestrea等
属性:
Length: 长度
方法:
IsNullOrEmpty() 静态方法 ,判断是否为null或""
ToCharArray() string转换为char[]
ToLower() 小写.必须接收返回值(因为字符串的不可变)
ToUpper() 大写
Equals() 比较两字串是否相同。忽略大小写的比较StringComparation.区别==
Contains() 是否包含
IndexOf() 如何没找到,返回-1
LastIndex() 从后向前找
Substring() 2个重载,截取字串
Split() 分割字串
Join() 静态方法
Replace()
Trim()
练习一:接收用户输入的字符串,将字符串反向输出。例:abc->cba
Console.WriteLine("请输入任意字符串:");
string s = Console.ReadLine();
char[] c = s.ToCharArray();
//Array.Reverse(c);
for (int i = 0; i < c.Length / 2; i++)
{
char temp = c[i];
c[i] = c[c.Length - 1 - i];
c[c.Length - 1 - i] = temp;
}
s = new string(c);
Console.WriteLine(s);
练习二:接收用户输入的一句英文,将其中的单词反向输入。例如I love you->you love I
Console.WriteLine("输入一句英文:");
string s = Console.ReadLine();
string[] s1 = s.Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries);
//Array.Reverse(s1);
for (int i = 0; i < s1.Length / 2; i++)
{
string temp = s1[i];
s1[i] = s1[s1.Length - 1 - i];
s1[s1.Length - 1 - i] = temp;
}
s = string.Join(" ", s1);
Console.WriteLine(s);
练习三: “2012年12月21日”,从日期字符串中把年月日分别取出来,打印到控制台。
string s = "2012年12月21日";
string[] s1 = s.Split(new char[] { '年', '月', '日' }, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine(string.Join("-", s1));
练习五:123-456---789-----123-2,把类似的字符串中重复的符号去掉。
即得到123-456-789-123-2
string s = "123-456---789-----123-2";
string[] s1 = s.Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
s = string.Join("-", s1);
Console.WriteLine(s);
练习六:从文件路径中提取出文件名(包含后缀),比如从c:\a\b.txt中提取以b.txt这个
文件名。(引申:以后还可用正则表达式进行提取)
string p = @"E:\a\b\1.txt";
Console.WriteLine(Path.GetFileName(p));
练习七:求员工工资文件中,员工的最高工资,最低工资,平均工资。
张三|1000
李四|900
王五|1100
赵六|600
string[] s = File.ReadAllLines(@"E:\1.txt");
int max = int.MinValue, min = int.MaxValue, sum = 0, count = 0;
for (int i = 0; i < s.Length; i++)
{
if (!string.IsNullOrEmpty(s[i]))
{
string temp = s[i].Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries)[1];
int temp1 = Convert.ToInt32(temp);
if (max < temp1)
{
max = temp1;
}
if (min > temp1)
{
min = temp1;
}
sum += temp1;
count++;
}
}
Console.WriteLine("{0},{1},{2}", max, min, sum / count);
当大量进行字串操作的时候,例如:很多次的字串的拼接操作。
若用string,但string对象是不可变的。每次使用system.string类中的一个方法时,都
要在内在中创建一个新的字串对象,这就需要为该新对象分配新的空间。在需要对字串
执行重复修改的情况下,与创建新的string对象相关的系统开销可能会非常大。
如果要修改字串而不创建新的对象,则可以使用System.Text.StringBuilder类。
例如,当在一个循环中许多字符串连接在一起时,使用StringBuilder可以提升性能。
StringBuilder!=string
将StringBuilder转换为string时,用ToSring()
StringBuilder仅仅是拼接字符串的工具,大多数情况下,还需要把StringBuilder转换
到string。
样例:
StringBuilder sb=new StringBuilder();
sb.Append();//追加
sb.ToString();//转为字串
sb.Insert();//插入 索引的前面
sb.Replace();//替换
案例:使用程序拼接html中的table并显示。
在winform中添加webbrowser,用StringBuilder拼成WebBrowser.documentText
这样就创建了一个网页。
在winform中添加一个webbrowser,再添加一个button,在button中添加代码。
private void button1_Click(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("下面是一个表格
");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("");
sb.Append("星期一 星期二 星期三 星期一 星期二 星期三 星期一 星期二 星期三
");
sb.Append("");
sb.Append("");
webBrowser1.DocumentText = sb.ToString();
}
注意:还可以添加css样式,在head部分添加
集合常用操作:添加,删除,遍历
命名空间:System.Collections
1、ArrayList可变长度数组,使用类似于数组
属性:
Capacity 容量,可容纳元素的个数,翻倍增长。
Count 集合中实际元素的个数
方法:
Add(),AddRange(ICollection c),Remove(),RemoveAt(),Clear()
Contain(),ToArray(),Sort(),Reverse(),
2、HashTable 键值对集合,类似于字典,HashTable在查找元素的时候,速度很快。
Add(object key,object value)
hash["key"]
hash["key"]=修改
ContainsKey("key")
Remove("key")
遍历
hash.keys
hash.Values/DictionaryEntry
3、装箱与拆箱
装箱:将值类型转换成引用类型;(栈中)
拆箱:将引用类型转换成值类型。(堆中)
值类型:bool,int,double,float,char,struct,enum decimal,byte,
引用类型:interface,object,string,数组,集合,自定义类
发生拆装箱的两种类型必须具有继承关系。
案例:两个ArrayList集合{"a","b","c","d","e"}和{"d","e","f","g","h"},把这两个
集合去除重复项合并成一个.
private static void Main(string[] args)
{
ArrayList al1 = new ArrayList() { "a", "b", "c", "d", "d" };
ArrayList al2 = new ArrayList() { "d", "e", "f", "g", "h" };
foreach (string value in al2)
{
if (!al1.Contains(value))
{
al1.Add(value);
}
}
foreach (string value in al1)
{
Console.Write(value + ",");
}
Console.ReadKey();
}
案例:随机生成10个1-10之间的数放到ArrayList中,要求这10个数不能重复,并且都
是偶数
private static void Main(string[] args)
{
Random r = new Random();
ArrayList al = new ArrayList();
for (int i = 0; i < 10; i++)
{
while (true)
{
int n = r.Next(1, 11);
if (al.Contains(n))
{
continue;
}
else
{
al.Add(n);
break;
}
}
}
foreach (var item in al)
{
Console.Write(item.ToString() + " ");
}
Console.ReadKey();
}
练习:有一个字符串是用空格分隔的一系列整数,写一个程序把其中的整数做如下重新
排列打印出来:奇数在左侧,偶数在右侧。
例如:2 7 8 3 22 9 5 11-->7 3 9 11 2 8 22
private static void Main(string[] args)
{
string s = "2 7 8 3 22 9 5 11";
string[] s1 = s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
ArrayList al = new ArrayList();
for (int i = 0; i < s1.Length; i++)
{
int n = Convert.ToInt32(s1[i]);
if (n % 2 == 0)
{
al.Add(n);//偶数在后
}
else
{
al.Insert(0, n);//奇数在前
}
}
Console.WriteLine(string.Join(" ", al.ToArray()));
Console.ReadKey();
}
注意:不要把Random的实例化放入循环体内,会降低执行效率。因为某次中new的时候
种子是一样的,取的是当前时间。在循环体内,循环一次new一次,会浪费资源
去取种子形成迭代算法。
在取随机数时,只需要new一次就可以了。(除非有特殊要求)
4、List
类似书橱,将不同类型进行归类整理。
System.Collections.Generic
List
Sort()
Max()
MIn()
Sum()
Dictionary
T,K,V就像一把锁,锁住集合只能存储某种特定的类型。
泛型集合可以进行foreach遍历.
因为它实现了IEnumerable
list.Add()
list.AddRange()
list.Insert()
list.InsertRange()
list.Remove()
list.RemoveAt()
list.RemoveRange()
list.RemoveAll()
list.Contain()
5、Dictionary字典
Dictionary
dic.Add()
dic.ContainsKey()
dic[key]=value
KeyValuePair
6、泛型集合练习:
初始化器:
1)集合:List
2)对象: Person p=new Person(){Name="张三",Age=11,Gender='男'};
3)数组:int[] ints = { 1, 2, 3, 4 };
int[] ints = new int[]{ 1, 2, 3, 4};
internal class Program
{
static void Main(string[] args)
{
Person p=new Person();
Person p1 = new Person("张三");//错误,无对应构造函数
Person p2 = new Person() { Name="张三" };//正确
Person p4 = new Person { Name = "张三" };//正确,调用无参构造
}
}
internal class Person
{
public string Name { get; set; }
}
对于上面例子,创建一个构造函数看看:
internal class Program
{
private static void Main(string[] args)
{
Person p1 = new Person("张三");//正确,无对应构造函数
Person p3 = new Person("李四") { Name = "张三" };//正确
Person p4 = new Person { Name = "张三" };//错误,没有无参构造函数
Console.WriteLine("{0},{1}", p1.Name, p3.Name);//张三,张三
Console.ReadKey();
}
}
internal class Person
{
public Person(string name)
{
Name = name;
}
public string Name { get; set; }
}
注意:上面初始化时,初始化器起作用。
若要子类使用构造函数,还是用原来的构造方法即括号()赋值.
{}用于快速初始化
案例:把分拣奇偶的程序用泛型实现.
private static void Main(string[] args)
{
string s = "2 7 8 3 22 9 5 11";
List ints = new List();
List ints1 = new List();
List ints2 = new List();
string[] s1 = s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < s1.Length; i++)
{
int temp = Convert.ToInt32(s1[i]);
if (temp % 2 == 0)
{
ints1.Add(temp);//偶
}
else
{
ints2.Add(temp);
}
}
ints.AddRange(ints2);
ints.AddRange(ints1);
foreach (int i in ints)
{
Console.Write(i + " ");
}
Console.ReadKey();
}
练习1:将int数组中的奇数放到一个新int数组中返回.
private static void Main(string[] args)
{
//练习1:将int数组中的奇数放到一个新int数组中返回.
int[] ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List list = new List();
for (int i = 0; i < ints.Length; i++)
{
if (ints[i] % 2 != 0)
{
list.Add(ints[i]);
}
}
int[] ints1 = list.ToArray();
Console.WriteLine(string.Join(",", ints1));
Console.ReadKey();
}
练习2:从一个整数的List
private static void Main(string[] args)
{
//练习2:从一个整数的List中取出最大数(找出最大值).别用max方法
List ints = new List() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
//Console.WriteLine(ints.Max());
int max = 0;
for (int i = 0; i < ints.Count; i++)
{
if (ints[i] > ints[max])
{
max = i;
}
}
Console.WriteLine(ints[max]);
Console.ReadKey();
}
练习3:把123转换为:壹贰参。
private static void Main(string[] args)
{
//练习3:把123转换为:壹贰参。
Dictionary map = new Dictionary();
//Dictionary map1 = new Dictionary() { { 1, "壹" },{ 2, "贰" },{ 3, "参" } };
map[1] = "壹";
map[2] = "贰";
map.Add(3, "参");
map[4] = "肆"; map[5] = "伍"; map[6] = "陆"; map[7] = "柒";
map[8] = "捌"; map[9] = "玖"; map[0] = "零";
foreach (KeyValuePair pair in map)
{
Console.Write($"{pair.Key}:{pair.Value},");
}
Console.WriteLine();
foreach (int key in map.Keys)
{
Console.Write($"{key},");
}
Console.WriteLine();
foreach (string value in map.Values)
{
Console.Write($"{value},");
}
Console.WriteLine();
int n = 234;
int b = n / 100;
int s = n / 10 % 10;
int g = n % 10;
Console.WriteLine($"{map[b]}佰{map[s]}拾{map[g]}元");
Console.ReadKey();
}
练习4:计算字符串中每种字符出现的次数(面试题)。
"Welcome to Chinaworld",不区分大小写,打印"W 2","e 2"...
提示:Dictionary,char有很多静态方法.char.Isletter()
private static void Main(string[] args)
{
//练习4:计算字符串中每种字符出现的次数(面试题)。
//"Welcome to Chinaworld",不区分大小写,打印"W 2","e 2"...
string s = "Welcome to Chinaworld";
Dictionary dic = new Dictionary();
for (int i = 0; i < s.Length; i++)
{
char c = char.ToLower(s[i]);
if (c == ' ') continue;
if (dic.ContainsKey(c))
{
dic[c] += 1;
}
else
{
dic.Add(c, 1);
}
}
foreach (KeyValuePair kvp in dic)
{
Console.WriteLine($"{kvp.Key} {kvp.Value},");
}
Console.ReadKey();
}
案例:简繁体转换.Dictionary.
File 静态类。操作文件,对文件整体操作:拷贝,删除,剪切等
Directory 静态类。操作目录(文件夹)
Path 对文件或目录的路径进行操作。实际上操作的是--字符串
Stream 抽象类。文件流
FileStream 文件流。MemoryStream内存流,NetworkStream网络流。
SteamReader 快速读取文本文件
StreamWriter 快速写入文本文件
1、静态与非静态的区别
静态 实例(非静态)
static关键字 无static
使用类名调用 使用实例调用
静态方法中可访问静态成员 实例中可直接访问静态成员
静态方法中不可直接访问实例成员 实例中可直接访问实例成员
调用前初始化 实例化对象时初始化
1)静态类有构造函数,且该构造函数只能是静态的。
2)静态构造函数前不能再有修饰符,例如public
2)静态类的构造函数在调用前就执行了,
3)静态构造函数在程序中只运行一次。
internal class Program
{
private static void Main(string[] args)
{
Student.Test();
Student.Test();
Student.Test();
Console.ReadKey();
}
}
public static class Student
{
static Student()//静态构造函数前不能有修饰符
{
Console.WriteLine("静态类的静态构造函数");
}
public static void Test()
{
Console.WriteLine("静态类中的静态方法");
}
}
结果:
静态类的静态构造函数
静态类中的静态方法
静态类中的静态方法
静态类中的静态方法
internal class Program
{
private static void Main(string[] args)
{
Teacher t = new Teacher();//一般类的静态构造函数,调用前执行
t.Hello(); //一般类中的一般方法
Teacher.Test(); //一般类中的静态方法
Teacher t2 = new Teacher(); //无内容,因为静态构造函数只执行一次.
Console.ReadKey();
}
}
public class Teacher
{
static Teacher()//静态构造函数前不能有修饰符
{
Console.WriteLine("一般类的静态构造函数");
}
public static void Test()
{
Console.WriteLine("一般类中的静态方法");
}
public void Hello()
{
Console.WriteLine("一般类中的一般方法");
}
}
结果:
一般类的静态构造函数
一般类中的一般构造函数
一般类中的一般方法
一般类中的静态方法
一般类中的一般构造函数
说明:程序执行时,在初始化实例前,静态的构造函数优先,先执行静态构造函数。
在实例化时执行一般构造函数。
后面按顺序执行,因为静态构造函数只执行一次,所以后面的不再出现静态构造.
2、结构与类的区别
1)类型上:结构是值类型,类是引用类型;
2)声明语法上:struct,class
3)结构也可以声明字段,属性,构造函数,但是:
结构的构造函数第一次赋值只能是字段,不能用属性。且有参构造必须写全字段。
不能选择性字段给构造函数,写全情况下重载也不行。
4)对于类new三件事:堆上开空间,存储对象,调用构造函数。
结构的new只做了一件事:调用结构的构造函数。(是在栈上开空间)
5)结构与类都有构造函数。
都有无参构造函数。
类中写了新构造函数后,原无参构造函数消失。
结构写了新构造函数(必须全字段,且只能是字段),原无参构造函数不消失。
也即结构最多只能有两个构造函数。
注意:结构的无参构造函数只能是隐式的,不能显式。
除非是静态的构造函数,它可以显示,但它只属于类,在实例化前就执行了。
结构与类在什么情况下使用:
1)如果仅单纯存储数据,推荐使用struct。因为它在栈上,节省空间。
2)如果是OOP思想开发程序,推荐使用class。因为结构不具备面向对象特征.
internal class Program
{
private static void Main(string[] args)
{
Dog d = new Dog();
d.Test();
Dog.Hello();
Console.ReadKey();
}
}
public struct Dog
{
private string _name;
private int _age;
public string Name { get => _name; set => _name = value; }
public int Age { get => _age; set => _age = value; }
static Dog()//属性类
{
Console.WriteLine("结构的静态构造函数。");
}
public Dog(string name, int age)
{
// Name = name;//错误。写在下句之后正确,相当于初始化再赋值
_name = name;//正确
_age = age;
Console.WriteLine("结构的有参构造函数");
}
public void Test()
{
Console.WriteLine("结构方法");
}
public static void Hello()
{
Console.WriteLine("结构中的静态方法");
}
}
说明:结构中有三个构造函数,但静态构造是属性类的,在实例化前就执行了,且在程序中
只执行一次。
例中结构中有无参隐式构造函数,它不允许显式写出来。
结果:
结构的静态构造函数。
结构方法
结构中的静态方法
结构的有参构造函数