• 13539426682
  • liuyixi2009@163.com

月度归档三月 2019

操作系统:精髓(三)

第三章

进程状态:五状态模型

  • 运行态
  • 就绪态:当操作系统准备好再接纳一个进程时,把一个进程从新建态转换到就绪态。
  • 阻塞/等待态
  • 新建态:通常是进程控制块已经创建但是还未加载到内存中的新内存。
  • 退出态

单阻塞队列和多阻塞队列:

单阻塞队列时,当一个事件发生时,操作系统必须扫描整个阻塞队列,搜索那些等待该事件的进程,这样子效率就会很低,一种改进方法就是维护多个队列,每个事件对应一个队列(比如打印机就绪了这个事件A对应队列1),另一种改进方法就是按照优先级方案分配进程,这样操作系统很容易确定哪个就绪程序具有最高优先级且等待时间最长。

包含两个挂起态的模型

最左边的两个状态代表了这些进程在外存那里同时在“监听”事件发生,若事件发生了则这个在外存的进程从阻塞态变成挂起态,等待被加载进内存。而中间的“就绪 –> 就绪/挂起”过程,是因为假如释放内存来获得足够空间的唯一方法就是挂起一个就绪态进程,则这种转换也是必须的。或者为了保护高优先级的阻塞状态进程,系统可能会选择挂起一个低优先级的就绪进程。同理在下面的阻塞换入也是这样,把优先级高的先换进去。

进程控制结构:

1.进程位置:这里先引用一个概念叫进程映像,它由用户数据,用户程序,栈,进程数据块组成,它是进程的物理表示(就是描述一个进程要用的东西)。操作系统要跟踪每个进程映像的哪个部分仍然在内存中,同时操作系统维护的进程表必须给出每个进程映像中的每页的位置。

2.进程属性:进程标识符(用户ID),处理器状态信息,进程控制信息(优先级)

进程切换:

中断分为两种,中断和陷阱,前者是与当前正在运行进程无关的某种外部事件相关,如完成一次I/O操作,而后者则是与进程产生的错误或者异常条件相关。

1.时钟中断,如果进程的执行时间超过了最大允许时间段

2.I/O中断

3.内存失效,在内存中找不到要的东西,就会发出调入内存块的I/O请求,内存失效进程将进入阻塞态。操作系统切换进程

进程切换和模式切换的不同:

当进程调用系统调用或者发生中断时,CPU从用户模式(用户态)切换成内核模式(内核态),此时,无论是系统调用程序还是中断服务程序,都处于当前进程的上下文中,并没有发生进程上下文切换。
当系统调用或中断处理程序返回时,CPU要从内核模式切换回用户模式,此时会执行操作系统的调用程序。如果发现就需队列中有比当前进程更高的优先级的进程,则会发生进程切换:当前进程信息被保存,切换到就绪队列中的那个高优先级进程;否则,直接返回当前进程的用户模式,不会发生上下文切换。

每个进程都拥有两个堆栈:用户空间的堆栈和内核空间堆栈
用户进程:执行用户空间的代码的程序,使用用户堆栈
系统进程:执行内核空间代码(系统调用或中断)的程序,使用内核堆栈(系统堆栈)
这里的用户进程和系统进程使用同一个PCB,他们并不是两个实体进程,而是同一个进程的两个侧面。当调用系统调用或发生中断时,CPU切换到内核态,用户进程“变身”系统进程,此时的寄存器上下文保存在系统进程的堆栈上,以便系统调用返回后的恢复。

Unix SVR4 进程管理

进程状态:Unix操作系统中共有九种状态,值得一提的是有一个状态叫抢占态。只有在进程准备从内核模式转换到用户模式时才可能发生抢占。

进程描述:分为用户级上下文和系统级上下文和寄存器上下文

进程创建:fork()

Arduino:串口的使用

输出

 

I LOVE YOU (单片机)

 

备战黑框框:(一)

小球的反复横跳:

打飞机:

 

来自新世界:输入,输出和文件

金字塔输出hello

设置空格(只对下一个有效)

设置填充字符

显示全部数字

 

来自新世界:标准模板库

什么是STL?

STL可分为容器(containers)、迭代器(iterators)、空间配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函数(functors)六个部分

 

引入:模板类vector

这里建立了一个类型为int,名字是rating的vector类,这个rating里面有两个int元素(其实就是数组)

但是这个vector类对外有接口,比如begin()方法返一个指向容器中第一个元素的迭代器,迭代器是什么?它可以是指针,可对其执行类似指针的操作(解除引用,递增),它存在的意义就是让STL能够为各种各样不同的容器类提供统一的接口。迭代器的类型是一个名字为iterator的typedef,作用域为整个类。

还有其他用法:

下面就来讲讲喜闻乐见的sort()函数,以前特别讨厌去写排序,一直想着去用sort去解决问题,那时都是用sort去对一个int/double数组进行排序,然鹅怎么对对象进行排序呢?

就是重载<,或者重新定义一个函数然后把函数当成参数传进sort()里面去

泛型编程

1.迭代器:连接容器和算法的一种重要桥梁,迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。

迭代器可以分为一下5种:

  • 输入迭代器:仅仅读。支持++、==、!=
  • 输出迭代器:仅仅写,支持++
  • 前向迭代器:读写,支持++、==、!=
  • 双向迭代器:读写,支持++、–
  • 随机訪问迭代器:读写,支持++、–、[n]、-n、<、<=、>、>=

 

前文提到像begin()方法,find()方法它也会返回一个迭代器,我们来看一下它的函数原型:

 

 

这里声明了find方法要传进一个输入迭代器,

我们可以看出,find是一种算法,vector是一种容器里面可以塞任何东西,当需要把算法应用于容器时,我们为了控制,才使用迭代器这个东西,可以看出不同的迭代器有着不同的权限,这也要求我们要根据算法的需求来选择迭代器。比如查找算法只需要定义++运算符和读取数据(假如查找算法可以修改数据将会变得很蛋疼)

 

sort算法使用双向迭代器对vector里面的数据进行修改与排序

 

 

事实上,迭代器是广义上的指针,指向int的常规指针是一个随机访问迭代器模型。

再来讲讲其他有用的迭代器

1.反向类迭代器,vector里面有一个rbegin和rend方法,返回的值分别和end与begin方法返回的值相同,但是前两者返回的迭代器的类型是reverse_iterator而不是iterator

2.插入迭代器(预定义的),在通过copy来把数据插到容器中时,很有可能会覆盖掉前面的数据,那么这时就要把特殊的迭代器传进容器中了

 

顺带一提copy可以:

  1. 将信息从一个容器复制到另外一个容器中
  2. 把数据从容器中复制到输出流
  3. 把信息从一个容器中插入到另外一个容器中

我们再来回顾一下前面的那一张图,我们可以发现,copy作为一种独立的算法,通过传给它不同的迭代器,可以实现对不同种类的容器的不同操作(输出,复制,插入)。

 

2.容器

list vector 什么的,还有关联容器set map pair,看的脑子疼就不计下来了

那个map和pair可以搭配使用

我感觉有点用的核心语句:

 

函数对象

函数对象是可以以函数方式与()结合使用的任意对象,包括:

1)函数名;

2)指向函数的指针;

3)重载了()操作符的类对象(即定义了函数operator()的类)

那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求——函数指针无法和STL其他组件(如配接器adapter)搭配,产生更灵活的变化。同时,函数指针无法保存信息,而仿函数可以。

 

。。。

 

看不懂,可能是直接用函数对象比较方便吧,比如:



 

这里可以发现,要用函数指针,先要这样子写一大串东西,然后才能传进去,但是用类的话会方便一点,直接实例化A<int>/<double>/<string>就完事了,然后还可以保存信息什么的,扩展性很高,但是占用内存更大和效率会比函数指针要慢得多。

 

其他库

1.vector,valarray,array

2.模板initializer_list:就是可以使用初始化列表语法来初始化STL容器,因为容器类现在包含将initializer_list<T>作为参数的构造函数。例如vector<double>包含一个将initializer_list<double>

作为参数的构造函数。

数据可以进行有必要的转化但不能隐式的窄化转化

 

总结

STL是一个容器类模板,迭代器模板,函数对象模板和算法模板的集合,他们的设计都是基于泛编程原则的。

  • 诸如vector和set等容器类是容器的概念(容器,序列,关联容器)的模型
  • 有一些算法被表示为容器的类方法,但是大量算法都被表示为通用的,非成员函数的,这是通过将迭代器作为容器和算法之间的接口得以实现的(把迭代器(这个类)给传进去,比如要把数据复制到输出流就传一个ostream_iterator,这样就把数据复制到输出流中。比如只是单纯的复制的话就传一个输入迭代器就成)。还有一个优点就是,我们只需要一个诸如copy(),for_each()这样的函数就可以了而不需要为每一个容器提供不同的版本。另外一个优点就是STL算法可以用于非STL容器,如常规数组,string对象。
  • 上面可以看到,算法对容器是有要求的,容器必须支持算法所需的迭代器,不然就要提供一个专门的方法。
  • STL还提供了函数对象,函数对象是重载了()运算符的类,可以用函数的表示方法来调用这种类的对象,同时可以携带更多的信息。

来自新世界:智能指针

不会发生内存泄漏的东西

智能指针的三个模板:auto_ptr  unique_ptr shared_ptr

当智能指针过期时,其析构函数会自动调用delete来释放内存。

注意所有指针都有一个explicit的构造函数,因此不能这样做:

事实上,我们可以发现,在编译文件时,vs code会发出警告,那是因为C++11已经要抛弃这个auto_ptr了。

为啥呢?

这个是因为有一个东西叫所有权,当一个智能指针赋值给了另外一个时,原来的就会放弃它的所有权,因此再次用原来的智能指针访问数据时就会出错。(我们称为“悬挂指针”)

怎么办?

可以把auto_ptr换成shared_ptr或者unique_ptr,前者的机制是当有多个指针指向同一对象时,引用计数增加,在析构时(先析构)那个最后声明的指针,其析构函数把计数器数减一,直到计数器降低到0时才会释放分配的空间。后者的机制是在使用等于号那一步编译器就报错了。但其实也不是不能通过等于号来给unique_ptr指针赋值,如果给它赋值的是临时右值,就不会报错,因为反正这个右值它很快就会被销毁了。

事实上也可以单纯的用等号,右值也不用是临时变量,在要转移的指针上加个move方法就行

怎么选择智能指针

如果有多个指针指向同一个元素,比如有很多个对象,但是每个对象里面都有两个指针,指向某个最大值和最小值,这时就要用shared_ptr

来自新世纪:打开文件

 

来自新世界:RTTI和类型转换运算符

RTTI元素

  • dynamic_cast 运算符将使用一个指向基类的指针来生成一个指向派生类的指针。否则返回一个空指针。
  • typeid 运算符返回一个指出对象的类型的值
  • type_info 结构储存了有关特定类型的信息

上代码:

结果是:

 

dynamic_cast在将父类cast到子类时,父类必须要有虚函数!!

具体有什么用呢?

其实就是基类指针可以指向它的派生类,但是有时候通过基类指针会调用到基类没用的方法,这时我们就可以用这个转换去通过使用一个基类指针来生成一个派生类指针再赋给其他指针,从而进行灵活的运用而不是通过typeid 和 if else 来判断这个基类指针指向的到底是什么类,再把操作写进去

类型转换运算符

上面说的dynamic_cast就是一种,而其他的还有像const_cast可以通过指针改变被const指针指向的值:

但是如果对象本来就是const,那终究是修改不了的。

来自新世界:关于子类父类的同名函数

之前我一直以为若子类的公有函数要和父类同名只能加个virtual,事实上不是的:

 

 

这时我们可以发现,即使基类指针指向派生类,调用的依旧是父类的函数,而非像加了virtual那样子的调用子类版本的show(),但是当指针被强制转换为子类指针时才可以调用子类的函数。想要用基类指针调用不同的版本的函数,就要在父类的show()前加一个virtual。

然鹅我突然想到,为什么这两个show函数不会冲突呢?其实这两个函数不在同一个作用域里面,C++会先去子类中搜索要用的函数,找不到再去父类里面找,因此如果要在子类对象里面调用父类函数,可以在子类对象中使用解析符 :: 去调用基类方法。

最后来个汇总:

结果:

 

 

(第二个由于没有默认构造一个B类,也就是没用构造好B类的部分,因此B类的私有成员值会变成一串无意义的数字)