精华内容
下载资源
问答
  • CS0136 无法在此范围中声明名为“channel”的局部变量或参数,因为该名称在封闭局 .ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel_a => { var pipel...

     

     

    CS0136 无法在此范围中声明名为“channel”的局部变量或参数,因为该名称在封闭局

     

     

         .ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel_a =>
                        {
                            var pipeline = channel_a.Pipeline;

    }

     

    展开全文
  • 如下代码不能通过编译 1 static void Main(string[] args) 2 { 3 int i = 0; 4 for(int i = 0; i < 10; i++) 5 { 6 WriteLine($"i={i}"); ...【错误原因】Line3与Line4中都用i作为了变量,...

    在VS2017中,如下代码不能通过编译

    using System;
    using static System.Console;
    
    namespace test
    {
        class Program
        {
    		static void Main(string[] args)
     		{
    			int i = 0;
    			for(int i = 0; i < 10; i++)
    			{
             		WriteLine($"i={i}");
         		}
         		ReadKey();
     		}
     	}
     }
    

    【错误原因】Line3与Line4中都用i作为了变量名,这在C#中是不允许的,虽然他们俩一个在Main()的作用域内,另一个在for循环的作用域内,但是Main()嵌套了for循环,这样的话对于编译器来说两个变量i会产生歧义。

    【解决方案】把其中一个变量名作更改即可
    下面的例子就没有问题(C#6语法)

    using System;
    using static System.Console;
    
    namespace test
    {
        //int i = 0; //错误,命名空间不能直接包含字段或方法之类的成员
        class Program
        {
            int i = 4396; //没问题
            static void Main(string[] args)
            {
                {
                    int i = 0;//没问题,同级的块中字段可以相同
                }
    
                for(int i = 0; i < 10; i++)//没问题,同级的块中字段可以相同
                {
                    WriteLine($"i={i}");
                    
                }
                Program p = new Program();
                WriteLine($"Global i={p.i}");
                ReadKey();
            }
        }
    }
    

    运行结果:

    i=0
    i=1
    i=2
    i=3
    i=4
    i=5
    i=6
    i=7
    i=8
    i=9
    Global i=4396
    

    至于为何Program类的成员变量i没有问题,我的理解是:C#中所有变量都是依托于类的,对于一个类来说,作用范围最大的某过于其public修饰的静态成员变量,其次就是普通成员变量了,因此可以把这里的i看作全局变量(至少对这个例子是的),而上述的错误是对于局部变量而言的。(如果这个解释有问题,欢迎在评论区纠正)

    注:C++中可以在内层作用域中重新定义外层作用域已有的名字
    因此以下C++代码没毛病

    #include<iostream>
    using namespace std;
    int main(){
    	int i=0;
    	for(int i=0;i<10;i++){
    		cout<<"i="<<i<<endl;
    	}
    	return 0;
    }
    

    有关C++变量作用域可以点击下面的链接了解:
    C++作用域

    展开全文
  • 参数 8.1.3 版已废弃。 值范围: TRUE | FALSE 默认值: FALSE mts_servers: 说明 : 指定启动例程后, 要为共享服务器环境创建的服务器进程的数量。 值范围: 根据操作系统而定。 默认值 : 1 mts_service: ...
  • 2、因为报表的“简介”列是大字段类型,如果是Oracle数据库,且采用的不是oracle10g的jdbc驱动,则可能不能正常的完成添加操作,需要将配置文件中此报表的 标签配置的SQL语句:insert into ...后面加上where ...
  •  本书针对C++初学者,从C语言基础知识开始介绍,然后在此基础上详细阐述C++新增的特性,因此不要 求读者有较多C语言方面的背景知识。本书可作为高等院校C++课程的教材,也可供初学者自学C++时使用。 本书享有...
  •  本书针对C++初学者,从C语言基础知识开始介绍,然后在此基础上详细阐述C++新增的特性,因此不要 求读者有较多C语言方面的背景知识。本书可作为高等院校C++课程的教材,也可供初学者自学C++时使用。 本书享有...
  •  本书针对C++初学者,从C语言基础知识开始介绍,然后在此基础上详细阐述C++新增的特性,因此不要 求读者有较多C语言方面的背景知识。本书可作为高等院校C++课程的教材,也可供初学者自学C++时使用。 本书享有...
  •  本书针对C++初学者,从C语言基础知识开始介绍,然后在此基础上详细阐述C++新增的特性,因此不要 求读者有较多C语言方面的背景知识。本书可作为高等院校C++课程的教材,也可供初学者自学C++时使用。 本书享有...
  • //数据串行发送,低位(bit0)前,高位(bit7)后 } k++; } IRcord[i]=value; value=0; } irpro_ok=1;//处理完毕标志位置1 } xian_shi() { uchar qian,bei,shi,ge; jj=mm; jj*=...
  • 一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。 句法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧包裹的内容一样。句法块...

    2.7. 作用域

    一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代码中可以有效使用这个名字的范围。

    不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。

    句法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧包裹的内容一样。句法块内部声明的名字是无法被外部块访问的。这个块决定了内部声明的名字的作用域范围。我们可以把块(block)的概念推广到包括其他声明的群组,这些声明在代码中并未显式地使用花括号包裹起来,我们称之为词法块。对全局的源代码来说,存在一个整体的词法块,称为全局词法块;对于每个包;每个for、if和switch语句,也都有对应词法块;每个switch或select的分支也有独立的词法块;当然也包括显式书写的词法块(花括弧包含的语句)。

    声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量,比如int、len和true等是在全局作用域的,因此可以在整个程序中直接使用。任何在函数外部(也就是包级语法域)声明的名字可以在同一个包的任何源文件中访问的。对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包。还有许多声明语句,比如tempconv.CToF函数中的变量c,则是局部作用域的,它只能在函数内部(甚至只能是局部的某些部分)访问。

    控制流标号,就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域。

    一个程序可能包含多个同名的声明,只要它们在不同的词法域就没有关系。例如,你可以声明一个局部变量,和包级的变量同名。或者是像2.3.3节的例子那样,你可以将一个函数参数的名字声明为new,虽然内置的new是全局作用域的。但是物极必反,如果滥用不同词法域可重名的特性的话,可能导致程序很难阅读。

    当编译器遇到一个名字引用时,它会对其定义进行查找,查找过程从最内层的词法域向全局的作用域进行。如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问:

    func f() {}
    
    var g = "g"
    
    func main() {
        f := "f"
        fmt.Println(f) // "f"; local var f shadows package-level func f
        fmt.Println(g) // "g"; package-level var
        fmt.Println(h) // compile error: undefined: h
    }
    

    在函数中词法域可以深度嵌套,因此内部的一个声明可能屏蔽外部的声明。还有许多语法块是if或for等控制流语句构造的。下面的代码有三个不同的变量x,因为它们是定义在不同的词法域(这个例子只是为了演示作用域规则,但不是好的编程风格)。

    func main() {
        x := "hello!"
        for i := 0; i < len(x); i++ {
            x := x[i]
            if x != '!' {
                x := x + 'A' - 'a'
                fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
            }
        }
    }
    

    x[i]x + 'A' - 'a'声明语句的初始化的表达式中都引用了外部作用域声明的x变量,稍后我们会解释这个。(注意,后面的表达式与unicode.ToUpper并不等价。)

    正如上面例子所示,并不是所有的词法域都显式地对应到由花括弧包含的语句;还有一些隐含的规则。上面的for语句创建了两个词法域:花括弧包含的是显式的部分,是for的循环体部分词法域,另外一个隐式的部分则是循环的初始化部分,比如用于迭代变量i的初始化。隐式的词法域部分的作用域还包含条件测试部分和循环后的迭代部分(i++),当然也包含循环体词法域。

    下面的例子同样有三个不同的x变量,每个声明在不同的词法域,一个在函数体词法域,一个在for隐式的初始化词法域,一个在for循环体词法域;只有两个块是显式创建的:

    func main() {
        x := "hello"
        for _, x := range x {
            x := x + 'A' - 'a'
            fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
        }
    }
    

    和for循环类似,if和switch语句也会在条件部分创建隐式词法域,还有它们对应的执行体词法域。下面的if-else测试链演示了x和y的有效作用域范围:

    if x := f(); x == 0 {
        fmt.Println(x)
    } else if y := g(x); x == y {
        fmt.Println(x, y)
    } else {
        fmt.Println(x, y)
    }
    fmt.Println(x, y) // compile error: x and y are not visible here
    

    第二个if语句嵌套在第一个内部,因此第一个if语句条件初始化词法域声明的变量在第二个if中也可以访问。switch语句的每个分支也有类似的词法域规则:条件部分为一个隐式词法域,然后是每个分支的词法域。

    在包级别,声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些相互嵌套或递归的类型或函数。但是如果一个变量或常量递归引用了自身,则会产生编译错误。

    在这个程序中:

    if f, err := os.Open(fname); err != nil { // compile error: unused: f
        return err
    }
    f.ReadByte() // compile error: undefined f
    f.Close()    // compile error: undefined f
    

    变量f的作用域只在if语句内,因此后面的语句将无法引入它,这将导致编译错误。你可能会收到一个局部变量f没有声明的错误提示,具体错误信息依赖编译器的实现。

    通常需要在if之前声明变量,这样可以确保后面的语句依然可以访问变量:

    f, err := os.Open(fname)
    if err != nil {
        return err
    }
    f.ReadByte()
    f.Close()
    

    你可能会考虑通过将ReadByte和Close移动到if的else块来解决这个问题:

    if f, err := os.Open(fname); err != nil {
        return err
    } else {
        // f and err are visible here too
        f.ReadByte()
        f.Close()
    }
    

    但这不是Go语言推荐的做法,Go语言的习惯是在if中处理错误然后直接返回,这样可以确保正常执行的语句不需要代码缩进。

    要特别注意短变量声明语句的作用域范围,考虑下面的程序,它的目的是获取当前的工作目录然后保存到一个包级的变量中。这本来可以通过直接调用os.Getwd完成,但是将这个从主逻辑中分离出来可能会更好,特别是在需要处理错误的时候。函数log.Fatalf用于打印日志信息,然后调用os.Exit(1)终止程序。

    var cwd string
    
    func init() {
        cwd, err := os.Getwd() // compile error: unused: cwd
        if err != nil {
            log.Fatalf("os.Getwd failed: %v", err)
        }
    }
    

    虽然cwd在外部已经声明过,但是:=语句还是将cwd和err重新声明为新的局部变量。因为内部声明的cwd将屏蔽外部的声明,因此上面的代码并不会正确更新包级声明的cwd变量。

    由于当前的编译器会检测到局部声明的cwd并没有使用,然后报告这可能是一个错误,但是这种检测并不可靠。因为一些小的代码变更,例如增加一个局部cwd的打印语句,就可能导致这种检测失效。

    var cwd string
    
    func init() {
        cwd, err := os.Getwd() // NOTE: wrong!
        if err != nil {
            log.Fatalf("os.Getwd failed: %v", err)
        }
        log.Printf("Working directory = %s", cwd)
    }
    

    全局的cwd变量依然是没有被正确初始化的,而且看似正常的日志输出更是让这个BUG更加隐晦。

    有许多方式可以避免出现类似潜在的问题。最直接的方法是通过单独声明err变量,来避免使用:=的简短声明方式:

    var cwd string
    
    func init() {
        var err error
        cwd, err = os.Getwd()
        if err != nil {
            log.Fatalf("os.Getwd failed: %v", err)
        }
    }
    

    我们已经看到包、文件、声明和语句如何来表达一个程序结构。在下面的两个章节,我们将探讨数据的结构。

     

    第三章 基础数据类型

    虽然从底层而言,所有的数据都是由比特组成,但计算机一般操作的是固定大小的数,如整数、浮点数、比特数组、内存地址等。进一步将这些数组织在一起,就可表达更多的对象,例如数据包、像素点、诗歌,甚至其他任何对象。Go语言提供了丰富的数据组织形式,这依赖于Go语言内置的数据类型。这些内置的数据类型,兼顾了硬件的特性和表达复杂数据结构的便捷性。

    Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。本章介绍基础类型,包括:数字、字符串和布尔型。复合数据类型——数组(§4.1)和结构体(§4.2)——是通过组合简单类型,来表达更加复杂的数据结构。引用类型包括指针(§2.3.2)、切片(§4.2))、字典(§4.3)、函数(§5)、通道(§8),虽然数据种类很多,但它们都是对程序中一个变量或状态的间接引用。这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。我们将在第7章介绍接口类型。

     

    3.1. 整型

    Go语言的数值类型包括几种不同大小的整数、浮点数和复数。每种数值类型都决定了对应的大小范围和是否支持正负符号。让我们先从整数类型开始介绍。

    Go语言同时提供了有符号和无符号类型的整数运算。这里有int8、int16、int32和int64四种截然不同大小的有符号整数类型,分别对应8、16、32、64bit大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64四种无符号整数类型。

    这里还有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int和uint;其中int是应用最广泛的数值类型。这两种类型都有同样的大小,32或64bit,但是我们不能对此做任何的假设;因为不同的编译器即使在相同的硬件平台上可能产生不同的大小。

    Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unicode码点。这两个名称可以互换使用。同样byte也是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。

    最后,还有一种无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。我们将在第十三章的unsafe包相关部分看到类似的例子。

    不管它们的具体大小,int、uint和uintptr是不同类型的兄弟类型。其中int和int32也是不同的类型,即使int的大小也是32bit,在需要将int当作int32类型的地方需要一个显式的类型转换操作,反之亦然。

    其中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位,一个n-bit的有符号数的值域是从$-2^{n-1}$到$2^{n-1}-1$。无符号整数的所有bit位都用于表示非负数,值域是0到$2^n-1$。例如,int8类型整数的值域是从-128到127,而uint8类型整数的值域是从0到255。

    下面是Go语言中关于算术运算、逻辑运算和比较运算的二元运算符,它们按照优先级递减的顺序排列:

    *      /      %      <<       >>     &       &^
    +      -      |      ^
    ==     !=     <      <=       >      >=
    &&
    ||
    

    二元运算符有五种优先级。在同一个优先级,使用左优先结合规则,但是使用括号可以明确优先顺序,使用括号也可以用于提升优先级,例如mask & (1 << 28)

    对于上表中前两行的运算符,例如+运算符还有一个与赋值相结合的对应运算符+=,可以用于简化赋值语句。

    算术运算符+-*/可以适用于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中,%取模运算符的符号和被取模数的符号总是一致的,因此-5%3-5%-3结果都是-2。除法运算符/的行为则依赖于操作数是否全为整数,比如5.0/4.0的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。

    一个算术运算的结果,不管是有符号或者是无符号的,如果需要更多的bit位才能正确表示的话,就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型,而且最左边的bit位是1的话,那么最终结果可能是负的,例如int8的例子:

    var u uint8 = 255
    fmt.Println(u, u+1, u*u) // "255 0 1"
    
    var i int8 = 127
    fmt.Println(i, i+1, i*i) // "127 -128 1"
    

    两个相同的整数类型可以使用下面的二元比较运算符进行比较;比较表达式的结果是布尔类型。

    ==    等于
    !=    不等于
    <     小于
    <=    小于等于
    >     大于
    >=    大于等于
    

    事实上,布尔型、数字类型和字符串等基本类型都是可比较的,也就是说两个相同类型的值可以用==和!=进行比较。此外,整数、浮点数和字符串可以根据比较结果排序。许多其它类型的值可能是不可比较的,因此也就可能是不可排序的。对于我们遇到的每种类型,我们需要保证规则的一致性。

    这里是一元的加法和减法运算符:

    +      一元加法(无效果)
    -      负数
    

    对于整数,+x是0+x的简写,-x则是0-x的简写;对于浮点数和复数,+x就是x,-x则是x 的负数。

    Go语言还提供了以下的bit位操作运算符,前面4个操作运算符并不区分是有符号还是无符号数:

    &      位运算 AND
    |      位运算 OR
    ^      位运算 XOR
    &^     位清空(AND NOT)
    <<     左移
    >>     右移
    

    位操作运算符^作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;也就是说,它返回一个每个bit位都取反的数。位操作运算符&^用于按位置零(AND NOT):如果对应y中bit位为1的话,表达式z = x &^ y结果z的对应的bit位为0,否则z对应的bit位等于x相应的bit位的值。

    下面的代码演示了如何使用位操作解释uint8类型值的8个独立的bit位。它使用了Printf函数的%b参数打印二进制格式的数字;其中%08b中08表示打印至少8个字符宽度,不足的前缀部分用0填充。

    var x uint8 = 1<<1 | 1<<5
    var y uint8 = 1<<1 | 1<<2
    
    fmt.Printf("%08b\n", x) // "00100010", the set {1, 5}
    fmt.Printf("%08b\n", y) // "00000110", the set {1, 2}
    
    fmt.Printf("%08b\n", x&y)  // "00000010", the intersection {1}
    fmt.Printf("%08b\n", x|y)  // "00100110", the union {1, 2, 5}
    fmt.Printf("%08b\n", x^y)  // "00100100", the symmetric difference {2, 5}
    fmt.Printf("%08b\n", x&^y) // "00100000", the difference {5}
    
    for i := uint(0); i < 8; i++ {
        if x&(1<<i) != 0 { // membership test
            fmt.Println(i) // "1", "5"
        }
    }
    
    fmt.Printf("%08b\n", x<<1) // "01000100", the set {2, 6}
    fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}
    

    (6.5节给出了一个可以远大于一个字节的整数集的实现。)

    x<<nx>>n移位运算中,决定了移位操作的bit数部分必须是无符号数;被操作的x可以是有符号数或无符号数。算术上,一个x<<n左移运算等价于乘以$2^n$,一个x>>n右移运算等价于除以$2^n$。

    左移运算用零填充右边空缺的bit位,无符号数的右移运算也是用0填充左边空缺的bit位,但是有符号数的右移运算会用符号位的值填充左边空缺的bit位。因为这个原因,最好用无符号运算,这样你可以将整数完全当作一个bit位模式处理。

    尽管Go语言提供了无符号数的运算,但即使数值本身不可能出现负数,我们还是倾向于使用有符号的int类型,就像数组的长度那样,虽然使用uint无符号类型似乎是一个更合理的选择。事实上,内置的len函数返回一个有符号的int,我们可以像下面例子那样处理逆序循环。

    medals := []string{"gold", "silver", "bronze"}
    for i := len(medals) - 1; i >= 0; i-- {
        fmt.Println(medals[i]) // "bronze", "silver", "gold"
    }
    

    另一个选择对于上面的例子来说将是灾难性的。如果len函数返回一个无符号数,那么i也将是无符号的uint类型,然后条件i >= 0则永远为真。在三次迭代之后,也就是i == 0时,i--语句将不会产生-1,而是变成一个uint类型的最大值(可能是$2^64-1$),然后medals[i]表达式运行时将发生panic异常(§5.9),也就是试图访问一个slice范围以外的元素。

    出于这个原因,无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。

    一般来说,需要一个显式的转换将一个值从一种类型转化为另一种类型,并且算术和逻辑运算的二元操作中必须是相同的类型。虽然这偶尔会导致需要很长的表达式,但是它消除了所有和类型相关的问题,而且也使得程序容易理解。

    在很多场景,会遇到类似下面代码的常见的错误:

    var apples int32 = 1
    var oranges int16 = 2
    var compote int = apples + oranges // compile error
    

    当尝试编译这三个语句时,将产生一个错误信息:

    invalid operation: apples + oranges (mismatched types int32 and int16)
    

    这种类型不匹配的问题可以有几种不同的方法修复,最常见方法是将它们都显式转型为一个常见类型:

    var compote = int(apples) + int(oranges)
    

    如2.5节所述,对于每种类型T,如果转换允许的话,类型转换操作T(x)将x转换为T类型。许多整数之间的相互转换并不会改变数值;它们只是告诉编译器如何解释这个值。但是对于将一个大尺寸的整数类型转为一个小尺寸的整数类型,或者是将一个浮点数转为整数,可能会改变数值或丢失精度:

    f := 3.141 // a float64
    i := int(f)
    fmt.Println(f, i) // "3.141 3"
    f = 1.99
    fmt.Println(int(f)) // "1"
    

    浮点数到整数的转换将丢失任何小数部分,然后向数轴零方向截断。你应该避免对可能会超出目标类型表示范围的数值做类型转换,因为截断的行为可能依赖于具体的实现:

    f := 1e100  // a float64
    i := int(f) // 结果依赖于具体实现
    

    任何大小的整数字面值都可以用以0开始的八进制格式书写,例如0666;或用以0x或0X开头的十六进制格式书写,例如0xdeadbeef。十六进制数字可以用大写或小写字母。如今八进制数据通常用于POSIX操作系统上的文件访问权限标志,十六进制数字则更强调数字值的bit位模式。

    当使用fmt包打印一个数值时,我们可以用%d、%o或%x参数控制输出的进制格式,就像下面的例子:

    o := 0666
    fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
    x := int64(0xdeadbeef)
    fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
    // Output:
    // 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
    

    请注意fmt的两个使用技巧。通常Printf格式化字符串包含多个%参数时将会包含对应相同数量的额外操作数,但是%之后的[1]副词告诉Printf函数再次使用第一个操作数。第二,%后的#副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。

    字符面值通过一对单引号直接包含对应字符。最简单的例子是ASCII中类似'a'写法的字符面值,但是我们也可以通过转义的数值来表示任意的Unicode码点对应的字符,马上将会看到这样的例子。

    字符使用%c参数打印,或者是用%q参数打印带单引号的字符:

    ascii := 'a'
    unicode := '国'
    newline := '\n'
    fmt.Printf("%d %[1]c %[1]q\n", ascii)   // "97 a 'a'"
    fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 国 '国'"
    fmt.Printf("%d %[1]q\n", newline)       // "10 '\n'"
    展开全文
  • 生命周期是指程序对象存在的有效时间段,在此时间段内,它可以被程序的其它部分引用,是一个运行时的概念。 语法块是由花括号所包含的一些列语句,就像函数体或循环体那样。块内部声明的对象是无法

    一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代码中刻意有效使用这个名字的范围。

    不要将作用域和声明周期混为一谈。作用域对应的是一个源代码的文本区域,它是编译时属性;生命周期是指程序中对象存在的有效时间段,在此时间段内,它可以被程序的其它部分引用,是一个运行时的概念。

    语法块是由花括号所包含的一些列语句,就像函数体或循环体那样。块内部声明的对象是无法被外部成员访问的。有一个语法块可以包含整个源代码中声明的对象,称为全局语法块;然后是每个包的包语法块;再然后是函数内部的语法块;再然后是for、if和switch语句的语法块;最后是这些语句内部的由花括号显示指定的语法块。

    整个源代码>包>文件>函数>循环/分支>循环/分支内部显示花括号

    声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量,比如 int、len和true等是全局作用域,因此可以在整个程序中直接使用。任何在函数外部(包级)声明的名字可以在同一个包的任何源文件中访问。对于导入的包,例如tempconv导入的fmt包,则是对应文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问此源文件导入的包。还有许多声明语句,比如tempconv.CToF函数中的变量c,则是局部作用域,它只能在函数内部(甚至是函数内部的某些部分)访问。

    控制流标号,就是break、continu或goto语句后面跟着的名字,则是函数级的作用域。

    一个程序可能包含多个同名的声明,只要它们在不同的词法域就没有关系。例如,你可以声明一个局部变量和包级变量同名。或者像前面所写将一个函数参数的名字声明为new,虽然内置的new是全局作用域。但是如果滥用不同词法域可同名的特性,可能导致程序很难阅读。

    当编译器遇到一个名字引用时,如果它看起来像一个声明,它会从最内层的局部作用域向全局作用域查找。如果未找到,则报告“未声明的名字”这样的错误。如果该名字在内部和外部都声明过,则首先在内部被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部声明的名字无法被访问:

    func f() {}
    
    var g = "g"
    
    func main() {
        f := "f"
        fmt.Println(f) // "f"; local var f shadows package-level func f
        fmt.Println(g) // "g"; package-level var
        fmt.Println(h) // compile error: undefined: h
    }

    在函数中词法域可以深度嵌套,因此内部的一个声明可能屏蔽外部的声明。下面的代码有三个不同的变量x,它们定义在不同的词法域:

    func main() {
        x := "hello!"
        for i := 0; i < len(x); i++ {
            x := x[i]
            if x != '!' {
                x := x + 'A' - 'a'
                fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
            }
        }
    }
    在 x[i ]和 x + 'A' - 'a'声明语句的初始化表达式中,都引用了外部作用域声明的变量x,稍后我们会解释这个。

    正如上面例子所示,并不是所有的词法域都显示地对应到由花括号包含的语句,还有一些隐式的规则。上面的for语句创建了两个词法域:花括号包含的for循环体是显示部分,

    另一个隐式部分则是循环的初始化部分,比如用于迭代变量的初始化。隐式的词法域部分还包含条件测试语和i++。

    下面的例子同样有三个不同的变量x,每个声明在不同的词法域,一个在函数体词法域,一个在for隐式的初始化词法域,一个在for循环体词法域;只有两个是显示创建的。

    func main() {
        x := "hello"
        for _, x := range x {
            x := x + 'A' - 'a'
            fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
        }
    }
    和for循环类似,if和switch语句也会在条件部分创建隐式词法域,还有他们对应的执行体词法域。下面的if-else 演示了x和y的有效作用域范围。

    if x := f(); x == 0 {
        fmt.Println(x)
    } else if y := g(x); x == y {
        fmt.Println(x, y)
    } else {
        fmt.Println(x, y)
    }
    fmt.Println(x, y) // compile error: x and y are not visible here
    第二个if语句嵌套在第一个内部,因此第一个if语句条件初始化词法域声明的变量在第二个if中也可以访问。switch语句的每个分支也有类似的词法域规则:条件部分是一个隐式词法域,然后是每个分支的词法域。

    在包级别声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些互相嵌套或递归的类型或函数。但是如果一个变量或常量递归引用了自身,则会产生编译错误。

    if f, err := os.Open(fname); err != nil { // compile error: unused: f
        return err
    }
    f.ReadByte() // compile error: undefined f
    f.Close()    // compile error: undefined f
    变量f的作用域只有在if语句内,因此后面的语句将无法引用它,这将导致编译错误。

    通常需要在if之前声明变量,这样可以确保后面的语句依然可以访问该变量:

    f, err := os.Open(fname)
    if err != nil {
        return err
    }
    f.ReadByte()
    f.Close()
    你可能会考虑通过将ReadByte和Close移动到if的else块来解决这个问题:

    if f, err := os.Open(fname); err != nil {
        return err
    } else {
        // f and err are visible here too
        f.ReadByte()
        f.Close()
    }
    但这不是Go语言推荐的做法,Go语言的习惯是在if中处理错误然后直接返回,这样可以确保正常执行的语句不需要代码缩进。

    要特别注意短变量声明语句的作用域范围,考虑下面的程序,它的目的是获取当前的工作目录然后保存到一个包级别的变量中。这本来可以直接通过调用os.Getwd完成,但是将这个从主逻辑中分离出来可能会更好,特别是在需要处理错误的时候。函数log.Fataif用于打印日志信息,然后调用os.Exit(1)终止程序。

    var cwd string
    
    func init() {
        cwd, err := os.Getwd() // compile error: unused: cwd
        if err != nil {
            log.Fatalf("os.Getwd failed: %v", err)
        }
    }
    虽然cwd在外部已经生命果,但是:=语句还是将cwd重新声明为新的局部变量。因为内部声明的cwd将屏蔽外部的声明,因此上面的代码并不会正确更新包级别的变量cwd。由于当前的编译器会检测到局部变量cwd并没有使用,所以会编译失败。但是这种检测并不可靠。因为一些小的代码变更,例如增加一个局部cwd的打印语句,就可能导致这种检测失效。

    var cwd string
    
    func init() {
        cwd, err := os.Getwd() // NOTE: wrong!
        if err != nil {
            log.Fatalf("os.Getwd failed: %v", err)
        }
        log.Printf("Working directory = %s", cwd)
    }
    全局的cwd变量依然没有被正确地初始化,而且看似正常的日志输出更是让这个bug更加隐晦。

    有许多方式可以避免出现类似潜在的问题。最直接的方法是通过单独声明err变量,来避免使用:=的简短声明方式:

    var cwd string
    
    func init() {
        var err error
        cwd, err = os.Getwd()
        if err != nil {
            log.Fatalf("os.Getwd failed: %v", err)
        }
    }

    展开全文
  • 您可以全部或部分复制,使用,修改,制作其衍生作品并分发代码,只要您将此声明保留与任何已分发作品相关的文档即可。 未经事先书面许可,不得使用纽约联邦储备银行(FRBNY)的名字或任何作者的名字来认可或...
  • 如果需要,可以配置进行更改-许多分级可能不可用或可能超出范围迄今为止,OMDb API可能无法按Netflix使用的名称找到标题,或者可能无法获得评级的许多其他原因-由于我们每天只能拨打OMDb API的电话数量有限,...
  • 630:对变量的引用不明确,如果两个结构体包含的成员名称相同,并且这两个结构体包含同一个结构体内,然后再打开+fab选项,直接引用了具有不同结构体具有相同名称的内部成员 634:进行比较运算==或!=...
  • jsp内置对象的用法

    2012-03-07 09:48:34
    它开始于服务器的启动, 直到服务器的关闭,在此期间,此对象将一直存在;这样在用户的前后连接或不同用户之间的连接,可以对此对象的同一属性进行操作;在任何地方对此对象属性 的操作,都将影响到其他用户对此的...
  • ZCE-crx插件

    2021-03-10 02:36:33
    我相信,增加通过考试机会的最有效方法之一就是从练习考试的错误吸取教训,就像这种方法一样! 但是,您最好的朋友永远是PHP.NET。 从1.0.2版开始,您可以导出PDF格式的测试,测试可以包含5或10个随机问题。 ...
  • C#微软培训资料

    2014-01-22 14:10:17
    18.2 C #代码调用 C++和 VB 编写的组件 .240 18.3 版 本 控 制 .249 18.4 代 码 优 化 .252 18.5 小 结 .254 第五部分 附 录 .255 附录 A 关 键 字.255 附录 B 错 误 码.256 附录 C .Net 名字空间...
  • php高级开发教程说明

    2008-11-27 11:39:22
    更大的项目如果缺乏计划将导致更多的错误,开发后期,可能会遇到没有或无法预见的 困难,这是由于缺乏计划的时间和工作,这些困难可能会严重到让你彻底地重组整个项目。例 如,对一个依赖额外数据库提取层的数据库...
  • C#微软培训教材(高清PDF)

    千次下载 热门讨论 2009-07-30 08:51:17
    18.2 C #代码调用 C++和 VB 编写的组件 .240 18.3 版 本 控 制 .249 18.4 代 码 优 化 .252 18.5 小 结 .254 第五部分 附 录 .255 附录 A 关 键 字.255 附录 B 错 误 码.256 附录 C .Net 名字空间...
  • PL/SQL 基础.doc

    2010-09-29 09:31:29
    (在此里层,对其他里层则不影响,想要不影响此里层的话可以使用标号标注外层); ---- 第四章 PL/SQL控制语句 ---- 1. 条件语句 IF boolean_expression1(条件1) THEN ... ELSIF boolean_expression2(条件2) ...
  • 之外,用户需要亲临检测机构实地送检,检测的每一个环节用户也无法有效的进行追踪,这些都制约着认证检测领域的进一步发展。鉴于市场上用户的手机型号、种类、屏幕分辨率等参差不齐,传统方式根据主流系统分别...
  • 在此基础之上,稍作定制即可实现一部手机上的“虚拟手机”。当然您也可以发挥想象,定制成应用于数据加密,数据隔离,隐私保护,企业管理的应用系统。 对于内部App的完全控制能力 VA对于内部的App具有完全的监控和...
  • 15.4.2 类层次结构漫游 365 15.4.3 类对象的构造与析构 367 15.4.4 typeid和扩展的类型信息 368 15.4.5 rtti的使用和误用 370 15.5 指向成员的指针 371 15.5.1 基类和派生类 373 15.6 自由存储 374 ...
  • 的对应命令”请务必准确填写静态库公开导出的符号名称(C函数(cdecl)编译后,符号名称通常是函数名称前加下划线(_));“的对应命令”以@开头表示以cdecl方式调用,否则表示以默认的stdcall...
  • C++程序设计语言(特别版)--源代码

    热门讨论 2012-04-23 07:33:51
    15.4.2 类层次结构漫游 365 15.4.3 类对象的构造与析构 367 15.4.4 typeid和扩展的类型信息 368 15.4.5 rtti的使用和误用 370 15.5 指向成员的指针 371 15.5.1 基类和派生类 373 15.6 自由存储 374 ...

空空如也

空空如也

1 2 3 4 5
收藏数 83
精华内容 33
关键字:

无法在此范围中声明名