1.C#相关知识篇
1.1 基础类型
关键字 |
说明 |
字节大小 |
bool |
逻辑值(真/假) |
1 |
sbyte |
有符号8位数 |
1 |
byte |
无符号8位数 |
1 |
short |
有符号16位数 |
2 |
ushort |
无符号16位数 |
2 |
int |
有符号32位数 |
4 |
uint |
无符号32位数 |
4 |
long |
有符号64位数 |
8 |
ulong |
无符号64位数 |
8 |
char |
16位字符类型 |
2 |
float |
32位双精度浮点类型 |
4 |
double |
64位双精度浮点类型 |
8 |
decimal |
128位高精度浮点类型 |
16 |
基础类型的作用是规定容器的大小,约束内存中放什么类型的数据
常量:程序运行期间,不能被改变的量。
变量:程序运行期间,可以被改变的量。定义的格式:类型修饰符 变量名= 初始值,列如: int age = 23;
赋值运算符“=”,
算数运算符 +、-、*、/、%、++、--
关系运算符:”==”、”>=””<=”“!=”
逻辑运算符:&&(与:两边为真即为真,两边一假即为假)、||(或:两边一真即为真,两边为假即为假)、!(非:取反操作)
表达式:由变量、常量与运算符组成
输出函数:System.Console.WriteLine(“Hello World!”);
基本输入函数:Console.ReadLine();
隐式转换:由小到大时系统会自动进行隐式转换,如:int到float就是隐式转换。
强制转换:显示转换需要签字转换运算符,由大变小需要用强制转换如:
(1)使用(类型名)变量名
Float a = 4.5f;
Int b = (int)a;
(2)使用Parse,键字符串转化为其他数据类型
String c = “123.2”;
Float n = float.Parse(c)+1;
(3)使用Convert进行类型转换
String n = “123”;
Int d= Convert.ToInt32(n);
C#当中的命名规范:
每个单词开头的字母大写(如 TestCounter).
除了第一个单词外的其他单词的开头字母大写. 如. testCounter.
仅用于一两个字符长的常量的缩写命名,超过三个字符长度应该应用Pascal规则.
变量命名
- 在简单的循环语句中计数器变量使用i, j, k, l, m, n
- 使用 Camel 命名规则
方法命名
- 使用Pascal规则
- 对方法名采用一致的动词/宾语或宾语/动词顺序。例如,将动词置于前面时,所使用的名称诸如InsertWidget 和InsertSprocket;将宾语置于前面时,所使用的名称诸如WidgetInsert 和SprocketInsert。
- 推荐名称应该为动词或动词短语.例如Save,SaveCustomer,而不要使用CustomerSave
- 不要在方法中重复类的名称。例如,如果某个类已命名为Book,则不要将某个方法称为Book.CloseBook,而可以将方法命名为Book.Close。
属性命名
- 名称应该为名词及名词短语
- 使用Pascal规则
- 对于bool型属性或者变量使用Is(is)作为前缀,不要使用Flag后缀,例如应该使用IsDeleted,而不要使用DeleteFlag
集合命名
- 名称应该为名词及名词短语
- 使用Pascal规则
- 名称后面追加“Collection”
事件命名
- event handlers命名使用EventHandler 后缀.
- 两个参数分别使用sender 及 e
- 使用Pascal规则
- 事件参数使用EventArgs后缀
- 事件命名使用语法时态反映其激发的状态,例如Changed,Changing.
- 考虑使用动词命名.变量命名
- 在简单的循环语句中计数器变量使用i, j, k, l, m, n
- 使用 Camel 命名规则
枚举命名规则
- 对于 Enum 类型和值名称使用 Pascal 大小写。
- 少用缩写。
- 不要在 Enum 类型名称上使用 Enum 后缀。
- 对大多数 Enum 类型使用单数名称,但是对作为位域的 Enum 类型使用复数名称。
- 总是将 FlagsAttribute 添加到位域 Enum 类型。
类命名指导
- 类名应该为名词及名词短语,尽可能使用完整的词.
- 使用Pascal规则
- 不要使用类前缀 - 不要使用下划线字符 (_)。
- 有时候需要提供以字母 I 开始的类名称,虽然该类不是接口。只要 I 是作为类名称组成部分的整个单词的第一个字母,这便是适当的。例如,类名称 IdentityStore 就是适当的。
- 在适当的地方,使用复合单词命名派生的类。派生类名称的第二个部分应当是基类的名称。例如,ApplicationException对于从名为 Exception 的类派生的类是适当的名称,原因是ApplicationException 是一种 Exception。请在应用该规则时进行合理的判断。例如,Button 对于从 Control 派生的类是适当的名称。尽管按钮是一种控件,但是将 Control 作为类名称的一部分将使名称不必要地加长。
1.2 C#程序三大结构
1.2.1 顺序结构:程序入口都是Main函数,代码从上往下该,从左往右,依次执行
1.2.2 分支结构:进行条件判断,根据判断的结构执行不同的操作
第一种形式
If(条件表达式)
{
语句1;
}
第二种形式
if (条件表达式) { 语句1; } else { 语句2; }
第三种形式(级联式)
if(条件表达式1){ 语句1; } else if(条件表达式2){ 语句2; } else { 语句3; }
switch语句:
•多分支语句,通过判断表达式的值,来决定执行哪个分支。
• switch通常与case配合使用。
• switch-开关
• case -情况
• 语法格式: switch(表达式)
{
case 值1:{语句1;break;}
case 值2:{语句2;break;}
...
case 值n:{语句n;break;}
default:{语句n + 1;break;} }
• 执行过程: 根据表达式的值,取大括号中查找与该值相匹配的分支。 如果匹配成功,就立即执行分支后对应的语句,直到遇到break关键 字,跳出switch继续往下执行。 如果没有匹配的结果,就执行default后的语句,直到遇到break关 键字,结束switch语句。
switch与if-else
• switch语句和if-else级联式比较,switch语句往往比级联式更容易阅读,更直观。
• switch—开关,case—情况,开关决定发生的情况
注意事项
• 整个default语句都可以舍掉,default语句只能有一个。
• switch(表达式)里面的表达式类型不能是浮点类型。
• case后边的标签只能是常量或者常量表达式,不能用变量作为case的标签。
• case后面只要有操作,最后一定要加break。
• 多个case可以运行一个语句块。
1.2.3 循环结构:在满足某个条件的时候反复执行一个语句序列(循环)
While(bool表达式)
{
语句
}
条件表达式为真,执行循环体,一旦表达式为假,循环停止
For(循环变量初始化;循环条件;循环增量)
{
语句
}
循环条件为真,执行循环体
总结:
For最常用, 通常用于指导循环次数的循环
While也很常用,通常用于不知道循环次数的循环
do…while不是特别常用,通常用于需要执行一次的循环
break:跳出本层循环,continue结束本次循环
1.3 数组
概念:数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。
数组初始化:动态初始化和静态初始化
动态初始化:数据类型[] 数组名 =new 数据类型[数组长度]{元素1,元素2};列如:int[]array = new int[6]{};
静态初始化: 数据类型[] 数组名 ={元素1,元素2,。。,元素3};列如:int arr = {1,2,1,3};
数组访问:数组名+下标,下标是0-数组名.Length-1;
数组属于引用类型.
二维数组与简单排序方法:
使用数组时,很多时候需要将数组中的元素按照一定条件进 行排序
比如:整型数组从小到大排序;学生姓名按升序排列等等
常见的排序方法很多: 比如:冒泡排序、选择排序、插入排序、归并排序等等。
冒泡排序:
(从小到大排序)存在10个不同大小的气泡,由底至上地把 较少的气泡逐步地向上升,这样经过遍历一次后,最小的气泡就会被上升到顶(下标为0),然后再从底至上地这样 升,循环直至十个气泡大小有序。
冒泡排序涉及到双循环,外层循环控制趟数,内层循环控制 比较次数。
具体代码实现:
int[] arr = new int[7] { 10, 53, 4, 78, 6, 3,56 };
for(int i = 0; i < arr.Length - 1; i++)
{
//因为之前循环的数都已经排列好顺序,所以不必再次运行一次了
for(int j = 0; j < arr.Length - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int temp;
temp = arr[j];
arr[j] = arr[j +1];
arr[j + 1] = temp;
}
}
}
for(int i = 0; i < arr.Length; i++)
{
Console.Write(arr[i] + " ");
}
选择排序:
int[] arr = new int[7] { 10, 53, 4, 78, 6, 3,56 };
int min;
int minIndex;
for (int i = 0; i < arr.Length; i++)
{
min= arr[i];
minIndex = i;
for(int j = i + 1; j < arr.Length; j++)
{
if (arr[j] < min)
{
min = arr[j];
minIndex = j;
}
}
arr[minIndex] = arr[i];
arr[i] = min;
}
for (int i = 0; i < arr.Length; i++)
{
Console.Write(arr[i] + " ");
}
二分查找:
二分查找又称折半查找,优点是比较次数少, 查找速度快,平均性能好;其缺点是要求待查 表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列 表。首先,假设表中元素是按升序排列,将表 中间位置记录的关键字与查找关键字比较,如 果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置 记录的关键字大于查找关键字,则进一步查找 前一子表,否则进一步查找后一子表。重复以 上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成 功。
int[] arr = new int[8] { 1, 2, 3, 4, 5, 6, 7, 8};
int len = arr.Length;
int low = 0;
int high = arr.Length-1; //减一是因为运行是以防数组越界
int x = int.Parse(Console.ReadLine());
while (low <= high)
{
intmid = (low + high) / 2;
if(arr[mid] == x)
{
int count = mid + 1;
Console.WriteLine(x + "是第" +count + "个数");
break;
}
elseif (arr[mid] < x)
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
if (low > high)
{
Console.WriteLine("该数不存在");
}
C#的foreach循环语句用于对数组、字符串及集合类型
foreach循环语句格式:
foreach(迭代类型 迭代变量名 in 数组名) {
//foreach循环语句循环体
}
数组局限性:
数组不能动态的扩展长度
数组只能存储相同类型的数据
1.4引用类型和值类型
C#的所有值类型均隐式派生自System.ValueType:
· 结构体:struct(直接派生于System.ValueType);
· 数值类型:
· 整型:sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long (System.Int64),byte(System.Byte),ushort(System.UInt16),uint (System.UInt32),ulong(System.UInt64),char(System.Char);
· 浮点型:float(System.Single),double(System.Double);
· 用于财务计算的高精度decimal型:decimal(System.Decimal)。
· bool型:bool(System.Boolean的别名);
· 用户定义的结构体(派生于System.ValueType)。
· 枚举:enum(派生于System.Enum);
· 可空类型(派生于System.Nullable
值类型(Value Type),值类型实例通常分配在线程的堆栈(stack)上,并且不包含任何指向实例数据的指针,因为变量本身就包含了其实例数据
C#有以下一些引用类型:
· 数组(派生于System.Array)
· 用户用定义的以下类型:
· 类:class(派生于System.Object);
· 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。主要是接口必须得要别人来实现它,实体类继承了借口,就必须实现接口里面的方法,这一点和抽象类很像,但两者有不同的区别,不要轻易搞混);
· 委托:delegate(派生于System.Delegate)。
· object(System.Object的别名);
简单的来说就是值类型可以存储数值,而引用类型存的是地址;值类型是有系统分配空间在栈上,而引用类型是要由程序员用关键字“new”来在堆上分配空间的。
1.5 枚举与结构体
定义:枚举类型为定义了一组可以赋值给变量的命名整数常 量提供了一种有效方法,枚举是一种值类型。
枚举经常应用于对取值有限定的地方,比如星期只能是周一 到周日,月份只能是一月到十二月
枚举的作用:在程序当中适当的使用枚举具有以下优点: 1. 规范化了参数形式,确保给变量指定的都是合法的值 2. 使用了描述性的名称来替代整数值提高了代码的可读性, 使代码更易于维护 3. 易于键入值,当我们在给枚举变量做赋值操作会弹出弹框,弹框包含了该枚举类型的所有枚举值
声明:
枚举的声明使用enum关键字,总共有以下三种情况基础类型为默认 格式: enum 枚举名{枚举数0,枚举数1......}
例如: enum Season{ Spring,Summer,Autumn,Winter }
3 枚举数与关联值的相互转换 获取枚举数关联的值需要对枚举值进行强制类型转换: int intValue = (int)Season.Spring;
打印intValue结果为0。
获得某个值所对应的枚举数同样要进行强制类型转换: Season autumn =(Season)2; 打印autumn结果为Autumn。 注意:当不存在该枚举数的时候则返回原数值。
获取某一整数值关联的枚举数的名称
String seasonStr0 =Enum.GetName(typeof(Season),0);
//结果:seasonStr0 =“Spring”;
总结:
1.定义枚举使用关键字enum
2. 枚举默认的基础类型是int,可以指定除char之外其他整型。
3. 枚举是值类型
4. 使用枚举可以确保变量的合法性,提高代码的可读性,使代码 更易于维护
结构体定义:
结构体是一种值类型,通常用来封装小型相关变量组。例如 坐标或者商品的特征。
结构体是一种自定义的数据类型,相当于一个复合容器,可以存储 多种类型。 结构体由结构体成员构成,结构体成员包含字段,属性与方法
声明:结构体的声明使用关键字struct 格式:struct 结构体名{ 结构体成员}
注意:字段不能有初始值,只能声明
struct Weapon
{//用来表示武器的结构体
public string name;
public int physicalDefense;//物理防御
public int maxHP;//+大生命
}
构造函数
1.结构体不能声明默认的构造函数,否则会报错
2.结构体只能声明带参数的构造函数,格式为 public 结构体名(参数列表)
3.在结构体的自定义构造函数当中要为所有的字段进行初始化
4.不管有没自定义构造函数,结构体默认构造函数一直存在
访问修饰符
1. 访问修饰符:所有类型和类型成员都具有可访问性级别,用来 控制是否可以程序集的其他代码中或其他程序集中使用它们。
2. 格式:访问修饰符 类型/类型成员(即在类型或类型成员的 声明加上访问修饰符)
访问修饰符的作用:设置了成员的可访问限制,提高代码的安全性。
常用的访问修饰符
1. public:同一程序集中的任何其他代码或引用该程序集的其他程序集都可以访问该类型或成员
2. private:只有同一类或结构中的代码可以访问该类型或成员
3. protected:只有同一类或结构或者此类的派生类中的代码才可以访问的类型或成员
4. Internal:同一程序集中的任何代码都可以访问该类型或成员,但其他程序集中的代码不可以
5. protected internal:访问限制在当前程序集或从包含派生的类型的类别
1.6 面对对象
面向对象以对象为中心,强调完成某一件事情需要哪些对象相互 协作来完成,参与该事情的对象需要具备哪些功能。同时,该事 件也是所有事件当中的某一小功能。比如将大象放进冰箱强调事物的参与者就是冰箱与大象.
1. 面向对象具有良好的可扩展性和重用性
2. 面向对象可以降低我们代码的耦合度,提高代码的可移植性
3. 面向对象接近于我们日常生活和自然的思考方式,可以提高我们代码软件开发的效率与质量
1、面向对象三大特征:封装、继承、多态
2、封装具体的表现是类,类是将具有相同特征的事物封装在一起,
存储的是一类事物的属性及方法;对象是类的具体实现,
表示的是一个具体的事物;
比如学生可看作是一类事物,包含学生姓名、学生证编号、年龄、专业、班级等等;
而张三是学生类的一个对象,每个具体的对象都包含学生类的
属性和方法;但是每个对象的属性值和方法执行的结果都是不一样的(静态属性及静态方法除外);
3、四种修饰符的访问权限:
1、private:本类中可访问
2、protected:本类及本类的子类中可访问
3、internal:本程序集中可访问
4、public:完全可访问
字段的默认修饰符为private;方法的默认修饰符是internal;顶级类默认修饰符为internal(只能是public或internal);
4、构造函数(方法):public 类名(){}
1、无返回值
2、方法名必须是类名
3、如无构造函数则系统默认一个无参构造函数;
4、可定义多个构造函数(重载:参数类型不同,个数不同,顺序不同)
5、静态属性与静态构造函数(静态构造函数的执行是系统进行控制的)
静态构造函数执行的情况有两种:
1)、实例化一个对象时(创建对象);
2)、调用静态属性时,类名.静态属性
5、属性与字段的定义;字段原则上都是private类型,属性为public类型,属性的定义方式为:
6、引用参数:值类型参数想要达到引用类型参数的效果,需要用到引用参数。 引用参数以ref修饰符声明
输出参数: 如果想要一个方法返回多个值,可以用输出参数来处理.输出参 数由out关键字标识,既它与普通形参相比只多了个out修饰
数组参数:如果形参表中包含了数组型参数,那么它必须在参数表中
位于最后,而且必须是一维数组类型。另外,数组型参数不可能将params修饰符与ref和out修饰符组合起来使用
值参数:不附加任何修饰符。
输出参数:以out修饰符声明,可返回一 个或多个值给 调用者。
引用参数:以ref修饰符声明。
数组参数:以params修饰符声明
方法重载是指在同一个类中方法同名,参数不同,调用时根据实 参的形式,选择与他匹配的方法执行操作的一种技术。
这里所说的参数不同是指以下几种情况:
1、参数的类型不同
2、参数的个数不同
3、参数的个数相同时他们的先后顺序不同
递归
函数体内调用本函数自身,知道符合某一条件不再继续调用
注:简单说就是让函数先执行到满足条件的那一步,然后带着数据开始调用函数本身
满足的条件:
1、有反复执行的过程(调用自身)
2、有跳出反复执行过程的条件(函数出口)
递归一般用于解决三类问题:
(1)数据的定义是按递归定义的。(Fibonacci函数,n的阶乘)
(2)问题解法按递归实现。(回溯) (
3)数据的结构形式是按递归定义的。(二叉树的遍历,图的搜索)
递归的缺点:
递归解题相对常用的算法如普通循环等,运行效率较低。因此,应该尽量避免使 用递归,除非没有更好的算法或者某种特定情况,递归更为适合的时候。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,因此递归次数过多容 易造成栈溢出。
1.7 string类、字符串以及StringBuild类
简单的来说就是值类型可以存储数值,而引用类型存的是地址;值类型是有系统分配空间在栈上,而引用类型是要由程序员用关键字“new”来在堆上分配空间的。
大家也看到了string也是属于引用类型的,接下来我要讲一讲string这个东西:
[csharp] view plain copy
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Threading.Tasks;
6.
7. namespace ConsoleApp4
8. {
9. class Program
10. {
11. static void Main(string[] args)
12. {
13. //1、先定义一个字符串
14. string str = "小编";
15. //2、给字符串添加另一个字符串
16. str += "最帅";
17. //3、打印出来str
18. Console.WriteLine(str);
19. }
20. }
21. }
输出:
上面就是字符串的添加了,其实大家看到以为是在同一个字符串后面添加了“最帅”两个字,其实不是,而是系统重新分配了个空间,把“小编最帅”这个字符串装了进去,然后让str变量重新指向新生成的空间地址.
当然C#还有自己一套的处理字符串的API方法,我们先快速介绍下String类中一些可用的方法。System.String是一个专门存放字符串的类,允许字符串进行许多操作。由于这种数据类型非常重要,C#提供了它自己的关键字和相关的语法,以便于使用这
个类来轻松地处理字符串。
这个类可以完成很多的常见的任务,如替换字符串、删除空白字符串和把字母变成大写形式等。
方法: |
作用: |
Compare |
比较字符串的内容,判断字符串某些字符是否相等 |
Contains |
返回一个值,该值只是指定的子串是否出现在字符串中 |
Remove |
返回指定数量字符在当前这个实例起始点在已删除的指定的位置的新字符串 |
Concat |
把多个字符串实例合并为一个实例 |
CopyTo |
把从选定的下标开始的特定数量的字符复制到数组的一个全新实例中 |
Format |
格式化包含各种值的字符串和如何格式化每个值的说明符 |
IndexOf |
定位字符串中第一次出现某个给定子字符串或字符的位置 |
IndexOfAny |
定位字符串中第一次出现某个字符或一组字符的位置 |
Insert |
把一个字符串实例插入到另一个字符串实例的指定索引处 |
Join |
合并字符串数组,创建一个新的字符串 |
LastIndexOf |
与IndexOf一样,但定位最后一次出现的位置 |
LastIndexOfAny |
与IndexOfAny一样,但定位最后一次出现的位置 |
PadLeft |
在字符串的左侧,通过添加指定的重复字符填充字符串 |
PadRight |
早字符串的右侧,通过添加指定的重复字符填充字符串 |
Replace |
用另一个字符或子字符串替换字符串中给定的字符或子字符串 |
Split |
在出现给定字符的地方,把字符串拆分为一个子字符串数组 |
Substring |
在字符串中检索给定位置的子字符串 |
ToLower |
把字符串转换为小写形式 |
ToUpper |
把字符串转换为大写形式 |
Trim |
删除首尾的空白 |
...... |
..... |
Replace()方法以一种智能的方式工作,在某种程度上,它并没有去创建一个新的字符串,除非它实际上要对旧字符串进行某些改变,原来的字符串中包含多个字母,所以在调用一次Replace()方法就会去分配一个新字符串,因此在执行的过程中需要在堆上创建多个存储字符的字符串对象,最终等待被垃圾收集!显然,如果使用字符串频繁进行文字处理,应用程序会遇到严重的性能问题。为了解决这类问题,系统提供了System.Text.StringBuilder类。
在使用String类构造一个字符串时,要给它分配足够的内存来保存字符串,然而,StringBuilder类通常分配的内存会比它需要的多。我们可以指定StringBuilder要分配多少内存,如果没有指定,那么默认情况下会根据它实例的字符串长度来确定内存的大
小。 StringBuilder有两个主要的属性:
· Length 指定字符串的实际长度
· Capacity 指定字符串在分配的内存中的最大长度
对字符串的修改就在赋予StringBuilder实例的内存中进行,这就大大提高了追加子字符串和替换单个字符的效率。
下面我们来介绍下StringBuilder的声明方法,它的参数是一个初始字符串及该字符串的容量,使用之前记得引用System.Text程序集
[csharp] view plain copy
1. //1、只提供一个字符串
2. StringBuilder strB1 = new StringBuilder("Hello World!");
3. //2、用给定的容量来创建一个空的
4. StringBuilder strB2 = new StringBuilder(20);
5. //3、可以限定StringBuilder的最大容量
6. StringBuilder strB3 = new StringBuilder(20, 100);
下面是一些StringBuilder的常用方法
方法: |
作用: |
Append |
给当前字符串追加一个字符串 |
AppendFormat |
追加特定格式的字符串 |
Insert |
在当前字符串中插入一个子字符串 |
Remove |
从当前字符串中删除字符 |
Replace |
在当前字符串中,用某个字符(串)替换另一个字符(串) |
ToString |
返回强制转换为System.String对象的字符串 |
下面是StringBuilder的常用方法的简单使用
[csharp] view plain copy
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Threading.Tasks;
6.
7. namespace ConsoleApp4
8. {
9. class Program
10. {
11. static void Main(string[] args)
12. {
13. //1、只提供一个字符串
14. StringBuilder strB1 = new StringBuilder("Hello World!");
15. strB1.Append("你好,世界");
16. Console.WriteLine(strB1);
17.
18. strB1.AppendFormat("钱{0:C}",100);
19. Console.WriteLine(strB1);
20.
21. strB1.Insert(2,"0",2);
22. Console.WriteLine(strB1);
23.
24. strB1.Remove(2,2);
25. Console.WriteLine(strB1);
26.
27. strB1.Replace("Hello","Welcome");
28. Console.WriteLine(strB1);
29.
30. strB1.ToString();
31. Console.WriteLine(strB1);
32. }
33. }
34. }
输出:
1.8 抽象类、静态类与单列
抽象类:
有抽象方法的类一定是在抽象类中。但是在抽象类中不一定要有抽象方法。同样的也是用abstract修饰符。
抽象类不能被实例化。
抽象类可以写虚方法,可以写方法,可以写构造方法,抽象类的虚方法还可以调用抽象方法,不会报错,主要是不能被实例化,其它与普通类没有大的区别。
被继承的具体类一定要实现抽象类中所有的抽象方法。所以这句话的另一层含义就是如果继承一个抽象类A的还是一个抽象类B,那么抽象类B可以实现父类A中的抽象方法,也可以不实现父类A中的抽象方法。但是最终还是有一个具体类实现它父类中所以没有被实现的抽象方法。
抽象方法是一种特殊的虚方法,它只起声明作用,所以只加“ ; ”号,一定不能带实现。用抽象方法就是因为在类中可以不用实现的时候,没必要写一个虚方法。
抽象方法要用abstract修饰。访问修饰符不能用private!
抽象方法特性
1. 抽象方法是隐式的虚方法
2. 抽象方法只能有声明不提供实际的实现
3. 只允许在抽象类中使用抽象方法声明
4. 抽象方法必须被子类重写并实现,否则其子类依旧为抽象类
5. 抽象方法不使用static或virtual修饰
静态类和类成员用于创建无需创建类的实例就能够访问的数据和函数。静态类成员可用于分离独立于任何对象标识的数据和行为:无论对象发生什么更改,这些数据和函数都不会随之变化。当类中没有依赖对象标识的数据或行为时,就可以使用静态类。
静态类
类可以声明为 static的,以指示它仅包含静态成员。不能使用 new关键字创建静态类的实例。静态类在加载包含该类的程序或命名空间时由.NET Framework 公共语言运行库 (CLR) 自动加载。
使用静态类来包含不与特定对象关联的方法。例如,创建一组不操作实例数据并且不与代码中的特定对象关联的方法是很常见的要求。您应该使用静态类来包含那些方法。
静态类的主要功能如下:
它们仅包含静态成员。
它们不能被实例化。
它们是密封的。
它们不能包含实力构造函数。
因此创建静态类与创建仅包含静态成员和私有构造函数的类大致一样。私有构造函数阻止类被实例化。
使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实利。
静态类是密封的,因此不可被继承。静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态
1.静态成员随着类的加载而加载,无论对一个类创建多少个实例,静态 成员都只有一个副本
2.静态方法可以被重载但不能被重写,因为他们是属于类,不属于任何 实例的 3.静态成员由类名通过点语法调用,非静态成员是由对象来调用
4.静态方法只能访问静态成员,包括静态成员变量和静态方法;实例方 法可以访问实例成员变量与实例方法,也可以访问静态成员
1. 静态构造函数没有修饰符修饰(public,private),因为静态构造函数不是我们程序员调用的,是由.net 框架在合适的时机调用的。
2. 静态构造函数没有参数,因为框架不可能知道我们需要在函数中添加什么参数,所以规定不能使用参数。
3. 静态构造函数前面必须是static 关键字。如果不加这个关键字,那就是普通的构造函数了。
4. 静态构造函数中不能实例化实例变量。(变量可以分为类级别和实例级别的变量,其中类级别的有static关键字修饰)。
5. 静态函数的调用时机,是在类被实例化或者静态成员被调用的时候进行调用,并且是由.net框架来调用静态构造函数来初始化静态成员变量。
6. 一个类中只能有一个静态构造函数。
7. 无参数的静态构造函数和无参数的构造函数是可以并存的。因为他们一个属于类级别,一个属于实例级别,并不冲突。
8. 静态构造函数只会被执行一次。并且是在特点5中的调用时机中进行调用。
9. 就像如果没有在类中写构造函数,那么框架会为我们生成一个构造函数,那么如果我们在类中定义了静态变量,但是又没有定义静态构造函数,那么框架也会帮助我们来生成一个静态构造函数来让框架自身来调用。
设计模式:
设计模式是一套被反复使用,多数人知晓,经过分类,代码设计经验的总结,简单理解就是前人代码设计的经 验,简称“前任攻略”。常用的设计模式有单例模式,工厂模式,代理模式。
单列模式:
一个类只能有一个实例那么这满足我们的需求,我们将满足某个类只有一个实例的代码设计方式成为单列模式:
单例模式的三要点:
(1)某个类只能有一个实例
(2)必须自行创建这个实例
(3)必须自行向外界提供这个实例
单例模式代码实现步骤:
public class SingleTon
{
private SingleTon (){} //构造方法必须私有化
private static SingleTon instance; //定义静态实例
static SingleTon()
{//静态构造函数中对静态实例进行初始化
instance = new SingleTon ();
}
public static SingleTon Instance
{//对外提供获取该实例的接口
get{
if (instance == null)
{
instance = new SingleTon ();
}
return instance;
}
}
}
单例模式作用:单例模式是资源管理的必备模式,单例类只有一个实例对象,因此可 以通过该实例对象获取到管理的资源。 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。 因为类控制了实例化过程,所以类可以灵活更改实例化过程。 单例类能够节省大量创建对像的过程,从而节省内存开销。
设计模式大概有23总之多,像我在项目期还使用过”模板方法模式”,简单的来说就是建造一个基类,让子类去实现基类里面实现的方法。
具体模板:ConcreteClass1和ConcreteClass2属于具体模板,实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现
使用场景:
● 多个子类有公有的方法,并且逻辑基本相同时。
● 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
● 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(见“模板方法模式的扩展”)约束其行为。
还有状态模式,根据人物状态来控制其的行为,我们项目期的时候,我们的敌人AI就是用的状态模式
定义:Allow an object to alter its behavior whenits internal state changes.The object will appear to change its class.(当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。)
● State——抽象状态角色
接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。
● ConcreteState——具体状态角色
每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
● Context——环境角色
定义客户端需要的接口,并且负责具体状态的切换。
使用场景:
● 行为随状态改变而改变的场景
这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。
● 条件、分支判断语句的替代者
注意:
状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个。
1.9 接口与泛型
接口:接口是一组包含了类或结构可以实现的功能的定义
由于C#只支持单继承,所以接口支持多实现的特性可以在一定程序上弥补该不 足。我们可以通过interface关键字定义接口,如:
Interface Ifly
{
VoidFly();
}
接口的特性:
1. 接口是一个引用类型,只包含了功能的定义,不包含功能的实现
2. C#接口成员的访问级别是默认的(默认为public),不可以使用 其他修饰词修饰
3. C#接口成员不能有static,abstract,override,virtual修饰
4. 接口一旦被实现,就必须实现接口当中的所有成员,除非实现类 本身是抽象类.口无法直接进行实例化,因为其成员必须通过由实现接口的任何类或 结构来实现
5. 接口可以包含事件,索引器,方法和属性,但是不能包含字段。
6. 类的继承只能单继承,接口的实现支持多实
注意⚠:接口中方法的定义不允许加上访问修饰符,默认修饰符为public。 接口的实现:在实现接口的类或结构后使用冒号加上要实现的接口名。 实现多个接口时,多个接口之间使用逗号隔开。类实现接口就必须实现接口当中定义的所有方法,除非该类是抽象类
接口间的继承:接口可以像类一样继承,并且接口支持多继承。当 接口A继承了接口B之后便拥有了接口B当中声明的方法。某个类实 现了接口A则必须实现接口A与接口B当中的所有方法。与类继承的差别:派生类继承了基类的方法的声明与实现,而派生 接口只继承了父接口的成员方法说明
有些初学者很容易把接口和抽象类弄混。接下里说说这两者的区别:
1. 抽象类当中除了拥有抽象成员外还可以拥有非抽象成员;而接口中所有 的所有成员都是抽象的
2. 抽象成员可以使用修饰符修饰,接口当中接口成员访问级别是默认不可 修改的,并且默认是public
3. 接口当中不可以包含构造方法,析构方法,静态成员以及常量
4. C#类只支持单继承,接口支持多支持
泛型:型能够将类型作为参数来传递,即在创建类型时用一个特定 的符号如“T”来作为一个占位符,代替实际的类型,等待在实例化时再用一个实际的类型来代替
泛型的优点:
1. 使用泛型可以的重用代码,保护类型的安全以及提高性能
2. 降低强制转换或装箱操作的成本和风险
3. 可以对泛型参数进行限定以访问特定数据类型的方法
泛型类型参数:
泛型类型或方法定义中,类型参数是客户端在实例化泛型类型的 变量时指定的特定类型的占位符。在上面例子泛型方法当中T即为泛 型类型参数
泛型类型参数常用的约束类型
1. where T:struct :表示泛型T是值类型(小数,整数,char,bool, struct) 2. where T:class :表示泛型T是引用类型
3. where T:new() :表示这个泛型具有一个无参数的构造方法,如果有 多个约束,new()必须放在后
4. where T:基类名 :表示这个泛型是该基类或者其派生类
5. where T:接口名 :表示泛型是实现了该接口的类型
泛型方法是使用泛型类型参数声明的方法,当方法中存在某些参数的 类型不明确的时候就可以使用泛型方法。未知具体类型的参数就使用泛型类型参数替代 格式: 访问修饰符 返回值类型 方法名<泛型类型参数>(参数列表){ 方法体。 }
public static string CombineToString
{
return value1.ToString () + value2.ToString ();
}
1.10 集合
集合的定义:集合原本是数学上的一个概念,表示一组具有某种性质的数学元素, 引用到程序设计中表示一组具有相同性质的对象。集合好比容器,将一系列相似的组合一起,是一个特殊的类,和数组一样,可以通 过索引访问集合成员,也可以通过”键”来访问.与传统数组不同 的是,集合的大小可以动态调整,可以在运行时添加或删除元素。
泛型集合与非泛型集合 泛型集合类一般位于System.Collections.Generic名称空间,非泛型集合类位于System.Collections名称空间。 此外,System.Collections.Specialized名称空间中也包含一些有用的集合类。
下面是各种常用的System.Collection 命名空间的类
类 |
描述和用法 |
动态数组(ArrayList) |
它代表了可被单独索引的对象的有序集合。 它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定 的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表 中进行动态内存分配、增加、搜索、排序各项。 |
堆栈(Stack) |
它代表了一个后进先出的对象集合。 当您需要对各项进行后进先出的访问时,则使用堆栈。当您在列表中添加一项, 称为推入元素,当您从列表中移除一项时,称为弹出元素。 |
队列(Queue) |
它代表了一个先进先出的对象集合。 当您需要对各项进行先进先出的访问时,则使用队列。当您在列表中添加一项, 称为入队,当您从列表中移除一项时,称为出队。 |
哈希表(HashTable) |
它使用键来访问集合中的元素。 当您使用键访问元素时,则使用哈希表,而且您可以识别一个有用的键值。哈 希表中的每一项都有一个键/值对。键用于访问集合中的项目。 |
下面是各种常用的System.Collection.Generic 命名空间的类
类 |
描述和用法 |
Dictionary |
同哈希表一样,标示根据键进行的键值对的集合.不同的是对键值进行了类型限定 |
List |
同ArrayList一样,表示通过索引访问对象的列表.不同的是对存储的对象进行了类型 限定 |
Stack |
同Stack一样,不同的是对存储的对象进行了类型限定 |
Queue |
同Queue一样,不同的是对存储的对象进行了类型限定 |
• List类是ArrayList类的泛型等效类 • 同样继承了IList接口,IEnumrator接口和ICollection • 与ArrayList不同的是,声明集合时需要声明集合内部的数据类 型,即T的类型. • 安全的集合类型 • 某种情况时,在处理值类型时其处理速度比ArrayList快的多.
堆栈:
后进先出(LIFO)的一种数据结构,本质上堆栈也是一种线性结构
• 线性结构的基本特点:即每个节点有且只有一个前驱结点和一个后续节点
• 随着像Stack中添加元素,容量通过重新分配按需自动增加
• 可以接受null作为有效值
• 允许重复的元素
• 不安全的数据结构
• 其泛型为Stack
队列:
• 后进先出(FIFO)的一种数据结构
• 随着像Queue中添加元素,容量通过重新分配按需自动增加,可以 通过TrimToSize来减少容量
• 可以接受null作为有效值
• 允许重复的元素
• 不安全的数据结构
• 其泛型为Queue
• 在A*算法中会用优先级队列处理路径节点
哈希表:
• 处理和表现类似key-value的键值对的集合
• key值必须唯一,区分大小写
• Value可以是变量,也可以是对象
1.11 委托
委托的定义: delegate(委托)是表示将方法作为参数传递给其他方法.委托类似 于函数指针,但与函数指针不同的是,委托是面向对象的,类型安全的和保险的.委托既能引用静态方法,也能引用实例方法.
委托的声明:
class HelloWorld
{
//声明委托
publicdelegate void GreetingDelegate(string name);
...
}
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个 方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以 避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有 更好的可扩展性
如你所料,这样是没有问题的.委托不同于string的一个特性:可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委 托,当调用这个委托的时候,将依次调用其所绑定的方法。如下:
将方法绑定到委托:
HelloWorld.GreetingDelegate delegate1;
delegate1 =HelloWorld.EnglishGreeting;
delegate1 += HelloWorld.ChineseGreeting;
hw.GreetingPeople("jim", delegate1);
匿名函数:
Click click = delegate ()
{
Console.WriteLine("按钮被点击了2222");
};
click();
以前我们都是先声明委托,在赋值对应的方法 或者直接new关联一个方法.实际上匿名方法的 出现就是在初始化时内联声明的方法,使得委 托的语法更简洁.
系统委托:
Func委托的5种类型
1. delegate TResult Func
2. delegate TResult Func
3. delegate TResultFunc
4. delegate TResult Func
Action委托的5种类型
1.delegate void Action(); 无参,无返回值
2.delegate void Action
3.delegate void Action
4.delegate void Action
5.delegate void Action
注意:Func委托和Action委托唯一的区别就是在于代理的方法(函数)有没有返回值.有返回值选择Func委托. 没有返回值选择Action委托.
系统内置的两种委托绝大多数情况下可以适配任何 情况,无需我们在写大量的代码去定义委托.但是不 代表内置委托匹配任何情况.根据特殊情况还需我们 手动定义委托,切记.
1.12 事件
事件是说在发生其他类或对象关注的事情时,类或对象可通过事件通 知它们.事件的关键字是event.
public event CatShoutEventHandler CatShout; 这行代码的意思是声明事件CatShout,它的事件类型是委托 CatShoutEventHandler 正如上节课我们提到的,委托是一种引用方法的类型,一旦为委托分 配了方法,委托将与该方法具有完全相同的行为.
事件和委托的区别:
1.事件是一种特殊的委托的实例,或者说是受限制的委托,是委托 一种特殊应用,在类的外部只能施加+=,-=操作符,二者本质上是 一个东西。
2.事件只允许用add(+=),remove(-=)方法来操作,这导致了它不 允许在类的外部被直接触发,只能在类的内部适合的时机触发。委 托可以在外部被触发,但是别这么用。使用中,委托常用来表达回调,事件表达外发的接口。
3.事件不可以当作形参传递,但是委托可以.
观察者模式:
观察者模式又叫发布-订阅模式(Publish/Subscribe)
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监 听某一个主题对象.这个主体对象在状态发生变化时,会通知所有的 观察者对象,是他们能够自动更新自己
观察者模式一般会牵扯至少两个角色 Subject或Publish,叫做具体主题或通知者,将所有有关状态存入具 体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察 者发出通知 observer或subscribe类,具体观察者.
当一个对象的改变需要同时改变其他对象的时候,并且它不知道 具体有多少对象有待改变时,应该采用观察者模式
观察者模式所做的工作其实就是在解耦合.让耦合的双方都依赖 于抽象,而不是依赖具体.从而使得各自的变化都不会影响另一边的 变化
定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
● Subject被观察者
定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
● Observer观察者
观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。
● ConcreteSubject具体的被观察者
定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
● ConcreteObserver具体的观察者
每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。