赞
踩
冒泡
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 一些适用 先进先出的规则的数据
键值对 需要频繁查找的 有对应关系的数据
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。