Golang的静态强类型、编译型、并发型

大家好,这里是Good Note,关注 公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Go语言的基础知识,包括数据类型,深浅拷贝,编程范式,Go语言是一种静态(静态类型语言 和 静态语言)强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

文章目录

1. Go 语言基础知识

下面将详细介绍 Go 语言的理论知识,包括数据类型、深拷贝与浅拷贝、以及如何在函数中传递数据。

数据类型

Go 语言是强类型的,意味着变量在使用时必须明确指定类型。Go 语言有很多内建的数据类型,主要可以分为以下几类:

基本数据类型
  • 布尔类型(bool) :表示真(true)或假(false)。
  • 数字类型
    • 整数:intint8int16int32int64uintuint8uint16uint32uint64
    • 浮点数:float32float64
    • 复数:complex64complex128
  • 字符类型(rune 和 byte)
    • runeint32 的别名,表示一个 Unicode 字符,通常用于表示多字节字符(如中文字符)。
    • byteuint8 的别名,表示单个字节(8 位数据),用于处理 ASCII 字符、字节流、二进制数据等。
  • 字符串类型(string):由一系列字符组成,Go 中的字符串是不可变的。
基本数据类型
  • 布尔型(bool) : 用于表示truefalse值。
  • 整型(int、int8、int16、int32、int64) : 用于表示整数,可以是有符号的整数(int)或无符号的整数(uint)。
  • 浮点型(float32、float64): 用于表示带小数的数字。
  • 字符型(rune) : 用于表示Unicode字符,rune实际上是int32的别名。
  • 复数型(complex64、complex128): 用于表示复数,复数由实部和虚部组成。
  • 字符串(string): 用于表示文本数据,是一个不可变的字符序列。
复合数据类型
  • 非引用类型:

    • 数组(Array) :Go 中的数组具有固定大小,数组类型由元素类型和数组长度共同决定。例如:var arr [5]int

    • 结构体(Struct) :Go 中的结构体是一种自定义的数据类型,用于聚合不同类型的多个字段。

      • 定义结构体:
      go 复制代码
      type Person struct {
          Name string
          Age  int
      }
  • 引用类型:

    • 指针: 存储变量的内存地址。
    • 切片(Slice):切片是 Go 中动态数组的抽象。切片并不包含数组的长度信息,它是一个指向数组的引用,可以动态改变大小。
    • 映射(Map) :Go 中的映射类似于其他语言中的哈希表(字典),用于存储键值对。可以通过 make 创建一个映射。
    • 通道(channel): 是一种用于在 goroutine 之间进行通信的引用类型。它是一种管道,可以让一个 goroutine 发送数据到另一个 goroutine,从而实现数据传递和同步。
    • 函数: 函数本身也可以作为类型,传递和使用。
  • 接口(Interface):Go 中的接口是一种类型,定义了一组方法,但不需要实现这些方法。任何类型只要实现了接口中的所有方法,就隐式地实现了该接口。

深拷贝与浅拷贝

在 Go 中,"拷贝" 是指将一个变量的值传递到另一个变量。根据拷贝的方式不同,可以分为浅拷贝和深拷贝。

浅拷贝(Shallow Copy)

浅拷贝是指直接复制一个变量的值,如果这个变量是引用类型(如切片、映射、指针等),则复制的是引用地址,即新变量和原变量指向同一内存地址,修改其中一个变量的内容会影响到另一个。

  • 对于切片、映射、指针等引用类型,执行浅拷贝时,两个变量会共享底层的数据结构。

示例:

go 复制代码
a := []int{1, 2, 3}
b := a // 浅拷贝,b 和 a 指向同一个切片
b[0] = 100
fmt.Println(a[0]) // 输出 100,a 和 b 是共享内存的
深拷贝(Deep Copy)

深拷贝是指创建一个完全独立的新变量,并且复制变量的值以及其所有引用的对象。如果变量是引用类型,深拷贝会复制其底层的数据,而不是引用。

  • 需要手动实现深拷贝,尤其是对于复杂的结构体(例如结构体内包含切片、映射等引用类型)。

示例:

go 复制代码
a := []int{1, 2, 3}
b := make([]int, len(a))
copy(b, a) // 通过 copy 函数实现深拷贝
b[0] = 100
fmt.Println(a[0]) // 输出 1,a 和 b 是独立的

对于结构体,如果结构体包含引用类型字段,也可以通过 deepcopy 函数实现深拷贝。

函数中传递数据

在 Go 语言中,所有的函数参数传递 都是 值传递 ,也就是说,传递的是参数的副本,即使是传递指针,传递的也是指针的副本。Go中的引用传递间接实现的。

  • Go中的引用传递 ,通常是指通过传递指针来间接 修改原始数据,传递的是指针的副本,而不是原始的内存地址。
  • 两个指针的地址是不同的,一个是原始指针的地址,另一个是指针副本的地址。
  • 它们指向的是相同的内存区域 ,也就是说,指针副本和原始指针都指向同一块数据的内存地址。,因此通过指针可以间接修改原始数据。修改的是 指针指向的内存内容,而不是指针本身。
值传递(Pass by Value)

值传递意味着在函数调用时,将变量的副本传递给函数。如果函数修改了参数的值,它不会影响到原始变量。

示例:

go 复制代码
func modify(x int) {
    x = 10
}

func main() {
    a := 5
    modify(a)
    fmt.Println(a) // 输出 5,a 的值未被改变
}
引用传递(Pass by Reference)

引用传递是间接实现的,意味着将指针的副本 传递给函数。函数可以通过副本指针访问和修改原始变量的值。

传递的是a 的指针的副本p,p的地址和a的地址不一样,但是p的值是a的地址。

示例:

go 复制代码
package main

import "fmt"

func modify(p *int) {
	// 打印指针 p 的地址:p 变量的内存地址
	fmt.Printf("指针 p 的地址:%p\n", &p) // 打印指针变量 p 的内存地址
	// 打印指针 p 的值:即指针 p 存储的内存地址(p 的值应该是 a 的地址)
	fmt.Printf("指针 p 的值(应该是 a 的地址):%p\n", p) // 打印指针 p 存储的地址(即 p 指向的内存地址)

	// 修改指针 p 指向的值
	*p = 10
}

func main() {
	a := 5
	// 打印 a 的值
	fmt.Println("传递之前 a 的值:", a)

	// 打印 a 的地址:a 变量存储的内存地址
	fmt.Printf("a 的地址:%p\n", &a)

	// 传递 a 的地址给 modify 函数
	modify(&a)

	// 打印修改后的 a 的值
	fmt.Println("传递之后 a 的值:", a)
}
Go 中都是值传递的优势

Go 中所有函数参数的传递机制都是值传递(pass-by-value),但 Go 提供了指针机制,允许通过指针修改数据。这种机制与很多其他语言(如 C)不同,后者通常会有显式的"引用传递"(pass-by-reference)。

  • 简洁性:值传递的方式比较简单,避免了很多指针错误,如悬空指针、内存泄漏等问题。
  • 内存安全性:Go 中的垃圾回收机制让指针的使用更加安全,通过值传递避免了很多复杂的内存管理问题。

总结

  • 数据类型:Go 提供了多种基本数据类型、复合数据类型(如切片、数组、映射、结构体等)和接口类型。
  • 深拷贝与浅拷贝:浅拷贝指的是对引用类型的复制,复制的是引用地址,深拷贝则是完全独立的复制,复制的是数据本身。
  • 函数传参:Go 支持值传递和引用传递。值传递会传递数据副本,而引用传递会传递数据的地址(指针),从而允许修改原始数据。

这种设计理念让 Go 语言在并发编程、高效内存管理和简洁易用性方面都表现优异。

2. Go 语言与编程范式对比

Go语言是一种静态(静态类型语言 和 静态语言)强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

编译型语言 vs 解释型语言

编译型语言
  • 编译型语言是需要通过编译器将源代码编译成机器码,之后才能执行的语言。编译过程通常包括编译(compile)和链接(linking)两个步骤。
    • 编译:把源代码编译成机器码
    • 链接:把各个模块的机器码和依赖库串连起来生成可执行文件。
以Go为例

Go语言从源文件到可执行目标文件的转化过程如下:

来源于:Go 程序员面试笔试宝典

这张图描述了 Go 语言 编译系统的整个工作流程,将 Go 源代码 转换为 可执行目标程序 的过程。它分为四个主要阶段:编译汇编链接 和生成最终的可执行程序。

  • 编译:翻译为汇编代码。
  • 汇编:转换为二进制机器码。
  • 链接:解决依赖,生成完整的可执行文件。

各模块及其功能:

  1. 源代码(hello.go):Go 编写的源代码。
  2. 编译器 :将源代码转换为汇编代码(hello.s)。
  3. 汇编器 :将汇编代码转换为二进制的目标文件(hello.o)。
  4. 链接器 :将目标文件 hello.o 与依赖库(如 fmt.o)进行链接,生成最终的可执行程序(hello)。

各阶段详细解释如下:

  1. 源代码阶段(hello.go)
  • 输入 :Go 源代码文件 hello.go
  • 文本格式:源代码是可读的文本文件,包含 Go 语言编写的代码。
  • 任务:提供程序的逻辑与功能实现。

  1. 编译器阶段(hello.s)
  • 编译器的作用 :Go 编译器会将 Go 源代码(hello.go)翻译成 汇编程序hello.s)。
  • 输出hello.s 文件,包含汇编代码,是与机器底层指令相关的文本。
  • 文本格式:汇编代码仍是人类可读的,但更加接近于底层硬件的操作。

  1. 汇编器阶段(hello.o)
  • 汇编器的作用 :将汇编代码(hello.s)转换为 二进制机器码 ,生成目标文件(hello.o)。
  • 输出hello.o 文件(目标程序),是 可重定位目标程序,尚未与库进行链接。
  • 二进制格式hello.o 是不可直接运行的二进制文件,需要进一步链接。

  1. 链接器阶段(hello 和 fmt.o)
  • 链接器的作用
    • hello.o 文件与标准库(如 fmt.o)或其他依赖的目标文件进行链接。
    • 解决函数调用和变量引用,将所有二进制代码组合为一个完整的程序。
  • 输入
    • hello.o:主程序的可重定位目标文件。
    • fmt.o:标准库 fmt 的目标文件(用于 fmt.Println 等函数)。
  • 输出 :最终的可执行文件 hello
  • 二进制格式 :生成的 hello 是一个可以直接运行的二进制目标程序。

解释型语言
  • 解释型语言的程序不需要预先编译,相比编译型语言省了道工序,解释性语言在运行程序的时候才逐行翻译。

编译型语言包括:C、C++、Delphi、Pascal、Fortran、Go

解释型语言包括:Basic、javascript、python、PHP

说明:

  • Java 并不是完全的编译型语言。Java 实际上是 编译-解释型语言,它首先将源代码编译成字节码(.class 文件),然后通过 Java 虚拟机(JVM)解释执行字节码。Java 的编译过程并不像 C/C++ 那样直接生成机器码,所以它不应被归类为纯粹的编译型语言。
  • Go 是编译型语言,它直接将源代码编译成机器码,并不依赖于虚拟机等中间层。
  • PHPPython 是解释型语言,但有一些现代的优化手段,例如 Python 使用 .pyc 文件和 JIT(即时编译)技术提升性能。

动态语言 vs 静态语言

动态语言
  • 动态语言:在运行时可以修改代码结构,如新增函数、修改对象等。
静态语言
  • 静态语言:在运行时代码结构不可改变。

动态语言:JavaScript、PHP、Python、Ruby、Erlang

静态语言:Java、C、C++、C#、Objective-C、Go

动态类型语言 vs 静态类型语言

动态类型语言
  • 动态类型语言:在运行时进行数据类型检查。
静态类型语言
  • 静态类型语言:在编译时确定数据类型。

动态类型语言:Python、Ruby、JavaScript、PHP、Perl

静态类型语言:C、C++、C#、Java、Go、Objective-C、Swift

强类型语言 vs 弱类型语言

强类型语言
  • 强类型语言:类型严格,变量的数据类型一旦确定就不能更改,不能随便进行隐式类型转换。
弱类型语言
  • 弱类型语言:类型较为宽松,可以进行隐式类型转换。

强类型语言:Java、C#、Python、Objective-C、Ruby、Go、C、C++(注意:C 和 C++ 在某些情况下表现为弱类型,但总体上它们是强类型语言)

弱类型语言:JavaScript、PHP

说明:

  • C 和 C++ 确实可以进行隐式类型转换(如将字符赋值给整型变量),这使得它们在某些情况下表现得像 弱类型语言 。但整体上它们仍然是 强类型语言,因为在大多数情况下,它们要求显式地进行类型转换,且类型检查比较严格。

动静态语言与动静态类型的区分

动静态语言:是指运行时代码结构是否可以改动

动静态类型:是检查数据类型的时机是在运行时还是运行前

强弱类型:是指数据类型是否可以改变。

3. Go 语言面向对象特性与 Java 对比

1. 继承

Java

Java 继承是通过父类和子类的关系来实现,子类通过 extends 关键字继承父类后,子类拥有父类所有非 private 的属性和方法。

区别(重载 vs 重写)

  • 重载(Overloading):是方法名相同,但参数不同(包括参数类型、个数、顺序)。这发生在同一个类中。
  • 重写(Overriding):是子类重新定义父类的某个方法,方法签名(名称、参数类型、返回值类型)必须相同。
Go

Go 中没有显式的继承机制,但可以通过 组合 (embedding)来实现类似继承的功能。在 Go 中,struct 可以嵌入其他 struct,嵌入的 struct 的字段和方法可以直接访问(interface也可以继承,但大多数情况下,interface用做多态)。

示例代码

go 复制代码
package main

import "fmt"

type People struct {
	Name string
	Age  int
}

type Student struct {
	People  // 结构体 People 的嵌入
	Score int
}

func main() {
	var lcc Student
	lcc.People.Age = 18
	lcc.Name = "lichuachua"
	lcc.Score = 100
	fmt.Println(lcc) // {{lichuachua 18} 100}
}

2. 封装

Java

Java 通过 publicprivateprotected 控制对类的成员的访问权限,实现封装。

Go

Go 通过首字母是否大写来判断访问权限:

  • 首字母大写为 public,可以被其他包访问。
  • 首字母小写为 private,它是私有的,只能在同一包内访问。

3. 多态

Java

Java 的多态通过继承、重写和父类引用指向子类对象实现。

  • 继承(或接口实现):使得子类继承或实现父类/接口的行为。
  • 方法重写:允许子类提供特定的行为,重写父类的行为。
  • 父类引用指向子类对象:通过父类或接口引用指向子类对象,动态调用子类的方法,从而实现多态性。
Go

Go 通过接口(interface) 来实现多态,结构体实现接口的所有方法,就可以认为它实现了该接口。这使得 Go 的多态更加灵活和松耦合。

Go 中没有类的概念,只有接口(interface)和结构体(struct)。一个结构体只要实现了接口中的所有方法,就可以被认为实现了该接口 ,这与 Java 的 implements 关键字不同。

示例代码

go 复制代码
package main

import "fmt"

type Animals interface {
	Say()
}

type Dog struct{}
type Cat struct{}

func (d Dog) Say() {
	fmt.Println("wangwang")
}

func (c Cat) Say() {
	fmt.Println("miaomiao")
}

func main() {
	var d Dog
	d.Say()  // 输出:wangwang

	var c Cat
	c.Say()  // 输出:miaomiao

	// 使用接口变量,可以接受任何实现了 Say() 方法的类型
	var a Animals
	a = d
	a.Say()  // 输出:wangwang
	a = c
	a.Say()  // 输出:miaomiao
}

说明

  • 通过接口 Animals 和实现了 Say() 方法的 DogCat 类型,Go 实现了多态的特性。在 main 函数中,接口类型的变量 a 可以存储任何实现了 Say() 方法的类型(如 DogCat)。

结论

  • 继承:Go 使用组合(embedding)来实现类似继承的效果,Java 使用类继承。Go 中没有直接的继承机制。
  • 封装:Java 使用访问修饰符控制访问权限,Go 使用首字母大小写来控制访问权限。
  • 多态 :Java 通过继承、重写和父类引用来实现多态,Go 通过接口(interface)来实现多态,Go 的多态更加灵活,没有显式的 implements 关键字。

GOROOT、GOPATH和 Go Modules

  • GOROOT:Go 的安装目录,通常不需要修改。
  • GOPATH :Go 项目的工作目录,存放源代码、依赖和编译结果。在 Go 1.11 以后,Go Modules 的引入使得 GOPATH 变得不再是强制要求,开发者可以自由选择工作目录。
  • Go Modules :在 Go 1.11 以后成为默认的依赖管理方式,可以绕过 GOPATH,直接在任何目录下进行开发。

GOROOT 和 GOPATH 的区别

属性 GOROOT GOPATH
作用 指定 Go 的安装目录,包括 Go 编译器和标准库。 指定 Go 项目的工作目录,包含源代码、依赖包、可执行文件等。
目录结构 包含 Go 编译器、标准库、工具等。 包含项目代码、第三方包、编译结果等。
设置方式 通常由 Go 安装自动配置。 由用户设置并用于存放代码和依赖。
修改权限 通常不需要修改。 需要开发者根据项目需要设置并修改。
目录位置 默认安装在 /usr/local/go(Linux/Mac) 或 C:\Go(Windows)。 默认是 $HOME/go(Linux/Mac) 或 %USERPROFILE%\go(Windows)。
包含内容 Go 的核心工具和标准库。 Go 项目的源代码、第三方库、编译后的可执行文件等。

Go Modules 引入后的变化

自 Go 1.11 引入 Go Modules 后,GOPATH 的使用发生了一些变化。Go Modules 可以让你在任何目录下工作,而不再需要将项目放在 GOPATH 内。Go Modules 改变了 Go 的包管理方式,使得 GOPATH 的作用逐渐减小,Go 开发者可以脱离 GOPATH 目录来管理项目。

Go Modules 的变化
  • 不再依赖于 GOPATH :你可以将代码存放在任何地方,而不必在 GOPATH/src 中。
  • 项目级依赖管理 :使用 go mod 来管理项目的依赖关系。

Go 1.16 后,Go Modules 成为了默认的依赖管理方式,GOPATH 变得不再那么重要,新的 Go 项目几乎都推荐使用 Go Modules。

示例:使用 Go Modules

在 Go Modules 模式下,你可以在任何目录下初始化项目并开始使用:

bash 复制代码
go mod init <module-name>  # 初始化 Go Modules
go mod tidy               # 下载并整理依赖

示例结构

假设你有一个基本的 Go 项目,使用 Go Modules 时,项目结构可能如下:

bash 复制代码
myproject/
├── go.mod       # Go Modules 文件,记录模块的依赖
├── main.go      # Go 源代码文件
└── go.sum       # Go Modules 校验文件

在 Go 1.11 之前,你的项目需要像这样放在 GOPATH/src 下:

bash 复制代码
GOPATH/
└── src/
    └── myproject/
        └── main.go

但随着 Go Modules 的引入,这种要求不再存在,你可以将项目放在任意目录中,只需使用 go mod init 来初始化项目。

历史文章

MySQL数据库

MySQL数据库

Redis

Redis数据库笔记合集

Golang

  1. Golang笔记------切片与数组
  2. Golang笔记------hashmap
相关推荐
NCIN EXPE2 小时前
redis 使用
数据库·redis·缓存
MongoDB 数据平台2 小时前
为编码代理引入 MongoDB 代理技能和插件
数据库·mongodb
lUie INGA2 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
极客on之路2 小时前
mysql explain type 各个字段解释
数据库·mysql
代码雕刻家2 小时前
MySQL与SQL Server的基本指令
数据库·mysql·sqlserver
lThE ANDE2 小时前
开启mysql的binlog日志
数据库·mysql
hERS EOUS2 小时前
nginx 代理 redis
运维·redis·nginx
yejqvow122 小时前
CSS如何控制placeholder文字的颜色_使用--placeholder伪元素
jvm·数据库·python
oLLI PILO2 小时前
nacos2.3.0 接入pgsql或其他数据库
数据库
geBR OTTE2 小时前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端