C# 基础
C#常量
使用关键字constor readonly修饰即可:
-
1.静态常量(编译时常量)const
声明时就进行初始化且之后不能进行更改,可在类和方法中定义。
const double a=3.14;// 正确声明常量的方法 const int b; // 错误,没有初始化2.动态常量(运行时常量)readonly
在运行时确定值,只能在声明时或构造函数中初始化,只能在类中定义。
class Program { readonly int a=1; // 声明时初始化 readonly int b; // 构造函数中初始化 Program() { b=2; } static void Main(string[] args) { } }
在下面两种情况下:
- a、取值永久不变(比如圆周率、一天包含的小时数、地球的半径等)。
- b、对程序性能要求非常苛刻。
可以使用 const 常量,除此之外的其他情况都应该优先采用 readonly 常量。
整数常量
212 /* 合法 */
215u /* 合法 */
0xFeeL /* 合法 */
078 /* 非法:8 不是一个八进制数字 */
032UU /* 非法:不能重复后缀 */
就是可以带后缀表示的一个具体的数
浮点常量
3.14159 /* 合法 */
314159E-5L /* 合法 */
510E /* 非法:不完全指数 */
210f /* 非法:没有小数或指数 */
.e55 /* 非法:缺少整数或小数 */
就是可以带后缀表示的一个具体的数
字符常量
类似'x'等,且可存储在一个简单的字符类型变量中。可以是一个普通字符(例如 'x')、一个转义序列(例如 '\t')或者一个通用字符(例如 '\u02C0')。
字符串常量
括在双引号 "" 里,或者是括在 @"" 里。字符串常量包含的字符与字符常量相似,可以是:普通字符、转义序列和通用字符
枚举
创建枚举变量方式:
[public] enum Gender
{
man,
woman,
male,
female
}
[public] enum QqState
{
Online=4,
Offline=2,
Busy=30,
QMe=1
}
//[public] 表示可选
Remark:枚举类型需要放置在命令空间或者类里面,不能放在Main函数里面,大多放在命令空间里
枚举列表中的每个符号代表一个整数值,一个比它前面的符号大的整数值。默认情况下,第一个枚举符号的值是 0.
枚举类型与其他类型的转换
枚举类型与int类型的转换
将枚举类型强制转换成为int类型,需要注意int类型和枚举类型是相互兼容的,所以可以通过强制类型转换的语法互相转换
当转换一个枚举中没有的值的时候,不会抛出异常,而是 直接将数字显示出来
枚举 ==> int
Gender gender = Gender.man;
Console.WriteLine(gender);
int num0 = (int)Gender.man;
Console.WriteLine(num0);
int num1 = (int)Gender.woman;
Console.WriteLine(num1);
int num2 = (int)Gender.male;
Console.WriteLine(num2);
Console.WriteLine((int)Gender.female);
//如果在枚举中设置了枚举变量的值,如这里将在枚举中设置 man=3,于是强转后man=3,后面几个则依次+1,即woman=4
int ==> 枚举
如果将int转为枚举类型时,枚举类型的大小包含这个int的值,则对应转换成该枚举变量,int的值在枚举类型大小值之外,则仍然是原来的int的值的大小
int n1 = 4;
Gender gender = (Gender)n1;
Console.WriteLine(n1);
Console.WriteLine(gender);
枚举 ==> string
枚举类型与string类型的转换,注意任何类型都可以转换成为string类型,利用ToString去转
Gender gender = Gender.woman;
Console.WriteLine(gender.ToString());
string ==> 枚举
前面介绍了Convert.ToInt32(), int.parse() ,int.TryParse()
如果字符串在枚举里没有对应的转换对象,对于整数而言直接返回数字,对于字符串(小数在这里也是字符串)程序不会中断但会在结果中显示没有找到
string s = "asddfas";Gender gender2 = (Gender)Enum.Parse(typeof(Gender), s);Console.WriteLine(gender2);
Remark :当手动给枚举指定数值时,可以任意指定枚举变量对应的数值
枚举练习
Q :选择您的QQ状态
A :
namespace ConsoleApp1{ class Program { static void Main(string[] args) { Console.WriteLine("请选择您的QQ状态,1-Online,2-Offline,3-Busy,4-QMe"); string str= Console.ReadLine(); switch (str) { case "1": QqState s1 = (QqState)Enum.Parse(typeof(QqState), str); Console.WriteLine(s1); break; case "2": QqState s2 = (QqState)Enum.Parse(typeof(QqState), str); Console.WriteLine(s2); break; case "30": QqState s3 = (QqState)Enum.Parse(typeof(QqState), str); Console.WriteLine(s3); break; case "4": QqState s4 = (QqState)Enum.Parse(typeof(QqState), str); Console.WriteLine(s4); break; } #endregion } } public enum QqState { Online=1, Offline, Busy, QMe }}
结构
结构可以帮助我们一次性声明多个不同类型的变量,放在命令空间里
[public] struct nameofStruct(结构名){ 成员;}public struct Person{ string name; int age; char gender;}
类与结构体的区别:
-
结构不支持继承。
-
结构不能声明默认的构造函数。
-
类是引用类型,结构是值类型。
-
结构体中声明的字段无法赋予初值,类可以:
struct test001{ private int aa = 1;}执行以上代码将出现“结构中不能实例属性或字段初始值设定”的报错,而类中无此限制,代码如下:
class test002{ private int aa = 1;} -
结构体的构造函数中,必须为结构体所有字段赋值,类的构造函数无此限制:
补充:类与结构的选择
结构和类的适用场合分析:
- 1、当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些;
- 2、对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低;
- 3、在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
- 4、大多数情况下,目标类型只是含有一些数据,或者以数据为主。
结构练习
using System;namespace ConsoleApp1{ public struct Mycolor { public int _red; public int _green; public int _blue; } class hello { static void Main(string[] args) { //定义一个结构叫Mycolor,有三个成员变量,分别定义为int类型的red,green,blue //声明一个Mycolor类型的变量,并对其成员赋值,是其表示一个红色 Mycolor mc; mc._red = 255; mc._blue = 0; mc._green = 0; } }}
using System;//using system.collections.generic;//using system.linq;//using system.text;//using system.threading.tasks;namespace ConsoleApp1{ public struct Person { //使用 _ 是为了区分字段和变量 public string _name;//字段,可以存很多值,不叫变量(只能存储一个值) public int _age; public Gender _gender; } public enum Gender { 男, 女 } class Program { static void Main(string[] args) { Person zsPerson; zsPerson._name = "张三"; zsPerson._age = 18; zsPerson._gender = Gender.男; Person lsPerson; lsPerson._name = "李四"; lsPerson._age = 19; lsPerson._gender = Gender.女; } }}
数组
一次性存储多个相同类型的变量。
-
语法:
数组类型[] 数组名=new 数组类型[数组长度]; -
三种数组赋值方式
- int[] nums = new int[10]; //但是后面不能像C那样直接给数组赋值,只能每个数组元素单独赋值
- int[] numsTwo =
- int[] numsThree =new int[3]
- int[] numsFour =new int[]
-
数组的长度一旦固定了,就不能再被改变了
int[] numbers = new int [10]; //默认初始化值都是0bool[] bools = new bool[10]; //默认初始化值都是falsestring[] str = new string[10]; //默认初始化值都是nullchar[] chars = new char[10]; //默认初始化值都是'\0'
for (int i = 0; i < nums.Length - 1; i++) { //for (int j = nums.Length - 1; j > i; j--)//交换排序 for (int j=0;j<nums.Length-i-1;j++)//冒泡排序 { cou2++; if (nums[i] > nums[j]) { nums[i] = nums[i] ^ nums[j]; nums[j] = nums[i] ^ nums[j]; nums[i] = nums[i] ^ nums[j]; //break;交换排序 } }}//上面的代码可以使用c#内部方法来实现Array.Sort(nums);//这是将数组元素升序排列Array.Reverse(nums);//这只是将数组中的元素反转,如果我们想要降序排列需要使用到这两个方法才行
方法
函数就是将一堆代码进行重用的一种机制,语法如下:
[public] static 返回值类型 方法名([参数列表]){ 方法体;}public : 访问修饰符,公开的,公共的,哪都可以访问的static : 静态的返回值类型 : 如果不需要返回值,写void方法名 : Pascal 每个单词的首字母都大写,其余字母小写参数列表 : 完成这个方法所必须要提供给这个方法的条件,如果没有参数,括号不能省略,不同于matlabreturn : 返回方法所返回的值,或者立即结束当前方法
eg:
public static int GetMax(int n1,int n2){ return n1 > n2 ? n1 : n2;}
Remark: 1、我们在Main()函数中,调用Test()函数,于是称Main函数为调用者,Test函数为被调用者,如果被调用者想要得到调用者的值:1)传递参数 2)使用静态字段来模拟全局变量(在类下面写 eg:public static int _number = 10;),因为C#不具有全局变量,所以采取这种方式
2、不管时实参还是形参,都是在内存中开辟了空间的
3、方法的功能一定要单一
4、方法中最忌讳的就是提示用户输入内容
out、ref、params
out参数
如果你在一个方法中,返回多个相同类型的值的时候,可以考虑返回一个数组。
但是如果返回多个不同类型的指定时候,返回数组就不行了,需要使用out参数
out参数就侧重于在一个方法中可以返回多个不同类型的值,类似C中指针
out参数在方法的内部必须赋值,否则会报错
using System;using System.Runtime.InteropServices;namespace ConsoleApp1{ class Program { static void Main(string[] args) { int[] nums = {362,12,653,324,23,53}; float[] res = GetResult(nums); Console.WriteLine("max:{0},min:{1},sum:{2},avg:{3:0.00}", res[0], res[1], res[2], res[3]); int max; int min; int sum; int avg; Test(nums,out max,out min,out sum,out avg); Console.WriteLine("max:{0},min:{1},sum:{2},avg:{3:0.00}", max,min,sum,avg); } public static float[] GetResult(int[] nums) { float[] result = new float[4]; result[0] = nums[0]; result[1] = nums[0]; result[2] = 0; result[3] = 0; for (int i = 0; i < nums.Length; i++) { if (result[0] < nums[i]) //max { result[0] = nums[i]; } if (result[1] > nums[i]) //min { result[1] = nums[i]; } result[2] += nums[i]; } result[3] = result[2] / nums.Length; return result; } public static void Test(int[] nums, out int max, out int min, out int sum, out int abg) { max = nums[0]; min = nums[0]; sum = 0; for (int i = 0; i < nums.Length; i++) { if (max < nums[i]) { max = nums[i]; } if (min>nums[i]) { min = nums[i]; } sum += nums[i]; } abg = sum / nums.Length; } }}
ref 参数
能够将一个变量带入一个方法中进行改变,改变完成后,再将改变后的值带出方法,类似于C中的指针
不同与out参数,ref 参数要求必须在方法外为其赋值,而方法内不能赋值,调用方法时,需要使用 ref + 实际参数
using System;using System.Runtime.InteropServices;namespace ConsoleApp1{ class Program { public static void change(ref int a, ref int b) { a = b-a; b = b-a; a = a + b; } static void Main(string[] args) { int a = 2; int b = 3; Console.WriteLine("a={0},b={1}",a,b); change(ref a, ref b); Console.WriteLine("a={0},b={1}",a,b); } } }
params 参数
将实参列表中跟可变参数数组类型一致的元素都当作数组的元素去处理,但是他必须是形参列表的最后一个元素,并且是唯一的
using System;using System.Runtime.InteropServices;namespace ConsoleApp1{ class Program { public static void Grade(string name, params int[] score) { int sum = 0; for (int i = 0; i < score.Length; i++) { sum += score[i]; } Console.WriteLine("{0}这次考试总成绩为{1}",name,sum); } static void Main(string[] args) { Grade("张三",90,91,92,93); } }}
方法重载
概念:方法的重载指的是方法的名称相同,但是参数不同
- 如果参数的个数相同,那么参数的类型就不能相同
- 如果参数的类型相同,那么参数的个数就不能相同
- 方法的重载跟返回值没关系
方法的综合练习
1、题目:
- 提示用户输入两个数字,计算这两个数字之间所有整数的和
- 用户只能输入数字
- 计算两个数字之间的和
- 要求第一个数字必须比第二个数字小
using System;using System.Globalization;using System.Runtime.InteropServices;using System.Threading;namespace ConsoleApp1{ class Program { static void Main(string[] args) { bool bb; int num11; int num22; MInput(out bb, out num11, out num22); while (!bb) { MInput(out bb, out num11, out num22); } int summary = Number.AddTwo(num11, num22); Console.WriteLine("{0}到{1}之间所有整数(包含{2}和{3})的和为:{4}",num11,num22,num11,num22,summary); } /// <summary> /// 调用方法,获得结果 /// </summary> /// <param name="bb"></param> /// <param name="num11"></param> /// <param name="num22"></param> public static void MInput(out bool bb, out int num11, out int num22) { string num1 = Number.InputNumber(); num11 = Number.GetNumber(num1); string num2 = Number.InputNumber(); num22 = Number.GetNumber(num2); bb = Number.JudgeNumber(num11, num22); } } class Number { public static int count = 1; /// <summary> /// 处理键盘输入的文本内容,将其转换为整型,否则提示输入错误 /// </summary> /// <param name="s"></param> /// <returns>number(用户输入的数字)</returns> public static int GetNumber(string s) { while (true) { try { int number = int.Parse(s); return number; } catch { Console.WriteLine("输入错误,请重新输入:"); s = Console.ReadLine(); GetNumber(s); } } } /// <summary> /// 判断输入的数字是否符合要求 /// </summary> /// <param name="n1"></param> /// <param name="n2"></param> /// <returns>if(n1>=n2) return false; /// return true</returns> public static bool JudgeNumber(int n1, int n2) { if (n1 >= n2) { Console.WriteLine("请重新输入,数字1必须小于数字2"); return false; } return true; } /// <summary> /// 获取键盘输入文本 /// </summary> /// <returns>input</returns> public static string InputNumber() { Console.WriteLine("请输入数字{0}:", count++); string input = Console.ReadLine(); if (count > 2) { count = 1; } return input; } /// <summary> /// 求和 /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns>a到b之间所有整数的和</returns> public static int AddTwo(int a, int b) { int sums = 0; for (int i = a; i < b+1; i++) { sums += i; } return sums; } }}
2、题目:
- 用户只能输入一个整数
- 对用户输入的数进行判断,如果是素数就返回是素数,否则返回是合数及其因子list
using System;using System.Globalization;using System.Runtime.InteropServices;using System.Threading;using System.Collections.Generic;namespace ConsoleApp1{ class Program { static void Main(string[] args) { int numb = Number.GetNum(); bool bb = Number.IsPrime(numb); if (bb) { Console.WriteLine("{0} 是一个素数",numb); } else { Console.WriteLine("{0} 是一个合数,因子如下:",numb); List<int> res=Number.YinZi(numb); foreach (var mm in res) { Console.Write("{0}\t",mm); } } } } class Number { public static bool IsPrime(int a) { for (int i = 2; i < Math.Sqrt(a); i++) { if (a % i == 0) { return false; } } return true; } public static int GetNum() { Console.WriteLine("请输入一个数字:"); string s = Console.ReadLine(); int num; while (true) { try { num = int.Parse(s); return num; } catch { Console.WriteLine("输入有误,请重新输入:"); s = Console.ReadLine(); } } } public static List<int> YinZi(int numb1) { List<int> results = new List<int>(); int m; // int fist = 1; // results.Add(fist); for (int pri = 1; pri <=Math.Sqrt(numb1); pri++) { if (numb1 % pri == 0) //&& pri != numb) { results.Add(pri); m = numb1 / pri; results.Add(m); } } // results.Add(numb1); // foreach (var result in results) // { // Console.WriteLine(result); // } return results; } }}
字符串
字符串常用类方法
String 类有许多方法用于 string 对象的操作。下面的表格提供了一些最常用的方法:
| 序号 | 方法名称 & 描述 |
|---|---|
| 1 | public static int Compare(
string strA,
string strB
)
比较两个指定的 string 对象,并返回一个表示它们在排列顺序中相对位置的整数。该方法区分大小写。 |
| 2 | public static int Compare(
string strA,
string strB,
bool ignoreCase
)
比较两个指定的 string 对象,并返回一个表示它们在排列顺序中相对位置的整数。但是,如果布尔参数为真时,该方法不区分大小写。 |
| 3 | public static string Concat(
string str0,
string str1
)
连接两个 string 对象。 |
| 4 | public static string Concat(
string str0,
string str1,
string str2
)
连接三个 string 对象。 |
| 5 | public static string Concat(
string str0,
string str1,
string str2,
string str3
)
连接四个 string 对象。 |
| 6 | public bool Contains(
string value
)
返回一个表示指定 string 对象是否出现在字符串中的值。 |
| 7 | public static string Copy(
string str
)
创建一个与指定字符串具有相同值的新的 String 对象。 |
| 8 | public void CopyTo(
int sourceIndex,
char[] destination,
int destinationIndex,
int count
)
从 string 对象的指定位置开始复制指定数量的字符到 Unicode 字符数组中的指定位置。 |
| 9 | public bool EndsWith(
string value
)
判断 string 对象的结尾是否匹配指定的字符串。 |
| 10 | public bool Equals(
string value
)
判断当前的 string 对象是否与指定的 string 对象具有相同的值。 |
| 11 | public static bool Equals(
string a,
string b
)
判断两个指定的 string 对象是否具有相同的值。 |
| 12 | public static string Format(
string format,
Object arg0
)
把指定字符串中一个或多个格式项替换为指定对象的字符串表示形式。 |
| 13 | public int IndexOf(
char value
)
返回指定 Unicode 字符在当前字符串中第一次出现的索引,索引从 0 开始。 |
| 14 | public int IndexOf(
string value
)
返回指定字符串在该实例中第一次出现的索引,索引从 0 开始。 |
| 15 | public int IndexOf(
char value,
int startIndex
)
返回指定 Unicode 字符从该字符串中指定字符位置开始搜索第一次出现的索引,索引从 0 开始。 |
| 16 | public int IndexOf(
string value,
int startIndex
)
返回指定字符串从该实例中指定字符位置开始搜索第一次出现的索引,索引从 0 开始。 |
| 17 | public int IndexOfAny(
char[] anyOf
)
返回某一个指定的 Unicode 字符数组中任意字符在该实例中第一次出现的索引,索引从 0 开始。 |
| 18 | public int IndexOfAny(
char[] anyOf,
int startIndex
)
返回某一个指定的 Unicode 字符数组中任意字符从该实例中指定字符位置开始搜索第一次出现的索引,索引从 0 开始。 |
| 19 | public string Insert(
int startIndex,
string value
)
返回一个新的字符串,其中,指定的字符串被插入在当前 string 对象的指定索引位置。 |
| 20 | public static bool IsNullOrEmpty(
string value
)
指示指定的字符串是否为 null 或者是否为一个空的字符串。 |
| 21 | public static string Join(
string separator,
string[] value
)
连接一个字符串数组中的所有元素,使用指定的分隔符分隔每个元素。 |
| 22 | public static string Join(
string separator,
string[] value,
int startIndex,
int count
)
连接一个字符串数组中的指定位置开始的指定元素,使用指定的分隔符分隔每个元素。 |
| 23 | public int LastIndexOf(
char value
)
返回指定 Unicode 字符在当前 string 对象中最后一次出现的索引位置,索引从 0 开始。 |
| 24 | public int LastIndexOf(
string value
)
返回指定字符串在当前 string 对象中最后一次出现的索引位置,索引从 0 开始。 |
| 25 | public string Remove(
int startIndex
)
移除当前实例中的所有字符,从指定位置开始,一直到最后一个位置为止,并返回字符串。 |
| 26 | public string Remove(
int startIndex,
int count
)
从当前字符串的指定位置开始移除指定数量的字符,并返回字符串。 |
| 27 | public string Replace(
char oldChar,
char newChar
)
把当前 string 对象中,所有指定的 Unicode 字符替换为另一个指定的 Unicode 字符,并返回新的字符串。 |
| 28 | public string Replace(
string oldValue,
string newValue
)
把当前 string 对象中,所有指定的字符串替换为另一个指定的字符串,并返回新的字符串。 |
| 29 | public string[] Split(
params char[] separator
)
返回一个字符串数组,包含当前的 string 对象中的子字符串,子字符串是使用指定的 Unicode 字符数组中的元素进行分隔的。 |
| 30 | public string[] Split(
char[] separator,
int count
)
返回一个字符串数组,包含当前的 string 对象中的子字符串,子字符串是使用指定的 Unicode 字符数组中的元素进行分隔的。int 参数指定要返回的子字符串的最大数目。 |
| 31 | public bool StartsWith(
string value
)
判断字符串实例的开头是否匹配指定的字符串。 |
| 32 | public char[] ToCharArray() 返回一个带有当前 string 对象中所有字符的 Unicode 字符数组。 |
| 33 | public char[] ToCharArray(
int startIndex,
int length
)
返回一个带有当前 string 对象中所有字符的 Unicode 字符数组,从指定的索引开始,直到指定的长度为止。 |
| 34 | public string ToLower() 把字符串转换为小写并返回。 |
| 35 | public string ToUpper() 把字符串转换为大写并返回。 |
| 36 | public string Trim() 移除当前 String 对象中的所有前导空白字符和后置空白字符。 |
可空类型(Nullable)
C# 单问好 ? 与双问号 ??
? 单问号用于对 int、double、bool 等无法直接赋值为 null 的数据类型进行 null 的赋值,意思是这个数据类型是 Nullable 类型的。
int? i = 3;
等同于:Nullable
int i; //默认值0
int? ii; //默认值null
?? 双问号用于判断一个变量在为 null 的时候返回一个指定的值。
-
a = b ?? c如果 b 为 null,则 a = c,如果 b 不为 null,则 a = b。
C# 可空类型(Nullable)
C# 提供了一个特殊的数据类型,nullable 类型(可空类型),可空类型可以表示其基础值类型正常范围内的值,再加上一个 null 值。
例如,Nullable< Int32 >,读作"可空的 Int32",可以被赋值为 -2,147,483,648 到 2,147,483,647 之间的任意值,也可以被赋值为 null 值。类似的,Nullable< bool > 变量可以被赋值为 true 或 false 或 null。
在处理数据库和其他包含可能未赋值的元素的数据类型时,将 null 赋值给数值类型或布尔型的功能特别有用。例如,数据库中的布尔型字段可以存储值 true 或 false,或者,该字段也可以未定义。
声明一个 nullable 类型(可空类型)的语法如下:
<data_type> ? <variable_name> = null;
using System;namespace CalculatorApplication{ class NullablesAtShow { static void Main(string[] args) { int? num1 = null; int? num2 = 45; double? num3 = new double?(); double? num4 = 3.14157; bool? boolval = new bool?(); // 显示值 Console.WriteLine("显示可空类型的值: {0}, {1}, {2}, {3}", num1, num2, num3, num4); Console.WriteLine("一个可空的布尔值: {0}", boolval); } }}result:显示可空类型的值: , 45, , 3.14157一个可空的布尔值:
Null 合并运算符( ?? )
Null 合并运算符用于定义可空类型和引用类型的默认值。Null 合并运算符为类型转换定义了一个预设值,以防可空类型的值为 Null。Null 合并运算符把操作数类型隐式转换为另一个可空(或不可空)的值类型的操作数的类型。
如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值
using System;namespace CalculatorApplication{ class NullablesAtShow { static void Main(string[] args) { 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.ReadLine(); } }}result:num3 的值: 5.34num3 的值: 3.14157
num3 = num1 ?? 5.34;num3 = (num1 == null) ? 5.34 : num1;
面向对象
类
语法:
[public] class 类名{ 字段;//Field 属性;//Method 方法;//Property}
类中的静态方法(有static 修饰的方法)使用类名.方法的方式来调用
类中的实例方法(没有static修饰的方法)使用 对象.方法的方式来调用
写好一个类之后,我们需要创建这个类的对象,于是我们称创建的这个类的对象过程称之为类的实例化,使用关键字new来实现,实例方法是对象的方法,不是类的,需要有对象才能使用,但是静态方法必须要是类才能调用,使用new 获得一个对象后,可以使用这个对象所在类的实例方法。
在实例方法中,如果需要使用到字段的值,需要使用this.字段名的方式来调用。
类是不占内存的,而对象是占内存的(与其说对象是占内存的,不如说是字段是占内存的,因为字段会自己初始化值)。
类的属性
属性的作用就是保护字段,对字段的赋值和取值进行限定
//在面向对象编程(OOP)中,是不允许外界直接对类的成员变量直接访问的,既然不能访问//所以C#中就要用set和get方法来访问私有成员变量,它们相当于外界访问对象的一个通道,一个“接口”。先来看一段代码:class Employee { private string name; private byte age; public string Name { get { return name; } set { name = value; } } public byte AgeC# { get { return age; } set { age = value; } } }
代码中定义了两个私有变量name和age,当我们不想让外界随意访问该私有变量时,可以使用属性来访问,语法为:
public <返回类型(要与被访问变量的类型相同)> <属性名(不能与被访问变量同名)>{ get{ return <被访问变量>;} set{ <被访问变量> = value;}}
当我们使用属性来访问私有成员变量时就会调用里面的get方法,当我们要修改该变量时就会调用set方法,必须使用上面的语法来定义属性。对get和set方法的内部进行修改只是限定而已
-
既有get()也有set()<==>可读可写
-
只有get()<==>只读
-
只有set()<==>只写
public char Gender { get { if (_gender!='男'&&_gender!='女') { return _gender = 'q'; } return _gender='女'; } set { if (value!='p') { value = 'p'; } _gender = value; } } //我们可以看到二者可以同时定义 //如果只定义了get或者set其中的一个, //那么只定义了get就必须在get中为字段赋值,也就是对象的该字段的值是确定的只能是get中赋的值,可以访问得到赋的值,但是不能修改 //只定义了set的就必须在new对象的时候为该字段赋值,但是不能够访问,因为只具有只写属性 如这个报错 :Program.cs(73, 76): [CS0154] 属性或索引器“Person.Age”不能用在此上下文中,因为它缺少 get 访问器
当属性对字段进行保护后,就不需要再用public修饰了,这样系统会默认是private修饰,我们无法使用字段名称对对象进行修改,但是可以使用属性名称达到相同的目的,可以通过这种属性来控制对员变量的读写,防止对成员变量的非法赋值等。
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Person suQuan = new Person();
suQuan.Name = "孙权";
suQuan.Age = 23;
suQuan.Gender = '男';
suQuan.Chels();
}
}
public class Person
{
string _name;
public string Name
{
get { return _name; }//这里get得到的字段的值,并返回字段的值就是经过set处理过的_name=value的值
set { _name = value; }//这里set的value就是new对象时为这个属性赋的值,也即我们个人赋的值
}
int _age;
public int Age
{
get { return _age; }
set { _age = value; }
}
char _gender;
public char Gender
{
get { return _gender; }
set { _gender = value; }
}
public void Chels()
{
Console.WriteLine("我叫{0},今年{1}岁了,是一个{2}生,可以吃喝拉撒睡!", this.Name, this.Age, this.Gender);
}
}
}
class Employee
{
private string name;//字段被设置为private后,只能再当前所在类中访问
private byte age;
public string Name//在这个过程中,属性没有赋值,只是对字段的值做了一个转换的工作
{
get { return name; }//当输出属性的值的时候,会执行get方法
set { name = value; }//当给属性赋值的时候,首先会执行set方法
}
//****修改后****↓↓↓↓↓↓↓↓
public byte Age
{
get { return age; }
set
{
if (value > 10 && value<=100) //一般在公司雇员的年龄都在10到100岁之间
age = value;
}
}
//****修改后****↑↑↑↑↑↑↑↑
}
字段被设置为private后,只能在当前所在类中访问
通过属性,对字段的值进行限制,当对属性进行赋值时,会走set方法,如下面的例子,但是我们需要注意到一点很重要的是,先执行set方法,后执行get方法,两者都是对字段进行限制的方式,但是二者在同一个字段的限定中只能存在一个(set是value对字段赋值,get是return赋值后的字段值)
也就是说:对取值进行限定在get方法里,对设值进行限定在set方法里
eg:
int _age;
public int Age
{
get { return _age; }
set
{
if (value<0|| value>100)
{
value = 0;
}
_age = value;
}
}
public char Gender
{
get
{
if (_gender!='男'&&_gender!='女')
{
return _gender = '男';
}
return _gender='女';
}
set { _gender = value; }
}
关于 this.属性名 和 this.字段名 这两者的区别是 this.属性名 调用属性的get方法,this.字段名 调用属性的set方法C#
public char Gender
{
get
{
if (_gender!='男'&&_gender!='女')
{
return _gender = 'Q';
}
return _gender='女';
}
set
{
if (value!='P')
{
value = 'P';
}
_gender = value;
}
}
。。。。。。。。
Person suQuan = new Person();
suQuan.Name = "孙权";
suQuan.Gender = 'F';
suQuan.Chels();
。。。。。。。。
//下面是this._gender(字段名)
/* public void Chels()
{
Console.WriteLine("我叫{0},是一个 {1} 生,可以吃喝拉撒睡!", this.Name, this._gender);
}
结果是:我叫孙权,今年岁了,是一个 P 生,可以吃喝拉撒睡!
*/
//下面是this.Gender(属性名)
/*public void Chels()
{
Console.WriteLine("我叫{0},今年岁了,是一个{1}生,可以吃喝拉撒睡!", this.Name, this.Gender);
}
结果是:我叫孙权,今年岁了,是一个 Q 生,可以吃喝拉撒睡!
*/
静态方法和非静态方法的区别
在非静态类中,既可以有实例成员(非静态成员)也可以有静态成员。(这里成员包括了方法,字段,属性)
-
在调用实例成员时,需要使用对象名.实例成员( )
在调用静态成员时,需要使用类名.静态成员( ) -
非静态类中可以定义静态字段: eg:private static string Name;{ get{return Person._name;} set{Pserson._name =value;}}
也可以定义非静态字段:eg:private char _gender; -
静态函数中,只能访问静态成员,不能访问实例成员,(静态方法比实例化先执行)
-
实例方法中,既可以使用实例成员,也可以使用静态成员
静态类中,只能使用静态成员(静态方法,静态字段)
- 静态类不能创建对象,因为他所有的成员都需要使用类名.静态成员来调用
- 静态类中不能定义非静态方法
- 静态类中只允许有静态成员
using System;
using System.Threading.Tasks;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections.Generic;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
//调用实例成员
Person p = new Person();
p.M1();
Person.M2();
//Student s = new Student(); 这是错误的,静态类不能创建对象,因为他所有的成员都需要使用类名.静态成员来调用
}
}
public class Person //非静态类
{
public void M1()
{
Console.WriteLine("我是非静态类中的一个非静态方法");
}
public static void M2()
{
Console.WriteLine("我是非静态类中的一个静态方法");
}
}
public static class Student
{
private static string _name;
public static string Name
{//静态类中的静态字段
get { return Student._name;}
set { Student._name = value; }
}
public static void M3()
{
Console.WriteLine("我是静态类中的一个静态方法");
}
/*public void M4()
{
Console.WriteLine("我是静态类中的一个非静态方法,我是错误的语法,静态类中不能定义非静态方法");
}*/
//private int age;这是错误的,静态类中只允许有静态成员
}
}
静态与非静态的使用
1、如果你想要你的类当作一个“工具类”去使用,这个时候可以考虑将类写成静态的
2、静态类在整个项目中资源共享 (堆 栈 方法区--> 静态存储区域)
- 只有当程序全部结束之后静态类才会释放资源
- C#拥有垃圾回收器(GC garbage collection)
构造函数
作用:帮助我们初始化对象(给对象的每个属性依次的赋值)
构造函数是一个特殊的方法:
- 构造函数没有返回值,连void也不能写
- 构造函数的名称必须跟类名一样
- 构造函数可以有参数,new对象的时候传递参数即可
- 如果不指定构造函数,则类有一个默认的无参数构造函数
- 如果指定了构造函数,则不再有默认的无参数构造函数
- 如果需要无参构造函数,则需要自己来写
- 构造函数可以重载,也就是有多个参数不同的构造函数
using System;
using System.Threading.Tasks;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Student zsStudent = new Student("张三",18,'中',120,150,135);
Console.WriteLine("这是第一个构造方法");
// zsStudent.Name = "张三";
// zsStudent.Age = 18;
// zsStudent.Gender = '中';
// zsStudent.Chinese = 120;
// zsStudent.Math = 150;
// zsStudent.English = 135;
zsStudent.SayHello();
zsStudent.ShowScore();
Student xlStudent = new Student("小兰",17,'女');
// xlStudent.Name = "小兰";
// xlStudent.Age = 17;
// xlStudent.Gender = '女';
// xlStudent.Chinese = 130;
// xlStudent.Math = 145;
// xlStudent.English = 148;
Console.WriteLine("这是第二个构造方法");
xlStudent.SayHello();
// xlStudent.ShowScore();
}
}
public class Student
{
public Student(string name,int age,char gender,int chinese,int math,int english)
{
this.Name = name;
this.Age = age;
this.Gender = gender;
this.Chinese = chinese;
this.Math = math;
this.English = english;
Console.WriteLine("构造函数在类初始化的时候被调用了");
}
public Student(string name, int age, char gender)
{
this.Name = name;
this.Age = age;
this.Gender = gender;
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private int _age;
public int Age
{
get { return _age; }
set
{
if (value<0||value>100)
{
value = 0;
}
_age = value;
}
}
private char _gender;
public char Gender
{
get {
if (_gender!='男'&& _gender!='女')
{
return _gender='男';
}
return _gender; }
set { _gender = value; }
}
private int _math;
public int Math
{
get { return _math; }
set { _math = value; }
}
private int _chinese;
public int Chinese
{
get { return _chinese; }
set { _chinese = value; }
}
private int _english;
public int English
{
get { return _english; }
set { _english = value; }
}
public void SayHello()
{
Console.WriteLine("我叫{0},今年{1}岁,是{2}生",this.Name,this.Age,this.Gender);
}
public void ShowScore()
{
Console.WriteLine("我叫{0},我的总成绩是{1},平均成绩是{2}",this.Name,this.Math+this.Chinese+this.English,(this.Math+this.Chinese+this.English)/3);
}
}
}
new关键字
Person zsPerson = new Person();
new帮助我们做了三件事:
- 在内存中开辟一块空间
- 在开辟的空间中创建对象
- 调用对象的构造函数进行初始化对象
using System;
using System.Threading.Tasks;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Student zsStudent = new Student("张三",18,'中',120,150,135);
Console.WriteLine("这是第一个构造方法");
// zsStudent.Name = "张三";
// zsStudent.Age = 18;
// zsStudent.Gender = '中';
// zsStudent.Chinese = 120;
// zsStudent.Math = 150;
// zsStudent.English = 135;
zsStudent.SayHello();
zsStudent.ShowScore();
Student xlStudent = new Student("小兰",17,'女');
// xlStudent.Name = "小兰";
// xlStudent.Age = 17;
// xlStudent.Gender = '女';
// xlStudent.Chinese = 130;
// xlStudent.Math = 145;
// xlStudent.English = 148;
Console.WriteLine("这是第二个构造方法");
xlStudent.SayHello();
// xlStudent.ShowScore();
}
}
public class Student
{
public Student(string name,int age,char gender,int chinese,int math,int english)
{
this.Name = name;
this.Age = age;
this.Gender = gender;
this.Chinese = chinese;
this.Math = math;
this.English = english;
Console.WriteLine("构造函数在类初始化的时候被调用了");
}
public Student(string name, int age, char gender)
{
this.Name = name;
this.Age = age;
this.Gender = gender;
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private int _age;
public int Age
{
get { return _age; }
set
{
if (value<0||value>100)
{
value = 0;
}
_age = value;
}
}
private char _gender;
public char Gender
{
get {
if (_gender!='男'&& _gender!='女')
{
return _gender='男';
}
return _gender; }
set { _gender = value; }
}
private int _math;
public int Math
{
get { return _math; }
set { _math = value; }
}
private int _chinese;
public int Chinese
{
get { return _chinese; }
set { _chinese = value; }
}
private int _english;
public int English
{
get { return _english; }
set { _english = value; }
}
public void SayHello()
{
Console.WriteLine("我叫{0},今年{1}岁,是{2}生",this.Name,this.Age,this.Gender);
}
public void ShowScore()
{
Console.WriteLine("我叫{0},我的总成绩是{1},平均成绩是{2}",this.Name,this.Math+this.Chinese+this.English,(this.Math+this.Chinese+this.English)/3);
}
}
}
继承
在一些类中,写一些重复的成员,我们可以将这些重复的成员单独封装到一个类中,作为这些类的父类,子类继承了父类的属性方法,但是不会继承父类的私有字段,因为子类根本访问不到,子类不能继承父类的构造函数,但是,子类会默认调用父类的无参数的构造函数,所以,如果在父类中重新写了一个有参数的构造函数之后,那个无参数的就被干掉了,子类就调用不到了,所以子类会报错
解决方法:
-
在父类中重新写一个无参数的构造函数
-
在子类中显示的调用父类的构造函数,使用关键字:base()
public class Student:Person { public Student(string name,int age,char gender,int id):base(name,age,gender) { //this.Name=name; //this.Age=age; //this.Gender=gender; 这三个字段是继承自父类的,使用base后就可以不用写了 this.Id=id; } private int Id { ger{return _id;} set{_id=value;} } } public class Perosn() { public Person(string name,int age,char gender) { this.Name=name; this.Age=age; this.Gender=gender; } private int _age; public string Age { get{return _age;} set{_age=value;} } private char _gender; public char Gender; { get{return _gender;} set{_gender=value;} } }
继承的特性:
- 继承的单根性:一个子类只能有一个父类
- 继承的传递性
里氏转换
- 子类可以赋值给父类
- 如果父类中装的是子类对象,那么可以讲这个父类强转为子类对象。
- 子类对象可以调用父类中的成员,但是父类对象永远都只能调用自己的成员。
- is:表示类型转换,如果能够转换成功,则返回一个true,否则返回一个false
as:表示类型转换,如果能够转换则返回对应的对象,否则返回一个null - protected
受保护的:可以在当前类的内部以及该类的子类中访问。
public class Person
{
public void SayHello()
{
Console.WriteLine("我是老师");
}
}
public class Student : Person
{
public void SayHello()
{
Console.WriteLine("我是学生");
}
}
…………
Person p = new Student;
Student s = (Student) p;
p.SyaHello();//结果是 我是学生
封装
C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。
一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:
public:所有对象都可以访问;
private:对象本身在对象内部可以访问;
protected:只有该类对象及其子类对象可以访问
internal:同一个程序集的对象可以访问;
protected internal:访问限于当前程序集或派生自包含类的类型。

1、public任何公有成员可以被外部的类访问。
2、private只有同一个类中的函数可以访问它的私有成员。
3、protected该类内部和继承类中可以访问。
4、internal同一个程序集的对象可以访问。
5、protected internal3 和 4 的并集,符合任意一条都可以访问。
Protected Internal 访问修饰符允许在本类,派生类或者包含该类的程序集中访问。这也被用于实现继承。
private < internal/protected < protected internal < public
比如说:一个人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都知道,其它人不知道
多态
多态:一个接口多个功能。
静态多态性:编译时发生函数响应(调用);
动态多态性:运行时发生函数响应。
静态绑定(早期绑定):编译时函数和对象的连接机制。 两种技术实现静态多态性:函数重载/运算符重载。
函数重载:在同一范围内对相同函数名有多个定义,可以是参数类型或参数个数的不同,但不许只有返回值类型不同。
运算符重载:
关键字 abstract 声明抽象类:用于接口部分类的实现(派生类继承抽象类时,实现完成)。抽象类包含抽象方法,抽象方法可被派生类实现。
抽象类规则:
- 1.不能创建抽象类的实例
- 2.不能在抽象类外定义抽象方法
- 3.不能把抽象类声明为sealed(类前带关键字sealed代表该类是密封类,不能被继承)
关键字virtual声明虚方法:用于方法在继承类中的实现(在不同的继承类中有不同的实现)。
抽象类和虚方法共同实现动态多态性。
注:继承类中的重写虚函数需要声明关键字 override,在方法参数传入中写(类名 形参名)例如 public void CallArea(Shape sh),意思是传入一个 shape 类型的类。
虚方法
(注意虚方法不同于子类重写父类方法,因为子类重写父类方法只能显示的用子类对象来调用重写过的方法,不能被父类对象集合或者数组元素调用)
步骤:
- 将父类的方法标记为虚方法,使用关键字 virtual,之后这个函数可以被子类重新写一遍(也即重写)
- 在子类中的方法加上关键字 override
class Program
{
static void Main(string[] args)
{
// Person[] pers = new Person[8];
Chinese cn1 = new Chinese("韩梅梅");
Chinese cn2 = new Chinese("李雷");
Japanses ja1 = new Japanses("竹下峻");
Japanses ja2 = new Japanses("井边子");
Korea ko1 = new Korea("金休闲");
Korea ko2 = new Korea("朴景辉");
American am1 = new American("特朗普");
American am2 = new American("拜登");
Person[] pers = {cn1, cn2, ja1, ja2, ko1, ko2, am1, am2,new English("Bob"),new English("Alice")};
for (int i = 0; i < pers.Length; i++)
{
pers[i].SayHello();
}
}
}
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public Person(string name) //构造方法
{
this.Name = name;
}
public virtual void SayHello()
{
Console.WriteLine("我是人类");
}
}
public class Chinese : Person
{
public Chinese(string name) : base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是中国人,我叫{0}", this.Name);
}
}
public class Japanses : Person
{
public Japanses(string name) : base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是脚盆国人,我叫{0}", this.Name);
}
}
public class Korea : Person
{
public Korea(string name) : base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是棒之思密达,我叫{0}", this.Name);
}
}
public class American : Person
{
public American(string name) : base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是美国人,我叫{0}", this.Name);
}
}
public class English : Person
{
public English(string name) : base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是一个英国人,名字叫做{0}",this.Name);
}
}
虚方法练习
using System;
class Animal
{
public virtual void Eat()
{
Console.WriteLine("这是animal的eat方法");
}
}
class Dog:Animal
{
public override void Eat()
{
Console.WriteLine("这是dog的eat");
}
}
class Cat : Animal
{
public override void Eat()
{
Console.WriteLine("这是cat的eat");
}
}
class Program
{
static void Main(string[] args)
{
// 多态的实现前提是继承和方法重写,基类可以new所有的派生类 new哪个类,他就执行那个类的方法
Animal animal1 = new Animal(); // 实例Animal类
Animal animal2 = new Dog(); // 实例Dog类
Animal animal3 = new Cat(); // 实例Cat类
animal1.Eat(); // 调用Animal类的eat方法
animal2.Eat(); // 实例Dog类
animal3.Eat(); // 实例Cat类
}
}
抽象类
- 当父类中的方法不知道如何去实现的时候,可以考虑将父类写成抽象类,将方法写成抽象方法
实例如下:狗狗会叫,猫咪会叫
class Program
{
static void Main(string[] args)
{
// Animal a = new Animal();//这条语句是错误的,抽象类无法创建对象,后面会提到接口也是无法创建对象的
Animal dog = new Dog();
Animal cat = new Cat();
Animal[] ani = {dog, cat};
for (int i = 0; i < ani.Length; i++)
{
ani[i].Bark();
}
}
public abstract class Animal
{
public abstract void Bark(); //抽象类才能定义抽象方法,抽象方法没有方法体
//为什么定义抽象类是因为我们并不知道这个函数是如何实现的
}
public class Dog : Animal
{
public override void Bark()
{
Console.WriteLine("狗狗旺旺的叫");
}
}
public class Cat : Animal
{
public override void Bark()
{
Console.WriteLine("猫咪喵喵的叫");
}
}
}
抽象成员必须标记为abstract,不能有任何实现
抽象成员必须在抽象类中
抽象类不能被实例化
子类继承抽象类后,必须把父类中的所有抽象成员都重写(除非子类也是一个抽象类,则可以不重写)
抽象类成员的访问修饰符不能是private
在抽象类中可以包含实例化成员,并且抽象类的实例成员可以不被子类实现
抽象类是有构造函数的,虽然不能被实例化
如果父类的抽象方法中有参数,那么继承这个抽象父类的子类在重写父类的方法的时候必须传入对应的参数,如果抽象父类的抽i想方法中有返回值,那么子类在重写这个抽象方法的时候也必须要传入返回值
如果父类中的方法有默认的实现,并且父类需要被实例化(父类需要调用时或者父类有意义时),这时可以考虑将父类定义成一个普通类,用虚方法来实现
如果父类中的方法没有默认实现,父类也不需要被实例化(不需要具体实现父类的功能时),则可以将该类定义为抽象类
抽象类练习
1、使用多态求矩形的面积和周长以及圆形的面积和周长
class Program
{
static void Main(string[] args)
{
Shape circle1 = new Circle(2);
Shape square1 = new Square(1, 2);
Console.WriteLine(circle1.GetArea());
Console.WriteLine(circle1.GetPerimeter());
Console.WriteLine(square1.GetArea());
Console.WriteLine(square1.GetPerimeter());
}
}
public abstract class Shape
{
public abstract double GetArea();
public abstract double GetPerimeter();
}
public class Circle : Shape
{
private double _r;
public double R
{
get { return _r; }
set { _r = value; }
}
public Circle(double r)
{
this.R = r;
}
public override double GetArea()
{
return Math.PI * this.R * this.R;
}
public override double GetPerimeter()
{ return Math.PI * this.R *2;
}
}
public class Square : Shape
{
private double _height;
public double Height
{
get { return _height; }
set { _height = value; }
}
private double _length;
public double Length
{
get { return _length; }
set { _length = value; }
}
public Square(double height,double length)
{
this.Height = height;
this.Length = length;
}
public override double GetArea()
{
return this.Height * this.Length;
}
public override double GetPerimeter()
{
return (this.Height + this.Length) * 2;
}
}
2、使用多态来实现将移动硬盘或者U盘或者MP3插到电脑上进行读写数据
class Program
{
static void Main(string[] args)
{ //用多态来实现将硬盘或者U盘或者MP3插到电脑上进行速写数据
MobileStorage ms = new Mp3();
Computer cpu = new Computer();
cpu.Ms = ms;//利用对象的属性来调用
cpu.CpuRead();
cpu.CpuWrite();
// Mp3 mp3 = new Mp3();
// Computer com = new Computer();
// com.CpuRead(u);
// com.CpuWrite(md);
//
// MobileStorage msd = new MobileDisk();
// com.CpuRead(msd);
// com.CpuWrite(msd);
}
}
public abstract class MobileStorage
{
public abstract void Read();
public abstract void Write();
}
public class MobileDisk : MobileStorage
{
public override void Read()
{
Console.WriteLine("移动硬盘在读取数据");
}
public override void Write()
{
Console.WriteLine("移动硬盘正在写入数据");
}
}
public class UDisk : MobileStorage
{
public override void Write()
{
Console.WriteLine("U盘在写入数据");
}
public override void Read()
{
Console.WriteLine("U盘在读取数据");
}
}
public class Mp3 : MobileStorage
{
public override void Write()
{
Console.WriteLine("MP3在写入数据");
}
public override void Read()
{
Console.WriteLine("MP3在读取数据");
}
public void PlayMusic()
{
Console.WriteLine("Mp3自己正在播放音乐");
}
}
// public class Computer
// {
// public void CpuRead(MobileStorage ms)
// {
// ms.Read();
// }
//
// public void CpuWrite(MobileStorage ms)
// {
// ms.Write();;
// }
// }
public class Computer
{
private MobileStorage _ms;
public MobileStorage Ms
{
get { return _ms; }
set { _ms = value; }
}
public void CpuRead()
{
Ms.Read();
}
public void CpuWrite()
{
Ms.Write();;
}
序列化与反序列化
序列化:就是将对象转换为二进制
反序列化:就是将二进制转换为对象
作用:传输数据
序列化:
- 将这个类标记为可以被序列化,使用 [Serializable]标记
- BinaryFormatter bf = new BinaryFormatter();
class Program
{
static void Main(string[] args)
{
//要将P这个对象传输给对方电脑 序列化
// Person p = new Person();
// p.Name = "zhangsan";
// p.Gender = '男';
// p.Age = 18;
// using (FileStream fsWrite = new FileStream("D:/Desktop/C#.txt", FileMode.Create, FileAccess.Write))
// {
// //开始序列化对象
// BinaryFormatter bf = new BinaryFormatter();
// bf.Serialize(fsWrite,p);
//
// }
// 接收对方发送过来的二进制 反序列化
Person p2;
using (FileStream fsRead = new FileStream("D:/Desktop/C#.txt", FileMode.Open, FileAccess.Read))
{
BinaryFormatter bf = new BinaryFormatter();
p2 = (Person) bf.Deserialize(fsRead);
}
Console.WriteLine(p2.Name);
Console.WriteLine(p2.Gender);
Console.WriteLine(p2.Age);
}
}
[Serializable]
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private char _gender;
public char Gender
{
get { return _gender; }
set { _gender = value; }
}
private int _age;
public int Age
{
get { return _age; }
set { _age = value; }
}
}
部分类
使用 partial 标记,就是同一个类写在不同的地方
class Program
{
static void Main(string[] args)
{
//部分类
}
}
public partial class Person
{
}
public partial class Person
{
}
密封类
使用 sealed 标记
public sealed class Person
{
//用来标记一个类是密封的,不能去继承,但是可以继承于其他类
}
重写ToString方法
class Program
{
static void Main(string[] args)
{
//
Person p = new Person();
Console.WriteLine(p.ToString());//直接打印对象的ToString方法,结果是该对象的命名空间
}
}
public class Person
{
//用来标记一个类是密封的,不能去继承,但是可以继承于其他类
public override string ToString()//这里说明ToString是object类的虚方法,子类可以修改重写
{
// return base.ToString();
return "hello world";
}
}
接口
[public] interface I...able (这种命名表示带有某种能力)
{
成员;
}
class Program
{
static void Main(string[] args)
{
//接口就是一个规范、能力
Person p = new Student();
p.CHLSS();//兑现P没有KouLan方法,因为他还是一个父类对象
Student stu = new Student();
stu.KouLan();
stu.CHLSS();
}
}
public class Person
{
public void CHLSS()
{
Console.WriteLine("我是人类,可以吃喝拉撒睡");
}
}
public class NBAPlayer
{
public void KouLan()
{
Console.WriteLine("我是NBA球员,可以扣篮");
}
}
public class Student : Person,IKouLanable
{
public void KouLan()
{
Console.WriteLine("我也可以扣篮");
}
}
public interface IKouLanable//interface表示接口,由于继承的单根性,可以使用接口来获取特性
{//接口与类在同等级,当需要多继承时可以考虑使用接口
void KouLan();//接口中的陈冠不允许添加访问修饰符,默认就是public,但是类当中如果不添加默认就是private
//接口中的成员不允许写方法体,也就是不能有任何实现,只是定义了了一组未实现的成员,具体实现还需要在调用接口的类中补全方法体
// 接口中也不能添加字段,但是可以添加自动属性,如下:
string Name//我们并没有写字段,但是在编译的时候会自动为其添加字段
{
get;
set;
}
}
接口的特点
接口是一种规范,只要一个类继承了一个接口,这个类就必须实现这个接口中的所有的成员
为了多态,接口不能被实例化,也就是说,就扣不能new(不能创建对象)
接口中只能有方法,属性,索引器,事件,不能有字段和构造函数
接口与接口之间可以继承,并且可以多继承
接口不能去继承一个类,而类可以继承接口(接口只能继承于接口,而类既可以集继承接口也可以继承类)
实现接口的子类必须实现该接口的全部成员
一个类可以同时继承一个类并实现多个接口,如果一个子类同时继承了父类A,并且实现了接口IA,那么语法上A必须写在IA的前面
class MyClass:A,IA{},因为类是单继承的
显示实现接口的目的,解决方法的重名问题
什么时候显示的去实现接口:当继承的接口中的方法和参数一模一样的时候,要是用显示的实现接口
当一个抽象类实现接口的时候需要子类去实现接口
class Program
{
static void Main(string[] args)
{
IFlyable fly = new Person();
fly.Fly();
Person p = new Person();
p.Fly();//Person类自己的Fly()方法
IFlyable fly2 = new Bird();//通过接口创建的对象,调用接口方法,通过类创建的对象,不管类方法与接口方法是否重名都只会调用自己的类方法,因为不是接口创建的对象
fly2.Fly();//也就是接口方法只针对于通过接口创建的对象来说的
Bird bird = new Bird();
bird.Fly();//这里是鸟类自己的Fly方法,类自己的类方法不能通过接口调用,如果使用接口必须是接口创建的对象,否则就是原来的子类
IFlyable stu = new Student();
stu.Fly();//这是接口的Fly方法
}
}
public class Person : IFlyable
{
public void Fly()
{
Console.WriteLine("人类在飞");
}
}
public interface IFlyable
{
//不允许有访问修饰符,默认为public,不能修改
// 方法,自动属性
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("我是鸟类,这是我自己的Fly方法,我在飞");
}
//这里发生了方法重名的问题,我们可以显示的实现接口
void IFlyable.Fly()//这里其实是private修饰的,省略了,但是这个只能在子类中这样写
{
Console.WriteLine("我是接口的Fly方法");
}
}
public class Student : Person, IFlyable
{
public void Fly()
{
Console.WriteLine("我是学生类,我也可以飞");
}
}
public class Car : SupperInterface
{
public void Test1()
{
throw new NotImplementedException();
}
public void Test2()
{
throw new NotImplementedException();
}
public void Test3()
{
throw new NotImplementedException();
}
}
public interface M1
{
void Test1();
}
public interface M2
{
void Test2();
}
public interface M3
{
void Test3();
}
public interface SupperInterface
{
}
接口小练习
1、鸭子问题:
class Program
{
static void Main(string[] args)
{
// 什么时候用虚方法来实现多态:需要实现和创建父类对象的时候,可以从对象中抽取出父类
// 什么时候使用抽象类来实现多态:不知道父类到底有什么方法,父类不需要实现方法可以从对象中抽取出父类
// 什么时候用接口来实现按多态:不可以从对象中抽取出父类,但是他们有一些共同的行为方法,常见对象具有会或者能这些关键字
// 例子:真的鸭子会游泳,木头鸭子不会游泳,橡皮鸭子会游泳
IYouAble duck1 = new MuDuck();
duck1.YouY();
IYouAble duck2 = new RealDuck();
duck2.YouY();
IYouAble duck3 = new XiDuck();
duck3.YouY();
}
}
public class RealDuck:IYouAble
{
public void YouY()
{
Console.WriteLine("我是真鸭子,我会游泳");
}
}
public class MuDuck : IYouAble
{
public void YouY()
{
Console.WriteLine("我是木头鸭子,我不会游泳");
}
}
public class XiDuck : IYouAble
{
public void YouY()
{
Console.WriteLine("我是橡皮鸭子,我会游泳");
}
}
public interface IYouAble
{
void YouY();
}
运算符重载
就是利用operator关键字来自定义运输符的运算规则
| 运算符 | 描述 |
|---|---|
| +, -, !, ~, ++, -- | 这些一元运算符只有一个操作数,且可以被重载。 |
| +, -, *, /, % | 这些二元运算符带有两个操作数,且可以被重载。 |
| ==, !=, <, >, <=, >= | 这些比较运算符可以被重载。 |
| &&, | |
| +=, -=, *=, /=, %= | 这些赋值运算符不能被重载。 |
| =, ., ?:, ->, new, is, sizeof, typeof | 这些运算符不能被重载。 |
ArrayList
- 动态的增加和减少元素
- 实现了ICollection和IList接口
- 灵活的设置数组的大小
ArrayList集合的长度问题
每次集合中实际包含的元素个数(count)超过了可以包含的元素的个数(capcity)的时候,
集合就会向内存中申请多开辟一倍的空间,来保证集合的长度一直够用。
using System.Drawing;
using System.Threading.Tasks;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System;
using System.Collections;
using System.Runtime.CompilerServices;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
ArrayList list = new ArrayList(); //创建一个集合对象
//count 表示这个集合中实际包含的元素的个数
//capcity 表示这个集合中可以包含的元素的个数,如果ArrayList集合对象中没有元素,默认这两个值都是0
list.Add(1);
Console.WriteLine(list.Capacity);
Console.WriteLine(list.Count);
list.Add(5);
list.Add(5);
list.Add(5);
list.Add(5);
// list.Add(5);
// list.Add(5);
//ArrayList中有两种往集合中添加元素的方法:Add() AddRange(),Add()是添加单个元素,AddRange()是添加多个元素,表示添加某一范围
Console.WriteLine(list.Capacity);
Console.WriteLine(list.Count);//每count超过2的n次方,则capacity就是2的n+1次方,
//但是起步capacity=4,也即当n=0...4时,capacity=4,当n>2时capacity=2^(n+1)时
}
}
public class Person
{
public void SayHello()
{
Console.WriteLine("我是人类");
}
}
}
ArrayList重要的方法和属性
构造器
ArrayList提供了三个构造器:
- public ArrayList(); 默认的构造器,将会以默认(16)的大小来初始化内部的数组
- public ArrayList(ICollection); 用一个ICollection对象来构造,并将该集合的元素添加到ArrayList
- public ArrayList(int); 用指定的大小来初始化内部的数组
IsSynchronized属性和ArrayList.Synchronized方法
IsSynchronized属性指示当前的ArrayList实例是否支持线程同步,而ArrayList.Synchronized静态方法则会返回一个ArrayList的线程同步的封装。
如果使用非线程同步的实例,那么在多线程访问的时候,需要自己手动调用lock来保持线程同步,
例如:
ArrayList list = new ArrayList();
//...
lock( list.SyncRoot ) //当ArrayList为非线程包装的时候,SyncRoot属性其实就是它自己,但是为了满足ICollection的SyncRoot定义,这里还是使用SyncRoot来保持源代码的规范性
{
list.Add( “Add a Item” );
}
如果使用ArrayList.Synchronized方法返回的实例,那么就不用考虑线程同步的问题,这个实例本身就是线程安全的,实际上ArrayList内部实现了一个保证线程同步的内部类,ArrayList.Synchronized返回的就是这个类的实例,它里面的每个属性都是用了lock关键字来保证线程同步。
但是,使用这个方法(ArrayList.Synchronized)并不能保证枚举的同步,例如,一个线程正在删除或添加集合项,而另一个线程同时进行枚举,这时枚举将会抛出异常。所以,在枚举的时候,你必须明确使用 SyncRoot 锁定这个集合。
Hashtable与ArrayList关于线程安全性的使用方法类似。
Count属性和Capacity属性
Count属性是目前ArrayList包含的元素的数量,这个属性是只读的。
Capacity属性是目前ArrayList能够包含的最大数量,可以手动的设置这个属性,但是当设置为小于Count值的时候会引发一个异常。
Add、AddRange、Remove、RemoveAt、RemoveRange、Insert、InsertRange
这几个方法比较类似
| 方法名 | 用途 |
|---|---|
| Add( ) | 用于添加一个元素到当前列表的末尾 |
| AddRange( , ) | 用于添加一批元素到当前列表的末尾 |
| Remove( ) | 用于删除一个元素,通过元素本身的引用来删除 |
| RemoveAt( ) | 用于删除一个元素,通过索引值来删除 |
| RemoveRange( , ) | 用于删除一批元素,通过指定开始的索引和删除的数量来删除 |
| Insert( ) | 用于添加一个元素到指定位置,列表后面的元素依次往后移动 |
| InsertRange( , ) | 用于从指定位置开始添加一批元素,列表后面的元素依次往后移动 |
| Clear | 用于清除现有所有的元素 |
| Contains | 用来查找某个对象在不在列表之中 |
| TrimSize | 用于将ArrayList固定到实际元素的大小,当动态数组元素确定不在添加的时候,可以调用这个方法来释放空余的内存 |
| ToArray | 把ArrayList的元素Copy到一个新的数组中 |
ArrayList与数组转换
-
两种从ArrayList转换到数组的方法
-
例1:
ArrayList List = new ArrayList();
List.Add(1);
List.Add(2);
List.Add(3);
Int32[] values = (Int32[])List.ToArray(typeof(Int32)); -
例2:
ArrayList List = new ArrayList();
List.Add(1);
List.Add(2);
List.Add(3);
Int32[] values = new Int32[List.Count];
List.CopyTo(values);
-
-
例3:
ArrayList List = new ArrayList();
List.Add( “string” );
List.Add( 1 );
//往数组中添加不同类型的元素
object[] values = List.ToArray(typeof(object)); //正确
string[] values = (string[])List.ToArray(typeof(string)); //错误
和数组不一样,因为可以转换为Object数组,所以往ArrayList里面添加不同类型的元素是不会出错的,但是当调用ArrayList方法的时候,要么传递所有元素都可以正确转型的类型或者Object类型,否则将会抛出无法转型的异常。
ArrayList最佳使用建议
ArrayList是Array的复杂版本
ArrayList内部封装了一个Object类型的数组,从一般的意义来说,它和数组没有本质的差别,甚至于ArrayList的许多方法,如Index、IndexOf、Contains、Sort等都是在内部数组的基础上直接调用Array的对应方法。
内部的Object类型的影响
对于一般的引用类型来说,这部分的影响不是很大,但是对于值类型来说,往ArrayList里面添加和修改元素,都会引起装箱和拆箱的操作,频繁的操作可能会影响一部分效率。
但是恰恰对于大多数人,多数的应用都是使用值类型的数组。
消除这个影响是没有办法的,除非你不用它,否则就要承担一部分的效率损失,不过这部分的损失不会很大。
数组扩容
这是对ArrayList效率影响比较大的一个因素。
每当执行Add、AddRange、Insert、InsertRange等添加元素的方法,都会检查内部数组的容量是否不够了
如果是,它就会以当前容量的两倍来重新构建一个数组,将旧元素Copy到新数组中,然后丢弃旧数组,在这个临界点的扩容操作,应该来说是比较影响效率的。
-
例1:比如,一个可能有200个元素的数据动态添加到一个以默认16个元素大小创建的ArrayList中,将会经过:
四次的扩容才会满足最终的要求,如果一开始就以:
ArrayList List = new ArrayList( 210 );
的方式创建ArrayList,不仅会减少4次数组创建和Copy的操作,还会减少内存使用。 -
例2:预计有30个元素而创建了一个ArrayList:
ArrayList List = new ArrayList(30);
在执行过程中,加入了31个元素,那么数组会扩充到60个元素的大小,而这时候不会有新的元素再增加进来,而且有没有调用TrimSize方法,那么就有1次扩容的操作,并且浪费了29个元素大小的空间。可以用:
ArrayList List = new ArrayList(40);所以说,正确的预估可能的元素,并且在适当的时候调用TrimSize方法是提高ArrayList使用效率的重要途径。
频繁的调用IndexOf、Contains等方法(Sort、BinarySearch等方法经过优化,不在此列)引起的效率损失
首先,我们要明确一点,ArrayList是动态数组,它不包括通过Key或者Value快速访问的算法,所以实际上调用IndexOf、Contains等方法是执行的简单的循环来查找元素,
所以频繁的调用此类方法并不比你自己写循环并且稍作优化来的快,如果有这方面的要求,建议使用Hashtable或SortedList等键值对的集合。
ArrayList al=new ArrayList();
al.Add("How");
al.Add("are");
al.Add("you!");
al.Add(100);
al.Add(200);
al.Add(300);
al.Add(1.2);
al.Add(22.8);
.........
//第一种遍历 ArrayList 对象的方法
foreach(object o in al)
{
Console.Write(o.ToString()+" ");
}
//第二种遍历 ArrayList 对象的方法
IEnumerator ie=al.GetEnumerator();
while(ie.MoveNext())
{
Console.Write(ie.Curret.ToString()+" ");
}
//第三种遍历 ArrayList 对象的方法
我忘记了,好象是 利用 ArrayList对象的一个属性,它返回一此对象中的元素个数.
然后在利用索引
for(int i=0;i<Count;i++)
{
Console.Write(al[i].ToString()+" ");
}
HashTable
HashTable 键值对集合 类似于我们学习过的字典
using System;
using System.Collections;
using System.Runtime.CompilerServices;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Hashtable ht = new Hashtable();//创建一个键值对集合
ht.Add(1,"张三");
ht.Add(3,true);
ht.Add(2,'男');
ht.Add(false,"出错error");
ht[6] = "新来的";//这也是一种添加数据的方式
for (int i = 0; i < ht.Count; i++)//在键值对集合中,是根据键去找值的
{
Console.WriteLine(ht[i]);
}
foreach (var item in ht.Keys)
{
Console.WriteLine("键是{0}===值是{1}",item,ht[item]);
}
}
}
}
Path(是一个静态类)
using System.IO;
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string str = @"D:\Desktop\static\host.txt";
int index = str.LastIndexOf("\\");
str = str.Substring(index + 1);
Console.WriteLine(str);
Path.GetFileName(str);//通过文件的路径获取文件名
Path.GetFileNameWithoutExtension(str);//获取文件的路径,没有扩展名
Path.GetExtension(str);//获取文件扩展名
Path.GetDirectoryName(str);//获取文件所在文件夹的名称
Path.Combine(@"c:\a", "b.txt");//连接两个字符串作为路径
}
}
}
File类
string path = @"c:\hello.txt";
File.Create(@"D:\Desktop\file类.txt");//创建文件
File.Delete(@"D:\Desktop\file类.txt");//删除文件
File.Copy(@"D:\hello1.txt",@"c:\hello2.txt");
字节数组--->字符串
byte[] buffer= File.ReadAllBytes(path);
string s = Encoding.Default.GetString(buffer);
字符串--->字节数组
string[] str File.WriteAllBytes(buffer);
Encoding.Default.GetBytes(str);
string s2 = "今天天气晴";//需要将字符串转换成字节数组
byte buffer2 = Encodeing.Default.GetBytes(s2);
File.WriteAllBytes(path);//如果路径文件存在则修改覆盖,否则创建该文件
string[] contents = File.ReadAllLine(path,Encoding.Default);//以行的形式读取文件,返回一个字符串数组,下面通过foreach遍历
foreach(string item in contents)
{
Console.WriteLine(item);
}
string s3 = File.ReadAllText(path,Encoding.Default);
Console.WriteLine(str);
File.WriteAllLine(path,new string[]{"aoe","ewu"});
File.WriteAllText(path,"张三李四王五赵六");
File.AppendAllText(path,"需要追加的文件内容");
//打开一个文件,向其中追加指定的字符串,然后关闭该文件,如果文件不存在,此方法创建一个文件,将指定的字符串写入文件,然后关闭该文件
FileStream、StreamReader、StreamWriter
FileStream用来操作字节的,也就是所有类型的文件都可以用这个类型来操作
StreamReader和StreamWriter是用来操作字符的
使用FileStream流来读写文件
string trace = @"D:\Desktop\static\host.txt";
FileStream fsRead = new FileStream(trace, FileMode.OpenOrCreate, FileAccess.ReadWrite);
byte[] buffer = new byte[1024*1024*5];
//返回本次实际读取到的有效字节数
int r = fsRead.Read(buffer, 0, buffer.Length);
//将字节数组中的每一个元素按照指定的编码格式解码成字符串
string s = Encoding.Default.GetString(buffer,0,r);
// 关闭流
fsRead.Close();
// 释放流所占用的资源
fsRead.Dispose();
//使用FileStream来写入数据,这里使用using是因为会帮助我们省去释放流所占用的资源这个步骤
using (FileStream fsWrite = new FileStream(trace, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
string str = "用来覆盖原来的内容的语句";
byte[] buffer2 = Encoding.Default.GetBytes(str);
fsWrite.Write(buffer2, 0, buffer2.Length);
}
使用FileStream流来实现多媒体文件的复制
using System.IO;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
//思路:就是先将要赋值的多媒体文件读取出来,然后写入到你指定的位置
string source = @"D:\Picture\SavedPicture\1.png";
string target = @"D:\Picture\2.png";
CopyFile(source,target);
}
public static void CopyFile(string source, string target)
{
//1、创建一个负责读取的流
using (FileStream fsRead = new FileStream(source, FileMode.Open, FileAccess.Read))
{
// 创建一个负责写入的流
using (FileStream fsWrite = new FileStream(target, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
//因为文佳比较大,所以我们在读取的时候,应该通过循环来读取
while (true)
{
// 返回本次读取实际读取到的字节数
int r = fsRead.Read(buffer, 0, buffer.Length);
// 如果返回一个0,也就意味着什么都没有读取到,也即读取完了
if (r == 0)
{
break;
}
fsWrite.Write(buffer,0,r);
}
}
}
}
}
}
StreamReader 、StreamWriter
static void Main(string[] args)
{
//使用StreamReader来读取一个文本文件
using (StreamReader sr = new StreamReader(@"D:\Desktop\hello.txt",Encoding.Default))
{
while (!sr.EndOfStream)
{
Console.WriteLine(sr.ReadLine());
}
}
//使用StreamWriter来写入一个文本文件
using (StreamWriter sw = new StreamWriter(@"D:\Desktop\world.txt",true))
{
sw.Write("Welcome To China!");
}
}
List<>泛型集合
// 创建泛型集合
List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
list.AddRange(new int[]{1,2,3,4,5,6});
list.AddRange(list);
// List泛型集合可以转换为数组,使用ToArray()方法,可以转换成为什么类型的数组取决于创建时是什么类型的泛型集合
int[] nums = list.ToArray();
List<int> listTwo = nums.ToList();
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
泛型集合的练习
1、将一个数组中的奇数放到一个集合中,再将偶数放到另一个集合中,最终将两个集合合并为一个集合,并且奇数显示在左边,偶数显示在右边
static void Main(string[] args)
{
int[] nums ={1, 2, 3, 4, 5, 6, 7, 8, 9,10};
List<int> listOu = new List<int>();
List<int> listji = new List<int>();
foreach (int i in nums)
{
if (i%2==0)
{
listOu.Add(i);
}
else
{
listji.Add(i);
}
}
BianLi(listji);
BianLi(listOu);
List<int> listHe = new List<int>();
listHe.AddRange(listji);
listHe.AddRange(listOu);
listji.AddRange(listOu);
BianLi(listHe);
BianLi(listji);
}
public static void BianLi(List<int> list)
{
foreach (int j in list)
{
Console.Write(j+"\t");
}
Console.WriteLine();
}
2、用户输入一个字符串,通过foreach循环将用户输入的字符串赋值给一个字符数组
string str = Console.ReadLine();
char[] chars = new char[str.Length];
int i = 0;
foreach (char item in str)
{
chars[i] = item;
}
3、统计 Welcome to china 中每个字符出现的次数,不考虑大小写
static void Main(string[] args)
{
string str = "Welcome Come To China";
//字符---->出现的次数
//键---->值
Dictionary<char, int> dic = new Dictionary<char, int>();
for (int i = 0; i < str.Length; i++)
{
if (str[i] == ' ')
{
continue;
}
if (dic.ContainsKey(str[i]))//如果dic已经包含了当前循环到的这个键
{
dic[str[i]]++;//值就加一,当没有为值赋值时,值默认都是0
continue;
}
// else//这个字符在集合当中是第一次出现
// {
dic[str[i]] = 1;//在这里为字典集合赋值了,键是str[i],值是1
// }
}
foreach (var kv in dic)
{
Console.WriteLine("字母\'{0}\'出现了{1}",kv.Key,kv.Value);
}
}
装箱和拆箱
// 装箱和拆箱,在代码中尽量避免
// 装箱:就是将值类型转换为引用类型
// 拆箱:将引用类型转换为值类型
int n = 10;
object o = n;//装箱
int nn = (int) o;//拆箱
// 但是我们需要注意:下面的过程没有发生任何类型装箱和拆箱
string str = "123456";
int n2 = Convert.ToInt32(str);
int n3 = Int32.Parse("232");
// 于是,判断两种类型是否发生了装箱或者拆箱要看这两种类型之间是否发生了继承关系
int n4 = 10;
IComparable i = n4;//装箱
字典集合
Dictionary<int, string> dic = new Dictionary<int, string>();
dic.Add(1,"zhangsan");
dic.Add(2,"lisi");
dic.Add(3,"wangwu");
foreach (var item in dic.Keys)
{
Console.WriteLine("{0}---{1}",item,dic[item]);
}
foreach (KeyValuePair<int,string> kv in dic)//以后多采用这种方式
{
Console.WriteLine("{0}---{1}",kv.Key,kv.Value);
}

浙公网安备 33010602011771号