当前位置: 首页 > news >正文

四、常量指针其他

常量&指针&其他

    • 1. 常量
      • 1.1 iota 常量生成器
    • 2. 指针
      • 2.1 如何理解指针
      • 2.2 使用指针修改值
      • 2.3 创建指针的另一种方法
      • 2.4 指针小案例
    • 3. 变量的生命周期
    • 4. 类型别名
    • 5. 注释
    • 6. 关键字和标识符
    • 7. 运算符优先级
    • 8. 字符串与其他数据类型的转换
    • 9. 练习:开发一款游戏

1. 常量

Go语言中的常量使用关键字const定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型数字型(整数型、浮点型和复数)和字符串型

由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式。

声明格式:

const name [type] = value

例如:

const pi = 3.14159

type可以省略

和变量声明一样,可以批量声明多个常量:

const (e  = 2.7182818pi = 3.1415926
)

所有常量的运算都可以在编译期完成,这样不仅可以减少运行时的工作,也方便其他代码的编译优化,当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。

常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex 和 unsafe.Sizeof。

因为它们的值是在编译期就确定的,因此常量可以是构成类型的一部分

如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。例如:

const (a = 1bc = 2d
)
fmt.Println(a, b, c, d) // "1 1 2 2"

1.1 iota 常量生成器

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。

在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加1

比如,定义星期日到星期六,从0-6

const (Sunday  = iota //0MondayTuesdayWednesdayThursdayFridaySaturday  //6
)

2. 指针

指针(pointer)在Go语言中可以被拆分为两个核心概念:

  • 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
  • 切片,由指向起始元素的原始指针、元素数量和容量组成。

受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。

同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。

切片比原始指针具备更强大的特性,而且更为安全。

切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。

2.1 如何理解指针

var a int = 10

如果用大白话来解释上述语句:

在内存中开辟了一片空间,空间内存放着数值10,这片空间在整个内存当中,有一个唯一的地址,用来进行标识,指向这个地址的变量就称为指针

如果用类比的说明:

内存比作酒店,每个房间就是一块内存,上述代码表示为:定了一间房间a,让10住进了房间,房间有一个门牌号px,这个px就是房间的地址,房卡可以理解为就是指针,指向这个地址。

一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。

当一个指针被定义后没有分配到任何变量时,它的默认值为 nil

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。

Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:

//其中 v 代表被取地址的变量,变量 v 的地址使用变量 ptr 进行接收,ptr 的类型为*T,称做 T 的指针类型,*代表指针。
ptr := &v    // v 的类型为 T
package main
import ("fmt"
)
func main() {var cat int = 1var str string = "我是码神"fmt.Printf("%p %p", &cat, &str)
}

变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址

当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值

// 指针与变量var room int = 10  // room房间 里面放的 变量10var ptr = &room  // 门牌号px  指针  0xc00000a0a8fmt.Printf("%p\n", &room)  // 变量的内存地址 0xc00000a0a8fmt.Printf("%T, %p\n", ptr, ptr)  // *int, 0xc00000a0a8fmt.Println("指针地址",ptr)   // 0xc00000a0a8fmt.Println("指针地址代表的值", *ptr)  // 10

取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址。
  • 对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。

2.2 使用指针修改值

通过指针不仅可以取值,也可以修改值。

package mainfunc main(){// 利用指针修改值var num = 10modifyFromPoint(num)fmt.Println("未使用指针,方法外",num)var num2 = 22newModifyFromPoint(&num2)  // 传入指针fmt.Println("使用指针 方法外",num2)
}func modifyFromPoint(num int)  {// 未使用指针num = 10000fmt.Println("未使用指针,方法内:",num)
}func newModifyFromPoint(ptr *int)  {// 使用指针*ptr = 1000   // 修改指针地址指向的值fmt.Println("使用指针,方法内:",*ptr)
}

2.3 创建指针的另一种方法

Go语言还提供了另外一种方法来创建指针变量,格式如下:

new(类型)
str := new(string)
*str = "我是码神——Go语言教程"
fmt.Println(*str)

new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。

2.4 指针小案例

获取命令行的输入信息

Go语言内置的 flag 包实现了对命令行参数的解析,flag 包使得开发命令行工具更为简单。

package main
// 导入系统包
import ("flag""fmt"
)
// 定义命令行参数
var mode = flag.String("mode", "", "fast模式能让程序运行的更快")func main() {// 解析命令行参数flag.Parse()fmt.Println(*mode)
}

3. 变量的生命周期

变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。

变量的生命周期与变量的作用域有不可分割的联系:

  1. 全局变量:它的生命周期和整个程序的运行周期是一致的;
  2. 局部变量:它的生命周期则是动态的,从创建这个变量的声明语句开始,到这个变量不再被引用为止;
  3. 形式参数和函数返回值:它们都属于局部变量,在函数被调用的时候创建,函数调用结束后被销毁。

go的内存中应用了两种数据结构用于存放变量:

  1. 堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态加入到堆上(堆被扩张)。当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减);
  2. 栈(stack):栈又称堆栈, 用来存放程序暂时创建的局部变量,也就是我们函数的大括号{ }中定义的局部变量。

栈是先进后出,往栈中放元素的过程,称为入栈,取元素的过程称为出栈。

栈可用于内存分配,栈的分配和回收速度非常快

在程序的编译阶段,编译器会根据实际情况自动选择或者上分配局部变量的存储空间,不论使用 var 还是 new 关键字声明变量都不会影响编译器的选择。

var global *int
func f() {var x intx = 1global = &x
}
func g() {y := new(int)*y = 1
}

上述代码中,函数 f 里的变量 x 必须在堆上分配,因为它在函数退出后依然可以通过包一级的 global 变量找到,虽然它是在函数内部定义的。

用Go语言的术语说,这个局部变量 x 从函数 f 中逃逸了。

相反,当函数 g 返回时,变量 y 不再被使用,也就是说可以马上被回收的。因此,y 并没有从函数 g 中逃逸,编译器可以选择在栈上分配 *y 的存储空间,也可以选择在堆上分配,然后由Go语言的 GC(垃圾回收机制)回收这个变量的内存空间。

4. 类型别名

类型别名是 Go 1.9 版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。

格式:

//TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型,就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
type TypeAlias = Type

还有一种是类型定义:

//定义Name为Type类型 ,定义之后 Name为一种新的类型
type Name Type

类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区别有哪些呢?

package main
import ("fmt"
)
// 将NewInt定义为int类型
type NewInt int
// 将int取一个别名叫IntAlias
type IntAlias = int
func main() {// 将a声明为NewInt类型var a NewInt// 查看a的类型名 main.NewIntfmt.Printf("a type: %T\n", a)// 将a2声明为IntAlias类型var a2 IntAlias// 查看a2的类型名 int //IntAlias 类型只会在代码中存在,编译完成时,不会有 IntAlias 类型。fmt.Printf("a2 type: %T\n", a2)
}

5. 注释

Go语言的注释主要分成两类,分别是单行注释和多行注释。

  • 单行注释简称行注释,是最常见的注释形式,可以在任何地方使用以//开头的单行注释;
  • 多行注释简称块注释,以/*开头,并以*/结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。

单行注释的格式如下所示

//单行注释

多行注释的格式如下所示

/*
第一行注释
第二行注释
...
*/

每一个包都应该有相关注释,在使用 package 语句声明包名之前添加相应的注释,用来对包的功能及作用进行简要说明。

同时,在 package 语句之前的注释内容将被默认认为是这个包的文档说明。一个包可以分散在多个文件中,但是只需要对其中一个进行注释说明即可。

6. 关键字和标识符

关键字

关键字即是被Go语言赋予了特殊含义的单词,也可以称为保留字。

Go语言中的关键字一共有 25 个:

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

之所以刻意地将Go语言中的关键字保持的这么少,是为了简化在编译过程中的代码解析。

和其它语言一样,关键字不能够作标识符使用。

标识符

标识符是指Go语言对各种变量、方法、函数等命名时使用的字符序列,标识符由若干个字母、下划线_、和数字组成,且第一个字符必须是字母。

下划线_是一个特殊的标识符,称为空白标识符

标识符的命名需要遵守以下规则:

  • 由 26 个英文字母、0~9、_组成;
  • 不能以数字开头,例如 var 1num int 是错误的;
  • Go语言中严格区分大小写;
  • 标识符不能包含空格;
  • 不能以系统保留关键字作为标识符,比如 break,if 等等。

命名标识符时还需要注意以下几点:

  • 标识符的命名要尽量采取简短且有意义;
  • 不能和标准库中的包名重复;
  • 为变量、函数、常量命名时采用驼峰命名法,例如 stuName、getVal;

在Go语言中还存在着一些特殊的标识符,叫做预定义标识符,如下表所示:

appendboolbytecapclosecomplexcomplex64complex128uint16
copyfalsefloat32float64imagintint8int16uint32
int32int64iotalenmakenewnilpanicuint64
printprintlnrealrecoverstringtrueuintuint8uintptr

预定义标识符一共有 36 个,主要包含Go语言中的基础数据类型和内置函数,这些预定义标识符也不可以当做标识符来使用。

7. 运算符优先级

所谓优先级,就是当多个运算符出现在同一个表达式中时,先执行哪个运算符。

Go语言有几十种运算符,被分成十几个级别,有的运算符优先级不同,有的运算符优先级相同,请看下表。

注:go中没有无符号右移运算符,但对无符号整数使用>>时,左侧空位会用零填充,这时与>>>运算符类似。例如:

// left、right为int类型的数据
mid := (left + right) >> 1	// mid可能为负数
mid := int(uint(left + right) >> 1)	// mid恒为正数
优先级分类运算符结合性
1逗号运算符,从左到右
2赋值运算符=、+=、-=、*=、/=、 %=、 >>=、 <<=、&=、^=、|=从右到左
3逻辑或||从左到右
4逻辑与&&从左到右
5按位或|从左到右
6按位异或^从左到右
7按位与&从左到右
8相等/不等==、!=从左到右
9关系运算符<、<=、>、>=从左到右
10位移运算符<<、>>从左到右
11加法/减法+、-从左到右
12乘法/除法/取余*(乘号)、/、%从左到右
13单目运算符!、*(指针)、& 、++、–、+(正号)、-(负号)从右到左
14后缀运算符( )、[ ]、->从左到右

注意:优先级值越大,表示优先级越高。

一下子记住所有运算符的优先级并不容易,还好Go语言中大部分运算符的优先级和数学中是一样的,大家在以后的编程过程中也会逐渐熟悉起来。如果实在搞不清,可以加括号,就像下面这样:

d := a + (b * c)

括号的优先级是最高的,括号中的表达式会优先执行,这样各个运算符的执行顺序就一目了然了。

8. 字符串与其他数据类型的转换

  1. 整数 与 字符串

    // 字符串与其他类型的转换
    // str 转 int
    newStr1 := "1"
    intValue, _ := strconv.Atoi(newStr1)
    fmt.Printf("%T,%d\n", intValue, intValue)  // int,1// int 转 str
    intValue2 := 1
    strValue := strconv.Itoa(intValue2)
    fmt.Printf("%T, %s\n", strValue, strValue)
    
  2. 浮点数 与字符串

        // str 转  floatstring3 := "3.1415926"f,_ := strconv.ParseFloat(string3, 32)fmt.Printf("%T, %f\n", f, f)  // float64, 3.141593//float 转 stringfloatValue := 3.1415926//4个参数,1:要转换的浮点数 2. 格式标记(b、e、E、f、g、G)//3. 精度 4. 指定浮点类型(32:float32、64:float64)// 格式标记:// ‘b’ (-ddddp±ddd,二进制指数)// ‘e’ (-d.dddde±dd,十进制指数)// ‘E’ (-d.ddddE±dd,十进制指数)// ‘f’ (-ddd.dddd,没有指数)// ‘g’ (‘e’:大指数,‘f’:其它情况)// ‘G’ (‘E’:大指数,‘f’:其它情况)//// 如果格式标记为 ‘e’,‘E’和’f’,则 prec 表示小数点后的数字位数// 如果格式标记为 ‘g’,‘G’,则 prec 表示总的数字位数(整数部分+小数部分)formatFloat := strconv.FormatFloat(floatValue, 'f', 2, 64)fmt.Printf("%T,%s",formatFloat,formatFloat)
    

9. 练习:开发一款游戏

//捕获标准输入,并转换为字符串 reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString(‘\n’)

if err != nil {

//如果有错误 退出

panic(err) }

需求:能打怪升级

package mainimport ("bufio""fmt""os"
)var level = 1
var ex = 0
func main()  {fmt.Println("请输入你的角色名字")//捕获标准输入,并转换为字符串reader := bufio.NewReader(os.Stdin)input, err := reader.ReadString('\n')if err != nil {panic(err)}//删除最后的\nname := input[:len(input)-1]fmt.Printf("角色创建成功,%s,欢迎你来到我是码神游戏,目前角色等级%d \n",name,level)s := `你遇到了一个怪物,请选择是战斗还是逃跑?1.战斗2.逃跑`fmt.Printf("%s \n",s)for {input, err := reader.ReadString('\n')if err != nil {panic(err)}selector := input[:len(input)-1]switch selector {case "1":ex += 10fmt.Printf("杀死了怪物,获得了%d经验 \n",ex)computeLevel()fmt.Printf("您现在的等级为%d \n",level)case "2":fmt.Printf("你选择了逃跑\n")fmt.Printf("%s \n",s)case "exit":fmt.Println("你退出了游戏")//退出os.Exit(1)default:fmt.Println("你的输入我不认识,请重新输入")}}
}func computeLevel() {if ex < 20 {level = 1}else if ex < 40{level = 2}else if ex < 200{level = 3}else {level = 4}
}

http://www.mrgr.cn/news/60756.html

相关文章:

  • 解决VMware虚拟机的字体过小问题
  • ENGAGE SHE连锁品牌盛启,寻找更多城市合伙人
  • MongoDB-Plus
  • centos8.5环境下openresty使用lua访问redis、本地缓存、获取get参数,请求头以及获取post body参数
  • Python 程序打包成 EXE 文件及相关操作详解
  • [MySQL#3] 数据约束 | 数值类 | varchar | timestamp | enum vs set
  • 信创认证(信创人才考评证书)的含金量?到底有多少?
  • 【Flask】三、Flask 常见项目架构
  • IPV6扩展头部
  • SQL进阶技巧:Hive如何进行更新和删除操作?
  • 自修室预约系统|基于java和小程序的自修室预约系统设计与实现(源码+数据库+文档)
  • 代码随想录第46天|
  • 前端:遇到的面试题
  • Oracle 第10章:触发器
  • Spring MVC介绍
  • Spring Boot 3项目创建与示例(Web+JPA)
  • 江协科技STM32学习- P23 DMA 直接存储器存取
  • CSS.选择器
  • Java性能调优与垃圾回收机制(4/5)
  • 当代AI大模型产品经理现状,及产品经理转型方向?
  • QT 机器视觉 (3. 虚拟相机SDK、测试工具)
  • 在没有 TIA Portal 的情况下,使用存储卡向 S7-1200 /S7-1500CPU 传输程序
  • Halcon 3D模型筛选操作
  • 如何通过AI提升产品经理效率!助产品经理工作效率翻倍
  • #Js篇:Date日期梳理
  • 嵌入式C语言中VT100特殊符号实现