精华内容
下载资源
问答
  • TypeScript 类型声明书写踩坑全解析

    千次阅读 2020-06-12 10:43:45
    本文总结了TypeScript类型声明的书写,很多时候写TypeScript不是问题,写类型就特别纠结,我总结下,我在使用TypeScript中遇到的问题。如果你遇到类型声明不会写的时候,多看看lodash的声明,因为lodash对数据进行...

    在这里插入图片描述
    本文总结了TypeScript类型声明的书写,很多时候写TypeScript不是问题,写类型就特别纠结,我总结下,我在使用TypeScript中遇到的问题。如果你遇到类型声明不会写的时候,多看看lodash的声明,因为lodash对数据进行各种变形操作,所以你能遇到的,都有参考示例。

    基本类型

     // 变量
     const num: number = 1;
     const str: string = 'str';
     const bool: boolean = true;
    
     const nulls: null = null;
     const undefine: undefined = undefined;
     const symbols: symbol = Symbol('symbal');
    
     const any: any = 'any types'; // typescript的any类型,相当于什么类型约束都没有
    

    数组

     // 数组: 推荐使用T[]这种写法
     const nums: number[] = [1, 2, 3, 4];
    
     // 不推荐:Array<T>泛型写法,因为在JSX中不兼容,所以为了统一都使用T[]这种类型
     const strs: Array<string> = ['s', 't', 'r'];
    
     const dates: Date[] = [new Date(), new Date()];
    

    数组的concat方法,返回类型为never[]问题

     // 数组concat方法的never问题
     // 提示: Type 'string' is not assignable to type 'never'.
     const arrNever: string[] = [].concat(['s']);
    
     // 主要问题是:[]数组,ts无法根据上下文判断数组内部元素的类型
     // @see https://github.com/Microsoft/TypeScript/issues/10479
     const fixArrNever: string[] = ([] as string[]).concat(['s']);
    
    

    接口

    接口是 TypeScript 的一个核心知识,它能合并众多类型声明至一个类型声明:

    而且接口可以用来声明:函数,类,对象等数据类型

    interface Name {
      first: string;
      second: string;
    }
    
    let username: Name = {
      first: 'John',
      second: 'Doe'
    };
    

    anynullundefinedvoid类型

    // 特殊类型
    const any: any = 'any types'; // typescript的any类型,相当于什么类型都没写
    let nobody: any = 'nobody, but you';
    nobody = 123;
    
    let nulls: number = null;
    let bool: boolean = undefined;
    
    // void
    function printUsername (name: string): void {
        console.log(name);
    }
    

    联合类型

    联合类型在option bags模式场景非常实用,使用 **|**来做标记

    function options(opts: {
        types?: string;
        tag: string | number;
    }): void {
        
    }
    

    交叉类型

    最典型的使用场景就是继承和mixin,或者copy等操作

    // 交叉类型:如果以后遇到此种类型声明不会写,直接看Object.assign声明写法
    function $extend<T, U>(first: T, second: U): T & U {
      return Object.assign(first, second); // 示意而已
    }
    

    元组 tuple

    let nameNumber: [string, number];
    
    // Ok
    nameNumber = ['Jenny', 221345];
    
    // Error
    // nameNumber = ['Jenny', '221345'];
    
    let tuple: [string, number];
    nameNumber = ['Jenny', 322134];
    
    const [usernameStr, uselessNum] = nameNumber;
    

    type的作用

    type用来创建新的类型,也可以重命名(别名)已有的类型,建议使用type创建简单类型,无嵌套的或者一层嵌套的类型,其它复杂的类型都应该使用interface, 结合implements ,extends实现。

    type StrOrNum = string | number;
    
    // 使用
    let sample: StrOrNum;
    sample = 123;
    sample = '123';
    
    // 会检查类型
    sample = true; // Error
    

    实践中遇到的问题


    第三方库没有提供声明d.ts文件 (常见)

    如果第三方库没有提供声明文件,第一时间去微软官方的仓库https://github.com/borisyankov/DefinitelyTyped 查找,或者在npmjs.com上搜索@types/依赖的模块名大部分情况都可以找到。

    手动添加声明文件

    声明文件一般都是统一放置在types文件夹下

    // 例子: types/axios.d.ts
    declare module 'axios'; // 这里的axios声明为any类型
    

    全局变量

    例如一些库直接把在window上添加的全局变量

    // globals.d.ts
    // 例子:jQuery,现实中jQuery是有.d.ts
    declare const jQuery: any;
    declare const $: typeof jQuery;
    

    JavaScript资源

    在前端工程中,import很多非js资源,例如:css, html, 图片,vue, 这种ts无法识别的资源时,就需要告诉ts,怎么识别这些导入的资源的类型。

    // 看看vue怎么处理的:shims-vue.d.ts
    declare module '*.vue' {
      import Vue from 'vue';
      export default Vue;
    }
    
    // html
    declare module '*.html';
    // css
    declare module '*.css';
    

    强制类型转换

    有时候遇到需要强制类型转换,尤其是对联合类型或者可选属性时。

    // 第一种:使用<>括号
    const convertArrType: string[] = <Array<string>>[].concat(['s']);
    
    // 第二种:使用as关键字
    const fixArrNever: string[] = ([] as string[]).concat(['s']);
    

    建议使用第二种,因为兼容JSX,第一种官方也不推荐了,虽然它是合法的。

    可选属性和默认属性

    API中提供的参数很多都有默认值,或者属性可选,怎么书写呢?

    class Socket {}
    // 联合类型
    export type SocketType = 'WebSocket' | 'SockJs';
    
    export interface SocketOptions {
      type: SocketType;
      protocols?: string | string[]; // 可选
      pingMessage: string | (() => string); // 联合类型,可以为string或者函数
      pongMessage: string | (() => string);
    }
    
    // 默认值
    export function eventHandler = (
      evt: CloseEvent | MessageEvent | Event,
      socket: Socket,
      type = 'WebSocket' // 默认值
    ) => any;
    

    独立函数怎么声明类型

    刚开始我也很纠结这个问题,我就是一个独立的函数,怎么声明类型呢?尤其是写事件处理函数的API时。

    class Socket {}
    // 函数的声明方式
    export type SocketEventHandler = (
      evt: CloseEvent | MessageEvent | Event,
      socket: Socket
    ) => any;
    
    const eventHandler: SocketEventHandler = (evt, socket) => {
    }
    
    // 可选参数和rest参数
    let baz = (x = 1) => {};
    let foo = (x: number, y: number) => {};
    let bar = (x?: number, y?: number) => {};
    let bas = (...args: number[]) => {};
    
    

    索引属性类型声明

    JavaScript中的对象都可以使用字符串索引直接取属性或者调用方法,TypeScript中也有相应的类型声明方法。

    type Hello = {
        hello: 'world';
        // key只是一个形式属性名(类似形参一样)
        [key: string]: string;
    };
    
    const greeting: Hello = {
        hi: 'morning'
    }
    
    console.log(greeting['hi']);
    

    动态添加的属性声明

    有的时候我们只声明了一个基本的类型结构,然后后续有扩展的情况 ,尤其时二次封装时的options

    interface AxiosOptions {}
    
    type AjaxOptions = {
       axiosOptions: AxiosOptions;
       // 额外扩展的放入到单独的属性节点下 
       extraOptions: {
           [prop: string]: any
       }; 
    };
    
    type AjaxOptions1 = {
      axiosOptions?: AxiosOptions;
      // 不要这样写,因为axiosOptions拼写错误时,ts不会提示
      // 尽量把后续扩展的属性,移动到独立的属性节点下
      [prop: string]: any
    };
    
    const ajaxOptions: AjaxOptions1 = {
      axiosOptions1: {}; // 本意是axiosOptions,但是ts不会提示
    }
    

    !的使用

    ! 标识符告诉ts编译器,声明的变量没有问题,再运行期不会报错。

    class BaseSelect extends Vue {
        options: string[]; // 这里会提示没有在constructor中初始化
        
        created() {
            this.options = ['inited']
        }
    }
    
    
    class BaseSelect extends Vue {
        options!: string[]; // 使用 ! 告诉编译器,我知道自己在做什么
        
        created() {
            this.options = ['inited']
        }
    }
    

    this 的使用

    对于独立使用的函数,可以声明指定的调用上下文

    class Handler {
        info: string;
        // 声明指定的this上下文
        onClickBad(this: Handler, e: Event) {
            // oops, used this here. using this callback would crash at runtime
            this.info = e.message;
        }
    }
    let h = new Handler();
    uiElement.addClickListener(h.onClickBad); // error!
    

    声明合并(扩展Vue声明)

    来看看使用场景,扩展vue,在vue上添加全局的属性。

    // vue的声明在 vue/types/vue.d.ts
    declare module 'vue/types/vue' {
      // 相当于Vue.$eventBus
      interface Vue {  
        $eventBus: Vue;
      }
        
      // 相当于在Vue.prototype.$eventBus
      interface VueConstructor {
        $eventBus: Vue;
      }
    }
    

    忽略TypeScript的校验

    Use // @ts-check to opt in to type checking for a single file.
    Use // @ts-nocheck to opt out of type checking for a single file.
    Use // @ts-ignore to opt out of type checking for a single line.
    

    总结

    TypeScript 声明还有很多高级的用法,目前我也没有用到那么多,在我纠结不会写声明的时候,我就会看看别人的声明文件时怎么写的。

    注意:尽量不要把解构声明写在一起,可读性极差。

    class Node {
      onNodeCheck(checkedKeys: any, { // 解构
          checked, checkedNodes, node, event,
        } : { // 声明
          node: any;
          [key: string]: any;
        }
      ) { 
      }
    }
    

    150讲轻松搞定Python网络爬虫


    Google开发专家带你学 AI:入门到实战(Keras/Tensorflow)(附源码)

    参考:https://juejin.im/post/5d64c2bef265da03da24a410#heading-16

    展开全文
  • C语言函数的定义和声明

    万次阅读 多人点赞 2019-08-08 14:38:17
    本科学C语言的时候,就对函数的定义和声明的作用很迷糊,刚看到一篇博客,写得非常清楚,贴出来与各位共享! 一、函数的声明 1.在C语言中,函数的定义顺序是有讲究的:默认情况下,只有后面定义的函数才可以调用...

    本科学C语言的时候,就对函数的定义和声明的作用很迷糊,刚看到一篇博客,写得非常清楚,贴出来与各位共享!

    一、函数的声明

    1.在C语言中,函数的定义顺序是有讲究的:默认情况下,只有后面定义的函数才可以调用前面定义过的函数

    复制代码

    1 int sum(int a, int b) {
    2     return a + b;
    3 }
    4 
    5 int main()
    6 {
    7     int c = sum(1, 4);
    8     return 0;
    9 }

    复制代码

    第5行定义的main函数调用了第1行的sum函数,这是合法的。如果调换sum函数和main函数的顺序,在标准的C编译器环境下是不合法的(不过在GCC编译器环境下只是一个警告)

     

    2.如果想把函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数的前面进行函数的声明

    复制代码

     1 // 只是做个函数声明,并不用实现
     2 int sum(int a, int b);
     3 
     4 int main()
     5 {
     6     int c = sum(1, 4);
     7     return 0;
     8 }
     9 
    10 // 函数的定义(实现)
    11 int sum(int a, int b) {
    12     return a + b;
    13 }

    复制代码

    在第11行定义了sum函数,在第2行对sum函数进行了声明,然后在第6行(main函数中)就可以正常调用sum函数了。

     

    3.函数的声明格式

    1> 格式

    返回值类型  函数名 (参数1, 参数2, ...)

    只要你在main函数前面声明过一个函数,main函数就知道这个函数的存在,就可以调用这个函数。而且只要知道函数名、函数的返回值、函数接收多少个参数、每个参数是什么类型的,就能够调用这个函数了,因此,声明函数的时候可以省略参数名称。比如上面的sum函数声明可以写成这样:

    int sum(int, int);

    究竟这个函数是做什么用的,还要看函数的定义。

     

    2> 如果只有函数的声明,而没有函数的定义,那么程序将会在链接时出错

    下面的写法是错误的:

    复制代码

    1 int sum(int a, int b);
    2 
    3 int main()
    4 {
    5     
    6     sum(10, 11);
    7 
    8     return 0;
    9 }

    复制代码

    • 在第1行声明了一个sum函数,但是并没有对sum函数进行定义,接着在第6行调用sum函数
    • 这个程序是可以编译成功的,因为我们在main函数前面声明了sum函数(函数的声明和定义是两码事),这个函数声明可以理解为:在语法上,骗一下main函数,告诉它sum函数是存在的,所以从语法的角度上main函数是可以调用sum函数的。究竟这个sum函数存不存在呢,有没有被定义呢?编译器是不管的。在编译阶段,编译器并不检测函数有没有定义,只有在链接的时候才会检测这个函数存不存在,也就是检测函数有没有被定义。
    • 因此,这个程序会在链接的时候报错,错误信息如下:

    • 我这里的源文件是main.c文件,所以编译成功后生成一个main.o文件。链接的时候,链接器会检测main.o中的函数有没有被定义。
    • 上面的错误信息大致意思是:在main.o文件中找不到sum这个标识符。
    • 错误信息中的linker是链接器的意思,下次看到这个linker,说明是链接阶段出错了。链接出错了,就不能生成可执行文件,程序就不能运行。
    • 这个错误的解决方案就是加上sum函数的定义。

     

    二、多源文件开发

    1.为什么要有多个源文件

    1> 在编写第一个c语言程序的时候已经提到:我们编写的所有C语言代码都保存在拓展名为.c的源文件中,编写完毕后就进行编译、链接,最后运行程序。

    2> 在前面的学习过程中,由于代码比较少,因此所有的代码都保存在一个.c源文件中。但是,在实际开发过程中,项目做大了,源代码肯定非常多,很容易就上万行代码了,甚至上十万、百万都有可能。这个时候如果把所有的代码都写到一个.c源文件中,那么这个文件将会非常庞大,也非常恶心,你可以想象一下,一个文件有十几万行文字,不要说调试程序了,连阅读代码都非常困难。

    3> 而且,公司里面都是以团队开发为主,如果多个开发人员同时修改一个源文件,那就会带来很多麻烦的问题,比如张三修改的代码很有可能会抹掉李四之前添加的代码。

    4> 因此,为了模块化开发,一般会将不同的功能写到不同的.c源文件中,这样的话,每个开发人员都负责修改不同的源文件,达到分工合作的目的,能够大大提高开发效率。也就是说,一个正常的C语言项目是由多个.c源文件构成。

     

    2.将sum函数写到其他源文件中

    接下来就演示一下多个源文件的开发,我将前面定义的sum函数写在另一个源文件(命名为sum.c)中。这时候就有两个源文件:

    1> main.c文件

    1 int main()
    2 {
    3 
    4     return 0;
    5 }

    2> sum.c文件

    1 int sum(int a, int b)
    2 {
    3     return a + b;
    4 }

     

    3.在main函数中调用sum函数

    1> 现在想在main函数中调用sum函数,那么你可能会直接这样写:

    复制代码

    1 int main()
    2 {
    3     int c = sum(10, 11);
    4 
    5     return 0;
    6 }

    复制代码

    这种写法在标准C语言编译器中是直接报错的,因为main函数都不知道sum函数的存在,怎么可以调用它呢!!!

    2> 我们应该骗一下main函数,sum函数是存在的,告诉它sum函数的返回值和参数类型即可。也就是说,应该在main函数前面,对sum函数进行声明。

    main.c文件应该写成下面这样

    复制代码

     1 #include <stdio.h>
     2 
     3 int sum(int, int);
     4 
     5 int main()
     6 {
     7     int c = sum(10, 11);
     8     
     9     printf("c is %d\n", c);
    10     
    11     return 0;
    12 }

    复制代码

    注意第3行,加了一个sum函数的声明。为了检验sum函数的调用结果,在第9行用prinf函数将结果输出。

     

    4.编译所有的源文件

    sum.c和main.c都编写完毕后,就可以使用gcc指令进行编译了。同时编译两个文件的指令是:cc -c main.c sum.c

    编译成功后,生成了2个.o目标文件

    也可以单独编译:

    cc -c main.c

    cc -c sum.c

     

    5.链接所有的目标文件

    前面已经编译成功,生成了main.o和sum.o文件。现在应该把这2个.o文件进行链接,生成可执行文件。

    1> 注意,一定要同时链接两个文件。如果你只是单独链接main.o或者sum.o都是不可能链接成功的。原因如下:

    • 如果只是链接main.o文件:cc main.o,错误信息是:在main.o中找到不到sum这个标识符,其实就是找不到sum函数的定义。因为sum函数的定义在sum.o文件中,main.o中只有sum函数的声明

    • 如果只是链接sum.o文件:cc sum.o,错误信息是:找不到main函数。一个C程序的入口点就是main函数,main函数定义在main.o中,sum.o中并没有定义main函数,连入口都没有,怎么能链接成功、生成可执行文件呢?

    可以看出,main.o和sum.o有密不可分的关系,其实链接的目的就是将所有相关联的目标文件和C语言函数库组合在一起,生成可执行文件。

    2> 链接main.o和sum.o文件:cc main.o sum.o,生成了可执行文件a.out

    3> 运行a.out文件:./a.out,运行结果是在屏幕上输出了:

    c is 21

    说明函数调用成功,我们已经成功在main.c文件的main函数中调用了sum.c文件中的sum函数

    4> 从中也可以得出一个结论:只要知道某个函数的声明,就可以调用这个函数,编译就能成功。不过想要这个程序能够运行成功,必须保证在链接的时候能找到函数的定义。

    三、#include

    理解完前面的知识后,接下来就可以搞懂一个很久以前的问题:每次写在最前面的#include是干啥用的?

    1.#include的作用

    先来看一个最简单的C程序:

    复制代码

    1 #include <stdio.h>
    2 
    3 int main()
    4 {
    5     printf("Hello, World!\n");
    6     return 0;
    7 }

    复制代码

    这个程序的作用是在屏幕上输出Hello,World!这一串内容,我们主要关注第一行代码。

    • #include 是C语言的预处理指令之一,所谓预处理,就是在编译之前做的处理,预处理指令一般以 # 开头
    • #include 指令后面会跟着一个文件名,预处理器发现 #include 指令后,就会根据文件名去查找文件,并把这个文件的内容包含到当前文件中。被包含文件中的文本将替换源文件中的 #include 指令,就像你把被包含文件中的全部内容拷贝到这个 #include 指令所在的位置一样。所以第一行指令的作用是将stdio.h文件里面的所有内容拷贝到第一行中。
    • 如果被包含的文件拓展名为.h,我们称之为"头文件"(Header File),头文件可以用来声明函数,要想使用这些函数,就必须先用 #include 指令包含函数所在的头文件
    • #include 指令不仅仅限于.h头文件,可以包含任何编译器能识别的C/C++代码文件,包括.c、.hpp、.cpp等,甚至.txt、.abc等等都可以

    也就是说你完全可以将第3行~第7行的代码放到其他文件中,然后用 #include 指令包含进来,比如:

    1> 将第3行~第7行的代码放到my.txt中

    2> 在main.c源文件中包含my.txt文件

    • 编译链接后,程序还是可以照常运行的,因为 #include 的功能就是将文件内容完全拷贝到 #include 指令所在的位置
    • 说明:这里用txt文件纯属演示,平时做项目不会这样做,除非吃饱了撑着,才会把代码都写到txt中去

     

    2.#include可以使用绝对路径

    上面的#include "my.txt"使用的是相对路径,其实也可以使用绝对路径。比如#include "/Users/apple/Desktop/my.txt"

     

    3.#include <>和#include ""的区别

    二者的区别在于:当被include的文件路径不是绝对路径的时候,有不同的搜索顺序。

    1> 对于使用双引号""来include文件,搜索的时候按以下顺序:

    • 先在这条include指令的父文件所在文件夹内搜索,所谓的父文件,就是这条include指令所在的文件
    • 如果上一步找不到,则在父文件的父文件所在文件夹内搜索;
    • 如果上一步找不到,则在编译器设置的include路径内搜索;
    • 如果上一步找不到,则在系统的INCLUDE环境变量内搜索

    2> 对于使用尖括号<>来include文件,搜索的时候按以下顺序:

    • 在编译器设置的include路径内搜索;
    • 如果上一步找不到,则在系统的INCLUDE环境变量内搜索

    我这里使用的是clang编译器,clang设置include路径是(4.2是编译器版本):/usr/lib/clang/4.2/include

    Mac系统的include路径有:

    • /usr/include
    • /usr/local/include

     

    4.stdio.h

    我们已经知道#include指令的作用了,可是为什么要在第一行代码包含stdio.h呢?

    • stdio.h 是C语言函数库中的一个头文件,里面声明了一些常用的输入输出函数,比如往屏幕上输出内容的printf函数
    • 这里之所以包含 stdio.h 文件,是因为在第5行中用到了在 stdio.h 内部声明的printf函数,这个函数可以向屏幕输出数据,第7行代码输出的内容是:Hello, World!
    • 注意:stdio.h里面只有printf函数的声明。前面已经提到:只要知道函数的声明,就可以调用这个函数,就能编译成功。不过想要这个程序能够运行成功,必须保证在链接的时候能找到函数的定义。其实链接除了会将所有的目标文件组合在一起,还会关联C语言的函数库,函数库中就有printf函数的定义。因此前面的程序是可以链接成功的。

     

    5.头文件.h和源文件.c的分工

    跟printf函数一样,我们在开发中会经常将函数的声明和定义写在不同的文件中,函数声明放在.h头文件中,函数定义放在.c源文件中。

    下面我们将sum函数的声明和定义分别放在sum.h和sum.c中

    这是sum.h文件

    这是sum.c文件

    然后在main.c中包含sum.h即可使用sum函数

    其实sum.h和sum.c的文件名不一样要相同,可以随便写,只要文件名是合法的。但还是建议写成一样,因为一看文件名就知道sum.h和sum.c是有联系的。

    运行步骤分析:

    1> 在编译之前,预编译器会将sum.h文件中的内容拷贝到main.c中

    2> 接着编译main.c和sum.c两个源文件,生成目标文件main.o和sum.o,这2个文件是不能被单独执行的,原因很简单:

    * sum.o中不存在main函数,肯定不可以被执行

    * main.o中虽然有main函数,但是它在main函数中调用了一个sum函数,而sum函数的定义却存在于sum.o中,因此main.o依赖于sum.o

    3> 把main.o、sum.o链接在一起,生成可执行文件

    4> 运行程序

     

    说到这里,有人可能有疑惑:可不可以在main.c中包含sum.c文件,不要sum.h文件了?

    大家都知道#include的功能是拷贝内容,因此上面的代码等效于:

    这么一看,语法上是绝对没有问题的,main.c、sum.c都能编译成功,分别生成sum.o、main.o文件。但是当我们同时链接main.o和sum.o时会出错。原因:当链接这两个文件时链接器会发现sum.o和main.o里面都有sum函数的定义,于是报"标识符重复"的错误,也就是说sum函数被重复定义了。默认情况下,C语言不允许两个函数的名字相同。因此,不要尝试去#include那些.c源文件。

     

    有人可能觉得分出sum.h和sum.c文件的这种做法好傻B,好端端多出2个文件,你把所有的东西都写到main.c不就可以了么?

    • 没错,整个C程序的代码是可以都写在main.c中。但是,如果项目做得很大,你可以想象得到,main.c这个文件会有多么庞大,会严重降低开发和调试效率。
    • 要想出色地完成一个大项目,需要一个团队的合作,不是一个人就可以搞的定的。如果把所有的代码都写在main.c中,那就导致代码冲突,因为整个团队的开发人员都在修改main.c文件,张三修改的代码很有可能会抹掉李四之前添加的代码。
    • 正常的模式应该是这样:假设张三负责编写 main函数,李四负责编写其他自定义函数,张三需要用到李四编写的某个函数,怎么办呢?李四可以将所有自定义函数的声明写在一个.h文件中,比如 lisi.h,然后张三在他自己的代码中用#include包含lisi.h文件,接着就可以调用lisi.h中声明的函数了,而李四呢,可以独立地在另外一个文件中(比如lisi.c)编写函数的定义,实现那些在lisi.h中声明的函数。这样子,张三和李四就可以相互协作、不会冲突。
    展开全文
  • 函数中的声明和变量的定义

    千次阅读 2019-04-14 19:44:13
    函数中的声明和变量的定义声明与定义比较前向引用函数属性内部/内嵌函数函数应用:打印图形和数学计算变量作用域全局变量与局部变量局部变量全局变量globa语句可变类型的全局变量 声明与定义比较 ​ 在某些编程语言...

    声明与定义比较

    ​ 在某些编程语言里,函数声明和函数定义区分开的,一个函数声明包括提供对函数名,参数的名字(传统上还有参数的类型),但不必给出函数的任何代码,具体的代码通常属于函数定义的范畴。
    ​ 在声明和定义有区别的语言中,往往是因为函数的定义可能和其声明放在不同的文件中。python将这两者视为一体,函数的子句由声明的标题行以及随后的定义体组成的。

    前向引用

    和其他高级语言类似python也不允许在函数未声明之前,对其进行引用或者调用。
    我们下面给出几个例子来看一下:

    def func():
    	print("in func()")
    	bar()
    print(func())
    #会报错
    

    修改:现在定义函数bar()在函数func()前给出bar()声明:

    #定义函数的前后顺序都可以!
    def func():
    	print("in func()")
    	bar()
    def bar():
    	print("in bar()")
    func()
    

    现在我们可以安全的调用func(),而不会出现任何问题:

    func()
    in func()
    in bar
    

    事实上,我们甚至可以在函数bar()前定义函数func():

    def func():
    	print("in func()")
    	bar()
    def bar():
    	print("in bar()")
    

    Amazing依然可以很好的运行,不会有前向引用的问题:

    func()
    in func()
    in bar
    
    这段代码是正确的因为即使(在 foo()中)对 bar()进行的调用出现在 bar()的定义之前,但 foo()本身不是在 bar()声明之前被调用的。换句话说,我们声明 foo(),然后再声明bar(),接着调用 foo(),但是到那时,bar()已经存在了,所以调用成功。
    

    注意 ,foo()在没有错误的情况下成功输出了’in foo()’。名字错误是当访问没有初始化的标识符时才产生的异常 。

    函数属性

    你可以获得每个 pyhon 模块,类,和函数中任意的名字空间。你可以在模块 foo 和 bar 里都有名为 x 的一个变量,,但是在将这两个模块导入你的程序后,仍然可以使用这两个变量。所以,即使在两个模块中使用了相同的变量名字,这也是安全的,因为句点属性标识对于两个模块意味了不同的命名空间,比如说,在这段代码中没有名字冲突:

    import foo, bar
    print(foo.x + bar.x) 
    

    函数属性是 python 另外一个使用了句点属性标识并拥有名字空间的领域。

    def foo():
    	'foo()-- properly created doc string'
    
    def bar():
    	pass
    
    bar.__doc__='Oops,forgot the doc str above'
    bar.version =0.1
    

    #按住shift在空白处打开 执行时在中间加-i
    PS C:\Users\asus\Desktop\pythondemo测试\小巷> python -i band.py
    >>> foo.__doc__
    'foo()-- properly created doc string'
    >>>
    

    ​ 上面的 foo()中,我们以常规地方式创建了我们的文档字串,比如, 在函数声明后第一个没有赋值的字串。当声明
    bar()时, 我们什么都没做, 仅用了句点属性标识来增加文档字串以及其他属性。我们可以接着任意地访问属性。下面是一个使用了交互解释器的例子。(你可能已经发现,用内建函
    数 help()显示会比用__doc__属性更漂亮,但是你可以选择你喜欢的方式)

    >>> help(foo)
    Help on function foo in module __main__:
    
    foo()
        foo() -- properly created doc string
    
    >>> bar.version 
    0.1
    >>> foo.__doc__
    'foo() -- properly created doc string'
    >>> bar.__doc__
    'Oops, forgot the doc str above'
    >>>
    

    注意我们是如何在函数声明外定义一个文档字串。然而我们仍然可以就像平常一样,在运行时刻访问它。然而你不能在函数的声明中访问属性。换句话说,在函数声明中没有’self‘这样的东西让你可以进行诸如_dict_[‘version’] = 0.1 的赋值。这是因为函数体还没有被创建,但之后你有了函数对象,就可以按我们在上面描述的那样方法来访问它的字典。另外一个自由的名字空间!

    内部/内嵌函数

    在函数体内创建另外一个函数(对象)是完全合法的,这种函数叫做内部/内嵌函数。
    最明显的创造内部函数的方法是在外部函数的定义体内定义函数(用def关键字),如在:

    def foo():
    	def bar():
    		print("bar() called.")
    	print("foo() called")
    	bar()
    foo()
    bar()
    

    我们将以上代码置入一个模块中,如inner.py,然后运行,我们会得到如下输出:

    bar() called.
    foo() called
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'bar' is not defined
    >>>
    
    http://www.pythontutor.com/visualize.html#mode=edit
    将简单的代码拷贝进去,可以查看执行的步骤过程
    
    内部函数一个有趣的方面在于整个函数体都在外部函数的作用域(即是你可以访问一个对象的区域;稍后会有更多关于作用域的介绍)之内。如果没有任何对 bar()的外部引用,那么除了在函数体内,任何地方都不能对其进行调用,这就是在上述代码执行到最后你看到异常的原因
    

    另外一个函数体内创建函数对象的方式是使用lambda 语句。 稍后讲述。如果内部函数的定义包含了在外部函数里定义的对象的引用(这个对象甚至可以是在外部函数之外),内部函数会变成被称为闭包(closure)的特别之物。

    函数应用:打印图形和数学计算

    函数的嵌套调用;

    程序设计的思路,复杂问题分解为简单问题。

    • 思考1:编程实现:

      • 写一个函数打印一条横线

      • 打印自定义行数的横线

        举例子说明:

    #打印一行方法一
    # def foo():
    # 	for i in range(5):
    # 		dar()
    # def dar():
    # 	print("-",end=" ")
    # foo()
    
    #打印一行方法二:
    # def line():
    # 	print("-"*15)
    # line()
    
    #打印多条横线的方法一:
    # def line():
    # 	print("-"*15)
    # line()
    # def lines(n):
    # 	for i in range(n):
    # 		line()
    # lines(5)
    
    #打印多条横线方法二:
    def printOneline():
    	print("-"*30)
    
    def printNumline(num):
    	a=0
    #因为prinOneline函数已经完成了打印横线的功能
    #只需要多次调用此函数即可
    	while a<num:
    		printOneline()
    		a+=1
    printNumline(3)
    
    • 思考2:编程实现
      • 写一个函数求三个数的和
      • 写一个函数求三个数的平均值

    参考代码

    # 求3个数的和
    def sum3Number(a,b,c):
        return a+b+c # return 的后面可以是数值,也可是一个表达式
    
    # 完成对3个数求平均值
    def average3Number(a,b,c):
    
     # 因为sum3Number函数已经完成了3个数的就和,所以只需调用即可
     # 即把接收到的3个数,当做实参传递即可
     sumResult = sum3Number(a,b,c)
     aveResult = sumResult/3.0
     return aveResult
    
     # 调用函数,完成对3个数求平均值
     result = average3Number(11,2,55)
     print("average is %d"%result)
    

    变量作用域

    标识符的作用域是定义为其声明在程序的可应用范围,或者即是我们所说的变量可见性,换句话说,就好像在问你自己,你可以在程序里的哪些部分去访问一个制定的标识符,变量可以是局部域或者是全局域。

    全局变量与局部变量

    定义在函数内的变量有局部作用域,在一个模块中最高级别的变量有全局作用域。
    “声明适用的程序的范围被称为了声明的作用域,在一个过程中,如果名字在过程的声明之内,它的出现即为过程的局部变量,否则的话,出现即为非局部的”.
    全局变量的一个特征是除非被删除掉,否则它们的存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的,然而局部变量,就像它们存放的栈,暂时的存在,仅仅只依赖于定义它们的函数现阶段是否处于活动,当一个函数调用出现时,其局部变量就进入声明它们的作用域,在那一刻,一个新的局部变量名为那个对象创建了,一旦函数完成,框架被释放,变量将会离开作用域。

    举例子说明:

    global_str='foo'
    def foo():
    	local_str='bar'
    	return global_str+local_str
    print(foo())#结果为:foobar
    print(global_str)#结果为:foo
    print(local_str)#会报错,因为局部变量只能在函数方法内部使用才有用
    
    题目:
    编写函数,实现求两个序列的交集。(为方便,序列类型可以直接统一采用列表)
    方法一:
    def foo():
    	a=[1,2,3,4,5]
    	b=[2,3,4]
    	list1=list((set(a)&set(b)))
    	print(list1)
    foo()
    结果:[2, 3, 4]
    方法二:
    def foo():
    	a=[1,2,3,4,5]
    	b=[2,3,4]
    	list1=[val for val in a if val in b]
    	print(list1)
    foo()
    结果:[2, 3, 4]
    

    局部变量

    例子:

    def foo():
    	a=666
    	print('foo(),修改前a:\t',a)
    	a=999
    	print('foo(),修改后a:\t',a)
    
    def bar():
    	a=8899
    	print('bar(),a\t',a)
    

    运行结果:

    >>> foo()
    foo(),修改前a:   666
    foo(),修改后a:   999
    >>> bar()
    bar(),a  8899
    >>>
    

    可以看出:

    • 局部变量,就是在函数内部定义的变量
    • 不同的函数,可以定义相同的名字的局部变量,但是各用个的不会产生影响
    • 局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储,这就是它的作用

    全局变量

    同样,先看一下例子:

    a=6699
    def foo():
    	print('foo(),a:\t',a)
    
    def bar():
    	print('bar(),a:\t',a)
     
    print(foo())
    print(bar())
    

    运行结果:

    foo(),a:	 6699
    None
    bar(),a:	 6699
    None
    

    讨论1:如果全局变量和局部变量名字相同?

    a=8899
    def foo():
    	a=666
    	print('foo(),修改前a:\t',a)
    	a=888
    	print('foo(),修改后a:\t',a)
    
    def bar():
    	print('bar(),a:\t',a)
    
    foo()
    bar()
    

    运行结果为:

    foo(),修改前a:	 666
    foo(),修改后a:	 888
    bar(),a:	 8899
    

    讨论2:全局变量,是能够在所有的函数中进行使用的变量,那么局部变量可否进行修改编程全局变量?

    globa语句

    如果将全局变量的名字声明在一个函数体内的时候,全局变量的名字能被局部变量给覆盖掉。

    a=6699
    def foo():
    	global a 
    
    	print('foo(),修改前,a:\t',a)
    	a=666
    	print('foo(),修改后,a:\t',a)
    
    def bar():
    	print('bar(),a:\t',a)
    foo()
    bar()
    

    运行结果:

    foo(),修改前,a:	 6699
    foo(),修改后,a:	 666
    bar(),a:	 666
    

    通过以上例子,我们可以观察出:

    • 在函数外边定义的变量叫做全局变量
    • 全局变量能够在所有的函数中进行访问
    • 如果在函数中修改全局变量,那么就需要使用global进行声明,否则出错
    • 如果全局变量的名字和局部变量的名字相同,那么使用的是局部变量的

    可变类型的全局变量

    先举2个例子:

    例子1:

    a=1
    def foo():
    	a+=1
    	print(a)
    foo()#内存地址改变了
    

    运行后:

    Traceback (most recent call last):
      File "C:\Users\asus\Desktop\pythondemo测试\小巷\band.py", line 108, in <module>
        foo()
      File "C:\Users\asus\Desktop\pythondemo测试\小巷\band.py", line 106, in foo
        a+=1
    UnboundLocalError: local variable 'a' referenced before assignment
    

    例子2:

    li=['a','b']
    def foo():
    	li.append('c')
    	print(li)
    print(foo())
    print(li)
    

    运行结果:

    ['a', 'b', 'c']
    None
    ['a', 'b', 'c']
    
    展开全文
  • 好久没有写这玩意儿了;今天在工作中遇到了这个问题,就拿出来分享下,希望对需要的朋友有所帮助,若有不足的地方,还... 就是当我们声明一个变量的时候,我们可以在其前方访问到它:这里就要提到一个函数作用域的...

    好久没有写这玩意儿了;今天在工作中遇到了这个问题,就拿出来分享下,希望对需要的朋友有所帮助,若有不足的地方,还请大神多多指出!

     

    1、什么是声明提前?

        它是指当我们声明一个变量或是一个函数的时候,我们可以在起前面访问带该变量或函数;即:声明统一提前,赋值原地不变

     

    2、变量声明提前

        就是当我们声明一个变量的时候,我们可以在其前方访问到它:这里就要提到一个函数作用域的概念:变量在声明它的函数体以及这函数体嵌套的任意函数体内都始终可见;这也就是变量的声明提前

      eg:   

        var a= 1

        function test () {

             console.log(a)   // undefined

             var a= 2

             console.log(a)    // 2

        }

    上面的这个例子可以解析成:

       var a=1

       function test() {

            var a;

            console.log(a)

            a=2;

            console.log(a)

        }

     

    3、函数声明提前

        函数声明提前的原理和变量声明提前类似;但需要注意的是:只有函数声明格式的函数才会声明提前   (像函数表达式、构造函数不会声明提前)

          a、函数声明格式: function  fun() { console.log('函数')}

          b、函数表达式:var fun = function () {console.log('函数')}

          c、构造函数: var fun = new Function(console.log('函数'))

      eg: 

       function test()  {
          console.log(a)   //函数a 本生

          function a() { console.log(a) }  

          console.log(a)  //函数本生

          a()   //能够执行,  函数本生

       }

       test()

       生面 的例子可以解析为:

      function test () {

          function a() { console.log(a)}

          console.log(a)

          console.log(a)

          a()

      } 

       test()

     

     下面再看一个例子:

       var sum=2

       function add(n) {

           return n= n+2;

        }

        var res= add(sum)

        console.log(res)

        function add(n) {

          return n= n+4

        }

        var res= add(sum)

        console.log(res)

    这个例子的结果是两个6;原因也是函数的声明提前;其代码等同于一下代码:

       var sum=2

       function add(n) {

          return n= n+2

       }

       function add(n) {

           return n= n+4

       }

       var res= add(sum)

       console.log(res)

       var res= add(sum)

       console.log(res)

     

    4、声明提前的先后顺序 

       函数声明提前,变量声明提前,到底谁会提前的更前??

       假设当两者的名字一样时,最后会输出什么呢? 看看下面这个例子

       function test() {

           console.log(a)   // 是函数本身

           var a='变量先输出'

           function a() { console.log(‘函数先输出’)} 

      }

      test()

      通过上面这个例子我们将会看到:最终输出的结果是一个函数本生,这是为什么呢?

      其实以上例子代码等同于一下代码:

      function test() {

          function a() {console.log('函数先输出')}

          var a   //上面的函数已经声明,相同的变量名声明会被忽略

          console.log(a)

          a='变量先输出'

      }

      test()

      这样按理说应该是输出‘undefined’, 为何是函数本生呢?

      这里是因为: 上面的函数已经声明,相同的变量名声明会被忽略 (而这里只是再次申明a,并未修改现有a的值

      这里可以举一个简单的例子:

        var a= 1

        var a 

        console.log(a)   // 1

     

    下面再看一个例子:

     console.log(xxp)   //‘undefined’

     var xxp= 1

     function fn() {

        console.log(xxp)  // 函数本身    function xxp() { console.log(xxp)}

        var xxp= 2

        function xxp() { console.log(xxp)}

        console.log(xxp)    // 2

        xxp()    //报错

     }

     fn()

     上面的代码等同于下面的代码:

     console.log(xxp)  // ‘undefined ’   声明提前

     var xxp= 1

     function fn() {

        function xxp() {console.log(xxp)}

        var xxp

        console.log(xxp)  // 函数本身  (当名字相同时,再次定义【没有赋值】会被忽略)

        xxp=2

        console.log(xxp)  // 2  这里进行了赋值操作    (函数已经不存在了,只有变量xxp)

        xxp()   // 报错; 这里没有函数了,只有变量xxp

     }

    fn()

     

    以上就变量、函数声明提前的概念和用法,以及之间的关系;若有不完善的地方,还望大佬指出,谢谢

     

      

    展开全文
  • TypeScript声明文件

    千次阅读 2019-08-14 15:54:48
    声明文件简介 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。 什么是声明语句 假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 ...
  • C++ 类声明 类前置声明范例

    千次阅读 2019-07-19 18:47:40
    在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration)。下面的程序中,带注释的那行就是类B的前置说明。这是必须的,因为类A中用到了类B,而类B的声明出现在类A的后面。如果没有类B的前置说明,下面的...
  • 声明方式: 1)变量声明: var foo = function () { .... } 2)函数声明: function foo(){ ..... } 函数参数:都是按值传递(把函数外部的值复制给函数内部的参数) 而变量复制则有两种方式——按地址...
  • 本文是对 Kubernetes 的核心概念,声明式API和编程范式的理解笔记。整理回答了”什么是声明式API“、”Kubernetes 编程范式是什么“这样的问题,然后简单记录了对更快更好造出 CRD 及其 Controller的 Operator 的...
  • 声明和定义的区别

    万次阅读 多人点赞 2019-01-10 13:21:07
    声明和定义的区别: 在我上课的书中并没有说明两者的区别,书上写着“在本书中,声明和定义有着相同的含义”,当时学的时候也没注意到这点,后来看到一些面试题,才注意到这些坑。  一种是需要建立存储空间的。...
  • C语言——全局变量的定义与声明

    万次阅读 多人点赞 2019-09-28 12:31:34
    本文讲述了全局变量定义与声明的用法,而且本为也将阐述这种用法的内在原理。我们先从两个错误例子引入,以下两个例程都在vc6.0平台上测试。 两种错误例程 1.unresolved external symbol 例子包含...
  • C语言中const的详细用法及声明规则

    千次阅读 多人点赞 2017-10-26 22:49:54
    前言本文主要涵盖了以下两部分的内容: 介绍了C语言中const的详细用法. 介绍了C/C++语言下声明语句的规则.
  • 理解C语言中指针的声明以及复杂声明的语法

    千次阅读 多人点赞 2016-04-01 23:51:29
    昨天刚把《C程序设计语言》中“指针与数组”章节读完,终于把心中的疑惑彻底解开了。现在记录下我对指针声明的理解,顺便说下如何在C语言中创建复杂声明以及读懂复杂声明
  • TypeScript 中的声明文件

    千次阅读 2019-06-16 15:12:43
    TypeScript 中的声明文件 tsconfig.json配置说明 【进阶】TS高级类型,泛型 TSlint注释忽略错误和RESTful理解 关闭Eslint语法校验 详解 ESLint 规则,规范你的代码 学习 TypeScript 稍微有一段时间了,每次写都会...
  • 命名空间是一个范畴,它包含类声明,函数声明,常量声明和模板声明等名字空间成员。本文拟讨论如何在名字空间中声明自己的类和函数,以及如何在程序中使用它们。 在使用C++类时,如果用到命名空间,在使用的时候需要...
  • C++中的类——类的定义和声明

    万次阅读 多人点赞 2018-08-23 10:49:04
    可以在类的内部把inline作为声明的一部分显式的声明成员函数,也可以在类的外部用inline修饰函数的定义。无需在声明和定义的地方同时说明inline,但最好只在外部定义的地方说明内联,使程序更容易理解。( inline...
  • 【C语言】声明与定义

    千次阅读 2018-10-20 19:59:49
    引用性声明 不分配存储空间,如extern int x; 只是告诉编译器x是整形,已经在其它地方定义了。 定义 是在内存中确定变量的位置、大小。 初始化 是定义变量时候赋给变量的值(从无到有) 赋值 是以后用到该变量,赋给...
  • 1、什么是数组 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。...数组的声明并不是声明一个个单独的变量,比如number0,bumber1……,而是声明一个数组,比如numbers,然后使用number[0],numbe...
  • C++中前置声明介绍

    千次阅读 2017-02-09 21:53:11
    C++中前置声明介绍
  • js 函数声明提升和变量声明提升

    千次阅读 2018-08-06 18:16:42
    1. 函数声明提升 func() function func () { } 上例不会报错,正是因为 ‘函数声明提升’,即将函数声明提升(整体)到作用域顶部(注意是函数声明,不包括函数表达式),实际提升后结果同下: // 函数声明提升...
  • Cocos技术派 | TS版属性声明详解

    千次阅读 2019-06-05 10:13:56
    这是一个CCClass类,用关键字 properties 声明了两个属性:userID和userName。 这种写法大家很熟悉,官方文档和范例教程里面都是用 JS 的项目,这种代码随处可见。 然后再看看属性声明的TS版: 注解式声明 ...
  • 【Spring】——声明式事务配置详解

    万次阅读 多人点赞 2018-07-15 21:29:20
     Spring的核心事务管理抽象,管理封装了一组独立于技术的方法,无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。 org.springframework.transaction.PlatformTransactionManager JDBC...
  • 全局变量的声明、定义及用法

    万次阅读 多人点赞 2019-01-20 21:38:46
    全局变量的声明、定义及用法 文章目录全局变量的声明、定义及用法1. 编译单元(模块)2. 声明和定义3. extern 作用4. 全局变量(extern)4.1 如果直接将声明和定义都放在头文件中会如何?5. 静态全局变量(static)6. 全局...
  • 变量的声明:用于向程序表明变量的类型和名字。 变量的定义:用于为变量分配存储空间,还可以为变量指定初始值。 变量的初始化:为变量指定初始值。 广义上来说,变量的声明有两种情况: 1.需要建立存储空间的声明...
  • C语言结构体声明的几种方式

    千次阅读 2020-11-07 16:08:46
    结构体声明的几种方式 1.先声明结构体类型,再定义结构体变量 /*图书的结构体类型声明*/ struct Book { char ISBN[20]; //图书的ISBN码。 char name[50]; //图书名称。 float price; //价格 }; /*结构体变量定义...
  • 如何在Python中声明一个数组?

    万次阅读 2020-01-30 10:45:24
    如何在Python中声明数组? 我在文档中找不到任何对数组的引用。
  • JavaScript 允许变量被重复声明,在声明变量时 JavaScript 会自行判断这个变量是否已经被声明了,如果已经被声明(即已经存在),那么重复声明(即除了变量的非首次声明)会被跳过,不再执行声明的操作。 ...
  • golang简短变量声明

    万次阅读 2020-10-11 10:52:53
    golang简短声明变量只能在函数体内使用,不能在全局使用 package main import "fmt" test1 := 1 func main(){ test2 := 2 fmt.Println(test2) } 编译报错:./test.go:5:1: syntax error: non-declaration...
  • C语言函数声明以及函数原型

    千次阅读 多人点赞 2020-02-24 13:47:56
    但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明。 函数声明(Declaration),就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上。 函数声明...
  • ES6系列之变量声明

    万次阅读 2020-07-01 09:40:30
    在之前的js版本中,声明变量一直都是用var, 这个估计大家都很熟悉了。 那么它的作用呢就是用来声明一个变量,比如像这样子: var a = 10 在这里我们就用var声明了一个变量a 并且在声明的同时给这个变量a赋值了 也...
  • C语言中声明和定义详解

    万次阅读 多人点赞 2018-09-05 18:37:19
    变量声明和变量定义 变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。 变量声明:用于向程序表明变量的类型和名字。 定义也是声明,extern声明不是定义 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,274,597
精华内容 1,309,838
关键字:

声明