目录:
一、C#语言编程基础
1:C#的数据类型和流程控制
1):数据类型
C#语言的数据类型分为两大类:值类型(value type)和引用类型(reference type)
值类型的变量包含其本身的数据,而引用类型的变量包含实际数据的引用(句柄),即引用类型变量是对真正包含数据的内存块的指向
C#语言的值类型包含结构类型和枚举类型(enum)
结构类型包括简单类型和自定义类型,简单类型有布尔类型、字符类型、数值类型等
简单类型 | 描述 | 示例 |
sbyte | 8-bit有符号整数 | sbyte val =12; |
byte | 8-bit无符号整数 | byte val1 =12; byte val2=34U; |
short | 16-bit有符号整数 | short val =12; |
ushort | 16-bit无符号整数 | ushort val1 =12;ushort val2 =34U; |
unit | 32-bit无符号整数 | unit val1 =12;uint val2 =34U; |
int | 32-bit有符号整数 | int val =12; |
long | 64-bit有符号整数 | long val1=12;long val2=34L; |
ulong | 64-bity无符号整数 | ulong val1=12;ulong val2=34U; ulong val3=54L;long val4=78UL; |
folat | 32-bit单精度浮点数 | float val =1.23F |
double | 64-bit双精度浮点数 | double val1=1.23; double val2=4.56D |
bool | 布尔 | bool val1=true |
char | 16-bit Unicode编码 | char val='h' |
decimal | 128-bit十进制类型 | decimal val=1.23M |
C#简单类型关键字及示例
/*
ASCII码:
一个二进制位(Bit)有0、1两种状态,一个字节(Byte)有8个二进制位,有256种状态,每种状态对应一个符号,就是256个符号,从00000000到11111111。
ASCII码规定了128个英文字符与二进制的对应关系,占用一个字节(实际上只占用了一个字节的后面7位,最前面1位统一规定为0)。例如,字母 a 的的ASCII码为 01100001,那么你暂时可以理解为字母 a 存储到内存之前会被转换为 01100001,读取时遇到 01100001 也会转换为 a。
完整的ASCII码表请查看:www.asciima.com
由于ASCII编码只占用1个字节,最多只能表示256个字符,所以Unicode应运而生
Unicode:
(统一码、万国码、单一码、标准万国码)是计算机科学领域里的一项业界标准,用以统一地体现和处理世界上大部分的文字系统,并为其编码。
Unicode 的编码方式与ISO10646的通用字符集(Universal Character Set, S)概念相对应,使用16位的编码空间。它固定使用16 bits(两个字节)来表示一个字符,也就是每个字符占用2个字节,共可以表示65536个字符,基本满足各种语言的使用。
*/
decimal主要用于金融、货币等对精度要求较高的计算环境
C#的引用类型包括类(class)、接口(interface)、数组(array)和委派(delegate)
类是自定义类型中最重要的渠道
C#已定义了两个比较特殊的类类型:object和string
object是C#中所有类型从其中继承的根类
stting类是一个密封类类型(不能被继承),该类对象表示用Unicode编码的字符串
接口类型定义了一个有关一系列方法的合同
委派类型是一个指向方法的签名,类似指针
这些类型实际上都是类的某种形式的包装
不同类型的数据之间可以相互转换,C#的类型转换有隐式转换、显示转换、标准转换、自定义转换
隐式转换和显示转换在之前的文章中有提到
零基础自学C#——Part2:C#中的运算符、语句、类型转换、数据结构_代码历险记的博客-CSDN博客
标准转换和自定义转换时针对系统内建转换而言的
值类型和引用类型之间的转换依靠装箱(boxing)和拆箱(unboxing)完成
装箱是将值类型变量隐式的转换为引用类型变量,它的具体过程是:
创建一个Object对象类型的实例,并把该值类型的值复制给该类型,它是隐式进行的,例如
int val =1000;
object obj =val;
拆箱先确认object类型实例的值是否为目标值类型的装箱值,如果是,将这实例的值复制给目标值类型的变量。拆箱操作必须显式的进行类型转换
int val =1000;
object obj= val; //装箱
int val = (int)obj; //拆箱
2):操作符与表达式
优先级 | 操作符 | 结合性 |
1 | . [] () new typeof sizeof checked unchecked | |
2 | ++ -- ~ ! + - (一元) | 右→左 |
3 | * / % | 左→右 |
4 | + - (二元) | 左→右 |
5 | << >> | 左→右 |
6 | < > <= >= is as | 左→右 |
7 | == != | 左→右 |
8 | & | 左→右 |
9 | ^ | 左→右 |
10 | | | 左→右 |
11 | && | 左→右 |
12 | || | 左→右 |
13 | ?: | 右→左 |
14 | = *= /= %= += -= <<= >>= &= ^= |= | 右→左 |
C#引入了几个具有特殊意义的操作符:as ,is , new , typeof.
new关键字用于创建对象和调用构造方法。值类型的对象是在栈上创建的,引用类型对象则是在堆上创建的
as操作符用于执行兼容类型之间的转换,当转换失败后,as操作符结果为null
is操作符用于检查对象的运行时类型是否与给定类型兼容,当表达式非null且可以转化为指定类型时,is操作符结果为true,否则为false
运算符typeof用于获取某一类型的System.Type对象。
3):流程控制
1)分支结构语句
if语句实现二路(多路)分支
switch语句实现多路分支
2)循环语句
while、do-while、for、foreach(C#独有)
更详细见
零基础自学C#——Part2:C#中的运算符、语句、类型转换、数据结构_代码历险记的博客-CSDN博客
3)转向语句
跳转语句右break、continue、goto、return、throw五种语句
3:类与对象
类与对象是面向对象的灵魂
1):对象的创建和使用
对象是类的实例,对象声明的格式为:
<类名> <对象名>;
对象的声明语句并没有实际创建新的对象,而需要new操作符在需要的时候创建新的对象,并为之分配内存:
<类名> <对象名>=new<与类同名的构造方法>(<参数列表>)
通过对象引用类的成员变量格式为:
<对象名>.<成员名>
通过对象调用成员方法的格式为:
<对象名>.<方法名>(<参数列表>)
C#中,只需要创建所需对象,而不需要显示地销毁,.NET平台的垃圾回收机制会自动判断对象是否还在使用,并能够自动销毁不再使用的对象,收回内存资源
2):类的声明
C#的类是一种用来定义封装包括数据成员和方法成员的数据类型的机制。
数据成员可以是常量和域
方法成员可以是方法、属性、索引器、事件、操作符方法、构造方法
在C#中,类的定义格式包括两个部分:类声明和类主体
类声明包括关键字class、类名及类的属性格式如下:
[<修饰符>] class <类名> [:<基类名>]
包含类主体的类结构如下:
<类声明>
{
<数据成员声明>
<方法成员声明>
}
声明常量成员用const关键字,并给出常量名和类型:
[<修饰符>] const <常量类型> <常量名> =<值>
声明字段成员必须给出变量名和类型,同时还可以指定其他特性:
[<修饰符>] [static] <变量类型> <变量名>
声明方法成员格式如下:
[<修饰符>] [static] <返回值类型> <方法名> (<参数列表>)
{
<方法体>
}
构造方法是一种特殊的方法成员,它具有与类名相同的名字,用于对类的实例进行初始化,构造方法的声明中不需要返回值类型。
属性提供对类的某个特征的访问,一个属性可以有两个访问操作符,分别是get和set,它们分别指定属性读取或写入新值得方式,属性定义格式如下:
[<修饰符>] [static] <类型> <属性名>
{
get{属性读取过程}
set{写入新属性值的过程}
}
//复数Complex类的框架定义p21
3):实例成员和类成员
C#类包括两种不同类型的成员:实例成员和类成员
类成员也称为静态成员,在类成员声明中,用static关键字声明静态成员,静态成员属于整个类,该类的所有实例共享静态成员。实例成员的声明没有static关键字,该类的每个实例都有自己转悠的实例成员。
4):类成员的访问权限控制
C#为类的成员定义了四种不同的访问权限限制修饰符
权限修饰符 | 本类 | 子类 | 本组合体 | 其他类 |
公有的(public) | √ | √ | √ | √ |
保护的(protected) | √ | √ | ||
内部的(internal) | √ | √ | √ | |
私有的(private) | √ |
4:类的继承
在C#程序中,除了object外每个类都有基类,如果没有显示地标明基类,则隐式地继承自object类
派生类继承基类中所有可被派生类访问的成员,当派生类成员和基类成员同名时,称派生类成员将基类同名成员隐藏,这时应在派生类成员定义中加入new关键字
对基类中声明的虚方法(virtual method),派生类可以重写(override),即为声明的方法提供新的实现
在C#中,通过this引用当前实例,访问自身的每个成员,通过base引用访问从基类继承的成员,在类中定义索引器也要用到this
5:抽象类和密封类
1)抽象方法与抽象类
当需要定义一个抽象概念时,可以声明一个抽象类,该类只描述抽象概念的结构,而不实现每个方法。
当声明一个方法为抽象方法时,则不需要提供该方法的实现,但这个方法或被派生类继续声明为抽象的,或被派生类实现,用关键字abstract来说明抽象方法。
任何包含抽象方法的类必须被声明为抽象类,抽象类不能直接被实例化
抽象类的派生类必须实现基类中的抽象方法,或将自己也声明为抽象的
2):密封类
不可以被继承,即不能有派生类,用sealed声明
6:接口
接口类似于类,但是它仅说明方法的形式,不需定义方法的实现,接口可视为是定义一组方法的合同。定义如下
[<修饰符>] interface <接口名> [:<基接口名>]
{
<方法一>
<方法二>
}
接口的所有成员都定义为公共成员(public),并且接口不能包含常量、字段(私有数据成员)、构造方法及任何类型的静态成员。
一旦定义了接口,一个或更多的类就能实现这个接口,即这个类将满足接口规定的合同,实现某接口的类声明的格式如下:
[<修饰符>] class <类名> : [<基类名>] , [<接口名>]
{
...
}
7:多态性
多态性(polymorphism)是指“一个接口,多个方法”,它有两种表现形式,重载和覆盖
1):方法的重载
一个类中如果有许多同名的方法带有不同的参数,称为方法的重载。
例如控制台输出方法Console.Write可以有不同的参数:
Console.Write(string a );
Console.Write(int b);
一个方法的名称和它的形参的个数及类型组成了方法的签名(signature),可以有多个同名的方法,但是每个方法的签名应该是唯一的,在重载时应该注意两点:
①:参数必须不同:可以是参数个数不同,也可以是参数类型不同,或者顺序不同
②:方法的签名不取决与方法的返回类型,即仅利用不同的返回类型无法区分方法
2):方法的覆盖
当一个虚方法通过某实例对象被调用时,该实例运行时类型(run-time type)决定哪个方法体被调用
子类覆盖父类的虚方法时,子类方法必须和父类中的方法有相同的签名
8:命名空间和程序集
命名空间(name space)和程序集(assembly)有助于组织开发基于组件的系统
1)命名空间定义一个声明空间,用于声明多个类型(类,结构,接口,枚举等)或嵌套的命名空间
2)程序集(也称组合体),用于物理打包和部署程序。程序集可以包含类型、用于实现这些类型的可执行代码、其他程序及的引用。有两种主要的程序集:应用程序和库,应用程序有一个主入口点,通常具有.exe文件拓展名,而库没有主入口点,通常具有.dll文件扩展名
/*
使用using命名空间指令来使得访问相应的命名空间中的所有类型时就不必加限定名称
比如在最上方使用using system .collections.Generic; 制定了命名空间
后面访问时候直接使用List来代替System .collections.Generic.List
*/
9:异常处理
在程序执行的过程中,无论什么时候发生了严重错误,.NET都会创建一个Exception的对象来描述和处理该错误,它是所有异常类的基类。
常见异常:
1)IndexOutOfRangeException:使用了大于数组或集合大小的索引
2)NullReferenceException:在将引用设置为有效的实例之前使用了引用的属性或方法
3)ArithmeticException:在操作产生溢出或下溢时发生异常
4)FormatException:参数或操作数格式不正确
当我们有一段容易引起异常的代码时,应将此代码放在try语句块中,紧接其后的是一个或多个提供错误处理的catch语句块,如果try块中的某一操作触发了一个异常,则错误将传递到catch作用域,可以在该作用域中适当的处理该问题
try
{
string myText=file.ReadAll(@"D:myTextFile.txt")
}
catch(fileNotFoundException ex)
{
Console.WriteLine("Error:{0},ex.Message");
}
catch(IOException ex)
{
Console.WriteLine("Error:{0},ex.Message");
}
10:C#的标准输入流和输出流
来自控制台的数据从标准输入流读取,传给控制台的正常数据会写入标准输出流,传给控制台的错误数据会写入标准错误输出流。启动控制台时,这些流自动与控制台(Console)相关联,并以In、Out、Error属性形式显示
1)Console类包含的控制台输入方法
从标准输入流读取下一个字符:
Public static int Read();
从标准输入流读取下一行字符:
public static string ReadLine();
2)Console类包含的控制台输出方法
public static void WriteLine(char);
public static void Write(char);
public static void WriteLine(int);
public static void Write(int);
public static void WriteLine(double);
public static void Write(double);
public static void WriteLine(string);
public static void Write(string);
使用指定的格式信息,将指定的对象数组写入标准输出流:
public static void WriteLine(string format , params object[]args);
11:C#的泛型
包括泛型类和泛型方法,泛型应用类型参数的概念,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到声明并实例化该类或调用方法的时候
1)泛型集合
List<int> a =new List<int>(); //声明构造整型数列表
a.Add(86); a.Add(100); //添加整形元素
List<string> s =new List<string>() //声明构造字符串列表
s.Add("Hello"); s.Add("World");
//也可以生命构造自定义类型列表
List<Student> st =new List<Student>(); //生命构造学生列表
st.Add(newStudent(200518001,"张三","男",18,92)) //向列表添加学生类型元素
2)自定义泛型类型和方法
12:委托与Lambda表达式
委托(delegate)是一种特殊类型,用来定义能够应用方法的对象,通过委托实例可以调用所引用的方法,委托实例可以引用不同的方法,因而调用同一个委托实例可以调用不同的方法实体
声明委托:
delegate rerutn-type Delegate-Identifier(parameter-list);
1)C#类库在system命名空间中有两个常用的委托
分别是Comparison委托和Predicate委托,它们的定义为如下:
public delegate int Comparison<T>(T x, T y);从形式上表示具有两个同类型的参数、返回值为有符号整数的方法,x<y,返回负,x>y,返回正
public delegate bool Predicate<in T>(Tk);从形式用来表示具有一个参数、返回值的布尔类型方法,k满足条件,返回值为true,反之为false
委托变量可以指向与之签名兼容的方法,包括匿名方法,这称为委托实例化
2)Lambda表达式
如果临时需要一个能完成比较操作的方法,不一定非要为这样简短的代码设计一个专门名称,这时可以用Lambda表达式,它可以用来方便简洁地表达匿名方法
例如下面地例子,用Lambda表达式定义了一个比较两个整数的匿名方法,并用之实例化委托变量C,该匿名方法定义了以绝对值来决定谁打谁小的规则:
Comparision<int> c=(x,y)=>Math.Abs(x).CompareTo(Math.Abs(y));
二、C#语言数据集合类型
1:数组
数组是由一组具有相同类型的元素组成的集合,各元素以此存储于一个连续的内存空间。
1)一维数组
声明一维数组变量的格式是:
<类型> [ ] <数组名字>; 比如:<int>[ ] a;
声明数组并没有实际创建数组实例,在C#中,数组是对象,必须进行实例化:
<数组名> = new <类型>[<长度>];
声明数组的同时也可以为数组进行初始化:
int [] a={1,2,3,4};
数组的多种初始化和修改引用方法见博客:
零基础自学C#——Part2:C#中的运算符、语句、类型转换、数据结构_代码历险记的博客-CSDN博客
2)多维数组
多维数组用说明多个下标的形式来定义,例如:
int[,] items = new int [5,4]
声明一个二维数组item,并分配5×4个存储单元
更多方法和更高维详见博客:
零基础自学C#——Part2:C#中的运算符、语句、类型转换、数据结构_代码历险记的博客-CSDN博客
3)数组的数组
如果数组的元素也是数组,则称为数组的数组,或者交错数组
int[] [] scores = new int [4] []
声明并分配交错数组scores,它具有四个元素,scores[0],scores[1],scores[2],scores[3],每个元素都是一个整形数组,即int[],每个数组的长度可以不一样
4)所有数组都是类类型的数据类型
C#中所有的数组都是类(Class)类型的数据类型,任何数组类型都隐含继承自基类型System.Array,因此数组变量属于引用类型变量数组实力都具有对象特性。System.Array类中定义的共有属性和其他成员方法都可以供所有数组实例使用
Array类中定义的公共属性:
virtual int Length{get;} //返回数组的所有维数中元素的总数
virtual int Rank{get;} //获得数组维数
Array类中定义的公共方法:
static void Copy(Array source , int scrIndex, Array destination , int destIndex , int length)
//从指定位置开始,复制源数组中的指定长度元素到目数组中的指定位置,有多个重载
void CopyTo(Array desArray, int dstIndex)
//将当前一维数组中的素有元素复制到目数组中的指定位置
static int IndexOf<T>(T[] a,T k) //返回给定数据首次出现位置
static void Sort(Array a ); //对整个一维数组元素进行分类,有多个重载
int GetLength(int dnmension); //获取数组指定维数中的元素
例:写完整的C#代码,声明并实例化三种数组
using System;
class ArraySample
{
public static void Main() //声明静态Main方法
{
int[]numbers = new int[5]; //一维整形数组
string[,]names= new string[5,4]; //二维字符串数组
Console.WriteLine(names.Length);
int[][]scores = new int[4][];
for(int i=0;i<scores.Length;i++)
{
scores[i]=new int [i+3]; //创建交错出租
}
for(int i = 0; i < scores.Length; i++)
{
Console.WriteLine("Length of row{0}is{1}",i,scores[i].Length);
}
Console.WriteLine("Number of row is{0}",scores.Length);
}
}
2:线性表类(List)
1)非泛型线性表ArrayList
C#类库在System.Collections命名空间中定义了一个线性表类ArrayList,ArrayList类提供了一种元素数目可按需求动态增加的数组,并可以在任意位置插入和删除元素的操作
ArrayList的元素类型是object,插入线性表的数据元素要求是object对象,因为object类是C#中类层次的根类,所有其他类都是由object类派生出来的,所以实际上可以将任何类型的对象加入线性表;
公共方法属性见博客:零基础自学C#——Part2:C#中的运算符、语句、类型转换、数据结构_代码历险记的博客-CSDN博客
2)泛型线性表List<>
C#语言(2.0起)和公共语言运行库(CLR)中增加了泛型(Generics),包括泛型类和泛型方法。
泛型通常和集合一起使用,C#提供新的命名空间System.Collections.Generics,它包含多个定义泛型集合的类和接口
泛型集合允许对象创建强类型的数据集合,能提供比非泛型集合更好的类型安全性和性能,建议都使用泛型
其实它和非泛型还是很像的,方法和属性见博客:零基础自学C#——Part2:C#中的运算符、语句、类型转换、数据结构_代码历险记的博客-CSDN博客
C#中增加了“集合初始化器”以便集合对象的初始化。
List<int> nums= new List<int>{1,2,3,4,5};
它其实是利用编译时技术对初始化器中的元素进行按序调用Add(T)方法
3:栈类(stack)
1)非泛型栈Stack类
它定义在System.Collections命名空间中,它刻画了一种数据后进先出的集合,其数据元素的类型是object类,入栈出栈都是object类型
2)泛型栈Stack<T>类
Stack<T>是Stack类的泛型等效类,它定义在System.Collections.Generic命名空间中,于非泛型栈的区别是它是强类型栈,更安全,实际使用过程也更安全
更多Stack的方法和属性见博客:
零基础自学C#——Part6:哈希表/字典、堆栈/队列_代码历险记的博客-CSDN博客
举例:创建Stack对象,并打印值
using System;
using System.Collections;
class SampleStack
{
static void Main()
{
Stack myStack = new Stack();
myStack.Push("Hello");
myStack.Push("World");
myStack.Push("!");
Console.Write("myStack");
Console.WriteLine("\tCount:{0}",myStack.Count);
Console.Write("\tValues:\n");
foreach(object o in myStack)
{
Console.Write("\t{o}",o);
}
}
}
打印的结果为:
mySack Count: 3
values: ! World Hello
由此可见它是后进先出的集合!
/*
\t :表示空4个字符,类似于文档中的缩进功能,相当于按一个Tab键。
\n :表示换行,相当于按一个 回车键
\n\t : 表示换行的同时空4个字符。
*/
4:队列类(queue)
1)非泛型队列
它定义在命名空间System.Collections中,它刻画了一种具有先进先出的数据集合,入队和出队数据元素类型都是object,所以在实际操作时可以是int、string等任意类型的对象
2)泛型队列
它是Queue类的泛型等效类,定义在System.Collections.Generic命名空间中,属于强类型队列
更多Queue的方法和属性见博客:
零基础自学C#——Part6:哈希表/字典、堆栈/队列_代码历险记的博客-CSDN博客
举例:创建Queue对象并添加值,打印
using System;
using System.Collections;
public class SampleQueue
{
public static void Main()
{
Queue myQ = new Queue();
myQ.Enqueue("Hello");
myQ.Enqueue("World");
myQ.Enqueue("!");
Console.Write("MyQ");
Console.WriteLine("\tCount:{0}",myQ.Count);
Console.Write("\tValues:\n");
foreach(object o in myQ)
{
Console.Write("\t{o}",o);
}
Console.WriteLine();
}
}
结果为:
MyQ
Count:3
Value:Hello World !
5:Hashtable和Dictionary类
C#类库中的Hashtable类和Dictionary类都应用哈希查找技术以实现快速查找,特别是对数据量大且某些数据会被高频查询的数据集合,它在时间效率上有较大提高
1)Hashtable类
它定义在System.Collections命名空间中,该类提供了表示(键,值)对(key-value pair)的集合,集合中的每个元素都是一个存储在DictionaryEntey对象中的键/值对,这些键值对根据键的哈希码进行组织,Hashtable中的元素可以直接通过键来索引,键的作用类似数组中的下标
2)Dictionary类
它是类似Hashtable类的范类,定义在System.Collections.Generic命名空间中
更多有关Hashtable和Dictionary的方法属性见博客:
零基础自学C#——Part6:哈希表/字典、堆栈/队列_代码历险记的博客-CSDN博客
举例:创建并初始化Dictionary并打印值
using System;
using System.Collections.Generic;
public class Dictionary
{
public static void Main()
{
//这里定义了字典存储的两种类型,并初始化
Dictionary<string,string> tpbook = new Dictionary<string, string>();
tpbook.Add("小红","123"); //添加元素
tpbook.Add("小黄","1234");
tpbook.Add("小粉","12345");
tpbook.Add("小绿","123456");
tpbook["王浩"]="1234567";
foreach(KeyValuePair<string,string>kvp in tpbook) //对键值对进行遍历!!!
{
Console.WriteLine("Key={0},Value={1}",kvp.Key,kvp.Value);
}
Console.WriteLine("\nRename(\"王浩\")");
tpbook.Remove("王浩");
if(!tpbook.ContainsKey("王浩")) //存在与否,使用key
{
Console.WriteLine("Key\"王浩\"is not found.");
}
}
}
结果:
Key=小红,Value=123
Key=小黄,Value=1234
Key=小粉,Value=12345
Key=小绿,Value=123456
Key=王浩,Value=1234567
Rename("王浩")
Key"王浩"is not found.