- 前言
- 基础篇
- 变量命名
- 字符串常量 @""
- 数组
- 参数数组
- 数组的基类 Array
- 结构体
- 循环
- 结构 struct
- 枚举
- 函数传参方式修饰符
- 访问修饰符
- 运算符重载
- 命名空间
- 类
- 基础
- 多态
- 预处理
- 异常处理
- 文件读写
- 高级篇
- 属性(properties)
- 反射
- 特性
我此前有2年+的python工作经验(游戏行业),后来技术栈换成了unity,用的C#+lua。lua是比较好上手的,毕竟也是脚本。不过C#自己还是不太熟悉,虽然C++都是学过,C#上学的时候也学过,哈哈都忘了,还是想花点时间熟悉一下C#,增加一点自己看C#代码的自信。
所以,如果你是一个脚本语言比较熟悉的程序,本文十分适合你,只要你是一个熟练老手,或者是已经掌握了那么1、2种编程语言,看这篇文章熟悉C#再适合不过了。不过即使你是一个刚刚开始学习编程语言的小白,本文展示的也是那些C#中比较特别或者其他语言也有,但是C#中叫法不同的一些点。(如果本文看不懂,可能需要去补C++基础。)
c#参考:
菜鸟教程
微软官方文档
说的一门高级语言的基础,也就是变量、运算符、结构体、函数、类那些事。需要强调的如下:
变量命名和C++类似
- 可以用 下划线_ 以及 字母 开头命名变量
- 大小写敏感
- 可以包含_,字母,数字
- 不能用保留字
但是C#可以使用@开头,并且命名里其他位置也可以有@。
字符串常量 @“”c# 中字符串之前加@ 可以使得字符串按照原格式,并且无需对特殊字符转义。
比如@“abbb”, 打印就是“abbb”,不用在前面加转义。
初始化数组的方式:
int[] balance = new int[10];
int[] balance = {1,2,3};
int[] balance = new int[3]{1,2,3};//3可以省略
c#里多维数组不同于c++,可以认为c++里的多维数组等同于数组的数组,而c#对两者进行了区分,c#中数组的数组被称为交错数组。多维数组以及交错数组的的声明、初始化,使用如下:
// 多维数组
int [3,4] names; //声明
int [,] names = new int [3,4]{ // 初始化以及赋值
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}};
int val = names[2,3]//访问 val值是11
// 交错数组
int [][] scores;// 声明
int[][] scores = new int[2][]{new int[]{92,93,94},new int[]{85,66,87,88}};//初始化(可以不赋值)
int val = scores[1][1]//访问 val值是66
参数数组
用params修饰 表示未知数量的参数,类似python的**kwargs, 传参时候可以传数组实参,也可以传一组数组元素。 代码如下:
public int AddElements(params int[] arr){
int sum = 0;
foreach (int i in arr){
sum += i;
}
return sum;
}
int []p = new int []{1, 2, 3, 4, 5};
int sum = app.AddElements(p); //传数组实参
int sum = app.AddElements(1, 2, 3, 4, 5);// 传一组数组元素 sum的结果都是15
数组的基类 Array
详见C# Array 类
常用的属性如 Length,方法如Sort()等
int []add = new int[]{3,4,5};
if (add is Array){
Console.WriteLine("true"); // 会打印
}
结构体
循环
foreach 类似于python for a in list 或者lua中 for i,v in ipairs(table)
int []n = new int[10]; // int [] n之间的空格随意 都可以省略或者省略其中1个,也可以都不省略
foreach (int j in n )
{
}
结构 struct
和c++一样,c#的struct也是值类型。简单来说,可以认为是轻量的类(类是引用类型),不支持继承、不支持重写默认构造函数。具体参考这里
枚举enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
Days a = Days.Sun;
函数传参方式修饰符
C#里有 ref 以及 out这两个传参方式修饰符
C#中有三种参数传递方式:
- 值传参:实参和形参有不同的内存空间,函数内对参数的值进行更改,不会影响函数外实参的值。
- 引用传参:实参和形参有相同的内存空间,改形参的值等于改实参的值,实参需要初始化。ref修饰。
- 输出传参:相当于引用传递,但是实参可以不初始化,仅声明即可。out修饰。
ref的使用如下段代码所示:
void fun(ref int param){ // 函数声明
param = 100;
}
int a = 10;
fun(ref a); // 函数调用, a的值会变成100
访问修饰符
默认是private, 除了private,还有public、protected、internal、protected internal一共5种访问修饰符。
引用网友的总结
比如说:一个人A为父类,他的儿子B,妻子C,私生子D(注:D不在他家里)
如果我们给A的事情增加修饰符:
public事件,地球人都知道,全公开
protected事件,A,B,D知道(A和他的所有儿子知道,妻子C不知道)
private事件,只有A知道(隐私?心事?)
internal事件,A,B,C知道(A家里人都知道,私生子D不知道)
protected internal事件,A,B,C,D都知道,其它人不知道
类似c++,这里简单举个代码的例子。例子参考菜鸟教程。主要是opreator关键字,有些运算符可以重载,有些不可以。
public static Box operator+ (Box b, Box c){
Box box = new Box();
box.length = b.length + c.length;
box.breadth = b.breadth + c.breadth;
box.height = b.height + c.height;
return box;
}
命名空间
namespace以及using这两个关键字。参考菜鸟教程
类的默认访问标识是internal,成员的默认访问标识是private。类基本和c++类似,都有构造函数、析构函数、成员初始化列表等,不过c#不支持多继承,只支持单继承,c++支持多继承,不过c#可以用接口来实现和多重继承等同的效果。
下面代码涵盖了c#类相关的一些基本内容,注释给出了进一步的描述。
using System;
class Animal{
static public int count = 0; // 定义静态变量
void countAdd(){// 定义静态方法,静态成员只能静态方法访问
count += 1;
}
//这是重写无参构造函数,不重写会有默认构造函数
protected Animal(){// 访问修饰符不能是private,按需是protected、public或者internal
countAdd();
Console.WriteLine("Create Animal!");
}
protected Animal(string name){
countAdd();
Console.WriteLine("Create Animal with name {0}!", name);
}
~Animal(){ //不能继承、重载(函数名相同,函数签名不同,是静态多态性的一种)
Console.WriteLine("Delete Animal!");
}
}
// 这是接口,接口只声明方法,不实现
interface Friend{
public void playWithPeople();
}
class Cat:Animal{ //冒号表示继承
public Cat(){
Console.WriteLine("Create Cat!");
}
public Cat(string name):base(name){ // 这里用到了成员初始化列表,另一个关键字this表示自己
Console.WriteLine("Create Cat with name {0}!", name);
}
~Cat(){ //不能继承、重载(函数名相同,函数签名不同)
Console.WriteLine("Delete Cat!");
}
}
class Dog:Animal,Friend{
public Dog(){
Console.WriteLine("Create Dog!");
}
public Dog(string name):this(){
Console.WriteLine("Create Dog with name {0}!", name);
}
// 继承了接口,这里必须实现,否则编译不通过
public void playWithPeople(){
Console.WriteLine("This dog is playing with people!");
}
}
class HelloWorld{
static void Main(string[] args){
Cat cat = new Cat("catty");
Dog dog = new Dog("doggy");
Console.WriteLine(Animal.count);
Console.WriteLine(Cat.count);
Console.WriteLine(Dog.count);
dog.playWithPeople();
}
}
运行程序最后输出的是
Create Animal with name catty! Create Cat with name catty! Create Animal! Create Dog! Create Dog with name doggy! 2 2 2 This dog is playing with people!多态
和c++类似
抽象类可以定义抽象方法,用abstract修饰抽象类,涉及多态以及方法重写的几个关键字:abstract、vritual、new、override
- abstract可以修饰类,可以修饰方法。抽象方法必须声明在抽象类中,抽象方法不能有实现,必须被子类实现,子类方法之前必须有override。
- vritual 修饰方法,抽象类和虚方法就可以实现多态,虚方法可以有实现,也可以被子类实现。
- new 覆盖基类方法, 子类(与父类)同名方法 默认的修饰,可以隐藏父类的abstract or vritual方法,也可以是普通方法,甚至没有声明的方法(不过对于没有声明的方法不会跑错,会有warning)。不实现多态,声明的什么类型就调用什么类型的方法。
- override 重写,重写父类的方法,父类的方法可以是抽象方法或者虚方法(只能是二者之一),重写会实现多态:即调用实例真正类型的方法。
关于 new和override的区别 例子如下
Animal catA = new Cat(); catA.speak(); catA.run();
比如Cat类继承Animal类,Animal定义两个虚方法,speak以及run,Cat重写了这两个方法,用override修饰speak,用new修饰run,上面的代码 speak调用的是Cat类的实现,而run调用的是Animal的实现。
预处理菜鸟教程里表示c#里好像没有宏定义,c++里是有的,这里我先不深究,不过c#里也有类似宏定义的功能,就是#define,可以直接看菜鸟教程,之后我可能会补充。
关于宏定义的利弊,可以参考知乎上 皮皮关的回答
一场处理常见的那几种语言都差不多,c#和java一样,比c++多一个关键字 finally,另外三个关键字就是try,catch, throw。
- try 后面跟一段可能抛出异常的代码
- catch后面跟异常处理代码
- finally后面跟不管有没有异常都要执行的代码,比如打开文件之后,不论有没有异常 文件都要关闭
- throw用于主动抛出异常
可以自定义异常,继承 System.ApplicationException 即可。
文件读写直接看菜鸟教程:文件的输入与输出吧,哈哈。
高级篇 属性(properties)参考官方文档
c#中的属性特指通过get or set 访问器(accessors)读写的数据域(field),有点类似于python的@property以及@name.setter(python里不定义setter就是一个只读属性),python声明访问器的方式比较简单,就是一个装饰器@property(等于c#中的get),c#定义访问器的语法会稍微显得正式(复杂)一些,如下:
class Student{
private string code = "sq";
public string Code{
get{
return code;
}
set{
code = value;
}
}
}
//下面写Main函数中
Student s = new Student();
s.Code = "hahaha";
Console.WriteLine(s.Code);//结果是hahaha
另外,C#的访问器可以使用简化版,其中= "instial string"是初始化语法,非必须,按需要使用,get,set至少有其中一个即可;
public string Code{get;set;} = "instial string";
当 get以及set的主体只有一个表达式的时候,还可以简化如下:
class Student{
private string code = "sq";
public string Code{
get => code;
set => code = value;
}
}
反射
菜鸟教程对于反射的定义是:
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
所以反射是一种能力,或者说一种特性,不同语言实现这种特性的方式会不太一样,比如:
python这种解释型语言,本身就可以动态(运行时)给对象添加属性(修改了对象本身的状态),或者用getAttr、setAttr运行时访问、修改自身的属性。
Java是在运行时,由JVM(Java virtual machine,Java虚拟机)加载一个Class类型的对象(class Class),这个对象会对应某个类型的对象,比如有一个自定义的People类,运行时加载这个类的时候,JVM就会创建一个Class对象cls(其实我想称他为元对象,meta object,描述类的对象),这个对象提供一些方法,比如cls.getName(),获取类名,或者获得某个属性的值,cls.getField(fieldName),具体可以参考廖雪峰的官方网站
C# 和Java一样,都是编译型的语言(虽然不是类似C语言那样是纯粹的编译语言,就是编译之后可以直接在计算机执行,Java编译之后还需要JVM解释执行,具体可以参考这里,一般更偏向把C#,Java称为编译型的语言。)其实C#实现反射的方式也类似,C#内置Assembly、Type、MethodInfo等类型,可以运行时访问对象自身的一些属性等。参考官方文档:Reflection (C#),官方文档:Reflection in .NET
int i = 42; Type type = i.GetType(); Console.WriteLine(type);特性
参考官方文档



