C#基础总结
C#
简介
C#是微软公司发布的一种由C和C++衍生出来的面向对象的编程语言,它不仅去掉了 C++ 和 Java 语言中的一些复杂特性,还提供了可视化工具,能够高效地编写程序。
C#是由C和C++衍生出来的一种安全的、稳定的、简单的、优雅的面向对象编程语言。它在继承C和C++强大功能的同时去掉了一些它们的复杂特性(例如没有宏以及不允许多重继承)。
C#使得C++程序员可以高效的开发程序,且因可调用由 C/C++ 编写的本机原生函数,而绝不损失C/C++原有的强大的功能。因为这种继承关系,C#与C/C++具有极大的相似性,熟悉类似语言的开发者可以很快的转向C#。
C#关键字
C#初次体验
使用Visual Studio创建第一个项目 打印Hello world!
1.1 创建项目
打开VS->新键项目->找到控制台程序(.NET Framework)->下一步->创建
eg:
1.2 打印Hello world!
如何编译当前程序?
- 1.C#程序–人能看懂,机器看不懂
- 2.执行程序的确是机器
- 3.需要将C#程序编译(翻译)成机器能够读懂的语言(二进制)
- 4.这样程序就可以被机器执行了
- 5.Windows:生成->生成解决方案 Ctrl + Shift + B
如何运行当前程序?
1.Windows:运行而不调试(Ctrl + F5/F5)
eg:
注释
- 1.注释是不会被编译的,更不会被执行
- 2.注释的作用:
- 3.解释说明当前的代码是什么含义
3.1、强调
- 1.在目前学习代码阶段,保证你写的每行代码都要配一行注释
- 2.解释说明,你这句代码是什么含义
3.1、暂时取消某些代码的执行
快捷键:
注释当前选中行的代码:Ctrl + K + C
取消注释当前选中行的代码:Ctrl + K + U
MSDN
MSDN地址
https://docs.microsoft.com/zh-cn/dotnet/api/system.reflection.fieldinfo?view=netframework-4.7.2
数据类型
数据的量级
- 1024字节(byte)=1KB
- 1024KB = 1MB
- 1024MB = 1GB
- 1024GB = 1TB
- 1024TB = 1PB
数据类型
bool 0000 0000 false 0000 0001 true
sbyte 有符号的8位整数 000 0000 — 111 1111 (0-127)
- 1.第一位表示符号(+ -)
- 2.特殊0:1000 0000 和 0000 0000
- 3.硬性规定:1000 0000 表示-128
- 4.取值范围:-128—+127
byte无符号的8位整数
- 1.0000 0000 —— 1111 1111
- 2.取值范围 0 ——255
short有符号的16位整数(文档中是Int16)
- 1.000 0000 0000 0000 —— 111 1111 1111 1111
- 2.负32768到正32767
int有符号的32位整数(文档中是Int32)
最常用的整数类型
一般说整型即int类型
无特殊情况,存储一个整数都用int
int占4个字节(面试经常问)
long有符号的64位整数(文档中是Int64)
浮点数(通常理解就是生活中的小数)
float(单精度浮点数)【文档中查:Single】
一般情况下,float足够
double(双精度浮点数)【文档中查:Double】
如果需要精度更高一点,用Double
decimal(高精度浮点数)
极少用到,天文数字可能会用到这个
常量、变量
程序运行期间
程序开始到程序结束
变量:在程序运行期间,可以被改变
变量的声明
数据类型 变量名 = 初值;
int a = 10;
变量可以不赋初值(在特殊情况下必须要赋初值)
不赋初值时,当前变量的值是默认值
int/float 默认值是0
char 默认值’\0’(表示空字符)
常量:在程序运行期间,不能被改变
常量的声明
const 数据类型 变量名 = 初值;
大写(潜规则)
const float money = 100.35f;
常数必须要赋初值
浮点型声明时注意:
float flo = 1.11f;【float数字后面要加f】
double damage = 1.11d【double后面要加d】
decimal damage = 1.223m【decimal后面要加m】
字符型声明注意:
字符类型(一定要用单引号括起来)
char cha = ‘name’;
常量及变量的命名规则
只能由字母、数字、@和下划线(_)这些组成
数字不能开头,⌨ 1a❌、3f❌、xiaoming1✅
@符号,要么不用,要用必须放在首位,🌰@zhansan✅,zhang@san❌
不能与系统的关键词同名,⌨ int,long,sbyte❌
变量名不能重复
中文变量名语法上是可以的,但极为不推荐
常量及变量的命名规范
全是英文单词,不要用拼音
驼峰命名法
大驼峰(每个单词的首字母大写,其余字母小写)
MyHeroDamage、HeroAttack
小驼峰:(第一个单词首字母不大写,后面每个单词的首字母大写,其余字母小写)
myHeroDamage、heroAttack【目前阶段都用小驼峰】
见名知意
yyds(你晓得这是啥?评论区见)❌
运算符
数据的量级
赋值运算符 “=”,是一个运算,将后面的结果赋给前面的变量或常量
前面 = 后面;后面的值赋给前面
前面必须是个变量【不能是具体是数值(2,3,’A‘,“123”)】
后面可以是具体的数值,也可以是变量,也可以是常量
算术运算符
+、- 加减法
、/ 乘除法
a / b 运算之后得到一个结果
被除数 ÷ 除数 = 商
除法有一个禁忌:除数不能为0
% 取余(做除法取余数)
5 % 3 : 5除以3,得到的余数,是结果
上面的+、-、、/、%都是二元运算符
++运算符和–运算符
举例:a++; 等价于 a = a+1;
++、- -是一元运算符
//int showYouAge = age++;//结果是18
//意味着 age++ 得到的结果是 age
//解析:
//第一步:将age的值赋给showYouAge
//第二步:age自增
int showYouAge = ++age;
//意味着 ++age 得到的结果是 age+1
//解析:
//第一步:age自增
//第二步:将age的值赋给showYouAge
//总结:
//age++;++age;
//++符号在前就先自增,后赋值
//++符号在后就先赋值,后自增
练习题:
符合运算符
a+=b;等价于a=a+b;
a=a+4; ==> a+=4;
a-=b;等价于a=a-b;
a=b;等价于a=ab;
a/=b;等价于a=a/b;
a%=b;等价于a=a%b;
输入与输出
输出
Console.WriteLine();
输出内容,并换行
Console.Write();
输出内容,不换行
输入
Console.Read();
从屏幕读取一个字符,并返回该字符所对应的整型数字
Console.ReadLine();
从屏幕读取一串字符,并返回该字符串
字符串是一个数据类型
关键词string
表示一串字符
用双引号括起来
字符串相加可以得到两个字符串组合到一起的字符串
预编译执行 region
作用:代码分成一个区域,方便折叠和展开
区域首部:#region
区域尾部:#endregion
eg:
类型转换
隐式转换
将占用字节小的、取值范围小的、精度小的,转换为占用字节大的、取值范围大的、精度高
不需要任何的修饰符,会自动转换
//整型
//1 2 4 8
//sbyte short int long
sbyte englishScore = 100;
//sbyte --> int
int myScore = englishScore;
//int --> long
long classScore = myScore;
//int --> float
float newScore = myScore;
//float --> double
double newClassScore = newScore;
显式转换(强制转换)
将占用字节大的、取值范围大的、精度高,转换为占用字节小的、取值范围小的、精度小的
需要强制转修饰符,会有精度的缺失,甚至数据的错误
转换情况:知道这个数字,在小的数据类型的取值范围内
//强制转换
int damage = 10000;
//int --> sbyte
sbyte health = (sbyte)damage;
//输出结果
Console.WriteLine(health); //16
float mathScore = 90.5f;
//float --> int
int myAllScore = (int)mathScore;
//会把小数点后面的内容全部舍去
Console.WriteLine(myAllScore); //90
int和char之间的类型转换
//int 和 char之间的类型转换
int num = 11101;
//int --> char
char letter = (char)num;
//a-97
Console.WriteLine(letter);
评论区请教大神!!!
int和bool之间的类型转换
不能进行转换
string与其他类型之间的转换
举例:
“false”,“true”
“10”,“3.14”
“A”
转换方法
System.Convert
System.Convert.ToBoolean()
System.Convert.ToInt32()
System.Convert.ToSingle()
System.Convert.ToDouble()
System.Convert.ToChar()
//数据类型.Parse()
int.Parse()
bool.Parse()
float.Parse()
char.Parse()
其他类型能不能转换成字符串
其他类型的变量.ToString();
关系运算符 & 逻辑运算符
关系运算符:>,<,>=,<=,==,!=
逻辑运算符:逻辑运算是bool与bool之间的运算
&:与运算
true & true : true
true & false : false
false & false : false
总结:一假则假
|:或运算
true | true : true
true | false : true
false | false : false
总结:一真则真
!:非运算
! true : false
! false : true
&&:短路与运算
普通的&与运算,无论第一个条件是真是假,都会继续判断第二条件
短路与运算&&,如果判断第一个条件已经是假,则不会继续判断第二个条件
||:短路或运算
普通的|与运算,无论第一个条件是真是假,都会继续判断第二条件
短路或运算||,如果判断第一个条件已经是真,则不会继续判断第二个条件
短路&&、||
优点:第一个条件已经得知整个逻辑运算的结果,就不会去判断第二个条件了
节约了运算量
缺点:如果判断中带有运算,如果不进行第二个条件的判断,那第二个条件中的运算也不能执行
练习:
手动计算下列表达式的值和number的值,并编写程序来验证结果是否正确
- ①number初值为6:number++ * ++number - number-- / --number
- ②number初值为5:number++ > 3 | --number == 0
- ③number初值为2:number-- > 2 && --number == 0
- ④number初值为12:number++ > 10 && --number != 2 && number-- == 0 && ++number != 3
- ⑤number初值为0:number++ > -1 || --number <= 2 && number-- != 10
- ⑥number初值为0:number++ < -1 || (–number >= 2 && number-- != 10)【小括号优先级最高】
分支语句
条件运算符
条件运算符(三元运算符)
条件表达式 ? 结果a : 结果b
if的第一种形式
if(条件表达式){
语句1;
}
if的第二种形式
if (条件表达式)
{
语句1;
}
else
{
语句2;
}
if的第三种形式
if (条件表达式1)
{
语句1;
}
else if (条件表达式2)
{
语句2;
}
else
{
语句3;
}
Switch
如果case 冒号后面没有任何语句,可以不加break;
Switch()括号中是可以允许添加浮点型变量的,但不推荐
浮点型是有误差的
浮点型一般不做等于的判断
企业面试题:有一个浮点数,判断该浮点数是不是等于5
⌨循环语句
while循环
while (条件表达式)
{
//循环内容
}
break关键词
跳出本层循环(通常与if连用)
continue关键词
结束本次循环(continue后面的代码不再执行),进入下次循环。(通常与if连用)
练习:👍👍👍👍👍👍
从键盘输入一个算数运算式,使用if语句实现正确的算数运算,并输出运算结果
例如:
请输入第一个数字:4
请输入运算符:+
请输入第一个数字:2
计算结果为:6
输入一个生日日期,输出其星座.
白羊座:3.21-4.19,金牛座:4.20-5.20,双子座:5.21-6.21,巨蟹座:6.22-7.22
狮子座:7.23-8.22,处女座:8.23-9.22,天秤座:9.23-10.23,天蝎座:10.24-11.22
射手座:11.23-12.21,魔羯座:12.22-1.19,水瓶座:1.20-2.18,双鱼座:2.19-3.20
例如:
请输入月:12
请输入日:21
您是射手座
数组
一维数组的初始化
动态初始化
数据类型[] 数组名=new 数据类型[数组长度];
此时数组中每一个元素都是为默认值
int的默认值0
float的默认值0
bool的默认值false
char的默认值‘\0’,表示空字符
string的默认值""
数据类型[] 数组名=new 数据类型[数组长度]{元素1,元素2…};
数据类型[] 数组名=new 数据类型[]{元素1,元素2…};
静态初始化
数据类型[] 数组名={元素1,元素2…};
数组的访问
从0开始计数
array.Length是数组的长度,只读的。
访问数组时,切记下标所对应的数组元素是存在的
如果不存在,就会引发数组越界异常
引用数据类型
数据类型
值类型(存储在栈内存)
栈内存里存的是具体的值
前面学习过的值类型
int、float、bool、char
引用类型(存储在堆内存)
栈内存里存的是地址(堆内存的地址)
目前学习过的引用类型
string(特例)、数组
遍历数组
遍历数组:访问数组中的每一个元素
int[] array = { 1,8,4,8,7,45,456,789,78,76};
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(i + ":" + array[i]);
}
二维数组、结构体、枚举
冒泡排序
思想:
当前数组元素与后面的数字元素进行对比,如果前大后小,则进行交换
每轮可以确定一个最大值在数组的末位,几轮之后即可完成排序
冒泡排序当然也可以从大到小排序,那样则前小后大进行交互
代码:
for (int i = 0; i < array.Length; i++)
{
//立个flag,表示判断过程中是否发生了交换
bool IsjiaoHuan= false;
for (int j = 0; j < array.Length - i - 1; j++)
{
if (array[j] > array[j + 1])
{
//说明交换了
IsjiaoHuan= true;
//交换
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
if(!IsjiaoHuan)
{
//说明已经排好序了,后面的轮次就没有必要了
break;
}
for (int m = 0; m < array.Length; m++)
{
Console.Write(array[m] + "\t");
}
Console.WriteLine();
}
Console.WriteLine("排序结束");
for (int m = 0; m < array.Length; m++)
{
Console.Write(array[m] + "\t");
}
二维数组的定义
数据类型[,] 数组名;
int[,] array;
二维数组的初始化
动态初始化
数据类型[,] 数组名 = new 数据类型[第一维长度,第二维长度];
int[,] arr= new int[50,3];
数据类型[,] 数组名 = new 数据类型[,]{数组值};
int[,] arr= new int[,]{{1,0,1},{3,0,5}};
静态初始化
数据类型[,] 数组名 = {数组值};
int[,] arr= {{1,0,1},{3,0,5}};
二维数组的元素访问
数组名[第一维下标,第二维下标]
arr[3,2]
谨防数组越界
二维数组的长度
总长度(二维数组的元素个数)
array.Length
第一维的长度
array.GetLength(0)
第二维的长度
array.GetLength(1)
二维数组的遍历
for (int i = 0; i < heroData.GetLength(0); i++)
{
for (int j = 0; j < heroData.GetLength(1); j++)
{
Console.Write(heroData[i,j] + "\t");
}
//换行
Console.WriteLine();
}
foreach迭代遍历
迭代遍历是只读的,不能修改
//foreach性能消耗要大一点,所以能用for的尽量用for
foreach (var item in number)
{
Console.WriteLine(item);
//迭代遍历是只读的,不能写入
//item = 1;
}
枚举类型
枚举类型是一个自定义类型
枚举类型是一个值类型
枚举类型的创建
//装备类型
enum EquipType
{
Helmet = 100,//头盔
BodyArmor = 200,//防弹衣
Knapsack,//背包
Knife
}
枚举类型变量的定义与使用
//定义一个枚举类型的变量
EquipType myEquip = EquipType.Knapsack;
EquipType yourEquip = EquipType.Knapsack;
//判断枚举变量
if(myEquip == EquipType.BodyArmor) { }
switch (myEquip)
{
case EquipType.BodyArmor:
break;
case EquipType.Helmet:
break;
case EquipType.Knife:
break;
//case EquipType.
default:
break;
}
//枚举类型和整型之间的转换
//枚举类型可以强制转换为整型
int num = (int)myEquip;
Console.WriteLine(num);
//整型可以强制转换为枚举类型
myEquip = (EquipType)200;
Console.WriteLine(myEquip);
//既然枚举可以用整数去表示
Console.WriteLine(myEquip+2);
结构体类型
结构体类型是自定义类型
结构体类型是值类型
结构体类型的创建
//学生类型
struct Student
{
public string name;
public char sex;
public int age;
}
结构体类型变量声明及字段赋值
//定义一个学生变量
Student xiaoming;
//学生结构内变量赋值
xiaoming.name = "xiaoming";
xiaoming.age = 16;
xiaoming.sex = 'M';
结构体的构造函数
结构体默认的构造函数,开发者不能创建默认构造(即无参构造)
public Student()
{
}
结构体的自定义构造函数,方便创建结构体变量时给字段赋值
//自定义构造函数
public Student(string n,char s,int a)
{
//作用:快速给结构体字段赋初值
//而且必须给每一个字段都赋初值
name = n;
sex = s;
age = a;
}
初始化结构体变量
//有了自定义的构造函数后,如何新建结构体变量
Student xiaogang = new Student("xiaogang",'M',18);
结构体练习题
eg:
#region 结构体、枚举
//创建英雄装备结构体,包含名称,攻击力加成,法术强度加成,血量加成,装备类型
enum EquipType
{
AD,
AP,
HP,
Other
}
//英雄装备
struct HeroEquip
{
public string name;
public float adAddition;
public float apAddition;
public float hpAddition;
public EquipType equipType;
public HeroEquip(string name, float adBuff, float apBuff, float hpBuff, EquipType equipType)
{
//给所有字段赋初值
this.name = name;
adAddition = adBuff;
apAddition = apBuff;
hpAddition = hpBuff;
this.equipType = equipType;
}
}
#endregion
#region 结构体、枚举练习
//有5个装备保存在结构体数组当中,编程找出血量加成最高者
//对装备数组按照攻击力加成排序并使装备按照攻击力加成升序进行信息打印
HeroEquip wjzr = new HeroEquip(
"无尽之刃", 100, 0, 50, EquipType.AD);
HeroEquip yxj = new HeroEquip(
"饮血剑", 80, 0, 20, EquipType.AD);
HeroEquip ktkj = new HeroEquip(
"狂徒铠甲", 0, 0, 150, EquipType.AD);
HeroEquip dmj = new HeroEquip(
"兰德里的折磨", 20, 100, 0, EquipType.AD);
//声明结构体数组存储这些装备
HeroEquip[] heroEquips = { wjzr, yxj, ktkj, dmj };
//设置初始血量最大值
float maxHPBuff = heroEquips[0].hpAddition;
//设置初始血量最大值的装备名称
string maxHPEquipName = heroEquips[0].name;
HeroEquip maxHPEquip = heroEquips[0];
//找血量最大
for (int i = 0; i<heroEquips.Length; i++)
{
if (maxHPBuff<heroEquips[i].hpAddition)
{
//更新最大值
maxHPBuff = heroEquips[i].hpAddition;
//更新最大值的装备名称
maxHPEquipName = heroEquips[i].name;
}
//如果声明结构体
if (maxHPEquip.hpAddition<heroEquips[i].hpAddition)
{
maxHPEquip = heroEquips[i];
}
}
Console.WriteLine("装备列表中,血量加成最高的装备是\n"
maxHPEquip.name + ",最大值是"
xHPEquip.hpAddition);
Console.Read();
#endregion
面向对象
面向过程
重点关心解决问题的步骤
优点:
可以很清晰的看明白问题解决的步骤
代码的行数要少一些,性能消耗低一些
缺点:
不易维护、不易拓展、不易复用
面向对象
重点关心解决问题过程中参与的对象有哪些,分别有哪些特性和行为
优点:
易维护、易拓展、易复用
缺点:
代码是分散的,行数会多一些
性能消耗要高一些
类和对象
创建一个类
[访问修饰符] Class 类名//大驼峰命名法
创建一个类的对象
类名 对象名;
类的类型是一个引用类的型
类的类型是一个自定义类型
一个对象在创建后,需要进行实例化(初始化)才能使用
类名 对象名 = new 类名();
原理 : 对象在进行new操作后,才分配了内存。
字段
描述类的特性
要使用小驼峰命名法命名
可以有初值
帮助注释
帮助注释也是一种注释
谁可以添加帮助注释
类
方法
字段
属性
帮助注释的优点
在使用类、方法、字段、属性的时候,可以在提示框中显示帮助注释的内容
this关键词
表示当前对象
如果没有重名冲突,可以不写(省去)
Random随机数类
第一步,先创建一个随机数对象
Random random = new Random();
第二步,调用随机数对象的Next方法
调用时要传入两个参数,即随机数字的取值范围
-
如传入的是4和8,则随机数字范围为4至7,不包含8(这个方法就是这么写的)
-
即取值范围为左闭右开,即[min,max)
-
方法返回的结果即随机到的数字,需要用整型变量去接收
int num = random.Next(0,array.Length);
1
方法
方法是描述一个类的某个特定行为
一个方法尽量就完成一件小的事情
如果要完成一件大事
先定义一些方法,完成这个大事拆分出来的小事
最后在按照流程,在大事方法中调用这些小事的方法
方法要用大驼峰命名法命名
方法的创建[访问修饰符] 返回值类型 方法名(参数列表) { //方法体 //实现方法功能 return 结果;//最终别忘了返回方法结果,结果类型需与返回值类型保持一致 }
方法的调用
对象名.方法名(参数列表);//无论有无参数,小括号都要存在
return关键词
返回结果
终止方法,即return后面的语句不会执行
属性、方法参数
属性
属性命名方式使用大驼峰
在属性访问器内写代码,切记一点
Get访问器内不要写读取该属性的语句
Set访问器内不要写写入该属性的语句
否则,会出现递归循环,死循环
属性简写:public string Name { get; set; } = “先生”;
引用参数ref
添加了ref关键词的参数
传递的就不是值了
而是地址
而如果没有赋初值,是没有地址的
所以ref参数一定是个变量
所以ref参数的实参一定是赋过初值
所以ref一般加在值类型参数的前面
使用应用参数,无论是形参还是实参前面都要加ref关键词
输出参数out
添加了out关键词的参数
参数就成了一个输出的通道
离开方法之前形参必须赋值
实参必须是一个变量
传递的实参一般是值类型
使用输出参数,无论是形参还是实参前面都要加out关键词
字符串
StringBuilder、重载、递归
StringBuilder
在System.Text命名空间下
所以使用时,需要先引入这个命名空间
using System.Text;
如果不引入,可以直接写:
System.Text.StringBuilder strB = new System.Text.StringBuilder();
使用时,先要new
StringBuilder stringB = new StringBuilder();
追加字符串
//追加字符串
stringBuilder.Append("天气好晴朗");
什么时候用StringBuilder而不用string
字符串需要经常修改的时候
方法的重载
同一个类里面,方法名一样,但参数不一样
参数的数据类型不一样
参数的个数不一样
参数的数据类型不一样,个数也不一样
参数个数和类型都一样,但返回值类型不一样,不能算做重载❌
class Funs
{
public string Fun(string a, string b)
{
return "a+b";
}
public int Fun(int a, int b)
{
return a + b;
}
}
Funs funs = new Funs();
int a = funs.Fun(1, 1);
string b = funs.Fun("2", "2");
string c = string.Format("{0},{1}", a, b);
Console.WriteLine(c);
Console.Read();
递归
方法自己调用自己
多个方法之间来回调用
使用递归时一定要有出口
使用递归时,一定概要慎重慎重再慎重…
构造函数
执行的时机
new对象()的时候调用
构造函数一般都是public
因为一旦构造函数被设置为了private,那么外界就无法new这个
构造函数没有返回值
也不能写返回值类型
构造函数的名字必须和类名保持完全的一致
构造函数是不能像普通方法一样被直接调用的
关于系统会不会自动创建一个空参空方法体的构造给你
如果一个类没有构造函数
那么系统会自动写一个空参数空方法体的构造函数
如果有构造函数
那么系统就不会帮你自动创建
构造函数可以有参、也可以无参
构造函数是可以重载的
如果想在执行当前构造函数之前,先执行其他的构造函数
当前构造函数(…) : this(传实参)
class Person
{
private int age;
private string name;
public Person(int age)
{
this.age = age;
Console.WriteLine(age);
}
public Person(string name) : this(18)
{
this.name = name;
Console.WriteLine(name);
}
public Person(int age, string name) : this("xian")
{
Console.WriteLine("我是性别,年龄!");
}
}
static void Main(string[] args)
{
Person body = new Person(128, "alll");
Console.Read();
}
多态
关于父类空参数构造函数的调用说明
首先,先要明确一点
子类在实例化的时候,必会调用父类的构造函数
子类在声明构造函数的时候,要想办法调用父类的构造
如果父类是空参数的构造函数 : base()
可以不写:base()
系统会默认调用父类的空参数构造
如果父类是有参数的构造函数,那么一定概要通过:base的方式调用,传参
关于VS自动生成类构造函数
右键类名
点击快速修复或重构
点击生成构造
public class Person
{
public string name;
public Person(string name)
{
this.name = name;
Console.WriteLine(name);
}
public void Say()
{
Console.WriteLine("你在干什么!");
}
}
public class Person1:Person
{
public Person1(string name):base(name)
{
}
public new void Say()
{
Console.WriteLine("弄啥呢!");
}
}
public class Person2:Person1
{
public Person2(string name) : base(name)
{
}
public new void Say()
{
Console.WriteLine("搞啥呢!");
}
}
static void Main(string[] args)
{
Person p = new Person("1");
p.Say();
Person1 p1 = new Person1("2");
p1.Say();
Person2 p2 = new Person2("3");
p2.Say();
PersonFun(p2);
Console.ReadLine();
}
public static void PersonFun(Person person)
{
person.Say();
}
子类方法的覆盖
前提条件:父类中有一个public函数,子类中没有该函数
因为子类中并没有该函数,所以调用必是父类的
前提条件:子类里已经有了该函数,父类里面也有该函数
此时,子类对象调用子类的该函数,父类对象调用父类的该函数
这种子类函数,可以称之为覆盖
子类在书写该函数的时候,规范的写法应该是:
[访问修饰符] new 返回值类型 函数名(参数列表)
覆盖:子类也有该函数了,以后调用的时候就调用子类的该函数
子类方法的重写【表现出多态】
如果父类想要子类可以重写该函数
那么父类的该函数必须是一个虚函数
[访问修饰符] virtual 返回值类型 函数名(参数列表)
子类该怎么重写
[访问修饰符] override 返回值类型 函数名(参数列表)
重写:把子类和父类的该函数都重新写了一遍,有的新的内容
此时,子类的对象,无论是不是转换成了父类的类型,都会执行重写后的该函数
关于VS自动生成类重写函数
右键类名
点击快速修复或重构
点击生成重写
public class Person
{
public string name;
public Person()
{
}
public Person(string name)
{
this.name = name;
}
public virtual void Say()
{
Console.WriteLine("我是父类的方法");
}
}
public class Person1 : Person
{
public Person1(string name)
{
}
public override void Say()
{
Console.WriteLine("我是字类的方法");
}
}
static void Main(string[] args)
{
Person1 p1 = new Person1("我是字类");
p1.Say();
Person p = new Person1("我是父类");
p.Say();
Console.ReadLine();
}
所有类的最终基类:Object
所以,所有类都可以重写Object类中的虚方法
Object的虚方法有三个:
Equals:描述对象与对象之间是否相等
GetHashCode:将一个对象编程一串数字
ToString:将一个对象转换为一个字符串
抽象方法
抽象方法的关键词abstruct
abstruct 返回值类型(参数列表)
抽象方法必须要放在抽象类里面
抽象方法没有方法体: [访问修饰符] abstruct 返回值类型 方法名(形参列表);
抽象方法的访问修饰符不能是private
抽象类即可以放抽象方法,也可以放普通方法
抽象方法必须在子类中全部实现
除非子类也是一个抽象类,那么可以先不实现该抽象方法
抽象方法和虚方法最大的区别
抽象方法必须其派生类中得以实现
而虚方法不是一定要在其派生类去重写
无论是虚方法还是抽象方法,子类进行了重写【实现】
那么子类的子类依旧可以继续重写
抽象类不能用sealed关键词修饰
总结:override可以重写哪些方法呢?
带有virtual、abstruct、override关键词的方法
代码:
//抽象类
public abstract class Person
{
public void Fun(string name)
{
Console.WriteLine(name);
}
public abstract void Say();
}
public class Person1 : Person
{
public override void Say()
{
Console.WriteLine("asd");
}
}
static void Main(string[] args)
{
Person1 a = new Person1();
a.Say();
Console.Read();
}
虚方法和抽象方法的区别:
- (1)抽象方法是只有方法名称,没有方法体,即没有方法的具体实现,子类必须重写父类抽象方法才能实现具体功能;虚函数有方法名称也也有方法体,但是子类可以覆盖,也可不覆盖。
- (2)抽象方法是一种强制派生类覆盖的方法,否则派生类将不能被实例化。
- (3)抽象方法只能在抽象类中声明,虚方法不是。
- (4)派生类必须重写抽象类中的抽象方法,虚方法则不必要。
虚方法:
- 1、virtual方法表示此方法可以被重写, 也就是说这个方法具有多态.父类中的方法是通用方法,可以在子类中重写以重新规定方法逻辑。
- 2、virtual方法可以直接使用,和普通方法一样。
- 3、不是必须重写的. 子类可以使用base.方法 的方式调用, 无论有没有在子类使用override去重写。
(virtual关键字只是明确标示此方法可以被重写, 其实它和一般的方法没有什么区别
【sealed关键字标示此方法不可以被重写】)
抽象方法:
- 1、抽象方法没有提供实现部分。
- 2、抽象方法只能在抽象类中声明。
- 3、抽象方法是一种强制派生类覆盖的方法,否则派生类将不能被实例化。
sealed关键词
密封类
sealed关键词修饰的类称之为密封类
语法:sealed class 类名
密封类是不能被别的类继承的
密封方法
sealed关键词修饰的重写的方法,称之为密封方法
语法:sealed override 返回值类型 方法名(参数列表)
密封方法无法再次被其子类重写
代码:
public class Person
{
public virtual void Fun()
{
Console.WriteLine(1);
}
}
public class Person1 : Person
{
public sealed override void Fun()
{
Console.WriteLine(2);
}
}
public class Person2 : Person1
{
//这里报错 因为续承的Person1是密封函数
public override void Fun()
{
base.Fun();
}
}
static void Main(string[] args)
{
Person a = new Person1();
a.Fun();
Console.Read();
}
静态类
关键词 static
静态成员
成员:字段、属性、方法
静态:跟对象没有任何关系,只跟类有关系
静态成员在何时开辟的内存
第一次访问这个类的时候【第一次用到这个类的时候】
比如:用这个类名去实例化一个对象
比如:用这个类型去访问一个静态字段
静态成员在何时释放内存?
在程序结束的时候才会释放
普通的实例成员,每有一个对象,就有一个该成员
而静态成员,跟对象没有关系,所以无论有多少个对象,静态成员都只有一个
例: 实例成员【name】,每有一个人,就会有对应的一个名字
而静态成员【Population】,跟对象没有关系,无论有多少个实例对象,人口数量只有一个
静态方法中是不可以访问非静态的成员的
不能访问非静态的字段、属性
不能调用非静态的方法
非静态方法中是可以访问静态成员的
能访问静态的字段、属性
能调用静态的方法
静态方法是可以有重载
静态类
静态的成员可以放在静态类中,也可以放在非静态类中
静态类中只能存在静态成员,不能存在非静态的成员
静态类是不能进行实例化的
静态构造函数
只有一种写法
static 类名()
静态构造函数必须无参数
静态构造函数在什么时候才会调用
静态构造函数在程序运行期间只会执行一次
在第一次访问该类的时候调用
用这个类去new一个对象
用这个类去访问某个静态成员
用这个类去调用某个静态方法
如果有继承关系
静态构造函数的执行顺序是:
先执行子类的静态构造,再执行父类的静态构造
先子后父
静态构造有什么作用?
一般用于对静态成员进行初始化
代码
class Person
{
public static float age=88;
public static void Fun()
{
Console.WriteLine("我是父静态类!");
}
static Person()
{
Console.WriteLine("我是基静态类!");
}
}
class Per : Person
{
static Per()
{
Console.WriteLine("我是子静态类!");
}
}
static void Main(string[] args)
{
Per p = new Per();
Console.WriteLine();
Console.ReadLine();
}
集合、栈、堆、队列、字典
1、集合~范型(命名空间using System.Collections.Generic;)
1.1、ArrayList
代码:
//实例化动态数组
ArrayList score = new ArrayList();
//向动态数组中添加元素
score.Add(90);
score.Add(85.5f);
score.Add("English:100");
int[] array = { 90,80,70 };
//向动态数组中批量添加元素
score.AddRange(array);
//向动态数组中插入元素
score.Insert(2, "Math:80.5");
//删除动态数组中的元素
score.Remove(85.5f);
//删除的是单个约束,如果动态数组中没有该元素,就忽略
score.Remove(90);
//根据下标移除动态数组中的元素
score.RemoveAt(0);
score.AddRange(new string[] { "A", "B", "C", "A" });
//批量删除元素
score.RemoveRange(2, 3);
//如果数组中没有该下标所对应的元素,则也会出现越界异常
Console.WriteLine(score[1]);
//数组元素翻转
score.Reverse();
//一般想要删除某个元素,会先进行判断是否存在
bool containsA = score.Contains("A");
Console.WriteLine("containsA:" + containsA);
//判断数组中是否包含某个元素
if(score.Contains("A"))
{
score.Remove("A");
}
Console.WriteLine("Count:" + score.Count);
//给一个元素的值,查该值在动态数组中的下标
Console.WriteLine("IndexOf:" + score.IndexOf(790));
score.Clear();
score.AddRange(new float[] { 1.1f,5.7f,4.5f,9.8f,3,2,1});
//从小到大排序
score.Sort();
//清空动态数组
//score.Clear();
Console.WriteLine("-------------");
for (int i = 0; i < score.Count; i++)
{
Console.WriteLine(score[i]);
}
Console.WriteLine("-------------");
foreach (var item in score)
{
Console.WriteLine(item);
}
1.2、List
代码:
//初始化范型集合
List<int> list = new List<int>();
//添加元素
list.Add(200);
list.Add(250);
list.Add(280);
//批量添加元素
list.AddRange(new int[] { 1,3,5,6,7,9 });
//移除某个元素
list.Remove(280);
//通过下标移除某个元素
list.RemoveAt(0);
list.RemoveAt(0);
//批量删除
list.RemoveRange(0, 3);
int index = list.IndexOf(9);
Console.WriteLine(index);
Console.WriteLine("-----------");
foreach (var item in list)
{
Console.WriteLine(item);
}
2、栈(Stack )
说明:后进先出
代码:
eg:非范型为例😥
Object<=Stack stack = new Stack();
//进栈
stack.Push("我是第一个进去的");
stack.Push("我是第二个进去的");
stack.Push("我是第三个进去的");
//出栈
Console.WriteLine(stack.Pop());
Console.WriteLine(stack.Pop());
Console.WriteLine(stack.Pop());
Console.Read();
//返回栈顶的元素Peek();
常用方法
Stack.TryPeek(T) 方法
返回一个值,该值指示 Stack 的顶部是否有对象;如果有,则将其复制到 result 参数。 不从 Stack 中删除对象。
public bool TryPeek (out T result);
返回
Boolean
如果 Stack 的顶部有对象,则为 true;如果 Stack 为空,则为 false。
Stack.TryPop(T) 方法
public bool TryPop (out T result);
返回一个值,该值指示 Stack 的顶部是否有对象;如果有,则将其复制到 result 参数,并从 Stack 中删除它。
返回
Boolean
如果 Stack 的顶部有对象,则为 true;如果 Stack 为空,则为 false。
3、堆(heaps)
4、队列(Queue)
说明:先进先出
eg:进列
eg:出列
代码:
eg:非范型为例😥
Queue queue = new Queue();
//进列
queue.Enqueue("我是第一个进去的");
queue.Enqueue("我是第二个进去的");
queue.Enqueue("我是第三个进去的");
//出列
Console.WriteLine(queue.Dequeue());
Console.WriteLine(queue.Dequeue());
Console.WriteLine(queue.Dequeue());
Console.Read();
常用方法:
5、字典(Dictionary<string,int>)
代码:
Dictionary<string, int> dis = new Dictionary<string, int>();
dis.Add("我是第一个进去的", 1);
dis.Add("我是第二个进去的", 2);
dis.Add("我是第三个进去的", 3);
Console.WriteLine(dis.Values);
Console.WriteLine(dis.ContainsValue(2));
Console.WriteLine(dis.ContainsValue(3));
Console.Read();
6、常见的接口
单例、接口和范型
1、单例
如果一个对象在声明时直接实例化【new】。
在访问这个类的时候调用
实例化的时间点最早,在静态构造之前就执行了
2、接口
接口相比类,最大的不同之处在于,只有定义没有实现
接口相当于一堆骨架,实现接口的类,用于填充骨架上的肉
接口不能进行实例化,只能被类或其他接口实现
如即继承类,又实现接口时,类要放在最前面,接口放在后面
eg:
class Person1:Person,jiankou1,jiekou2
2.1、接口和抽象类的对比
相同点
两者都不能被实例化
两者都包含了由其他类或结构继承或实现的抽象成员不同点
不同点
抽象类当中除了拥有抽象成员外还可以拥有非抽象成员;而接口中所有的所有成员都是抽象的
抽象成员可以使用修饰符修饰,接口当中接口成员访问级别是默认不可修改的,并且默认是public
接口当中不可以包含构造方法,析构方法,静态成员以及常量
C#类只支持单继承,接口支持多继承
3、范型
范型类的构造函数不用写范型类
有些时候重载的方法只有参数类型不同,其他的都一样,这时就可以使用泛型。
泛型:需要用户自己传过来的一个数据类型
平时方法里传递的是参数,是变量,参数传递用的是小括号()。
而泛型传递的是数据类型,泛型传递用的尖括号<>
泛型定义之后,一定要用,不然就没有意义
泛型都在方法的哪里用?
定义参数
在方法体呢使用泛型定义局部变量口
设置返回值类型是一个泛型。给泛型添加约束
给范型舔加约束
方法名(参数列表)where 泛型:约束内容
方法名(参数列表)where 泛型A:约束内容1,约束内容2 where 泛型B:约束内容3
关于泛型方法的重载
如果泛型的个数不同,可以重载
如果泛型的个数相同,但约束不同,不可以重载
关于泛型类和泛型接口
class 类名<T,F>
类中的字段类型、方法参数、方法返回值,都可以使用类中的泛型
interface 接口名< T >
代码:
-
public class fanxing { } public static void FX<T>(T sex, T age) { T temp = sex; sex = age; age = temp; Console.WriteLine(age); } public static void FX<T>(T sex, T age, T a) { T temp = sex; sex = age; age = temp; Console.WriteLine(age); } static void Main(string[] args) { FX<int>(7,8); FX<float>(7,8); Console.ReadLine(); } -
范型方法的范型重载
eg 代码:
public static void faning<T>()
{
}
public static void faning<T,F>()
{
}
public static void faning<T,F,U>()
{
}
static void Main(string[] args)
{
faning<>
}
委托与事件
1、委托(delegate)
2.1常见委托类型
什么是委托:相当于中介
委托的别名:代理、句柄
委托是自定义类型
委托是引用类型
代码eg:
eg:非范型为例
//定义委托
delegate void weituo(float momey);
class p1
{
public void zhao1(float momey)
{
Console.WriteLine("中介一需要:"+momey+"$");
}
public void zhao2(float momey)
{
Console.WriteLine("中介二需要:"+ momey + "$");
}
public void zhao3(float momey)
{
Console.WriteLine("中介三需要:"+ momey + "$");
}
public void zhao4(float momey)
{
Console.WriteLine("中介四需要:"+momey + "$");
}
}
internal class Program
{
static void Main(string[] args)
{
p1 p = new p1();
weituo we;
we = p.zhao1;
we += p.zhao2;
we += p.zhao3;
we += p.zhao4;
we(1232143);
Console.Read();
}
}
2.2系统委托类型
无返回值系统委托 Action<>
代码eg:
eg:
class p1
{
public void wcs()
{
Console.WriteLine("我是无参数的中介需要:" + "18456456456456"+ "$");
}
public void zhao1(float momey)
{
Console.WriteLine("中介一需要:"+momey+"$");
}
public void zhao2(float momey)
{
Console.WriteLine("中介二需要:"+ momey + "$");
}
public void zhao3(float momey)
{
Console.WriteLine("中介三需要:"+ momey + "$");
}
public void zhao4(float momey)
{
Console.WriteLine("中介四需要:"+momey + "$");
}
}
internal class Program
{
static void Main(string[] args)
{
p1 p = new p1();
//使用系统委托
//无参数
Action action;
action = p.wcs;
action();
//参数
Action<float> action1;
action1 = p.zhao1;
action1 += p.zhao2;
action1 += p.zhao3;
action1 += p.zhao4;
action1(182);
Console.Read();
}
}
有返回值系统委托 Func<>
代码eg:
eg:
class Hout
{
}
class p1
{
public string Func1()
{
return "有返回值函数无参数:";
}
public Hout Func2(string name)
{
Console.WriteLine("有返回值函数有参数:"+name);
return null;
}
}
internal class Program
{
static void Main(string[] args)
{
p1 p = new p1();
Func<string> func;
func = p.Func1;
Console.WriteLine(func());
Func<string,Hout> func1;
func1 = p.Func2;
Hout a= func1("先生");
Console.Read();
}
}
2.3委托之匿名函数
代码eg:
eg:
class p1
{
public Action<string> action;
public string name;
public int age;
public p1(string name, int age)
{
this.name = name;
this.age = age;
}
public void isAction(string age)
{
Console.WriteLine("我是个中介:"+ age);
if (action != null)
{
action(age);
Console.WriteLine("我是个中介:" + age);
}
}
}
internal class Program
{
static void Main(string[] args)
{
p1 p = new p1("先生",18);
p.action = delegate (string a)
{
Console.WriteLine(a);
Console.WriteLine("我好蒙啊2");
};
p.action += read;
p.isAction("我好蒙啊1");
Console.Read();
}
public static void read(string name)
{
Console.WriteLine(name);
Console.BackgroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.Black;
}
}
❌❌❌如何使用VS进行程序调试
程序是从Main函数开始,顺序执行的
调试就是在你觉得可能会发生问题的地方打下断点
什么是断点?
让程序执行到这个地方暂停下来的点
有什么好处,可以一句句或一段段让程序执行
调试步骤
给想要暂停的代码行添加断点
开始调试
通过监视或局部变量窗口,去看此时你想观察的变量的值
如果想看变量or对象的内存地址
找到即时窗口
&变量名