go语言中实现切片(slice)的三种方式

Python014

go语言中实现切片(slice)的三种方式,第1张

定义一个切片,然后让切片去引用一个已经创建好的数组。基本语法如下:

索引1:切片引用的起始元素位

索引2:切片只引用该元素位之前的元素

例程如下:

在该方法中,我们未指定容量cap,这里的值为5是系统定义的。

在方法一中,可以用arr数组名来操控数组中的元素,也可以通过slice切片来操控数组中的元素。切片是直接引用数组,数组是事先存在的,程序员是可见的。

通过 make 来创建切片,基本语法如下:

make函数第三个参数cap即容量是可选的,如果一定要自己注明的话,要注意保证cap≥len。

用该方法可以 指定切片的大小(len)和容量(cap)

例程如下:

由于未赋值系统默认将元素值置为0,即:

数值类型数组:    默认值为 0

字符串数组:       默认值为 ""

bool数组:           默认值为 false

在方法二中,通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素。

定义一个切片,直接就指定具体数组,使用原理类似于make的方式。

例程如下:

在Go语言中,规定的方式是,函数返回错误信息。这没什么。如果一个文件并不存在,op.Open函数会返回一个错误信息。这没什么。如果你向你一个中断了的网络连接里写数据,net.Conn里的Write方法会返回一个错误。这没什么。这种状况在这种程序中是可以预料到的。这种操作就是容易失败,你知道程序会如何运行,因为API的设计者通过内置了一种错误情况的结果而让这一切显得很清楚。

从另一方面讲,有些操作基本上不会出错,所处的环境根本不可能给你提示错误信息,不可能控制错误。这才是让人痛苦的地方。典型的例子;一个程序执行

x[j],j值超出数组边界,这才痛苦。像这样预料之外的麻烦在程序中是一个严重的bug,一般会弄死程序的运行。不幸的是,由于这种情况的存在,我们很难写出健壮的,具有自我防御的服务器——例如,可以应付偶然出现的有bug的HTTP请求处理器时,不影响其他服务的启动和运行。为解决这个问题,我们引入了恢复机制,它能让一个go例程从错误中恢复,服务余下设定的调用。然而,代价是,至少会丢失一个调用。这是特意而为之的。引用邮件中的原话:“这种设计不同于常见的异常控制结构,这是一个认真思考后的决定。我们不希望像java语言里那样把错误和异常混为一谈。”

我刚开始提到的那篇文章里问“为什么数组越界造成的麻烦会比错误的网址或断掉的网络引出的问题要大?”答案是,我们没有一种内联并行的方法来报告在执行x[j]期间产生的错误,但我们有内联并行的方法报告由错误网址或网络问题造成的错误。

使用Go语言中的错误返回模式的规则很简单:如果你的函数在某种情况下很容易出错,那它就应该返回错误。当我调用其它的程序库时,如果它是这样写的,那我不必担心那些错误的产生,除非有真正异常的状况,我根本没有想到需要处理它们。

有一个你需要记在心里的事情是,Go语言是为大型软件设计的。我们都喜欢程序简洁清晰,但对于一个由很多程序员一起开发的大型软件,维护成本的增加很难让程序简洁。异常捕捉模式的错误处理方式的一个很有吸引力的特点是,它非常适合小程序。但对于大型程序库,如果对于一些普通操作,你都需要考虑每行代码是否会抛出异常、是否有必要捕捉处理,这对于开发效率和程序员的时间来说都是非常严重的拖累。我自己做开发大型Python软件时感受到了这个问题。

Go语言的返回错误方式,不可否认,对于调用者不是很方便,但这样做会让程序中可能会出错的地方显的很明显。对于小程序来说,你可能只想打印出错误,退出程序。对于一些很精密的程序,根据异常的不同,来源的不同,程序会做出不同的反应,这很常见,这种情况中,try

+

catch的方式相对于错误返回模式显得冗长。当然,Python里的一个10行的代码放到Go语言里很可能会更冗长。毕竟,Go语言主要不是针对10行规模的程序的。

就是要说明这一点:Go语言程序员认为,把error作为一种内置的类型是非常重要的。

三次握手:

            1. 主动发起连接请求端(客户端),发送 SYN 标志位,携带数据包、包号

            2. 被动接收连接请求端(服务器),接收 SYN,回复 ACK,携带应答序列号。同时,发送SYN标志位,携带数据包、包号

            3. 主动发起连接请求端(客户端),接收SYN 标志位,回复 ACK。

                        被动端(服务器)接收 ACK —— 标志着 三次握手建立完成( Accept()/Dial() 返回 )

四次挥手:

            1. 主动请求断开连接端(客户端), 发送 FIN标志,携带数据包

            2. 被动接受断开连接端(服务器), 发送 ACK标志,携带应答序列号。 —— 半关闭完成。

            3. 被动接受断开连接端(服务器), 发送 FIN标志,携带数据包

            4. 主动请求断开连接端(客户端), 发送 最后一个 ACK标志,携带应答序列号。—— 发送完成,客户端不会直接退出,等 2MSL时长。

                        等 2MSL待目的:确保服务器 收到最后一个ACK

滑动窗口:

            通知对端本地存储数据的 缓冲区容量。—— write 函数在对端 缓冲区满时,有可能阻塞。

TCP状态转换:

            1. 主动发起连接请求端:

                        CLOSED ——>发送SYN ——>SYN_SENT(了解) ——>接收ACK、SYN,回发 ACK ——>ESTABLISHED (数据通信)

            2. 主动关闭连接请求端:

                        ESTABLISHED ——>发送FIN ——>FIN_WAIT_1 ——>接收ACK ——>FIN_WAIT_2 (半关闭、主动端)

                        ——>接收FIN、回复ACK ——>TIME_WAIT (主动端) ——>等 2MSL 时长 ——>CLOSED

            3. 被动建立连接请求端:

                        CLOSED ——>LISTEN ——>接收SYN、发送ACK、SYN ——>SYN_RCVD ——>接收 ACK ——>ESTABLISHED (数据通信)

            4. 被动断开连接请求端:

                        ESTABLISHED ——>接收 FIN、发送 ACK ——>CLOSE_WAIT ——>发送 FIN ——>LAST_ACK ——>接收ACK ——>CLOSED

windows下查看TCP状态转换:

            netstat -an | findstr  端口号

Linux下查看TCP状态转换:

            netstat -an | grep  端口号

TCP和UDP对比: 

            TCP: 面向连接的可靠的数据包传递。 针对不稳定的 网络层,完全弥补。ACK

            UDP:无连接不可靠的报文传输。 针对不稳定的 网络层,完全不弥补。还原网络真实状态。

                                    优点                                                            缺点

            TCP: 可靠、顺序、稳定                                      系统资源消耗大,程序实现繁复、速度慢

            UDP:系统资源消耗小,程序实现简单、速度快                          不可靠、无序、不稳定

使用场景:

            TCP:大文件、可靠数据传输。 对数据的 稳定性、准确性、一致性要求较高的场合。

            UDP:应用于对数据时效性要求较高的场合。 网络直播、电话会议、视频直播、网络游戏。

UDP-CS-Server实现流程:

            1.  创建 udp地址结构 ResolveUDPAddr(“协议”, “IP:port”) ——>udpAddr 本质 struct{IP、port}

            2.  创建用于 数据通信的 socket ListenUDP(“协议”, udpAddr ) ——>udpConn (socket)

            3.  从客户端读取数据,获取对端的地址 udpConn.ReadFromUDP() ——>返回:n,clientAddr, err

            4.  发送数据包给 客户端 udpConn.WriteToUDP("数据", clientAddr)

UDP-CS-Client实现流程:

            1.  创建用于通信的 socket。 net.Dial("udp", "服务器IP:port") ——>udpConn (socket)

            2.  以后流程参见 TCP客户端实现源码。

UDPserver默认就支持并发!

------------------------------------

命令行参数: 在main函数启动时,向整个程序传参。 【重点】

            语法: go run xxx.go   argv1 argv2  argv3  argv4 。。。

                        xxx.exe:  第 0 个参数。

                        argv1 :第 1 个参数。

                        argv2 :第 2 个参数。

                        argv3 :第 3 个参数。

                        argv4 :第 4 个参数。

            使用: list := os.Args  提取所有命令行参数。

获取文件属性函数:

            os.stat(文件访问绝对路径) ——>fileInfo 接口

            fileInfo 包含 两个接口。

                        Name() 获取文件名。 不带访问路径

                        Size() 获取文件大小。

网络文件传输 —— 发送端(客户端)

            1.  获取命令行参数,得到文件名(带路径)filePath list := os.Args

            2.  使用 os.stat() 获取 文件名(不带路径)fileName

            3.  创建 用于数据传输的 socket  net.Dial("tcp", “服务器IP+port”) —— conn

            4.  发送文件名(不带路径)  给接收端, conn.write()

            5.  读取 接收端回发“ok”,判断无误。封装函数 sendFile(filePath, conn) 发送文件内容

            6.  实现 sendFile(filePath,  conn)

                        1) 只读打开文件 os.Open(filePath)

                                    for {

                                    2) 从文件中读数据  f.Read(buf)

                                    3) 将读到的数据写到socket中  conn.write(buf[:n])

                                    4)判断读取文件的 结尾。 io.EOF. 跳出循环

                                    }

网络文件传输 —— 接收端(服务器)

            1. 创建用于监听的 socket net.Listen() —— listener

            2. 借助listener 创建用于 通信的 socket listener.Accpet()  —— conn

            3. 读取 conn.read() 发送端的 文件名, 保存至本地。

            4. 回发 “ok”应答 发送端。

            5. 封装函数,接收文件内容 recvFile(文件路径)

                        1) f = os.Create(带有路径的文件名)

                        for {

                        2)从 socket中读取发送端发送的 文件内容 。 conn.read(buf)

                        3)  将读到的数据 保存至本地文件 f.Write(buf[:n])

                        4)  判断 读取conn 结束, 代表文件传输完成。 n == 0  break

                        }