Go语言基础 | 豆包MarsCode AI刷题

128 阅读16分钟

一、Go基础

1.1、基本数据类型

序号类型和描述
1布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码
3字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型

1.2、语言变量

Go 语言变量名由字母、数字、下划线组成,其中==首个字符==不能为数字。

声明如下:

var identifier type

一次声明多个变量:

var identifier1, identifier2 type

对于一个变量,如果声明的时候没有指定初始值,会以默认值的形式赋值。

其中:

  • 数值类型:0
  • 布尔类型:false
  • 字符串:""
  • 一下几种类型为nil
    • var a *int
    • var a []int
    • map 集合
    • chan 通道
    • 函数变量, var a func(string) int
    • 接口, var a error

当然,还可以使用:= 实现声明并赋值,注意,这个变量之前不能出现过,不然会报错。

==Tips== : 在JavaC++ 中,要做到交换两个基本数据类型的值,我们通常要定义一个swap函数,如下:

public void swap(int a, int b) {
    int t = a;
    a = b;
    b = t;
}

实现交换,但是在Go 语言中,我们无需这么做,只需要下面一行代码即可:

a, b = b, a

解释: 首先会把a,b的值取出来,依次放在第二个,第一个位置,然后按顺序赋值。

值引用与类型引用

Go语言中,基本数据类型(除了map)之外都是值类型,更复杂的数据通常会需要使用==多个字==,这些数据一般==使用引用类型==保存。

常量

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

声明如下:

const identifier [type] = value

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iotaconst关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。可以用作枚举值

const (
    a = iota
    b = iota
    c = iota
)

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

const (
    a = iota
    b
    c
)

进阶用法:

package main

import "fmt"

func main() {
    const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7,恢复计数
            i          //8
    )
    fmt.Println(a,b,c,d,e,f,g,h,i)
}

1.3、语言运算符

1.3.1、算术运算符

运算符描述实例
+相加A + B 输出结果 30
-相减A - B 输出结果 -10
*相乘A * B 输出结果 200
/相除B / A 输出结果 2
%求余B % A 输出结果 0
++自增A++ 输出结果 11
--自减A-- 输出结果 9

1.3.2、关系运算符

运算符描述实例
==检查两个值是否相等,如果相等返回 True 否则返回 False。(A == B) 为 False
!=检查两个值是否不相等,如果不相等返回 True 否则返回 False。(A != B) 为 True
检查左边值是否大于右边值,如果是返回 True 否则返回 False。(A > B) 为 False
<检查左边值是否小于右边值,如果是返回 True 否则返回 False。(A < B) 为 True
>=检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。(A >= B) 为 False
<=检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。(A <= B) 为 True

1.3.3、逻辑运算符

运算符描述实例
&&逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。(A && B) 为 False
||逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。(A || B) 为 True
!逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。!(A && B) 为 True

1.3.4、位运算符

运算符描述实例
&按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。(A & B) 结果为 12, 二进制为 0000 1100
|按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或(A | B) 结果为 61, 二进制为 0011 1101
按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。(A ^ B) 结果为 49, 二进制为 0011 0001
<<左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。A << 2 结果为 240 ,二进制为 1111 0000
>>右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。A >> 2 结果为 15 ,二进制为 0000 1111

==Note==:Go语言中位运算是以二进制补码的方式计算的。

1.3.5、赋值运算符

运算符描述实例
=简单的赋值运算符,将一个表达式的值赋给一个左值C = A + B 将 A + B 表达式结果赋值给 C
+=相加后再赋值C += A 等于 C = C + A
-=相减后再赋值C -= A 等于 C = C - A
*=相乘后再赋值C *= A 等于 C = C * A
/=相除后再赋值C /= A 等于 C = C / A
%=求余后再赋值C %= A 等于 C = C % A
<<=左移后赋值C <<= 2 等于 C = C << 2
>>=右移后赋值C >>= 2 等于 C = C >> 2
&=按位与后赋值C &= 2 等于 C = C & 2
^=按位异或后赋值C ^= 2 等于 C = C ^ 2
|=按位或后赋值C |= 2 等于 C = C | 2

1.3.6、其他运算符

运算符描述实例
&返回变量存储地址&a; 将给出变量的实际地址。
*指针变量。*a; 是一个指针变量

1.3.7、运算符优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

优先级运算符
5* / % << >> & &^
4+ - | ^
3== != < <= > >=
2&&
1||

1.3.8、笔记

指针变量 ***** 和地址值 & 的区别:

指针变量保存的是一个地址值,会分配独立的内存来存储这个整型数字,当变量前面有*标识的时候,才等同于&的用法,也就是取到实际的值,否则会输出地址值。

func main() {
   var a int = 4
   var ptr *int
   ptr = &a
   println("a的值为", a);    // 4
   println("*ptr为", *ptr);  // 4
   println("ptr为", ptr);    // 824633794744
}

1.4、条件运算符

语句描述
if 语句if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
if...else 语句if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
if 嵌套语句你可以在 ifelse if 语句中嵌入一个或多个 ifelse if 语句。
switch 语句switch 语句用于基于不同条件执行不同动作。
select 语句select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

==Note==

1、if语句用作返回值的时候,要保证{}\{\}外至少有一个返回值。

如下面代码在某些编辑器里面可能会报编译错误

func gt3(n int) bool {
	if n > 3 {
		return true
	} else {
		return false
	}
}

2、else 语句应该紧跟在 if 语句的 } 后面,不能换行

下面代码会报编译错误

func gt3(n int) bool {
	if n > 3 {
		return true
	} 
    else {
		return false
	}
}

1.5、循环语句

Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。和 C 语言的 for 一样:

for init; condition; post { }

Go语言只有for循环,没有while关键字,下面效果等同于while循环

for condition { }

死循环:

for {}

1.5.1、循环控制语句

控制语句描述
break 语句经常用于中断当前 for 循环或跳出 switch 语句
continue 语句跳过当前循环的剩余语句,然后继续进行下一轮循环。
goto 语句将控制转移到被标记的语句。

1.6、函数

函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

1.6.1、函数参数

传递类型描述
值传递值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

1.6.2、函数用法

1、函数作为实参

Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。下面用这个特性实现回调。

package main

import "fmt"
// 声明一个函数类型
type cb func(int) int
func testCallBack(x int, f cb) {
	f(x)
}
func callBack(x int) int {
	fmt.Printf("我是回调1, x:%v\n", x)
	return x
}
func main() {
	testCallBack(1, callBack)
	testCallBack(2, func(x int) int {
		fmt.Printf("我是回调2, x:%v\n", x)
		return x
	})
}

2、匿名函数(闭包)

Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。匿名函数是一种==没有函数名==的函数,通常用于在==函数内部==定义函数,或者==作为函数参数==进行传递。

func getSequence() func() int {
	i := 0
	return func() int {
		i += 1
		return i
	}
}
func main() {
	/* nextNumber 为一个函数,函数 i 为 0 */
	nextNumber := getSequence()

	/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
	fmt.Println(nextNumber())
	fmt.Println(nextNumber())
	fmt.Println(nextNumber())
}

相信不少人疑惑这里ii 为什么会自增。因为闭包允许函数访问并修改其外部作用域中的变量,即使外部函数已经返回。在这个例子中,getSequence 函数返回的匿名函数==持有==对 i 的引用,因此每次调用这个匿名函数时,i 的值都会增加。

虽然返回值没有被显式地接收,但它被 fmt.Println 函数接收并打印出来。fmt.Println 函数会接收传入的参数并将其打印到标准输出。

  1. nextNumber := getSequence() 调用 getSequence 函数,该函数返回一个匿名函数。这个匿名函数持有对 getSequence 函数内部 i 变量的引用。
  2. fmt.Println(nextNumber()) 调用 nextNumber 函数(即返回的匿名函数),这会使 i 自增1并返回新的值,然后 fmt.Println 打印这个值。
  3. 每次调用 nextNumber() 都会执行相同的操作:i 增加1,返回新的值,并打印出来。

因此,尽管返回值没有被显式地接收,它实际上被 fmt.Println 接收并用于打印。这就是为什么每次调用 nextNumber() 时,i 都会加1并打印出不同的值。

1.6.3、方法

Go 语言中同时有函数和方法。一个方法就是一个==包含了接受者的==函数,接受者可以是==命名类型==或者==结构体类型==的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。

格式如下:

func (variable_name variable_data_type) function_name() [return_type]{
   /* 函数体*/
}

示例:

package main

import (
   "fmt"  
)

/* 定义结构体 */
type Circle struct {
  radius float64
}

func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

深度解释:

在Go语言中没有面向对象,但是Java、C++等语言中,实现类的方法的做法都是编译器==隐式==的给函数加一个thisthis 指针,而在Go里面,这个指针需要我们显示的指定出来,也就是上面的接受者

例如,在C++中是这样的:

class Circle {
  public:
    float getArea() {
       return 3.14 * radius * radius;
    }
  private:
    float radius;
}

// 其中 getArea 经过编译器处理大致变为
float getArea(Circle *const c) {
  ...
}

而在Go语言中如下:

func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

1.7、数组

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:

var arrayName [size]dataType

初始化:

var numbers [5]int // 全部是0,也就是int的默认值
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}

如果数组长度不确定,可以使用... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// or
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

1.8、指针

Go 语言的取地址符是 &\&,放到一个变量前使用就会返回相应变量的内存地址。==指针==变量指向了一个==值的内存地址==。在使用指针前你需要声明指针。指针声明格式如下:

var var_name *var-type

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

package main

import "fmt"

func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */
   ip = &a  /* 指针变量的存储地址 */
   fmt.Printf("a 变量的地址是: %x\n", &a  )
   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}

1、指针数组

数组的每一个元素值都是一个指针。

var ptr [MAX]*int;

示例:

package main

import "fmt"

const MAX int = 3

func main() {
   a := []int{10,100,200}
   var i int
   var ptr [MAX]*int;

   for  i = 0; i < MAX; i++ {
      ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
   }

   for  i = 0; i < MAX; i++ {
      fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
   }
}

==Note==:使用Range循环遍历创建的时候要注意,不能直接将取出来的值赋值给指针数组。

const max = 3

func main() {
    number := [max]int{5, 6, 7}
    var ptrs [max]*int //指针数组
    //将number数组的值的地址赋给ptrs
    for i, x := range &number {
        ptrs[i] = &x
    }
    for i, x := range ptrs {
        fmt.Printf("指针数组:索引:%d 值:%d 值的内存地址:%d\n", i, *x, x)
    }
}
// out:
//指针数组:索引:0 值:7 值的内存地址:824634204304
//指针数组:索引:1 值:7 值的内存地址:824634204304
//指针数组:索引:2 值:7 值的内存地址:824634204304

可以发现,内存地址都是一样的,为什么呢?

这个问题是range循环的实现逻辑引起的。跟for循环不一样的地方在于range循环中的x变量是临时变量。range循环只是将值拷贝到x变量中,这个x变量内存地址不会变,因此内存地址都是一样的。

1.9、结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。定义格式如下:

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

声明格式:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

访问结构体成员:

结构体.成员名

1.10、切片

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义切片:

var identifier []type

或者使用make函数:

var slice1 []type = make([]type, len)
// or
slice1 := make([]type, len)

同时还可以指定容量,capacity是可选参数:

make([]T, lengthm capacity)

初始化切片:

s :=[] int {1,2,3 } 
// 从数组中获取
s := arr[startIndex:endIndex] // 类似python

len()   or   cap()len() \ \ \ or \ \ \ cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少.

切片截取

可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]

比如上面的ss[1:] 可以获取除了第一个之外的所有元素。

==Note==:切片截取操作也会改变容量。

例如:

package main
import "fmt"

unc main() {
   numbers := []int{0,1,2,3,4,5,6,7,8}
   printSlice(numbers)
   fmt.Println("numbers ==",numbers)
   numbers1 := numbers[1:4]
   printSlice(numbers1)
   fmt.Println("numbers[:3] ==", numbers[:3])
   numbers2 :=numbers[4:]
   printSlice(numbers2)
   numbers3 := make([]int,0,5)
   printSlice(numbers3)
   numbers4 := numbers[:2]
   printSlice(numbers4)
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// out:
// len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
// numbers == [0 1 2 3 4 5 6 7 8]
// len=3 cap=8 slice=[1 2 3]
// numbers[:3] == [0 1 2]
// len=5 cap=5 slice=[4 5 6 7 8]
// len=0 cap=5 slice=[]
// len=2 cap=9 slice=[0 1]

这是因为切片是由三部分组成:

  • 指向底层数组的指针
  • len
  • cap

假如底层数组的长度是kk, 那么其切片slice[i:j]slice[i:j]的计算公式如下:

  • 长度:jij-i
  • 容量:kik-i

1.11、Range函数

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回==元素的索引==和==索引对应的值==,在集合中返回 keyvaluekey-value 对。

for key, value := range oldMap {
    newMap[key] = value
}
	
for i, v := range arr {
	// ...
}
for _, v := range arr {
	// ...
}