调试和错误处理
一、正常模式下的调试
1.在VS中使用Console.WriteLine()的方法向控制台输出变量的值,通过查看这个变量的值是否适合我们预期来调试错误。
2.在Unity中使用Debug.Log() 、Debug.LogError()、Debug.LogWar()向Unity console窗口输出信息帮助调试错误。
二、中断模式下的调试
指暂停程序执行,然后查看程序中的状态,也可以让程序继续执行。
断点:断点是源代码中自动进入中断模式的一个标记,当遇到断点的是很好程序会自己进入中断模式。
插入断点:①右键代码行,选择breakpoint(断点)->insert breakpoint(插入断点)。
②光标定位到代码行,选择菜单上的Debug(调试)->Toggle Breakpoint(切换断点)。
③光标定位到代码行,在此行按下F9,再按一次取消。(常用)
④在需要添加断的行首位置,直接单击,再次点击取消。(常用)
窗口BreakPoint:调试->窗口->断点
单步执行代码
在中断模式下我们可以单步执行代码,单步执行代码有两种:逐过程和逐语句,两者都是一条一条语句执行,区别在于逐过程遇到函数,不会进入函数内部,而是把函数当成一条语句执行。
异常处理
try catch finally
try{
}
catch(<ExceptionType> e){
}
finally{
}
其中catch块可以有0或多个,finally块可以有0或1个。但如果没有catch块,必须要有finally块,没有finally块必须要有catch块,catch块和finally块可以共存。
try:try块包含可能出现的异常代码(一条或多条语句)
catch:catch块用来捕捉异常,当代码发生异常,而且异常类型catch块中类型一样时,就会执行catch块,若catch块参数不写,则会发生任何异常都会执行的catch块。
finally:finally块中包含了始终执行代码,不管有无异常都会执行。
在try块中,如果有一行代码发生异常,那么try块中剩余的代码将不会执行。例如:
while(true)
{
try{
int num =Convert.ToInt32(Console.ReadLine());
break;
}
cathc{
Console.WriteLine("输入的不是整数 ");
}
}
上述代码中如果输入的不是int类型的整数,将会无限继续循环用户输入,直到输入正确为止。证明了,try中第一行代码错误时,不会执行下一行的break代码。
面向对象编程
简单来说就是结构化编程,对程序中的变量结构划分,让编程更加清晰。
类
实际上是创建对象的模板。每个对象都包含数据,并提供数据处理和访问数据的方法。
类中的数据和函数称为成员。
class Vehicle
{
public int speed;//数据成员
public int maxSpeed;//这些数据成员叫做域,也叫字段
public void Run()//函数成员
{
}
}
编程规范上,习惯把所有字段设成private,只可以在类内部访问,当其他类对象想要范文时,需要设置set方法进行访问。例如:
private x,y,z;
public void SetX(float x)
{
this.x=x;
}
①如果我们直接在方法内部访问同名的变量的时候,优先访问最近的(形参)。
②我们可以通过this.表示访问的时类的字段或者方法。
构造函数
我们在构造对象的时候,对象的初始化过程是自动完成的,构造函数就是用于初始化的函数。
声明基本的构造函数的语法就是去声明一个和所在类同名的方法,但该方法没有返回类型。例如:
class MyClass
{
public MyClass()
{
//构造函数的函数体
}
}
当我们使用创建类或结构时,将会调用其构造函数,构造函数与该类或结构具有相同名称,并且通常初始化新对象的数据成员。当使用new运算符对该类进行实例化时,在为新对象分配内存后,new运算符会立即调用该类的构造函数。如:
public static void Main(string []args)
{
MyClass mc = new MyClass();//立即调用构造函数
}
构造函数可以进行重载,跟普通函数的重载一样的规则。
不带任何参数的构造函数称为“无参数构造函数” 。 每当使用 new 运算符实例化对象且不为 new 提供任何参数时,会调用无参数构造函数。即上诉代码。
注意:当我们不写构造函数时,编译器会提供给我们一个默认的无参构造函数,但我们定义一个或者多个构造函数时,编译器就不会再提供默认构造函数。
属性的定义
public int MyIntProp
{
get{
//get code}
set{
//set code}
}
①定义属性需要名字和类型
②属性包含get块个set块
③访问属性和访问字段一样,当获取属性的值的时候,就会调用属性的get块,所以get块需要返回值,类型就是属性类型;当我们给属性设置值时,就会调用set块,可以在set块中通过value访问到我们设置的值。
官方文档:
属性结合了字段和方法的多个方面。 对于对象的用户来说,属性似乎是一个字段,访问属性需要相同的语法。 对于类的实现者来说,属性是一两个代码块,表示 get 访问器和/或 set 访问器。 读取属性时,执行 get 访问器的代码块;向属性赋予新值时,执行 set 访问器的代码块。 将不带 set 访问器的属性视为只读。 将不带 get 访问器的属性视为只写。 将具有以上两个访问器的属性视为读写。
与字段不同,属性不会被归类为变量。 因此,不能将属性作为 ref 或 out 参数传递。
属性具有许多用途:它们可以先验证数据,再允许进行更改;可以在类上透明地公开数据,其中数据实际是从某个其他源(如数据库)检索到的;可以在数据发生更改时采取措施,例如引发事件或更改其他字段的值。
简而言之就是属性的get块和set块就是读和写的功能,get块会返回值,set块中始终有一个名为value的参数,value可以理解为外部设置值时候传递的参数。例如:
private int age;
public int Age
{
set
{
if(value < 0)
{
return ;
}
age = value;
}
get
{
return age;
}
}
自动实现属性
private string name;
public string Name
{
get{
return name;}
set{
name = value;}
}
等价于:
public string Name{
get;set;}
编译器会自动给我们提供一个字段来存储name。
public static void Main(string [] args)
{
\\假设属性定义在Vector类上
Vector v3 =new Vector();
v3.Name = "CHAI";
Console.WriteLine(v3.Name);
}
输出结果为CHAI。
匿名类型
在创建变量(对象的时候)必须指定类型,其实也可以不指定类型,这个就是匿名类型,例:
var v1 = 1000;
当匿名类型被赋值时,其类型就会被确定下来,即上述代码时int类型。
堆和栈
程序运行时的内存区域
我们把内存空间分为堆空间和栈空间。栈空间小,读取速度快,堆空间比较大,读取速度慢。
栈:数据只能从栈顶插入和删除,把数据插入栈顶叫做入栈(push),把数从栈顶删除叫出栈(pop)。
堆:是一块内存区域,与栈不同,堆里内存能够任意顺序存入和移除。
值类型和引用类型
值类型(整数,bool,char,小数,struct)值类型数据放在栈中。值类型数据放在栈中。
引用类型(string ,数值,定义的类,内置的类)。引用类型数据放在堆里,引用存放在栈中。
引用类型的赋值:当我们使用引用类型赋值时,其实时赋值引用类的引用。
注:如果数组是一个值类型的数组(指数组中存的是值类型的元素),那么数组直接存值。如果是一个引用类的数组(指数组中存的是引用类型的元素)那么数组存的是引用(即内存地址)。
继承
C#中不支持多种继承
继承:父类中所有的数据成员和函数成员,都会继承到子类里面。父类里面公有的数据和函数成员才能在子类中访问。
官方文档:
继承(以及封装和多态性)是面向对象的编程的三个主要特征之一。 通过继承,可以创建新类,以便重用、扩展和修改在其他类中定义的行为。 其成员被继承的类称为“基类” ,继承这些成员的类称为“派生类” 。 派生类只能有一个直接基类。 但是,继承是可传递的。
实现继承
要声明派生自另外一个类,用下面语法:
class MyDerivedClass:MyBaseClass
{
//MyDerivedClass是派生类,继承自基类MyBaseClass
}
MyDerivedClass是派生类,继承自基类MyBaseClass。
若类(或结构)也派生自接口,则用逗号分隔列表中得到基类和接口
public calss MyDerivedClass:MyBaseClass.IInterface1,IIterface2{
}
父类声明的对象,可以用子类去构造,但是子类声明的对象不能用父类去构造,因为子类的方法,数据等要比父类更多,更强大。
父类A
class A
{
public void F()
{
Console.WriteLine("这是父类的F方法");
}
}
子类B
public void F()