【Go 快速入门】基础语法 | 流程控制 | 字符串
文章目录
- 基础语法
- 值
- 变量
- 常量
- 运算符
- 指针
- new 和 make 区别
- 字符串
- byte 和 rune 类型
- 流程控制
- for 循环
- If else 分支
- switch 分支
基础语法
项目代码地址:02-basicgrammar
值
- 基本类型值
Go 最基础的数据类型,比如整型、浮点型、布尔型。
- 复合类型值
由基本类型组成的复杂数据类型,比如数组、切片、结构体、函数、map、通道、接口、字符串。
- 指针类型值
指针类型的变量与指针类型值绑定,它内部存储的是另外一个内存单元的地址。
简单示例:
// 值
func function01() {fmt.Println(123_456) // 123456fmt.Println("go" + "lang") // golangfmt.Println("1 + 1 = ", 1+1) // 1 + 1 = 2fmt.Println("3 / 2 = ", 3/2) // 3 / 2 = 1fmt.Println("7.0 / 3 = ", 7.0/3) // 7.0 / 3 = 2.3333333333333335fmt.Println(true && false) // falsefmt.Println(true || false) // truefmt.Println(!true) // false
}
Go 中的值类型和引用类型:
- 值类型:int系列、float系列、bool、string、数组、结构体
- 引用类型:指针、slice切片、管道channel、接口interface、map、函数
值类型:变量直接存储值,内存通常在栈中分配
引用类型:变量存储的是一个地址,地址空间存储真正的值,内存通常在堆中分配
在Go语言中,所有东西都是以值的形式存在的,只有值传递没有引用传递。
- 传值:你是你,我是我
- 传指针:你是你,我是我,但我们共同指向他
- 传“引用”:你是你,我是我,但我们有一部分共同指向他
// 指针类型
func function02() {a := 1ptr := func(b *int) {fmt.Println(&b, b) // 0xc000088028 0xc00000a0c8*b = 2fmt.Println(*b) // 2}fmt.Println(&a) // 0xc00000a0c8ptr(&a)fmt.Println(a) // 2
}// 引用类型
func function03() {s := []int{1, 2}ref := func(a []int) {fmt.Printf("%p\n", &a) // 0xc000008060a[1] = 0fmt.Println(a) // [1 0]}fmt.Printf("%p\n", &s) // 0xc000008048ref(s)fmt.Println(s) // [1 0]
}
不难看出,上述在传递参数过程中都是进行值传递的。尽管切片是引用类型,但传递时仍然会拷贝新的变量,只不过新变量的底层结构有对另一个基础类型的指向。
Go 的引用类型不同于 C++ 的引用,引用并不是同一个地址,不过底层指针是同一个指向。
对比 C++ 的引用:引用传递地址不变,a,b变量共享同一地址
#include<iostream>
using namespace std;struct Node {int a, b, c;
};void func(Node &a) {cout << &a << endl; // 0x7ffc43a4a21c
}int main() {Node b = {1, 2, 3};func(b);cout << &b << endl; // 0x7ffc43a4a21creturn 0;
}
额外补充,切片容易踩的坑:
切片在运行时由三个字段构成,值拷贝创建副本也就是对 Data、Len、Cap字段进行拷贝,而 Data 指针指向与原slice 同一地址。
type SliceHeader struct {Data uintptr // 指向底层数组的指针Len int // 切片长度Cap int // 切片容量
}
所以在副本中修改元素时,原 slice 的元素也会被修改。但是修改的是副本的 Len、Cap 时,原 slice 保持不变。如果副本由于扩容导致 Data 地址重新分配,那么之后副本的操作完全无法影响到原 slice。
// 传"引用"
func function04() {a := make([]int, 1, 2) // 指定len(a)=1,cap(a)=2a[0] = 1fmt.Println(len(a), cap(a), a) // 1 2 [1]ref := func(b []int) {b[0] = 4fmt.Println(len(b), cap(b), b, len(a), cap(a), a) // 1 2 [4] 1 2 [4]b = append(b, 2)fmt.Println(len(b), cap(b), b, len(a), cap(a), a) // 2 2 [4 2] 1 2 [4]b[0] = 0fmt.Println(len(b), cap(b), b, len(a), cap(a), a) // 2 2 [0 2] 1 2 [0]b = append(b, 3)fmt.Println(len(b), cap(b), b, len(a), cap(a), a) // 3 4 [0 2 3] 1 2 [0]b[0] = 1fmt.Println(len(b), cap(b), b, len(a), cap(a), a) // 3 4 [1 2 3] 1 2 [0]}ref(a)
}
上面的例子阐明了两点:
- “引用” 是我们共同一部分指向它:b 添加元素后,len 变为 2,而原 a 的 len 还是 1,所以切片的第一个元素是被共同指向的
- 扩容后“引用”的元素地址改变:b 扩容后,cap 变为 4,Data 地址改变,再修改第一个元素时,不再能影响原 a 的元素
如果上述不能理解,可以先放着,等下章学习了切片等类型后,再来回顾。
变量
常用两种声明变量格式:
-
var用来声明变量,标准声明格式:var 变量名 变量类型 -
:=短变量只能用于声明局部变量,不能用于全局变量声明,即在函数或方法内部使用。
变量只声明未初始化,则为默认值。
// 变量
func function05() {var a int64 // 声明未初始化,默认 int64(0)var b string = "ABC" // 标准声明并初始化var c = 1 // 自动类型推导var d, e = 2, "3" // 一次初始化多个变量// 批量声明var (f float64 = 3.1g bool = false)h := .1 // 简短变量声明fmt.Println(a, b, c, d, e, f, g, h) // 0 ABC 1 2 3 3.1 false 0.1
}
常量
var 换为 const 即可定义常量
// 常量
func function06() {const pi = 3.1415// 同时声明多个常量,某个常量省略值,则和上一个相同const (e = 2.7182a // 2.7182b // 2.7182c = 3d // 3)// iota 预声明标识符,表示连续的无类型整数常量,初始值 0const (level1 = iota + 1 // 1level2 // 2level3 // 3_ // 跳过某些值level5 // 5n = 1000 // 在 iota 声明中插队level6 = iota // 6, 需要再使用 iota)// 多个 iota 定义在一行,iota 值逐行增加const (A, B = iota + 1, iota + 2 // 1 2C, D // 2 3E, F // 3 4)// 用来定义数量级const (_ = iotaKB = 1 << (10 * iota) // 1 << (10 * 1)MB = 1 << (10 * iota) // 1 << (10 * 2)GM = 1 << (10 * iota) // 1 << (10 * 3)TB = 1 << (10 * iota) // 1 << (10 * 4))
}
运算符
需要注意的是
++(自增)和--(自减)在 Go 中是单独的语句,并不属于运算符&^按位清除;z := x &^ y,y 中位 1,z 位 0,y 中位 0,z 位为 x 位
// 运算符
func function10() {var a inta++fmt.Println(a) // 1// b := a-- 错误!i++、i--只能单独使用// ++a 错误!没有 ++i、--i 操作x := 11y := (1 << 0) | (1 << 3) // 保证 z 中的第 0 位和第 3 位为 0z := x &^ yfmt.Printf("x = %b\n", x)fmt.Println("\t&^")fmt.Printf("y = %b\n", y)fmt.Println("————————")fmt.Printf("z = %04b\n", z)/*x = 1011&^y = 1001————————z = 0010*/
}
指针
指针用法和其他语言类似,而且 Go 中使用了垃圾回收机制,不需要手动释放内存。
// 指针
func function15() {str := "123"var strPtr *string = &strfmt.Println(str, *strPtr) // 123 123fmt.Println(&str, strPtr, &strPtr) // 0xc000026070 0xc000026070 0xc000088020
}
new 和 make 区别
Go 语言中 new 和 make 是内建的两个函数,主要用来分配内存。
-
new:用于类型的内存分配,返回值是一个指向新分配类型零值的指针 -
make:只用于 slice、map、channel 初始化,返回这三个引用类型本身
// new、make
func function16() {a := new(bool)fmt.Printf("%T %t %v\n", a, *a, a) // *bool false 0xc000192068b := make([]int, 1, 2)fmt.Printf("%T %v %p %p %p\n", b, b, b, &b, &b[0]) // []int [0] 0xc0001920b0 0xc000190030 0xc0001920b0
}
这里重点关注 make 后返回的切片引用类型本身 b,可见 b 和 &b[0] 的地址是一样的,都是指向底层数组的指针。而 &b 则是表示当前这个切片结构体的地址。
字符串
在运行时字符串类型表示如下:
type StringHeader struct {Data uintptr // 指向底层字节数组指针Len int // 字节数组长度
}
Go 在编程语言层面对值做了限制,常量值是不可变的,字符串类型值是不可变的,其他则为可变值。
Go 中只允许用双引号和反引号定义字符串,使用单引号定义字符类型。字符串是一个只读的 byte 类型切片,组成每个字符串的元素叫字符,字符变量默认 rune 型。
// 字符串
func function07() {s1 := "123"s2 := "字符串"s3 := `第一行
第二行
第三行
`// s1[0] = '4' // 不能修改s1 += "456" // 123456fmt.Println(len(s1), len(s2), len(s3)) // 3 9 30
}
byte 和 rune 类型
Go 语言字符有两种类型,byte 代表 ASCII 码的一个字符,rune 代表一个 UTF-8 字符。
byte型,本质上是uint8rune型,本质上是int32
特殊的 rune 类型表示 Unicode 编码的整数,能让基于 Unicode 的文本处理更方便,比如处理中文或其他复合字符。
-
内建函数
len()函数用来获取字符串的 ASCII 字符个数或字节长度。Go 语言的字符串都以 UTF-8 格式保存,每个中文占用 3 个字节 -
unicode/utf8包提供的utf8.RuneCountInString()函数用来统计 Unicode 字符数量 -
unsafe包提供的unsafe.Sizeof返回数据类型的大小 -
for 循环遍历,对应 ASCII 码;for range 遍历,对应 Unicode 码
// byte、rune
func function08() {var a = 'a'var b byte = 'b'fmt.Println(unsafe.Sizeof(a), unsafe.Sizeof(b)) // 4 1s1 := "Golang语言"fmt.Println(len(s1), utf8.RuneCountInString(s1)) // 12 8for i := 0; i < len(s1); i++ {fmt.Printf("%c", s1[i])} // Golangè¯è¨fmt.Println()for _, s := range s1 {fmt.Printf("%c", s)} // Golang语言
}
读者可能会疑惑,之前代码中 s1 += "456" 这不是修改了字符串本身了吗?
func function09() {// 仅声明字符串var str stringvar stringHeader = (*reflect.StringHeader)(unsafe.Pointer(&str))fmt.Printf("%p %p %d\n", &str, unsafe.Pointer(stringHeader.Data), stringHeader.Len)// 字符串赋值str = "快速入门"fmt.Printf("%p %p %d\n", &str, unsafe.Pointer(stringHeader.Data), stringHeader.Len)// 字符串拼接str += "GO语言"fmt.Printf("%p %p %d\n", &str, unsafe.Pointer(stringHeader.Data), stringHeader.Len)// 字符串转 byte 切片var bytes = []byte(str)fmt.Printf("%p %p %d\n", bytes, unsafe.Pointer(stringHeader.Data), stringHeader.Len)bytes[3] = 1fmt.Println(str)// 0拷贝 byte 切片转换var bytes1 = *(*[]byte)(unsafe.Pointer(&str))fmt.Printf("%p %p %d\n", bytes1, unsafe.Pointer(stringHeader.Data), stringHeader.Len)bytes1[3] = 1fmt.Println(str)
}
0xc000026070 0x0 0
0xc000026070 0x541808 12
0xc000026070 0xc000012108 20
0xc000012120 0xc000012108 20
快速入门GO语言
0xc000012108 0xc000012108 20
快��入门GO语言
unsafe.Pointer 类似于 C 语言中的 void * 指针,能接收任意类型的指针变量转为通用型指针,再可强转为其他指针类型。
分析上述代码:字符串在赋值和拼接后,实际变量的地址没发生变化,但是 stringHeader 的 Data 字节数组改变了地址,证明字符串本身不可被修改,变的是内部的字节数组。上述进行了强转 byte 字节数组,会实际拷贝一份 string 内部的字节数组,要想实现 0 拷贝,则可通过 unsafe.Pointer 转换。
流程控制
for 循环
基本格式:
for 初始语句; 条件表达式; 结束语句 {循环体
}
for - range 循环:
- 数组、切片、字符串返回索引和值
- map返回键和值
- 通道(channel)只返回通道内的值
基本格式:
for k, v := range x {
}
常见的循环写法:
// for
func function11() {i := 1for i < 2 {fmt.Println(i)i++} // 1for j := 8; j < 9; j++ {fmt.Println(j)} // 8for {fmt.Println("loop")break} // loopfor n := 1; n <= 3; n++ {if n%2 == 0 {continue}fmt.Println(n)} // 1 3var num int
flag:num++for num <= 3 {fmt.Println(num)goto flag} // 1 2 3s := []int{11, 12}for k, v := range s {fmt.Println(k, v)} // 0 11; 1 12
}
If else 分支
// if-else
func function12() {score := 70if score < 60 {fmt.Println("不及格")} else if score >= 60 && score <= 80 {fmt.Println("良好")} else {fmt.Println("优秀")} // 良好if err := 1; err != 0 {fmt.Println("没有出错")} // 没有出错
}
switch 分支
switch 不同于 C 语言,break 不写会跳出 case,并且一个分支可以有多个值:
// switch case
func function13() {switch suf := ".a"; suf {case ".html":fmt.Println("页面")case ".doc", ".txt":fmt.Println("文档")case ".js":fmt.Println("脚本文件")default:fmt.Println("其它后缀")} // 其它后缀
}
为了兼容 C 语言的 case 设计,fallthrough 语法可以执行满足条件的 case 的下一个case:
// fallthrough
func function14() {var suf = ".doc"switch suf {case ".html":fmt.Println("页面")case ".doc":fmt.Println("文档")fallthroughcase ".js":fmt.Println("脚本文件")default:fmt.Println("其它后缀")} // 文档 脚本文件
}
相关文章:
【Go 快速入门】基础语法 | 流程控制 | 字符串
文章目录 基础语法值变量常量运算符指针new 和 make 区别 字符串byte 和 rune 类型 流程控制for 循环If else 分支switch 分支 基础语法 项目代码地址:02-basicgrammar 值 基本类型值 Go 最基础的数据类型,比如整型、浮点型、布尔型。 复合类型值 …...
腾讯云轻量应用Ubuntu服务器如何一键部署幻兽帕鲁Palworld私服?
幻兽帕鲁/Palworld是一款2024年Pocketpair开发的开放世界生存制作游戏,在帕鲁的世界,玩家可以选择与神奇的生物“帕鲁”一同享受悠闲的生活,也可以投身于与偷猎者进行生死搏斗的冒险。而帕鲁可以进行战斗、繁殖、协助玩家做农活,也…...
Redis的SDS你了解吗?
初识SDS: Redis的String和其他很多编程语言中的语义相似,它能够表达3种值的类型: 1.字符串 2.整数 3.浮点数 三种类型根据具体场景由Redis完成相互之间的自动转换,并且根据需要选取底层的承载方式,Redis内部&#x…...
C#中常见的软件设计模式及应用场景
文章目录 前言1、单例模式 (Singleton)1.1 详细说明1.2 应用场景示例 2、工厂模式 (Factory Method)2.1 详细说明2.2 应用场景示例 3、观察者模式 (Observer)3.1 详细说明3.2 应用场景示例 4、策略模式 (Strategy)4.1 详细说明4.2 应用场景示例 5、适配器模式 (Adapter)5.1 详细…...
字符串相关函数和文件操作
文章目录 1. C/C 字符串概述1.1 字符串常量1.2 字符数组 2. 字符串函数2.1 拷贝赋值功能相关函数(覆盖)2.1.1 strcpy2.1.2 strncpy2.1.3 memcpy2.1.4 memmove2.1.5 memset2.1.6 注意小点2.1.7 【函数区别】 2.2 追加功能相关函数2.2.1 strcat2.2.2 strnc…...
【c++学习】数据结构中的栈
c栈 栈代码用线性表实现栈用链表实现栈 栈 栈:先进后出 只对栈顶元素进行操作,包括新元素入栈、栈顶元素出栈和查看栈顶元素(只支持对栈顶的增、删、查)。 代码 下述代码实现了栈及其接口 包括对栈顶的增、删、查以及查看栈的大…...
新建react项目,react-router-dom配置路由,引入antd
提示:reactrouter6.4版本,与reactrouter5.0的版本用法有区别,互不兼容需注意 文章目录 前言一、创建项目二、新建文件并引入react-router-dom、antd三、配置路由跳转四、效果五、遇到的问题六、参考文档总结 前言 需求:新建react项…...
Transformer and Pretrain Language Models3-6
Pretrain Language Models预训练语言模型 content: language modeling(语言模型知识) pre-trained langue models(PLMs)(预训练的模型整体的一个分类) fine-tuning approaches GPT and BERT(…...
Linux系统中编写bash脚本进行mysql的数据同步
一、为何要用脚本做数据同步 (一)、问题 我们的视频监控平台云服务器,需要向上级的服务器定期同步一些数据表的数据,前期做了个程序,可以实现同步。但是,现在数据库的结构改了,结果又需要该程序…...
光耦驱动继电器电路图大全
光耦驱动继电器电路图(一) 注: 1U1-1脚可接12V,也可接5V,1U1导通,1Q1导通,1Q1-30V,线圈两端电压为11.7V. 1U1-1脚不接或接地,1U1不通,1Q1截止,1…...
【AI量化分析】小明在量化中使用交叉验证原理深度分析解读
进行交叉验证好处 提高模型的泛化能力:通过将数据集分成多个部分并使用其中的一部分数据进行模型训练,然后使用另一部分数据对模型进行测试,可以确保模型在未见过的数据上表现良好。这样可以降低模型过拟合或欠拟合的风险,提高模…...
2024最新版Visual Studio Code安装使用指南
2024最新版Visual Studio Code安装使用指南 Installation and Usage Guide for the Latest Visual Studio Code in 2024 By JacksonML Visual Studio Code最新版1.85已经于2023年11月由其官网 https://code.visualstudio.com正式发布,这是微软公司2024年发行的的最…...
接口请求重试八种方法
请求三方接口需要加入重试机制 一、循环重试 在请求接口的代码块中加入循环,如果请求失败则继续请求,直到请求成功或达到最大重试次数。 int retryTimes 3; for(int i 0;i < retryTimes;i){try{//请求接口的代码break;}catch(Exception e){//处理…...
【Linux 基础】常用基础指令(上)
文章目录 一、 创建新用户并设置密码二、ls指令ls指令基本概念ls指令的简写操作 三、pwd指令四、cd指令五、touch指令六、rm指令七、mkdir指令八、rmdir 指令 一、 创建新用户并设置密码 ls /home —— 查看存在多少用户 whoami —— 查看当前用户名 adduser 用户名 —— 创建新…...
【RT-DETR有效改进】EfficientFormerV2移动设备优化的视觉网络(附对比试验效果图)
前言 大家好,我是Snu77,这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进,内容持续更新,每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本,同时修改内容也支持Re…...
《动手学深度学习(PyTorch版)》笔记4.4
注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在Jupyter Notebook上运行的,较为分散,本文将代码集中起来,并加以完善,全部用vscode在python 3.9.18下测试通过。…...
Linux/Academy
Enumeration nmap 首先扫描目标端口对外开放情况 nmap -p- 10.10.10.215 -T4 发现对外开放了22,80,33060三个端口,端口详细信息如下 结果显示80端口运行着http,且给出了域名academy.htb,现将ip与域名写到/et/hosts中,然后从ht…...
windows .vscode的json文件配置 CMake 构建项目 调试窗口中文设置等
一、CMake 和 mingw64的安装和环境配置 二、tasks.json和launch.json文件配置 tasks.json {"version": "2.0.0","options": {"cwd": "${workspaceFolder}/build"},"tasks": [{"type": "shell&q…...
uniapp canvas做的刮刮乐解决蒙层能自定义图片
最近给湖南中烟做元春活动,一个月要开发4个小活动,这个是其中一个难度一般,最难的是一个类似鲤鱼跃龙门的小游戏,哎,真实为难我这个“拍黄片”的。下面是主要代码。 <canvas :style"{width:widthpx,height:hei…...
利用SPI,结合数据库连接池durid进行数据服务架构灵活设计
接着上一篇文章业务开始围绕原始凭证展开,而展开的基础无疑是围绕着科目展开的。首先我们业务层面以财政部的小企业会计准则的一级科目引入软件中。下面我们来考虑如何将科目切入软件更加灵活,方便业务扩展、维护与升级。 SPI是首先想到的数据服务方式 为什么会想到它呢?首…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
