-
// 取地址符 (指针) var num int num = 1000 fmt.Printf("num的值:%d,内存地址:%p\n", num, &num) num = 2000 fmt.Printf("num的值:%d,内存地址:%p\n", num, &num) var name string name = "张三" // 思考:这个num在计算机中是什么样子的。 num fmt.Printf("name的值:%s,内存地址:%p\n", name, &name) name = "李四" fmt.Printf("name的值:%s,内存地址:%p\n", name, &name) num的值:1000,内存地址:0xc0000a6058 num的值:2000,内存地址:0xc0000a6058 name的值:张三,内存地址:0xc000088240 name的值:李四,内存地址:0xc000088240
-
匿名变量不占用内存空间,不会分配内存。
全局变量定义在go文件非函数内,在package和import下面
全局变量的定义必须使用 var 关键字, 如果直接使用 := 则无法创建该变量
-
常量不能取地址
-
内存地址是以十六进制的形式表示的
-
在开发的时候,就需要给一些定义的变量赋值空间大小。每种在Go语言中出现的基本数据类型,会有一个默认的空间大小
-
布尔型的值只可以是常量true或者 false。
序号 | 类型和描述 |
---|---|
1 | uint8 无符号 8 位整型 (0 到 255) |
2 | uint16 无符号 16 位整型 (0 到 65535) |
3 | uint32 无符号 32 位整型 (0 到 4294967295) |
4 | uint64 无符号 64 位整型 (0 到 18446744073709551615) |
5 | int8 有符号 8 位整型 (-128 到 127) |
6 | int16 有符号 16 位整型 (-32768 到 32767) |
7 | int32 有符号 32 位整型 (-2147483648 到 2147483647) |
8 | int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) |
- 空间的大小是有限的,基本的数据类型默认都会有一些大小的约束
- float32和float64
- 特殊的数字类型:byte(uint8),rune(int32),int(32位的操作系统是int32,64位操作系统是int64)
- Go语言中,所有的字符串都是由单个字符连接起来的。
- 单引号是字符、双引号才是 string 类型
- 字符本质是整型
在Go语言中,字符的本质是整型,这是因为字符在计算机中是通过数值来表示的。具体原因如下:
字符编码:字符在计算机内部是通过字符编码(如ASCII、UTF-8等)来表示的。字符编码将字符映射为整数值。例如,在ASCII编码中,字符 ‘A’ 的整数值是 65,字符 ‘a’ 的整数值是 97。
rune 类型:Go语言中的字符类型是
rune
,它实际上是一个别名,代表int32
类型。rune
用于表示一个Unicode码点(Unicode code point),每个Unicode码点都是一个整数。因此,一个rune
值实际上就是一个整数,它表示一个字符的Unicode码点。整型操作:将字符表示为整数有助于简化字符的处理和操作。例如,判断字符是否在某个范围内(如字母或数字范围)、字符的大小写转换等,都可以通过整型运算来实现。
一致性和简洁性:将字符视为整数保持了语言的简单性和一致性。在Go语言中,任何可以应用于整数的操作都可以直接应用于字符。例如,可以直接对字符进行加减运算,得到结果仍然是一个有效的字符。
下面是一些示例代码,展示了字符作为整数的使用:
package main import "fmt" func main() { // 定义一个字符 var char rune = 'A' fmt.Println("字符:", string(char)) // 输出: 字符: A fmt.Println("整数值:", char) // 输出: 整数值: 65 // 字符加减运算 char = char + 1 fmt.Println("字符:", string(char)) // 输出: 字符: B fmt.Println("整数值:", char) // 输出: 整数值: 66 // 判断字符范围 if char >= 'A' && char <= 'Z' { fmt.Println(string(char), "是一个大写字母") } else { fmt.Println(string(char), "不是一个大写字母") } }
在这个示例中,我们看到字符
char
可以直接参与整数运算,且可以通过比较运算符判断字符范围。这些特性都得益于字符在Go语言中本质上是整型这一设计。
v1 := 'A'
fmt.Printf("%T,%d\n", v1, v1)
// int32,65
fmt.Printf("%T,%c\n", v1, v1)
// int32,A
fmt.Printf("%T,%s\n", v1, v1)
//error:
//占位符实参 'v1' 具有错误的类型 'int32' (%s)
//int32,%!s(int32=65)
fmt.Println(v1)
// 65
v1 := '魑'
fmt.Printf("%T,%d\n", v1, v1)
// int32,39761
fmt.Printf("%T,%c\n", v1, v1)
// int32,魑
fmt.Printf("%T,%s\n", v1, v1)
//int32,%!s(int32=39761)
fmt.Println(v1)
// 39761
v2 := "A"
fmt.Printf("%T,%s\n", v2, v2)
// string,A
fmt.Printf("%T,%c\n", v2, v2)
//string,%!c(string=A)
fmt.Printf("%T,%d\n", v2, v2)
//string,%!d(string=A)
fmt.Println(v2)
// A
- 我有一个疑问,就是在这个编码中,一个utf8编码方式的字符是占多少字节
在Go语言中,一个UTF-8编码方式的字符占用的字节数是可变的。具体来说,根据Unicode字符的不同,UTF-8编码的字符可能占用1到4个字节。详细如下:
- 1字节:用于表示U+0000到U+007F范围内的字符,即标准ASCII字符。
- 2字节:用于表示U+0080到U+07FF范围内的字符。
- 3字节:用于表示U+0800到U+FFFF范围内的字符。
- 4字节:用于表示U+10000到U+10FFFF范围内的字符。
这意味着,对于常见的英文字母和一些符号,一个字符通常占用1个字节;而对于汉字等其他字符,通常占用3个字节。
在Go语言中,可以使用内置的
utf8
包来处理UTF-8编码的字符串,并计算字符占用的字节数。例如:package main import ( "fmt" "unicode/utf8" ) func main() { str := "Hello, 世界" for i := 0; i < len(str); { r, size := utf8.DecodeRuneInString(str[i:]) fmt.Printf("字符: %c 占用字节数: %d\n", r, size) i += size } }
该程序会输出每个字符及其占用的字节数:
字符: H 占用字节数: 1 字符: e 占用字节数: 1 字符: l 占用字节数: 1 字符: l 占用字节数: 1 字符: o 占用字节数: 1 字符: , 占用字节数: 1 字符: 占用字节数: 1 字符: 世 占用字节数: 3 字符: 界 占用字节数: 3
这样,你可以看到每个字符的具体字节数。
- 一个数字类型的字符,一个转义字符,一个字母类型的字符和一个汉语类型的字符所占的字节是一样的吗
在UTF-8编码中,不同类型的字符占用的字节数是不一样的。我们可以分别来看一下不同类型字符在UTF-8编码下所占用的字节数:
- 数字类型的字符:例如’1’,属于ASCII字符,UTF-8编码占用1个字节。
- 转义字符:例如换行符’\n’,也是ASCII字符,UTF-8编码占用1个字节。
- 字母类型的字符:例如’A’,属于ASCII字符,UTF-8编码占用1个字节。
- 汉语类型的字符:例如’汉’,属于Unicode字符,UTF-8编码占用3个字节。
我们可以通过Go语言的代码来验证一下:
package main import ( "fmt" "unicode/utf8" ) func main() { chars := []string{"1", "\n", "A", "汉"} for _, char := range chars { fmt.Printf("字符: %s 占用字节数: %d\n", char, utf8.RuneLen([]rune(char)[0])) } }
运行结果如下:
字符: 1 占用字节数: 1 字符: 占用字节数: 1 字符: A 占用字节数: 1 字符: 汉 占用字节数: 3
从结果可以看出:
- 数字字符’1’占用1个字节。
- 转义字符’\n’占用1个字节。
- 字母字符’A’占用1个字节。
- 汉字’汉’占用3个字节。
因此,这些不同类型的字符在UTF-8编码下所占用的字节数是不一样的。
- 在字符或字符串中一定是utf8的编码方式吗
在字符或字符串中,并不一定是使用UTF-8编码方式。编码方式可以多种多样,常见的包括:
- UTF-8:可变长度编码,每个字符占用1到4个字节,是目前最常用的编码方式之一,特别适用于网络传输和存储。
- UTF-16:可变长度编码,每个字符占用2或4个字节,广泛用于Windows系统和一些编程语言中,如Java和C#。
- UTF-32:固定长度编码,每个字符占用4个字节,适用于需要快速访问特定字符的场景,但会占用更多的存储空间。
- ASCII:单字节编码,每个字符占用1个字节,只能表示基本的英文字符和一些控制字符,适用于早期的计算机系统。
- ISO-8859-1(Latin-1):单字节编码,每个字符占用1个字节,可以表示西欧的语言字符。
- GBK:双字节编码,常用于表示简体中文字符。
在不同的应用场景和系统中,可能会使用不同的编码方式。对于现代的互联网应用和跨平台的软件开发,UTF-8通常是默认且推荐的编码方式,因为它兼容性好,效率高,且能够表示所有的Unicode字符。
在Go语言中,字符串默认使用UTF-8编码。但是,可以手动转换字符串的编码。例如,如果需要将一个字符串从UTF-8转换为GBK,可以使用类似于
iconv
库或其他编码转换库。举个简单的例子,如果你想在Go语言中处理不同的编码,可以使用
golang.org/x/text/encoding
包来进行编码转换。下面是一个将UTF-8字符串转换为GBK编码的示例:package main import ( "fmt" "log" "golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/transform" "bytes" "io/ioutil" ) func main() { utf8Str := "你好,世界" // 将UTF-8字符串转换为GBK编码 gbkStr, err := Utf8ToGbk(utf8Str) if err != nil { log.Fatal(err) } fmt.Println("GBK编码字符串:", gbkStr) } func Utf8ToGbk(s string) (string, error) { reader := transform.NewReader(bytes.NewReader([]byte(s)), simplifiedchinese.GBK.NewEncoder()) d, err := ioutil.ReadAll(reader) if err != nil { return "", err } return string(d), nil }
这个示例将一个UTF-8编码的字符串转换为GBK编码并输出。类似地,你可以根据需要进行其他编码之间的转换。
- 在字符串中的是如何进行表示出来的,我觉得如果是用整型去表示出来的话,那么一个字符串里面全是整型数字,哪个是哪个也不知道了,我的意思是说在字符串中的这些整型哪个代表哪个它真正的字符就不知道了
在计算机科学中,字符在字符串中的表示是通过编码来实现的。编码将字符映射到特定的整数值,这些整数值在内存中实际存储为字节。不同的编码方式(例如ASCII、UTF-8、UTF-16等)有不同的映射规则和存储方式。
以下是字符编码和存储的基本概念:
字符编码表:字符编码表(如ASCII、Unicode)定义了每个字符与其对应的整数值之间的映射关系。例如,在ASCII编码中,字符’A’对应的整数值是65,字符’1’对应的整数值是49。
存储为字节:字符串在内存中存储为一系列字节。每个字节是一个8位的二进制数,可以表示0到255之间的整数值。字符编码决定了如何将字符映射到这些字节。
字节序列:字符串在内存中表示为字节序列。不同的编码方式将字符映射到字节序列的方式不同。例如,UTF-8编码使用1到4个字节表示一个Unicode字符,而UTF-16编码使用2或4个字节表示一个Unicode字符。
举个例子,假设我们有一个包含字符’A’、‘1’、'你’的字符串:
- ASCII字符:字符’A’的整数值是65,字符’1’的整数值是49。
- Unicode字符:字符’你’在Unicode中的码点是20320(十进制)。
在UTF-8编码中:
- ‘A’(码点65)表示为一个字节:0x41。
- ‘1’(码点49)表示为一个字节:0x31。
- ‘你’(码点20320)表示为三个字节:0xE4 0xBD 0xA0。
因此,字符串"A1你"在UTF-8编码中表示为以下字节序列:
0x41 0x31 0xE4 0xBD 0xA0
为了理解一个字符串中的字节序列代表哪个字符,我们需要知道该字符串使用的编码方式。例如,Go语言默认使用UTF-8编码,下面是如何在Go中查看字符串中的字节和字符:
package main import ( "fmt" "unicode/utf8" ) func main() { str := "A1你" // 打印每个字节的值 fmt.Println("字节序列:") for i := 0; i < len(str); i++ { fmt.Printf("0x%x ", str[i]) } fmt.Println() // 打印每个字符及其码点值 fmt.Println("字符及其码点:") for i := 0; i < len(str); { r, size := utf8.DecodeRuneInString(str[i:]) fmt.Printf("字符: %c 码点: U+%04X 占用字节数: %d\n", r, r, size) i += size } }
输出结果将显示每个字节和每个字符的详细信息:
字节序列: 0x41 0x31 0xe4 0xbd 0xa0 字符及其码点: 字符: A 码点: U+0041 占用字节数: 1 字符: 1 码点: U+0031 占用字节数: 1 字符: 你 码点: U+4F60 占用字节数: 3
通过这种方式,我们可以清楚地看到每个字符在内存中的表示以及它们对应的字节序列。这也是计算机能够正确处理和显示字符串的基础。
- utf8和ascii表示的都是二进制位的整型吗
是的,UTF-8和ASCII编码表示的都是二进制位的整数。这些整数实际上是存储在内存中的字节。
ASCII编码
ASCII(American Standard Code for Information Interchange)是一种早期的字符编码标准,使用7位二进制数表示128个字符,包括英文字母、数字、标点符号和控制字符。例如:
- 字符’A’的ASCII码是65,二进制表示为01000001。
- 字符’1’的ASCII码是49,二进制表示为00110001。
在计算机中,ASCII字符通常用一个字节(8位)表示,但实际使用的是7位,最高位通常为0。
UTF-8编码
UTF-8(8-bit Unicode Transformation Format)是一种变长的字符编码,用于表示Unicode字符。它使用1到4个字节来编码每个字符。UTF-8向后兼容ASCII,即前128个字符(0x00到0x7F)与ASCII编码完全相同。对于超出这个范围的字符,UTF-8使用多个字节进行编码。示例如下:
- 字符’A’(Unicode码点U+0041)的UTF-8编码是01000001(与ASCII相同)。
- 字符’你’(Unicode码点U+4F60)的UTF-8编码是三个字节:11100100 10111101 10100000(0xE4 0xBD 0xA0)。
具体示例
我们可以通过代码来演示UTF-8和ASCII编码在内存中的表示方式。以下是一个用Go语言编写的示例代码:
package main import ( "fmt" "unicode/utf8" ) func main() { // 定义字符串,包含ASCII字符和非ASCII字符 str := "A1你" // 打印每个字符及其UTF-8编码的二进制表示 fmt.Println("字符及其UTF-8编码的二进制表示:") for i := 0; i < len(str); { r, size := utf8.DecodeRuneInString(str[i:]) fmt.Printf("字符: %c 码点: U+%04X UTF-8编码: ", r, r) for j := 0; j < size; j++ { fmt.Printf("%08b ", str[i+j]) } fmt.Println() i += size } }
输出结果如下:
字符及其UTF-8编码的二进制表示: 字符: A 码点: U+0041 UTF-8编码: 01000001 字符: 1 码点: U+0031 UTF-8编码: 00110001 字符: 你 码点: U+4F60 UTF-8编码: 11100100 10111101 10100000
从输出结果中可以看出:
- ASCII字符’A’和’1’的UTF-8编码与其ASCII编码相同,均为1个字节。
- 非ASCII字符’你’的UTF-8编码占用3个字节。
总结
- ASCII和UTF-8都是使用二进制位来表示字符的编码方式。
- ASCII使用7位或8位二进制数表示字符,而UTF-8使用1到4个字节(8到32位)来表示字符。
- ASCII字符在UTF-8编码中保持不变,因此UTF-8兼容ASCII。
这种二进制表示方式使得计算机能够在内存中有效地存储和处理各种字符,并通过适当的编码和解码机制正确地显示和操作这些字符。
UTF-8 和 ASCII 都是字符编码方式,它们都以二进制形式表示字符。ASCII 是一种较早的字符编码标准,它使用 7 位或 8 位二进制数表示字符,最多可以表示 128 或 256 个字符。这些字符主要包括英文字母、数字、一些标点符号和控制字符。
UTF-8 是 Unicode 的一种编码方式,能够表示所有的 Unicode 字符。UTF-8 使用 1 到 4 个字节来编码一个字符,其中前 128 个字符与 ASCII 完全相同,这使得 UTF-8 具有向后兼容性。例如,ASCII 中的字符 ‘A’ 在 UTF-8 中也用一个字节表示,其二进制表示为 01000001。
Unicode 标准为每个字符分配一个唯一的数字(码点),这些码点可以通过多种编码形式表示。UTF-8 是一种变长编码方式,使用 1 到 4 个字节表示一个字符,这取决于字符的码点范围。
总结来说,UTF-8 和 ASCII 都是以二进制形式表示字符的编码方式,但 UTF-8 能够表示的字符范围远远超过 ASCII,并且在表示 ASCII 字符时具有向后兼容性。这意味着在处理文本数据时,UTF-8 是更为通用和灵活的编码选择。
-
函数中的参数传递
值类型:int、string、bool、float64、array… 拷贝,创建的时候,拷贝一份
引用类型:操作的是数据的地址,切片slice、map、chan…
结构体:
package main
import "fmt"
// 定义一个结构体
type Person struct {
Name string
Age int
}
// 修改结构体的副本
func modifyStruct(p Person) {
p.Name = "Alice"
p.Age = 30
fmt.Println("Inside modifyStruct:", p) // 打印 {Alice 30}
}
// 修改结构体的引用
func modifyStructPointer(p *Person) {
p.Name = "Alice"
p.Age = 30
fmt.Println("Inside modifyStructPointer:", *p) // 打印 {Alice 30}
}
func main() {
// 初始化结构体
p1 := Person{Name: "Bob", Age: 25}
fmt.Println("Before modifyStruct:", p1) // 打印 {Bob 25}
modifyStruct(p1)
fmt.Println("After modifyStruct:", p1) // 仍然打印 {Bob 25}
// 使用结构体指针
p2 := Person{Name: "Bob", Age: 25}
fmt.Println("Before modifyStructPointer:", p2) // 打印 {Bob 25}
modifyStructPointer(&p2)
fmt.Println("After modifyStructPointer:", p2) // 打印 {Alice 30}
}
// Before modifyStruct: {Bob 25}
// Inside modifyStruct: {Alice 30}
// After modifyStruct: {Bob 25}
// Before modifyStructPointer: {Bob 25}
// Inside modifyStructPointer: {Alice 30}
// After modifyStructPointer: {Alice 30}
array:
package main
import "fmt"
// 修改数组的副本
func modifyArray(arr [3]int) {
arr[0] = 10
fmt.Println("Inside modifyArray:", arr) // 打印 [10 2 3]
}
// 修改数组的引用(通过指针)
func modifyArrayPointer(arr *[3]int) {
arr[0] = 10
fmt.Println("Inside modifyArrayPointer:", arr) // 打印 [10 2 3]
}
func main() {
// 初始化数组
arr1 := [3]int{1, 2, 3}
fmt.Println("Before modifyArray:", arr1) // 打印 [1 2 3]
modifyArray(arr1)
fmt.Println("After modifyArray:", arr1) // 仍然打印 [1 2 3]
// 使用数组指针
arr2 := [3]int{1, 2, 3}
fmt.Println("Before modifyArrayPointer:", arr2) // 打印 [1 2 3]
modifyArrayPointer(&arr2)
fmt.Println("After modifyArrayPointer:", arr2) // 打印 [10 2 3]
}
// Before modifyArray: [1 2 3]
// Inside modifyArray: [10 2 3]
// After modifyArray: [1 2 3]
// Before modifyArrayPointer: [1 2 3]
// Inside modifyArrayPointer: &[10 2 3]
// After modifyArrayPointer: [10 2 3]
slice:
package main
import "fmt"
// 修改切片的副本
func modifySlice(s []int) {
s[0] = 10
fmt.Println("Inside modifySlice:", s) // 打印 [10 2 3]
}
// 修改切片的引用(通过指针)
func modifySlicePointer(s *[]int) {
(*s)[0] = 10
fmt.Println("Inside modifySlicePointer:", *s) // 打印 [10 2 3]
}
func main() {
// 初始化切片
slice1 := []int{1, 2, 3}
fmt.Println("Before modifySlice:", slice1) // 打印 [1 2 3]
modifySlice(slice1)
fmt.Println("After modifySlice:", slice1) // 打印 [10 2 3]
// 使用切片指针
slice2 := []int{1, 2, 3}
fmt.Println("Before modifySlicePointer:", slice2) // 打印 [1 2 3]
modifySlicePointer(&slice2)
fmt.Println("After modifySlicePointer:", slice2) // 打印 [10 2 3]
}
// Before modifySlice: [1 2 3]
// Inside modifySlice: [10 2 3]
// After modifySlice: [10 2 3]
// Before modifySlicePointer: [1 2 3]
// Inside modifySlicePointer: [10 2 3]
// After modifySlicePointer: [10 2 3]
map:
package main
import "fmt"
// 修改map的副本
func modifyMap(m map[string]int) {
m["one"] = 10
fmt.Println("Inside modifyMap:", m) // 打印 map[one:10 two:2]
}
// 修改map的引用(通过指针)
func modifyMapPointer(m *map[string]int) {
(*m)["one"] = 20
fmt.Println("Inside modifyMapPointer:", *m) // 打印 map[one:20 two:2]
}
func main() {
// 初始化map
map1 := map[string]int{"one": 1, "two": 2}
fmt.Println("Before modifyMap:", map1) // 打印 map[one:1 two:2]
modifyMap(map1)
fmt.Println("After modifyMap:", map1) // 打印 map[one:10 two:2]
// 使用map指针
map2 := map[string]int{"one": 1, "two": 2}
fmt.Println("Before modifyMapPointer:", map2) // 打印 map[one:1 two:2]
modifyMapPointer(&map2)
fmt.Println("After modifyMapPointer:", map2) // 打印 map[one:20 two:2]
}
// Before modifyMap: map[one:1 two:2]
// Inside modifyMap: map[one:10 two:2]
// After modifyMap: map[one:10 two:2]
// Before modifyMapPointer: map[one:1 two:2]
// Inside modifyMapPointer: map[one:20 two:2]
// After modifyMapPointer: map[one:20 two:2]
channel:
package main
import "fmt"
// 修改channel的值
func modifyChannel(ch chan int) {
ch <- 10
fmt.Println("Inside modifyChannel, sent 10 to channel")
}
// 修改channel的引用(通过指针)
func modifyChannelPointer(ch *chan int) {
*ch <- 20
fmt.Println("Inside modifyChannelPointer, sent 20 to channel")
}
func main() {
// 初始化channel
ch1 := make(chan int, 1)
fmt.Println("Before modifyChannel")
modifyChannel(ch1)
fmt.Println("Received from channel:", <-ch1) // 打印 10
// 使用channel指针
ch2 := make(chan int, 1)
fmt.Println("Before modifyChannelPointer")
modifyChannelPointer(&ch2)
fmt.Println("Received from channel:", <-ch2) // 打印 20
}
// Before modifyChannel
// Inside modifyChannel, sent 10 to channel
// Received from channel: 10
// Before modifyChannelPointer
// Inside modifyChannelPointer, sent 20 to channel
// Received from channel: 20
- 切片引用了一个底层的数组,切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
- 切片一旦扩容,就是重新指向一个新的底层数组。
- 深拷贝和浅拷贝
深拷贝:拷贝是数据的本身
- 值类型的数据,默认都是深拷贝,array、int、float、string、bool、struct…
浅拷贝:拷贝是数据的地址,会导致多个变量指向同一块内存。
- 引用类型的数据: slice、map
- 因为切片是引用类的数据,直接拷贝的是这个地址
- 切片实现深拷贝
package main
import "fmt"
// 切片实现深拷贝
func main() {
// 将原来切片中的数据拷贝到新切片中
s1 := []int{1, 2, 3, 4}
s2 := make([]int, 0) // len:0 cap:0
for i := 0; i < len(s1); i++ {
s2 = append(s2, s1[i])
}
fmt.Println(s1)
fmt.Println(s2)
s1[0] = 100
fmt.Println(s1)
fmt.Println(s2)
// copy
s3 := []int{5, 6, 7}
fmt.Println(s2)
fmt.Println(s3)
// 将s3中的元素拷贝到s2中
//copy(s2, s3)
// 将s2中的元素拷贝到s3中
copy(s3, s2)
fmt.Println(s2)
fmt.Println(s3)
}
- 变量是一种占位符,底层指向是一个内存地址。
- &取地址符,拿到一个变量 a,&a取出这个变量的地址。