目录
(2).为了避免上述的复制开销和直接修改原始数据,会使用切片来代替数组:
一、变量作用域
1.局部变量:
函数内定义的变量称为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。
package main
import "fmt"
func main() {
/* 声明局部变量 */
var a, b, c int
/* 初始化参数 */
a = 10
b = 20
c = a + b
fmt.Printf("结果: a = %d, b = %d and c = %d\n", a, b, c)
}
2.全局变量
函数外定义的变量称为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。
package main
import "fmt"
/* 声明全局变量 */
var g int
func main() {
/* 声明局部变量 */
var a, b int
/* 初始化参数 */
a = 10
b = 20
g = a + b
fmt.Printf("结果: a = %d, b = %d and g = %d\n", a, b, g)
}
3.形式参数
形式参数会作为函数的局部变量来使用。实例如下:
package main
import "fmt"
/* 声明全局变量 */
//var g int
/* 声明全局变量 */
var a int = 20
func main() {
/* main 函数中声明局部变量 */
var a int = 10
var b int = 20
var c int = 0
fmt.Printf("main()函数中 a = %d\n", a)
c = sum(a, b)
fmt.Printf("main()函数中 c = %d\n", c)
}
/* 函数定义-两数相加 */
func sum(a, b int) int {
fmt.Printf("sum() 函数中 a = %d\n", a)
fmt.Printf("sum() 函数中 b = %d\n", b)
return a + b
}
4.初始化局部和全局变量
不同类型的局部和全局变量的默认值为:
int为0
float32为0
pointer为nil
二、数组
1.声明数组
例如以下定义了数组 balance 长度为 10 类型为 float32:
var balance [10] float32
2.初始化数组
package main
import "fmt"
func main() {
//1.初始化数组
balance1 := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance1)
//如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
balance2 := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance2)
}
3.访问数组元素
package main
import "fmt"
func main() {
//3.访问列表元素,通过for循环方式遍历
var n [10]int /* n 是一个长度为 10 的数组 */
var i, j int
/* 为数组 n 初始化元素 */
for i = 0; i < 10; i++ {
n[i] = i + 100 /* 设置元素为 i + 100 */
}
/* 输出每个数组元素的值 */
for j = 0; j < 10; j++ {
fmt.Printf("Element[%d] = %d\n", j, n[j])
}
}
4.多维数组
多维数组是指数组的元素自身也是数组。这种结构可以用来表示二维矩阵或更高维度的数据结构。最常见的多维数组是二维数组,可以想象成一个表格,有行和列。
package main
import "fmt"
func main() {
// 创建一个2x3的二维数组
var twoD [2][3]int
// 填充数组
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
// 打印数组
fmt.Println("2D array:")
for _, row := range twoD {
for _, col := range row {
fmt.Printf("%d ", col)
}
fmt.Println()
}
}
5.向函数传递数组
在Go语言中,当向函数传递数组时,它会按值传递,这意味着函数接收数组参数的时候,实际上是接收的数组的一个副本,而不是原始数组的引用。这可能会导致性能问题,因为数组可能会很大,而拷贝整个数组需要时间和内存。因此,在Go中,更常见的做法是传递一个指向数组的指针,或者使用切片,后者在内部本质上就是一个数组指针的封装。
(1).向函数传递数组的简单示例:
package main
import "fmt"
// 函数接收一个整型数组的指针,这样可以直接修改原始数组
func modifyArray(a *[3]int) {
(*a)[1] = 10 // 修改数组的第二个元素
}
func main() {
// 声明并初始化一个整型数组
arr := [3]int{1, 2, 3}
// 打印原始数组
fmt.Println("Before:", arr)
// 向函数传递数组的地址
modifyArray(&arr)
// 打印修改后的数组
fmt.Println("After:", arr)
}
(2).为了避免上述的复制开销和直接修改原始数据,会使用切片来代替数组:
package main
import "fmt"
// 函数接收一个整型切片,可以直接修改底层的数组
func modifySlice(s []int) {
s[1] = 10 // 修改切片的第二个元素
}
func main() {
// 切片是对数组的引用
slice := []int{1, 2, 3}
// 打印原始切片
fmt.Println("Before:", slice)
// 向函数传递切片
modifySlice(slice)
// 打印修改后的切片
fmt.Println("After:", slice)
}
三、指针
1.指针的使用
一个指针变量指向了一个值的内存地址。
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
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)
}
2.空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
package main
import "fmt"
func main() {
//空指针
var ptr *int
fmt.Printf("ptr 的值为 : %x\n", ptr)
}
3.指针数组
在 Go 语言中,指针数组是一个数组,其每个元素都是指向某种类型值的指针。指针数组可以用来存储数据的地址而非数据本身,这样可以在不同的函数或方法间共享和修改数据,而不需要复制数据本身,这样可以提高效率,尤其是在处理大型结构体或其他需要避免复制的数据类型时。
(1).代码示例:
package main
import "fmt"
// 定义一个简单的结构体
type Vertex struct {
X, Y int
}
func main() {
// 创建一个包含两个元素的 Vertex 结构体数组
vertices := [2]Vertex{{1, 2}, {3, 4}}
// 创建一个指针数组,用于存放结构体的指针
var ptrs [2]*Vertex
// 将 vertices 数组中每个元素的地址分配给指针数组
for i := 0; i < len(vertices); i++ {
ptrs[i] = &vertices[i] // 取地址符 & 获取元素的地址
}
// 输出原始数组中的元素
fmt.Println("Original Vertex array:")
for _, v := range vertices {
fmt.Println(v)
}
// 修改指针数组指向的结构体字段
// 这将直接修改原始结构体数组中对应的数据
ptrs[0].X = 7
ptrs[1].Y = 8
// 再次输出原始数组中的元素
// 可以看到原始数组中的数据已经被更改
fmt.Println("\nModified Vertex array through pointers:")
for _, v := range vertices {
fmt.Println(v)
}
}
4.指向指针的指针
在 Go 语言中,你也可以有一个指向指针的指针,即一个二级指针。这是一个存储另一个指针地址的指针。这样的构造在多级间接引用时很有用,比如在链表、树或者是更复杂的数据结构中。
(1).代码示例:
package main
import "fmt"
func main() {
a := 100
p := &a // 指向 a 的指针
pp := &p // 指向 p 的指针,也就是指向指针的指针
fmt.Printf("Original value: %d\n", a)
fmt.Printf("Value via pointer: %d\n", *p)
fmt.Printf("Value via pointer to pointer: %d\n", **pp)
**pp = 200 // 通过指向指针的指针更改 a 的值
fmt.Printf("\nValue after modification via pointer to pointer: %d\n", a)
fmt.Printf("Value via pointer: %d\n", *p)
fmt.Printf("Value via pointer to pointer: %d\n", **pp)
}
在这个示例中,a
是一个整型变量,p
是一个指向 a
的指针,而 pp
是一个指向 p
的指针。
%d
是一个格式占位符,用于输出一个整数。&a
获取变量a
的内存地址。&p
获取指针p
的内存地址。*p
是间接引用或解引用p
,它给出了p
指向的变量的值。**pp
是两次解引用pp
,它给出了pp
指向的指针所指向的变量的值。
通过二级指针 pp
修改 a
的值是通过两次解引用实现的:首先解引用 pp
来访问 p
,然后解引用 p
来访问并修改 a
。
输出结果将展示通过直接访问、通过指针以及通过指向指针的指针访问和修改 a
的值。
5.指针作为函数参数
在Go语言中,将指针作为函数参数传递允许函数直接修改传入的变量的值。这与传值调用不同,在传值调用中,函数接收的参数是原始数据的副本,对这些参数的任何修改都不会影响原始数据。使用指针可以避免数据副本的开销,并且可以修改原始数据。
(1).代码示例:
package main
import "fmt"
// 定义一个函数,接收一个整型指针作为参数
func doubleValue(num *int) {
*num *= 2 // 解引用指针并更新它所指向的值
}
func main() {
value := 10
fmt.Printf("Original value: %d\n", value)
// 调用函数时,传入 value 的地址
doubleValue(&value)
// value 的值已经被函数 doubleValue 修改
fmt.Printf("Value after doubleValue function call: %d\n", value)
}
在这个示例中,doubleValue
函数接受一个指向 int
类型的指针 num
作为参数。在函数内部,我们使用解引用操作符 *
来访问 num
指针指向的值,并将其翻倍。在 main
函数中,我们创建了一个整型变量 value
,然后将它的地址传递给 doubleValue
函数。因为我们传递了 value
的地址,doubleValue
函数能够直接修改 value
的原始值。因此,在调用函数后,value
的值从 10 变成了 20。
参考资料: