Crystal‘s Unity Notes:C#基础知识
命名空间
namespace 可以看做是一个文件夹
- 一个文件中可以存在多个命名空间
- 命名空间中可以再嵌套命名空间
- 命名空间的取名规范依照标识符的取名规范
using(使用程序引用集) 后面其实也是命名空间 (系统写好的命名空间)
using System;
using System.Collections.Generic; //嵌套的命名空间
using UnityEngine;
C#语言是一门完全面向对象的语言 (在面向过程中Main函数是主函数,也是程序的入口)
第一个函数
Console.WriteLine("Hello World!"); //输出Hello World!
输入函数 (同时也是阻塞函数 执行到这里就等待用户输入)
Console.Read();
数据类型的分类:
- 引用类型:数组、字符串、类类型、接口、委托(事件)
- 值类型:数值类型、字符类型、结构体、枚举、Boolean类型
值类型:
1.数值类型:
(1)整数类型:按照字节大小来划分 1byte = 8bit
一个字节 | byte(无符号) | sbyte(有符号) |
两个字节 | short(有符号) |
ushirt(无符号) |
四个字节 | int(有符号) |
uint(无符号) |
八个字节 | long(有符号) |
ulong(无符号) |
(2)浮点类型:通过减法进行浮点数的判断 差值小于0.001近似相等
单精度浮点数(没有符号类型) | 四字节 | float ( float float_number = 3.14f ) |
双精度浮点数: |
八字节 |
double(double double_number = 3.14 || double double_number = 3.14d ) |
完全精确的小数类型 |
十六字节 | decimal (先将数据类型转换为字符传进行存储 使用时再转换为数值类型) |
2.字符类型:
char 两字节。
1.char赋值的数据是用 ' ' 括起来的(使用单引号'A')
2. ' ' 中括起来的数据不仅仅只有1位数据 ( 'NUT' )
3.char类型可以跟整数类型之间进行转换 ASCII码
4.字符串类型中的每一个元素都是char类型(字符串类型可以看成是一个字符数组)
3.结构体:
struct(结构体关键字) 结构体名字 {属性;属性……;构造函数;方法}
4.枚举:
enum(枚举关键字) 枚举的名字 {内容1,内容2....};
5.Boonlean类型:
bool 一个字节(true和false)
在C#中0和1不能代表true和false
数据类型之间的转换:
1.数值类型之间的转换
隐式转换:没有进行强制类型转;将储存范围小的转换为储存范围大的。
显示转换:将储存范围大的转换为储存范围小的,如果转换的数值需要的储存空间大于数据类型的储存空间,转换不准确。
2.字符串转换为数值类型
前提:字符串内只能是一个具体的数值,不能包括其他的字符,首位空格除外。
type.Parse(string)
Conver.To...(string)
3.枚举转换为数值类型
显示转换:type num = (type)枚举类.内容
值传递和引用传递
值类型:数值 布尔 字符 枚举 结构体...
引用类型:类 数组 字符串 接口 委托(事件)...
值传递:将变量的值复制一份到函数值进行操作
引用传递:将变量本身传递到函数中
在函数间传递值类型的参数时,如果需要对实参中的数据作出改变的话,就得加上ref关键字,将值传递变为引用传递
函数:特定功能块
返回值 函数名(参数列表){函数体}
当一个函数不需要返回值的时候为void,如果需要返回值的用对应的类型
一个简单的输出函数
int a = 666;print(a); //666void print(int a)
{Console.WriteLine(a);
}
函数的参数:ref out params 默认参数
ref:reference 引用
out:让一个函数拥有多个返回值
- ref和out修饰的参数,在实际调用的时候,需要在对应的实参前把对应的关键字加上
- ref关键字修饰的参数,实参传递之前必须先初始化好
- out关键字修饰的参数,在函数返回之前对out修饰的参数进行赋值。
- params修饰的参数可以实现一个不定长度的参数列表出来
- params修饰的参数一定是放到参数列表的末尾
- 默认参数在具体使用的时候可以不用给实参,但是不传递实参的时候将会用这个参数默认给的值
- 默认参数必须放到所有非默认参数后
这是一个简单的交换数值代码
int num1 = 5;
int num2 = 3;int num3 = num1;
num1 = num2;
num2 = mum3;Console.WriteLine(num1); //3
Console.WriteLine(num2); //5
写成函数如下
int num1 = 5;
int num2 = 3;Swap(num1,num2);Console.WriteLine(num1); //5
Console.WriteLine(num2); //3void Swap(int num1,int num2)
{ int num3 = num1;num1 = num2;num2 = mum3;
}
实际上输出的还是5 和 3;并没有发生交换
因为函数的形参和实参的传递实际上是值传递 相当于函数新创建的两个变量分别接受了外界调用所传的值 交换实际上只是函数内两个变量的交换
可以改变函数成这样 在函数体输出
int num1 = 5;
int num2 = 3;Swap(num1,num2);void Swap(int num1,int num2)
{ int num3 = num1;num1 = num2;num2 = mum3;Console.WriteLine(num1); //3Console.WriteLine(num2); //5
}
或者改变为引用传递 (加上ref关键字变成引用传递 传址 共用一个地址 其一发生改变另一也改变)
int num1 = 5;
int num2 = 3; Swap(ref num1,ref num2);Console.WriteLine(num1); //3
Console.WriteLine(num2); //5void Swap(ref int num1,ref int num2)
{ int num3 = num1;num1 = num2;num2 = mum3;}
函数的重载
函数的返回值不变,函数名不变,
仅仅是参数列表不同,那么就构成了函数的重载
数组 :将相同数据类型的数据同 意存放在一个有序的集合。
1.数组是连续的内存空间;
2.数组的长度时没法改变;
3.数组的元素需要通过下标,从0开始;
4.数组的长度和元素的个数是一致的。
定义数组的方式(以整数类型int为例)
int[] nums = { 1, 2, 3 };// 声明了数据类型,有一个数组名,有具体的元素字符串
int[] nums2 = new int[10];//声明的数据类型,有数组名,但是只声明了数组长度,没有给到具体的数据
int[] nums3 = new int[] { 10, 20, 30, 40, 50 };//除了长度以外都声明
定义二维数组的方式:
int[,] arr = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
int[,] arr2 = new int[2, 3];
int[,] arr3 = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
数组函数:
arr2.Length //总长度 所有元素:
arr2.GetLength(0) //行数:
arr2.GetLength(1) //列数:
arry[所在行数 ,所在列数] //访问具体元素:
//常用的函数:Sort、Reverse、Clear、Exists、IndexOf等
枚举
枚举类型就是用来列举一种类型可能出现的几个选项
- 枚举类型具有自解释性(枚举的名字叫什么就可以看的出来是做什么)
- 枚举类型还有限制性(定什么就只能用什么)
- 枚举类型的名字实际上就是一个数据类型
- 枚举类型可以给枚举类型中的元素进行数据类型的设定,如果没有做特别设定(在枚举类型名字后:类型),那么默认的数据类型的int
- 枚举类型所占的字节数跟设定的数据类型的字节数相同
用enum关键字定义一个枚举
enum Sex
{man, //0woman, //1
}Console.WriteLine(Sex.max); //0
结构体(struct)
为了去管理不同数据类型
结构体还可以有自己的行为(函数)
struct Player
{ //加public外部才可访问public string name; //名字:stringpublic float height //身高:floatpublic Sex sex; //性别:上面写的枚举public int age //年龄:int
}//构造函数 如果一个结构体显示的写出了构造函数,那么构造函数中必须带参数并且必须对结构体中的所有变量进行完全赋值
public Player(string name,int height,Sex sex,int age)
{this.name = name; //this代表结构体本身 前一个name是上面初始化的name 后一个name是传入的参数namethis.height = height;this.sex = sex;this.age = age;
}
构造函数对应结构体来说,一旦显示的写出了构造函数,那么必须是带参数的
带参的构造函数都必须对结构体中所有的变量进行完全赋值
面向过程:分析出解决问题的步骤,然后再用函数逐步实现每个步骤,使用的时候依次调用函数。
面向对象:将数据和数据操作的方法放在一起,作为一个相互依存的整体(对象),对同类对象抽象出共性,形成类。类通过一个简单的外部接口和外界发生联系.
类: 高度抽象的具体有一些统一特征的事物
对象:对于类的实例(实际存在的才叫对象)类看成一个模板,对象是这个模板下比较具体的事物
类
Class People
{public string name;//构造函数 可不带参数 可不完全赋值 可用来初始化数据...public People(){}
}
类和结构体的区别
类 | 结构体 | |
类型不同 | 引用类型 | 值类型 |
赋值传递不同 | 引用传递(类似指针 传的是地址) | 值传递 |
构造函数不同 | 类中的构造函数可以不用给所有的属性(字段)进行完全赋值 | 结构体中的构造函数必须要对结构体中所有的实例(非静态)属性(字段)进行完全赋值 |
类中的构造函数可以没有参数 | 结构体中的构造函数必须要有参数 | |
初始化操作 | 类中的成员变量可以直接进行初始化的操作 (类开始可以直接赋值) | 结构体中的变量成员不能直接进行初始化的操作(结构体不能直接赋值)(静态字段除外) |
结构体的赋值
class Program
{static void Main(string[] args){People people1 = new People(666);People people2 = people1; //值传递(结构体) 复制了一份给people2了 people1.num = 222; //修改的数据只是people1自己的 跟people2无关Console.WriteLine(people1.num); //222Console.WriteLine(people2.num); //666 }
}struct People
{public int num;public People(int num){this.num = num;}
}
类的赋值
class Program
{static void Main(string[] args){ Animal animal1 = new Animal(666);Animal animal2 = animal1; //引用传递(类) 把自己的地址给animal2了animal1.num = 222; //修改了animal1的值后 因为地址一样 所以animal2的值也改变了//animal2.num = 222; //同理 因为共用的是一个地址Console.WriteLine(animal1.num); //222Console.WriteLine(animal1.num); //222 }
}class Animal
{public int num;public Animal(int num){this.num = num;}
}
引用类型之间的 == 比较的两个变量对应的内存地址是否一样
值类型的 == 比较的里面的值是否一样
ps:string是引用类型 string == 可以进行判断,是用来比较两个值是否相等(特殊)
类比起结构体的最大优势是多了两个特征:继承和多态
为什么还要有结构体,什么情况下才能用结构体?
1、运算量比较大的时候
2、需求空间效率更高的事物和需求内存释放更快的事物
面向对象的三大特性:封装、继承、多态
封装:隐藏内部实现,稳定外部接口
对外提供可访问的属性和方法。外部对象必须通过这些属性和方法访问此对象的信息。最主要是通过访问修饰符来控制, 使用访问修饰符对类中的元素进行了保护, 成为类的封装性。
public(公有的) | 本类的成员以及其他类的成员都可以进行访问 |
private(私有的) | 只有本类的成员可以访问 |
protected(受保护的) | 本类和继承的类可以访问 |
internal | 当前程序集可以访问 |
构造函数:
构造函数是类的一个特殊方法,在类中自动运行
特点:
- 构造函数没有返回值,并且不能加void,可初始化成员变量
- 构造函数的名字都必须和所要构造的对象名字相同
- 系统会自动帮我们创建一个不带任何参数的构造函数
- 可以自己创建一个带参数的构造函数
public MyClass(string name)
{this.name = name;
}
new关键字可以实例化一个新的对象, 制定对某个带参数的构造函数。
MyClass myclass = new MyClass("小明");
静态成员(static)
static关键字:静态 用static修饰的成员称作静态成员 无论对象是什么都不会发生变化
- 在类中用static修饰的成员属于类本身而不是对象(只能通过类本身调用)
- static修饰的成员具有唯一性 (类名在当前的命名空间是唯一的 所以static修饰的也是唯一的)
- 静态方法(函数)中所使用的只能是 静态成员 (如Main函数中只能调用静态成员)
const和static的区别:
const修饰的成员也是一个静态成员,但const修饰的只能是变量 不能是函数
const修饰的变量后续不能再发生变化(常量),但是static修饰的变量可以发生变化
当一个类中有有了静态成员,系统会自动生成一个静态的构造函数,静态的构造函数就是用来给类中的静态变量进行赋值
- 可以自己写一个静态的构造函数,但是不能有访问修饰符
- 显示的静态的构造 函数不能有参数
- 静态的成员不能由用户调用,只能是系统调用
- 在调用自己的构造函数前,如果有静态的构造函数,会先调用静态构造函数
- 静态构造函数只会被调用一次,在第一个对象成员声明前或第一个静态成员被调用前
静态类:
static修饰的类称之为静态类 静态类中所有的成员都是静态成员或常量成员
静态类还是一个密封类(使用关键字sealed修饰的类被称之为密封类,这个类无法产生派生类)
属性
如果一个类中的字段(变量)可以给其他的地方访问 但是不允许或要授权了才让其他地方修改怎么办
如果那个字段是public 那么外部即可访问又可修改 如果是private 外部既不可访问又不可修改 若是变成常量const 那么此变量只属于类了 创建出来的对象又无法访问
那么就用属性访问
class Person
{ //外部无法获得你的年龄private int age;//属性public int Age {get //实际在编译的过程中,会将自己转换为一个函数{return age;}set{age = value; //value实际上是set访问器在编译过程中变成的函数的形参} }
}
属性访问实际在编译的过程中会将自己转换为一个函数
实际被转换的函数(系统内部):
public int get_Age()
{return this.age;
}
public void set_Age(int value)
{this.age = value;
}
如果只有一个get访问器,那么称之为只读属性
如果只有一个set访问器,那么 称之为只写属性
不会存在都没有的情况 否则访问器会报错 并且也没有意义
所以大部分情况下用的都是仅get 让外部只能获取内部的数据 不让外部修改
ps.选中字段(变量)点击右键 选择重构 封装字段(或者选中之后Ctrl+R,E)可以自动封装
外部如何使用呢?
Person p = new Person(); //创建对象
int num = p.Age; //p.Age 在有get方法时使用直接点属性的方法可以获取到get下面的值
p.Age = 18; //在有set方法时使用直接点属性赋值的方法可以对里面的值进行修改(value = 18)
自动属性 (简便写法)
在程序编译的时候,会自动生成一个新的变量,get和set访问器会与这个变量关联起来
public int height{ get; set;}
//如果不想外部修改可以改成 那么只能获取
public int height{ get; private set;}
但是比起属性而言 get和set必须都存在 若不想被修改可以再前面加上private
readonly
readonly修饰的字段称之为只读字段,只读字段只能在构造函数或者字段初始值设定项赋值
(字段初始化赋值本质上也是要经过构造函数赋值)
class Test
{public readonly int test = 0;public Test(){test = 5;}
}
readonly只能用来修饰字段,无法修饰方法
readonly和const的区别
readonly关键字可以和static一起使用 但是const关键字无法和static一起使用(const也是静态)
const修饰的成员只能进行初始值设定项赋值 而readonly修饰的成员还可以在构造函数
单例模式:让代码的安全性更高,让一个类只会有一个对象存在
class Singleton{//通过访问修饰符控制该变量只能在本类中访问//static使得该变量在本类中唯一 设置为空值 private static Singleton _instance = null; //引用类型的默认值都是null //私有的构造函数 实现了本类无法从外部实例化(外部无法调用此构造函数,不能new它)private Singleton(){}//静态访问器:它属于类,也就是说可以直接通过类名调用(static)public static Singleton Instance{//只有get表示:这个访问器是只读的,同时_instance这个属性也是只读(没有set 不能被外部修改)get{//在实际调用前,先判断是否是第一次调用if(_instance == null){//第一次调用的话,在本类中调用构造函数_instance = new Singleton();}//返回_instance供外部使用return _instance;}}}
静态成员变量保证了它的唯一性 只能通过类名调用 但是private使他私有化 别的类无法访问
私有构造函数保证了他的安全性 使他不能在外部实例化对象
但是若想访问到当前类 只能用对象来访问 (如果当前类还有静态成员 那么单例模式就失去了意义)
所以只能用到上面定义的 在本类中实例化的对象 然后通过静态访问器 即可通过类名调用该访问器 然后返回出在本类实例化 的唯一对象 只有一个get方法使他只能被访问
在外部可以用类名.访问器得到该对象(Singleton.Instance) 即可访问类中的成员
*析构函数
~Person()
{Console.WriteLine("这是一个析构函数");
}
析构函数的特点
- 析构函数前不能加访问修饰符
- 析构函数不能加参数
- 析构函数不能由用户自己去调用,而是由系统调用(最后)
- 析构函数只有等内存的生命周期到了才会释放
继承:
一个类会得到继承到的类中所有的字段 属性 方法 (把上一个类中的所有成员全部给到下一个类)
继承的好处:提高代码的复用性(偷懒)
被继承的类称为基类(父类),继承出来的类被称为派生类(子类)
访问权限修饰符 再继承中的效果 | |
public | 公有的,被继承了之后,可以在外部调用 |
private | 私有的,只允许在自己的类中内部调用,被继承了,子类也没有权限进行调用 |
protected | 受保护的,只能在自己类中和子类中进行访问 (其他类的对象也无法访问) |
class Father
{int age = 18;private string name = "小明";protected int height = 185;public int money = 666;
}class Son : Father //:加类名即可继承
{}
Son son = new Son();
Console.WriteLine(son.age); //报错! 没有访问修饰符只能在当前类访问
Console.WriteLine(son.name); //报错! 私有的成员被继承了没有访问的权限
Console.WriteLine(son.height);// 185 proteced修饰的可以在当前类和子类中使用
Console.WriteLine(son.money); //666 public修饰的外界可以使用
有两个东西不能被继承:构造函数(ps:可以有个空壳) 析构函数
*密封类无法被继承 静态类也是密封类(静态类里面的全是静态成员 可以直接访问 无继承意义)
继承中的构造函数
如果一个类拥有父类,那么调用子类中的构造函数的时候会先去调用父类的构造函数
class Father
{public Father(){Console.WriteLine("这是父类的构造函数");}
}
class Son : Father
{public Son(){Console.WriteLine("这是子类的构造函数");}
}Son son = new Son();
//这是父类的构造函数
//这是子类的构造函数//若都有静态构造函数 那么会先调用自己的静态构造函数 再调用父类的静态构造函数
如果子类调用自己的构造函数的时候,没有去指定调用父类中的哪个构造函数,那么必定调用的是父类中不带参数的构造函数
如果子类调用自己的构造函数的时候,希望能调用父类中确定的构造函数,那么可以用:base(参数)通过具体的参数来去指定
比如父类中有个带参数的构造函数 若子类想去调用它的话可以写成这样
public Son(int num):base(666)
{//Console.WriteLine(Son.num)
}
is关键字
is关键字就是用来判断一个类型是否是另一个类型 或者是另一个类型的派生类 返回一个bool值
class People
{
}
class Man : People
{
}
class Child : Man
{
}
Child child = new Child();
Console.WriteLine(child is Man); //TRUE
Console.WriteLine(child is People); //TRUE
Console.WriteLine(child is Child); //TRUEMan man = new Man();
Console.WriteLine(man is Child); //FALSE
as关键字
as关键字和强转之前的区别
as关键字使用的时候如果没有转换成功,那么不会直接报错,但是会将null赋值过去
强转如果没有转换成功的话,那么会直接报错
//Chinese类和Japanese都继承了People类
People[] peoples = new People[2];
peoples[0] = new Chinese();
peoples[1] = new Japanese();
//可以用父类的对象存储子类的对象,这是一个隐式转换Console.WriteLine(peoples[0].name); //这是访问的是People类的 因为是是父类的对象
Console.WriteLine((peoples[0] as Chinese).name); //as关键字 访问的是Chinese的
Console.WriteLine( ((Chinese)peoples[0]).name); //API强转 访问的是Chinese的Console.WriteLine(((Chinese)peoples[1]).name); //报错!没有这个属性 因为peoples[1]不是Chinese
Chinese chinese = (peoples[1] as Chinese); //这里没有报错 但是用的as关键字 chinese是null
Console.WriteLine(chinese.name); //报错!chinese中没有这个属性
引用类型之间的数据类型转换 (必须这两个类型之间有继承关系)
隐式转换:从派生类的对象转换为基类的对象的时候,那么就是隐式转换
显示转换:从基类的对象转换为派生类的对象的时候,那么就是显示转换
如果两者之前没有继承关系 是绝对不可能转换的
接口
比如有个飞机类 可以继承交通工具类 但比交通工具类多了一个功能 可以飞 那么就要继承鸟类
但是鸟类的很多属性飞机类都用不到 只想使用飞这个功能 并且C#不存在多重继承
一个类只能继承一个类 但是可以继承多个接口 接口跟类的区别就是接口只能有行为,并且是高度抽象的行为(不用实现的方法(函数))
接口是一种数据类型 里面只有行为的高度抽象的一种事物,用来辅助实现多重继承
- 使用interface关键字
- 接口取名字时,开头大写I再大写对应的单词
- 接口中的方法仅有声明,没有函数体,也没有访问修饰符
- 如果一个类继承了基类,那么也要继承接口
可以继承于接口的数据类型:类,结构体,接口
类写在前面,接口写在后面,中间用逗号隔开,且可以继承多个接口
//如果一个类继承了接口 那么就必须把接口中的成员全部实现一遍class Plane : Vehicle, IFly{//隐式实现接口 对应的函数前必须要加public访问权限修饰符 并且只能是publicpublic void Fly() //当两个接口有同名方式时,会产生二义性(继承多个接口的方法名一致){Console.WriteLine("飞起来了")} //显示实现接口 那么对应的函数前不能加任何访问权限修饰符void IFly.Fly() //方便区分是哪个接口中的方法{Console.WriteLine("飞起来了")}}interface IFly //接口中所有的方法 只能声明 不能实现{ //在接口中,函数的声明前不要去加任何访问权限修饰符 访问修饰符对接口无效void Fly(); //函数的声明(因为没有去写具体的函数内容(函数体),所以实际上就是一个抽象的函数(抽象函数))}
结构体不能继承于类 也不能继承于结构体 但是可以继承于接口
struct Person : Fly
{public void Fly(){Console.WriteLine("飞");}
}
接口也能去继承接口 拥有父接口的所有成员
//此接口被继承时 继承者可以获得此接口以及父接口的所有成员
interface ISuperFly : Fly
{void SuperFly();
}
接口中包括的成员:
函数的声明, 属性的声明 ,索引器的声明,事件的声明
interface IFly
{//函数的声明void Fly();//属性的声明 属性在接口中的写法,get;set;分别为两个访问器的声明( set(),get() )int Speed { get; set; } //绝对不是自动属性!!! 可以只写一个 (自动属性必须都写)//索引器的声明//事件的声明
}
多态:
多态是继承的扩展 不同的事物(需要有同一个父类或者接口),在做同一件事表现出不同的形态
上方的继承中提到 若是通过这种方式调用的话其实调用的就是父类的方法 若想调用子类的方法必须进行强转或者as关键字
//Chinese类和Japanese都继承了People类 People people = new People; //用父类的对象存了子类的对象 用new关键字申请了Chinese的内存 people = new Chinese();Console.WriteLine(people.name); //People类的name 因为是父类的对象 Console.WriteLine((people as Chinese).name); //as关键字 访问的是Chinese的
那么除了这种方法还有一种方法就是把父类写成虚函数 子类写成重写函数对他进行重写
虚函数:用virtual关键字声明的函数。
- 父类中和子类如果有同名且同函数签名一致的函数(返回值类和参数列表)
- 如果在调用父类的对象时,想要使用自己子类中的对应函数, 就要把父类中的函数写成虚函数
- 如果父类中有虚函数 那么子类就可以使用override关键字进行重写这个虚函数
最开始的父类中对应的函数必须是一个用virtual修饰的虚函数
Animal[] animals = new Animal[2];animals[0] = new Dog();animals[1] = new Cat();for (int i = 0; i < animals.Length; i++){animals[i].Call(); //狗叫 猫叫}Cat[] cats = new Cat[2];cats[0] = new BlackCat();cats[1] = new WriteCat();for (int i = 0; i < cats.Length; i++){cats[i].Call(); //黑猫叫 白猫叫}//创建自己的对象调用的还是自己的函数 因为内存中没有子类的对象Animal animals1 = new Animal();animals1.Call(); //动物叫//动物类class Animal{public virtual void Call(){Console.WriteLine("动物叫");}}//狗class Dog : Animal{//如果父类中使用了虚函数,那么子类就可以重写这个虚函数 overridepublic override void Call() //重写{Console.WriteLine("狗叫");}}//猫class Cat : Animal{//如果父类中使用了虚函数,那么子类就可以重写这个虚函数 overridepublic override void Call() //重写{Console.WriteLine("猫叫");}}//黑猫class BlackCat : Cat{public override void Call() //重写{Console.WriteLine("黑猫叫");}}//白猫class WriteCat : Cat{public override void Call() //重写{Console.WriteLine("白猫叫");}}
狗叫
猫叫
黑猫叫白猫叫
动物叫
请按任意键继续. . .
调用父类类型对象的函数 因为是父类类型 那么能找到的范围就只能在父类中 如果父类函数加了virtual关键字 那么这个函数将会变成虚函数 使他变的很抽象 在调用这个方法的时候 如果内存中子类的对象有同名且签名一致 并且加了override关键字的(相当于变成了一个实体) 那么就会调用该重写函数 如果没有找到满足此条件的函数 才会调用父类自身的虚函数
虽然使用的是父类的对象 但是调用的是子类的函数 *在C++语言中被成为函数指针的重定向
如果父类中的虚函数,在子类中没有用override关键字进行重写这个函数,实际上是隐藏了一个new关键字(隐藏) 父类就会永远都找不到此函数 就不会重定向到这里
注意
1. virtual / override关键字不能和static关键字一起使用!因为static修饰的函数属于类不属于对象
2. override修饰的函数本质上还是一个虚函数,可以在它的子类中继续去重写,虽然后面可以用override重写,但是第一个父类的函数
*里氏替换原则
因为继承带来的入侵性,增加了耦合性,也降低了代码灵活性,父类修改代码,子类也会受影响,此时就需要里氏替换原则
1.子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法
2.子类中可以增加自己特有的方法
3.当子类覆盖或实现父类的方法时,方法的前置条件(形参)要比父类方法的输入参数更宽松
(如果名字都一样 肯定会调用父类的 这个时候会有二义性 如果加入形参 调用的就是形参的)
4.当子类的方法实现父类的抽象方法时,方法的后置条件(返回值)要比父类更严格
(如果父类返回的是一个对象的话 那么子类返回的应该是比较精确的对象)
抽象类:
用abstract关键字修饰的类称之为抽象类
抽象类不能被实例化 ,只能被继承 (因为抽象)
抽象类中的行为,会有很多的抽象行为(抽象函数)
抽象行为类似于接口中的行为,只对抽象函数进行声明,不能定义
abstract class Animal
{//抽象函数是没有方法体的函数(函数的声明)abstract public void Call(); //抽象函数(纯虚函数)
}
抽象类里面除了有抽象函数以外,还可以拥有非抽象类中的所有成员
非抽象类中不能包含抽象函数
在实现抽象函数的时候,用override关键字,抽象函数也是虚函数 (虚函数有方法体)
class Cat : Animal
{public override void Call(){//}
}
如果一个类继承了抽象类 那么就必须去实现抽象类中的抽象方法 (用父类约束子类必须实现)
虚函数可以被派生类用override关键字重写 也可以用new关键字将重写函数隐藏起来 但是abstract就可以强制派生类必须重写 abstract最大作用就是一种强制行为
抽象类就是用来强制要求派生类中必须实现抽象类中的抽象成员的一种强制做法
抽象类是最适合做基类 (父类) 的类
抽象类和接口的区别:
1、抽象类是类类型,接口是接口类型
2、抽象类只能被单一继承,不能被多重继承,而接口是可以被多重继承
3、抽象类可以拥有类中所有的成员,接口只能有函数的声明
数据结构
泛型、索引器、list容器
泛型:
如果要写一个交换数值的函数 比如这样的 (真正交换值得用ref关键字)
static void Swap(int a,int b)
{int c = a;a = b;b = c;
}
//调用
int a;
int b;
Swap(a,b)
但是这仅仅只是int类型的交换 如果要交换float string object...等等 要写那么多重载吗
因为有时候要将一个代码封装的足够安全,那么可能需要重载很多次才能做到,写代码来说效率低
那么需要自定义一种数据类型,用这种数据类型来泛指其他数据类型,这个数据类型就称之为泛型
泛型函数在调用的时候必须给定具体的数据类型
static void Swap<T>(T a,T b)
{T c = a;a = b;b = c;
}
//调用
Swap<int>(5,3); //交换两个int类型的数据 那么T就是int类型
Swap<float>(5.2f,2.5f); //交换两个float类型
泛型类
在类名后添加<T>的形式的类称之为泛型类
泛型类中的字段和方法如果需要用泛型,那么就可以不需要额外再去写出
class Test<T>
{T test1;T test2;//泛型类和泛型函数中也可以存在正常的数据类型int test3;public void DoTest(T test1){ }
}
泛型类中的构造函数,不需要重新写出泛型,
但创建一个泛型对象,在调用构造函数的时候要给出具体的数据类型
class Test<T>
{ public Test() //不需要写泛型{ }
}
//实例化要给出具体的数据类型
Test<int> test = new Test<int>();
泛型约束:
用where关键字约束
约束一个泛型是值类型 struct
*大部分的值类型都是用struct封装出来的(int,float,bool...) 所以使用用struct来约束
class Test<T> where T : struct //约束一个泛型是值类型
{ public Test() { }
}Test<int> test = new Test<int>(); //没问题
Test<string> test1 = new Test<string>(); //报错 因为已经把泛型T约束成值类型了
约束一个泛型是引用类型 class
class Test<T> where T : class//约束一个泛型是引用类型
{ public Test() { }
}Test<string> test1 = new Test<string>(); //没问题
Test<int> test = new Test<int>(); //报错 因为已经把泛型T约束成引用类型了
将泛型约束成规定的数据类型,只能约束成值类型,引用类型,不能约束具体的类型(int、float、string)
直接约束类名:
使传进来的 必须是约束的这个类本身 或者继承于这个类的 (继承可以获得父类所有属性)
public static void Get<T>(T t) where T : People
{t.Say(); //只有约束了People才可以使用People的方法
}
直接约束接口名:
使传进来的 一定实现了这个接口的
public static void Get<T>(T t) where T : IFly
{t.Fly(); //只有约束了IFly才可以使用IFly的方法
}
直接约束class :
使传进来的 一定是个引用类型
public static void Get<T>(T t) where T : class
{T tValue = null; //只有约束了class才可以使用引用类型的方法 因为引用类型才可等于null
}
直接约束struct :
使传进来的 一定是个值类型
public static void Get<T>(T t) where T : struct
{T tValue = default(T);
}
构造函数约束new():
使传进来的 必须有一个无参构造函数,
public static void Get<T>(T t) where T : new()
{T t = new T(); //只有约束了new()才可以这样写
}
可以和class一起约束,不能和struct一起
约束之间是且关系 可以约束多个
使能用这个方法的 必须是People或是其子类 并且实现了IFly接口 并且还有个无参构造函数
public static void Get<T>(T t) where T : People, IFly, new()
{T t = new T(); //只有约束了new()才可以这样写
}
约束顺序:class/struct -> 类/接口 -> new();
struct适用情况:需要效率
泛型可以有多个
索引器:
1、让类像数组一样通过特定的方式被访问;
2、可以让访问形式变成只单一的访问单一元素,安全性高;
修饰符 返回值 this[]
索引器和属性的区别:
1、属性是需要有标识符,索引器只有this;
2、属性不需要参数,索引器至少要有一个参数(参数类型可以是:泛型、值类型,引用类型);
属性访问:不安全
容器
容器
委托
委托
Crystal‘s Unity Notes:C#基础知识相关推荐
- Unity游戏开发基础知识(新手必看)
内容会持续更新,有错误的地方欢迎指正,谢谢! 0.Unity最大的优点 unity提供的最大优点就是跨平台. 以前项目移植很麻烦,现在只要一份代码,然后注意平台差异就好了. 1.灯光类型 平行光:Di ...
- Unity入门 ---- unity2D基础知识
Unity入门 DAY 一: 向量Vector 向量夹角 物体的指向 屏幕坐标 DAY 二 鼠标事件处理 探测鼠标事件 鼠标跟随事件 鼠标拖拽 事件函数 Event Functions 脚本的执行顺序 ...
- Crystal‘s Unity Notes:Unity2D
用Unity做2D:新建项目时无需特地点击2D模板: 1.将Unity改为2D: Edit=>Project settings =>Default Behavior Mode:2D: ...
- Unity基础知识汇总
2.Unity相关知识 2.1 Unity介绍 Unity成为一款可以轻松创建游戏和三维互动的开发工具,是一个专业跨平台游戏引擎 Unity操作快捷键 Ctrl N New Scene 新建场景 Ct ...
- Unity基础知识学习七,帧同步源码学习
前言 在学习帧同步框架源码之前,先过一遍基础知识:Unity基础学习六,网络同步_u013617851的博客-CSDN博客 视频地址:帧同步教程[合集]_哔哩哔哩_bilibili github地址: ...
- 《Unity 3D 游戏开发技术详解与典型案例》——1.1节Unity 3D基础知识概览
本节书摘来自异步社区<Unity 3D 游戏开发技术详解与典型案例>一书中的第1章,第1.1节Unity 3D基础知识概览,作者 吴亚峰 , 于复兴,更多章节内容可以访问云栖社区" ...
- 游戏开发unity基础知识系列:(一)unity 2019 下载与安装
游戏开发unity基础知识系列:(一)unity 2019 下载与安装 声明:未经作者允许,严禁商用,转载请标明出处和来源,谢谢 零.前言 本人在unity2d方面使用较多,关于unity的使用后面预 ...
- Unity中的MonoBehaviour脚本-基础知识和继承关系
本文分享Unity中的MonoBehaviour脚本-基础知识和继承关系 作为一个程序员, 在Unity中开发, 我们接触最多的对象之里一定有MonoBehaviour, 所以作为Unity基础知识学 ...
- Oculus Quest 2 和 Unity 的 VR 开发基础知识
使用 Meta 强大的 Quest 2 一体式 VR 耳机学习虚拟现实开发的基础知识 课程英文名:VR Development Fundamentals With Oculus Quest 2 And ...
最新文章
- R语言构建logistic回归模型:WVPlots包PRTPlot函数可视化获取logistic回归模型的最优阈值、优化(precision、enrichment)和recall之间的折衷
- Java注释 link_开源代码中注释中的那些a link p @ 是给什么编辑器用的????
- java ee 分页_【JavaEE】JavaEE分页实践
- HDU - 5335 Walk Out(bfs+路径输出+贪心)
- Hexo安装配置详解
- python去掉字符串开头的零_Python / Pandas-删除以字符串开头的列
- 如何从ngrx-store-devtools.js里找到actions的触发源头
- matlab怎么安装compiler,关于MATLAB中compiler配置问题
- Auto activation triggers for Java(代码提示)功能扩展
- 残疾人软件开发_组织如何使残疾人更具包容性
- js计算字典的个数_JS数据结构与算法_集合字典
- servelt笔记一
- 诺基亚的「翻身」之战
- Salesforce和SAP HANA的元数据访问加速
- 北理在线作业答案c语言,北理乐学C语言答案,最新.doc
- 估计算法类有哪些最新发表的毕业论文呢?
- Ps抠图(小白教程)
- C++ 11,智能指针(整理总结)3
- keep T 不是 KG等级_初中英语动词28讲:根据短语倒推 keep 的用法
- royal tsx连接闪退_Mac上使用Royal TSX链接服务器