编程语言基础
一、万物皆Object:
- 自定义类怎样继承自Object,源于编译器的自动添加继承声明
以下代码作用完全一致
public class A {}
public class A : Object {}
- 有关类型转换:
- try……catch固然方便常用,但每次处理会新建异常堆栈,影响性能。
- .NET中提供了类型之间的判断和转换方法,不会抛出异常。(as、is)
– is:检查两类型是否兼容,返回true/false
– as:as 不仅负责检查兼容性还会进行类型转换,并返回结果,如果不兼容则null
- 实际操作代码
using System;
public class test
{
//学生类
public class Student
{
public string Name
{
set;get;
}
//重写ToString()方法
public override string ToString()
{
return $"name:{Name}";
}
}
public static void Main()
{
Student student = new Student();
student.Name = "xiaoming";
if (student is Object) //判断是否兼容
{
Object o = student as Object; //判断兼容并返回对象或null
Console.WriteLine(o.ToString());
}
else Console.WriteLine(false);
}
}
二、值类型与引用类型
- 无论值类型/引用类型皆继承自System.Object,区别在于值类型继承自System.Object.ValueType。(恰好值类型又将Object.Equal()方法重写为值比较,引出Equal、== 、ReferenceEqual()、===、的区别)
- 由此引出拆箱封箱的概念。
- 封箱与拆箱涉及了堆与栈的来回复制移动,影响性能,编程原则就是避免任何没必要的装箱拆箱操作。代码中提供两种解决方式。
public void Method(){
int i = 4;
object O = i; //装箱操作
int j = (int) O; //拆箱操作
//1、值类型的格式化输出,此处重写了Object中的虚方法ToString(),不会出现类型转换。
int i = 10;
Console.WirteLine("Value is {0}",i.ToString());
//2、利用泛型避免大规模的装箱拆箱。
ArrayList arrList = new ArrayList(); //内部实现是将对象当作object来对待,隐含了装箱拆箱操作。
arrList.Add(0);
arrList.Add("1");
// 使用泛型数据结构代替ArrayList,提高性能。
List<int> intList = new List<int>();
intList.Add(1);
intList.Add(2);
}
一)传参方式
- 由此再学习C#中三种传递参数的方式:
- ref关键字:要求参数在传入前被初始化;
- out关键字:要求参数在方法返回前被初始化;
- params关键字:允许方法在定义时不确定参数的数量,但params参数后不允许再有其他任何参数。
二)深拷贝与浅拷贝
- 传参涉及了拷贝,由此延申深拷贝与浅拷贝,实际区别体现在引用类型中。
-1. .NET中,基类Object为所有类型实现浅拷贝。
-2. System.Object.MemberWiseClone();方法为浅拷贝
-3. 深拷贝的实现:
1、依次实例化新的对象并赋予相同的值。
2、利用序列化/反序列化来对对象进行深度复制
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace System
{
[Serializable]
public class DeepCopy : ICloneable
{
public object Clone()
{
//序列化反序列化具体实现
//以简单的二进制数据流以及某些附加的类型信息,实现持久化。XmlSerializer( );
BinaryFormatter bf = new BinaryFormatter();
//创建内存流ms
MemoryStream ms = new MemoryStream();
//将this=>DeepCopy类序列化入内存流ms
bf.Serialize(ms, this);
//将ms存入流中的0位置。
ms.Position = 0;
//反序列化得到的是object对象
return bf.Deserialize(ms);
}
}
public class Program
{
static void Main()
{
//创建实例
DeepCopy copy = new DeepCopy();
//实例调用方法,通过序列化反序列化深拷贝操作
DeepCopy deepCopy =(DeepCopy)copy.Clone();
//浅拷贝操作
DeepCopy shadowCopy = copy;
Console.WriteLine("原对象哈希码:{0}", copy.GetHashCode());
Console.WriteLine("浅拷贝对象哈希码:{0} 浅拷贝引用比较:{1}", shadowCopy.GetHashCode(),Object.ReferenceEquals(copy,shadowCopy));
Console.WriteLine("深拷贝对象哈希码:{0} 深拷贝引用比较:{1}", deepCopy.GetHashCode(), Object.ReferenceEquals(copy, deepCopy));
}
}
}
三)堆栈、托管堆、非托管堆、GC
- 类型的存储总要存入堆或栈中,.NET开辟三块内存作为 堆栈、托管堆、非托管堆。
- 堆栈:存储值类型的对象和引用类型的引用。
- 托管堆:也是连续的,还存在着暂时不能被分配但已无用的对象内存块,当内存不够时才进行GC机制。GC不仅仅释放资源,还负责移动合并内存块。~Object();(Finalize,非实时释放资源;)
- 非托管堆:无法合并移动,所以不连续分布。非.NET托管,需程序员手动分配和释放。Dispose();(操作系统资源的对象、文件流、数据库连接等属于非托管资源)
- GC(Garbage Collect)垃圾回收机制
- 清理托管堆上不会再被使用的对象内存,并且移动仍在被使用的对象使它们紧靠托管堆的一边。
- Finalize(~Object())的机制:
- NET将每个带有Finalize的对象引用添加到一个“带析构表”。
- GC检测到不被使用的对象时,进一步检查带析构表。无则认为垃圾,有则入“待析构表”。
- CLR一个单独线程处理“待析构表”,就是调用Finalize方法。
- 下个GC执行时,将释放已调用Finalize方法的对象实例和垃圾。
三、特殊的String
一)StringBuilder
- 内部以字符数组char[ ]为基础维护一个链表来进行操作。
二)字符串常量池(字符串驻留池)
- 字符串常量池机制:
- 当一个新的字符串对象需要分配时,CLR首先监测内部容器中是否已经存在该字符串对象。
- 如果已经包含则直接返回已经存在的字符串对象引用;
- 如果不存在,则新分配一个字符串对象,同时把其添加到内部容器中。
- new申请新的分配空间,所以该机制不会有作用。
class Program
{
static void Main(string[] args)
{
// 1.两个字符串对象,理论上引用应该不相等
// 但是由于字符串池机制,二者指向了同一对象
string a = "abcde";
string b = "abcde";
Console.WriteLine("a=b(引用)?{0}", Object.ReferenceEquals(a, b));
// 2.由于编译器的优化,所以下面这个c仍然指向了同一引用地址
string c = "a" + "bc" + "de";
Console.WriteLine("a=c(引用)?{0}", Object.ReferenceEquals(a, c));
// 3.显示地使用new来分配内存,这时候字符串池不起作用
char[] arr = { 'a', 'b', 'c', 'd', 'e' };
string d = new string(arr);
Console.WriteLine("a=d(引用)?{0}", Object.ReferenceEquals(a, d));
Console.ReadKey();
}
}
四、数组
一)数组的类型
无论存储值类型或引用类型,其本身都是引用类型。
二)数组的转换
- 数组直接转换的条件
- 包含值类型的数组不能被隐式转换成其他任何类型;
- 两个数组类型能够相互转换的一个前提是两者维数相同;
//
// 摘要:
// 将一种类型的数组转换为另一种类型的数组。
// 参数:
// array:
// 要转换为目标类型的从零开始的一维 System.Array。
// converter:
// 用于将每个元素从一种类型转换为另一种类型的 System.Converter`2。
// 类型参数:
// TInput:
// 源数组元素的类型。
// TOutput:
// 目标数组元素的类型。
// 返回结果:
// 目标类型的数组,包含从源数组转换而来的元素。
// 异常:
// T:System.ArgumentNullException:
// array 为 null。 - 或 - converter 为 null。
public static TOutput[] ConvertAll<TInput, TOutput>(TInput[] array, Converter<TInput, TOutput> converter);
- 只需指定原数组的类型、对象数组的类型和具体的转换算法
class Program
{
static void Main(string[] args)
{
int[] sz = { 1, 2, 3 };
object[] o = Array.ConvertAll(sz, new Converter<int, object>(IntToObject));
Console.WriteLine("sz的类型:{0},o的类型:{1}",sz.GetType().ToString(),o.GetType().ToString());
}
public static object IntToObject(int temp)
{
return (object)temp;
}
}
五、面向对象编程
一)父类子类构造顺序
public class Program
{
public static void Main(string[] args)
{
// 构造了一个最底层的子类类型实例
C newObj = new C();
Console.ReadKey();
}
}
// 基类类型
public class Base
{
public Ref baseString = new Ref("Base 初始化表达式");
public Base()
{
Console.WriteLine("Base 构造方法");
}
}
// 继承基类
public class A : Base
{
public Ref aString = new Ref("A 初始化表达式");
public A():base() //显示调用父类的无参构造
{
Console.WriteLine("A 构造方法");
}
}
// 继承A
public class B : A
{
public Ref bString = new Ref("B 初始化表达式");
public B() //隐式调用父类的无参构造
{
Console.WriteLine("B 构造方法");
}
}
// 继承B
public class C : B
{
public Ref cString = new Ref("C 初始化表达式");
public C()
{
Console.WriteLine("C 构造方法");
}
}
// 一个简单的引用类型
public class Ref
{
public Ref(string str)
{
Console.WriteLine(str);
}
}
二)基本特性
1、封装
public class test
{
public class Student
{
//C#1中,调用构造函数和方法
int sno;
public int Sno { get { return sno; } }
public Student(int sno) { this.sno = sno; }
//C#2私有赋值方法
string name;
public string Name
{
private set { name = value; }
get { return name; }
}
//C#3中自动实现了属性,是代码更为简单。
public int Age { set; get; }
}
}
2、继承
- 减少复用,is-a的关系(Bird is a Animal),解决复用,子类(Birds)可实现父类(Animal)的Sleep、Eat和自己的Fly,父类有的子类都有。
- 里氏替换
public class test
{
public class Animal{ }
public class Birds : Animal{ }
public static void Main()
{
//里氏替换:子类对象可以代替父类对象。
Animal animal = new Birds();
//反之不行,系统报错无法转换。
Animal animal1 = new Animal();
if (animal1 as Birds == null)
{
Console.WriteLine("Birds birds = new Animal();转换失败。\r\n");
}
else Console.WriteLine("Birds birds = new Animal();转换成功。\r\n");
//可以把 指向子类对象的父类变量 转换为 子类对象。
if (animal as Birds == null)
{
Console.WriteLine("Birds birds = (Birds)animal;转换失败。");
}
else Console.WriteLine("Animal animal = new Birds();\r\nBirds birds = (Birds)animal;转换成功。");
}
}
- 堆栈的展示:
3、多态
① 方法多态性
- 重写、重载、隐藏
- 重写:子类用Override关键字重新实现定义在基类中的虚方法,并且在实际运行时根据对象类型来调用相应的方法。
- 隐藏:子类用new关键字重新实现定义在基类中的方法,但在实际运行时只能根据引用来调用相应的方法。其作用个人理解就是子类对父类的方法扩展私有化,不能被父类的引用变量调用,只能自己的引用变量才能调用。
public class Father
{
public virtual void Say()
{
Console.WriteLine("I am Father.");
}
}
public class Son : Father
{
//new关键字
public new void Say()
{
Console.WriteLine("new关键字方法:I am Son.");
}
//override关键字
/*public override void Say()
{
Console.WriteLine("override关键方法:I am Son.");
}*/
}
public static void Main()
{
Father father = new Son();
father.Say();
}
override关键字结果:调用子类的重写方法。根据对象类型来调用。
new关键字结果:隐藏了子类特有方法,调用父类的虚方法。根据引用类型来调用。
- 重载:拥有相同名字和返回值的方法却拥有不同的参数列表,实现多态。
② 对象多态性
③ 抽象
- 抽象类:
- 子类必须实现抽象类中的 抽象方法
- 抽象方法不能有方法体,因为要依靠子类实现。
- 对于非抽象方法:1、可以通过子类实例来调用。2、可以通过虚函数让子类重写或隐藏。
- 抽象类中可以定义方法的具体实现,调用方法如下:
public class test
{
public abstract class Animal
{
public abstract void Eat();
public void Walk(string name) { Console.WriteLine("I am {0},I am walking.", name); }
}
public class Cat : Animal
{
public override void Eat() //override
{
Console.WriteLine("I am Cat,I eat fish.");
}
}
public static void Main()
{
Cat cat = new Cat();
cat.Eat(); //调用子类实现的抽象方法
cat.Walk("Cat"); //调用抽象类中已实现的方法
}
2. 接口类:
- 接口的子类必须全部实现接口类方法和属性。
- 实现多接口继承:
public class test
{
public interface PaxingAnimal //爬行动物
{
void Walk();
}
public interface MiaoAnimal //猫科动物
{
void Voice();
}
public class Cat : MiaoAnimal,PaxingAnimal
{
public void Walk() { Console.WriteLine("I am a Cat, I am walking."); }
public void Voice() { Console.WriteLine("I am a Cat, Miao~~~."); }
}
public static void Main()
{
Cat cat = new Cat();
cat.Walk();
cat.Voice();
}