一、进程和线程
1.什么是进程?
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,简单理解就是我们每打开一个应用程序就是在操作系统上开启了一个进程。
2.什么是线程
操作系统能够进行运算调度的最小单位,包含在进程中,是进程中的实际运作单位,一个线程指的是进程中的单一顺序的控制流,一个进程可以并发多个线程,我们目前写的程序都在主线程中,简单理解就是代码从到下运行的一条“管道”
3.什么是多线程
我们可以通过代码开启新的线程,可以同时运行多条“管道”,运行多个线程
4.申明一个新的线程
开辟一个新的线程用的是线程了Thread,需要引用using System.Threading命名空间,开辟的线程的代码逻辑是放到传进来的函数中的,所以申明一个线程需要传入一个函数。
5.启动线程
默认启动的线程是前台线程,也就是会影响主线程结束的时机,默认是要等前台线程结束后,主线程才会结束,比如我们在新开线程中写一个死循环,这时候主线程中的代码运行结束了,本来应该要结束的,但是由于我们默认申明的是前台线程,所以必须等新开线程结束后主线程才会结束。
6.将线程设置为后台线程
设置为后台线程后,主线程代码如果运行结束会强制终止还在运行的后台线程
7.终止线程
我们可以在线程的代码中加一个循环,循环的判断条件是是我主线程中提供的一个布尔变量,当我们想要关闭线程的时候直接将该变量设置为false就可以了,这时候新线程中的代码就会跳出循环停止运行
8.线程休眠
让线程休眠多少毫秒,1s=1000毫秒,Thread.Sleep(填毫秒值),在哪个线程中执行就会休眠对应的线程,不会干扰到其他线程
9.线程之间共享数据
多个线程使用的内存是共享的,都属于该进程,所以当多个线程同时操作一片内存区域时可能会出现问题,
使用加锁lock的形式,将线程中的代码锁住,比如程序如果是在运行主线程中的代码,这时候会将lock中的代码进行加锁,也就是在其他加锁的地方必须等这边的代码执行完才会去执行另外一个线程中加锁的代码
10.线程练习题
控制台有一个■通过键盘上的↑,↓,←,→键控制方块的移动,通过开启一个多线程来检测输入控制它的转向,而且要判断边界,方块不能超出边界。
- 提示:检测键盘上的按键我们可以通过Console.ReadKey().key和ConsoleKey.按键名来判断输入的是哪个按键来改变对应方块移动的方向
using System.Collections;
namespace demo1028am
{
//创建一个方块类
public class Block
{
public Block(int x, int y)
{
this.x = x * 2;
this.y = y;
}
public int x;
public int y;
public ConsoleColor color = ConsoleColor.White;
//方块都有一个画的方法
public void draw()
{
Console.SetCursorPosition(this.x, this.y);
Console.ForegroundColor = color;
Console.Write("■");
}
//清除的方法
public void clear()
{
Console.SetCursorPosition(this.x, this.y);
Console.Write(" ");
}
}
//创建一个子类 头
public class Head : Block
{
public Head(int x, int y, Dir dir = Dir.right) : base(x, y)
{
this.dir = dir;
base.color = ConsoleColor.Red;
}
public Dir dir;
//移动的方法
public void move()
{
switch (dir)
{
case Dir.up:
y--;
break;
case Dir.down:
y++;
break;
case Dir.left:
x -= 2;
break;
case Dir.right:
x += 2;
break;
}
}
//碰撞检测的方法
public bool crash(Block block)
{
if (block.x == this.x && block.y == this.y)
{
return true;
}
return false;
}
}
//创建一个枚举来存方向
public enum Dir
{
up, down, left, right
}
internal class Program
{
public static object obj = new object();
public static int width = Console.BufferWidth;//屏幕宽度
public static int height = Console.BufferHeight;//屏幕高度
//创建一个不动方块对象
public static Block block = new Block(5, 5);
//创建一个头
public static Head head = new Head(0, 0);
//创建一个列表存每一个方块
public static List<Head> list = new List<Head>();
//更新不动的方块
public static void update()
{
Random random = new Random();
int x = random.Next(0, 10) * 2;
int y = random.Next(0, 10);
block.x = x;
block.y = y;
block.draw();
}
static void Main(string[] args)
{
Console.CursorVisible = false;
block.draw();
list.Add(head);
list[0].draw();
//创建一个线程
Thread T = new Thread(ThreadLogic);
T.Start();
T.IsBackground = true;
while (true)
{
Thread.Sleep(200);
//在方块的坐标还没改变之前先获取当前列表最后一个元素的坐标
int x = list[list.Count - 1].x;
int y = list[list.Count - 1].y;
list[0].clear();
//清除所有尾巴的x,y坐标
//变化的坐标
//所有尾巴要改变坐标为前面的方块的x,y坐标
for (int i=list.Count-1;i>=1;i--) //3个
{
list[i].clear();//清除原来的位置
//更新坐标为前一个方块没更新之前的坐标位置
list[i].x = list[i - 1].x;
list[i].y = list[i - 1].y;
}
list[0].move();
//在什么时机去判断碰撞是不是我现在头移动到下个位置刚好是不动方块的坐标
if (block.x==head.x&&block.y==head.y)
{
//添加新的方块到列表中 这个时候的坐标应该最后的方块上一次的位置
list.Add(new Head(x/2,y));//因为我们初始化的时候给x乘于二了,所以传进来的时候要除以二
//更新一下新的方块的位置
update();
}
for (int i =0; i <list.Count; i++)
{
list[i].draw();
}
}
}
static void ThreadLogic()
{
while (true)
{
switch (Console.ReadKey(true).Key)//检测用户输入的按键
{
case ConsoleKey.UpArrow:
//只有一个头随便跑 如果不是一个方块那么不能往反方向走
if (head.dir != Dir.down || list.Count == 1) head.dir = Dir.up;
break;
case ConsoleKey.DownArrow:
//只有一个头随便跑 如果不是一个方块那么不能往反方向走
if (head.dir != Dir.up || list.Count == 1) head.dir = Dir.down;
break;
case ConsoleKey.LeftArrow:
//只有一个头随便跑 如果不是一个方块那么不能往反方向走
if (head.dir != Dir.right || list.Count == 1) head.dir = Dir.left;
break;
case ConsoleKey.RightArrow:
//只有一个头随便跑 如果不是一个方块那么不能往反方向走
if (head.dir != Dir.left || list.Count == 1) head.dir = Dir.right;
break;
}
}
}
}
}
二、事件
1.事件是什么
事件的本质其实是对委托的封装,是委托的安全包裹,只能在类内部调用和赋值,让委托的使用更具有安全性
2.事件的使用
- 声明事件
- 事件和委托的区别
- 事件总结
练习
有一个热水器类,里面申明了一个事件myEvent,委托的类型为无返回值,形参为小数(温度值),还有温度的初始值为0度,还有一个报警器类,有一个showInfo的方法会打印出当前温度为xx度,最后还有一个显示器类,也有一个showInfo方法,打印了当前水已烧开,热水器类中有一个加热的方法每隔200毫秒给給水加一度,当水到95度时显示器和报警器中的方法开始调用(使用事件调用)
三、几种排序补充
1.插入排序
- 原理
将数组中的数分为左边排序区和右边的未排序区,每次拿未排序中的第一个值和排序区中的值进行对比,如果比排序区中的值大则交换两个的值然后再继续往前判断直到判断到左边的值没有比右边的值大的时候,那么这个位置就是刚才未排序区第一个值在排序区中的位置,接下来继续判断下一个未排序区的值一次类推直到最后一个值
namespace 插入排序
{
internal class Program
{
static void Main(string[] args)
{
//插入排序分为排序区和未排序区,排序区从第一个元素开始
int[] newArray = new int[] {9,10,34,21,0,3,1};
int temp = newArray[0];
//从未排序区的第一个元素开始遍历,每一次确定一个未排序区中的值,
//找到它在排序区中合适的位置然后插入
for (int i=1;i<newArray.Length;i++)
{
for (int j=i;j>0;j--)//拿当前第i个的值和排序区中的值进行比对找到合适的位置
{
if (newArray[j - 1] < newArray[j]) break;
//如果比左边的值要小交换值
temp = newArray[j];
newArray[j] = newArray[j - 1];
newArray[j - 1] = temp;
}
}
Console.WriteLine(String.Join(" ",newArray));
}
}
}
2.希尔排序
- 原理
namespace ConsoleApp8
{
internal class Program
{
static void Main(string[] args)
{
int[] intArray = new int[] {23 , 3 , 45, 6 , 10, 8 , 1};
int temp = intArray[0];
for (int step=intArray.Length/2;step>=1;step/=2)//步长的一个变化
{
//每一轮进行步长为step的插入排序
for (int start=step;start<intArray.Length;start++)
{
//要拿当前未排序区的每一个值和左边的值进行对比,而且是每隔一个步长为step进行对比
for (int s=start;s>=step;s-=step)
{
//如果左边比右边大交换值
if (intArray[s] < intArray[s-step]) //s-step>=0
{
temp = intArray[s];
intArray[s] = intArray[s - step];
intArray[s - step] = temp;
}
}
}
}
Console.WriteLine(String.Join(" ",intArray));
}
}
}