当前位置:   article > 正文

C#学习笔记 面试提要

C#学习笔记 面试提要

冒泡

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

 {

     for (int n = 0; n < arr.Length - 1 - m; n++)

     {

         if (arr[n] > arr[n+1])

         {

             int temp = arr[n];

             arr[n] = arr[n + 1];

             arr[n+1] = temp;

         }

     }

 }

选择

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

{

    int index = 0;

    for (int n = 1; n < arr.Length - m; n++)

    {

        if (arr[index] < arr[n])

        {

            index = n;

        }

    }

    if (index != arr.Length - 1 - m)

    {

        int temp = arr[index];

        arr[index] = arr[arr.Length - 1 - m];

        arr[arr.Length - 1 - m] = temp;

    }

}

委托是函数的容器

是用来传递函数的方法

Action是可以传n个参数的委托  1-16个参数

Func是可以穿n个参数 并且有返回值的委托 1-16个参数

命名空间 工具包

类 工具

函数 工具能做的事情

#region 折叠代码

#endregion

有符号的整形变量  能存储一定范围内的正负数 包括0的变量类型

sbyte -128 - 127  占一个字节 0000 0000 标粉的那一位 是符号位 所以 能表示正负

short -32768 - 32768 占两个字节

int  -21亿 -  21亿 占四个字节

long -9百万兆 - 9百万兆 占八个字节

无符号的整型变量 一定范围内 0和正数的变量类型 

byte 0- 255 占一个字节 

ushort 0- 65535 占两个字节

uint 0- 42亿 占四个字节

ulong 0 - 18百万兆 占八个字节

浮点数 小数

float 存储7/8位 有效数字 根据编译器的不同可能不一样   占四个字节

float = 0.1234567f 

加f是因为c#默认的小数是double

有效数字 是从左到右 从非0数开始算有效数字

double

double 存储15-17位 有效数字 根据编译器的不同可能不一样 占八个字节

decimal 存储27-28位 有效数字 根据编译器的不同可能不一样 占十六个字节

decimal = 0.1231321321321321m;

特殊类型

bool   占一个字节

char 用来存储单个字符的变量类型  占二个字节

char c = 'a';

char c = '然';可以中文一个字符

string 用来存储多个字符 没有上限 长度可变

string s = “string”;

1byte = 8bit 1byte = 0000 0000 8个数 就叫8位 一个字节

1kb = 1024 byte

1mb = 1024kb

1gb = 1024mb

1tb = 1024gb

通过sizeof 可以知道变量类型所占的空间 单位 字节

常量必须初始化

常量不能被修改

const

string s = @“\\\\” 取消转义字符

值类型 数字类型  默认值 是0 bool 默认值是 false

引用类型 的默认值是null

隐式转换 大范围装小范围

decimal 不能隐式转换

无符号不能装有符号 有符号不能隐式转换为无符号

有符号能装无符号的 范围要在有符号的范围内

浮点数能装任何类型的整数 很大的数会转成科学计数法

char可以隐式转换为整型 其实是转为int 然后int可以转为其他int可转的 对于short byte等小的范围 无法表现为隐式转换

double -》 float -》整形(有符号 无符号)-》char

decimal -》 整形(有符号 无符号)-》char

显式转换

高精度转低精度 括号强制转换 可能会出现范围问题

浮点数会丢失精度

浮点强转整形时 会直接抛弃小数

parse法 将字符串类型转成对应的类型 字符串必须转换成对应的类型

int I = int.parse("asdf");

Convert法 更准确的将各个类型进行转换 填写的变量要正确 会四舍五入

int I = Convert.ToInt("123123");

toint16 是转为short touint 是ushort

toint32 是转为int  同上

toint64 是转为long 同上

float是tosingle

double是todouble

decimal  todecimal

还有 变量类型.ToString();

进行字符串拼接时+++ 自动调用了tostring

异常捕获

try

{

    //希望进行异常捕获的代码

}catch (Exception ex) {

    //如果出错 则会执行catch的代码

}finally {

    //不论有没有出现异常 都会执行的代码

}

默认的整数是 int 如果要得数是小数 则在运算时 要有浮点数的特征 f/m

乘除取余 高于加减 算术运算符的优先级

调换 交换

            int a2 = 99;

            int b2 = 87;

            a2 = a2 + b2;//a2 = 99 + 87

            b2 = a2 - b2;//b2 = 99 + 87 - 87

            a2 = a2 - b2;//a2 = 99 + 87 - 99

 //99999秒转为 多少天多少时多少分钟多少秒来显示

 int daySec = 60 * 60 * 24;

 int hourSec = 60 * 60;

 int minSec = 60;

 int day = 99999 / daySec;//int会抛弃除不尽的小数 正好除不尽的小数是不足一天的秒数

 int hour = 99999 % daySec /hourSec;//取余出来的余数 就是不足一天的秒数 用来除hoursec 正好就是几个小时 同时int会抛弃除不尽的小数

 int min = 99999 % hourSec /minSec;//同上 取余就是不足一小时的秒数 再除于minSec 正好是几分钟

 int sec = 99999 % minSec;

字符串拼接

直接用+号拼接 注意的是

string s = “”+1+2+3+4

输出1234

string s =1+2+3+4

输出 10

string s = "1";

s += 1 + 2 + 3 + 4;

输出

110

string.format({0},{1},{2},1,2,3)后面的东西可以是任意的变量类型 占位符

console.writeLine({0},{1},{2},1,2,3)  也一样可以用占位符

条件运算符 优先级低于算数运算符

逻辑运算符<条件运算符<算数运算符<!

!>&&>||

逻辑运算符的短路规则

只要满足条件判断左边的内容 则右边的内容不重要

如逻辑与 有假则假 左边假 右边不用看

逻辑或 有真则真 左边真 右边不用看

位运算符

转为2进制后 对位运算

位与 类似逻辑与 有0则0

位或 类似逻辑或 有1则1

异或 相同为0不同为1

位取反 0变1 1变0

左移 向左移几位就在右侧补几个0

右移 向右移几位 就删最右侧几个数

异或加密

位运算与buff

三目运算符

int year = int.Parse(Console.ReadLine());

//年份能被400整除 或

//年份能被4整除但不能被100整除

Console.WriteLine(year % 400 ==0 || year % 4 == 0 && year % 100!=0 ? "闰年" :"平年");

switch

贯穿

int num = int.Parse(Console.ReadKey().KeyChar.ToString());//获取输入的一个字符 转为string之后 再转为int

//如果直接获得char 是直接获取ascii码所以要转成string

斐波那契数列

int index = 0;

int result = 0;

int n1 = 1;

int n2 = 1;

while (index < 20)

{

    index++;

    if (index == 1)

    {

        result = n1;

    }

    else if (index == 2)

    {

        result = n2;

    }

    else

    {

        result = n1 + n2;

        //这里要注意的是 把n1 n2 后移了

        n1 = n2;

        n2 = result;

    }

}

Console.WriteLine(result);

//1-100的素数

int num = 2;

while (num < 100)

{

    int i = 2;

    while (i < num)

    {

        if (num % i ==0)

        {

            //不是素数

            break;

        }

        i++;

    }

    if (i == num)

    {

        //是素数

        Console.WriteLine("{0}是素数", num);

    }

    num++;

}

static bool IsPrime(int num)

{

    for (int i = 2; i < num; i++)

    {

        if (num % i == 0) {

            return false;

        }

    }

    return true;

}

//水仙花数

for (int i = 100; i < 1000; i++)

{

    int hundred = i / 100;

    int ten = i % 100 /10;

    int ge = i % 10;

    if (hundred * hundred * hundred + ten* ten * ten + ge * ge * ge == i)

    {

        Console.WriteLine(i);

    }

}

   //九九乘法表

   for (int i = 1; i <= 9; i++)

   {

       for (int j = 1; j <= i; j++)

       {

           Console.Write("{0} x {1} = {2}  ",j,i,j*i);

       }

       Console.WriteLine();

   }

复杂数据类型

枚举

E 或 E_ 开头

枚举不能声明在函数中

数组

数组 增加元素 要全部元素挪过去之余 还要将

newarr = arr;//把引用的地址也转过去

二维数组的行长度arr.getlength(0)

二维数组的列长度arr.getlength(1)

数组声明初始化后 就不能在原有的基础上增加数组的长度或删除数组长度了

引用类型有 string  数组  类  相互赋值 是两者指向同一个值(地方)

值类型 其他的类型 结构体  值类型 相互赋值 把内容拷贝给对方

值类型和引用类型存储在内存区域不同

值类型存储在栈空间  系统分配 自动回收 小而快

引用类型 存储在堆空间  手动申请 释放 大而慢

string是特殊的引用类型 重新赋值时 会在堆中重新分配空间 拼接或重新赋值时 都会产生内存垃圾 对性能造成影响

函数的作用

1 封装代码

2 提升代码复用率

3 抽象行为

ref和out

解决值类型和引用类型 在函数内部改值或重新声明 能够影响外部传入的值 让其也能被修改

使用就是 在声明参数前 加上ref或out 关键字

区别就是 ref 使用前 必须初始化

out在内部必须赋值

原因就是 必须要让变量有值

默认参数

Static void Speak(string str  = "nihao"){}

如果普通参数和默认参数混在一起 默认参数 必须在普通参数后面

变长参数

最多出现一个params 并且必须出现在最后

params 后面必须跟的是数组

public void xxx(params int[] arr){

}

函数的重载

名字一样的函数 但是 参数的类型或者参数的顺序不一样

作用

1 减少函数名的数量 避免命名空间的污染

2 提升程序的可读性

递归函数

正确的递归的函数

1 必须有结束调用的条件

2 用于条件判断的 条件 必须改变 能够达到停止的目的

先拼凑出普通逻辑 然后 再拼凑出 递归逻辑 然后再加入条件做跳出

结构体

用来表现存在关系的数据集合

结构体申明的变量 不能直接初始化 

可以包含别的结构体 不能包含自己

面向对象

面向对象是一种对现实世界理解和抽象的编程方法

把相关的数据和方法组织为一个整体来看待

从更高的层次来进行程序开发

更贴近事物的自然运行模式

万物皆对象

用程序来抽象 形容对象

用面向对象的思想来编程

提高代码复用率

提高开发效率
提高程序可拓展性

清晰的逻辑关系

面向对象 

三大特性

封装  用程序语言来描述对象

继承  复用封装对象的代码 儿子继承父亲 复用现成代码

多态  同样行为的不同表现 儿子继承父亲的基因但是有不同的行为表现

七大原则

里氏替换原则

类是对象的模板 蓝图 可以通过类 创建出对象

Person p;

Person p = null; //这两种都没有在堆上开辟空间 只在栈上面开辟了一个空间 里面是空的 没有存地址

Person p3 = new Person(); //这种是栈上有地址 堆上有空间

构造函数

如果先实现了有参构造函数 而没有实现无参构造函数 则会失去默认的无参构造

public Person():this(){}

this()意思是先去调用 一个无参的构造函数 在this()中加参数 意思就是先调用有参的构造函数

析构函数

在引用类型的堆内存被回收的时候会被调用 当 垃圾 真正被回收 才会调用 很少用

~Person(){

}

垃圾回收

垃圾回收的过程就是遍历堆 heap 上动态分配的所有对象

通过识别它们是否被引用来确定哪些对象是垃圾 哪些对象仍要被使用

所谓的垃圾 就是没有被任何变量,对象引用的内容

垃圾回收的算法

引用计数

标记清除

标记整理

复制集合

GC. Collect(); 手动触发GC

GC只负责堆heap内存的回收

引用类型都是存在堆heap中的 所以它的分配和释放都通过垃圾回收机制来管理

stack栈上的内存都是由系统来自动管理的

值类型在栈stack中分配内存  它们有自己的生命周期 不用对他们进行管理 会自动分配和释放

c# 中内存回收的机制的大概原理

0代内存  1代内存   2代内存

代的概念

代是垃圾回收机制使用的一种算法  (分代算法)

新分配的对象都会配置在第0代内存中

每次分配都可能会进行垃圾回收以释放内存(0代内存满时)

在1次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾 会进行以下两步

1 标记对象 从根(静态字段 方法参数 )开始检查引用对象 标记后为可达对象 未标记的为不可达对象

不可达对象就是垃圾

2 搬迁对象 压缩栈 (挂起执行托管代码线程) 释放未标记的对象  搬迁可达对象 修改引用地址

大对象总被认为时第二代内存 目的时减少性能损耗 提高性能

不会堆大对象进行搬迁压缩 85000字节 83kb以上的对象 为大对象

0代内存满了有用的放进1代  触发0代垃圾回收 放进一代 一代满了  有用的放进2代  

触发 1代 0代垃圾回收 二代满了 触发0代 1代 2代 垃圾回收机制

为什么大对象都放在2代内存 因为大对象的释放需要消耗较多的性能 不轻易释放

成员属性  主要对成员变量有保护作用

类里面

get

set

方法就是成员属性

可以做加密

set的时候 加5 (加密) set里面value就是传入的内容

get的时候 减5 (解密)

这样内存里存的值就不会被轻易拿出来

自动属性

没有什么特殊处理的时候

public int Height{

//不用去去声明 成员变量  直接声明 成员属性

get;

set;

}

索引器

让对象可以像数组一样通过索引访问其中元素 让程序看起来更直观 更容易编写

索引器里面可以写逻辑

//相当于有一个快捷的方法来使用 类里面 的元素 规则自己定

//访问时 和数组一样

//比较适合类中有数组的时候使用

class Person{

Person[] friends;

public Person this[int index]{

get{

return friends[index];

}

set{

friends[index] = value;

}

}

}

使用:

Person p = new Person();

p[0] = new Person();

Console.WriteLine(p[0]);

静态成员

static

程序开始时 就会分配内存空间 给静态成员 直到程序结束 内存空间才会被释放

静态成员和程序同生共死

每一个静态成员都会有自己唯一的“内存小房间”

这让静态成员有唯一性

在任何地方用的都是小房间里的内容 改变它也是改变小房间内的内容

内存里面有专门的静态区域

静态函数中 不能使用非静态成员  要实例化对象 由对象使用

静态成员 方便别人使用

静态方法 常用的 唯一的方法声明

常量是特殊的静态?

常量要初始化

静态可以不初始化

常量不能改  静态可以改

const 只能修饰变量

static 可以修饰很多东西

相同就是都可以通过    类名。出来

单例

        class Test

        {

            private static Test t = new Test(); //利用了 静态的唯一性

            private Test() { //外部不能实例化

            }

            public static Test T

            {

                get

                {

                    return t;//保护了 t 对象 不能被修改

                }

            }

        }

静态类

将常用的静态类 写在静态类中 方便使用

静态类 不能被实例化 更能体现工具类的唯一性

静态构造函数

在构造函数前 加上static修饰

特点

静态类和普通类都可以有

不能使用访问修饰符

不能有参数

只会自动调用一次  (在第一次使用的时候 被调用)

作用

在静态构造函数中初始化 静态变量

拓展方法

为现有非静态类 变量类型 添加新方法 

相当于为一些数值类型 或者 类什么的 新加一些自定的工具方法

作用

提升程序拓展性

不需要在对象中 重新写方法

不需要继承来添加方法

为别人封装的类型写额外的方法

特点

一定是写在静态类中

一定是个静态函数

第一个参数为拓展目标

第一个参数用this 修饰

public static void SpeakValue(this int value){

//value 是传进来的实例化的对象

}

运算符重载  让自定义类和结构体对象可以进行运算

一定是个 公共的静态方法

返回值写在operator 前面

逻辑处理 自定义

不能使用ref out

一个符号可以多个重载

条件运算符需要成对实现

operator

参数里面 必须有一个是原类型

public static Point operator + (Point p1,Point p2){

return p1;

}

逻辑与 && 逻辑或 ||

索引符[]

强转运算符()

特殊运算符

点.  三目运算符?: 赋值符号=

内部类

在一个类里声明另一个类

为了表现亲密关系

使用时 要用外面的类 来。出内部的类

分部类

partial

将声明和实现分开两个地方来写

继承

一个类A继承一个类B

类A将会继承类B的所有成员

A类将拥有B类的所有特征和行为

单根性  子类 只能有一个父类

传递性 子类可以简接继承 父类的父类

里式替换原则

如何父类出现的地方 子类都可以替代

Father son = new Son();

父类容器 装载子类对象

is判断一个对象是否是指定类对象 返回一个bool

as 将一个对象转换为指定类对象 成功返回对象 不成功返回null

继承中 构造函数

声明一个子类对象时

先执行父类的构造函数  调用的是父类的无参构造函数

再执行子类的构造函数

父类的构造函数 很重要

子类可以通过base关键字 代表父类 调用父类构造

class Father

{

    public Father(int i ) { }

}

class Son : Father

{

    public Son(int i):base(i) { }

}

子类在实例化对象的时候是必须调用父类的构造函数的 所以 base用来使用一个指定的父类构造函数

万物之父

object 是所有类型的基类

object与引用类型 用is 判断 用as转换

object与值类型 用()强转

object与string 用tostring  或者 as

装箱拆箱

用object存 值类型 叫装箱  object o = 1f;  把值类型用引用类型来存储 栈内存会迁移到堆内存中

把object转为值类型 叫拆箱 float f1 = (float) o2;  把引用类型的值类型取出来 堆内存会迁移到栈内存中

好处 不确定类型时 可以方便参数的存储和传递

坏处 存在内存迁移 增加性能消耗

密封类

sealed

让类无法被继承

保证最底层子类不被继承

保证程序的规范性  安全性 结构性

多态

让继承同一父类的子类们 在执行相同方法时 有不同的表现

目的

同一父类的对象 执行相同行为 方法的时候有不同的表现

解决的问题 

让同一个对象有唯一行为的特征

多态vob

编译时的多态是 函数重载 (确实是多态 但是不能以多态来理解 真正的多态是 运行时多态)

运行时 多态 是 vob 抽象函数 接口

v virtual 虚函数

o override  重写

b base 父类

class Father{

public virtual void Atk(){

}//虚函数 虚函数就是用来给子类重写的

}

class Son{

//重写虚函数

public override void Atk(){

base.Atk();

//base代表父类 可以用过base来保留父类的行为

}

}

virtual 和 override 必须配对使用

抽象类

abstract

 不能被实例化

可以包含抽象方法

继承抽象类必须重写其抽象方法

抽象方法

只能在抽象类中声明

没有方法体

不能是private

继承后必须实现 用override 重写

整体框架时 会使用

孙子可以不实现抽象方法

接口

接口时抽象行为的  基类

interface

接口是行为的抽象规范

不包含成员变量

只包含 方法 属性 索引器 事件

成员不能被实现

成员可以不写访问修饰符 不能是私有的

接口不能继承类 但是可以继承另一个接口

类可以继承多个接口

类继承接口后 必须实现接口中 所有成员

和类的声明类似

接口是用来继承的

接口不能被实例化 但是可以作为容器存储对象

接口里的修饰符 默认public

实现接口的地方 必须是public

接口也遵循里氏替换原则 也即是说 可以用同一个行为接口来获取 不同的对象 (都继承该接口的)

接口继承接口时 可以不需要实现接口

等到类继承时 再实现接口

显示实现接口

当一个类继承两个接口

但是接口里面存在着同名的方法时

不能写 访问修饰符

interface Iatk{

void atk();

}

interface Isuperatk{

void atk();

}

class Person{

void Iatk.atk(){

}

void Isuperatk.atk(){

}

//显示实现接口的语法  接口名.方法名

}

继承类

对象间的继承 包括特征行为等等

继承接口

行为间的继承 继承接口的行为规范 按照规范实现内容

密封方法

sealed

让虚方法或者抽象方法不能被重写

和override 一起出现

public sealed override void Eat(){

}

//加了sealed 就不能被重写了

命名空间

(工具包)  类就是工具

命名空间是用来组织和重用代码的

命名空间可以同名写多个

using  引用命名空间

不同命名空间允许有同名类

引用两个命名空间的同名类时 要使用 指明出处的语法 XXX.xxx

internal 只能在该程序集里使用

Object

静态方法

Equals()  判断两个 对象 是否相等 最终的判断权 交给左侧对象的Equals方法

不管值类型 引用类型都会按照左侧对象的equals方法进行比较

RefereceEquals

比较两个对象是否是相同的引用 主要是用来比较引用类型的对象

值类型对象返回值始终是false

成员方法

GetType  需要结合反射

获取对象运行时的类型Type

普通方法

MemberwiseClone

该方法用于获取对象的浅拷贝对象 口语化的意思就是返回一个新的对象

但是新对象的引用变量会和老对象一致

浅拷贝 一个类里的值类型会直接变成新的  但是引用类型的变量 还是指向拷贝之前的那个

假如一个类里有一个值类型 这个值类型是放在堆里面的

虚方法

Equals

默认实现是比较两者是否是同一个引用 即相当于ReferenceEquals

但是微软在所有值类型的基类System.ValueType 中重写了该方法 用来比较值相等

我们也可以重写该方法 定义自己的比较相等的规则

GetHashCode

该方法是获取对象的哈希码

哈希码 一种算出来的 用于表示对象的唯一编码 不同对象的哈希码有可能一样

可以通过重写该方法来重新定义哈希码的算法

ToString

该方法用于返回当前对象代表的字符串 我们可以重写它定义我们自己的对象转字符串规则

调用打印方法的时候 默认就是调用的对象的Tostring方法打印出来的

string

字符串本质是char数组

String str = "1231";

int I  = str.IndexOf("1");//正向查找字符位置  0

int I  = str.LastIndexOf("1");//正向查找字符位置 3

String str2  = str.Remove(1);//移除指定位置后的字符  12

String str2  = str.Remove(1,1);//移除指定位置后的字符  121

String str3 = str.Replace("12","56");//替换  5631

ToUpper 转换大写

ToLower 转换小写

String str4 = str.Substring(1);//截取从指定位置开始之后的字符串  31

String str4 = str.Substring(1,1);

str = "1,2,3,4,5,6,7,8,9";

string [] strs = str .Split(",");

strs[0] = "1";

String 和 string

Int32 和 int

Int16 和short

Int 64 和 long 区别是什么 没有区别 后者是前者的别名

stringbuilder

一开始就有容量和长度 长度没达到最大容量时 更改或增加stringbuilder不会产生垃圾

stringbulider是一个字符数组

append

remove(0,3)

str[0]=“i”   string不能这样改 stringbuilder可以

equals

string和stringbuilder的区别

string相对于stringbuilder更容易产生垃圾 每次修改拼接都会产生垃圾

string相对于stringbuilder更加灵活 因为提供了更多的方法

需要频繁更改的时候选择stringbuilder

需要使用string的一些特殊方法的时候使用string

结构体和类的区别

结构体和类最大的区别是存储空间上的 结构体是值类型 在栈空间  类是引用类型 在堆空间

结构体具备面向对象的封装的特性 但是不具备继承和多态 因此大大减少它的使用频率

结构体没有继承 所以不能用protected

结构体成员变量不能指定初始值 类可以

结构不能声明无参的构造函数 类可以

结构体声明有参构造函数之后 无参构造不会被覆盖 类会被覆盖

结构不能声明析构函数 类可以

结构体不能被继承 类可以

结构体需要在构造函数中初始化所有成员变量 类随便

结构体不能被static修饰 类随意

结构体不能再内部声明和自己一样的结构体变量 类可以

结构体可以继承接口 因为接口是行为的规范

string

如何选择结构体和类

有继承和多态的 直接选择类

对象是数据集合的 考虑结构体 如位置 坐标等

从值类型和引用类型上面去考虑

   ( 经常被传递赋值 但是源对象不想跟着改变时就用结构体)?

抽象类和接口的区别

都可以被继承

都不能直接实例化

都可以包含方法声明

子类必须实现未实现的方法

都遵循里氏替换原则(父类装子类)

抽象类中有构造函数 接口中没有

只能继承一个抽象类 可以继承多个接口

抽象类中可以有成员变量 接口中不能

抽象类中可以声明成员方法 虚方法 抽象方法 静态方法  接口中只能声明没有实现的抽象方法

抽象类中方法可以使用访问修饰符 接口中建议不写 默认public

抽象类与接口的选择

表示对象的用抽象类 表示行为的用接口

面向对象七大原则

高内聚 低耦合  将东西内聚予一个类 尽量少跟别的类产生关系

单一职责原则  做类里面应该做的事情 专注于单一功能 只处理自己应该处理的内容

开闭原则  对拓展开放(可以拓展新功能) 对修改关闭(不允许修改源代码) 例如 子类继承父类后重写 新加功能尽量是加处理 而不是改代码

里氏替换原则  任何父类出现地方 子类都可以代替  父类容器装子类

依赖倒装原则  依赖抽象 不要依赖具体的实现 依赖抽象(接口)

迪米特原则 一个对象应当对其他对象 尽可能少的了解   降低耦合性  尽量少用别的类和自己关联

接口隔离原则  不应该强迫别人依赖他们不需要的方法 一个接口不需要提供太多的行为 应该尽量只提供一个对外的功能 不要一个接口n个行为

合成复用原则  尽量使用对象组合 而不是继承来达到复用的目的 尽量用组合复用的形式

UML

Unified Modeling Language

统一建模语言

ArrayList  尽量少用

ArrayList 是一个系统为我们封装好的类

它的本质是一个object类型的数组

ArrayList类帮助我们实现了很多方法

比如数组的增删查改

add   addrange

remove() 从头开始找 找到之后开始删

removeAt() 指定位置

Contains  查看元素是否存在   返回bool

indexof 正向查找元素的索引 返回 int

lastindexof 反向查找元素的索引

insert 插入

Count 长度

Capacity 容量

for遍历 和foreach遍历

因为是object数组 所以有时候要as出想要的对象来用

arraylist和数组的区别

arraylist本质是一个object数组

arraylist可以不用一开始就定长 单独使用数组是定长的

数组可以指定存储类型 arraylist是object类型

数组的增删需要我们自己实现 arraylist有api

arraylist使用的时候可能存在装箱拆箱 数组使用时 不是object数组就不存在这个问题

数组长度是length arraylist长度是 count

Stack 栈

在c# 里面栈是一个 object数组 封装了特殊的存储规则

栈存储容器 先进后出

增删改查

push

pop

peek 查看栈顶的内容

contains 栈里面是否存在

没有改的方法

clear 清空

stack.count 长度

没有提供索引器 所以不能用for遍历

只能用foreach 遍历 顺序也是从栈顶到栈底

还可以转成object数组 然后在遍历 ToArray();

队列

先进先出

本质也是一个object数组

enqueue 入队

dequeue 出队

没办法改 只能清空

哈希表 hashtable

散列表 是基于键的哈希代码组织起来的 键值对

它的主要作用是提高数据查询的效率

使用键来访问集合的元素

键值都是object类型的

不能出现相同的键

只能通过键去删除 remove

contains和containskey一样

containsValue 存在某个值

只能改键对应的值 没办法改值

Hashtable hashtable = new Hashtable();

foreach (object item in hashtable.Keys)

{

    Console.WriteLine("键",item);

    Console.WriteLine("值", hashtable[item]);

}

遍历出来是没有规律的

foreach (object item in hashtable.Values)

{

    Console.WriteLine("值", item);

}

泛型

泛型实现了类型参数化 达到代码重用目的

通过类型参数化来实现同一份代码操作多种类型

泛型相当于 类型占位符

定义类或者方法时使用替代符代表变量类型

当真正使用类或者方法时再具体指定类型

使用泛型可以一定程度避免装箱拆箱

Class xxx<T> //T可以是任意字母 建议是大写字母

interface xxx<T>

函数名 <T> (参数列表)

泛型占位字母可以有多个 用逗号分开

class Test<T>

{

    public T value;

    public void TestFun<T>(T value)

    {

        Console.WriteLine(value);

    }

}

Test<int> t = new Test<int>();

t.value = 1;

t.TestFun(1);

 public T TestFun2()

 {

     return default(T);//作为返回值

 }

public void TestFun3<T>()

{

    T value = default(T);//做逻辑处理

}

泛型约束

让泛型的类型有一定的约束

值类型     where T:struct //T是泛型字母

class Test<T> where T :struct

{

public T value;

public void TestFun<K>(K v) where K :struct{

}

}

Test<int> t2 = new Test<int>();

t2.value = 2;

t2.TestFun<float>(13.4f);

引用类型     where T:class //T是泛型字母

class Test3<T> where T : class

{

    public T value;

    public void TestFun<K>(K v) where K : class

    {

    }

}

Test3<object> t3 = new Test3<object>();

t3.value = new object();//其实什么都可以的 因为是object

t3.TestFun(new Random());

存在公共无参构造函数     where T:new() //T是泛型字母

class Test4<T> where T : new()//是这个T必须要有公共无参的构造函数

{

    public T value;

  

}

是指传进去的东西 必须有无参的构造函数

某个类本身或者其派生类     where T:类名 //T是泛型字母

class Test5

{

}

class Test6: Test5

{

}

class Test4<T> where T : Test5

{

    public T value;

  

}

Test4<Test5> t4 = new Test4<Test5>();

Test4<Test6> t5 = new Test4<Test6>();

某个接口的派生类型     where T:接口名 //T是泛型字母

    class Test8:IFly {

    }

    class Test7<T> where T:IFly

    {

    }

Test7<Test8> t7 = new Test7<Test8>(); //要传一个 是继承了这个接口的引用类型(类)

另一个泛型类型本身或者派生类型     where T:另一个泛型字母 //T是泛型字母

class Test10<T,U> where T : U

{

    //U是T的类型本身或者是T的派生类

    public void TestFunc<K,C>() where K:C

    {

        //C是K的类型本身或者是C的派生类

    }

}

约束的组合使用

class Test11<T> where T : Test9,new()

{

    //逗号隔开 多个约束联合

}

class Test12<T,K> where T : Test9, new() where K : new()

{

    //多个泛型反别有多个不同约束

}

用泛型实现一个单例基类

    class SingleBase

    {//最基础的单例

        private static SingleBase instance = new SingleBase();

        private SingleBase() {}

        public static SingleBase Instance { get { return instance; } }

    }

    class SingleBaseT<T> where T:new()//因为单例必须在基类里new出来 所以必须在此加泛型约束

    {//用泛型实现的单例 缺点是在外面可以new一个单例 但是作为程序员 应该知道 单例不能在外面new 后续有解决方法

        public static T instance = new T();

        public static T Instance { get { return instance; } }

    }

常用泛型数据结构类

List

List的本质是一个可变类型的泛型数组

List类帮助我们实现了很多方法

如增删查改 跟arraylist的方法一样

遍历 foreach

Dictionary

Dictionary 是一个拥有泛型的Hashtable    很像hashtable

她也是基于键的哈希代码组织起来的键/值对

键值对类型从Hashtable的object变为了可以自己制定的泛型

值可以一样 键不能一样

通过键查看值

dict[2]

dict["2"]

是否存在

containsKey  //通过键查找

containsValue //通过值查找

遍历 foreach

Dictionary<int, string> dict = new Dictionary<int, string>();

            foreach (int item in dict.Keys) { }

            foreach (string item in dict.Values) { }

 foreach (KeyValuePair<int, string> item in dict) { Console.WriteLine(item.Key+item.Value);}

还有迭代器

顺序存储和链式存储

线性表 是一种线性结构 是由n个具有相同特性的数据元素的有限序列

比如数组 arraylist stack queue 链表

顺序存储 用一组连续的存储单元依次存储线性表各个数据元素 stack queue list arraylist 数组

只要是数组 都是顺序存储 没办法更改容量

链式存储 也叫链接存储

单向链表 双向链表 循环链表

用一组任意的存储单元存储线性表中的各个数据元素

单向链表 像火车头 有个钩子 钩着车厢 下一个车厢又有个钩子勾着下一个车厢

双向链表 有两个钩子

单向链表

class LinkedNode<T>

{

    public T value;

    public LinkedNode() {

    }

    public LinkedNode(T value)

    {

        this.value = value;

    }

    public LinkedNode<T> nextNode;

}

class LinkedList<T>

{

    public LinkedNode<T> head;

    public LinkedNode<T> last;

    public void Add(T value)

    {

        LinkedNode<T> node = new LinkedNode<T>(value);

        if (head == null)

        {

            head = node;

            last = node;

        }

        else

        {

            last.nextNode = node;

            last = node;

        }

    }

    public void Remove(T value) {

        if (head == null)

        {

            return;

        }

        if (head.value.Equals(value))

        {

            head = head.nextNode;

            if (head == null)

            {

                last = null;

            }

        }

        LinkedNode<T> node = head;

        while (node.nextNode != null)

        {

            if (node.nextNode.value.Equals(value))

            {

                node.nextNode = node.nextNode.nextNode;

                break;

            }

        }

    }

}

顺序存储和链式存储的优缺点

增:链式存储 计算上 优于顺序存储 中间插入时链式不用像顺序一样移动位置

删:链式存储 计算上 优于顺序存储 中间删除时链式不用像顺序一样移动位置

改 顺序存储 使用上 优于链式存储 数组可以直接通过下标得到元素 链式需要遍历

查 顺序存储 使用上 优于链式存储 数组可以直接通过下标得到元素 链式需要遍历

双向链表

class LinkedNode<T>

{

    public T value;

    public LinkedNode<T> frontNode;

    public LinkedNode<T> nextNode;

   

    public LinkedNode(T value){

        this.value = value;

    }

}

class LinkedList<T>

{

    private int count;

    private LinkedNode<T> head;

    private LinkedNode<T> tail;

    public int Count { get;set;}

    public LinkedNode<T> Head

    {

        get

        {

            return head;

        }

    }

    public LinkedNode<T> Tail

    {

        get

        {

            return tail;

        }

    }

    public void Add(T value)

    {

        LinkedNode<T> node = new LinkedNode<T>(value);

        if (head == null)

        {

            head = node;

            tail = node;

        }

        else

        {

            //顺序别搞错了

            tail.nextNode = node;

            node.frontNode = tail;

            tail = node;

        }

        ++count;

    }

    public void RemoveAt(int index)

    {

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

        {

            return;

        }

        int tempCount = 0;

        LinkedNode<T> tempNode = head;

        while (true)

        {

            if (tempCount == index)

            {

                if (tempNode.frontNode != null)

                {

                    tempNode.frontNode.nextNode = tempNode.nextNode;

                }

                if (tempNode.nextNode != null)

                {

                    tempNode.nextNode.frontNode = tempNode.frontNode;

                }

                if (index == 0)

                {

                    head = head.nextNode;

                }

                if (index == count - 1)

                {

                    tail = tail.frontNode;

                }

                break;

            }

            ++tempCount;

            tempNode = tempNode.nextNode;

        }

     }

}

LinkedList

LinkedList本质是一个可变类型的泛型双向链表

尾增

addlast

头增

addFirst

某节点后

addafter(node,value)

addbefore(node,value)

removefirst 移除头

removelast 移除尾

remove(value) value是节点里的值

clear 清空列表

.First 头节点

.Last 尾节点

遍历找节点

find(value)value是节点里的值

contains  是否存在

先得到节点 然后再改变值

泛型栈和泛型队列

跟栈和队列差不多

数组 list dictionary stack queue linkedlist

如何选择

数组 list linkedlist

数组固定不变的一组数据

list 经常改变 经常通过下标查找

linkedlist 不确定长度 经常插入 查找不多

stack 一些适用先进后出的规则的数据

queue 一些适用 先进先出的规则的数据

键值对 需要频繁查找的 有对应关系的数据

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/391343
推荐阅读
相关标签
  

闽ICP备14008679号