console.Write("Hello world!");
与
console.Writeline("Hello world!");
//区别:前者输出不换行,后者输出换行
console.Readkey();
//这个函数是为了在控制台窗口停留一下,直到敲击键盘为止。
//不然运行时,"Hello World!" 这句话会在控制台窗口一闪而过,没法查看。
输入和输出
console.Write(); //输出不换行
Console.WriteLine(); //输出并换行
Console.ReadLine(); //读取键盘输入的所有字符,返回字符串。按下回车键退出
Console.Read(); //读取键盘输入的第一个字符,返回对应的ASCII值,按下回车键退出
Console.Readkey(); //等待用户按下任意键并执行退出(此函数的作用是为了在控制台窗口停留一下,直到用户敲击键盘为止。不然运行时,"Hello World!" 这句话会在控制台窗口一闪而过,没法查看。)
using System; //using关键字用于在程序中包含System命名空间,一个程序一般有多个using语句
namespace HelloWorldApplication //namespace声明,namespace包含一系列的类,HelloWorldApplication包含类Helloworld
{
class HelloWorld
//class声明。类HelloWorld包含程序使用的数据和方法声明。
static void Main(string[] args)
//Main方法,是所有c#程序的入口点。说明当执行时类将做什么动作
{
Console.WriteLine("Hello World")
//指定它的行为,定义在System命名空间中的Console类的一个方法
Console.ReadKey()
//针对VS.NET用户,使得程序等待一个按键的动作,防止程序从Visual Studio.NET启动时屏幕会快速运行并关闭
}
占位符
static void main(string[] args)
{
Console.WriteLines("A:{0}, a:{1}",65, 97);
//当WriteLines()函数有多个参数时,输出第一个参数的内容,而第二个参数中的内容替换掉第一个参数中的占位符一起输出
Console.WriteLines("A:,a:",65, 97)
//输出 A:,a:
//如果第一个参数没有留占位符,那么第二个参数内容不输出
Console.WriteLine("A:{1},a:{0}",65,97);
//运行结果 A:97,a:65
//占位符从零开始计数,且占位符中的数字不能大于第二参数的个数减一(要求占位符必须有可替换的值)占位符数字与第二参数字符一一对应
Console.ReadLine();
}
运行结果
A:65,a:97
XML注释
///
也是一种注释,但是这种注释主要有两种作用:
1.这些注释能够生成一个XML文件,这样,看起来会对程序内容一目了然;
2.以这种方式对你的类,属性,方法等等加上注释之后,当你在其他地方使用以这种方式加上的类,属性,方法等的时候,下方提示框会将你写的注释显示出来,使你更加明白你要使用的内容的作用;
/// <summary>
/// 定义用户姓名属性,该属性为可读可写属性
/// </summary>
数据类型
变量类型:
- 值类型
- 引用类型
- 指针类型
值类型
从类System.ValueType
中派生;
类型 | 描述 | 范围 | 默认值 |
---|---|---|---|
byte | 8位无符号整数类型 | 0—255 | 0 |
decimal | 128位精确的十进制值,28—29有效位数 | (-7.9 x 1028到 7.9 x 1028) / 10^(0 到 28)^ | 0.0M |
sbyte | 8位有符号整数类型 | -128~127 | 0 |
uint | 32位无符号整数类型 | 0 到 4,294,967,295 | 0 |
ulong | 64 位无符号整数类型 | 0 到 18,446,744,073,709,551,615 | 0 |
ushort | 16 位无符号整数类型 | 0 到 65,535 | 0 |
引用类型
包含对变量的引用;指向一个内存位置;
内置的引用类型:object
,dynamic
和string
对象(object)类型
对象类型是c#通用类型系统(common Type System -CTS)所有数据类型的终极基类;object
是System.Object
类型的别名。对象(object)类型可以被分配任何其他类型(值类型,引用类型,预定义类型或用户自定义类型)的值。在分配前,一定要进行类型转换;
当一个值类型转化为对象类型,被称为装箱;当一个对象类型转化为值类型,称为拆箱;
object obj;
obj = 100; //装箱
动态(Dynamic)类型
可以存储任何类型的值在动态数据类型变量中,这些变量检查在运行时发生的。
声明动态类型的语法:
dynamic<variable_name> = name;
dynamic d = 20;
对象类型变量的类型检查在编译时发生,而动态类型变量的类型检查在运行时发生;
字符串类型
允许给变量分配任意字符串值。String
是System.String
类的别名。从Object
类型派生,String
类型的值分配形式:引号和@引号;
String str = "runoob.com";
@"runoob.com";
字符串前面可加@将转义字符(\)当作普通字符对待:
string str = @"c:\windows";
string str = "c:\\windows";
@字符串中可以任意换行,换行符及缩进空格都计算在字符串长度以内
string str = @"<script type = ""text/javascript"">
<!--
-->
</script>";
指针类型
声明指针类型
char* cptr;
int* iptr;
类型转换
隐式类型转换;
显示类型转换;
C#类型转换方法
序号 | 方法&描述 |
---|---|
1 | ToBoolan 将类型转化为bool类型 |
2 | ToByte 把类型转化为字节类型 |
3 | ToChar 把类型转化为单个Unicode字符类型 |
4 | ToDateTime 把类型(整型或字符串)转化为日期-时间结构 |
5 | ToDecimal 把浮点型或整型类型转化为十进制型 |
6 | ToDouble 把类型转换为双精度浮点型 |
其他见参考文档
string s1 = "abcd";
string s2 = "1234";
int a, b;
bool bol = int.TryParse(s1, out a);
//把数字内容的字符串转化为int类型,比int.Parse(string s)好,不会出现异常,如果转换成功则输出相应的值,转换失败则输出0
Console.WriteLine(s1 + " " + bol + " " + a);
bool bo2 = int.TryParse(s2, out b);
Console.WriteLine(s2 + " " + bo2 + " " + b);
convert.toint32()
偶数值
double a = 1.35;
double b = 1.65;
int a1 = Convert.ToInt32(a);
int a2 = (int)(a);
int b1 = Convert.ToInt32(b);
int b2 = (int)b;
Console.WriteLine("{0}使用convert方法转化的结果为:{1}", a, a1);
Console.WriteLine("{0}使用int方法转化的结果为:{1}", a, a2);
Console.WriteLine("{0}使用convert方法转化的结果为:{1}", b, b1);
//输出2, 四舍五入
Console.WriteLine("{0}使用int方法转化的结果为:{1}", b, b2);
try catch
static void Main(string[] args)
{
try
{
Console.WriteLine("输入数字,计算出加一的答案");
int a = int.Parse(Console.ReadLine()); //用户输入的东西,以字符串形式呈现
Console.WriteLine("答案是{0}", ++a);
Console.ReadKey();
}
catch(Exception)
{
Console.WriteLine("无法转换");
}
Console.ReadKey();
}
变量
值类型
类型 | 举例 |
---|---|
整数类型 | sbyte、byte、short、ushort、int、uint、long、ulong 和 char |
浮点型 | float 和 double |
十进制类型 | decimal |
布尔类型 | true 或 false 值,指定的值 |
空类型 | 可为空值的数据类型 |
num = Convert.ToInt32(Console.ReadLine()); //Console.ReadLine():用于接受用户输入,并把它存储在一个变量中
//Convert.ToInt32() 把用户输入的数据转换为 int 数据类型,Console.Readline()只接受字符串格式的数据
C#中的Lvalues和Rvalues
表达式:
Lvalue
:可以出现在赋值语句的左边或右边;Rvalues
:rvalue表达式可以出现在赋值语句的右边,不能出现在左边;
变量是lvalue
,可以出现在赋值语句的左边;数值是Rvalue
,不能被赋值,不能出现在赋值语句的左边;
double a = 42.29;
int b = 4229;
double c = a + b; // 当一个精度高的数据类型与一个精度低的数据类型进行运算时,定义运算结果必须与精度最高的变量类型相同。这是为了防止在运算过程中造成数据丢失
Console.WriteLine("c = {0}", c);
Console.ReadKey();
常量
整数常量
可以是十进制、八进制或者十六进制的常量;0X
表示十六进制,0
表示八进制,整数常量也可以有后缀,可以是U
和L
的组合,后缀可以是大写或者是小写;
0XFeeL //合法
032UU //非法不能重复后缀
浮点常量
一个浮点常量是由整数部分、小数点、小数部分和指数部分组成。
314159E-5L //合法
510E //非法,不完整指数
210f //非法,没有小数或指数
.e55 //非法,缺少整数或小数
使用小数形式时,必须包含小数点、指数或者同时包含;使用指数形式时,必须包含整数部分、小数部分或者同时包含;
字符常量
字符串常量
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
//string a = "0.2";
//string a = null;
string a = "";
try
{
double d1 = Double.Parse(a);
}
catch (Exception err)
{
Console.WriteLine("d1转换出错:" + err.Message);
}
try
{
double d2 = Convert.ToDouble(a);
}
catch (Exception err)
{
Console.WriteLine("d2转换出错:" + err.Message);
}
try
{
double d3;
Double.TryParse(a, out d3);
}
catch (Exception err)
{
Console.WriteLine("d3转换出错:" + err.Message);
}
}
finally
{
Console.ReadKey();
}
}
}
}
//null 输出:d1转换出错:Value cannot be null. (Parameter 's')
//"" 输出: d1转换出错:Input string was not in a correct format.
// d2转换出错:Input string was not in a correct format.
常量变量命名规则
带有private
私有访问修饰符的常量要以骆驼命名法命名。即以下划线开头,第一个单词首字母小写,余下单词首字母大写;
private const string _bookName = "新华字典";
带有public
公共修饰符,protected
受保护修饰符等的常量要以帕斯卡命名法命名,即每个单词首字母大写;
public const int BookPrice = 10;
运算符
算数运算符
关系运算符
逻辑运算符
位运算符
其他运算符
运算符 | 描述 | 实例 |
---|---|---|
is | 判断对象是否为某一类型 | is(Ford is car) |
as | 强制转换,即使转换失败也不会抛出异常 | Object obj = new StringReader(“Hello”); StringReader r = obj as StringReader; |
c#中的运算符优先级
见文档
循环
for each
int[] finary = new int[] { 0, 1, 2, 3, 4, 5 };
foreach(int element in finary)
{
System.Console.WriteLine(element);
}
int count = 0;
foreach (int c in finary)
{
count++;
System.Console.WriteLine("Element #{0}: {1}", count, c);
}
System.Console.WriteLine("Number in array: {0}", count);
封装
访问修饰符
public
:所有对象都可以访问;private
:对象本身在对象内部可以访问;protected
:只有该类对象及其子类对象可以访问internal
:同一个程序集对象可以访问;protected internal
:访问限于当前程序集或派生自包含类的类型;
Internal访问修饰符
允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有internal
访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类和方法访问。
using System;
namespace RectangleApplication
{
class Rectangle
{
//成员变量
internal double length;
internal double width;
double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}//end class Rectangle
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.length = 4.5;
r.width = 3.5;
r.Display();
Console.ReadLine();
}
}
}
方法
按引用传递参数
使用关键字ref
关键字声明引用参数;
using System;
namespace CaculatorApplication
{
class NumberManipulator
{
public void swap(ref int x, ref int y)
{
int temp = x;
x = y;
y = temp;
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
int a = 100, b = 200;
Console.WriteLine("在交换之前, a = {0}", a);
Console.WriteLine("在交换之前, b = {0}", b);
n.swap(ref a, ref b);
Console.WriteLine("在交换之后, a = {0}", a);
Console.WriteLine("在交换之后, b = {0}", b);
Console.ReadKey();
}
}
}
按输出传递参数
使用输出参数来从函数中返回两个值;输出参数会把方法输出数据赋给自己,其他方面与引用相似;
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void swap(ref int x, ref int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
int b = 200;
Console.WriteLine("在交换之前,a 的值: {0}", a);
Console.WriteLine("在交换之前,b 的值: {0}", b);
/* 调用函数来交换值 */
n.swap(ref a, ref b);
Console.WriteLine("在交换之后,a 的值: {0}", a);
Console.WriteLine("在交换之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
在交换之前,a 的值:100
在交换之前,b 的值:200
在交换之后,a 的值:200
在交换之后,b 的值:100
结果表明,swap 函数内的值改变了,且这个改变可以在 Main 函数中反映出来。
按输出传递参数
return 语句可用于只从函数中返回一个值。但是,可以使用 输出参数 来从函数中返回两个值。输出参数会把方法输出的数据赋给自己,其他方面与引用参数相似。
下面的实例演示了这点:
实例
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void getValue(out int x )
{
int temp = 5;
x = temp;
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
Console.WriteLine("在方法调用之前,a 的值: {0}", a);
/* 调用函数来获取值 */
n.getValue(out a);
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.ReadLine();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
在方法调用之前,a 的值: 100
在方法调用之后,a 的值: 5
提供给输出参数的变量不需要赋值。当需要从一个参数没有指定初始值的方法中返回值时,输出参数特别有用。请看下面的实例,来理解这一点:
实例
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void getValues(out int x, out int y )
{
Console.WriteLine("请输入第一个值: ");
x = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入第二个值: ");
y = Convert.ToInt32(Console.ReadLine());
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a , b;
/* 调用函数来获取值 */
n.getValues(out a, out b);
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.WriteLine("在方法调用之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
//输出:
/*请输入第一个值:
7
请输入第二个值:
8
在方法调用之后,a 的值: 7
在方法调用之后,b 的值: 8
*/
ref
型传递变量前,变量必须初始化,否则编译器会报错,而out
型不需要初始化;
out
型数据在方法中必须要赋值,否则编译器会报错;
class C
{
//1. in型参数
public void sum(int a, int b) {
a += b;
}
//2. ref型参数
public void sum(ref int a, int b)
{
a += b;
}
//3. out型参数
public void sum1(out int a, int b)
{
a = b+2;
//改为 a += b;编译器会报错。原因:out型只出不进,在没给a赋值前不能使用
//改为 b += b + 2;也会报错。原因:out数据在方法中必须被赋值;
}
public static void Main(string[] args)
{
C c = new C();
int a = 1, b = 2;
c.sum(a,b);
Console.WriteLine("a:{0}", a);
a = 1; b = 2;
c.sum(ref a, b);
Console.WriteLine("ref a:{0}", a);
a = 1; b = 2;
c.sum1(out a, b);
Console.WriteLine("out a:{0}", a);
}
}
重载方法时若两个方法的区别仅限于一个参数类型为ref 另一个方法中为out,编译器会报错
原因:参数类型区别仅限于 为 ref 与为 out 时,若重载对编译器而言两者的元数据表示完全相同。
扩展方法
扩展方法可以实现不需要修改目标类,也不需要继承目标类的情况下为其添加一个方法。
规则:
- 扩展类必须是静态类,扩展方法必须是静态方法;
- 扩展方法的第1个参数开头必须适用
this
关键字然后再填入扩展的目标类; - 如果需要接受参数则从第2个参数开始算起,第1个参数再真正调用方法是隐藏的;
public static class ExtensionString
{
//向 String 类扩展一个统计单词数量的方法
public static int CountWord(this String str)
{
return str.Split(' ').Length;
}
}
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine("单词数量:" + "Hello World".CountWord()); //没有参数
}
}
可空类型(Nullable)
C#单问号?与双问号?
?
:单问号对于int
,double
,bool
等无法直接赋值为null
的数据类型进行null
的赋值,意思是这个数据类型是NullAble
类型的;
int? i = 3
等同于
Nullable<int> i = new Nullable<int>(3);
int i; //默认值为0
int? ii; //默认值为null
??
双问号 可用于判断一个变量为null
时返回一个指定值;
c#可空类型(Nullable)
可空类型表示其基础类型正常范围内的值,再加上一个null
值,Nullable< Int32 >
,读作"可空的 Int32",可以被赋值为 -2,147,483,648 到 2,147,483,647 之间的任意值,也可以被赋值为 null 值。
类似的,Nullable< bool >
变量可以被赋值为 true 或 false 或 null。
在处理数据库和其他包含可能未赋值的元素的数据类型时,将 null
赋值给数值类型或布尔型的功能特别有用。例如,数据库中的布尔型字段可以存储值 true
或 false
,或者,该字段也可以未定义。
class NullableAtShow
{
static void Main(string[] args)
{
int? num1 = null;
int? num2 = 45;
double? num3 = new double?();
double? num4 = 3.1415926;
bool? boolval = new bool?();
Console.WriteLine("显示可空类型的值:{0}, {1}, {2}, {3}", num1, num2, num3, num4);
Console.WriteLine("一个可空的布尔值:{0}", boolval);
Console.ReadKey();
}
}
//输出:
显示可空类型的值: , 45, , 3.14157
一个可空的布尔值:
Null合并运算符(??)
用于定义可空类型和引用类型的默认值。NULL
合并运算符为类型转换定义了一个预设值,以防可空类型的值为Null
。Null
合并运算符把操作数类型隐式转换为另一个可空(或者不可空)的值类型的操作数的类型。
如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。
double? num1 = null;
double? num2 = 3.14157;
double num3;
num3 = num1 ?? 5.34; //num1如果为空则返回5.34
Console.WriteLine("num3的值: {0}", num3);
num3 = num2 ?? 5.34;
Console.WriteLine("num3的值: {0}", num3);
Console.ReadKey();
数组
声明数组
datatype[] arrayname;
double[] balance;
初始化数组
数组是一个引用类型
double[] balance = new double[10];
创建并初始化一个数组
int[] marks = new int[5]{99, 98, 92, 97, 1};
//省略数组大小
int[] marks = new int{1, 2, 3, 4, 4};
通过赋值一个数组变量到另一个目标数组变量中。在这种情况下,目标和源会指向相同的内存位置
int[] marks = new int[] {99, 8, 91, 1, 3};
int[] sorces = marks;
当您创建一个数组时,C# 编译器会根据数组类型隐式初始化每个数组元素为一个默认值。例如,int
数组的所有元素都会被初始化为0
。
多维数组
声明一个string
变量的二维数组
string [,] names;
int
三维数组
int[, , ]m;
int[,,] muarr = new int[2, 2, 3]
{
{{1, 2, 3}, {4, 5, 6} },
{{7, 8, 9}, {2, 3, 4} }
};
int rank = muarr.Rank;
Console.WriteLine("该多维数组的维度为:{0}", rank);
int length1 = muarr.GetLength(1);
Console.WriteLine("该多维数组的第二维有{0}个元素", length1);
Console.WriteLine("开始遍历多维数组");
Console.WriteLine("----------------------");
int wei = 1;
int index = 1;
for (int i = 0; i < muarr.GetLength(0); i++)
{
index = 1;
for (int j = 0; j < muarr.GetLength(1); j++)
{
for (int k = 0; k < muarr.GetLength(2); k++)
{
Console.WriteLine("第{0}行的第{1}值为{2}", wei, index, muarr[i, j, k]);
}
++index;
}
++wei;
}
交错数组
交错数组是数组的数组;
是一维数组;
声明一个带int
交错数组
int[][] scorces;
创建上述数组
int[][] scorces = new int[5][];
for (int i = 0; i < scores.length(); i++)
{
scores[i] = new int[4];
}
交错数组与二维数组的区别:直观理解交错数组的每一行的长度是可以不一样的。
// 声明一个交错数组 a,a 中有三个元素,分别是 a[0],a[1],a[2] 每个元素都是一个数组
int[][] a = new int[3][];
//以下是声明交错数组的每一个元素的,每个数组的长度可以不同
a[0] = new int[] { 1,2,3 };
a[1] = new int[] { 4,5,6,7,8 };
a[2] = new int[] { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
参数数组
不能确定传给函数作为参数的个数;参数数组通常用于传递未知数量的参数给函数;
C# 提供了 params
关键字,使调用数组为形参的方法时,既可以传递数组实参,也可以传递一组数组元素。
using System;
namespace ArrayApplication
{
class ParamArray
{
public int AddElements(params int[] arr)
{
int sum = 0;
foreach (int i in arr)
{
sum += i;
}
return sum;
}
}
class TestClass
{
static void Main(string[] args)
{
ParamArray app = new ParamArray();
int sum = app.AddElements(512, 720, 250, 567, 889);
Console.WriteLine("总和是: {0}", sum);
Console.ReadKey();
}
}
}
Array类
类属性
序号 | 属性&描述 |
---|---|
1 | IsFixedSize 获取一个值,该值指示数组是否带有固定大小 |
2 | IsReadOnly 获取一个值,该值指示数组是否只读 |
3 | Length 获取一个 32 位整数,该值表示所有维度的数组中的元素总数 |
4 | LongLength 获取一个 64 位整数,该值表示所有维度的数组中的元素总数 |
5 | Rank 获取数组的秩(维度) |
Array类的方法
序号 | 方法&描述 |
---|---|
1 | Clear 根据元素的类型,设置数组某个范围的元素为零,为false或者null |
2 | Copy(Array, Array, Int32) 从数组的第一个元素开始复制某个范围的元素到另一个数组的第一个元素位置。长度由一个 32 位整数指定。 |
3 | CopyTo(Array, Int32) 从当前的一维数组中复制所有的元素到一个指定的一维数组的指定索引位置。索引由一个 32 位整数指定。 |
4 | GetLength 获取一个 32 位整数,该值表示指定维度的数组中的元素总数。 |
其他详见文档Array类的方法
字符串
见文档字符串函数
结构体
c#
中的结构体与传统c
或C++
中的结构不同,c#
中的结构有以下特点:
- 结构可带有方法、字段、索引、属性、运算符方法和事件。
- 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
- 与类不同,结构不能继承其他的结构或类。
- 结构不能作为其他结构或类的基础结构。
- 结构成员不能指定为 abstract、virtual 或 protected。
- 当您使用
New
操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。 - 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
类VS结构体
不同点:
- 类是引用类型,结构是值类型。
- 结构不支持继承。
- 结构不能声明默认的构造函数。
枚举(Enum)
enum Days {Sum, Mon, Tue, Wed, Thu, Fri, Sat};
类(class)
继承
一个对象可以用父类声明,却用子类实例化
(声明一个基类指针或引用可以动态绑定一个派生类对象)
在子类中用override
重写父类中用virtual
声明的虚方法时,实例化父类调用该方法,执行的是子类中重写的方法(动态绑定);
如果子类中用new
覆盖父类中用virtual
声明的虚方法时,实例化父类调用该方法,执行时调用的是父类中的虚方法(静态绑定);
using System;
namespace InheritanceApplication
{
public class ParentClass
{
public virtual void ParVirMethod()
{
Console.WriteLine("父类的方法……");
}
}
public class ChildClass1: ParentClass
{
public override void ParVirMethod()
{
Console.WriteLine("子类1的方法……");
}
}
public class ChildClass2: ParentClass
{
public new void ParVirMethod()
{
Console.WriteLine("子类2的方法……");
}
public void Test()
{
Console.WriteLine("子类2的其他方法……");
}
}
class Test
{
static void Main(string[] args)
{
ParentClass par = new ChildClass1();
par.ParVirMethod();
par = new ChildClass2();
par.ParVirMethod();
}
}
}
//输出
子类1的方法……
父类的方法……
基类访问(访问隐藏的基类成员)
如果想要使派生类能够完全访问被隐藏的继承成员,可以使用基类访问表达式访问被隐藏的继承成员,基类访问表达式由base
后面跟一个点和成员名称组成:
Console.WriteLine("{0}", base.Field1);
多态性
在编译时,函数和对象的连接机制称为早期绑定,也称为静态绑定;
动态多态性
使用abstract
创建抽象类,用来提供接口的部分类的实现;
下面是有关抽象类的一些规则:
- 您不能创建一个抽象类的实例。
- 您不能在一个抽象类外部声明一个抽象方法。
- 通过在类定义前面放置一个关键字
sealed
,可以将类命名成密封类
。当一个类被命名为sealed
时,他不能被继承,抽象类不能被命名成sealed;
静态绑定的实现技术
函数重载/运算符重载
虚方法的调用:调用时,使用子类构造的对象调用虚方法,就会调用子类的方法;使用父类构造的对象,就会调用父类的方法;(动态绑定)
隐藏方法的调用:调用上,使用子类类型的声明调用隐藏方法,就会调用子类的方法;使用父类类型的声明调用,就会调用父类的方法;
using System;
class Enemy
{
public void Move()
{
Console.WriteLine("调用了enemy的move方法");
}
public virtual void Attack()
{
Console.WriteLine("enemy attac");
}
}
class Boss: Enemy
{
public override void Attack()
{
Console.WriteLine("Boss Attac");
}
public new void Move()
{
Console.WriteLine("Boss move");
}
}
class app
{
static void Main(string[] args)
{
//用什么类声明,隐藏方法就调用什么类的方法
Boss oneEnemy = new Boss();
oneEnemy.Move();
Enemy twoEnemy = new Boss();
twoEnemy.Move();
//虚方法调用,用什么类型构造,在调用时就会使用什么类型的方法
Enemy threeEnemy = new Enemy();
threeEnemy.Attack();
Enemy fourEnemy = new Boss();
fourEnemy.Attack();
Console.ReadKey();
}
}
运算符重载
接口(interface)
定义接口使用interface
关键字声明,接口声明默认是public;
文件的输入和输出
当打开文件进行读写时,它就变成了流;
从根本上说,流是通过通信路径传递的字节序列;
IO FileStream读写实例
using System;
using System.IO;
namespace FileIOApplication
{
class program
{
static void Main(string[] args)
{
FileStream F = new FileStream("test.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite);
for (int i = 1; i <= 20; i++)
{
F.WriteByte((byte)i);
}
F.Position = 0;
for (int i = 0; i <= 20; i++)
{
Console.WriteLine(F.ReadByte() + " ");
}
F.Close();
Console.ReadKey();
}
}
}
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -1
文本文件的读写
StreamReader和StreamWriter类
抽象基类Stream
StreamReader
StreamReader
类继承自抽象基类TextReader
,表示阅读器读取一系列字符;
实例:
using System;
using System.IO;
namespace FileApplication
{
class program
{
static void Main(string[] args)
{
try
{
//创建一个StreamReader的实例来读取文件
//using 同时也能关闭StreamReader
//某些类型的非托管对象有数量限制或很耗费系统资源,在代码使用完它们后,尽可能快的释放它们时非常重要的。using语句有助于简化该过程并确保这些资源被适当的处置(dispose)。
using (StreamReader sr = new StreamReader("D:/hejie.txt"))
{
string line;
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
catch (Exception e)
{
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}
Console.ReadKey();
}
}
}
异常输出:
The file could not be read:
Could not find file 'D:\heji.txt'.
StreamWriter
抽象基类TextWriter,表示编写器写入一系列字符;
常用方法
using System;
using System.IO;
namespace FileApplication
{
class Program
{
static void Main(string[] args)
{
string[] names = new string[] { "hejie", "hebishuai" };
using (StreamWriter sw = new StreamWriter("D:/hejie.txt"))
{
foreach (string s in names)
{
sw.WriteLine(s);
}
}
string line = "";
using (StreamReader sr = new StreamReader("D:/hejie.txt"))
{
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
Console.ReadKey();
}
}
}
二进制文件的读写
BinaryReader 和BinaryWriter用于二进制文件的读写
BinaryReader
常用方法
using System;
using System.IO;
namespace BinaryFileApplication
{
class Program
{
static void Main(string[] args)
{
BinaryWriter bw;
BinaryReader br;
int i = 25;
double d = 3.14159;
bool b = true;
string s = "I am happy";
try
{
bw = new BinaryWriter(new FileStream("D:/mydata.txt", FileMode.Create));
}
catch (IOException e)
{
Console.WriteLine(e.Message + "\n Cannot create file.");
return;
}
try
{
bw.Write(i);
bw.Write(d);
bw.Write(b);
bw.Write(s);
}
catch (IOException e)
{
Console.WriteLine(e.Message + "\n Cannot write file.");
}
bw.Close();
try
{
br = new BinaryReader(new FileStream("D:/mydata.txt", FileMode.Open));
}
catch (IOException e)
{
Console.WriteLine(e.Message + "\n Cannot open file.");
return;
}
try
{
i = br.ReadInt32();
Console.WriteLine("Integer data:{0}", i);
d = br.ReadDouble();
Console.WriteLine("Double data: {0}", d);
b = br.ReadBoolean();
Console.WriteLine("Boolean data: {0}", b);
s = br.ReadString();
Console.WriteLine("String data: {0}", s);
}
catch (IOException e)
{
Console.WriteLine(e.Message + "\n Cannot read from file.");
return;
}
br.Close();
Console.ReadKey();
}
}
}
Windows文件系统的操作
使用各种目录和文件相关的类来操作目录和文件,Directoryinfo和FileInfo
Directoryinfo
派生自FileSystemInfo,提供各种用于创建、移动、浏览目录和子目录的方法,该类不能被继承。
常见属性和方法
FileInfo
派生自FileSystemInfo,提供了用于创建、复制、删除、移动、打开文件的属性和方法,且有助于FileStream,该类不能被继承;
高级教程
特性(Attribute)
** 特性(Attribute)**是用于在运行时传递程序中的各个元素(比如类、方法、结构、枚举、组件等)的行为信息的声明型标签;可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。
特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。
两种类型的特性:
预定义特性
自定义特性
规定特性(Attribute)
预定义特性(Attribute)
三种预定义特性:
- AttributeUsage
- Conditional
- Obsolete
AttributeUsage
描述如何使用一个自定义特性类,规定了特性可应用到项目的类型
语法:
[AttributeUsage(
validon, //规定特性可被放置的语言元素
AllowMultiple=allowmultiple, //参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值
Inherited=inherited //参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。
)]
Conditional
标记了一个条件方法,其执行依赖于指定的预处理标识符
Conditional("DEBUG")
Obsolete
标记了不应被使用的程序实体。可以通知编译器丢弃某个特定的目标元素;
当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。
语法
[Obsolete(
message // 是一个字符串,描述项目为什么过时以及该替代使用什么
)]
[Obsolete(
message,
iserror // 是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。
)]
属性(Property)
属性(Property)是类(class)、结构(structure)和接口(interface)的命名(named)成员。类或结构中的成员变量或方法称为 域(Field)。属性(Property)是域(Field)的扩展,且可使用相同的语法来访问。使用访问器(accessors)让私有域的值可被读写或操作。
属性(Property)不会确定存储位置。它们具有可读写或计算它们值得访问器(accessors);
名为 Student 的类,带有 age、name 和 code 的私有域。我们不能在类的范围以外直接访问这些域,但是我们可以拥有访问这些私有域的属性。
访问器(Accessors)
属性的访问器(accessor)包含有助于获取(读取或计算)或设置(写入)属性的可执行语句。访问器(accessor)声明可包含一个 get 访问器、一个 set 访问器,或者同时包含二者。
委托(Delegate)
委托类似c++中的指针,委托(Delegate)是存有对某个方法的引用的一种引用类型变量。
委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自**System.Delegate **类。
using System;
delegate int NumberChanger(int n);
//委托声明,决定了可由该委托引用的方法,可被用于引用任意一个带有单一int参数的方法,并返回一个int类型的变量;
namespace DelegateApp
{
class TestDelegate
{
static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MulNum(int p)
{
num *= p;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MulNum);
nc1(25);
Console.WriteLine($"Value of Num:{getNum()}");
nc2(5);
Console.WriteLine($"Value of Num:{getNum()}");
Console.ReadKey();
}
}
}
重载参数及返回值相同的函数调用;
委托的多播(Multicasting of a Delegate)
创建一个委托被调用时要调用的方法的调用列表,被称为委托的多播,也称为组播;
委托对象可使用 “+” 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。"-" 运算符可用于从合并的委托中移除组件委托。
using System;
delegate int NumberChanger(int n);
namespace DelegateAppl
{
class TestDelegate
{
static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
// 创建委托实例
NumberChanger nc;
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
nc = nc1;
nc += nc2;
// 调用多播
nc(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
委托(Delegate)的用途
委托 printString 可用于引用带有一个字符串作为输入的方法,并不返回任何东西。
事件(Event)
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
使用事件机制实现进程间的通信;
通过事件使用委托
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。
接受该事件的类称为订阅器(subscriber)。事件使用 **发布-订阅(publisher-subscriber)**模型。
**发布器(publisher) **是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
声明事件(Event)
首先必须声明该事件的委托类型
public delegate void BoilerLogHandler(string status);
然后,声明事件本身,使用event关键字
//基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;
集合(Collection)
集合类是专门用来数据存储和检索的类;
位于命名空间System.Collection
命名空间的类
动态数组(ArrayList)
泛型(Generic)
允许延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。
泛型委托
delegate T NumberChanger<T> (T n);
匿名方法(Anonymous methods)
一种传递代码块作为委托参数的技术。
匿名方法没有名称只有主体的方法,不需要指定返回类型,是从方法主体的return
语句推断的。
编写匿名方法的语法
delegate void NumberChanger(int n);
……
NumberChanger nc = delegate(int x);
{
Console.WriteLine($"Anonymous Method:{x}"); //匿名方法主体
};
委托可以通过匿名方法调用,也可以通过命名方法调用,即,通过向委托对象传递方法参数;
nc(10);
不安全代码
当一个代码块使用unsafe
修饰符标记时,c#允许再函数中使用指针变量。不安全代码或非托管代码是指使用指针变量的代码块;
多线程
c#中System.Threading.Thread
类用作线程的工作,允许创建并访问多线程应用的单个线程。使用Thread
类的CurrentThread访问线程;