🍊Go基础语法-II
2024-4-13
| 2024-9-20
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
🍊
Go语言基本语法学习-II

目 录

函数

函数定义与使用

函数构成代码执行的逻辑结构。在Go语言中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句。
func 函数名(参数名 参数类型,…)(返回值名 返回值类型){}
若参数列表中若干相邻的参数数据类型相同时,可以统一用一个类型。例如func 函数名(参数1,参数2,… int)
如果返回值列表中多个返回值的类型相同,也可以用同样的方式合并。
如果返回值仅有一个,可以直接省去返回值名,只写类型即可
函数调用非常方便,只要事先导入了该函数所在的包,即可使用对应的函数,例如fmt包

不定参数

不定参数是指函数传入的参数个数为不定数量,并且这些参数都是同一数据类型。形如…type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。类型…type本质上是一个数组切片,也就是[]type
不定参数的传递

任意类型的不定参数

通过接口实现传递任意类型的不定参数

多返回值

匿名函数与闭包

匿名函数是指不需要定义函数名的一种函数实现方式,即没有名字的函数。在Go里面,函数可以像普通变量一样被传递或使用,这与C语言的回调函数比较类似。不同的是,Go语言支持随时在代码里定义匿名函数。匿名函数最大的用途是来模拟块级作用域,避免数据污染的。
匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数可以直接赋值给一个变量或者直接执行。
notion image
Golang匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。
notion image

闭包

闭包是由函数和与其相关的引用环境组合而成的实体。Go语言中,匿名函数与它的引用环境结合,形成了闭包。当匿名函数访问其引用环境的变量时,会在堆上生成此变量的副本,以便延 prolong 生存周期。闭包说白了就是函数的嵌套,内层的函数可以使用外层函数的所有变量,即使外层函数已经执行完毕。
以下是一个Go语言闭包的例子。在这个例子中,adder函数返回了一个闭包,该闭包包含了它的外部变量sum。每次调用pos函数时,都会改变sum的值,即使在多次调用之间,sum的值也会被保留。
notion image

defer关键字

在函数中,经常需要创建资源(比如数据库连接,文件句柄,锁等)为了在函数执行完毕后及时地释放资源,defer延迟语句可以满足这一需求
defer执行逻辑如下:
  1. 当程序执行到一个defer语句时,不会立即执行defer后的语句,而是将defer后的语句压入一个专门存储defer语句的栈中,然后继续执行函数下一个语句
  1. 当函数执行完毕后再从defer栈中依次从栈顶取出语句执行(注:先进去的最后执行,最后进去的最先执行)
  1. 在defer将语句放入栈时,也会将相关的值复制进入栈中
notion image

错误处理

error接口

一个错误处理其实就是一个接口,这个接口包含了一个简单的Error()方法,该方法的返回值是字符串。因此实现一个错误处理,所需要做的就是给出Error方法并返回一个简单的字符串。
对于大多数函数,如果要返回错误,可以将error作为多种返回值的最后一个,并非是强制要求的。
错误处理

面向对象

为类型添加方法

在Go语言中,可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法。例如,下面这个例子就是为简单的内置类型int添加了一个新方法。
这样实现了Integer后,就可以让整型像一个普通的类一样使用
notion image
如果使用之前的面向过程的方法
Go语言与C语言相同,通常都是值引用传递数据,想要修改变量的值,通常需要引用传递,如指针。下面是一个可以修改对象的方法实例
notion image
这种显式的方式与c系列中的隐藏this指针、以及Python中类的self类似,实现了相同的需求。所有的Go语言类型(指针类型除外)都可以有自己的方法。

结构体

声明一个结构体类型,例如下面定义了一个矩阵结构体
初始化结构体
在Go语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以NewXXX来命名,表示“构造函数”
Go语言可以为所有类型添加方法,自定义的结构体类型也是可以。例如,为矩阵结构体添加一个求周长的方法
定义一个圆的结构体并添加一个求周长的方法。创建并初始化一个对象实例,使用所定义方法。
notion image

匿名组合

确切地说,Go语言也提供了继承,但是采用了组合的文法,将其称为匿名组合。类似于其他语言中的继承
notion image
派生类通过指针的方式来继承基类,这种方法与前一种没有太大区别,唯一的区别在于初始化该派生类时需要额外提供一个基类的指针变量
notion image
派生类成员方法名与基类成员方法名相同时基类方法将被覆盖,对于成员变量名来说,规则也是一致的,派生类成员变量名与基类成员方法名相同时基类成员变量名同样会被覆盖
notion image
类中名字冲突:匿名组合相当于以其类型名称(去掉包名部分)作为成员变量的名字。那么按此规则,类型中如果存在两个同名的成员,即使类型不同,但我们预期会收到编译错误。
这个编译错误并不是一定会发生的。假如这两个Logger在定义后再也没有被用过,那么编译器将直接忽略掉这个冲突问题,直至开发者开始使用其中的某个Logger。

可见性

Go语言对关键字的增加非常吝啬,其中没有private、protected、public这样的关键字。要使某个符号对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母开头。对于变量、函数、类型方法都是一样适用。

接口

入侵式接口 & 非入侵式接口

入侵式接口是指一个类型必须显式地声明它实现了某个接口,这通常是通过继承或者实现接口中的所有方法来完成的。这种方式在很多面向对象的语言中非常常见,例如Java和C++。例如,在Java中,如果一个类想要实现某个接口,它需要使用implements关键字明确地声明这一点:
在上面这段代码中,Circle类显式地声明了它实现了Drawable接口,这就是入侵式接口的一个例子。相反的,Go语言的接口是非入侵式的,也就是说,不需要在类型中显式地声明这个类型实现了某个接口。只要一个类型包含了接口所有的方法,我们就可以说这个类型实现了那个接口
例如,定义了一个接口叫做Mover,它要求类型有一个Move()方法。然后定义了一个类型叫做Car,它也有一个Move()方法。在Go语言中,不需要像Java那样在Car类型中明确写出“Car实现了Mover接口”。只要Car拥有了Move()方法,它就自动满足了Mover接口。这就是Go语言接口的非入侵性。在下面这个例子中,Car类型自动满足了Mover接口,因为它拥有Move()方法。在main函数中可以看到,可以把Car类型的变量赋值给Mover类型的变量,然后调用Move()方法。
下面这个例子更好地展示了Go语言非入侵式接口这一概念以及非入侵式接口的独特优势
File类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是File类中实现了这些接口中的所有方法,就等同于File类实现了这些接口,可以将File类实例赋值给这些接口实例并调用File的这些方法。
Go并不需要关注于类实现了那个接口的那些方法,只要Go的某个类实现了某个接口的所有方法,那么该类即可赋值给接口,接口就可以调用这些方法。

接口赋值

Go语言中接口赋值有两种情况:1)将类型实例赋值给接口;2)将接口赋值给另一个接口
对于将类型实例赋值给接口的情况而言,通常使用下面两种赋值均可。
再看下面这个例子,对于这个例子,赋值方法只能使用:接口实例 = &类型实例
notion image
使用var b LessAdder = &a这种赋值方法,Go语言可以按照下面左边这个函数生成右边这个函数。这样,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口
notion image
使用var b LessAdder = a会报错,因为无法从下面左边函数自动生成右边这个函数。(&a).Add()改变的只是函数参数a,对外部实际要操作的对象并无影响,这不符合用户的预期。所以,Go语言不会自动为其生成该函数。因此,类型Integer只存在Less()方法,缺少Add()方法,不满足LessAdder接口。接口中的Add方法是一个“引用传递”,完成加法运算后,会修改a的值,这个a是外部实际要操作的对象。
接口赋值的第二种类型是接口可以赋值给接口。例如下面这个例子,接口A与接口B虽然位于不同包,并且接口内方法顺序不同,但是它们实现了所有的相同的方法,因此Go语言认为接口A与接口B是相同的,可以进行相互赋值。接口C是接口A以及接口B的子集,因此可以将接口A和接口B赋值给接口C,但是接口C不能赋值给接口A和接口B,因为接口C并没有完全实现接口A与B中的所有方法。

接口查询

接口类型可以被查询其动态类型或动态值。查询语法是:value, ok := element.(T),其中 value 是变量的值,ok 是一个 bool 类型,element 是接口变量,T 是断言的类型。如果断言不成功,ok 为 false,否则 ok 为 true。这个查询可以在 if 中进行,以便于错误处理。例如下面这个例子,首先创建了一个空接口变量 value,并赋值为 123。然后,尝试断言该接口变量的类型为 int。如果断言成功,打印出该变量的值。否则,打印出断言失败的错误信息。
notion image

类型查询

Go语言的类型查询以type-switch的形式进行。type-switch和一般的switch语句类似,它的switch关键字后面跟的是接口变量的类型而不是接口变量的值。这种类型查询的语法是switch x := element.(type),其中x是接口变量的值,element是接口变量,type是一个固定的关键字。例如,下面这个例子中,首先创建了一个空接口变量value,然后赋值为123。然后,使用type-switch查询该接口变量的类型。如果类型为int,打印出123。如果类型为string,打印出abc。如果类型为其他,打印出unknown
notion image
notion image
 
Go语言的类型查询和接口查询都是用来检查和确定一个接口变量的具体类型的方法,但它们在使用和应用上有一些重要的区别。
接口查询是使用类型断言来检查接口变量是否包含指定的类型。语法是value, ok := element.(T),其中element是接口变量,T是我们想要断言的类型。如果elementT类型,那么ok将为truevalue将包含element的值。否则,ok将为falsevalue将为T类型的零值。接口查询通常用于确定接口变量的具体类型,然后执行特定类型的操作。
类型查询是使用type-switch语句来确定接口变量的具体类型。语法是switch x := element.(type),其中element是接口变量。type-switch会依次比较element的动态类型和每个case子句列出的类型,当找到匹配的类型时,就会执行相应的代码块。类型查询用于当你需要处理多种类型,而不只是一种类型的时候。
总的来说,接口查询和类型查询都可以用来确定接口变量的具体类型,但接口查询主要用于确定接口变量是否为特定类型,而类型查询主要用于根据接口变量的类型执行不同的操作

接口组合

接口组合是Go语言中的一种重要特性,允许一个接口可以由多个其他接口组合而成。这种方式可以实现更复杂的行为。接口组合是通过在接口定义中包含其他接口的方式来完成的。例如,如果有两个接口Reader和Writer,可以定义一个新的接口ReadWriter,它包含了接口Read和接口Writer的所有方法。这种组合方式的好处是可以创建出更复杂的接口,而不需要重新定义所有的方法。而且,如果一个类型实现了接口ReadWriter,那么它也自动实现了接口Read和接口Writer。这是因为接口ReadWriter包含了接口Read和接口Writer的所有方法。这样的接口组合方式为Go语言提供了极大的灵活性,使得代码更易于理解和维护。

Any类型

Any类型是 Go 语言中一个特殊的接口类型,它没有定义任何的方法,因此所有类型都实现了 Any 接口。这样我们可以使用 Any 类型来存储任意类型的变量。例如,我们可以定义一个 Any 类型的切片,然后往切片中添加任意类型的元素。这样我们就可以使用一个切片来存储各种类型的元素。下面是一个使用 Any 类型的例子:在这个例子中,首先定义了一个 Any 类型的变量 any,然后分别给 any 赋值了一个整数、一个字符串和一个整数切片。由于所有类型都实现了 Any 接口,因此可以将任何类型的变量赋值给 Any 类型的变量。
当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,类似于C++中的模板Template

反射

在计算机科学中,反射(英语:reflection)是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。反射就是程序在运行的时候能够“观察”并且修改自己的行为。

Web应用程序

运行原理

notion image

DNS服务器

DNS服务器提供的服务是:将主机名或者域名转换为对应的ip地址
notion image
URL(Uniform Resource Locator)、URI(Uniform Resource Identifier)和 URN(Uniform Resource Name)是互联网中用来标识资源的三种标准,它们各自有不同的用途和特性。以下是它们的原理和区别:

URL(统一资源定位符)

原理: URL是一种具体的URI,用于描述网络上的资源及其访问方法。它不仅标识资源,还提供资源的位置和获取方式。
组成: URL的结构通常包括以下部分:
  • scheme:指定使用的协议(如http、https、ftp等)。
  • user:password(可选):用于身份验证的信息。
  • host:资源所在的主机(域名或IP地址)。
  • port(可选):端口号,默认情况下HTTP协议使用端口80。
  • path:资源在服务器上的路径。
  • query(可选):提供参数的查询字符串。
  • fragment(可选):片段标识符,用于指定资源中的某个部分。
示例

URI(统一资源标识符)

URL、URI和URN是互联网资源标识的三种标准,各自有不同的用途和特性。URI是一个统称,包含了URL和URN。URL用于描述资源的位置和访问方法,而URN用于唯一标识资源而不依赖于其位置。了解它们的区别和用途,有助于更好地设计和使用网络资源标识系统。
原理: URI是一个抽象概念,用于统一标识互联网中的资源。URI包括两类:URL和URN。URL描述了资源的位置和访问方法,而URN则用于唯一标识资源而不指定其位置。
组成: URI的组成与URL类似,但更为广泛,可以包含任何能够唯一标识资源的字符串。
  • scheme:标识符的命名方案。
  • identifier:根据特定方案确定的资源标识符。
示例
  • URL:https://www.example.com/path/to/resource
  • URN:urn:isbn:0451450523

URN(统一资源名称)

原理: URN是URI的一种,用于唯一标识资源,而不依赖其位置或访问方法。URN的设计目标是资源的持久性和唯一性,即使资源的位置发生变化,其URN也保持不变。
组成: URN的格式如下:
  • namespace identifier:命名空间标识符,定义了特定类别的URN。
  • namespace specific string:命名空间中的具体标识符。
示例
  • urn:方案名称。
  • isbn:命名空间标识符,表示国际标准书号。
  • 0451450523:具体的ISBN编号。

区别

  1. 定义
      • URI:统一资源标识符,是一个抽象概念,用于标识资源,可以是URL或URN。
      • URL:统一资源定位符,是一种具体的URI,提供资源的位置和访问方法。
      • URN:统一资源名称,是一种具体的URI,唯一标识资源而不依赖于其位置。
  1. 目的
      • URI:用于统一标识互联网中的所有资源。
      • URL:用于定位和访问资源。
      • URN:用于唯一标识资源,不考虑其位置或访问方法。
  1. 组成结构
      • URI:由方案和标识符组成,格式灵活。
      • URL:包含详细的资源位置和访问信息,如协议、主机、端口、路径等。
      • URN:包含命名空间和命名空间中特定的标识符,强调持久性和唯一性。
  1. 持久性
      • URI:可以是持久的(URN)或不持久的(URL)。
      • URL:资源的位置可能会改变,不保证持久性。
      • URN:设计为持久性标识符,即使资源位置改变,URN也保持不变。

HTTP&HTTPS

notion image
Go语言实现一个简单的返回“hello world”的 http 服务器

参考文献

注:本文大部分内容来源于《Go语言编程》这本书,非常感谢这本书在学习Go语言方面给我的指导!
注:To be Continued…… 🍊
☎️
有关这篇文章的任何问题,欢迎您在底部评论区留言,一起交流~ 🍊
 
Go基础语法-IGo并发编程
  • Twikoo
目录