本篇文章是针对于有C++基础的同学对C#的快速了解即入门,不适合零基础的小伙伴,本文为第二部分,如果没有看过第一部分请在文章末尾链接观看第一部分
13.访问修饰符
与C++类似,存在部分扩展
- public:所有对象都可以访问;
- private:对象本身在对象内部可以访问;
- protected:只有该类对象及其子类对象可以访问
- internal:同一个程序集的对象可以访问;
- protected internal:访问限于当前程序集或派生自包含类的类(不常用)
- private protected:只有在其声明的程序集内,通过相同class中的代码或派生自该class的类型,才能访问类型或成员(不常用)
14.继承
1.构造方法
和C++一样,存在默认构造函数,如果程序员不进行编写构造函数,那么其存在一个默认构造函数。也可以自行编写有参构造函数。
也可以使用this关键字进行选择变量。
class Person
{
public string name;
public int age;
//构造方法
public Person(string name,int age)
{
this.name = name;
this.age = age;
}
}
2.析构函数
同C++析构函数形式相同
~Person()
{
}
3.继承
class Person
{
private string name;//子类中无法访问
public int age;
}
class XiaoLi:Person//继承语句Person为基类
{
}
若基类也就是父类中存在有参构造或者是非默认构造,子类在继承中也需要非默认构造,这点也类似于C++中的子类初始化
class Person
{
private string name;//子类中无法访问
public int age;
//构造方法
public Person(string name,int age)
{
this.name = name;
this.age = age;
}
}
class XiaoLi:Person
{
public XiaoLi(string name,int age):base(name,age)//子类通过base的方式初始化基类
{
}
}
15.多态、重载与重写
多态往往表现为“一个接口,多个功能”!
主要表现形式:
1.父类变量指向子类
2.函数重载
1.父类变量指向子类
using System;
namespace ConsoleApp2
{
class Person//基类(父类)
{
public virtual void run()//虚函数
{
}
}
class xiaoLi:Person//小李子类继承于基类Person
{
public override void run()//子类中虚函数的重写
{
base.run();//先调用父类run()函数,可以删除
Console.WriteLine("小李跑步");
}
}
class xiaoWang:Person//小王子类继承于基类Person
{
public override void run()
{
base.run();//先调用父类run()函数,可以删除
Console.WriteLine("小王跑步");
}
}
class Program
{
static void Main(string[] args)
{
xiaoLi xl = new xiaoLi();//小李实例化
xiaoWang xw = new xiaoWang();//小王实例化
Person p = xl;//p指向xl
p.run();//此时通过p.run()运行的是小李的run函数
p = xw;//p指向xw
p.run();//此时通过p.run()运行的是小王的run函数
}
}
}
上述代码中父类p通过改变指针的指向使得run函数拥有了不同的功能,从而实现了多态。
2.函数重载
class Person
{
public void run()//run函数
{
}
public void run(string name)//重载1
{
}
public void run(int age)//重载2
{
}
}
函数名相同,但参数不同,可以存在多种形式,即为函数重载,通过传入相应的参数来调用对应的函数。
16.属性
在C++中为了避免变量值被随意的修改,通常类中的变量我们会设为private私有,那么外界就无法访问这个变量了,为了解决这个问题在C++中我们经常会以如下的形式来解决:
class Perosn
{
public:
string getName()
{
return name;
}
void setName(string name)
{
this.name = name;
}
private:
string name;
}
通过编写set和get函数的方式进行值的设置与读取,那么上述代码更改一下格式,仍然可以在C#中进行使用。
但是,因为这个函数非常常用,C#就提供了更为简便的编写方式。
using System;
namespace ConsoleApp2
{
class Person
{
private string? name;//私有变量
public string? Name//共有Name属性,需要和变量不同名
{
get//get访问器,若不存在则不可读
{
return name;//返回name值
}
set//set访问器,若不存在则不可写
{
name = value;//设置name值,传进来的参数默认为value,所以只需进行简单的赋值即可
}
}
}
class Program
{
static void Main(string[] args)
{
Person p = new Person();
p.Name = "小王";//通过=号进行赋值,不可使用函数调用的方式
string name = p.Name;//p.Name返回name的值
Console.WriteLine(name);
}
}
}
17.静态
与C++几乎一致
static关键字声明静态函数或变量,静态函数只能调用静态变量,静态变量是属于类的各个实例之间数据共享。
using System;
namespace ConsoleApp2
{
class Person
{
public static string? name;//name为静态成员,属于Person类,而不是属于某个实例
public string? Name
{
get
{
return name;
}
set
{
name = value;
}
}
}
class Program
{
static void Main(string[] args)
{
Person xl = new Person();
xl.Name = "小李";//name为小李
Person xw = new Person();
Console.WriteLine(xw.Name);//输出xw的name仍然为小李,实例之间数据共享。
}
}
}
上述主函数中是通过实例点属性获取静态成员的数值和设置数值的,那么因为静态成员是属于Person类的,如果不存在属性,则应通过”类.成员名“
来进行调用的。
using System;
namespace ConsoleApp2
{
class Person
{
public static string? name;
}
class Program
{
static void Main(string[] args)
{
Person xl = new Person();
Person.name = "小李";//通过类名来进行调用
Person xw = new Person();
Console.WriteLine(Person.name);
}
}
}
静态成员的一些特点
- 静态成员随着类的加载而加载,无论一个类拥有多少个实例,静态成员永远都只有一份副本。
- 静态方法可以被重载
- 静态成员由“类名.变量名”语法进行调用。
- 静态方法只能访问静态成员,实例方法可以访问实例成员变量和实例方法,也可以访问静态成员。
18.抽象
- 抽象类不能使用new关键字进行实例化
- 抽象方法必须放在抽象类里,class前面加修饰符abstract
- 抽象方法返回值前面也需要加修饰符abstract
- 抽象发放没有方法体
- 抽象方法必须被子类重写并实现,或使其子类已就位抽象类
- 抽象方法的访问修饰符不能使private,不然就无法被子类访问了,也就无法重写了
abstract class Enemy//抽象类
{
public abstract void Attack();//抽象方法
}
class Zombie : Enemy//子类
{
public override void Attack()//重写抽象类
{
Console.WriteLine("挠人");
}
}
class Dog : Enemy
{
public override void Attack()
{
Console.WriteLine("咬人");
}
}
虽然上述过程也可以通过virtual虚函数重写来实现,但是当代码量足够多时,你可能会忘记虚函数是不应该被实例化的,就会导致问题出现。
而抽象类,是不可以进行实例化的,有效的避免了这问题。
19.接口
接口是一组包含了类或结构可以实现的功能的定义,使得这些类或结构可以保持一致的形式与特征。
接口也为C#提供了多继承的功能实现。
特点:
- 接口可以包含事件、索引器、方法和属性,不能包含字段
- 接口是引用类型
- 接口只包含了功能的定义,不包含功能的实现
- 接口默认为public 不可以修改
- 接口被实现后,就必须实现接口中的所有成员内容
class Person
{
public void eat()
{
}
}
interface wolf
{
void attack();
}
class wolfPerson:Person,wolf
{
public void attack()
{
Console.WriteLine("攻击");
}
}
20.泛型
类似于C++中的模板
public static void Swap<T>(ref T value0,ref T value1)
{
T temp = value0;
value0 = value1;
value1 = temp;
}
使用泛型可以重用代码,保护类型的安全以及提高性能
降低强制转换或装箱操作的成本和风险
可以对泛型参数进行限定以访问特定数据类型的方法
多泛型
void swap<T,U>(T p0,U p1)
{
}
泛型where约束,避免参数传入出错
约束 | 说明 |
---|---|
where T:struct | 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。 |
where T:class | 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。 |
where T:new () | 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。 |
where T:<基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
where T:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 |
where T:U | 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束. |
void swap<T,U>(T p0,U p1) whrere U: struct where T:Computer,new()
{
}
21.集合
集合类似于容器,可以通过索引访问集合成员,也可以通过“键”来进行访问,集合的大小可以动态调整,可以在运行时添加或删除元素。
集合可以分为两类:泛型集合与非泛型集合(使用较少)
泛型集合类一般位于System.Collections.Generic名称空间中,
非泛型集合类一般位于System.Collections名称空间。
此外,System.Collections.Specialized名称空间中也包含一些有用的集合类。
1.数组ArrayList非泛型集合
- 动态的增加和减少元素
- 实现了ICollecton和IList和IEnumerable接口
- 灵活的设置数组的大小
- 不安全的集合类型
- 其元素为值类型时,效率不高(装箱和拆箱消耗性能)
以下为代码示例:
using System;
using System.Collections;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
ArrayList array = new ArrayList();
//依次添加元素
array.Add(0);
array.Add(1.1f);
//插入元素
array.Insert(0, "新元素");
//删除
array.Remove(1.1f);
array.RemoveAt(0);
//判断某元素是否存在
bool res = array.Contains("新元素");
//清空
array.Clear();
int num = array.Count;//数量
}
}
}
2.堆栈Stack非泛型集合
先进后出。
线性结构,即每个节点有且只有一个前驱结点和一个后续结点。
容量自动分配添加。
可以接受null作为有效值。
允许重复的元素。
不安全的数据结构。
其泛型为Stack。
using System;
using System.Collections;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
//堆栈
Stack stack = new Stack();
//入栈
stack.Push(1);
stack.Push("2");
//出栈
object? num = stack.Pop();
//仅获取最上面的内容而不出栈
num = stack.Peek();
int num1 = stack.Count;
}
}
}
3.队列Queue非泛型集合
先进先出
using System;
using System.Collections;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
//队列
Queue queue = new Queue();
//入队
queue.Enqueue(1);
queue.Enqueue(1.1f);
//出队
object? obj = queue.Dequeue();
//获取队首元素,但是不出队
obj = queue.Peek();
}
}
}
4.哈希表hash非泛型集合
处理和表现类似key-value的键值对组合
key和value都是obkect类型
key值必须唯一,区分大小写
value可以是值类型变量,也可以是对象
using System;
using System.Collections;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
//创建hash表
Hashtable hashtable = new Hashtable();
//添加对应的键值对
hashtable.Add("name", "xiaobai");
hashtable.Add("score", 100);
hashtable["age"] = 18;//第二种方法
//查询是否包含某个键或值
hashtable.ContainsKey("name");
hashtable.ContainsValue("xiaobai");
//移除
hashtable.Remove("name");
object? score = hashtable["score"];
}
}
}
5.数组(泛型)
using System;
using System.Collections.Generic;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
//泛型创建,其余用法与非泛型一致
List<int> list = new List<int>();
}
}
}
6.堆栈泛型
//泛型创建,其余用法与非泛型一致
Stack<int> stack = new Stack<int>();
stack.Push(2);
7.队列泛型
//泛型创建,其余用法与非泛型一致
Queue<int> queue = new Queue<int>();
queue.Enqueue(2);
8.字典(哈希表)泛型
//泛型创建,其余用法与非泛型一致
Dictionary<string, int> dic = new Dictionary<string, int>();
//添加键值对
dic.Add("xiaoli", 22);
dic["xiaohei"] = 24;
//取值
int value;
dic.TryGetValue("xiaohei", out value);//尝试取值,避免直接取值导致的程序崩溃
22.委托
C#中的委托类似于C++中的函数指针。
委托是一种可以存放方法的自定义类型。
示例1:
using System;
using System.Collections.Generic;
namespace ConsoleApp2
{
class Program
{
//声明一个委托类型
public delegate int myDelegate(int x, int y);
static int add(int a, int b)
{
return a + b;
}
static int sub(int a, int b)
{
return a - b;
}
static void Main(string[] args)
{
//声明一个委托变量
myDelegate mydel;
//委托保存方法
mydel = add;
//使用
mydel(3, 4);
mydel = sub;//改变存储
mydel(3, 5);
}
}
}
示例2:
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace ConsoleApp2
{
class Person
{
//委托类型
public delegate void doSomething();
public void run(doSomething dosomething)//参数中声明一个委托变量
{
dosomething();//执行
}
}
class Program
{
static void eat()
{
Console.WriteLine("吃饭");
}
static void play()
{
Console.WriteLine("玩");
}
static void Main(string[] args)
{
Person person = new Person();//实例化一个person类
person.run(eat);//委托运行eat
person.run(play);//委托运行play
}
}
}
1.委托链-多播
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace ConsoleApp2
{
class Person
{
//委托类型
public delegate void doSomething();
public void run(doSomething? dosomething)//参数中声明一个委托变量
{
dosomething();//执行
}
}
class Program
{
static void eat()
{
Console.WriteLine("吃饭");
}
static void play()
{
Console.WriteLine("玩");
}
static void Main(string[] args)
{
Person person = new Person();//实例化一个person类
Person.doSomething? dosomething;//实例化一个委托变量
dosomething = eat;//等于函数eat
dosomething += play;//添加函数play
dosomething -= play;//删除函数play
//匿名函数添加
dosomething += delegate ()
{
Console.WriteLine("睡觉");
};
//lambda表达式方式添加,自我感觉很像JavaScript中的箭头函数
dosomething += () =>
{
Console.WriteLine("lambda表达式");
};
person.run(dosomething);//委托运行dosomething
}
}
}
基本语法完结!
文章内容是根据书籍《新印象 Unity 2020游戏开发基础与实战》随书附赠的C#入门讲解视频对于存在C++基础的同学总结而来,节省部分时间,同时也作为本人的学习笔记。如有错误或不足之处还请联系我指出进行修改,文章内容存在部分本人自我理解,具体标准内容请以官方文档为主。
- 本博客所拥有的文章除特别声明外,均默认采用 CC BY 4.0 许可协议。
- 文章部分内容可能来源于公共网络,如有侵权,请联系博主在核实后进行修改或删除。
暂无评论内容