Go 语言基础(五) 之 进阶

内存分配

Go 同样也垃圾收集,也就是说无须担心内存分配和回收。

Go 有两个内存分配原语,new 和 make。它们应用于不同的类型,做不同的工作, 可能有些迷惑人,但是规则很简单。

1、用 new 分配内存

内建函数 new 本质上说跟其他语言中的同名函数功能一样: new(T) 分配了零值填充的 T 类型的内存空间,

并且返回其地址,一个 *T 类型的值。用 Go 的术语 说,它返回了一个指针,指向新分配的类型 T 的零值。

有一点非常重要:

new 返回指针。

这意味着使用者可以用 new 创建一个数据结构的实例并且可以直接工作。

type SyncedBuffer struct {

lock sync.Mutex

buffer bytes.Buffer

}

SyncedBuffer 的值在分配内存或定义之后立刻就可以使用。

在这个片段中, p 和v 都可以在没有任何更进一步处理的情况下工作。

p := new(SyncedBuffer) ← Type *SyncedBuffer,已经可以使用

var v SyncedBuffer ← Type SyncedBuffer,同上

2、用 make 分配内存

内建函数 make(T, args) 与 new(T) 有着不同的功能。它只能创建 slice,map 和 channel,

并且返回一个有初始值(非零)的 T 类型,而不是 *T。

本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前 必须被初始化。

3、new 和 make 的区别

例如,一个 slice,是一个包含指向数据(内部 array)的指针, 长度和容量的三项描述符;

在这些项目被初始化之前,slice 为 nil。对于 slice, map 和 channel,

make 初始化了内部的数据结构,填充适当的值。

make 返回初始化后的(非零)值。

例如, make([]int, 10, 100) 分配了 100 个整数的数组,然后用长度 10 和容量 100 创建了 slice

结构指向数组的前 10 个元素。区别是,new([]int) 返回指向新分配的内存的指针,

而零值填充的 slice 结构是指向 nil 的 slice 值。

这个例子展示了 new 和 make 的不同。

var p *[]int = new([]int) ← 分配 slice 结构内存;*p == nil, 已经可用

var v []int = make([]int,100) ← v 指向一个新分配的有 100 个整数的数组。

var p *[]int = new([]int) ← 不必要的复杂例子

*p = make([]int, 100, 100)

v := make([]int, 100) ← 更常见

务必记得 make 仅适用于 map,slice 和 channel,并且返回的不是指针。

应当用 new 获得特定的指针。

new 分配;make 初始化上面的两段可以简单总结为:

? new(T) 返回 *T 指向一个零值 T

? make(T) 返回初始化后的 T

当然 make 仅适用于 slice,map 和 channel。

4、构造函数与复合声明

有时零值不能满足需求,必须要有一个用于初始化的构造函数

例如这个来自 os 包的例子。

func NewFile(fd int, name string) *File {

if fd < 0 {

return nil

}

f := new(File)

f.fd = fd

f.name = name

f.dirinfo = nil

f.nepipe = 0

return f

}

有许多冗长的内容。可以使用复合声明使其更加简洁,每次只用一个表达式创建一个新的实例。

func NewFile(fd int, name string) *File {

if fd < 0 {

return nil

}

f := File{fd, name, nil, 0} ← Create a new File

return &f ← Return the address of f

}

返回本地变量的地址没有问题;在函数返回后,相关的存储区域仍然存在。

事实上,从复合声明获取分配的实例的地址更好,

因此可以最终将两行缩短到一行。

return &File{fd, name, nil, 0}

在特定的情况下,如果复合声明不包含任何字段,它创建特定类型的零值。

表达式 new(File) 和 &File{} 是等价的。

复合声明同样可以用于创建 array,slice 和 map,通过指定适当的索引和 map 键 来标识字段。

十二、定义自己的类型

1、Go 允许定义新的类型,通过保留字 type 实现:

type foo int

创建了一个新的类型 foo 作用跟 int 一样。创建更加复杂的类型需要用到 struct 保留字。

这有个在一个数据结构中记录某人的姓名(string)和年龄(int),

并且使其成为一个新的类型的例子:

package main

import "fmt"

type NameAge struct {

name string ← 不导出

age int ← 不导出

}

func main() {

a := new(NameAge)

a.name = "Pete"

a.age = 42

fmt.Printf("%v\n", a)

}

通常,fmt.Printf("%v\n", a) 的输出是 &{Pete 42}

如果仅想打印某一个,或者某几个结构中的 字段,需要使用 .<field name>。例如,仅仅打印名字:

fmt.Printf("%s", a.name) ← %s 格式化字符串

2、结构字段

之前已经提到结构中的项目被称为field。

没有字段的结构:struct {}

有四个字段的:

struct {

x, y int

A *[]

int F func()

}

如果省略字段的名字,可以创建匿名字段,例如:

struct {

T1 ← 字段名字是 T1

*T2 ← 字段名字是 T2

P.T3 ← 字段名字是 T3

x,y int ←字段名字是x 和y

}

注意首字母大写的字段可以被导出,也就是说,在其他包中可以进行读写。

字段名以小写字母开头是当前包的私有的。包的函数定义是类似的。

3、方法

可以对新定义的类型创建函数以便操作,可以通过两种途径:

1. 创建一个函数接受这个类型的参数。

func doSomething(in1 *NameAge, in2 int) { /* ... */ }

(你可能已经猜到了)这是 函数调用。

2. 创建一个工作在这个类型上的函数:

func (in1 *NameAge) doSomething(in2 int) { /* ... */ }

这是方法调用,可以类似这样使用:

var n *NameAge

n.doSomething(2)

这里 Go 会查找 NameAge 类型的变量 n 的方法列表,没有找到就会再查找 *NameAge

类型的方法列表,并且将其转化为 (&n).doSomething(2)。

使用函数还是方法完全是由程序员说了算,但是若需要满足接口就必须使用方法。

如果没有这样的需求,那就完全由习惯来决定是使用函 数还是方法了。

如果 x 可获取地址,并且 &x 的方法中包含了 m, x.m() 是 (&x).m() 更

短的写法。

十四、转换

1、有时需要将一个类型转换为另一个类型。在 Go 中可以做到,不过有一些规则。

首先,将一个值转换为另一个是由操作符(看起来像函数:byte())完成的,

并且不是所有的转换都是允许的。

从string到字节或者ruin的slice。

mystring := "hello this is string"

byteslice := []byte(mystring)

转换到 byte slice,每个 byte 保存字符串对应字节的整数值。

注意 Go 的字符串是 UTF-8 编码的,一些字符可能是 1、2、3 或者 4 个字节结尾。

runeslice := []rune(mystring)

转换到 rune slice,每个 rune 保存 Unicode 编码的指针。

字符串中的每个字符对应一个整数。

从字节或者整形的slice到string。

b := []byte{‘h‘,‘e‘,‘l‘,‘l‘,‘o‘} ← 复合声明

s := string(b)

i := []rune{257,1024,65}

r := string(i)

对于数值,定义了下面的转换:

? 将整数转换到指定的(bit)长度:uint8(int);

? 从浮点数到整数:int(float32)。这会截断浮点数的小数部分;

? 其他的类似:float32(int)。

2、用户定义类型的转换

如何在自定义类型之间进行转换?这里创建了两个类型 Foo 和 Bar,

而 Bar 是 Foo 的一个别名:

type foo struct { int } ← 匿名字段

type bar foo ← bar 是 foo 的别名

然后:

var b bar=bar{1} ←声明b 为bar 类型

var f foo=b ←赋值b 到f

最后一行会引起错误:

cannot use b (type bar) as type foo in assignment(不能使用 b(类型 bar)作为

类型 foo 赋值)

这可以通过转换来修复:

var f foo = foo(b)

注意转换那些字段不一致的结构是相当困难的。

同时注意,转换 b 到 int 同样 会出错;

整数与有整数字段的结构并不一样。

时间: 2024-11-05 06:29:11

Go 语言基础(五) 之 进阶的相关文章

C语言基础五 数组的应用

.根据用户输入的10人成绩并将其保存到数组中,求最高成绩,最低成绩和平均成绩 int scoure[10];//存储10个数据的数组 int i; int sum;//总成绩 int max,min,avg;//最大值,最小值,平均成绩 for(i=0;i<10;i++){ printf("请输入%d个成绩",i+1);//获取用户输入的值 scanf("%d",&scoure[i]); sun+=scoure[i]//总成绩 }avg=sum/10;

第五天:语言基础(四)

Java 中的运算符 一门编程语言,最常坐的工作就是数据运算.在 Java 中,有丰富的运算符来进行数值运算.这些运算符可以分为以下几类: 算数运算符 关系运算符 位运算符 逻辑运算符 赋值运算符 其它运算符 算数运算符 先来说说算数运算符. 操作符 描述 说明 + 加--运算符两侧的值进行相加 注意类型转换 - 减--左操作数减去右操作数 注意类型转换 * 乘--运算符两侧的值进行相乘 注意类型转换 / 除--左操作数除以右操作数 注意左右操作数类型的不同,结果的类型也不同 % 取余--左操作

第二十五节:Java语言基础-面向对象基础

面向对象 面向过程的代表主要是C语言,面向对象是相对面向过程而言,Java是面向对象的编程语言,面向过程是通过函数体现,面向过程主要是功能行为. 而对于面向对象而言,将功能封装到对象,所以面向对象是基于面向过程的.以前是主要以面向过程为思想,现在是将功能装进对象中,如果要用到功能时,就调用该对象即可. 面向对象是入门思想的进步,适用于人类的思考,将复杂的东西进行了简单化,将我们程序员从执行者变化成指挥者.由以前面向功能(面向过程)转变为面向对象(封装的功能). **** 面向对象设计思想 设计思

黑马程序员---C语言基础---结构体

------iOS培训.Java培训.Android培训, iOS学习型技术博客,期待与您交流------ C语言基础---结构体 一.什么是结构体 介绍结构体之前,我想先简单介绍下数组.我想大家对数组都再熟悉不过了,顾名思义,数组就是将一些数据(元素)组合在一起,作为一个整体.使用数组需要注意的是这些元素必须是相同类型.而结构体和数组类似,也是将一些数据组合在一起作为一个整体,但是这些元素可以是不同类型.可以这么理解:结构体是更加灵活的数组,因为它允许元素是不同的类型. 实际生活中,如果我们要

嵌入式 Linux C语言——C语言基础

嵌入式 Linux C语言--C语言基础 一.数据类型 1.基本数据类型 数据类型是创建变量的模型.变量名是连续存储空间的别名,程序中使用变量命名存储空间,通过变量可以使用存储空间.变量所占的内存大小取决于创建变量的数据类型. 2.有符号和无符号 有符号数中数据类型的最高位用于标识数据的符号,最高位为1表示为负数,最高位为0表示为正数. 计算机中有符号数通常使用补码表示,正数的补码为正数本身,负数的补码为负数的绝对值的各位取反后加1. 计算机中无符号数通常使用原码表示,无符号数默认为正数,没有符

C#语言基础

第一部分 了解C# C#是微软公司在2000年7月发布的一种全新且简单.安全.面向对象的程序设计语言,是专门为.NET的应用而开发的.体现了当今最新的程序设计技术的功能和精华..NET框架为C#提供了一个强大的.易用的.逻辑结构一致的设计环境.其特点: 语言简洁 保留了C++的强大功能: 快速应用开发功能: 语言的的自由性: 强大的Web服务器控件: 支持跨平台: 与XML相融合: 第二部分 C#语言基础: 一.C#项目的组成结构: 1.项目后缀 .config——配置文件(存放配置参数文件)

【quick-cocos2d-x】Lua 语言基础

版权声明:本文为博主原创文章,转载请注明出处. 使用quick-x开发游戏有两年时间了,quick-x是cocos2d-Lua的一个豪华升级版的框架,使用Lua编程.相比于C++,lua的开发确实快速便捷了许多,下文只是lua这门语言的一个基础知识点,没有涵盖整个Lua的内容,但是作为对lua入门的初步了解还是可以的,由于内容精简了不少,所以语言上可能会有点跳跃,但是问题不大. 要了解一门语言,首先要了解的是语言的标识符.保留字.常量和变量,命名规范和注释以及数据类型等.然后是运算符.控制流语句

03 java语言基础逻辑运算符

03.01_Java语言基础(逻辑运算符的基本用法)(掌握) A:逻辑运算符有哪些 &,|,^,! &&,|| B:案例演示 逻辑运算符的基本用法 注意事项: a:逻辑运算符一般用于连接boolean类型的表达式或者值. b:表达式:就是用运算符把常量或者变量连接起来的符合java语法的式子. 算术表达式:a + b 比较表达式:a == b(条件表达式) C:结论: &逻辑与:有false则false. |逻辑或:有true则true. ^逻辑异或:相同为false,不同

嵌入式linux C++语言(二)——C++对C语言基础语法的扩展

嵌入式linux C++语言(二)--C++对C语言基础语法的扩展 C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面. 一.类型增强 1.类型检查更严格 在C语言中: const int a = 100; int *p = &a; 在C++语言中: const int a = 100;//必须在定义的时候初始化 const int *p = &a; 在C++语言中不能隐式转换数据类型. error: invalid conversion

VBA 语言基础

VBA 语言基础 第一节 标识符 一.定义 标识符是一种标识变量.常量.过程.函数.类等语言构成单位的符号,利用它可以完成对变量.常量.过程.函数.类等的引用. 二.命名规则 1) 字母打头,由字母.数字和下划线组成,如 A987b_23Abc 2) 字符长度小于40,(Excel2002 以上中文版等,可以用汉字且长度可达254 个字符) 3) 不能与VB 保留字重名,如public, private, dim, goto, next, with, integer, single等 第二节 运