精华内容
下载资源
问答
  • python Pygame的具体使用讲解

    千次阅读 多人点赞 2020-05-19 22:59:02
    这里解释一下上面程序的运行方式 一个游戏循环(也可以称为主循环)就做下面这三件事: 处理事件 更新游戏状态 绘制游戏状态到屏幕上 2 绘制图形 Pygame的坐标原点(0,0)点位于左上角,X轴自左向右,Y轴自上向下...

    源码及资料下载:http://labfile.oss.aliyuncs.com/courses/940/foundation.zip

    1.HelloWorld

    # -*- coding: UTF-8 -*-
    # helloworld.py
    
    # 导入所需的模块
    import pygame, sys
    # 导入所有pygame.locals里的变量(比如下面大写的QUIT变量)
    from pygame.locals import *
    
    
    # 初始化pygame
    pygame.init()
    
    # 设置窗口的大小,单位为像素
    screen = pygame.display.set_mode((500, 400))
    
    # 设置窗口标题
    pygame.display.set_caption('Hello World')
    
    # 程序主循环
    while True:
    
      # 获取事件
      for event in pygame.event.get():
        # 判断事件是否为退出事件
        if event.type == QUIT:
          # 退出pygame
          pygame.quit()
          # 退出系统
          sys.exit()
    
      # 绘制屏幕内容
      pygame.display.update()
    
    

    效果图如下:

     

    这里解释一下上面程序的运行方式

    一个游戏循环(也可以称为主循环)就做下面这三件事:

    1. 处理事件
    2. 更新游戏状态
    3. 绘制游戏状态到屏幕上

     

    2 绘制图形

    Pygame的坐标原点(0,0)点位于左上角,X轴自左向右,Y轴自上向下,单位为像素。

    这里介绍一下常用的方法:

    pygame.draw.line(Surface, color, start_pos, end_pos, width)此方法用于绘制一条线段

    pygame.draw.aaline(Surface, color, start_pos, end_pos, blend)此方法用于绘制一条抗锯齿的线

    pygame.draw.lines(Surface, color, closed, pointlist, width)此方法用于绘制一条折线

    pygame.draw.rect(Surface, color, Rect)此方法用于绘制一个矩形

    pygame.draw.rect(Surface, color, Rect, width)此方法用于绘制一个矩形框

    pygame.draw.ellipse(Surface, color, Rect)此方法用于绘制一个椭圆

    pygame.draw.ellipse(Surface, color, Rect, width)此方法用于绘制一个椭圆框

    pygame.draw.polygon(Surface, color, pointlist, width)此方法用于绘制一个多边形

    pygame.draw.arc(Surface, color, Rect, start_angle, stop_angle, width)此方法用于绘制一条弧线

    pygame.draw.circle(Surface, color, Rect, radius)此方法用于绘制一个圆

    以下为示例代码:

    # -*- coding: UTF-8 -*-
    # drawing.py
    
    # 导入需要的模块
    import pygame, sys
    from pygame.locals import *
    from math import pi
    
    
    # 初始化pygame
    pygame.init()
    
    # 设置窗口的大小,单位为像素
    screen = pygame.display.set_mode((400,300))
    
    # 设置窗口标题
    pygame.display.set_caption('Drawing')
    
    # 定义颜色
    BLACK = ( 0, 0, 0)
    WHITE = (255, 255, 255)
    RED = (255, 0, 0)
    GREEN = ( 0, 255, 0)
    BLUE = ( 0, 0, 255)
    
    # 设置背景颜色
    screen.fill(WHITE)
    
    # 绘制一条线
    pygame.draw.line(screen, GREEN, [0, 0], [50,30], 5)
    
    # 绘制一条抗锯齿的线
    pygame.draw.aaline(screen, GREEN, [0, 50],[50, 80],True)
    
    # 绘制一条折线
    pygame.draw.lines(screen, BLACK, False, 
             [[0, 80], [50, 90], [200, 80], [220, 30]], 5)
    
    # 绘制一个空心矩形
    pygame.draw.rect(screen, BLACK, [75, 10, 50, 20], 2)
    
    # 绘制一个矩形
    pygame.draw.rect(screen, BLACK, [150, 10, 50, 20])
    
    # 绘制一个空心椭圆
    pygame.draw.ellipse(screen, RED, [225, 10, 50, 20], 2)
    
    # 绘制一个椭圆
    pygame.draw.ellipse(screen, RED, [300, 10, 50, 20])
    
    # 绘制多边形
    pygame.draw.polygon(screen, BLACK, [[100, 100], [0, 200], [200, 200]], 5)
    
    # 绘制多条弧线
    pygame.draw.arc(screen, BLACK,[210, 75, 150, 125], 0, pi/2, 2)
    pygame.draw.arc(screen, GREEN,[210, 75, 150, 125], pi/2, pi, 2)
    pygame.draw.arc(screen, BLUE, [210, 75, 150, 125], pi,3*pi/2, 2)
    pygame.draw.arc(screen, RED, [210, 75, 150, 125], 3*pi/2, 2*pi, 2)
    
    # 绘制一个圆
    pygame.draw.circle(screen, BLUE, [60, 250], 40)
    
    # 程序主循环
    while True:
    
      # 获取事件
      for event in pygame.event.get():
        # 判断事件是否为退出事件
        if event.type == QUIT:
          # 退出pygame
          pygame.quit()
          # 退出系统
          sys.exit()
    
      # 绘制屏幕内容
      pygame.display.update()
    
    

    效果图如下:

    3 实现动画

    由于人类眼睛的特殊生理结构,当所看画面的帧率高于24的时候,就会认为是连贯的,此现象称之为 视觉暂留 。

    帧率(Frame rate)是用于测量显示帧数的量度,所谓的测量单位为每秒显示帧数(Frames per Second,简称:FPS)

    一般来说30fps是可以接受的,但是将性能提升至60fps则可以明显提升交互感和逼真感,但是一般来说超过75fps一般就不容易察觉到有明显的流畅度提升了。

    在我们原有坐标系的基础上添加偏移量,再重新绘制,依次一张一张的循环绘制下去,就会得到我们想要的物体移动的效果。

    Pygame实现动画主要用到的方法:

    pygame.image.load(filename)加载一张图片

    pygame.Surface.blit(source, dest, area=None, special_flags = 0)将图片绘制到屏幕相应坐标上(后面两个参数默认,可以不传)

    pygame.time.Clock()获得pygame的时钟

    pygame.time.Clock.tick(FPS)设置pygame时钟的间隔时间

    以下为示例代码:

    # -*- coding: UTF-8 -*-
    # animation.py
    
    # 导入需要的模块
    import pygame, sys
    from pygame.locals import *
    
    # 初始化pygame
    pygame.init()
    
    # 设置帧率(屏幕每秒刷新的次数)
    FPS = 30
    
    # 获得pygame的时钟
    fpsClock = pygame.time.Clock()
    
    # 设置窗口大小
    screen = pygame.display.set_mode((500, 400), 0, 32)
    
    # 设置标题
    pygame.display.set_caption('Animation')
    
    # 定义颜色
    WHITE = (255, 255, 255)
    
    # 加载一张图片(所用到的的图片请参考1.5代码获取)
    img = pygame.image.load('resources/shiyanlou.PNG')
    
    # 初始化图片的位置
    imgx = 10
    imgy = 10
    
    # 初始化图片的移动方向
    direction = 'right'
    
    # 程序主循环
    while True:
    
      # 每次都要重新绘制背景白色
      screen.fill(WHITE)
    
      # 判断移动的方向,并对相应的坐标做加减
      if direction == 'right':
        imgx += 5
        if imgx == 380:
          direction = 'down'
      elif direction == 'down':
        imgy += 5
        if imgy == 300:
          direction = 'left'
      elif direction == 'left':
        imgx -= 5
        if imgx == 10:
          direction = 'up'
      elif direction == 'up':
        imgy -= 5
        if imgy == 10:
          direction = 'right'
    
      # 该方法将用于图片绘制到相应的坐标中
      screen.blit(img, (imgx, imgy))
    
      for event in pygame.event.get():
        if event.type == QUIT:
          pygame.quit()
          sys.exit()
    
      # 刷新屏幕
      pygame.display.update()
    
      # 设置pygame时钟的间隔时间
      fpsClock.tick(FPS)
    
    

     

    效果图如下:

    4 绘制文字 

    如果你想绘制文字到屏幕上,Pygame提供了很方便的方法使用.ttf字体文件,这样我们就能很轻易的将文字绘制在屏幕上了。

    这里我使用了ARBERKLEY.ttf作为字体,字体文件的获取请参考1.5代码获取。

    主要用到的方法:

    pygame.font.Font(filename, size)

    filename:字体文件的文件名;

    size:字体的高height,单位为像素;

    pygame.font.Font.render(text, antialias, color, background=None)

    text:要显示的文字;

    antialias: 是否抗锯齿;

    color:字体颜色;

    background:背景颜色(可选参数);

    .get_rect()

    获得一个对象的rect,以便于设置其坐标位置

    以下为示例代码:

    # -*- coding: UTF-8 -*-
    # font.py
    
    # 导入需要的模块
    import pygame, sys
    from pygame.locals import *
    
    # 初始化pygame
    pygame.init()
    
    # 设置窗口的大小,单位为像素
    screen = pygame.display.set_mode((500,400))
    
    # 设置窗口的标题
    pygame.display.set_caption('Font')
    
    # 定义颜色
    WHITE = (255, 255, 255)
    GREEN = ( 0, 255, 0)
    BLUE = ( 0, 0, 128)
    
    # 通过字体文件获得字体对象
    fontObj = pygame.font.Font('resources/ARBERKLEY.ttf', 50)
    
    # 配置要显示的文字
    textSurfaceObj = fontObj.render('Pygame', True, BLUE, GREEN)
    
    # 获得要显示的对象的rect
    textRectObj = textSurfaceObj.get_rect()
    
    # 设置显示对象的坐标
    textRectObj.center = (250, 200)
    
    # 设置背景
    screen.fill(WHITE)
    
    # 绘制字体
    screen.blit(textSurfaceObj, textRectObj)
    
    # 程序主循环
    while True:
    
      # 获取事件
      for event in pygame.event.get():
        # 判断事件是否为退出事件
        if event.type == QUIT:
          # 退出pygame
          pygame.quit()
          # 退出系统
          sys.exit()
    
      # 绘制屏幕内容
      pygame.display.update()
    
    

    效果图如下:

     

     

    5.播放音频

    在Pygame里播放音频有两个方法,一个用来播放特效声音,一个用来播放背景音乐:

    pygame.mixer.Sound(filename)

    该方法返回一个Sound对象,调用它的.play( )方法,即可播放较短的音频文件(比如玩家受到伤害、收集到金币等);

    pygame.mixer.music.load(filename)

    该方法用来加载背景音乐,之后调用pygame.mixer.music.play( )方法就可以播放背景音乐(Pygame只允许加载一个背景音乐在同一个时刻)

    以下为示例代码:

    # -*- coding: UTF-8 -*-
    # audio.py
    
    # 导入需要的模块
    import pygame, sys
    from pygame.locals import *
    
    # 初始化pygame
    pygame.init()
    
    # 设置窗口的大小,单位为像素
    screen = pygame.display.set_mode((500,400))
    
    # 设置窗口的标题
    pygame.display.set_caption('Audio')
    
    # 定义颜色
    WHITE = (255, 255, 255)
    
    # 设置背景
    screen.fill(WHITE)
    
    # 加载并播放一个特效音频文件(所用到的音频文件请参考1.5代码获取)
    sound = pygame.mixer.Sound('resources/bounce.ogg')
    sound.play()
    
    # 加载背景音乐文件
    pygame.mixer.music.load('resources/bgmusic.mp3')
    
    # 播放背景音乐,第一个参数为播放的次数(-1表示无限循环),第二个参数是设置播放的起点(单位为秒)
    pygame.mixer.music.play(-1, 0.0)
    
    # 程序主循环
    while True:
    
      # 获取事件
      for event in pygame.event.get():
        # 判断事件是否为退出事件
        if event.type == QUIT:
          # 停止播放背景音乐
          pygame.mixer.music.stop()
          # 退出pygame
          pygame.quit()
          # 退出系统
          sys.exit()
    
      # 绘制屏幕内容
      pygame.display.update()
    
    

    6.事件

    Pygame里常用的事件如下表:

    事件产生途径参数
    QUIT用户按下关闭按钮none
    ACTIVEEVENTPygame被激活或者隐藏gain, state
    KEYDOWN键盘被按下unicode, key, mod
    KEYUP键盘被放开key, mod
    MOUSEMOTION鼠标移动pos, rel, buttons
    MOUSEBUTTONDOWN鼠标按下pos, button
    MOUSEBUTTONUP鼠标放开pos, button
    VIDEORESIZEPygame窗口缩放size, w, h

    以下为示例代码:

    # -*- coding: UTF-8 -*-
    # event.py
    
    # 导入需要的模块
    import pygame, sys
    from pygame.locals import *
    
    # 定义颜色
    WHITE = (255, 255, 255)
    
    # 初始化pygame
    pygame.init()
    
    # 设置窗口的大小,单位为像素
    screen = pygame.display.set_mode((500,400), 0, 32)
    
    # 设置窗口的标题
    pygame.display.set_caption('Event')
    
    # 设置背景
    screen.fill(WHITE)
    
    # 程序主循环
    while True:
    
      # 获取事件
      for event in pygame.event.get():
        # 判断事件是否为退出事件
        if event.type == QUIT:
          # 退出pygame
          pygame.quit()
          # 退出系统
          sys.exit()
    
        # 获得鼠标当前的位置  
        if event.type ==MOUSEMOTION:
          print(event.pos)
    
        # 获得鼠标按下的位置
        if event.type ==MOUSEBUTTONDOWN:
          print("鼠标按下:",event.pos)
    
        # 获得鼠标抬起的位置
        if event.type ==MOUSEBUTTONUP:
          print("鼠标抬起:",event.pos) 
    
        # 获得键盘按下的事件  
        if event.type == KEYDOWN:
          if(event.key==K_UP or event.key==K_w):
            print("上")
          if(event.key==K_DOWN or event.key==K_s):
            print("下")
          if(event.key==K_LEFT or event.key==K_a):
            print("左")
          if(event.key==K_RIGHT or event.key==K_d):
            print("右")
          # 按下键盘的Esc键退出
          if(event.key==K_ESCAPE):
            # 退出pygame
            pygame.quit()
            # 退出系统
            sys.exit()
    
      # 绘制屏幕内容
      pygame.display.update()
    
    

     效果图如下:

    若想要深入了解,可参考下方Pygame官方文档的链接。

    https://www.pygame.org/docs/

    展开全文
  • Socket原理讲解

    万次阅读 多人点赞 2019-07-18 15:50:36
    创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动...

    对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:

    1.         什么是TCP/IP、UDP?
    2.         Socket在哪里呢?
    3.         Socket是什么呢?
    4.         你会使用它们吗?

    什么是TCP/IP、UDP

             TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
             UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
            这里有一张图,表明了这些协议的关系。

                                                                                    

                                                                            图1

           TCP/IP协议族包括运输层、网络层、链路层。现在你知道TCP/IP与UDP的关系了吧。
    Socket在哪里呢?
           在图1中,我们没有看到Socket的影子,那么它到底在哪里呢?还是用图来说话,一目了然。



    图2

           原来Socket在这里。
    Socket是什么呢?
           Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
    你会使用它们吗?
           前人已经给我们做了好多的事了,网络间的通信也就简单了许多,但毕竟还是有挺多工作要做的。以前听到Socket编程,觉得它是比较高深的编程知识,但是只要弄清Socket编程的工作原理,神秘的面纱也就揭开了。
           一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。    生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。

          

    图3

           先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

    ============================================

    我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页 时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是 socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的。本文的主要内容如下:

    • 1、网络中进程之间如何通信?

    • 2、Socket是什么?

    • 3、socket的基本操作

      • 3.1、socket()函数

      • 3.2、bind()函数

      • 3.3、listen()、connect()函数

      • 3.4、accept()函数

      • 3.5、read()、write()函数等

      • 3.6、close()函数

    • 4、socket中TCP的三次握手建立连接详解

    • 5、socket中TCP的四次握手释放连接详解

    • 6、一个例子

    1、网络中进程之间如何通信?

    本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:

    • 消息传递(管道、FIFO、消息队列)

    • 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)

    • 共享内存(匿名的和具名的)

    • 远程过程调用(Solaris门和Sun RPC)

    但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

    使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。

    2、什么是Socket?

    上面我们已经知道网络中的进程是通过socket来通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),这些函数我们在后面进行介绍。

    socket一词的起源

    在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:“命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”计算机历史博物馆补充道:“这比BSD的套接字接口定义早了大约12年。”

    3、socket的基本操作

    既然socket是“open—write/read—close”模式的一种实现,那么socket就提供了这些操作对应的函数接口。下面以TCP为例,介绍几个基本的socket接口函数。

    3.1、socket()函数

    int socket(int domain, int type, int protocol);
    

    socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

    正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

    • domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
    • type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
    • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。

    注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

    当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

    3.2、bind()函数

    正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    函数的三个参数分别为:

    • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
    • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:
      struct sockaddr_in {
          sa_family_t    sin_family; 
          in_port_t      sin_port;   
          struct in_addr sin_addr;   
      };
      
      
      struct in_addr {
          uint32_t       s_addr;     
      };
      
      ipv6对应的是:
      struct sockaddr_in6 { 
          sa_family_t     sin6_family;    
          in_port_t       sin6_port;      
          uint32_t        sin6_flowinfo;  
          struct in6_addr sin6_addr;      
          uint32_t        sin6_scope_id;  
      };
      
      struct in6_addr { 
          unsigned char   s6_addr[16];    
      };
      
      Unix域对应的是:
      #define UNIX_PATH_MAX    108
      
      struct sockaddr_un { 
          sa_family_t sun_family;                
          char        sun_path[UNIX_PATH_MAX];   
      };
      
    • addrlen:对应的是地址的长度。

    通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

    网络字节序与主机字节序

    主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

      a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

      b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

    网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

    所以: 在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于 这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再 赋给socket。

    3.3、listen()、connect()函数

    如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

    int listen(int sockfd, int backlog);
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

    connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

    3.4、accept()函数

    TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    

    accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

    注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

    3.5、read()、write()等函数

    万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:

    • read()/write()
    • recv()/send()
    • readv()/writev()
    • recvmsg()/sendmsg()
    • recvfrom()/sendto()

    我推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。它们的声明如下:

           #include 
    
           ssize_t read(int fd, void *buf, size_t count);
           ssize_t write(int fd, const void *buf, size_t count);
    
           #include 
           #include 
    
           ssize_t send(int sockfd, const void *buf, size_t len, int flags);
           ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
           ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                          const struct sockaddr *dest_addr, socklen_t addrlen);
           ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                            struct sockaddr *src_addr, socklen_t *addrlen);
    
           ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
           ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
    
    

    read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

    write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节 数。失败时返回-1,并设置errno变量。在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是 全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示 网络连接出现了问题(对方已经关闭了连接)。

    其它的我就不一一介绍这几对I/O函数了,具体参见man文档或者baidu、Google,下面的例子中将使用到send/recv。

    3.6、close()函数

    在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

    #include 
    int close(int fd);
    

    close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

    注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

    4、socket中TCP的三次握手建立连接详解

    我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

    • 客户端向服务器发送一个SYN J
    • 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
    • 客户端再想服务器发一个确认ACK K+1

    只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

    image

    图1、socket中发送的TCP三次握手

    从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

    总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

    5、socket中TCP的四次握手释放连接详解

    上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:

    image

    图2、socket中发送的TCP四次握手

    图示过程如下:

    • 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;

    • 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;

    • 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;

    • 接收到这个FIN的源发送端TCP对它进行确认。

    这样每个方向上都有一个FIN和ACK。

     

    6.下面给出实现的一个实例

     

    首先,先给出实现的截图

    服务器端代码如下:

     

     

     

    [cpp] view plaincopyprint?

    1. #include "InitSock.h"
    2. #include
    3. #include
    4. using namespace std;
    5. CInitSock initSock; // 初始化Winsock库
    6. int main()
    7. {
    8. // 创建套节字
    9. SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    10. //用来指定套接字使用的地址格式,通常使用AF_INET
    11. //指定套接字的类型,若是SOCK_DGRAM,则用的是udp不可靠传输
    12. //配合type参数使用,指定使用的协议类型(当指定套接字类型后,可以设置为0,因为默认为UDP或TCP)
    13. if(sListen == INVALID_SOCKET)
    14. {
    15. printf("Failed socket() \n");
    16. return 0;
    17. }
    18. // 填充sockaddr_in结构 ,是个结构体
    19. sockaddr_in sin;
    20. sin.sin_family = AF_INET;
    21. sin.sin_port = htons(4567); //1024 ~ 49151:普通用户注册的端口号
    22. sin.sin_addr.S_un.S_addr = INADDR_ANY;
    23. // 绑定这个套节字到一个本地地址
    24. if(::bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    25. {
    26. printf("Failed bind() \n");
    27. return 0;
    28. }
    29. // 进入监听模式
    30. //2指的是,监听队列中允许保持的尚未处理的最大连接数
    31. if(::listen(sListen, 2) == SOCKET_ERROR)
    32. {
    33. printf("Failed listen() \n");
    34. return 0;
    35. }
    36. // 循环接受客户的连接请求
    37. sockaddr_in remoteAddr;
    38. int nAddrLen = sizeof(remoteAddr);
    39. SOCKET sClient = 0;
    40. char szText[] = " TCP Server Demo! \r\n";
    41. while(sClient==0)
    42. {
    43. // 接受一个新连接
    44. //((SOCKADDR*)&remoteAddr)一个指向sockaddr_in结构的指针,用于获取对方地址
    45. sClient = ::accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
    46. if(sClient == INVALID_SOCKET)
    47. {
    48. printf("Failed accept()");
    49. }
    50. printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
    51. continue ;
    52. }
    53. while(TRUE)
    54. {
    55. // 向客户端发送数据
    56. gets(szText) ;
    57. ::send(sClient, szText, strlen(szText), 0);
    58. // 从客户端接收数据
    59. char buff[256] ;
    60. int nRecv = ::recv(sClient, buff, 256, 0);
    61. if(nRecv > 0)
    62. {
    63. buff[nRecv] = '\0';
    64. printf(" 接收到数据:%s\n", buff);
    65. }
    66. }
    67. // 关闭同客户端的连接
    68. ::closesocket(sClient);
    69. // 关闭监听套节字
    70. ::closesocket(sListen);
    71. return 0;
    72. }

     


    客户端代码:

     

     

     

    [cpp] view plaincopyprint?

    1. #include "InitSock.h"
    2. #include
    3. #include
    4. using namespace std;
    5. CInitSock initSock; // 初始化Winsock库
    6. int main()
    7. {
    8. // 创建套节字
    9. SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    10. if(s == INVALID_SOCKET)
    11. {
    12. printf(" Failed socket() \n");
    13. return 0;
    14. }
    15. // 也可以在这里调用bind函数绑定一个本地地址
    16. // 否则系统将会自动安排
    17. // 填写远程地址信息
    18. sockaddr_in servAddr;
    19. servAddr.sin_family = AF_INET;
    20. servAddr.sin_port = htons(4567);
    21. // 注意,这里要填写服务器程序(TCPServer程序)所在机器的IP地址
    22. // 如果你的计算机没有联网,直接使用127.0.0.1即可
    23. servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    24. if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)
    25. {
    26. printf(" Failed connect() \n");
    27. return 0;
    28. }
    29. char buff[256];
    30. char szText[256] ;
    31. while(TRUE)
    32. {
    33. //从服务器端接收数据
    34. int nRecv = ::recv(s, buff, 256, 0);
    35. if(nRecv > 0)
    36. {
    37. buff[nRecv] = '\0';
    38. printf("接收到数据:%s\n", buff);
    39. }
    40. // 向服务器端发送数据
    41. gets(szText) ;
    42. szText[255] = '\0';
    43. ::send(s, szText, strlen(szText), 0) ;
    44. }
    45. // 关闭套节字
    46. ::closesocket(s);
    47. return 0;
    48. }

     


    封装的InitSock.h

     

     

     

    [cpp] view plaincopyprint?

    1. #include
    2. #include
    3. #include
    4. #include
    5. #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
    6. class CInitSock
    7. {
    8. public:
    9. CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
    10. {
    11. // 初始化WS2_32.dll
    12. WSADATA wsaData;
    13. WORD sockVersion = MAKEWORD(minorVer, majorVer);
    14. if(::WSAStartup(sockVersion, &wsaData) != 0)
    15. {
    16. exit(0);
    17. }
    18. }
    19. ~CInitSock()
    20. {
    21. ::WSACleanup();
    22. }
    23. };
    展开全文
  • 现在我为大家讲解一下for循环及if条件判断语句在实例中的应用,为了大家能更好的理解,我用一个石头剪刀布的小游戏的代码来展示具体内容,一些细节问题我会在下方做注释。 这个游戏的具体玩法很简单,就是人与电脑...

        无论是学习c,c+,还是python语言,都会有一些对基本语句的应用,想要在这个领域学的更好,必须要掌握一些基本语法的用法。现在我为大家讲解一下for循环及if条件判断语句在实例中的应用,为了大家能更好的理解,我用一个石头剪刀布的小游戏的代码来展示具体内容,一些细节问题我会在下方做注释。

        这个游戏的具体玩法很简单,就是人与电脑之间的对决,人是自由的,随机用手打出石头剪刀布,而电脑一方则是通过代码中的通过random这个包,来随机选择与人对应打出石头剪刀布的顺序,下边是具体代码:

    from random import randint
    #导入一个随机包
    Pe_win = 0
    computer_win = 0
    
    ping = 0
    
    #上边三行代码的意思是先给变量赋予一个初始值,否则系统既不能辨别变量的类型
    
    # index 代表编号(索引)  value代表值(内容) enumerate 枚举,列举
    for index, value in enumerate(range(3)):
        # print(index)
        # print(value)
        Pe_num = input("请输入一个数字:")#Pe_num 代表玩家出的石头,剪刀,或者布;在这里用0,1,2代表
        Pe_num = int(Pe_num)#强制将输入的字符串或者数字转化成数字类型
        computer_num = randint(0,2)#随机取值在0,1,2之间
    
        if Pe_num - computer_num == -1 or Pe_num - computer_num == 2:
            print("第{}局玩家获胜".format(index+1))#在这里解释下 index+1 并不是真正意义上的+1,而是为了在终端输出没有第0局这样的尴尬而对代码进行的调整
            Pe_win += 1
        elif Pe_num - computer_num ==0:#玩家和电脑随机出的0,1,2
            print("第{}局平局".format(index+1))
            ping +=1
        else:
            print("第{}局电脑获胜".format(index+1))
            computer_win +=1
        print("@@@@@@@@@第{}局结束@@@@@@@@@@@".format(index+1))
        #分析能出现的各种情况
        if computer_win == 2 :#电脑赢两局
            print("电脑获胜")#最终结果电脑获胜
            break#结束并跳出循环
        elif Pe_win == 2 :
            print("玩家获胜")
            break
        else :
            #平3局,平两局,
            if ping ==1 and computer_win - Pe_win == 0 and index==2:
                print("平局")
            #上边的情况是平局一场,然后玩家与电脑各赢一场
            elif ping ==3:
                print("平局")
            elif ping ==2 and index ==2:
                if computer_win-Pe_win ==1:
                    print("电脑获胜")
                else:
                    print("玩家获胜")
            #最后这种情况有点复杂,通过最简单的逻辑想,意思就是在电脑与玩家两局平局的情况下,电脑的胜场多一场那就等于电脑获胜,反之, 
    



        

    展开全文
  • 递归算法讲解

    万次阅读 多人点赞 2017-06-15 22:11:51
     在我们明确递归算法设计三要素后,接下来就需要着手开始编写具体的算法了。在编写算法时,不失一般性,我们给出两种典型的递归算法设计模型,如下所示。 模型一: 在递去的过程中解决问题 function ...

    原作者:书呆子Rico 《递归的内涵与经典应用》 http://my.csdn.net/justloveyou_

    摘要:

      大师 L. Peter Deutsch 说过:To Iterate is Human, to Recurse, Divine.中文译为:人理解迭代,神理解递归。毋庸置疑地,递归确实是一个奇妙的思维方式。对一些简单的递归问题,我们总是惊叹于递归描述问题的能力和编写代码的简洁,但要想真正领悟递归的精髓、灵活地运用递归思想来解决问题却并不是一件容易的事情。本文剖析了递归的思想内涵,分析了递归与循环的联系与区别,给出了递归的应用场景和一些典型应用,并利用递归和非递归的方式解决了包括阶乘、斐波那契数列、汉诺塔、杨辉三角的存取、字符串回文判断、字符串全排列、二分查找、树的深度求解在内的八个经典问题。

    版权声明:

      本文原创作者:书呆子Rico
      作者博客地址:http://blog.csdn.net/justloveyou_/

    友情提示:

      若读者需要本博文相关完整代码,请移步我的Github自行获取,项目名为 SwordtoOffer,链接地址为:https://github.com/githubofrico/SwordtoOffer

    一. 引子

       大师 L. Peter Deutsch 说过:To Iterate is Human, to Recurse, Divine.中文译为:人理解迭代,神理解递归。毋庸置疑地,递归确实是一个奇妙的思维方式。对一些简单的递归问题,我们总是惊叹于递归描述问题的能力和编写代码的简洁,但要想真正领悟递归的精髓、灵活地运用递归思想来解决问题却并不是一件容易的事情。在正式介绍递归之前,我们首先引用知乎用户李继刚(https://www.zhihu.com/question/20507130/answer/15551917)对递归和循环的生动解释:

       递归:你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开它。若干次之后,你打开面前的门后,发现只有一间屋子,没有门了。然后,你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这你把钥匙打开了几扇门。

       循环:你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门(若前面两扇门都一样,那么这扇门和前两扇门也一样;如果第二扇门比第一扇门小,那么这扇门也比第二扇门小,你继续打开这扇门,一直这样继续下去直到打开所有的门。但是,入口处的人始终等不到你回去告诉他答案。

       上面的比喻形象地阐述了递归与循环的内涵,那么我们来思考以下几个问题:

    什么是递归呢?
    递归的精髓(思想)是什么?
    递归和循环的区别是什么?
    什么时候该用递归?
    使用递归需要注意哪些问题?
    递归思想解决了哪些经典的问题?
    这些问题正是笔者准备在本文中详细阐述的问题。

    二. 递归的内涵

    1、定义 (什么是递归?)

       在数学与计算机科学中,递归(Recursion)是指在函数的定义中使用函数自身的方法。实际上,递归,顾名思义,其包含了两个意思:递 和 归,这正是递归思想的精华所在。

    2、递归思想的内涵(递归的精髓是什么?)

       正如上面所描述的场景,递归就是有去(递去)有回(归来),如下图所示。“有去”是指:递归问题必须可以分解为若干个规模较小,与原问题形式相同的子问题,这些子问题可以用相同的解题思路来解决,就像上面例子中的钥匙可以打开后面所有门上的锁一样;“有回”是指 : 这些问题的演化过程是一个从大到小,由近及远的过程,并且会有一个明确的终点(临界点),一旦到达了这个临界点,就不用再往更小、更远的地方走下去。最后,从这个临界点开始,原路返回到原点,原问题解决。  

                        这里写图片描述

      
       更直接地说,递归的基本思想就是把规模大的问题转化为规模小的相似的子问题来解决。特别地,在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况,这也正是递归的定义所在。格外重要的是,这个解决问题的函数必须有明确的结束条件,否则就会导致无限递归的情况。

    3、用归纳法来理解递归

       数学都不差的我们,第一反应就是递归在数学上的模型是什么,毕竟我们对于问题进行数学建模比起代码建模拿手多了。观察递归,我们会发现,递归的数学模型其实就是 数学归纳法,这个在高中的数列里面是最常用的了,下面回忆一下数学归纳法。

       数学归纳法适用于将解决的原问题转化为解决它的子问题,而它的子问题又变成子问题的子问题,而且我们发现这些问题其实都是一个模型,也就是说存在相同的逻辑归纳处理项。当然有一个是例外的,也就是归纳结束的那一个处理方法不适用于我们的归纳处理项,当然也不能适用,否则我们就无穷归纳了。总的来说,归纳法主要包含以下三个关键要素:

    步进表达式:问题蜕变成子问题的表达式
    结束条件:什么时候可以不再使用步进表达式
    直接求解表达式:在结束条件下能够直接计算返回值的表达式
    事实上,这也正是某些数学中的数列问题在利用编程的方式去解决时可以使用递归的原因,比如著名的斐波那契数列问题。

    4、递归的三要素

       在我们了解了递归的基本思想及其数学模型之后,我们如何才能写出一个漂亮的递归程序呢?笔者认为主要是把握好如下三个方面:

    1、明确递归终止条件;
    
    2、给出递归终止时的处理办法;
    
    3、提取重复的逻辑,缩小问题规模。

    1). 明确递归终止条件

       我们知道,递归就是有去有回,既然这样,那么必然应该有一个明确的临界点,程序一旦到达了这个临界点,就不用继续往下递去而是开始实实在在的归来。换句话说,该临界点就是一种简单情境,可以防止无限递归。

    2). 给出递归终止时的处理办法

       我们刚刚说到,在递归的临界点存在一种简单情境,在这种简单情境下,我们应该直接给出问题的解决方案。一般地,在这种情境下,问题的解决方案是直观的、容易的。

    3). 提取重复的逻辑,缩小问题规模*

       我们在阐述递归思想内涵时谈到,递归问题必须可以分解为若干个规模较小、与原问题形式相同的子问题,这些子问题可以用相同的解题思路来解决。从程序实现的角度而言,我们需要抽象出一个干净利落的重复的逻辑,以便使用相同的方式解决子问题。

    5、递归算法的编程模型

       在我们明确递归算法设计三要素后,接下来就需要着手开始编写具体的算法了。在编写算法时,不失一般性,我们给出两种典型的递归算法设计模型,如下所示。

    模型一: 在递去的过程中解决问题

    function recursion(大规模){
        if (end_condition){      // 明确的递归终止条件
            end;   // 简单情景
        }else{            // 在将问题转换为子问题的每一步,解决该步中剩余部分的问题
            solve;                // 递去
            recursion(小规模);     // 递到最深处后,不断地归来
        }
    }

    模型二: 在归来的过程中解决问题

    function recursion(大规模){
        if (end_condition){      // 明确的递归终止条件
            end;   // 简单情景
        }else{            // 先将问题全部描述展开,再由尽头“返回”依次解决每步中剩余部分的问题
            recursion(小规模);     // 递去
            solve;                // 归来
        }
    }

    6、递归的应用场景

       在我们实际学习工作中,递归算法一般用于解决三类问题:

       (1). 问题的定义是按递归定义的(Fibonacci函数,阶乘,…);

       (2). 问题的解法是递归的(有些问题只能使用递归方法来解决,例如,汉诺塔问题,…);

       (3). 数据结构是递归的(链表、树等的操作,包括树的遍历,树的深度,…)。

      在下文我们将给出递归算法的一些经典应用案例,这些案例基本都属于第三种类型问题的范畴。

    三. 递归与循环

       递归与循环是两种不同的解决问题的典型思路。递归通常很直白地描述了一个问题的求解过程,因此也是最容易被想到解决方式。循环其实和递归具有相同的特性,即做重复任务,但有时使用循环的算法并不会那么清晰地描述解决问题步骤。单从算法设计上看,递归和循环并无优劣之别。然而,在实际开发中,因为函数调用的开销,递归常常会带来性能问题,特别是在求解规模不确定的情况下;而循环因为没有函数调用开销,所以效率会比递归高。递归求解方式和循环求解方式往往可以互换,也就是说,如果用到递归的地方可以很方便使用循环替换,而不影响程序的阅读,那么替换成循环往往是好的。问题的递归实现转换成非递归实现一般需要两步工作:

       (1). 自己建立“堆栈(一些局部变量)”来保存这些内容以便代替系统栈,比如树的三种非递归遍历方式;

       (2). 把对递归的调用转变为对循环处理。

       特别地,在下文中我们将给出递归算法的一些经典应用案例,对于这些案例的实现,我们一般会给出递归和非递归两种解决方案,以便读者体会。

    四. 经典递归问题实战

    1. 第一类问题:问题的定义是按递归定义的

    (1). 阶乘

    /**
     * Title: 阶乘的实现 
     * Description:
     *      递归解法
     *      非递归解法
     * @author rico
     */
    public class Factorial {
        /**     
         * @description 阶乘的递归实现
         * @author rico       
         * @created 2017年5月10日 下午8:45:48     
         * @param n
         * @return     
         */
        public static long f(int n){
            if(n == 1)   // 递归终止条件 
                return 1;    // 简单情景
    
            return n*f(n-1);  // 相同重复逻辑,缩小问题的规模
        }
    
    --------------------------------我是分割线-------------------------------------
    
        /**     
         * @description 阶乘的非递归实现
         * @author rico       
         * @created 2017年5月10日 下午8:46:43     
         * @param n
         * @return     
         */
        public static long f_loop(int n) {
            long result = n;
            while (n > 1) {
                n--;
                result = result * n;
            }
            return result;
        }
    }

    (2). 斐波纳契数列

    /**
    * Title: 斐波纳契数列
    *
    * Description: 斐波纳契数列,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……
    * 在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)。
    *
    * 两种递归解法:经典解法和优化解法
    * 两种非递归解法:递推法和数组法
    *

     * @author rico
     */
    public class FibonacciSequence {
    
        /**
         * @description 经典递归法求解
         * 
         * 斐波那契数列如下:
         * 
         *  1,1,2,3,5,8,13,21,34,...
         * 
         * *那么,计算fib(5)时,需要计算1次fib(4),2次fib(3),3次fib(2),调用了2次fib(1)*,即:
         * 
         *  fib(5) = fib(4) + fib(3)
         *  
         *  fib(4) = fib(3) + fib(2) ;fib(3) = fib(2) + fib(1)
         *  
         *  fib(3) = fib(2) + fib(1)
         *  
         * 这里面包含了许多重复计算,而实际上我们只需计算fib(4)、fib(3)、fib(2)和fib(1)各一次即可,
         * 后面的optimizeFibonacci函数进行了优化,使时间复杂度降到了O(n).
         * 
         * @author rico
         * @created 2017年5月10日 下午12:00:42
         * @param n
         * @return
         */
        public static int fibonacci(int n) {
            if (n == 1 || n == 2) {     // 递归终止条件
                return 1;       // 简单情景
            }
            return fibonacci(n - 1) + fibonacci(n - 2); // 相同重复逻辑,缩小问题的规模
        }

    ——————————–我是分割线————————————-

    /**     
     * @description 对经典递归法的优化
     * 
     * 斐波那契数列如下:
     * 
     *  1,1,2,3,5,8,13,21,34,...
     * 
     * 那么,我们可以这样看:fib(1,1,5) = fib(1,2,4) = fib(2,3,3) = 5
     * 
     * 也就是说,以1,1开头的斐波那契数列的第五项正是以1,2开头的斐波那契数列的第四项,
     * 而以1,2开头的斐波那契数列的第四项也正是以2,3开头的斐波那契数列的第三项,
     * 更直接地,我们就可以一步到位:fib(2,3,3) = 2 + 3 = 5,计算结束。 
     * 
     * 注意,前两个参数是数列的开头两项,第三个参数是我们想求的以前两个参数开头的数列的第几项。
     * 
    
        * 时间复杂度:O(n)
         * 
         * @author rico       
         * @param first 数列的第一项
         * @param second 数列的第二项
         * @param n 目标项
         * @return     
         */
        public static int optimizeFibonacci(int first, int second, int n) {
            if (n > 0) {
                if(n == 1){    // 递归终止条件
                    return first;       // 简单情景
                }else if(n == 2){            // 递归终止条件
                    return second;      // 简单情景
                }else if (n == 3) {         // 递归终止条件
                    return first + second;      // 简单情景
                }
                return optimizeFibonacci(second, first + second, n - 1);  // 相同重复逻辑,缩小问题规模
            }
            return -1;
        }
    
    --------------------------------我是分割线-------------------------------------
    
        /**
         * @description 非递归解法:有去无回
         * @author rico
         * @created 2017年5月10日 下午12:03:04
         * @param n
         * @return
         */
        public static int fibonacci_loop(int n) {
    
            if (n == 1 || n == 2) {   
                return 1;
            }
    
            int result = -1;
            int first = 1;      // 自己维护的"栈",以便状态回溯
            int second = 1;     // 自己维护的"栈",以便状态回溯
    
            for (int i = 3; i <= n; i++) { // 循环
                result = first + second;
                first = second;
                second = result;
            }
            return result;
        }
    
    --------------------------------我是分割线-------------------------------------
    
    /**     
         * @description 使用数组存储斐波那契数列
         * @author rico       
         * @param n
         * @return     
         */
        public static int fibonacci_array(int n) {
            if (n > 0) {
                int[] arr = new int[n];   // 使用临时数组存储斐波纳契数列
                arr[0] = arr[1] = 1;
    
                for (int i = 2; i < n; i++) {   // 为临时数组赋值
                    arr[i] = arr[i-1] + arr[i-2];
                }
                return arr[n - 1];
            }
            return -1;
        }
    }

    (3). 杨辉三角的取值

    /**     
     * @description 递归获取杨辉三角指定行、列(从0开始)的值
     *              注意:与是否创建杨辉三角无关
    
        * @author rico 
         * @x  指定行
         * @y  指定列    
         */
      /**
        * Title: 杨辉三角形又称Pascal三角形,它的第i+1行是(a+b)i的展开式的系数。
        * 它的一个重要性质是:三角形中的每个数字等于它两肩上的数字相加。
        * 
        * 例如,下面给出了杨辉三角形的前4行: 
        *    1 
        *   1 1
        *  1 2 1
        * 1 3 3 1
        * @description 递归获取杨辉三角指定行、列(从0开始)的值
        *              注意:与是否创建杨辉三角无关
        * @author rico 
        * @x  指定行
        * @y  指定列  
        */
        public static int getValue(int x, int y) {
            if(y <= x && y >= 0){
                if(y == 0 || x == y){   // 递归终止条件
                    return 1; 
                }else{ 
                    // 递归调用,缩小问题的规模
                    return getValue(x-1, y-1) + getValue(x-1, y); 
                }
            }
            return -1;
        } 
    }

    (4). 回文字符串的判断

    /**
    * Title: 回文字符串的判断
    * Description: 回文字符串就是正读倒读都一样的字符串。如”98789”, “abccba”都是回文字符串
    *
    * 两种解法:
    * 递归判断;
    * 循环判断;
    *

     * @author rico       
     */      
    public class PalindromeString {
    
        /**     
         * @description 递归判断一个字符串是否是回文字符串
         * @author rico       
         * @created 2017年5月10日 下午5:45:50     
         * @param s
         * @return     
         */
        public static boolean isPalindromeString_recursive(String s){
            int start = 0;
            int end = s.length()-1;
            if(end > start){   // 递归终止条件:两个指针相向移动,当start超过end时,完成判断
                if(s.charAt(start) != s.charAt(end)){
                    return false;
                }else{
                    // 递归调用,缩小问题的规模
                    return isPalindromeString_recursive(s.substring(start+1).substring(0, end-1));
                }
            }
            return true;
        }
    
    --------------------------------我是分割线-------------------------------------
    
        /**     
         * @description 循环判断回文字符串
         * @author rico       
         * @param s
         * @return     
         */
        public static boolean isPalindromeString_loop(String s){
            char[] str = s.toCharArray();
            int start = 0;
            int end = str.length-1;
            while(end > start){  // 循环终止条件:两个指针相向移动,当start超过end时,完成判断
                if(str[end] != str[start]){
                    return false;
                }else{
                    end --;
                    start ++;
                }
            }
            return true;
        }
    }

    (5). 字符串全排列

    递归解法
    /**
    * @description 从字符串数组中每次选取一个元素,作为结果中的第一个元素;然后,对剩余的元素全排列

         * @author rico
         * @param s
         *            字符数组
         * @param from
         *            起始下标
         * @param to
         *            终止下标
         */
        public static void getStringPermutations3(char[] s, int from, int to) {
            if (s != null && to >= from && to < s.length && from >= 0) { // 边界条件检查
                if (from == to) { // 递归终止条件
                    System.out.println(s); // 打印结果
                } else {
                    for (int i = from; i <= to; i++) {
                        swap(s, i, from); // 交换前缀,作为结果中的第一个元素,然后对剩余的元素全排列
                        getStringPermutations3(s, from + 1, to); // 递归调用,缩小问题的规模
                        swap(s, from, i); // 换回前缀,复原字符数组
                    }
                }
            }
        }
    
        /**
         * @description 对字符数组中的制定字符进行交换
         * @author rico
         * @param s
         * @param from
         * @param to
         */
        public static void swap(char[] s, int from, int to) {
            char temp = s[from];
            s[from] = s[to];
            s[to] = temp;
        }

    非递归解法(字典序全排列)
    /**
    * Title: 字符串全排列非递归算法(字典序全排列)
    * Description: 字典序全排列,其基本思想是:
    * 先对需要求排列的字符串进行字典排序,即得到全排列中最小的排列.
    * 然后,找到一个比它大的最小的全排列,一直重复这一步直到找到最大值,即字典排序的逆序列.
    *
    * 不需要关心字符串长度
    *
    * @author rico
    */
    public class StringPermutationsLoop {

    /**
     * @description 字典序全排列
     * 
     * 设一个字符串(字符数组)的全排列有n个,分别是A1,A2,A3,...,An
     * 
     * 1. 找到最小的排列 Ai
     * 2. 找到一个比Ai大的最小的后继排列Ai+1
     * 3. 重复上一步直到没有这样的后继
     * 
     * 重点就是如何找到一个排列的直接后继:
     * 对于字符串(字符数组)a0a1a2……an,
     * 1. 从an到a0寻找第一次出现的升序排列的两个字符(即ai < ai+1),那么ai+1是一个极值,因为ai+1之后的字符为降序排列,记 top=i+1;
     * 2. 从top处(包括top)开始查找比ai大的最小的值aj,记 minMax = j;
     * 3. 交换minMax处和top-1处的字符;
     * 4. 翻转top之后的字符(包括top),即得到一个排列的直接后继排列
     * 
    
      * @author rico
         * @param s
         *            字符数组
         * @param from
         *            起始下标
         * @param to
         *            终止下标
         */
        public static void getStringPermutations4(char[] s, int from, int to) {
    
            Arrays.sort(s,from,to+1);  // 对字符数组的所有元素进行升序排列,即得到最小排列 
            System.out.println(s);    
    
            char[] descendArr = getMaxPermutation(s, from, to); // 得到最大排列,即最小排列的逆序列
    
            while (!Arrays.equals(s, descendArr)) {  // 循环终止条件:迭代至最大排列
                if (s != null && to >= from && to < s.length && from >= 0) { // 边界条件检查
                    int top = getExtremum(s, from, to); // 找到序列的极值
                    int minMax = getMinMax(s, top, to);  // 从top处(包括top)查找比s[top-1]大的最小值所在的位置
                    swap(s, top - 1, minMax);  // 交换minMax处和top-1处的字符
                    s = reverse(s, top, to);   // 翻转top之后的字符
                    System.out.println(s);
                }
            }
        }
    
        /**
         * @description 对字符数组中的制定字符进行交换
         * @author rico
         * @param s
         * @param from
         * @param to
         */
        public static void swap(char[] s, int from, int to) {
            char temp = s[from];
            s[from] = s[to];
            s[to] = temp;
        }
    
        /**     
         * @description 获取序列的极值
         * @author rico       
         * @param s 序列
         * @param from 起始下标
         * @param to 终止下标
         * @return     
         */
        public static int getExtremum(char[] s, int from, int to) {
            int index = 0;
            for (int i = to; i > from; i--) {
                if (s[i] > s[i - 1]) {
                    index = i;
                    break;
                }
            }
            return index;
        }
    
        /**     
         * @description 从top处查找比s[top-1]大的最小值所在的位置
         * @author rico       
         * @created 2017年5月10日 上午9:21:13     
         * @param s
         * @param top 极大值所在位置
         * @param to
         * @return     
         */
        public static int getMinMax(char[] s, int top, int to) {
            int index = top;
            char base = s[top-1];
            char temp = s[top];
            for (int i = top + 1; i <= to; i++) {
                if (s[i] > base && s[i] < temp) {
                    temp = s[i];
                    index = i;
                }
                continue;
            }
            return index;
        }
    
        /**     
         * @description 翻转top(包括top)后的序列
         * @author rico       
         * @param s
         * @param from
         * @param to
         * @return     
         */
        public static char[] reverse(char[] s, int top, int to) {
            char temp;
            while(top < to){
                temp = s[top];
                s[top] = s[to];
                s[to] = temp;
                top ++;
                to --;
            }
            return s;
        }
    
        /**     
         * @description 根据最小排列得到最大排列
         * @author rico       
         * @param s 最小排列
         * @param from 起始下标
         * @param to 终止下标
         * @return     
         */
        public static char[] getMaxPermutation(char[] s, int from, int to) {
            //将最小排列复制到一个新的数组中
            char[] dsc = Arrays.copyOfRange(s, 0, s.length);
            int first = from;
            int end = to;
            while(end > first){  // 循环终止条件
                char temp = dsc[first];
                dsc[first] = dsc[end];
                dsc[end] = temp;
                first ++;
                end --;
            }
            return dsc;
        }

    (6). 二分查找

    /**
    * @description 二分查找的递归实现
    * @author rico
    * @param array 目标数组
    * @param low 左边界
    * @param high 右边界
    * @param target 目标值
    * @return 目标值所在位置
    */

    public static int binarySearch(int[] array, int low, int high, int target) {
    
            //递归终止条件
            if(low <= high){
                int mid = (low + high) >> 1;
                if(array[mid] == target){
                    return mid + 1;  // 返回目标值的位置,从1开始
                }else if(array[mid] > target){
                    // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
                    return binarySearch(array, low, mid-1, target);
                }else{
                    // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
                    return binarySearch(array, mid+1, high, target);
                }
            }
            return -1;   //表示没有搜索到
        }
    
    --------------------------------我是分割线-------------------------------------
    
    /**     
         * @description 二分查找的非递归实现
         * @author rico       
         * @param array 目标数组
         * @param low 左边界
         * @param high 右边界
         * @param target 目标值
         * @return 目标值所在位置
         */
    public static int binarySearchNoRecursive(int[] array, int low, int high, int target) {
    
            // 循环
            while (low <= high) {
                int mid = (low + high) >> 1;
                if (array[mid] == target) {
                    return mid + 1; // 返回目标值的位置,从1开始
                } else if (array[mid] > target) {
                    // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
                    high = mid -1;
                } else {
                    // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
                    low = mid + 1;
                }
            }
            return -1;  //表示没有搜索到
        }
    1. 第二类问题:问题解法按递归算法实现

    (1). 汉诺塔问题

    /**
    * Title: 汉诺塔问题
    * Description:古代有一个梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小不等,大的在下,小的在上。
    * 有一个和尚想把这64个盘子从A座移到C座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,
    * 小盘在上。在移动过程中可以利用B座。要求输入层数,运算后输出每步是如何移动的。
    *

     * @author rico
     */
    public class HanoiTower {
    
        /**     
         * @description 在程序中,我们把最上面的盘子称为第一个盘子,把最下面的盘子称为第N个盘子
         * @author rico       
         * @param level:盘子的个数
         * @param from 盘子的初始地址
         * @param inter 转移盘子时用于中转
         * @param to 盘子的目的地址
         */
        public static void moveDish(int level, char from, char inter, char to) {
    
            if (level == 1) { // 递归终止条件
                System.out.println("从" + from + " 移动盘子" + level + " 号到" + to);
            } else {
                // 递归调用:将level-1个盘子从from移到inter(不是一次性移动,每次只能移动一个盘子,其中to用于周转)
                moveDish(level - 1, from, to, inter); // 递归调用,缩小问题的规模
                // 将第level个盘子从A座移到C座
                System.out.println("从" + from + " 移动盘子" + level + " 号到" + to); 
                // 递归调用:将level-1个盘子从inter移到to,from 用于周转
                moveDish(level - 1, inter, from, to); // 递归调用,缩小问题的规模
            }
        }
    
        public static void main(String[] args) {
            int nDisks = 30;
            moveDish(nDisks, 'A', 'B', 'C');
        }
    1. 第三类问题:数据的结构是按递归定义的

    (1). 二叉树深度

    /**
    * Title: 递归求解二叉树的深度
    * Description:
    * @author rico
    * @created 2017年5月8日 下午6:34:50
    */

    public class BinaryTreeDepth {
    
        /**     
         * @description 返回二叉数的深度
         * @author rico       
         * @param t
         * @return     
         */
        public static int getTreeDepth(Tree t) {
    
            // 树为空
            if (t == null) // 递归终止条件
                return 0;
    
            int left = getTreeDepth(t.left); // 递归求左子树深度,缩小问题的规模
            int right = getTreeDepth(t.left); // 递归求右子树深度,缩小问题的规模
    
            return left > right ? left + 1 : right + 1;
        }
    }

    (2). 二叉树深度

    /**
     * @description 前序遍历(递归)
     * @author rico
     * @created 2017年5月22日 下午3:06:11
     * @param root
     * @return
     */
    
     public String preOrder(Node<E> root) {
            StringBuilder sb = new StringBuilder(); // 存到递归调用栈
            if (root == null) {   // 递归终止条件
                return "";     // ji 
            }else { // 递归终止条件
                sb.append(root.data + " "); // 前序遍历当前结点
                sb.append(preOrder(root.left)); // 前序遍历左子树
                sb.append(preOrder(root.right)); // 前序遍历右子树
                return sb.toString();
            }       
        }
    展开全文
  • 双缓冲技术讲解

    万次阅读 2017-05-15 17:49:17
    我们的程序开始循环帧缓冲区,着色像素 , 我们没有意识到的是,视频驱动程序正在写入帧缓冲区。 当它扫描我们写的像素时,我们的脸开始出现, 结果是 像素撕裂 ,一个可怕的视觉错误,你看到屏幕上画的一半东西。...
  • 区块链P2P网络详细讲解

    万次阅读 2019-04-17 08:08:42
    不过需要指出的是,这里所说的网络模型主要是指路由查询结构,即不同节点之间如何建立连接通道,两个节点之间一旦建立连接,具体传输什么数据则是两个节点之间的事情了。 最简单的路由方式就是 集中式 ,即存在一...
  • 【STM32】SysTick滴答定时器(delay延时函数讲解

    万次阅读 多人点赞 2018-04-09 13:51:56
    STM32F1xx官方资料: 《Cortex-M3权威指南-中文》-第8章...Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM...
  • 而SSD1306提供一块显存,芯片具体讲解见下文。 SSD1306的显存总共为128*64bit大小,SSD1306将这些显存分为了8页。 每页包含了128个字节,总共8页,这样刚好是128*64的点阵大小。 程序显示原理 在STM32...
  • nodejs事件和事件循环详解

    千次阅读 2021-01-14 16:40:41
    上篇文章我们简单的介绍了nodejs中的事件event和事件循环event loop。本文本文将会更进一步,继续讲解nodejs中的event,并探讨一下setTimeout,setImmediate和process.nextTick的区别。
  • video4linux 讲解

    千次阅读 2016-11-26 19:04:45
    Linux系统中,摄像头驱动程序安装好后,为了进行视频采集必须加入Video4Linux模块,从而可以通过Video4Linux模块提供的编程接口(API)从摄像头设备中获取图像帧。下面具体研究基于V4L的视频采集程序设计。 1 Video4...
  • 35 Spark系统运行循环流程

    千次阅读 2016-05-14 01:27:38
    DT大数据梦工厂第三十五课 Spark系统运行循环流程 内容: 1. TaskScheduler工作原理 2. TaskScheduler源码
  • AutoSAR系列讲解(实践篇)10.2-EcuM的上下电流程

    万次阅读 多人点赞 2019-12-13 10:04:01
    讲解了Ecu的上下电流程和Sleep的流程
  • } 其中Type代表数组元素的类型,可以是整数,也可以是浮点数,也可以是一个类的实例,这里我们统一用int来讲解,即 32位有符号整型。 3、移动的实现 所谓「移动」,其实是将某个元素执行后移,实现如下: a[j + 1] ...
  • SVM详细讲解

    万次阅读 多人点赞 2018-04-15 20:02:51
     具体写出来,目标函数变成了:     这里用 表示这个问题的最优值,且和最初的问题是等价的。如果直接求解,那么一上来便得面对w和b两个参数,而 又是不等式约束,这个求解过程不好做。不妨把最小和最大...
  • CMPP讲解

    千次阅读 2008-07-18 15:50:00
    1 、缩略语解释 ...作为底层通信承载,具体结构由图 所示:   ...
  • 下面我们就来具体讲解这个demo涉及到的一些知识点: 一、AI怪物攻击与思考方式设计 例如今天我们要展示的这个回合制游戏demo里的AI,就有如下几种 行为 : (1)利爪攻击 (2)闪电链攻击 (3)致命一击 (4)使用...
  • 递归算法的讲解

    万次阅读 多人点赞 2019-04-09 15:35:00
     在我们明确递归算法设计三要素后,接下来就需要着手开始编写具体的算法了。在编写算法时,不失一般性,我们给出两种典型的递归算法设计模型,如下所示。 模型一: 在递去的过程中解决问题   function ...
  • 关于分布式商城的项目讲解

    千次阅读 2018-12-07 19:28:04
    我们这个项目是基于SOA的架构来实现...下面我给您具体介绍一下。 首先进入我们的网站首页:最上面是我们的网站的logo,搜索框,下面左边是测分类栏对商品进行分类,轮中间是轮播图广告位,跟着下面是新闻公告栏,再...
  • 另外,小编也有根据以下总结内容,录制了对JVM讲解视频。里面有个用visio画的JVM内部结构图,每部分的作用都有详细讲解,希望能有帮助。网址:https://edu.csdn.net/lecturer/board/10494 1.java自动管理堆(heap...
  • 直播技术原理讲解

    万次阅读 2018-06-15 15:00:59
    在视频直播领域,有不同的商家提供各种的商业...本文要讲解的是如何使用一系列免费工具,打造一套视频直播方案。视频直播流程视频直播的流程可以分为如下几步: 采集 —&gt;处理—&gt;编码和封装—&gt;...
  • 系统安全系列作者将深入研究恶意样本分析、逆向分析、攻防实战和Windows漏洞利用等,...这篇文章将带领大家来学习科锐钱林松老师的视频,详细讲解条件语句和循环语句源码还原及流程控制逆向。希望对入门的同学有帮助。
  • SDL系列讲解(十一) SDL_QUIT流程

    千次阅读 2017-11-11 16:05:47
    进入到C代码里面Java_org_libsdl_app_SDLActivity_nativeQuit,这里向系统发出一个退出消息SDL_SendQuit();,以及一个 SDL_SendAppEvent(SDL_APP_TERMINATING) ;消息,让我们去接收,处理退出事件。发送消息,最后...
  • 非常详细的讲解车牌识别easypr

    万次阅读 多人点赞 2017-11-29 09:27:23
    更新:基于keras-tensorflow的车牌识别,HyperLPR是一个基于Python的使用深度学习针对对中文车牌...非常详细的讲解车牌识别EasyPR 我正在做一个开源的中文车牌识别系统,Git地址为:https://github.com/liuruoze/
  • 全网最全面的python的讲解,讲的无可挑剔《记得收藏》目录1、简介Pyhon中如何文件拷贝Python数学库及其应用Python异常处理机制ETCturtle库的常用指令使用turtle库绘制五角星使用turtle库绘制蟒蛇数据驱动的动态路径...
  • 笔记本平台信号讲解

    千次阅读 2019-12-03 19:30:07
    1、power button:这个信号会引起SMI#或者SCI来表示系统请求进入到睡眠状态。如果系统已经处于睡眠状态,这将导致唤醒事件信号。 如果PWRBTN#键超过4秒,这将导致一个无条件的过渡(电源按钮替代)到S5状态。即使...
  • J.U.C系列-线程安全的理论讲解

    千次阅读 热门讨论 2013-03-05 10:35:56
    在J U C里面,要谈到并发,就必然就存在可见性问题,其实对于程序来讲,要说到锁,首先要确保可见性,也就是要在这个基础上才能做到,而CAS也是基于这种原理来完成,我们在文章:Java JUC之Atomic系列12大类实例讲解和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 66,046
精华内容 26,418
关键字:

循环系统具体讲解