C#

2021-05-18 19:19发布

数据类型

整型int:占4个字节(23,0,-15)浮点数float:占4个字节(12.5f,3.4f)从精准度看:decimal>double>float 从取值范围看:double>float>decimal  字符类型char:占2个字节,用单引号表示('x','y','0','')字符串string:用双引号表示("zhangsan")布尔类型bool:占用1个字节(True/False)

变量和常量

变量命名规则

1.变量名里面只能包含数字(0-9),字母(a-z,A-Z)和下划线_ 2.只能以字母和下划线开头 3.同一个变量名不能够重复定义 4.不能够使用关键字定义 5.区分大小写 Int则可以 6.使用驼峰命名法(第一个单词全部小写,从第二个单词开始,首字母大写) 7.见名知意 @age =age

算术运算符

如果字符串和其它数据相加,先把其他数据转成字符串,然后再和其他的字符串进行拼接  ++在前:++a  先++。++在后:a++  后++。

复合运算符

+= -= *= /= %=

基本的输入和输出

输出:输出两个数据 Console.WriteLine(10+","+23);  格式化输出,即输出的数据可以带有一定的格式 Console.WriteLine("{0},{1},{2:p2},{3:F3},{3,8:F3},{3,-8:F3}",a,23,0.23,0.56);  

输入:Console.Read()从键盘上读取数据,读取了输入流里面第一个字符的ASC码值

数据类型转换

把一个取值范围小精准度低的数据赋值给取值范围大精准度高的变量,此过程进行了隐式转换

  1. 数值之间的转换,使用强制类型转换符:(类型) a = (int)23.5f;

  2. 把任何数据转成字符串 string str = 123 + "";

  3. 把字符串转成数值类型 a = int.Parse("123");

  4. *通用写法 a = Convert.ToInt32(23.5f); str = Convert.ToString(123); a = Convert.ToInt32("123");

try,catch,finally 把有可能出现异常的代码放在try里面,如果代码出现异常,则运行catch里面的代码,否则只运行try里面呢代码。try和catch一定要同时存在,finall可有可无。不管运行try还是catch都运行finall。

比较运算符

比较运算符组成的表达式,其结果是bool类型  bool res = a != b;

逻辑运算符

逻辑与(&&),逻辑或(||),逻辑非(!) 逻辑运算符两边的值均是bool类型 

  1. 当&&两边的值都是true的时候,整个表达的结果为true,只要有一个结果是假,整个表达的结果就是false  bool res = (a < b) && (a < c);    

  2. 当||两边的值都是false的时候,整个表达式的结果false,只要有一个值为true,整个表达的结果就是true    res = (a < b) || (a > c);

  3. !逻辑非,取反

if语句

语法格式:if(bool类型的表达式){语句}

条件运算符

max = a > b ? a : b;   三组:max = (a > b ? a : b) > c ? (a > b ? a : b) : c;   字符串:string str = a > b ? "a>b" : "a<b" ;

switch...case

  1. 如果case下面有代码,则一定要有break;

  2. 当程序运行break之后,会直接跳出switch后面的大括号,执行大括号后面的代码

  3. 当所有的case后面的值和switch后面表达式的值不相同时,会执行default下面的代码,如果没有default语句,则直接跳出开关

  4. case的位置可以不固定

  5. 如果case下面没有代码,可以省去break,此时该case就和下面的case共用一段代码

while循环

语法格式:while(bool类型的表达式){要重复运行的代码}

break和continue

break在switch里面作用是跳出switch...case,在循环体里面其作用是跳出本层循环

如果只知道循环条件而不知道循环次数的情况下,通常使用while循环,同时会结合break来使用

continue提前结束本次循环,进入下一次循环,代码执行continue之后,continue后面的代码不再执行

do...while循环

while:循环判断循环条件之后再进行循环,又称为当型型循环

do...while:循环不管循环条件是否成立,先执行一次循环

for循环

for循环通常用在已知循环次数的情况

循环的嵌套

外层循环控制打印的行数,内层循环控制打印的列数

一维数组

数组的声明:类型名[]数组名;int[] a;

数组的赋值称为初始化,共有三种方式的初始化

第一种:动态初始化 new是关键字,用来给数组或者对象分配内存空间,用来保持数据,5是数组的长度即数组所存放数组的个数,{}里面的数据称为元素. 

第二种:没有给数组元素赋具体的值,此时系统会给元素赋一个默认值,数值类型的数组,默认值为0,bool类型数组默认值为false,字符串数组,默认值为空,字符数组默认值为空

第三种:静态初始化,数组的声明和初始化必须在一行.

数组做为一个整体不能够直接参与运算,但赋值除外

要得到数组中的元素,需要使用数组名[下标]  元素下标的最大值是数组长度-1

枚举

枚举的类型名是开发者自定义的,故类型名每个单词的首字母必须大写,赋值时,使用枚举类型名加.运算符,选择要赋的值,点运算符是用来点出枚举/机构体或者里面的成员的.其运算级别最高

  1. 每一个枚举值都会对应一个默认的整数,该整数从0开始,向下依次加1,故可以使用强制类型转换符在整数和枚举值之间进行转换

  2. 可以给枚举值赋值一个新的整数值

  3. 枚举有自注释的作用

枚举又叫一一列举,它是把我们要赋的值先列举出来,然后再通过选择的方式,得到我们要赋的值

enum是关键字,用来定义一个枚举类型的,enum后面时类型名

结构体

struct是关键字,用来定义一个结构体类型的,关键字后面是类型名      大括号里面定义的变量称为成员变量或者字段

  1. public是访问修饰符,用来控制结构体或者类里面的成员,在结构体或者类的外面是否能被访问到,如果没有public访问修饰符,该成员在类的外面不能够被访问

  2. 要想访问结构体或者类里面的成员,需要使用点运算符

结构体作为一个整体是不能够直接参与运算的,但赋值除外.只能让结构体或者类里面的成员参与运算       使用结构体或者类可以同时保存多个不同类型的数据

访问修饰符

访问修饰符用来控制结构体或者类里面成员的访问级别的

  1. public表示访问不受限制,public修饰的成员在结构体或者类的内部和外部均可以被访问

  2. 如果结构体或者类里面的成员没有任何访问修饰符,则该成员默认是私有的即private,private修饰的成员只能在结构体或者类的内部使用

合理设计结构体:姓名,性别,出生日期,三门课分数


struct Birth

    {

        public int year;

        public int month;

        public int day;

    }

    struct Test

    {

        public string name;

        public Sex sex;

        public Birth birth;

        public double[] scores;

    }

Test t;

            t.name = "zhangsan";

            t.sex = Sex.男;

            //Birth b;

            //b.year = 2001;

            //b.month = 12;

            //b.day = 13;

            //t.birth=b;

            t.birth.year = 2001;

            t.birth.month = 12;

            t.birth.day = 13;

            t.scores = new double[] { 95, 85, 99 };


类和对象

使用new关键字创建类的一个对象,new用来给对象在内存中开辟空间

类里面的字段都会有一个默认的初始值,如果是数值类型,其默认值为0,字符或者字符串默认值为空,bool类型默认值为false,枚举类型默认值为0

方法定义

(方法是一种代码重用机制)

1.处理的数据均为int类型,2个数据

2.功能均为求两个int类型数据的和

3.都得到了一个具体的结果

4.结果是int类型

方法的调用:

1.看方法名前面返回值的类型,返回值是什么类型,就定义一个和它类型一样的变量,用来接收返回值;

2.看方法名后面的形参,要传入数据的类型以及个数要和形参一一对应.

3.调用方法时实际传入的具体的数值或者变量称为实参,它有实际意义.

方法的类型:

第一种方法类型:有参有返回值的方法


1.方法名即代表了有特殊功能的代码块

2.形式参数:只是告诉开发者,该方法要处理的数据类型以及个数是多少个,它没有实际意义

3.返回值类型是调用该方法后得到的结果的类型

4.return用来跳出方法,回到调用该方法的位置,同时把return后面的值带回去,return后面只能有一个返回值

第二种方法类型:有参无返回值的方法

1.可以不用写return,方法体里面的代码运行完以后,直接跳出方法体,回到调出方法的位置

2.如果有return,则return后不加任何数据

第三种方法类型:无参有返回值

 语法格式:public 返回值类型 方法名(){方法体}

第四种方法类型:无参无返回值

 语法格式:public void 方法名() {方法体}

public void PlayerSet(int hp, int dp)

        {

            //当对象调用该方法时,this指的就是该对象,当不调用的时候,this指的是方法所在的类

            this.hp = hp;

            this.dp = dp;

        }


属性

属性分为三种:即有get又有set称为可读可写属性,如果只有get表示只读属性,只有set表示只写属性

属性本质上是方法,它是get和set方法的另外一种表现形式,在使用属性的时候,和使用字段的规则是一样的.

方法类型

  1. 值类型的数据存在栈区,引用类型的数据存在堆区

  2. 值类型的变量在赋值时是拷贝赋值,两个变量属于不同的内存.引用类型的变量在赋值时,两个变量公用一块内存空间,当一个变量把内存里面的值修改后,另外一个变量的值也会相对应的更改

方法参数

  1. ref引用参数:当方法的形参和实参均为值类型时,形参的值改而实参的值不会一起改变,但在实际解决问题过程中,有些时候需要让实参跟着形参的值一起进行改变,此时需要用引用参数.只需要在形参的前面加ref关键字,调用时,实参的前面也加ref.由于是把形参的值赋值给形参,故实参必须有初始值


    public void Change(ref int x,ref int y)

            {

                int t = x;

                x = y;

                y = t;

            }

  2. out输出参数,它是把形参的值向外传递给实参,需要在形参的前面加out关键字,调用时,实参的前面也加out关键字,在方法体里面,形参必须有值,此种参数用在调用方法时同时得到多个返回值的情况.


    public int SumAndSub(int x,int y,out int sub,out int ji,out int shang)

            {

                shang = x / y;

                ji = x * y;

                sub = x - y;

                return x + y;

            }

  3. params可变数组参数:在调用方法时,可以把数组名传递给形参,也可以直接在()里面把数组的元素写出来,形参只能有一个可变数组参数,并且只能放在最后一位


    public int Sum(params int[] a)

            {

                int sum = 0;

                foreach (var item in a)

                {

                    sum += item;

                }

                return sum;

            }

string

字符串是引用类型,可以直接赋值为null        字符串在赋值时是拷贝赋值        

方法调用:

Contains    IndexOf    LastIndexOf    PadLeft    Remove    Replace    Split    ToUpper    Trim    Substring

(了解)    string修改时内存会增加,而StringBuilder则不会增加内存

方法重载

方法重载的必要条件:方法名字相同,方法的参数类型或者个数不同,和方法的返回值无关

public int Sum(int x,int y){return x + y;}

public float Sum(float x,float y){return x + y;}

public double Sum(double x,double y){return x + y;}

public int Sum(int x,int y,int z){return x + y + z;}

递归(了解)

斐波那契数列


        public int Fun(int n)

        {

            if (n == 1 || n == 2)

            {

                return 1;

            }

            return Fun(n - 1) + Fun(n - 2);

        }


构造方法

和类的名字相同的方法称为构造方法,构造方法的作用是创建对象的时候,在内存中给对象开辟空间同时给对象的字段赋初始值

没有参数的构造方法称为默认构造,它会给字段赋一个默认值,如果类里面没有任何构造方法,创建对象时系统会创建一个默认构造方法

构造方法的名字与类名相同,没有返回值并且不加void,构造方法只能在创建对象的时候被调用一次,其它任何时候和地方均不能被调用

如果类里面只有带参的构造方法,要想使用默认构造,必须显示的在类里面写出默认构造

面向对象程序设计

面向对象:先分析问题所涉及的对象,通过对象调用所需要的方法达到解决问题的目的

面向对象编程可以方便的使代码进行重用,功能进行扩展,代码比较好维护.

面向对象编程三大特性

  1. 封装

    为了保护对象数据的安全性,把字段写成私有的,然后通过构造方法在创建对象时给私有的字段赋初始值,最后再通过公有的属性来访问私有的字段

  2. 继承

    如果多个类里面有相同的成员,此时把相同的成员抽离出来,放到一个公共的类里面,其他的类继承该公共的类,这样就可以使用公共类里面的成员.

    被继承的类(GongGong)称为父类,又称为基类,继承的类(Person_,Student_)称为子类,又称为派生类

    类在继承的时候,类名后面只能继承一个类,类的继承有可传递性


    里氏转换原则

    1.所有父类的对象所在的位置均可以被子类的对象所替换,即把子类的对象赋值给父类的对象

    2.可以把父类的对象强制转换成子类

    在强制类型转换之前,需要先判断其类型是否和目标对象类型一致(使用is关键字来判断类型)(可以直接使用as关键字来进行类型转换,如何转换成功,就会得到一个具体的对象,如果不成功,返回值是null)

    class GongGong

     {

            //protected是受保护的,只能在父类和子类里面使用,在类的外面不能使用

            //internal是内部的,在名字空间内部使用

            public string Name { get; set; }

            public int Age { get; set; }

        }

  3. 多态

    相同类型的不同对象,执行相同的行为时,得到不同的结果.

    父类里面方法写成虚方法(并且实现),子类使用override进行重写,当把子类的对象赋值给父类以后,调用父类的虚方法时会调子类重写的方法,如果子类不重写,就调用父类里面的方法.

装箱和拆箱

装箱是把值类型转换成引用类型的过程;

拆箱是把引用类型转换成值类型的过程;

如果有大量的装箱和拆箱,会额外占用一部分内存,所以编程的过程中应尽量避免装箱和拆箱操作

抽象类

  1. 抽象方法所在的类必须是抽象类

  2. 普通的子类继承该类后,必须把父类里面的抽象方法重写

  3. 抽象类不能够直接实例化,必须由子类进行实例化

  4. 在抽象里面可以有非抽象的成员

  5. 如果子类也是一个抽象类,则不用重写抽象类里面的抽象方法


abstract class SportsLayer

    {

        public abstract void Sport();

        public float Time { get; set; }


    }

    class Swim : SportsLayer

    {

        public override void Sport()

        {

            Console.WriteLine("Swim训练");

        }

    }

    class Run : SportsLayer

    {

        public override void Sport()

        {

            Console.WriteLine("Run训练");

        }

    }


静态类和静态成员

由static或者const修饰的成员,称为静态成员

在类的成员里面,非静态的方法可以调用任何成员,静态方法只能调用静态成员,简单的可以记忆为:静态只能调用静态,其他正常使用

静态成员通过类名加点运算符来调用,当为了方便使用某个方法或者属性,而没必要创建对象时,通常把该成员写成静态成员

static修饰的类称为静态类,类里面所有的成员均为静态成员,静态类一般做为工具类来使用,类里面均为静态的常用的方法或者属性,静态类不能够被实例化,并且静态类默认是密封类,不能够被其它类继承

单例


class Player

    {

        public int HP { get; set; }

        //1.为了保证类的外面不能够直接创建对象,把构造方法写成私有的

        private Player()

        {


        }

        //2.在类的里面创建一个Player的对象,并给其赋值,然后再把该对象通过属性的只读方式返出去,这样在类的外面就可以得到一个对象了.

        private static Player instance;

        public static Player Instance

        {

            get

            {

                if (instance==null)

                {

                    instance = new Player();

                }

                return instance;

            }

        }

    }


接口

interface关键字,用来定义一个接口

接口里面的方法不要实现,接口里面的成员均不加访问修饰符,默认都是共有的。接口里面的成员不能包含字段。

子类必须把接口里面的所有成员全部实现,并且成员前面加public访问修饰符

一个子类同时只能继承一个父类,但可以同时继承多个接口。如果子类同时继承接口和父类,则父类要放在所有接口的最前面。

接口不能够直接进行实例化,可以通过子类进行实例化

泛型

泛型是把类型当作参数来传递,泛型用在方法上称为泛型方法,用在类上称为泛型类

泛型的定义格式:在类名或者方法名后面加<类型占位符1,类型占位符2...>,当使用泛型类或者泛型方法时,所有类型占位符都会被所指定的类型替换掉

public static void Swap<T>(ref T a,ref T b)

        {

            //由于泛型的类型占位符所代表的类型不确定,故泛型参数不能够参与运算

            T t = a;

            a = b;

            b = t;

        }


调用泛型方法时,方法名后面的<>里面要给出具体的类型

泛型约束:使用where关键字,用来约束泛型的类型是值类型还是引用类型,如果约束为值类型,则使用struct关键字,引用类型使用class关键字,如果使用了new(),表示所指定的类型必须有一个默认构造


class Test<T,U>where T:struct where U:class

    {

        public T[] arr;

        public string name;

        public void Swap(int a,int b)

        {

            int t = a;

            a = b;

            b = t;

        }

        public void Swap(float a,float b)

        {

            float t = a;

            a = b;

            b = t;

        }


动态数组

class MyList

    {

        int[] arr;

        //实际放入元素的个数

        int count;

        //数组的容量

        int capacity;

        public MyList()

        {

            count = 0;

            capacity = 8;

            arr = new int[capacity];

        }

        public void Add(int item)

        {

            if (count==capacity)

            {

                //把数组的容量扩展

                EnlargeCapacity();

            }

            arr[count] = item;

            count++;

        }

        //扩展数组的容量

        public void EnlargeCapacity()

        {

            capacity += 8;

            //根据扩展后的容量,创建新的数组

            int[] brr = new int[capacity];

            //把原来数组里面的元素拷贝到新数组中去

            for (int i = 0; i < arr.Length; i++)

            {

                brr[i] = arr[i];

            }

            arr = brr;

        }

        //在指定的位置插入指定的值

        public void Insert(int index,int item)

        {

            if (count==capacity)

            {

                EnlargeCapacity();

            }

            //插入越界

            if (index<0||index>count)

            {

                Console.WriteLine("输入下标不合法");

            }

            //刚好插入现有数据的尾部

            else if (index==count)

            {

                Add(item);

            }

            else

            {

                for (int i = count-1; i >= index; i--)

                {

                    arr[i + 1] = arr[i];

                }

                arr[index] = item;

                count++;

            }

        }

        //查询是否存在所指定的数据,如果存在返回值ture,同时得到具体的下标,否则返回值为false,下标为-1

        public bool Contains(int item,out int index)

        {

            index = -1;

            for (int i = 0; i < count; i++)

            {

                if (item==arr[i])

                {

                    index = i;

                    return true;

                }

            }

            return false;

        }

        //把指定下标的数据替换成指定的数据

        public void Replace(int index,int item)

        {

            if (index<0||index>=count)

            {

                Console.WriteLine("下标不合法");

            }

            else

            {

                arr[index] = item;

            }

        }

        public void Remove(int index)

        {

            if (index < 0 || index >= count)

            {

                Console.WriteLine("下标不合法");

            }

            else

            {

                for (int i = index+1; i < count; i++)

                {

                    arr[i - 1] = arr[i];

                }

                count--;

            }

        }

        //清空动态数组的元素

        public void Clear()

        {

            count = 0;

            capacity = 8;

            arr = new int[capacity];

        }

        public void PrintList()

        {

            for (int i = 0; i < count; i++)

            {

                Console.WriteLine(arr[i]);

            }

        }

    }


集合

分为:非泛型集合和泛型集合。非泛型都是object类型,泛型需要自己定义

泛型集合名字空间

using System.Collections.Generic;

非泛型集合名字空间

using System.Collections;

stack集合

把数据加入到栈集合对象里面,是一种先进后出的数据机构

stack.Pop();

把栈顶的元素从集合中移除,并得到该元素

stack.Peek();

得到栈顶的元素,但不从集合中移除

Queue

是一种先进先出的数据结构,即有泛型也有非泛型

Dictionary

字典集合只有泛型,并且字典里面的元素都是一个键值队,在创建字典集合的对象时,要分别指定键和值的类型。在字典里面,键必须时唯一的

如果字典里面已经存在wangwu这个键,如果赋值的话,会更新键所对应的值,如果集合里面没有这个键,赋值时就相当于在集合中添加一个键,并且给键赋值,功能和Add一样.

得到字典里面所有的键,返回值是一个集合


            Dictionary<int, string>.KeyCollection keys = sort.Keys;

            foreach (var item in keys)

            {

                Console.WriteLine(item);

            }


List


在指定的下标处插入指定的数据

list.Insert();

移除指定的数据,如果移除成功返回true,否则返回false

list.RemoveAt();

list.Reverse();

list.Sort();


索引器

如果一个类的里面有数组,要想访问数组中的元素,正常的做法是通过对象.数组名[下标]的方式来访问,如果能够使用对象[下标]直接访问对象的数组成员,这样的话,代码会更加的简洁.此时可以使用索引器

索引器没有名字,用this来代替,当使用索引器时,this指的是对象。索引器可以重载

public int this[int index]

        {

            get { return Arr[index]; }

            set { Arr[index] = value; }

        }

        public int this[string key]

        {

            get { return Dic[key]; }

            set { Dic[key] = value; }

        }


委托的定义和使用

使用关键字delegate来定义一个方法的类型

如果一个方法的形参是一个委托类型,调用该方法时,需要把一个具体的方法名字赋值给形参变量

方法名字即为代码块所在的内存地址,当使用方法名时,就是使用了该方法名所代表的代码块.如果把方法赋值给一个变量,则该变量的类型要与方法的类型一样.此时就需要使用委托来定义一个方法的类型。

委托变量在赋值时,只需要把方法名字赋值给变量即可.此时方法和变量共用一块内存空间,使用变量和使用方法没有区别,使用委托变量其规则和调用方法的规则一样。

不管方法是静态还是非静态,只要方法的类型和委托变量的类型一致,均可以赋值给委托变量。

一个委托变量里面可执行多个方法,此种方式称为多播委托,委托变量要有初始值,其他方法再赋值给委托变量时,使用+=运算符

委托用来定义一个方法的类型,可以直接把类型相同的方法名字赋值给委托变量,使用该委托变量就相当于使用了该变量里面存储的方法,委托最大的作用时把方法当参数来传递

匿名委托

  1. 匿名委托

    MyGreet greet_ = Test.EnglishGreet;

    greet_ = delegate (string n) { Console.WriteLine("good morning " + n); };

    greet_("lisi");

  2. lambda表达式,语法格式:(形参列表)=>{方法体}

    greet_ += (string n) => { Console.WriteLine("早上好 " + n); };

事件

event修饰的委托对象使用+=运算符进行赋值                                                            (在类的里面,通过event关键字对委托对象进行封装)

enent修饰的委托对象在主程序中不能够直接使用,它必须放在方法体里面使用


超纲题

有一台热水器,给水进行加热,当温度达到95度时,报警器开始报警,显示面板开始显示温度,使用面向对象编程,利用循环进行加热,当温度达95度时,实现上述功能



class Water

    {

        public delegate void DHeat(int t);

        public event DHeat dheat;

        public int Tempreature { get; set; }

        public void Heat()

        {

            Tempreature++;

            if (Tempreature>=95)

            {

                dheat(Tempreature);

            }

        }

        public void Init(Alarm alarm,Show show)

        {

            dheat += alarm.Baojing;

            dheat += show.xianshi;

        }

    }

class Alarm

    {

        public void Baojing(int Du)

        {

            Console.WriteLine("嘀嘀嘀!!!现在温度:{0} ",Du);

        }

    }

class Show

    {

        public void xianshi(int Du)

        {

            Console.WriteLine("温度:{0} ",Du);

        }

    }


Func和Action委托

Func类型的委托,一定会有返回值,故Func类型后面<>里面,至少有一个类型,该类型就是返回值类型,如果有多个类型,则最后一位是返回值类型,其它均为参数类型

Func<int,int,int> func = delegate (int x,int y) { return x > y ? x : y; };

Action类型的委托一定没有返回值,如果Action后面的<>里面有类型,则均为参数类型

Action<int> ac = delegate (int x) { Console.WriteLine(x); };