Go面试高频考点-make/new、结构体、指针、map

69 阅读7分钟

指针传递调用函数

对于基本类型的指针,用法和C一样

func func1(x *int) {
    (*x) += 1
}
func main() {
    var x = 1
    var p *int = &x
    fmt.Println(*p)
    func1(p)
    fmt.Println(*p)
}
func func1(x []int) {
	x[0] += 1
}
func func2(x []int) {
	x = append(x, 5)
}
func main() {
	var x []int = []int{1, 2}
	fmt.Println(x)	[1 2]
	func1(x)
	fmt.Println(x)	//[2 2]
	func2(x)
	fmt.Println(x)	//[2 2]
}
func func2(x *[]int) {
    *x = append(*x, 5)
}
func main() {
    x := []int{}
    func2(&x)
    fmt.Println(x) //[5]
}

引用类型和结构体类型的赋值

注意对于切片或数组类型,直接截取后给新变量赋值后,修改新变量会影响旧变量

func main() {
    var x []int = []int{1, 2, 3, 4, 5}
    fmt.Printf("%T, %v\n", x, x) //[]int, [1 2 3 4 5]
​
    y := x[0:]
    fmt.Printf("%T, %v\n", y, y) //[]int, [1 2 3 4 5]
​
    y[0] += 1
    fmt.Printf("%T, %v\n", x, x) //[]int, [2 2 3 4 5]
    fmt.Printf("%T, %v\n", y, y) //[]int, [2 2 3 4 5],会修改原来的切片的值
}
func main() {
    var x [5]int = [5]int{1, 2, 3, 4, 5}
    fmt.Printf("%T, %v\n", x, x) //[5]int, [1 2 3 4 5]
​
    y := x[0:]
    fmt.Printf("%T, %v\n", y, y) //[]int, [1 2 3 4 5]
​
    y[0] += 1
    fmt.Printf("%T, %v\n", x, x) //[5]int, [2 2 3 4 5]
    fmt.Printf("%T, %v\n", y, y) //[]int, [2 2 3 4 5],会修改原来的数组的值
}

对于结构体,属于值类型;除非传递的是结构体的地址引用,否则修改新变量不会对就变量产生副作用(但是有浅拷贝的影响)

package main
​
import "fmt"
​
type Person struct {
    Id  int
    Ptr *int
}
​
// 总的来说两个不同的变量一定占用不同的内存空间,关键是变量存的值是多少
func main() {
​
    x := 1
    p := Person{Id: 0, Ptr: &x}                                           //这种方式相当于直接复制了一个结构体
    fmt.Printf("%T, %v, %p, %p, %d\n", p, p, &(p.Id), p.Ptr, *p.Ptr)      //两个不同的变量一定占用不同的内存空间,关键是变量存的值是多少
    pp := p                                                               //浅拷贝, Id和Ptr都重新占用了新的内存,但是Ptr的值还是指向原来的位置
    fmt.Printf("%T, %v, %p, %p, %d\n", pp, pp, &(pp.Id), pp.Ptr, *pp.Ptr) //main.Person, {0 0xc00000a0d8}, 0xc0000260a0, 0xc00000a0d8, 1
    pp.Id = 1
    (*pp.Ptr) = 2
    fmt.Printf("%T, %v, %p, %p, %d\n", p, p, &(p.Id), p.Ptr, *p.Ptr)      //main.Person, {0 0xc00000a0d8}, 0xc000026070, 0xc00000a0d8, 2
    fmt.Printf("%T, %v, %p, %p, %d\n", pp, pp, &(pp.Id), pp.Ptr, *pp.Ptr) //main.Person, {1 0xc00000a0d8}, 0xc0000260a0, 0xc00000a0d8, 2
    fmt.Printf("---\n")
​
    y := 1
    p2 := &Person{Id: 0, Ptr: &y}                                         //p2是一个地址,直接把地址的值赋给了pp2
    fmt.Printf("%T, %v, %p, %p, %d\n", p2, p2, &(p2.Id), p2.Ptr, *p2.Ptr) //*main.Person, &{0 0xc00000a118}, 0xc000026110, 0xc00000a118, 1
    pp2 := p2
    fmt.Printf("%T, %v, %p, %p, %d\n", pp2, pp2, &(pp2.Id), pp2.Ptr, *pp2.Ptr) //*main.Person, &{0 0xc00000a118}, 0xc000026110, 0xc00000a118, 1
    pp2.Id = 1
    (*pp2.Ptr) = 2
    fmt.Printf("%T, %v, %p, %p, %d\n", p2, p2, &(p2.Id), p2.Ptr, *p2.Ptr)      //*main.Person, &{0 0xc00000a118}, 0xc000026110, 0xc00000a118, 2
    fmt.Printf("%T, %v, %p, %p, %d\n", pp2, pp2, &(pp2.Id), pp2.Ptr, *pp2.Ptr) //*main.Person, &{0 0xc00000a118}, 0xc000026110, 0xc00000a118, 2
    fmt.Printf("---\n")
​
    z := 1
    p3 := new(Person)   //p3是一个地址,直接把地址赋值给了pp3,这种方式等价于p2:=&Person{Id: 0, Ptr: &y}      
    p3.Id, p3.Ptr = 0, &z
    fmt.Printf("%T, %v, %p, %p, %d\n", p3, p3, &(p3.Id), p3.Ptr, *p3.Ptr)
    pp3 := p3
    fmt.Printf("%T, %v, %p, %p, %d\n", pp3, pp3, &(pp3.Id), pp3.Ptr, *pp3.Ptr)
    pp3.Id = 1
    (*pp3.Ptr) = 2
    fmt.Printf("%T, %v, %p, %p, %d\n", p3, p3, &(p3.Id), p3.Ptr, *p3.Ptr)
    fmt.Printf("%T, %v, %p, %p, %d\n", pp3, pp3, &(pp3.Id), pp3.Ptr, *pp3.Ptr)
    /*
    *main.Person, &{0 0xc0000960d8}, 0xc00008a120, 0xc0000960d8, 1
    *main.Person, &{0 0xc0000960d8}, 0xc00008a120, 0xc0000960d8, 1
    *main.Person, &{1 0xc0000960d8}, 0xc00008a120, 0xc0000960d8, 2
    *main.Person, &{1 0xc0000960d8}, 0xc00008a120, 0xc0000960d8, 2
     */
    fmt.Printf("---\n")
}
​

make和new的区别

new主要用于基本数据类型和自定义结构体类型(切片等类型当然也可以,但是不常用),申请一块内存空间,然后返回指向这快内存的指针

make只用于切片、映射和通道等,返回初始化后的变量本身,因为这三个类型是引用类型

func main() {
    arr := new([]int)
    (*arr) = append((*arr), 1)
    fmt.Println(arr, *arr)  //&[1] [1]
}
func main() {
    p := new(int)                 //new返回的是指针
    fmt.Printf("%v, %v\n", p, *p) //0xc00000a0d8, 0
​
    p1 := []int{1, 2}
    fmt.Printf("%T, %v\n", p1, p1) //[]int, [1 2]
​
    p3 := make([]int, 5)
    fmt.Printf("%T, %v\n", p3, p3)  //[]int, [0 0 0 0 0]
    
    p2 := [5]int{}
    fmt.Printf("%T, %v\n", p2, p2) //[5]int, [0 0 0 0 0]
}

结构体和指针

先说结论:

  • 结构体的方法和普通函数修改指针的效果是一样的,没有区别
  • UpdatePerson1和UpdatePerson2是等价的,go语言语法糖将p.Id转换成了(*p).Id
package main
​
import "fmt"
​
type Person struct {
    Id int
}
​
func UpdatePerson1(p *Person) {
    p.Id = 1
}
func UpdatePerson2(p *Person) {
    (*p).Id = 2
}
func UpdatePerson3(p Person) {
    //按值传递,可类比C语言的按值传递
    p.Id = 3
}
func (p *Person) UpdatePerson4() {
    p.Id = 4
}
func (p *Person) UpdatePerson5() {
    (*p).Id = 5
}
func (p Person) UpdatePerson6() {
    p.Id = 6
}
func main() {
    p := Person{Id: 0}
    fmt.Printf("%T, %v\n", p, p) //main.Person, {0}
    UpdatePerson1(&p)
    fmt.Printf("%T, %v\n", p, p) //main.Person, {1}
    UpdatePerson2(&p)
    fmt.Printf("%T, %v\n", p, p) //main.Person, {2}
    UpdatePerson3(p)
    fmt.Printf("%T, %v\n", p, p) //main.Person, {2}
    fmt.Printf("---\n")
​
    p2 := &Person{Id: 0}
    fmt.Printf("%T, %v\n", p2, p2) //*main.Person, &{0}
    UpdatePerson1(p2)              //有作用
    fmt.Printf("%T, %v\n", p2, p2) //*main.Person, &{1}
    UpdatePerson2(p2)              //有作用
    fmt.Printf("%T, %v\n", p2, p2) //*main.Person, &{2}
    UpdatePerson3(*p2)             //没有作用
    fmt.Printf("%T, %v\n", p2, p2) //*main.Person, &{2}
    fmt.Printf("---\n")
​
    p3 := new(Person)
    fmt.Printf("%T, %v\n", p3, p3) //*main.Person, &{0}
    fmt.Println(p3)                //&{0}
    UpdatePerson1(p3)
    fmt.Printf("%T, %v\n", p3, p3) //*main.Person, &{1}
    UpdatePerson2(p3)
    fmt.Printf("%T, %v\n", p3, p3) //*main.Person, &{2}
    UpdatePerson3(*p3)
    fmt.Printf("%T, %v\n", p3, p3) //*main.Person, &{2}
    fmt.Printf("---\n")
​
    p4 := Person{Id: 0}
    fmt.Printf("%T, %v\n", p4, p4) //main.Person, {0}
    p4.UpdatePerson4()
    fmt.Printf("%T, %v\n", p4, p4) //main.Person, {4}
    p4.UpdatePerson5()
    fmt.Printf("%T, %v\n", p4, p4) //main.Person, {5}
    p4.UpdatePerson6()
    fmt.Printf("%T, %v\n", p4, p4) //main.Person, {5}
    fmt.Printf("---\n")
​
    p5 := &Person{Id: 0}
    fmt.Printf("%T, %v\n", p5, p5) //*main.Person, &{0}
    p5.UpdatePerson4()             //有作用
    fmt.Printf("%T, %v\n", p5, p5) //*main.Person, &{4}
    p5.UpdatePerson5()             //有作用
    fmt.Printf("%T, %v\n", p5, p5) //*main.Person, &{5}
    p5.UpdatePerson6()             //没有作用
    fmt.Printf("%T, %v\n", p5, p5) //*main.Person, &{5}
    fmt.Printf("---\n")
​
    p6 := new(Person)
    fmt.Printf("%T, %v\n", p6, p6) //*main.Person, &{0}
    fmt.Println(p6)                //&{0}
    p6.UpdatePerson4()
    fmt.Printf("%T, %v\n", p6, p6) //*main.Person, &{4}
    p6.UpdatePerson5()
    fmt.Printf("%T, %v\n", p6, p6) //*main.Person, &{5}
    p6.UpdatePerson6()
    fmt.Printf("%T, %v\n", p6, p6) //*main.Person, &{5}
    fmt.Printf("---\n")
}

C语言通过指针访问结构体成员示例

#include <stdio.h>
#include <stdlib.h>struct Person{
    int id;
};
void UpdatePerson(Person *p){
    p->id += 1;
}
void UpdatePerson2(Person *p){
    (*p).id +=1;
}
int main(){
    Person p;
    p.id = 1;
    UpdatePerson(&p);
    printf("%d\n", p.id);   //2
    UpdatePerson2(&p);
    printf("%d\n", p.id);   //3
    return 0;
}

另外,按地址传递调用函数,以下三种方式定义的变量等价,都会修改原始变量的值,关键是函数的参数是按地址传递

type Person struct {
    Id int
}
func UpdatePerson1(p *Person) {
    fmt.Printf("[UpdatePerson1] &p: %p\n", p)
    p.Id = 1
}
func main() {
    p := Person{Id: 0}                   //这种方式传入的p变量的地址,函数内部和调用的变量是相同的内存空间
    fmt.Printf("%T, %p, %v\n", p, &p, p) //main.Person, 0xc000096068, {0}
    UpdatePerson1(&p)                    //[UpdatePerson1] &p: 0xc000096068
    fmt.Printf("%T, %p, %v\n", p, &p, p) //[UpdatePerson1] &p: 0xc000096068
    fmt.Printf("---\n")
​
    p2 := &Person{Id: 0}                   ////这种方式传入的p变量的地址,函数内部和调用的变量是相同的内存空间
    fmt.Printf("%T, %p, %v\n", p2, p2, p2) //*main.Person, 0xc00000a118, &{0}
    UpdatePerson1(p2)                      //[UpdatePerson1] &p: 0xc00000a118
    fmt.Printf("%T, %p, %v\n", p2, p2, p2) //*main.Person, 0xc00000a118, &{1}
    fmt.Printf("---\n")
​
    p3 := new(Person)
    fmt.Printf("%T, %p, %v\n", p3, p3, p3)
    UpdatePerson1(p3)
    fmt.Printf("%T, %p, %v\n", p3, p3, p3)
    fmt.Printf("---\n")
}

~

map

map底层是一个hash表,通过键值对进行映射