前言

前言是我写完本篇文章后补充的:
如果道友是奔着Unity来学习的,只需要学到继承多态这里就可以了。后面完全是看道友有没有兴趣继续学下去。
该语言是一门面向对象语言,十分强大的语言,在微软平台上面发挥空间十分大,还有一些强大的功能,比如使用微软的winform平台,能直接弄一个电脑窗口,在winform上面写一个简易版的QQ更不是问题,但这些仍需慢慢探索。

本散仙是奔着去了解一下的心态,没想到学到后面越学越上瘾,因为C#这门语言能干的事情非常多,有些能够较快的得到反馈。

话不多说了,祝各位道友一路顺利~

数组的几种定义方法

用new开辟出来的数组都是动态数组,都是在堆中的内存。

  • int[] p = new int[10] {1,2,3,4,5,6,7,8,9,10};

我对于第一种的理解:赋值号的右边字面解释是->在堆中new10个空间出来,如果不加中括号赋值,初始化全部为0。如果要加中括号进行手动赋值的话,必须要赋值够10个,也就是说10个都要进行手动赋值,一个都不能少,否则就报错。(因为系统会自动给你赋初值为0 ,如果你手动赋值了,那么那些0都会丢掉,只能10个空间都由你来给,这就是为什么想要手动赋值就必须全部都赋上)。

  • int[] p = new int[] {1,2,3,4,5,6};

这种方式与第一种一样的,也是在堆中new一个空间出来,但是这个空间取决于你在右边给出的出初始化好了的值,给出多少个就给你new多少个空间出来存放这些。

  • int[] nums = {1,2,3,4,5,6};

这种方式开辟的空间就是在栈上开辟出来的,因为是静态数组,所以是存放在栈中。

out 和 ref 的区别

  • out:在一个函数中使用 (out 参数A) 的意思是带出多余的返回值给参数A。常常用在函数计算需要带出一个结果的时候就可以用。用 out+参数 作为参数的时候,该参数可以不进行赋初值就传进来。原因是因为:在out参数进来后会自动清空,然后在函数内部进行赋值,必须要在函数内部进行赋初值才能进行使用,这样就是为什么可以不赋初值就把他传进来。
  • ref:在一个函数中使用 (ref 参数B) 的意思是把B的本体传进来,类似于C语言中的指针类型,但是在这里的ref 参数B就是相当于一个指针进行了解引用,所以可以在函数中可以通过该参数直接把本体的值进行修改。用 ref+参数 的时候是需要进行赋初值的,我的理解是因为本身就是一个指针类型的解引用,如果没有进行赋初值的话就会和out关键字冲突,也就说避免重载的问题,
  • 总结:out 关键字 是当你需要返回多个变量的时候,可以把一个变量加上 out 关键字,并在函数内对它赋值,以实现返回多个变量。而ref是相当于一个指针类型的解引用,可以直接修改本体的值。
  • 区别:out可以不赋初值也可以赋初值,但是在函数内部必须进行赋初值才能使用。ref传进来之前必须赋初值。

可变参数params

  • 方法中传参必须在参数最右边作为参数
    例如:方法名(参数1,参数2,params 参数3,参数4…);
    意思就是这样,一旦使用params必须在最右边放参数,且这些参数的类型是一样的,同一个类型才能可以使用 params 关键字,就是说参数3,参数4往后的这些参数都必须是同一个类型的才行。

  • 1.对于方法内,可以理解为就是个普通数组;

  • 2.对于方法外部来说,参数可以为数组,也可以传一组相同的变量集合。

静态方法与非静态方法

实例成员:用类命名的一个对象称为实例成员。

在同一个类中

  • 静态方法:静态方法只能访问同一个类中的静态成员,不能访问到实例成员。在调用的时候需要通过类名 ‘ . ’ 出来,不能使用对象 ‘ . ’ 出来。
  • 非静态方法:既可以访问静态成员又可以访问实例成员。在调用方法的时候只需要通过实例成员 ‘ . ’ 出来即可。
  • 由此引出:不能在静态类中声明实例成员,因为静态只能访问静态,不允许访问非静态的实例成员。
  • 总结:在创建了一个静态的类时候,在Main中调用方法时,不能创建对象,因为是直接用类名进行直接使用,创建对象对静态类是没有用的。原因:因为该类中的方法成员使用的都是静态,说官方点就是静态类不允许实例化。如果我们创建的是非静态的,就需要通过创建一个对象,然后用这个对象进行 ‘ . ’ 出来,便可以使用该方法。

>❀什么时候使用静态和非静态

  • 首先我们都知道C#中Console这个就是一个类,还是一个静态类,因为我们不需要对这个类进行创建对象,而是直接点出来方法就可以使用,这个Console也是使用的很频繁,因此微软公司就把他封装成一个静态类作为一个工具使用。也就是说当我们在开发中经常需要使用到的类方法,那么该方法就可以封装成一个静态类作为一个工具进行使用。静态类在开发一个项目中是资源共享的,因此使用比较频繁,不需要进行创建对象减少不必要的麻烦。

构造函数

下面是当我们用new创建一个对象时进行初始化的过程:

  • 创建对象时,new一个空间,也就是先开辟一块空间。
  • 在开辟空间中创建一个对象。
  • 如果我们写了构造函数,就先进行对象的初始化。
  • 紧接着再进行下面的方法。
  • 总结: 构造函数是一种特殊的方法,是对其对象进行初始化。
  • 好处:减少了大量的初始化代码,对该函数进行传参,即可在类中对该对象赋初值。

如何写构造函数:

  • 构造函数的方法名需要和类名一样。
  • 不需要返回值,不需要写返回类型,void也不用写。
  • 该函数初始化方法的范围一般是public,因为初始化的一般都是对象,对象一般都是非静态类,因此如果我们不写上public的话就会自动默认为private静态方法,所以在我们在写构造函数的时候写上public。
  • 在构造函数中初始化赋值时,一般情况下我们都会对每一个字段写一个属性,我们在构造函数的时候,如果有属性,一般是给属性赋值,而不是直接用对象的字段信息进行赋值。(最终本本质还是会把对象的信息进行赋值,属性只是起着一个过渡作用。)
    构造函数的细节:
  • 有时候我们不需要一下子初始化这么多信息,有时候只需要初始化一两个,我们这时候就可以进行重载构造函数的方法,也就是方法名相同,但其作用不同。
  • 当我们不写构造函数的时候,系统会自动的给我们自带一个什么也不干的构造函数,也就是说该函数是空的。但是当我们需要的时候还是要手动进行写出来,只是系统默认给你自带一个以便区分。当我们自己写一个构造函数后,不挂是否有参数,原本默认自带的都会被替代掉。
  • 学到这里我基本上明白了为什么在new一个对象的时候后面有一个小括号。
    如: Student p = new Student () ; 这个小括号其实就是系统会自带一个构造函数方便你区分,也方便我们对该对象进行初始化工作。
  • 系统自带的构造函数是和类名一样的,这也解释了为什么我们在写构造函数的时候也是把构造函数的函数名写成和类名一样的名字。
  • 括号里面是需要传参的,不传参的时候就是系统自带的构造函数。
  • 总结 : 构造函数的作用就是方便我们在构造函数中把传进来的参数赋值给对象的属性,然后方便初始化对象。

>❀类中方法的重载

  • 重载需要注意的点:如果在类中构造函数重载中初始化内容都差不多,只是参数少和多的问题的话,可以将比较多参数的那个作为主要函数,在次函数后 加上 :this( 参数1… ),参数必须是主函数的参数,只是把次函数的参数传进了主函数进行初始化,在this后的函数,如果次函数比主函数少参数,那么少的那几个参数也要用一个初值传进去,只是用次函数的参数传进去初始化。
    代码如下:
    主构造函数是第一个Student,次函数用:this把参数传到主函数里面。
    注意:主函数多少个参数,次函数也要多少个函数,这个方法就是去掉了累赘的代码,因为次函数的代码和主函数的代码有重叠部分,为了能够 偷懒 节省时间,这个方法就比较好用。
    次函数的中括号一概不能去掉
    在set中不能用字段,要用value来限制,然后才是把value赋值给字段
    在构造函数中是把参数赋值给字段的属性
 public class Student
{
public Student(string name, int age, char sex)//主构造函数
{
this.Name = name;
this.Sage = age;
this.Ssex = sex; }
public Student(string name) : this(name, 18, '女')
{ }
public Student(int age) : this( "空", age, '女')
{ } public Student(char sex) : this("空", 18, sex)
{ } string _sname;
public string Name
{
get { return _sname; }
set { _sname = value; }
} int _sage;
public int Sage
{
get { return _sage; }
set {
if (value < 0 || value > 120)
value = 18;
_sage = value; }
} char _ssex;
public char Ssex
{
get {
if (_ssex != '女' && _ssex != '男') return _ssex = '女';
return _ssex; }
set { _ssex = value; }
}
public void Sayhello()
{
Console.WriteLine("我叫{0}, 今年{1}岁, 性别{2}", this.Name, this.Sage, this.Ssex);
}

>❀在类中输出

  • 一般输出的时候是要用this或者输出一个属性的名字,都不会报错,但是一般都要加上this,因为有时候输出方法里面会有和属性一样的名字的时候,就会优先输出局部变量,而不会通过属性输出。
  • 注意要和Main函数中的输出不一样,在Main中输出要用对象名 ‘ . ’ 出来使用方法或者对象的字段信息。

值类型和引用类型

两种类型的存储方式:

  • 值类型存储在栈中
  • 引用类型存储在堆中
    每次我们使用类new一个对象的时候,new的空间就是在堆中存放着,在栈中存放的是对象名,对象名就是一个值类型,通过该对象找到引用类型的空间进行使用类中的方法。
  • 引用类型(存储在堆中): string, 数组, 自定义类
  • 除了上面的类型外都是在栈中存储的值类型

字符串方法操作

注意:string[]和char[]不一样,string[]是字符串数组,都是指针指向堆里面存放的字符串类型,char[]就是每一个元素都是字符类型。

  • 字符串具有不可变性:每当写出一个字符串,用一个变量存储,都会为该字符串在堆中开辟一个空间,专门放该字符串,即使你再次用该变量进行修改字符串,那么该修改过的新的字符串会在堆中另外开辟一个空间存放,而之前的字符串虽然访问不到了,但是仍然还在之前那个堆申请的空间里面。
    至于会不会造成空间申请过多,只要控制好一般都不会造成内存不过用的情况,而且在程序运行结束之后,会有GC来专门清空这些垃圾,把这个当成垃圾回收机就行。

>❀字符串比较

  • 忽略两个字符串的字母大小写进行比较的方法
    例如ch1 = “C#” 和ch2 = “c#” 两个字符串虽然大小写不一样但是表达意思是一样的,这时候我们有两种方法让他们比较的结果是相同的。
  • 1:.ToLower / .ToUpper ,把两个字符都转化为小写或者都转化为大写字母再进行比较。
  • 2:使用ch1.Equals(ch2, StringComparison.OrdinalIgnoreCase)就可以比较这两个字符串,意思是忽略大小写进行比较。

>❀字符串分隔分离

使用.Split这个函数的返回值是字符串数组,因为分隔后把各个部分做成字符串形式,然后进行返回。
并且注意:这里的分割是按照给出的字符进行分割几部分,不管你每个部分有多少字符或者一长串字符,都只给你按照指定的字符进行分割成几份,比如下面的就分割成三份,2022有四个字符,但是只给你看成一个东西进行分割。

public class Prgoram
{
public static void Main()
{
//将{2022—07—10}转化为2022年7月10日
string s = "2022—07—10";
string[] date = s.Split(new char[] {'—'}, StringSplitOptions.RemoveEmptyEntries);
//将字符串按照遇到—的形式进行分隔,每一个部分分割成字符串类型传到字符串数组中,并且移除掉—。
Console.WriteLine("{0}年{1}月{2}日",date[0],date[1],date[2]);
//Console.WriteLine(date[3]);
Console.ReadKey();
}
}

>❀判断敏感字符串

  • 主串.Contains(字符串A),如果主串中含有词语A就会返回一个true值。
  • 主串.Replace(字符串B, 字符串A),该函数一般和Contains搭配使用。该函数解释是:把主串中的字符串B替换成字符串A。
string ch = "日本安倍三阵亡";
if(ch.Contains("安倍三"))
{
ch = ch.Replace("安倍三", "***");
} Console.WriteLine(ch);
Console.ReadKey();

>❀截取字符串

1和2中的函数方法有重载。

  • 1:s.Substring(int startindex)
    解释:参数意思是字符串中的字符下标,你想让哪个作为startindex就传进来哪一个下标。
    假设:s = “123456”;
    如果用了s.Substring(2);字符串中下标为2的字符为3,
    当你接收该函数返回值后,ch = s.Substring(2)的话,再次打印ch就不是123456,而是3456。
  • 2:s.Substring(int startindex, int endindex)
    解释:很明显现在这个两个参数也时代表下标,两个下标作为限制,那就是相当于在字符串中画了一个区间。看下面的例子
    假设:s = “123456”;
    ch = s.Substring(2, 4);
    打印ch的时候就是从s的下标为2到下标为4的区间中信息,结果是:345
  • 3:s.StartWith("字符串”)和s.EndWith(“字符串”)
    解释:该方法就是一个判断主串中是否有参数中传进来的字符串作为前后缀,返回值为bool类型。也可以是整一个主串作为参数,这样肯定是包含在前缀后缀的。

方法4 , 5函数有重载

  • 4:s.IndexOf(字符);
    解释:s字符串中找到字符参数的下标位置,如果没找到返回-1,找到了就返回数组下标。
  • 5:s.IndexOf(字符,下标A);
    解释:在s字符串中从参数给出的下标位置A开始找字符的下标,找到第一个字符出现的位置并返回,若没找到就返回-1。
  • 6:s.LastIndexOf(字符);
    解释:在s字符串中找到字符最后一次出现的位置,并返回该字符的下标。
    同样的也有一个重载方法,也是和5的重载一样,多了一个开始搜索的下标位置。

>❀去掉字符串中的空格

  • s.Trim(); //无参数
    解释:去掉s字符串中所有的空格。
  • s.TrimStart(); //无参数
    解释:去掉s字符串中前面出现过的空格,仅在字符出现之前的空格,如果是字符中间夹着的空格不能去掉。
  • s.TrimEnd(); //无参数
    解释:去掉s字符串中后面出现的空格,假如是中间出现的空格也是不能去掉,只能去掉后面尾部的空格。

>❀判断字符串是否为空

  • string.IsNullOrEmpty(字符串);(使用类方法)
    该方法可以判断字符串是否为空或者Null

>❀用分隔符将字符串分隔

  • s.Join(参数1:分隔符, 参数二:可变参数,也可以传一个数组进来);
    解释:将可变参数中每一个元素用分隔符隔开
    示例:
//使用分隔符把元素分隔开
string[] name = { "张三", "李四", "梨花", "荷花" };
string new_ch1 = string.Join("|", name);
Console.WriteLine(new_ch1);
string new_ch2 = string.Join("_", 1,2,5,4,9,6,7);
Console.WriteLine(new_ch2);

运行结果:

☢面向对象继承

  • 属性:一个类里面的字段需要用属性来限制,项目开发 中经常用到
    字段写成prviate私有的,不写范围的话也行,会自动算进prviate里面。
    属性必须是public,所有成员都可以访问到的才行,我们在创建了对象后,调用的是属性。
    我们在属性里面对字段进行赋值和限制,属性相当于一个中间商,我们表面上访问到的public属性其实目的就是为了再属性中限制字段,让字段不能是某些值。

一个合法的字段和该字段属性代码如下

private string _name; //私有字段,只允许在类的内部访问
public string Name //属性
{
get { return _name;}
set { _name = value; }
}
  • 类的继承:如果多个类有相同的属性字段或方法,就可以通过继承来优化代码累赘问题。我们用一个最能体现所有类的作为基类,也就是作为所有类的祖先。有两种说法:①基类、派生类②父类、子类
    继承的特性:
  • 继承具有单根性。
  • 继承具有传递性。

单根性的解释:每一个类都只能对应一个基类,对应方法是在派生类名右边加上 :基类名。
传递性的解释:就像一个族谱,父亲有他的父亲,我们叫父亲的父亲为爷爷,那么爷爷具有的一些品质会传到我们父亲身上,父亲身上的品质也会传到我们身上,又因为父亲有着爷爷的一些品质,那我们也就相当于有了爷爷的一些品质,所以这叫继承的传递性。

  • 如果基类构造函数没有参数,那么我们的派生类无须再进行另外的操作就完成了这两个类的继承。
  • 如果基类构造函数有参数,**前提:派生类的构造函数也要包含基类构造函数的参数。**那么我们的派生类的构造函数需要再进行一步操作 ,就是在派生类构造函数名右边加上

    \Rightarrow

    (冒号->): + base ( 这括号里面写与基类参数一致的派生类参数 )
    其实这一个限制也更加验证了类之间的继承一般都是两个类之间的方法和字段大部分都有重叠的时候用继承!

我的念头通达瞬间 ↓ :

  • 如果一个构造函数为无参的类想要继承一个构造函数有参的类,这语法上是报错的,因为你没有满足基类的条件,构造函数是一个初始化的方法,如果我们派生类想要拥有基类的一些方法是行不通的,因为该派生类没有满足基类的参数传进来。
  • 在一个类中构造函数是必须存在的,即使你没有写,系统内部也会自动生成一个看不见的构造函数,一旦我们写了一个构造函数出来就立即摧毁掉系统自带的那个内部构造函数。所以这也就牵扯到了我们在继承过程中,构造函数是必须要对应起来的,要么都没有参数的构造函数才能继承,要么派生类中的构造函数必须拥有基类的构造函数的参数类型,在派生类中的构造函数的参数用 : base(), 在括号中传进来。

构造函数的重载与类的构造函数继承问题有类似的地方,总结一下:

  • 都是在构造函数中加上 : 冒号。

  • 构造函数重载:在一个类中含有多个构造函数,目的是为了再初始化的时候能够按照需求初始化不同的值,但是也要一个全参的构造函数作为主体,其他重载构造函数就用 :this (主构造函数参数),**this的意思其实就是在一个类中进行重载构造函数。**如果次构造函数参数缺少主构造函数参数的话依旧按照主构造函数参数个数,强制赋上一个初值。总之一定要给出主构造函数的全参。

  • 类之间的继承:不同类中含有多个重定义的字段或者方法,首先类之间直接使用冒号+基类名即可,但是由于构造函数是必须要的,所以在继承之前,构造函数要么都是无参,要么派生类中构造函数的参数能满足基类的构造函数参数方可继承,那么不同类之间,派生类的构造函数这时候依旧使用冒号 ,但是后面的关键词不一样了,因为是不同类之间,而且要和父类继承,所以关键词也很贴切的使用了base,这也就是上面所说的 :base()。

  • 一些收获心得:类继承说是说继承,其实和重载的核心是一致的,都是把累赘的代码优化到一个类中,而构造函数是堆到同一个函数中去,省去了重复写代码的过程。

  • 总结: 首先需要继承一个无参父类的话,只需要将类的名字右边加上父类即可完成继承关系。若是由参数的父类的话,子类想要继承父类,就要包含父类的参数,通过构造函数进行传参,但是需要关键字bas意思是基于父类,然后通过构造函数的参数,直接把参数写到base括号内即可,虽然在base中不用谢类型名,但是要对应好类型位置。

父类与子类的代码

	public class Person
{
public Person(string name)
{
this.Name = name;
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value;}
} public void Person_Say()
{
Console.WriteLine("你好我不是人。");
}
}
public class Handsomeboy : Person
{
public Handsomeboy(string name) base : (name)
{
this.Name = name;
}
private int _name;
public int Name
{
get { return this._name; }
set { this._name= value; }
}
public void Handsomeboy_Say()
{
Console.WriteLine("我是{0}靓仔喔。",this.Name);
}
}

关键字

new

  • 用法1:创建对象,在堆中申请空间给对象用,但是对象是存在栈中的。
  • 用法2:当我们刻意想在派生类中写一个与基类方法或者字段一样的成员,就需要在成员范围后加一个new关键字表示把基类的方法隐藏掉,在调用对象类成员的时候只能调用到派生类。总结精华就是:隐藏从基类中继承过来的同名成员。

protected

  • 只允许同类中访问或者该类的子类能够访问。
  • 如果不是用类进行访问,即使用类创建的对象不能访问到,也就是说在类内部能访问。
    比如:Person是一个类,Person. 就能点出来类的protected保护的成员,如果是用Person p = new Person();创建出来的 p对象就是不能访问到Person类里面的protected保护的成员。
  • 子类也能访问到,因为子类同样继承了父类的属性成员,但同样的我们出了子类,即使用子类创建对象,那么该子类对象照样不能访问到父类的Protected对象。

里氏转换※

  • 子类可以转换为父类,但是父类不能转换为子类。
    这种转换说法是非常牛掰的。
  • 当我们创建一个类作为其他类的父类的时候,可以将父类转换为子类,换句话说就是相当于强制类型转换。

假设现有一个父类 Person 和一个子类 Student
创建对象 : Person p = new Person();
里氏转换: p = new Student();
注意的是:即使你把子类内容new给了父类p,但是在调用p的时候还是不能访问到Student子类中的对象,只有使用强制转换的语法能调用到。
如: ((Student)p).方法/成员,这样用括号括起来的时候才能够将Student的方法或者成员进行调用到。

集合ArrayList

创建集合对象 :ArrayList list = new ArrayList();
创建的方法和正常的创建对象是一模一样的。
创建完成后,该集合对象存放任何类型,也就是object类型,就是任意的,所以在集合中经常会使用到里氏转换,我们可以存放不同的类型进一个集合中,因为任意类型就相当于一个父类,那么子类就可以是随便的一个指定类型。不知道你能不能理解到位。
集合:数据很多,类型可以很多,能存放很多类型。最重要的是一个集合的长度是可变的,所以只要你存放的东西进去,内存用完了就会继续给你开辟空间。特点就是长度可以任意改变,类型随便。
数组:类型单一,长度不可变,定义多少就只能存多少。

下面继续用刚刚创建的对象 list。

一: 增加单个元素

  • list.Add();
    在list数组中增加元素,括号里面可以是任意类型,所以我们直接把元素类型放进去就好。
    如:list.Add(1) / list.Add(“张三”)

  • 当我们存放了很多个类型的时候,list就是一个集合数组,只是数组元素的类型不一样,同样我们可以将他当作数组一样使用即可

  • list.Count
    表示的是集合存储的个数,和数组的关键词不一样而已,同样是自带的计算出个数的。
    示例代码:

list.Add("张三");
list.Add(3.14);
list.Add(500m);
list.Add('女');
list.Add(true);
for(int i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
Console.ReadKey();

因为是object类型,所以是可以是任意,因此我存放一个数组类型,把数组类型看成单个整体,存放方式如下:
list.Add(new int[] {1,2,3,4,5,6}) / list.Add( 直接放一个开创好的数组名 )

如果直接存放数组进去的话当你打印的时候是打印不出来数组里面的内容的,因为没有什么打印的占位符是给数组的,数组不过是类型的元素集合。
所以我们在打印的时候需要将其先强制类型转换一下,记住,我们存什么类型进去,强转的时候就转什么类型,数组就强转数组,不要强转成数组元素类型,代码如下:强转方式:( ( int[] ) list[0] )

static void Main()
{
ArrayList list = new ArrayList();
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
//直接初始化数组,并且存多少,数组长度就为多少
list.Add(nums);//添加的是数组,直接给数组名即可
for (int i = 0; i < nums.Length; i++)
{
Console.Write(((int[])list[0])[i]+"/");
}
Console.ReadKey(); }

运行结果是:


如果是一个类存进了集合里面,我们就要用里氏强转规则,也就是将集合的类对象元素强转成为该类对象的类,然后才能使用类对象成员。
代码如下:
Person类代码 ↓ :

public class Person
{
public void Say()
{
Console.WriteLine("你好。hello.");
}
}

使用Person类对象存进集合里面 ↓ :

ArrayList list = new ArrayList();
Person per = new Person();
list.Add(per);
((Person)list[list.Count-1]).Say();
Console.ReadKey();
  • 集合甚至还能将自己包含进去。
    lisi.Add(list).这个是不会报错的,因为集合本身也是一个类型,所以也能把自己存进去。

      但是有一个弊端就是访问的时候,不能访问自己,
    目前我还没有找到单个元素存自己的访问方法(待定区域。。。)

解决访问自己的办法是下面的增加多个元素的方法。

总结:只要看成单个元素,就能存放进去,不过拿出来使用的时候要拆开成多个元素。


二: 增加多个元素

  • list.AddRange(int[] nums{1,2,3,4,5,6});(添加其他数组类型也一样)
    我的理解是:把这个数组里面的元素一个个拆开然后放进集合里面去。
    当我们用list.Count的时候是不是返回一个元素了,因为已经把数组中的各个元素拆开单独放进去,所以这时候我们把集合当成一个正常数组进行访问即可,极大地方便了我们不用频繁的使用强制类型转换。
    示例代码:
ArrayList list = new ArrayList();
list.AddRange(new int[] {1,2,3,4,5,6});
for(int i=0;i<list.Count;i++)
{
Console.Write(list[i]+"/");
}

如果这时候我将集合自己添加自己,用list.AddRange(list)的方式进行存储的话,我们就相当于把自己拆开当成了一个数组存放在在自己的下一个空间。有点晦涩难懂,访问的时候其实就his相当于访问了自己两遍。
代码:

ArrayList list = new ArrayList();
list.AddRange(new int[] {1,2,3,4,5,6}); //添加自己
list.AddRange(list); for(int i=0;i<list.Count;i++)
{
Console.Write(list[i]+"/");
}

运行结果:
就是相当于将自己本来的元素作为一个集合形式,全部拆开后又全部存了一遍进去,所以打印出来的时候相当于打印了两遍的样子。


三:插入

插入单个元素

  • list.Insert(int 要插入的下标位置, object 类型);
    第一个参数是要插入的下标位置,第二个参数就没有类型要求。

插入多个元素

  • list.InsertRange(int 要插入的下标位置 , 一个集合/数组);
    第二个参数必须是一个集合形式或者数组形式传进来。

四:删除元素
删除单个元素

  • 根据元素名进行删除:
    list.Remove(输入删除的元素);
    括号里面输入删除的元素的和你添加他的时候的样子一样就行。

  • 根据元素下标进行删除:
    list.RemoveAt(待删除的元素下标)

  • 根据元素下标范围进行删除:
    list.RemoveRange(int startindex, int endidex)


五: 清空

  • list.Clear();

移除元素,没有返回值,如果使用了该函数,集合中所有元素将会被全部清空。


六: 排序

  • list.Sort();
    使用该函数方法之前该list集合内的元素必须是可以排序的,一般都是能够按照数字排序的才能使用该方法。

七: 反转

  • 将集合排好的元素进行反转:list.Reverse();
    如果括号内没有输入参数,那么就是直接将list集合进行全部反转。
  • 将特定范围的元素进行反转:
  • list.Recerse( int 开始反转的元素下标, int 反转的元素个数)
    括号内有参数就是一个按照范围进行反转。

八:判断集合内是否包含该元素

  • list.Contains();
    返回类型是bool类型

九:计算集合现在的总空间

  • list.Capacity;
    该计算和Count不一样,Count计算的是元素的个数,而Capacity计算的是集合现在能够容纳多少空间。
    深入了解Capacity:如果说空间用完了,那么在下次添加元素的时候会自动给你这个集合开辟一倍的空间,等到下次用完了空间再给你开辟出来。

Hashtable集合

  • 相同点:hashtable集合是一个和arraylist集合差不多,他包含的添加删除和插入都是的方法名字都几乎是一样的。
  • 不同点:hashtable(object 键, object 键对应的值value);
Hashtable ht = new Hashtable();
ht.Add(1, "jackson");
ht.Add(2, true);
ht.Add("新添加的", 54);

因为hashtable仍然是一个集合,所以遍历的时候依旧是以遍历数组的形式进行访问集合内的元素,但是不同的不再通过下标进行访问,而是通过键来对应的访问。第一个参数就相当于一个路标,用该路标可以访问到对应的右边存放的元素。

如何遍历

  • 这时候想要遍历Hashtable集合一般要用foreach循环,但是如果你的键是按照下标的形式存放的话也可以用一般的循环来遍历,但是既然用到了Hashtable就一般都是想要用特殊的键来访问元素,所以我们要用foreach循环。

  • foreach : 循环参数(var item in 对应的集合)
    解释:用item替换集合里面的每一个元素,每循环一次都用item来替代对应集合里面的元素。这时候可以利用foreach的特性,用Hashtable的键集合放进去,然后用item代替,输出ht[item]即可。
    注意的是item不是固定的,只是var类型的一个命名而已。

  • var : 能够把传进来的集合转化为对应的类型,然后就能用item接收该元素,然后再进行输出,这个和拆箱类似,但是他没有执行拆箱,所以他不会消耗性能。

代码如下

Hashtable ht = new Hashtable();
ht.Add(1, "jackson");
ht.Add(2, true);
ht.Add("新添加的", 54); foreach (var it in ht.Values)
{
Console.WriteLine(it);
}
Console.ReadKey();
  • 细节:再添加元素的时候发现,最后添加的反而在用foreach输出的时候是先输出的,第一个添加的反而是最后输出的。
    我自己推测出来就是当往hashtable添加元素的时候相当于一个队列,先进先出。
  • 注意:添加新元素用Add是正确的,如果想要修改对应键的值就不能用Add,记住Add只能用来添加新元素,想要修改元素的话就要用修改正常数组的方法即可,ht [ 键 ] = 修改值。

List泛型集合

泛型集合有什么用?
大大避免了前面两个集合中操作发生的装箱和拆箱(装拆箱后面有讲,就是会拖慢电脑性能的一个东西),那么泛型就是不一样,他参数是T,代表泛型,不是object。

  • 个人见解:当我们不知道自己要存多少元素进去的时候,嫌麻烦老是要new空间的时候可以用,我们想随时添加随时清除的时候也可以用,因为集合方法都有Add这类方法进行对元素的操作。
  • List<泛类型>:尖括号是放你所要存的所有元素的一个类型,所以这也叫泛类型的原因,为了存放广泛的元素,这时候就可以使用泛型集合。
List<string> str = new List<string>();
str.Add("张三");
str.Add("李四");
str.Add("王五"); for(int i = 0; i < str.Count; i++)
{
Console.WriteLine(str[i]);
}
  • 转化为数组形式:因为泛型数组和数组的关系就像是双胞胎一样,都是同类型的一个集合,所以在泛型集合中能够把集合转化为数组。str.ToArray()
  • 同样数组也能转化为list泛型:str.ToList()

装箱与拆箱

  • 引用类型:object
  • 装箱:将值类型转换为引用类型。
  • 拆箱:将引用类型转换为值类型。

装箱和拆箱只发生在继承关系中,而且只有发生了装箱才能发生拆箱。
上述讲到的集合就是在做装箱和拆箱,因为object是所以类型的父类,所以在转换的时候总能将object能够拆箱,转化为对应的类型。
但是装箱和拆箱相当消耗性能,耗时间,所以之后就很少用这些甚至是不用。
重要:但是在List泛类型中,参数却不是引用类型,T是泛型的默认值,也就是说不是引用类型,那么前辈们为了解决重复装箱和拆箱操作而拖慢电脑,后面就不使用arraylist集合和hashtable集合,后面就使用了List泛型集合,因为泛型不会发生装箱和拆箱,这暂时没有深入研究为什么,具体我后面有空了就写一个博客。(待定)

  • 记住,只有在object参数和在进行转换的时候二者是继承关系的时候会发生装箱和拆箱。

☢面向对象多态

用途:当想用一个方法实现多种不同效果的时候就可以用多态。

  • 例如:想要用一个类型数组实现多个不同人的打招呼方式。学生,老,医生等…这时候就可以用多态。就算是类不同,但是我们能通过多态,只用一个方法也能实现不同的打招呼方式,这比之前的里氏转换强行用父类转成子类大招呼的方式简便多了,而且代码也少了,只需要用一个父类存放子类,而且也不用强转类型,所以多态是C#中很重要的一部分。
  • 下面有两种实现多态的方法。

虚方法实现多态

  • 虚方法解释:用父类的方法作为虚拟的,当子类继承了父类之后,想要用同一个方法名进行不同的实现方式的时候,我们在调用父类的方法的时候会自动跳到父类中存放的该子类的方法里面进行实现,因为之前说过,子类继承了父类之后,父类对象可以通过子类进行赋值,也就说父类表面上是父类,但是通过虚方法之后,会跳到存放的对应子类的方法进行调用,这就是多态。

  • 如何操作:首先要对父类和子类进行一个继承关系,继承完成后,因为我们的多态就是要通过一个方法名来实现不同的效果,这时候就要把父类的同名方法加一个virtual关键字,子类同名方法中加一个override,意思就当创建了一个父类对象的时候,我们存的是子类的空间给了该对象,那么在调用同名方法的时候就会自动调用到子类的方法而不是像之前直接继承的时候,父类只能调用到父类,或者通过里氏转换才能调用到该父类对象存放的子类方法。因此虚方法极大的简便了实现多态的过程中写更多重代码。

  • 注意: 在我实现虚方法的时候遇到的困难是,在父类中想要传参一定要在构造函数中传,父类构造函数传参,我们在的子类也要进行构造函数的传参,用base基于父类将参数传给父类实现,然后在调用同名方法的时候再跳到子类方法运行。在子类中实现多态(同名)方法的前,是从父类方法的虚方法中跳过来的,override就是一个将父类中virtual中的方法进行重写。

  • 个人理解:在调试过程中我发现,使用父类类型创建子类对象的时候, 如果我们new的子类对象有参数,会把参数首先传进子类的构造函数中,然后在子类构造函数通过base把参数传到父类中,然后在父类构造函数中进行属性赋值和限制参数的格式,然后就是开辟成功。
    当我们调用方法的时候,首先会进入父类同名方法中,然后再通过判断该子类是哪一个,进入该子类的override函数中进行重写,这样就调用到了子类对象的方法。就是通过父类空间存储子类的东西,然后表现出来的就是一个类型表现出多个不同的状态,这就是我所理解的多态。

  • 在调用override的时候,有一个base.方法,这个是通过在子类方法中调用回父类的虚方法函数(如果你需要的话)。

  • 总结:通过学习虚方法和调试过程中,我对public这个修饰符更加深刻,通过传参的时候,因为我们的属性是public,所以在调用子类的重写函数的时候,我们依旧可以调用到父类的属性字段,也就是时候,我们在继承过程中,子类所有和父类重复的参数,我们都把他堆到父类一个属性中完成赋值,所以我们在子类中实现的时候直接调用父类的属性即可,那个即是我们子类传进去的参数,就是属性位置

  • 虚方法实现代码如下

class Program
{
static void Main()
{
#region 虚方法实现多态
virtual_Person[] zhang = new virtual_Person[3]
{
new virtual_Student("爱学习的学霸"),
new virtual_Teacher("教书的老师"),
new virtual_Person("不想学习的学渣")
}; for (int i = 0; i < zhang.Length; i++)
{
zhang[i].Vir_SayHello();
}
#endregion
Console.ReadKey(); }
}
public class virtual_Person
{
public virtual_Person(string name)
{
tihs.Name = name;
}
private string _name;
public string Name
{ get { return _name; }
set { _name = value; }
}
public virtual void Vir_SayHello()
{
Console.WriteLine("你好,我是{0}。",this.Name);
} }
public class virtual_Student : virtual_Person
{
public virtual_Student(string name) : base(name)
{ }
public override void Vir_SayHello()
{
//base.Vir_SayHello();
Console.WriteLine("你好你好,我是学生{0}",this.Name);
} }

抽象类方法实现多态

我对抽象类的解释有如下几点:

  • 抽象类和虚方法有着异曲同工之处,二者有各自的优势。
  • 抽象方法是用abstract替代掉virtual关键字。
  • 抽象方法只能在抽象类中出现,也就是说想要抽象出一个方法,必须要声明一个抽象类,然后才能在抽象类中写一个抽象方法实现多态。
  • 与虚方法不同的是,虚方法只需要在方法中添加一个virtual即可,不需要把类也虚化,但是抽象类限制比较多,只能在抽象类中声明抽象方法。
  • 抽象类中可以写不是抽象的成员,但是由于抽象类不能够被实例化,所以我们是不能通过抽象类访问到这些,只有继承了他的子类能够访问到。因此利用这个特性,我们能够把这些属性都堆到抽象类中,子类构造函数只要把对应参数传到父类中即可,我们就可以通过在子类中访问到该成员,避免了多次进行属性的限制和代码重写。
  • 抽象方法因为不能写方法体,而且我们也不能将抽象类实例化,只能将抽象类作为一个类型,作为一个盒子一样的东西,将抽象类的子类装进抽象类命名的空间中,然后访问抽象类的方法的时候我们可以直接访问到对应的子类方法和成员,这样也实现了多态。

需要注意的点:

  • 抽象类中的成员只要被抽象化了,在对应的子类中也要将对应抽象化了的成员在子类中将其实现,也就是要写上对应的override,和虚方法的override一样,都是将虚的,在这里应该说是抽象的重写一遍,就是override。简单来说就是,当子类继承了父类的抽象成员之后,必须在子类中将其抽象成员都重写一遍,因为那是抽象的,想要继承就要重写。
    (像极了老一辈没有完成的宏伟目标,然后要我们这些晚辈去帮他们实现)
  • 总结上面这点就是:抽象类中的成员长啥样子,我们在override重写的时候都要严格按照他的样子去重写,比如方法有参数就要在子类中重写的方法也要对应有参数,有返回值就要对应有返回值。

☢总结继承与多态的关系

多态的前提是继承,只有存在继承关系了,各个类之间才能建立联系。
首先我们想要使用多态是因为我们想要实现现实生活中,一个类别能表现出来很多种可能
比如说动物叫:猫叫,狗叫,鸟叫,等等,我们在代码中想要用一个类的一个方法就能实现多种不同的叫法就是多态,但是想要实现这种情况就要抽象出来一个类那就是动物类。
那么我们抽象出一个动物类之后很显然我们是要用你抽象类实现多态,这是我们一般比较多使用的情况。
如果我们抽象不出来一个类,我再打个比方说:走路。
很明显我们抽象不出来一个对象,也就是说世界上走路的方式有很多,动物人类就有很多不同,这时候我们就要用虚方法,虚方法只是针对方法,将走路虚化,然后在不同类中实现的时候只要将该方法重写就可以使用,而且都是用走路这个方法名,这也是多态,用一个方法名实现多种不同的走路方式。

FileStream文件操作

在C#中使用文件流操作是一个比较强大的功能,能够通过字节的复制将任何文件。

  • Filestream类:相当于C语言中的FILE类型,通过该类创建一个对象,然后将文件路径,对文件进行打开or创建…,对文件进行读写操作。
  • Filestream读取的是二进制字节流:所以任何类型的文件都能读取到,所以该文件流是比较万能的。
  • 因为读取的是二进制的字节byte,所以在读取完成后接收的字节内容是要用byte类型数组来接收的。
  • 把文件的字节接收完成后需要进行解码,解码的时需要和读取的该文件的编码方式一样的,例如:UTF-8,ANSI等。
  • 有两种操作方式,一种是比较麻烦的,就是当我们打开了文件之后就需要手动的进行关闭和清除文本流中所占用的资源。另一种是不需要进行这些操作的,只要使用using(在这里打开文件){ 在括号内进行操作},出了括号之后就会自动的帮你做这些操作。(帮你擦屁股)

第一种,需要手动尽心关闭文件和清除,代码如下:

#region 写入文件(手动关闭文件流,没有使用using)
//打开文件
FileStream fileread = new FileStream(@"D:\桌面\old.txt",FileMode.OpenOrCreate,FileAccess.Read);
byte[] buffer = new byte[1024*1024*5];
int de = fileread.Read(buffer, 0, buffer.Length);
string str = Encoding.UTF8.GetString(buffer);
//关闭流
fileread.Close();
//清除流占用的空间
fileread.Dispose();
Console.WriteLine(str);
#endregion

第二种,不需要进行关闭文件和清除文件流占用的空间,代码如下:

using(FileStream filewrite = new FileStream(@"D:\桌面\new.txt", FileMode.OpenOrCreate, FileAccess.Write))
{
filewrite.Write(buffer, 0, buffer.Length);
}

文件流的实操:复制mp3音频到另一个文件夹

class Program
{
public static void Main()
{
#region 实现天天酷跑音乐mp3的复制
string source = @"D:\MY C#\3:面向对象多态\文件操作\音频文件\source\SNH48 - 酷跑Run To You.mp3";
string target = @"D:\MY C#\3:面向对象多态\文件操作\音频文件\target\SNH48 - 酷跑.mp3";
Copy(target, source);
Console.WriteLine("复制成功");
Console.ReadKey();
#endregion
} public static void Copy(string target, string source)
{
using (FileStream music = new FileStream(source, FileMode.OpenOrCreate, FileAccess.Read))
{ using (FileStream cpymusic = new FileStream(target, FileMode.OpenOrCreate, FileAccess.Write))
{
byte[] buffer = new byte[1024 * 1024 * 5];
while(true)
{
int r = music.Read(buffer, 0, buffer.Length);
if(r == 0) break;
else
{
cpymusic.Write(buffer, 0, r);
}
}
}
}
}
}

运行结果截图:复制成功
两个文件夹,实现从source中的音频复制到target中

source文件夹

target文件夹
实现成功,target中已经复制过来了。

  • 学带这里的个人感受 :当我用这个文件流操作对我的视频和音频进行进行复制的时候我就感受到了C#的魅力和强大的功能,在这之前我仅仅学过C语言,且不是很深入的学习,只是一些皮毛,所以当我们看到居然能通过字节进行对所有文件的复制的时候就像个从乡下出城里的感觉。

Directory文件夹操作

注意:一定要在同一个盘里面进行操作,否则会报异常。

  • Directory.CreateDriectory(@“D:\1”);
    创建文件夹
  • Directory.Delete(@“D:\1”);
    删除文件夹,如果文件夹里面有文件夹就会报错
    假设我硬要给该文件夹进行删除,不管里面是否有文件夹了,那么就应该在后面加一个true - > 就是Directory.Delete(@“D:\1”,true);
  • Directory.Move(@“D:\1”);
    剪切文件夹
  • Directory.GetFiles(@“D:\1”);
    获取该文件夹里面所有的文件名,所以返回值是一个字符串数组。
    因此我们要用字符串数组进行接收,
    string[] path = Directory.GetFiles(@“D:\1”);
    该功能非常食用,能够一次性接收一个文件夹里面的图片或者音频等等文件名,然后就能进行对获取的文件名进行操作和使用。
  • Directory.GetFiles(@“D:\1”, “*.文件类型”);
    这个功能更加精准到获取什么文件类型,
    只需要在后面再加上一个 *. jpg,就代表是获取文件夹中某一种文件类型的文件。
  • Directory.GetDirectory(@“D:\1”);
    获取该文件夹里面的文件夹。(套娃)
  • Directory.Exists(@“D:\1”);
    判断文件夹是否存在。

结束语

道友历尽艰辛终于学完了最最基础部分,并且我写的内容十分有限,很多细节没有补充上去,仅仅代表个人观点,认为比较重要的,较常用的写上去了而已,望各位道友多多担待~
如有错误还请各位道友私信我,指出错误,我会修改一番再拿出来供各位道友观看。
劝君更尽一杯酒,西出阳关无故人,本散仙就此告别。

C#语言:散修笔记的更多相关文章

  1. C语言细节总结笔记

    C语言细节总结笔记 */--> C语言细节总结笔记 Table of Contents 1. 三步异或法交换数字 2. 做差法交换数字 3. 按n位置位 4. 求余求商求积 5. 辗除法求最大公 ...

  2. # C语言假期学习笔记——6

    C语言假期学习笔记--6 数组 主要学习了一位数组和二维数组. 数组是一组具有相同类型的变量的集合.C语言程序通过元素的下标来访问数组中的元素.(下标从0开始) 一位数组 类型 数组名[元素个数] 二 ...

  3. R语言可视化学习笔记之添加p-value和显著性标记

    R语言可视化学习笔记之添加p-value和显著性标记 http://www.jianshu.com/p/b7274afff14f?from=timeline   上篇文章中提了一下如何通过ggpubr ...

  4. 二级C语言真题笔记

    二级C语言真题笔记 1. 知识重点:数据类型.循环.数组.函数.指针.结构体与共同体 2. 求程序的运行结果 #include <stdio.h> main() {     short i ...

  5. C/C++编程笔记:C语言入门知识点(三),请收藏C语言最全笔记!

    今天我们继续来学习C语言的入门知识点,第一课:C/C++编程笔记:C语言入门知识点(二),请收藏C语言最全笔记! 21. 输入 & 输出 当我们提到输入时,这意味着要向程序填充一些数据.输入可 ...

  6. Go语言--基础语法笔记

    ### 换了工作,好久没有添加新文章了,本来是想更新到github上的,想想还是在博客里放着,感觉以前的文章都没有很仔细,都只是问题处理的记录, 以后想新加一些整理的笔记也好 ### 主要内容 2.1 ...

  7. swift语言的学习笔记

    swift参考了OC,Rust,Haskell,Ruby,Python,C#等语言的特性.首先,学习这门语言是速学的,我不想浪费太多时间在笔记这门语言和其他语言的哪里不同,特性你自己亲自实践就知道了. ...

  8. C语言学习随笔记

    第一次接触C语言,心中对新知识还是充满好奇的.最开始是从晓鹏老师那听说的C语言,记得当时晓鹏老师是在给我们介绍软考,叫我们去准备软考的时候说到了C语言告诉我们C语言是基础,C语言很重要,叫我们能学多好 ...

  9. object - c 语言基础 进阶笔记 随笔笔记

    重点知识Engadget(瘾科技)StackOverFlow(栈溢出)Code4Apprespon魏先宇的程序人生第一周快捷键: Alt+上方向键 跳到最上面  Alt+下方向键 跳到最下面      ...

  10. 《Go语言实战》笔记之第四章 ----数组、切片、映射

    原文地址: http://www.niu12.com/article/11 ####数组 数组是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连续块. 数组存储的类型可以是内置类型,如整型 ...

随机推荐

  1. 内容分发策略与 SEO 优化指南

    内容分发 内容分发是指通过各种媒介分享.发布或传播内容给受众的过程.这些媒介可以包括不同的渠道,例如社交媒体平台(Facebook.Twitter.LinkedIn.朋友圈.微博.小红书.B 站.抖音 ...

  2. Java 内存分析(程序实例),学会分析内存,走遍天下都不怕!!!

    相信大多数的java初学者都会有这种经历:碰到一段代码的时候,不知该从何下手分析,不知道这段代码到底是怎么运行最后得到结果的..... 等等吧,很多让人头疼的问题,作为一名合格的程序员呢,遇到问题一定 ...

  3. Python设置电脑桌面壁纸

    其实 Python 设置电脑桌面壁纸是很简单的,主要是调用 win32gui  这个库来实现的 代码如下: import win32api import win32con import win32gu ...

  4. 【直播回顾】Hello HarmonyOS应用篇第六课——短视频应用开发

    由HDE夏德旺老师主讲的Hello HarmonyOS进阶系列应用篇第六课<短视频应用开发>, 已于6月8日晚上 19 点在HarmonyOS社群内成功举行. 本节课夏德旺老师带领大家了解 ...

  5. github 小技巧

    前言 简单记一下github 小技巧,因为经常忘. 正文 就是如何快速搜索到自己想找的项目. 如果自己知道项目名,那么直接输入就可以搜索到. 如果不是,那么一般要通过高级搜索. https://git ...

  6. 使用WebApi+Vue3从0到1搭建《权限管理系统》:二、搭建JWT系统鉴权

    视频地址:[WebApi+Vue3从0到1搭建<权限管理系统>系列视频:搭建JWT系统鉴权-哔哩哔哩] https://b23.tv/R6cOcDO qq群:801913255 一.在ap ...

  7. 上架即封神!3.6k Star 的开源游戏模拟器,Delta 冲上 App Store 免费榜

    一直以来,苹果设备的应用商店(App Store)都是禁止游戏模拟器上架,所以 iPhone/iPad 用户不能直接安装 GBA.红白机之类的模拟器应用,这也让想在 iPhone 上重温童年游戏机的机 ...

  8. 力扣153(java&python)-寻找旋转排序数组中的最小值(中等)

    题目: 已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组.例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:若旋转 4 次,则可以 ...

  9. 力扣784(java)-字母大小写全排列(中等)

    题目: 给定一个字符串 s ,通过将字符串 s 中的每个字母转变大小写,我们可以获得一个新的字符串. 返回 所有可能得到的字符串集合 .以 任意顺序 返回输出. 示例 1: 输入:s = " ...

  10. 力扣68(java)-文本左右对齐(困难)

    题目: 给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本. 你应该使用 "贪心算法" ...