数据类型
五大数据类型
类型 | 例如 |
---|---|
类 | Window,form,console |
结构体 | Int32,Int64,Single,Double |
枚举 | HorizontalAlignment,Visbility |
接口 | |
委托 | action action<> func<> |
类
默认构造器会把值类型赋值为0,引用类型赋值为null。
当实例没有变量引用时,gc会将其视为内存垃圾,收回,收回会执行实例的析构器
//默认构造器
public Student()
{
}
//静态构造器
static Student()
{
Amount = 0;
}
public Student(int ID, String Name)
{
this.ID = ID;
this.Name = Name;
}
//析构器
~Student()
{
Console.WriteLine(this.Name + "执行析构器");
}
类声明的全貌
声明位置
命名空间外:这里属于全局命名空间,平时做小例子测试可以,项目上不要这样玩
命名空间外,类外:正常情况啦
类内:成员类(JAVA中的内部类),实际上用得很多
声明与定义细究
declaration声明,definitiondi定义,c#和JAVA中 声明即定义
但是C++中不一样,声明和定义是可以分开的(推荐),声明就像接口那样,没有实际实现,定义有实际的实现。
.h文件是类的声明
.cpp文件时类的定义
调用
若声明了但没有定义,编译器不会报错,但是运行后,一旦调用了这个没有定义的方法,链接器就会爆错
c++中声明和定义也可以合在一起
声明规范
[属性] [类修饰符] class 类名 [:基类名和任何实现接口的列表]
{
类体
}
【说明】
其中,类的属性(attributes)、类修饰符(class-modifiers)、基类(class-base)都是可选的。即最简单的一个类声明博阿阔3个部分:类类型声明(class)、声明的类名(identifer)和类体(class-body)。
类修饰符
-
new:仅允许在嵌套类中使用。表示所修饰的类会把继承下来的同名成员隐藏起来。比如,new 关键字修饰后,Print方法不是重写方法,而是子类中的新方法。父类调用Print方法的时候调用的时自身的Print方法而不是子类的方法
-
abstract:同C++一样,抽象类不允许建立类的实例,只能用作其他类的基类
-
sealed:密封类,不允许被继承,即该类不能做为其他类的基类。
-
static:静态类,仅包含静态成员,无法实例化,是密封的,不能包含实例构造函数。
下面时访问级别的修饰符
-
public:表示不限制对该类的访问
-
protected:表示只能从所在类和所在类派生的子类进行访问
-
internal:表示只能被本组合体(assembly,正常为本工程项目文件内)内所在类进行访问
-
private:只有对包.NET中的应用程序或库访问
-
类默认值为interal,字段和方法默认值为private,而访问级别internal和protected其实不存在谁大谁小
类的初始化顺序
- 子类静态变量
- 子类静态构造函数
- 子类非静态变量
- 父类静态变量
- 父类静态构造函数
- 父类非静态变量
- 父类构造函数
- 子类构造函数
静态构造器
特性
- 静态构造函数不使用访问修饰符或不具有参数。
- 类或结构只能有一个静态构造函数。
- 静态构造函数不能继承或重载。
- 静态构造器只执行一次
- 静态构造函数不能直接调用,并且仅应由公共语言运行时 (CLR) 调用。 可以自动调用它们。
- 用户无法控制在程序中执行静态构造函数的时间。
- 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数以初始化类。 静态构造函数应在实例构造函数之前运行。 调用(而不是分配)分配给事件或委托的静态方法时,将调用类型的静态构造函数。 如果静态构造函数类中存在静态字段变量初始值设定项,它们将在执行静态构造函数之前立即以在类声明中显示的文本顺序执行。
- 如果未提供静态构造函数来初始化静态字段,会将所有静态字段初始化为其默认值,如 C# 类型的默认值中所列。
- 如果静态构造函数引发异常,运行时将不会再次调用该函数,并且类型在程序运行所在的应用程序域的生存期内将保持未初始化。 大多数情况下,当静态构造函数无法实例化一个类型时,或者当静态构造函数中发生未经处理的异常时,将引发 TypeInitializationException 异常。 对于未在源代码中显式定义的隐式静态构造函数,故障排除可能需要检查中间语言 (IL) 代码。
- 静态构造函数的存在将防止添加 BeforeFieldInit 类型属性。 这将限制运行时优化。
- 声明为
static readonly
的字段可能仅被分配为其声明的一部分或在静态构造函数中。 如果不需要显式静态构造函数,请在声明时初始化静态字段,而不是通过静态构造函数实现更好的运行时优化。
用法
- 静态构造函数的一种典型用法是在类使用日志文件且将构造函数用于将条目写入到此文件中时使用。
- 静态构造函数对于创建非托管代码的包装类也非常有用,这种情况下构造函数可调用
LoadLibrary
方法。 - 也可在静态构造函数中轻松地对无法在编译时通过约束(类型参数约束)检查的类型参数强制执行运行时检查。
readonly
只读字段主要有以下几个要点:
- 只读字段可以在定义的同时赋值或者在类的构造方法中给其赋值;
- 除了构造方法外,其他地方不可以修改只读字段的值;
- 只读字段的属性只能有get访问器,不能有set,这是显而易见的。
- 由于值类型直接包含数据,因此属于
readonly
值类型的字段不可变。 - 由于引用类型包含对其数据的引用,因此属于
readonly
引用类型的字段必须始终引用同一对象。 该对象是可变的。readonly
修饰符可防止字段替换为引用类型的其他实例。 但是,修饰符不会阻止通过只读字段修改字段的实例数据。 - private没有set方法,不是只读属性,这只是外部无法访问,内部还可以访问(抛出的异常河readonly也不一样)
字段
字段发展为属性
Get/Set别样编写
直接输入属性名即可调用,无需getXX(),setXXX()
属性快捷声明
详细声明 propfull+tap tap,然后修改属性名+tap
简略声明(一般用于传递数据的类DOVO) prop+tap tap
类似JS的计算属性实现
方法一(推荐get很少使用的时候)
方法二 age在set时候计算CanWork(推荐age的set很少使用的时候)
常量
比如自己创建的类是无法作为常量的,这点好像和JAVA不一样,可以通过静态只读字段作为常量
继承
概念
继承是全面继承父类属性和方法(访问级别不允许的情况下无法调用,但是实例化内存中还是存在的。构造器不会被继承,需要手动继承),然后横向或者纵向扩展。横向增加对类成员在数量进行扩充,让类变得强大或者臃肿,类的数量不会变多,纵向是不增加类成员的数量,但是对类成员版本进行重写。(理不清)。
- 子类实例其实也是父类实例, ( 子类实例 is 父类)==true,父类实例不是子类实例( 父类实例is 子类)==false
- 父类作为变量引用子类实例,会无法调用子类方法,但是其属性数据保留
- 子类的访问级别不能超过父类,道理很简单,你继承人家的东西,自然要接受人家约束,不能超越。
- 子类会继承父类访问级别protected和public的方法的属性和方法。而internal,在不同项目中属性就不会继承,但是相同项目会继承。总之就是会继承访问级别内的东西。
- 子类其实是会继承父类的private方法和成员,在实例化子类时,会为这一部分分配内存,但是子类不可以调用父类的private成员和方法。
- protected和internal同时存在时,是或关系,不是同时关系,满足其一即可访问
using System;
namespace AnimalsAndPeople
{
class Program
{
static void Main(string[] args)
{
People people = new People() { Name = "周杰伦" };
Animals animals = new Animals();
Animals animalsRefPeople = new People() { Name = "许嵩" };
Console.WriteLine(animals is People);
Console.WriteLine(people is Animals);
animals.eat();
people.PlayComputer();
People p = (People)animalsRefPeople;
animalsRefPeople.eat();
p.PlayComputer();
people.UseAnimalsfun();
//继承父类的构造器,操作
People peopleExtendBase = new People("类型对应父类的构造方法");
Console.WriteLine(peopleExtendBase.Height);
Console.WriteLine(peopleExtendBase.Name);
}
}
class Animals
{
public String Name { get; set; }
public Animals()
{
this.Height = "Animals无参构造高度";
}
public Animals(String name)
{
this.Height = "继承父类的有参构造器高度";
this.Name = name;
}
public String Height
{
get
;
set;
}
internal virtual void eat()
{
Console.WriteLine("吃");
}
}
class People : Animals
{
//默认调用父类的构造方法
public People()
{
this.Height = "People高度";
}
//默认调用父类无参构造,若父类没有无参构造就会报错,除非手动继承
//继承父类的构造器操作
//这里两个name要相同,代表name是父类中的参数
public People(String name):base(name)
{
}
public void PlayComputer()
{
Console.WriteLine(this.Name + "玩电脑");
}
public void UseAnimalsfun()
{
Console.WriteLine(this.Height);
Console.WriteLine(base.Height);
}
}
}
重写
-
重写的方法,根据实例类型调用,也就是说只要实例时子类,哪怕引用变量是父类,调用的方法还是子类的方法
-
如果没有virtual和override,这就不是重写,是隐式隐藏(隐式编译器会报黄色错误,显式是加了new),隐藏意思是父类方法被隐藏了。这时候调用方法,会根据变量类型进行调用,如果变量类型是本身,就调用本身方法,如果变量类似是父类或者爷爷类(滑稽)以上的,就调用父类的。
-
除了第一次需要用virtual,后面的override自带了virtual的意思
-
注意父类方法需要可见的
-
属性get set的重写[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kEKhhWjK-1617609618499)(…/…/AppData/Roaming/Typora/typora-user-images/image-20210329160519575.png)]
-
多态基于重写机制
代差是指变量的类型和实例的类型不一致。python和js就是这样,变量是没有类型的,使用不存在不一致情况(这么说怎么觉得有点别扭),所以这些是没有多态的
结构体
int等价于Int32
在 C# 中的结构与传统的 C 或 C++ 中的结构不同。C# 中的结构有以下特点:
- 结构可带有方法、字段、索引、属性、运算符方法和事件。
- 结构可定义构造函数,但不能定义析构函数。而且不能自定义无参构造,无参构造会给所以字段赋值,在无参数的构造函数中为所有的字段赋值,值类型的字段赋值0,给引用类型的字段赋值null.
- 析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。
- 与类不同,结构不能继承其他的结构或类。
- 结构不能作为其他结构或类的基础结构。
- 结构可实现一个或多个接口。
- 结构成员不能指定为 abstract、virtual 或 protected。
- 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
结构体和类的主要区别
- 而结构体无法自定义无参构造函数,无参构造函数(默认)是自动定义的,且不能被改变,这也是无法赋初值的原因
- 结构体中不可以赋初值,但是常量可以赋初值(这不是废话吗)。成员变量赋初值其实相当于在构造函数中赋初值(编译器会这么编译)
- 类的方法外是无法运行执行代码的。
- 结构体不可以继承类
- 结构成员不能指定为 abstract、virtual 或 protected。
枚举类型
介绍
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
默认情况下,枚举成员的关联常数值为类型 int
;它们从零开始,并按定义文本顺序递增 1。 可以显式指定任何其他整数数值类型作为枚举类型的基础类型。 还可以显式指定关联的常数值,如下面的示例所示:
枚举类型 E
的默认值是由表达式 (E)0
生成的值,即使零没有相应的枚举成员也是如此(即如果你设置的不是从0开始,默认值还是会是0)
enum ErrorCode : ushort
{
None = 0,
Unknown = 1,
ConnectionLost = 100,
OutlierReading = 200
}
不能在枚举类型的定义内定义方法。 若要向枚举类型添加功能,请创建扩展方法。
作为位标志的枚举类型
如果希望枚举类型表示选项组合,请为这些选项定义枚举成员,以便单个选项成为位字段。 也就是说,这些枚举成员的关联值应该是 2 的幂。 然后,可以使用按位逻辑运算符|
或 &
分别合并选项或交叉组合选项。 若要指示枚举类型声明位字段,请对其应用 Flags 属性。 如下面的示例所示,还可以在枚举类型的定义中包含一些典型组合。
[Flags]
public enum Days
{
None = 0b_0000_0000, // 0
Monday = 0b_0000_0001, // 1
Tuesday = 0b_0000_0010, // 2
Wednesday = 0b_0000_0100, // 4
Thursday = 0b_0000_1000, // 8
Friday = 0b_0001_0000, // 16
Saturday = 0b_0010_0000, // 32
Sunday = 0b_0100_0000, // 64
Weekend = Saturday | Sunday
}
public class FlagsEnumExample
{
public static void Main()
{
Days meetingDays = Days.Monday | Days.Wednesday | Days.Friday;
Console.WriteLine(meetingDays);
// Output:
// Monday, Wednesday, Friday
Days workingFromHomeDays = Days.Thursday | Days.Friday;
Console.WriteLine($"Join a meeting by phone on {meetingDays & workingFromHomeDays}");
// Output:
// Join a meeting by phone on Friday
bool isMeetingOnTuesday = (meetingDays & Days.Tuesday) == Days.Tuesday;
Console.WriteLine($"Is there a meeting on Tuesday: {isMeetingOnTuesday}");
// Output:
// Is there a meeting on Tuesday: False
var a = (Days)37;
Console.WriteLine(a);
// Output:
// Monday, Wednesday, Saturday
}
}
枚举约束
System.Enum 类型是所有枚举类型的抽象基类。 它提供多种方法来获取有关枚举类型及其值的信息。 有关更多信息和示例,请参阅 System.Enum API 参考页。
从 C# 7.3 开始,你可以在基类约束中使用 System.Enum
(称为枚举约束),以指定类型参数为枚举类型。 所有枚举类型也都满足 struct
约束,此约束用于指定类型参数为不可为 null 的值类型。
下面的where是约束了T的范围,范围是T必须是枚举类型
public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
var result = new Dictionary<int, string>();
var values = Enum.GetValues(typeof(T));
foreach (int item in values)
result.Add(item, Enum.GetName(typeof(T), item));
return result;
}
Enum.GetValues
和 Enum.GetName
使用反射,这会对性能产生影响。 可调用 EnumNamedValues
来生成可缓存和重用的集合,而不是重复执行需要反射才能实施的调用。
enum Rainbow
{
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
}
如以下示例所示,可使用它来创建枚举并生成其值和名称的字典:
var map = EnumNamedValues<Rainbow>();
foreach (var pair in map)
Console.WriteLine($"{pair.Key}:\t{pair.Value}");
与其他数值的显式转换
对于任何枚举类型,枚举类型与其基础整型类型之间存在显式转换。 如果将枚举值转换为其基础类型,则结果为枚举成员的关联整数值。
public enum Season
{
Spring,
Summer,
Autumn,
Winter
}
public class EnumConversionExample
{
public static void Main()
{
Season a = Season.Autumn;
Console.WriteLine($"Integral value of {a} is {(int)a}"); // output: Integral value of Autumn is 2
var b = (Season)1;
Console.WriteLine(b); // output: Summer
var c = (Season)4;
Console.WriteLine(c); // output: 4
}
}
循环与遍历
break和continue都只影响一层循环,continue只会跳出一次循环
for(;;)相当于while(true)
拥有foreach()的应该都实现了IEnumerator接口,拥有GetEnumerator()方法,foreach是迭代器的简记法(简单写法)
其他
动态(Dynamic)类型
您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。下面输出结果是String 和Int32
dynamic d = "asd";
Console.WriteLine(d.GetType().Name);
d = 1;
Console.WriteLine(d.GetType().Name);
指针类型(Pointer types)
指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。
声明指针类型的语法:
type* identifier;
var自动适配
会根据实例对象自适应变化,但是只会变化一次