c#语法(一)
第一个hello world
using System;
namespace MyApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
命名空间 namespace
namespace 相当于java 的包,package。cs 的代码的最小模块是class(类),而类包含在命名空间里面,大括号包裹代码快,里面可以申明类。using xxxx就是使用别的命名空间的代码,相当于java的导包 import.
Console.WriteLine窗口的输出打印。
- Main函数第一个大写
关键字
- 常量声明:
const java对应的final
string 字符串声明 注意小写开头
字符串操作
- 占位符
static void Main(string[] args)
{
string name = "许聪";
int age = 18;
Console.WriteLine("my name is {0} and age is {1}",name,age);//
}
cs 的占位符以大括号包围,里面为序列号,0,1...代表第几个变量
C# 数据类型
数据类型有三种值类型、引用类型、指针类型
- 值类型
值类型就是基础数据类型
类型 | 描述 | 范围 | 默认值 |
---|---|---|---|
bool | 布尔值 | True 或 False | False |
sbyte | 8 位有符号整数类型 | -128 到 127 | 0 |
uint | 32 位无符号整数类型 | 0 到 4,294,967,295 | 0 |
ulong | 64 位无符号整数类型 | 0 | |
ushort | 16 位无符号整数类型 | 0 | |
sbyte | 8 位无符号整数类型 | 0 |
可以用sizeof(type) 来查看一个数据占几个字节
- 引用类型
- 内置的 引用类型有:object、dynamic 和 string
- Object 也是所有引用类型的基类,也有自动装箱拆箱。
- Dynamic动态类型:可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的
dynamic d = 20;
动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的
- string 类型
@引号字符串
string d = @"hello \t world"; // hello \t world
引号字符串不考虑转义字符,将转义字符看成普通字符,和kotlin的""" xxxx """ 相似
xx* 指针类型
c/c++ 指针类型一样
类 class
cs 的类的声明和实例化和java几乎一样
- class 关键字声明类
- 类有成员变量方法
- 实例化:A a = new A() 关键字new来实例化
类型转换
在c#中,基础数据类型是有和kotlin一样的类型转换函数:
int a = 3;
a.
接收用户的键盘输入
int num;
num = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("num = {0}", num);
循环控制
int[] arr = {1,2,3,4,5 };
foreach(int item in arr)
{
Console.WriteLine("item : {0}",item);
}
参数传递
在 C# 中,有三种向方法传递参数的方式:
一、值参数:在使用参数时,是把一个值传递给函数使用的一个变量。对函数中此变量的任何修改都不会影响函数调用中指定的参数。(由于函数只有一个返回值,不能用作参数的多个变量值)。
二、引用参数:即函数处理的变量与函数调用中使用的变量相同,而不仅仅是值相同的变量。因此,对这个变量的任何改变都会影响用作参数的变量值。需用ref关键字指定参数。用作ref参数的变量有两个限制,由于函数可能会改变引用参数的值,所有必须在函数调用中使用“非常量”变量。其次,必须使用初始化过的变量。
三、输出参数:out关键字,指定所给定的参数是一个输出参数。Out关键字的使用方式与ref关键字相同,实际上,他的执行方式与引用参数完全一样,因为在函数执行完毕后,该参数的值将返回给函数调用中使用的变量。
四、引用参数和输出参数的一些重要区别:
把未赋值的变量用作ref参数是非法的,但可以把未赋值的变量用作out参数。
另外,在函数使用out参数时,必须把它看成是尚未赋值。即调用代码可以把已赋值的变量用作out参数,但存储在该变量中的值会在函数执行时丢失。
可空类型
定义:
int? i = null;
Nullable<int> ii = new Nullable<int>(2);
可以用个?来声明是一个可空类型,或者Nullable这个特殊类型来定义。
c#的数据类型分为值类型(基础数据类型)和引用类型,引用类型是可以为空的,但是基础数据类型是有默认值的,int i = 0
i的默认值为0,但是数据库中就存在没有定义的情况,有没有默认值,所以为了和数据库的类型对应上就增加了空类型,比如,bool类型就有三个值:true,false,null
- Null 合并运算符( ?? )
c# 还有判空运算符,用双问号表示。类似于kotlin 的:?,表示如果变量为null,则取后面的值。
Nullable<int> ii = new Nullable<int>(2);
Console.WriteLine("i = {0}", i);// i =
int j = i ?? 3;
Console.WriteLine("j = {0}", j);// j =3
C# 结构体
结构体用struct 关键字声明,结构体是值类型数据结构。它使得一个单一变量可以存储各种数据类型的相关数据
struct Book
{
public string name;
public float price;
public string author;
}
public static void Main(string[] args)
{
Book book;
book.name = "c入门到放弃";
book.author = "xucong";
book.price = 2.3f;
}
- 结构可带有方法、字段、索引、属性、运算符方法和事件
- 与类不同,结构不能继承其他的结构或类。
- 结构不能作为其他结构或类的基础结构。
- 可以使用 New 操作符创建一个结构对象,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用
- 结构体不可以将其变量赋初始值
- 结构体是值类型的数据,内存分配在栈中,而类的实体在堆中间。堆空间大,访问慢,栈内存小,访问快。故而,当我们描述一个轻量级对象的时候,结构可提高效率,成本更低
C# 中的析构函数
和c++的析构函数类似,在类回收的时候来调用,java 里面也有类似的方法finalize(),在类回收之前调用,但是java里面的这个方法不一定可靠。
析构函数定义为类名前加上“~”后面名字的方法:
class Line
{
private double length; // 线条的长度
public Line() // 构造函数
{
Console.WriteLine("对象已创建");
}
~Line() //析构函数
{
Console.WriteLine("对象已删除");
}
public void setLength( double len )
{
length = len;
}
public double getLength()
{
return length;
}
static void Main(string[] args)
{
Line line = new Line();
// 设置线条长度
line.setLength(6.0);
Console.WriteLine("线条的长度: {0}", line.getLength());
}
}
输出:
对象已创建
线条的长度: 6
对象已删除
- 虚方法
虚方法用关键字 virtual 声明,c#有抽象方法abstract,和虚方法virtual。
1.virtual修饰的方法必须有实现(哪怕是仅仅添加一对大括号),而abstract修饰的方法一定不能实现。
2.virtual可以被子类重写,而abstract必须被子类重写。
3.如果类成员被abstract修饰,则该类前必须添加abstract,因为只有抽象类才可以有抽象方法。
4.无法创建abstract类的实例,只能被继承无法实例化。
虚方法、方法的继承重写
- java中的方法重写,子类只需要复写父类的方法,在子类的实例的引用调用该方法的时候,调用的是子类的复写的方法:
A a = new B()
a.fun()
其中B是A的子类,fun是B复写A的方法,a.fun()执行的就是子类的方法。
但是在c#中当父类的引用指向子类的引用的时候和java就有较大的差异了。c#里面称为声明类和实例类,上面A称为声明类,B称为实例类。方法的调用遵循下面规则:
当调用一个类的实例的时候,首先会去检查这个类的声明类,检查这个方法是否是virtual方法
如果这个方法不是virtual方法,会调用声明类中的该方法,如果该声明类中找不到就去父类找该方法。
class A
{
public virtual void fun1()
{
Console.WriteLine("A:fun1");
}
public void fun2()
{
Console.WriteLine("A:fun2");
}
}
class B : A
{
public override void fun1()
{
Console.WriteLine("B:fun1");
}
public void fun2()
{
Console.WriteLine("B:fun2");
}
}
A a = new A();
A b = new B();
a.fun2();
b.fun2();
// 输出
//A:fun2
//A:fun2
A a = new A();
B b = new B();
a.fun2();
b.fun2();
// 输出
//A:fun2
//B:fun2
把B类修改为:
class B : A
{
public override void fun1()
{
Console.WriteLine("B:fun1");
}
// 去掉func2方法
}
A a = new A();
B b = new B();
a.fun2();
b.fun2();
// 输出
//A:fun2
//A:fun2
这里需要注意:
上面A、B的fun2在java中是重写方法,但是在c#这里不是,子类允许定义和父类相同的方法,如果是重写方法,需要满足两个条件:1、父类的这个方法是overide,2、父类的方法是virtual的。而且如果是方法重写的,则必须有overide 关键字,否则视为普通方法。
普通方法看完了,接下来看virtual方法:
如果执行当前的方法是虚方法,则取实例类里面去找到相应的虚方法,在这个实例类里,他会检查这个实例类的定义中是否有重新实现该虚函数(通过
override
关键字),如果是有,那么OK,它就不会再找了,而 马上执行该实例类中的这个重新实现的函数。而如果没有的话,系统就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重载了 该虚函数的父类为止,然后执行该父类里重载后的函数。
class A
{
public virtual void fun1()
{
Console.WriteLine("A:fun1");
}
}
class B : A
{
}
class C : B
{
public override void fun1()
{
Console.WriteLine("C:fun1");
}
}
A a = new A();
A b = new B();
A c = new C();
a.fun1();
b.fun1();
c.fun1();
输出:
A:fun1
A:fun1
C:fun1
运算符重载
c#可以重载内置运算符,这个语法和c++是一样的,一般我们我们的运算符有加、减、大于、等于,等等,这些都是作用在数字类型的变量上,但是如果需要对一个对象进行这些运算就需要运算符重载了。
定义:
public class Box
{
int weight;
public static MyApp1.Box operator +(Box a,Box b)
{
Box box = new Box();
box.weight = a.weight + b.weight;
return box;
}
public static void Main(string[] args)
{
Box a = new Box();
a.weight = 3;
Box b = new Box();
b.weight = 4;
Console.WriteLine((a + b).weight);
// 输出:7
}
}
运算符重载以关键字operator 后面紧跟运算符定义,参数的个数和类型往往也是有限定的。定义完成之后我们就可以对对象进行和数字类型一样进行加号操作。
委托和事件
匿名方法&lambda表达式
委托代表一类签名相同的函数(相同参数合返回值),匿名函数和lambda表达式也是一样,所以委托可以用匿名函数和lambda表达式来表示,java的lambda表达式表达的是函数式接口,c#的lambda表达的是匿名方法。
不安全代码
c#中可以和c/c++一样用指针操作变量,这样的代码块称之为不安全代码,不安全代码块需要用unsafe关键字包裹起来。
c#对于指针的声明和使用方法和c语言是一样的。
int a = 10;
int* p = &a;
Console.WriteLine("p = {0}", *p);// 10
- 指针操作数组
指针操作数组需要把数组的首地址赋值给指针变量,然后用fixed修饰,防止变量的内存地址的改变。导致指针失效
int[] arr = new int[4]{ 1, 2, 3, 4 };
fixed (int* arr_p = arr)
for (int i = 0; i < 3; i++) {
Console.WriteLine("i = {0}", *(arr_p + i));
}
-
fixed关键字
由于C#中声明的变量在内存中的存储受垃圾回收器管理;因此一个变量(例如一个大数组)有可能在运行过程中被移动到内存中的其他位置。如果一个变量的内存地址会变化,那么指针也就没有意义了。解决方法就是使用fixed关键字来固定变量位置不移动。
1.png
当然也可以用开辟堆栈空间来进行指针变量的使用,因为栈空间是不收垃圾回收机制影响的。
int* ptr = stackalloc int[3];
协程
https://blog.csdn.net/qq_30695651/article/details/79105332
https://blog.csdn.net/dk_0520/article/details/53859871
https://blog.csdn.net/fjl2007/article/details/46860561
http://dsqiu.iteye.com/blog/2029701
http://gad.qq.com/article/detail/28027
http://www.voidcn.com/article/p-tlctyuiq-bcx.html
http://www.unity.5helpyou.com/2658.html
- 协程是什么
简单来说,协程是一个有多个返回点的函数
从程序结构的角度来讲,协程是一个有限状态机,这样说可能并不是很明白,说到协程(Coroutine),我们还要提到另一样东西,那就是子例程(Subroutine),子例程一般可以指函数,函数是没有 状态 的,等到它return之后,它的所有局部变量就消失了,但是在协程中我们可以在 一个函数里多次返回, 局部变量被当作状态保存在协程函数中,知道最后一次return,协程的状态才别清除。
简单来说,协程就是:你可以写一段顺序的代码,然后标明哪里需要暂停,然后在下一帧或者一段时间后,系统会继续执行这段代码
协程是否为异步执行?严格意义上来讲,协程并不是异步执行的,但是调用者可以分时间片去执行每一个yield,让程序看起来像是异步的
IEnumerator,它是一个迭代器,你可以把它当成指向一个序列的某个节点的指针,它提供了两个重要的接口,分别是Current(返回当前指向的元素)和MoveNext()(将指针向前移动一个单位,如果移动成功,则返回true)。IEnumerator是一个interface,所以你不用担心的具体实现
public class CoroutinesExample : MonoBehaviour
{
public float smoothing = 1f;
public Transform target;
void Start ()
{
StartCoroutine(MyCoroutine(target));
}
// 注意这个函数返回值是IEnumerator,必须
IEnumerator MyCoroutine (Transform target)
{
// 处理第一阶段
// 和目标大于0.05就按照smoothing * Time.deltaTime移动一段距离
while(Vector3.Distance(transform.position, target.position) > 0.05f)
{
transform.position = Vector3.Lerp(transform.position, target.position, smoothing * Time.deltaTime);
yield return null;// 这里暂停后返回,等待下帧执行机会
}
print("我到了");
// 处理第二阶段
yield return new WaitForSeconds(3f);
print("完成!");
}
}
monobehavior.png
unity中的yield
yield 后面可以跟的表达式:
a) return null - 等下个Update之后恢复
b) return new WaitForEndOfFrame() - 等下个OnGUI之后恢复
c) return new WaitForFixedUpdate() - 等下个FixedUpdate之后恢复,有可能一帧内多次执行
d) return new WaitForSeconds(2) - 2秒后,等下个Update之后恢复
e) return new WWW(url) - Web请求完成了,Update之后恢复
f) return StartCorourtine() - 新的协成完成了,Update之后恢复
g) break -退出协程
h) return Application.LoadLevelAsync(levelName) - load level 完成,异步加载场景
i) return Resources.UnloadUnusedAssets(); // unload 完成
/// <summary>
/// 延时执行
/// </summary>
/// <param name="action">执行的委托</param>
/// <param name="delaySeconds">延时等待的秒数</param>
public IEnumerator DelayToInvokeDo(Action action, float delaySeconds)
{
yield return new WaitForSeconds(delaySeconds);
action();
}
/// <summary>
/// 使用例子
/// </summary>
StartCoroutine(DelayToInvokeDo(delegate() {
task.SetActive(true);
task.transform.position = Vector3.zero;
task.transform.rotation = Quaternion.Euler(Vector3.zero);
task.doSomethings();
},1.5f));