精华内容
下载资源
问答
  • 嵌入式C语言可不用的关键字

    千次阅读 多人点赞 2010-10-29 19:47:00
    标准C程序一直由下列部分组成:  1)正文段——CPU执行的机器指令部分,也就是你的程序。一个程序只有一个副本;只读,这是为了防止程序由于意外事故而修改自身指令;  2)初始化数据段(数据段)...

    1.static关键字

    这个关键字前面也有提到,它的作用是强大的。

    要对static关键字深入了解,首先需要掌握标准C程序的组成。

    标准C程序一直由下列部分组成:

           1)正文段——CPU执行的机器指令部分,也就是你的程序。一个程序只有一个副本;只读,这是为了防止程序由于意外事故而修改自身指令;
           2)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
           3)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0

    注意:只有全局变量被分配到数据段中。
           4)栈——增长方向:自顶向下增长;自动变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。这句很关键,常常有笔试题会问到什么东西放到栈里面就足以说明。
           5)堆——动态存储分配。

     

    在嵌入式C语言当中,它有三个作用:

    作用一:在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

    这样定义的变量称为局部静态变量:在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。也就是上面的作用一中提到的在函数体内定义的变量。除了类型符外,若不加其它关键字修饰,默认都是局部变量。比如以下代码:

    void test1void

    {

        unsigned char a

        static unsigned char b

       

        a++

        b++

    }

    在这个例子中,变量a是局部变量,变量b为局部静态变量。作用一说明了局部静态变量b的特性:在函数体,一个被声明为静态的变量(也就是局部静态变量)在这一函数被调用过程中维持其值不变。这句话什么意思呢?若是连续两次调用上面的函数test1

        void mainvoid

        {

          

           test1();

           test1();

          

        }

    然后使程序暂停下来,读取ab的值,你会发现,a=1b=2。怎么回事呢,每次调用test1函数,局部变量a都会重新初始化为0x00;然后执行a++;而局部静态变量在调用过程中却能维持其值不变。

    通常利用这个特性可以统计一个函数被调用的次数。

    声明函数的一个局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因为这个变量是和函数息息相关的,而函数可能在多个不同的地方被调用,所以从调用者的角度来统计比较困难。代码如下:

    void count();
    int main()
    {
        int i;
        for (i = 1; i <= 3; i++)

        {
            count();

        {
      
      return 0;
    }
    void count()
    {
        static num = 0;
        num++;
        printf(" I have been called %d",num,"times/n");
    }

    输出结果为:
    I have been called 1 times.
    I have been called 2 times.
    I have been called 3 times.

     

    看一下局部静态变量的详细特性,注意它的作用域。

     1)内存中的位置:静态存储区

       2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)

       3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

       注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问。

     

    作用二:在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

    这样定义的变量也称为全局静态变量:在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。也就是上述作用二中提到的在模块内(但在函数体外)声明的静态变量。

    定义全局静态变量的好处:

    <1>不会被其他文件所访问,修改,是一个本地的局部变量。

    <2>其他文件中可以使用相同名字的变量,不会发生冲突。

    全局变量的详细特性,注意作用域,可以和局部静态变量相比较:

    1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)

        2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)

        3)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。

    static用来修饰全局变量的时候,它就改变了全局变量的作用域(在声明他的文件之外是不可见的),但是没有改变它的存放位置,还是在静态存储区中。

     

    作用三:在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

     

    这样定义的函数也成为静态函数:在函数的返回类型前加上关键字static,函数就被定义成为静态函数。函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

    定义静态函数的好处:

    <1> 其他文件中可以定义相同名字的函数,不会发生冲突

    <2> 静态函数不能被其他文件所用。它定义一个本地的函数。

    这里我一直强调数据和函数的本地化,这对于程序的结构甚至优化都有巨大的好处,更大的作用是,本地化的数据和函数能给人传递很多有用的信息,能约束数据和函数的作用范围。在C++的对象和类中非常注重的私有和公共数据/函数其实就是本地和全局数据/函数的扩展,这也从侧面反应了本地化数据/函数的优势。

     

    最后说一下存储说明符,在标准C语言中,存储说明符有以下几类:

    autoregisterexternstatic

    对应两种存储期:自动存储期和静态存储期。
    auto
    register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。

    关键字externstatic用来说明具有静态存储期的变量和函数。用static声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。虽然他的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。

    2. const 关键字

    const关键字也是一个优秀程序中经常用到的关键字。关键字const 的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。通过给优化器一些附加的信息,使用关键字const 也许能产生更紧凑的代码。合理地使用关键字const 可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

    深入理解const关键字,你必须知道:

    a. const关键字修饰的变量可以认为有只读属性,但它绝不与常量划等号。如下代码:

    const int i=5;

           int j=0;

           ...

           i=j;   //非法,导致编译错误,因为只能被读

           j=i;   //合法

    b. const关键字修饰的变量在声明时必须进行初始化。如下代码:

    const int i=5;    //合法

         const int j;      //非法,导致编译错误

    c. const声明的变量虽然增加了分配空间,但是可以保证类型安全。const最初是从C++变化得来的,它可以替代define来定义常量。在旧版本(标准前)c中,如果想建立一个常量,必须使用预处理器:
                   
    #define PI 3.14159

    此后无论在何处使用PI,都会被预处理器以3.14159替代。编译器不对PI进行类型检查,也就是说可以不受限制的建立宏并用它来替代值,如果使用不慎,很可能由预处理引入错误,这些错误往往很难发现。而且,我们也不能得到PI的地址(即不能向PI传递指针和引用)。const的出现,比较好的解决了上述问题。

    d. C标准中,const定义的常量是全局的。

    e. 必须明白下面语句的含义,我自己是反复记忆了许久才记住,方法是:若是想定义一个只读属性的指针,那么关键字const要放到‘* ’后面。

    char *const cp; //指针不可改变,但指向的内容可以改变

    char const *pc1; //指针可以改变,但指向的内容不能改变

    const char *pc2; //同上(后两个声明是等同的)

           f. 将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。

           参数const通常用于参数为指针或引用的情况,且只能修饰输入参数;若输入参数采用值传递方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以不用const修饰。例子:

    void fun0(const  int * a );

    void fun1(const  int & a);

    调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const int * a,则不能对传递进来的指针所指向的内容进行改变,保护了原指针所指向的内容;如形参为const int & a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。

           g. 修饰函数返回值,可以阻止用户修改返回值。(在嵌入式C中一般不用,主要用于C++

           h. const消除了预处理器的值替代的不良影响,并且提供了良好的类型检查形式和安全性,在可能的地方尽可能的使用const对我们的编程有很大的帮助,前提是:你对const有了足够的理解。

           最后,举两个常用的标准C库函数声明,它们都是使用const的典范。

           1.字符串拷贝函数:char *strcpychar *strDestconst char *strSrc);

           2.返回字符串长度函数:int strlenconst char *str);

     

    3. volatile关键字

    一个定义为volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

    由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如: 

    static int i=0; 

    int main(void) 

        ... 
        while (1) 
        { 
            if (i) 
                dosomething(); 
        } 


    /* Interrupt service routine. */ 
    void ISR_2(void) 

         i=1; 


          
    程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。 

    如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
     

    一般说来,volatile用在如下的几个地方:
     

    1
    、中断服务程序中修改的供其它程序检测的变量需要加volatile
     

    2
    、多任务环境下各任务间共享的标志应该加volatile
     

    3
    、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;

    不懂得volatile 的内容将会带来灾难这也是区分C语言和嵌入式C语言程序员的一个关键因素。为强调volatile的重要性,再次举例分析:

    代码一:                               

    int a,b,c;                             

    //读取I/O空间0x100端口的内容    

    a= inword(0x100);                                                  

    b=a;                               

    a=inword(0x100)                            

    c=a;  

    代码二:   

      volatile int a;   

      int a,b,c;                             

    //读取I/O空间0x100端口的内容    

    a= inword(0x100);                                               

    b=a;                               

    a=inword(0x100)                        

    c=a; 

    在上述例子中,代码一会被绝大多数编译器优化为如下代码:

           a=inword(0x100)

           b=a;

           c=a;

    这显然与编写者的目的不相符,会出现I/O空间0x100端口漏读现象,若是增加volatile,像代码二所示的那样,优化器将不会优化掉任何代码.

           从上面来看,volatile关键字是会降低编译器优化力度的,但它保证了程序的正确性,所以在适合的地方使用关键字volatile是件考验编程功底的事情.

     

    4.structtypedef关键字

    面对一个人的大型C/C++程序时,只看其对struct的使用情况我们就可以对其编写者的编程经验进行评估。因为一个大型的C/C++程序,势必要涉及一些(甚至大量)进行数据组合的结构体,这些结构体可以将原本意义属于一个整体的数据组合在一起。从某种程度上来说,会不会用struct,怎样用struct是区别一个开发人员是否具备丰富开发经历的标志。

      在网络协议、通信控制、嵌入式系统的C/C++编程中,我们经常要传送的不是简单的字节流(char型数组),而是多种数据组合起来的一个整体,其表现形式是一个结构体。

    经验不足的开发人员往往将所有需要传送的内容依顺序保存在char型数组中,通过指针偏移的方法传送网络报文等信息。这样做编程复杂,易出错,而且一旦控制方式及通信协议有所变化,程序就要进行非常细致的修改。

    用法:

    C中定义一个结构体类型要用typedef:
    typedef struct Student
    {
           int a;
    }Stu;
    于是在声明变量的时候就可:
    Stu stu1;
    如果没有typedef就必须用struct Student stu1;来声明

    这里的Stu实际上就是struct Student的别名。
    另外这里也可以不写Student(于是也不能struct Student stu1;了)
    typedef struct
    {
           int a;
    }Stu;

    struct关键字的一个总要作用是它可以实现对数据的封装,有一点点类似与C++的对象,可以将一些分散的特性对象化,这在编写某些复杂程序时提供很大的方便性.

    比如编写一个菜单程序,你要知道本级菜单的菜单索引号、焦点在屏上是第几项、显示第一项对应的菜单条目索引、菜单文本内容、子菜单索引、当前菜单执行的功能操作。若是对上述条目单独操作,那么程序的复杂程度将会大到不可想象,若是菜单层数少些还容易实现,一旦菜单层数超出四层,呃~我就没法形容了。若是有编写过菜单程序的朋友或许理解很深。这时候结构体struct就开始显现它的威力了:

    //结构体定义

    typedef struct

    {

    unsigned char CurrentPanel;//本级菜单的菜单索引号

    unsigned char ItemStartDisplay; //显示第一项对应的菜单条目索引

    unsigned char FocusLine;  //焦点在屏上是第几项

    }Menu_Statestruct;

     

    typedef struct

    {

    unsigned char *MenuTxt; //菜单文本内容

    unsigned char MenuChildID;//子菜单索引

    void    (*CurrentOperate)();//当前菜单执行的功能操作

    }MenuItemStruct;

     

    typedef struct

    {

    MenuItemStruct  *MenuPanelItem;

    unsigned char    MenuItemCount;

    }MenuPanelStruct;

     

           这里引用我巩师兄所写的菜单程序中的结构体定义,这个菜单程序最大可以到256级菜单。我当初要写一个菜单程序之前,并没有对结构体了解多少,也没有想到使用结构体。只是一层层菜单的单独处理:如果按键按下,判断是哪个按键,调用对应按键的子程序,刷屏显示。这样处理起来每一层都要判断当前的光标所在行,计算是不是在显示屏的顶层,是不是在显示层的底层,是不是需要翻页等等,非常的繁琐。后来在网上找资料,就找到了我师兄编写的这个程序,开始并不知道是他写的,在看源程序的时候看到了作者署名:中国传惠 TranSmart,才知道是他。花了一天的时间阅读代码和移植,才知道结构体原来有这么个妙用,当定义了上述三个结构体之后,菜单程序结构立刻变的简单很多,思路也无比的清晰起来。

    展开全文
  • 【单选题】直径数字前应加符号( )【单选题】Python 中对变量描述错误的选项是:【填空题】近头者为【单选题】以下对 Python 程序缩进格式描述错误的选项是...()【单选题】药物通过细胞膜转运的特点 下列哪项 正确...

    【单选题】直径数字前应加符号( )

    【单选题】Python 中对变量描述错误的选项是:

    【填空题】近头者为

    【单选题】以下对 Python 程序缩进格式描述错误的选项是

    【单选题】关于Python语言的特点,以下选项描述正确的是

    【单选题】较小的尺寸应离轮廓线较近,较大的尺寸线离轮廓线较远。( )

    【判断题】树立正确的历史观,需要自觉抵制历史虚无主义。()

    【单选题】药物通过细胞膜转运的特点 下列哪项 正确

    【简答题】拍摄并上传正画葫芦与倒画葫芦练习(各 15 秒)

    【单选题】尺寸由尺寸线、尺寸起止符号和尺寸数字组成。

    【多选题】水表的结构有:( )。

    【判断题】按使用介质压力分,水表可分为普通水表压力和高压水表。

    【单选题】以下选项中,不是Python语言特点的是

    【单选题】关于 Python 程序格式框架的描述,以下选项中错误的是()

    【单选题】尺寸线和尺寸界线( )绘制。

    【单选题】以下选项中,不符合Python语言变量命名规则的是

    【简答题】1.结合章后案例,分析物流 企业开展物流金融业务的风险? (5.0分)

    【单选题】1825年英国的克路斯发明了真正具有仪表特征是:( )。

    【单选题】关于 Python 程序格式框架的描述,以下选项中错误的是

    【判断题】按水表计数器形式分,水表可分为液封水表、干式水表、湿式水表。

    【简答题】你如何理解近代中国的落后这一问题?

    【单选题】以下对Python程序设计风格描述错误的选项是

    【多选题】女士在正式场合可以选择的坐姿有( )

    【单选题】图样上的尺寸单位,除标高及总平面图以外,其余必须以( )为单位

    【单选题】根据2018年版《中国近现代史纲要》,中国的近代史,是从1840年开始到()结束。

    【多选题】按安装方式分,水表一般可分为:( )。

    【单选题】Python文件的后缀名是

    【单选题】关于Python语句P = –P,以下选项中描述正确的是

    【单选题】以下选项中,不是Python语言保留字的是

    【多选题】按测量原理来分,水表一般可分为:( )。

    【简答题】1.结合章后案例, 概括物流 企业有效开展物流金融业务的策略 ? (5.0分)

    【单选题】关于 Python 语言的特点,以下选项中描述错误的是

    【其它】常桦 音阶和芭音练习_标清_标清.3gp 爱果音乐 盛原1: 讲述钢琴是怎样诞生的? 演奏 、最早的钢琴曲_标清.mp4

    【单选题】以下选项中符合Python语言变量命名规则的是

    【简答题】What are the key factors that help people learn English as a foreign language?

    【单选题】以下选项不属于 Python 语言特点的是

    【单选题】以下不是python中的关键字

    【单选题】以下选项中,Python语言中代码注释使用的符号是

    【单选题】以下选项中不符合 Python 语言变量命名规则的是

    【简答题】CAD另存的快捷键是什么?

    【单选题】Python语言提供的3个基本数字类型是

    【判断题】按使用介质的温度分。水表可分为冷水水表和热水水表。

    【简答题】Do you think grammar is important in English learning?

    【单选题】关于 Python 语言的注释,以下选项中描述错误的是

    【单选题】在非常正式的场合,尤其有尊长在场的情况下,一般坐凳子的( )。

    【多选题】男士在正式场合可以选择的坐姿有( )

    【单选题】Python可以将一条长语句分成多行显示的续行符号是:

    【单选题】关于Python语言的变量,以下选项中说法正确的是

    【单选题】下面哪个不是Python合法的标识符

    【简答题】Do you have any problem in English learning?

    展开全文
  • Swift关键字总结上篇

    千次阅读 2017-12-24 20:41:13
    在Swift官方文档的词汇结构中, 有非常多的关键字, 它们被用于声明中、语句中、表达式中、类中、模式中, 还有以数字符号#开头的关键字, 以及特定上下文环境使用的关键字。 本文中涉及的代码可以在这里下载代码资源。...

    Swift关键字总结上篇
    Swift关键字总结下篇


    Swift中有多少关键字?

    在Swift官方文档的词汇结构中, 有非常多的关键字, 它们被用于声明中、语句中、表达式中、类中、模式中, 还有以数字符号开头的关键字, 以及特定上下文环境使用的关键字。 本文中涉及的代码可以在这里下载代码资源

    另外, 在特性中还有一些关键字, 是以@开头的关键字。这些所有的关键字将在 Swift关键字总结上篇Swift关键字总结下篇 两篇文章中详细列举。

    本篇主要写到不带符号的关键字, 如带#的关键字和带@的特性将在下篇文章中详细说明。

    用在声明中的关键字

    associatedtypeclassdeinitenumextensionfuncimportinitinoutinternalletoperatorprivateprotocolpublicopenfileprivatestaticstructsubscripttypealiasvar

    用在语句中的关键字

    breakcasecontinuedefaultdeferdoelsefallthroughforguardifinrepeatreturnswitchwherewhile

    用在表达式和类型中的关键字

    ascatchdynamicTypefalseisnil , rethrowssuperselfSelfthrowthrowstruetry

    特定上下文中被保留的关键字

    associativityconveniencedynamicdidSetfinalgetinfixindirectlazyleftmutatingnonenonmutatingoptionaloverridepostfixprecedenceprefixProtocolrequiredrightsetTypeunownedweakwillSet

    起始于数字标记(#)的关键字

    #available#column#else#elseif#endif#file#function#if#line#selector#sourceLocation

    用在模式中的关键字

    _

    注意事项

    以上的关键字被预留,不能被用作标识符,除非它们像上一节标识符中描述的那样使用反引号(`), 才能使用保留字作为标识符。

    有个例外是, 特定上下文中被保留的关键字在特定上下文语法之外可以被用于标识符。

    以下标记被当作保留符号,不能用于自定义操作符:(){}[].,:;=@#& (作为前缀操作符)、 ->\?! (作为后缀操作符)。

    关键字如何使用?

    我想在上面的这些关键字中, 大部分的大家应该烂熟于心了。那我就在其中挑选部分特殊的或不常用的来说一说吧。

    inout

    在函数的入参的类型前添加一个 inout关键字可以定义一个输入输出形式参数。输入输出形式参数有一个能输入给函数的值,函数能对其进行修改,还能输出到函数外边替换原来的值。

    你只能把变量作为输入输出形式参数的实际参数。你不能用常量或者字面量作为实际参数,因为常量和字面量不能修改。在将变量作为实际参数传递给输入输出形式参数的时候,直接在它前边添加一个和符合 (&) 来明确可以被函数修改。

    func swapTwoInts(_ a: inout Int, _ b: inout Int) {
        let temporaryA = a
        a = b
        b = temporaryA
    }
    
    var abc = 99
    var efg = 88
    swapTwoInts(&abc, &efg)
    
    print(abc, efg)
    // print result is "88 99\n"
    

    typealias、协议组合类型

    类型别名可以为已经存在的类型定义了一个新的可选名字。用 typealias 关键字定义类型别名。一旦为类型创建了一个别名,你就可以在任何使用原始名字的地方使用这个别名。

    typealias AudioSample = UInt16
    var maxAmplitudeFound = AudioSample.min
    
    typealias Point = (Int, Int) 
    let origin: Point = (0, 0)
    

    关于typealias还有一种很高效的用法与协议组合类型相关。

    协议组合类型

    协议组合类型允许你指定一个值,该值的类型遵循多个协议的要求而不必显式定义一个新的命名型的继承自每个你想要该类型遵循的协议的协议。比如,指定一个协议组合类型 ProtocolA & ProtocolB & ProtocolC实际上是和定义一个新的继承自 ProtocolA,ProtocolB, ProtocolC的协议 ProtocolD是完全一样的,但不需要引入一个新名字同理,标明一个协议组合类型 SuperClass & ProtocolA与声明一个新类型 SubClass继承自 SuperClass 并遵循 ProtocolA是一样的,但不需要引入新名字。

    协议组合列表中的每项元素必须是类名,协议名或协议组合类型、协议、类的类型别名。列表可以最多包含一个类。

    当协议组合类型包含类型别名,就有可能同一个协议在定义中出现不止一次——重复会被忽略。比如说,下面的 PQR 定义等价于 P & Q & R 。

    typealias PQ = P & Q
    typealias PQR = PQ & Q & R
    

    associatedtype

    定义一个协议时,有时在协议定义里声明一个或多个关联类型是很有用的。关联类型给协议中用到的类型一个占位符名称。直到采纳协议时,才指定用于该关联类型的实际类型。关联类型通过 associatedtype 关键字指定。

    这里是一个叫做Container 的示例协议,声明了一个叫做 ItemType 的关联类型:

    protocol Container {
        associatedtype ItemType
        mutating func append(_ item: ItemType)
        var count: Int { get }
    }
    

    Container 协议定义了所有容器必须提供的功能:
    1.必须能够通过 append(_: ) 方法向容器中添加新元素
    2.必须能够通过一个返回 Int 值的 count 属性获取容器中的元素数量
    3.必须能够通过 Int 索引值的下标取出容器中每个元素。

    任何遵循 Container协议的类型必须能指定其存储值的类型。尤其是它必须保证只有正确类型的元素才能添加到容器中。为了实现这些要求, Container 协议声明了一个叫做 ItemType 的关联类型,写作 associatedtype ItemType

    struct IntStack: Container {
        // original IntStack implementation
        var items = [Int]()
        
        // conformance to the Container protocol
        typealias ItemType = Int
        mutating func append(_ item: Int) {
            // append...
        }
        var count: Int {
            return items.count
        }
    }
    

    IntStack为了实现 Container协议,指定了适用于ItemType的类型是 Int类型。typealias ItemType = IntItemType抽象类型转换为了具体的 Int类型。如果你从代码中删除了 typealias ItemType = Int,一切都会正常运行,因为 ItemType 会由Swift的类型推断推断出来。

    另外,还可以为协议定义默认的associatedtype,可以用于不能进行类型推断的时候。这样,在默认的实现中,就可以省略类型关联了。

    protocol Statistic {
        associatedtype result: Codable = String
    
        var count: Int { get }
    }
    

    subscript

    下标的语法, 下标脚本允许你通过在实例名后面的方括号内写一个或多个值对该类的实例进行查询。它的语法类似于实例方法和和计算属性。使用关键字 subscript 来定义下标,并且指定一个或多个输入形式参数和返回类型,与实例方法一样。与实例方法不同的是,下标可以是读写也可以是只读的。这个行为通过与计算属性中相同的 gettersetter 传达:

    subscript(index: Int) -> Int {
        get {
            // return an appropriate subscript value here
        }
        set(newValue) {
            // perform a suitable setting action here
        }
    }
    

    newValue 的类型和下标的返回值一样。与计算属性一样,你可以选择不去指定 setter 的(newValue)形式参数。 setter 默认提供形式参数 newValue ,如果你自己没有提供的话。

    struct TimesTable {
        let multiplier: Int
        subscript(index: Int) -> Int {
            return multiplier * index
        }
    }
    let threeTimesTable = TimesTable(multiplier: 3)
    print("six times three is \(threeTimesTable[6])")
    // prints "six times three is 18"
    

    operator、prefix、postfix、infix

    除了实现标准运算符,在 Swift 当中还可以声明和实现自定义运算符(custom operators)。可以用来自定义运算符的字符列表请参考运算符。

    新的运算符要在全局作用域内,使用 operator 关键字进行声明,同时还要指定 prefixinfix 或者 postfix 限定符, 语法结构如下:

    prefix operator `operatorName`
    postfix operator `operatorName`
    infix operator operatorname: `precedenceGroup`
    

    上面的代码定义了一个新的名为 +++ 的前缀运算符。这个运算符在 Swift 中并没有意义,我们针对下面这个类SomeNumer的实例来赋予它意义。对这个例子来讲, +++ 作为“平方”运算符。

    prefix operator +++
    
    class SomeNumber {
        var minNum = 0
        var maxNum = 0
    
        static prefix func +++(number: SomeNumber) -> SomeNumber {
            number.minNum = number.minNum * number.minNum
            number.maxNum = number.maxNum * number.maxNum
            return number
        }
    }
    
    var aaa = SomeNumber()
    aaa.minNum = 3
    aaa.maxNum = 6
    +++aaa
    print(aaa.minNum, aaa.maxNum)
    // result is "9 36\n"
    

    需要注意的地方是, 当使用自定义运算时, 传入的参数至少要有一个当前对象, 否则编译不会通过。定义前缀或后缀运算符时,不要指定优先级。但是,如果将前缀和后缀运算符应用于相同的操作时,则首先进行后缀运算。

    上面这个例子是前缀prefix, 当然后缀 postfix也是同样的用法, 还有一个中缀 infix是比较特殊的, 涉及到结合性associativity 和 优先级precedence的使用。下面继续来进行说明。

    precedenceGroup、precedence、associativity

    自定义的中缀( infix )运算符也可以指定优先级和结合性。优先级和结合性中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。

    以下示例定义了一个名为+ - 的新自定义中缀运算符,该运算符属于优先级组AdditionPrecedence:

    infix operator +-: AdditionPrecedence
    extension SomeNumber {
        static func +- (left: SomeNumber, right: SomeNumber) -> Int {
            return  left.minNum * left.maxNum + right.minNum * right.maxNum
        }
    }
    print(aaa +- aaa)
    // result is 648
    

    infix operator +-: AdditionPrecedence这一行对自定义运算符有一个声明的作用。当然也可以不使用系统中的优先级分组,自己自定义。有一点需要注意,从 Swift4.0开始,声明中必须要使用优先级分组而不支持之前直接定义precedence的语句了,已然废弃。否则会有如下错误产生:

    infix operator +- { associativity left precedence 140 }
    // Eror: Operator should no longer be declared with body; use a precedence group instead
    

    中缀的表达式中的precedenceGroup 是中缀运算符优先级分组。优先级组声明 (A precedence group declaration) 会向程序的中缀运算符引入一个全新的优先级组运算符的优先级指定运算符在没有分组括号的情况下绑定到其操作数的紧密程度。

    自定义优先级分组:

    precedencegroup 优先级组名称 {
        higherThan: 较低优先级组的名称
        lowerThan: 较高优先级组的名称
        associativity: 结合性
        assignment: 赋值性
    }
    

    precedencegroup定义了一个优先级组,表示操作符优先级别。操作符优先级的定义和类型声明有些类似, 一个操作符需要属于某个特定的优先级。Swift定义了许多优先组与标准库提供的运算符一起使用。例如,加(+)和减( - )运算符属于AdditionPrecedence组,乘(*)和除(/)运算符属于MultiplicationPrecedence组。查看提供这些优先级组请看这里Improved operator declarations。如果没有合适你的运算符的优先级组, 你就需要向我们在例子中做的这样, 自己指定结合律方式和优先级顺序了。

    higherThanlowerThan定义了较高优先级组和较低优先级组,说明了新建的优先级组是依赖于现存的优先级组的。 lowerThan 优先级组的属性只可以引用当前模块外的优先级组。当两个运算符为同一个操作数竞争时,比如表达式2 + 3 * 5,优先级更高的运算符将优先参与运算。

    优先级组的结合性associativity定义了结合律, 即多个同类的操作符顺序出现时的计算顺序。比较常见的加法和减法都是left, 就是说多个加法同时出现时按照从左往右的顺序计算。比如在加法和乘法中,它们满足加法交换律和乘法交换律,无论怎样的顺序都不影响最后的计算结果。而在减法和除法中,就不可以,是需要从左向右进行计算的(即左关联性的),否则结果就会出错。右关联性的运算符是从右往左分组的,指定为none结合性的运算符就没有结合性。还需要注意的是,同样优先级没有结合性的运算符不能相邻出现,例如<运算符是none结合性,那表示1 < 2 < 3就不是一个有效表达式。

    优先级组的赋值性assignment表示在包含可选链操作时的运算符优先级。当设为true时,与优先级组对应的运算符在可选链操作中使用和标准库中赋值运算符同样的分组规则,当设为false或者不设置,该优先级组的运算符与不赋值的运算符遵循同样的可选链规则。

    自定义优先级分组举例:

    precedencegroup MulAddPrecedence {
        associativity: none
        higherThan: MultiplicationPrecedence
    }
    
    infix operator +*: MulAddPrecedence
    
    struct Seat {
        var row = 0
        var column = 0
    
        static func +* (left: Seat, right: Seat) -> Seat {
            let row = left.row * left.row + right.row * right.row
            let column = left.column * left.column + right.column * right.column
            return Seat(row: row, column: column)
        }
    }
    

    defer

    defer 语句用于在退出当前作用域之前执行代码。

    defer {
        statement
    }
    

    defer 语句中的语句无论程序控制如何转移都会被执行。在某些情况下,例如,手动管理资源时,比如关闭文件描述符,或者即使抛出了错误也需要执行一些操作时,就可以使用 defer 语句。

    如果多个 defer 语句出现在同一作用域内,那么它们执行的顺序与出现的顺序相反。给定作用域中的第一个 defer 语句,会在最后执行,这意味着代码中最靠后的 defer 语句中引用的资源可以被其他 defer 语句清理掉。

    func f() {
        print("111")
        defer { print("First") }
        print("222")
        defer { print("Second") }
        print("333")
        defer { print("Third") }
        print("444")
    }
    f()
    
    // 打印结果
    // 111
    // 222
    // 333
    // 444
    // Third
    // Second
    // First
    

    defer 语句中的语句无法将控制权转移到 defer 语句外部。

    fallthrough

    fallthrough 语句用于在 switch 语句中转移控制权。fallthrough 语句会把控制权从 switch 语句中的一个 case 转移到下一个 case。这种控制权转移是无条件的,即使下一个 case 的模式与 switch 语句的控制表达式的值不匹配。

    fallthrough 语句可出现在 switch 语句中的任意 case中,但不能出现在最后一个 case 中。同时,fallthrough 语句也不能把控制权转移到使用了值绑定的 case

    switch 1 {
    case 1:
        print("111")
        fallthrough
    case 2:
        print("222")
    case 3:
        print("333")
    default:
        print("default")
    }
    // result is 
    // 111 
    // 222
    

    dynamicType

    注意: 这个关键字在Swift 4.0 开始已经废弃了(depricate from Swift 4.0)!!!
    你可以对类型的实例使用 dynamicType 表达式来获取该实例的动态运行时的类型。

    class SomeBaseClass {
        class func printClassName() {
            print("SomeBaseClass")
        }
    }
    class SomeSubClass: SomeBaseClass {
        override class func printClassName() {
            print("SomeSubClass")
        }
    }
    let someInstance: SomeBaseClass = SomeSubClass()
    // The compile-time type of someInstance is SomeBaseClass,
    // and the runtime type of someInstance is SomeSubClass
    someInstance.dynamicType.printClassName()
    // Prints "SomeSubClass"
    

    do 、 try 、 catch 、throw 、 throws、rethrows

    表示错误

    这些关键字都是关于错误处理的, 错误处理是相应和接收来自你程序中错误条件的过程。Swift 给运行时可恢复错误的抛出、捕获、传递和操纵提供了一类支持。

    在 Swift 中,错误表示为遵循 Error 协议类型的值。这个空的协议明确了一个类型可以用于错误处理。

    Swift 枚举是典型的为一组相关错误条件建模的完美配适类型,关联值还允许错误错误通讯携带额外的信息。比如说,SomeError 的错误条件:

    enum SomeError: Error {
        case SomeError1
        case SomeError2
        case SomeError3(code: Int)
    }
    

    抛出一个错误允许你明确某些意外的事情发生了并且正常的执行流不能继续下去。你可以使用 throw 语句来抛出一个错误。

    throw SomeError.SomeError2
    throw SomeError.SomeError3(code: value)
    

    抛出错误

    为了明确一个函数或者方法可以抛出错误,你要在它的声明当中的形式参数后边写上 throws关键字。使用 throws标记的函数叫做抛出函数。如果它明确了一个返回类型,那么 throws关键字要在返回箭头 ( ->)之前。

    func makeSomeError(value: Int)
    func makeSomeError(value: Int) throws
    func makeSomeError(value: Int) throws -> String
    

    但是只有抛出函数可以传递错误。任何在非抛出函数中抛出的错误都必须在该函数内部处理。函数类型如果要抛出错误就必须使用 throws 关键字标记,而且能重抛错误的函数类型必须使用 rethrows 关键字标记。

    完善这个可抛异常的函数实现:

    func makeSomeError(value: Int) throws {
        switch value {
        case 1:
            throw SomeError.SomeError1
        case 2:
            throw SomeError.SomeError2
        case 3:
            throw SomeError.SomeError3(code: 888)
        case 4:
        	// 默认的这里随便找了一个错误, 来说明catch的范围
            throw MachError(.exceptionProtected)
        default:
            print("excute normal code")
        }
    }
    

    处理错误

    在 Swift 中有四种方式来处理错误。你可以将来自函数的错误传递给调用函数的代码中,使用 do-catch 语句来处理错误,把错误作为可选项的值,或者错误不会发生的断言。

    使用 do-catch语句来通过运行一段代码处理错误。如果do分句中抛出了一个错误,它就会与 catch分句匹配,以确定其中之一可以处理错误。

    这是 do-catch语句的通常使用语法:

    do {
        try expression
        statements
    } catch pattern 1 {
        statements
    } catch pattern 2 where condition {
        statements
    }
    

    按照上面的例子来写, 如下:

    do {
        try makeSomeError(value: 1)
    } catch SomeError.SomeError1 {
        print("SomeError1")
    } catch SomeError.SomeError2 {
        print("SomeError2")
    } catch SomeError.SomeError3(let anyCode) {
        print("SomeError3 code is \(anyCode)")
    }
    

    结合三者, 就是一个完整的例子, 当我们执行如上代码时, 将会catchSomeError1 并打印。

    如果将 try 语句换为如下代码时, 打印结果如下:

    try makeSomeError(value: 3)
    // SomeError2
    try makeSomeError(value: 3)
    // SomeError3 code is 888
    try makeSomeError(value: 4)
    // nothing print, because can't catch
    try makeSomeError(value: 5)
    // excute normal code
    

    makeSomeError执行 default分支时, 抛出的异常是不能 处理的, 因为catch中没有涉及相关的异常所以catch不到的.

    convenience

    便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。

    如果你的类不需要便利构造器你可以不提供它。在为通用的初始化模式创建快捷方式以节省时间或者类的初始化更加清晰明了的时候时候便利构造器。

    便利构造器可以将构造过程委托给另一个便利构造器或一个指定构造器。但是,类的构造过程必须以一个将类中所有属性完全初始化的指定构造器的调用作为结束。便利构造器不能调用超类的构造器。

    便利构造器有着相同的书写方式,但是要用 convenience 修饰符放到 init 关键字前,用空格隔开:

    convenience init(parameters) {
        statements
    }
    
    class Food {
        var name: String
        init(name: String) {
            self.name = name
        }
        convenience init() {
            self.init(name: "[Unnamed]")
        }
    }
    var food = Food.init()
    print(food.name)
    // result is [Unnamed]
    

    willSet、didSet

    可以在声明存储型变量或属性时提供 willSetdidSet 观察器。一个包含观察器的存储型变量或属性以如下形式声明:

    var 变量名称: 类型 = 表达式 {  
        willSet(setter 名称) {  
            语句
        }  
        didSet(setter 名称) {  
            语句
        }  
    }  
    

    可以在全局范围、函数内部,或者类、结构的声明中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,观察器表示一个存储型变量观察器。当它在类和结构的声明中被声明时,观察器表示一个属性观察器。

    可以为任何存储型属性添加观察器。也可以通过重写父类属性的方式为任何继承的属性(无论是存储型还是计算型的)添加观察器。

    当变量或属性的值被改变时,willSetdidSet 观察器提供了一种观察方法。观察器会在变量的值被改变时调用,但不会在初始化时被调用。

    willSet 观察器只在变量或属性的值被改变之前调用。新的值作为一个常量传入 willSet 观察器,因此不可以在 willSet 中改变它。didSet 观察器在变量或属性的值被改变后立即调用。和 willSet 观察器相反,为了方便获取旧值,旧值会传入 didSet 观察器。这意味着,如果在变量或属性的 didiset 观察器中设置值,设置的新值会取代刚刚在 willSet 观察器中传入的那个值。

    willSetdidSet 中,圆括号以及其中的 setter 名称是可选的。如果提供了一个 setter 名称,它就会作为 willSetdidSet 的参数被使用。如果不提供 setter 名称,willSet 观察器的默认参数名为 newValuedidSet 观察器的默认参数名为 oldValue

    提供了 willSet 时,didSet 是可选的。同样的,提供了 didSet 时,willSet 则是可选的。

    open、public、internal、fileprivate、private

    这些关键字是 Swift 为代码的实体提供个五个不同的访问级别。这些访问级别和定义实体的源文件相关,并且也和源文件所属的模块相关。

    open 访问是最高的(限制最少)访问级别,private 是最低的(限制最多)访问级别。

    private

    private 访问, 将实体的使用限制于封闭声明中。当一些细节仅在单独的声明中使用时,使用 private 访问隐藏特定功能的实现细节。

    fileprivate

    File-private 访问, 将实体的使用限制于当前定义源文件中。当一些细节在整个文件中使用时,使用 file-private 访问隐藏特定功能的实现细节。

    internal

    Internal 访问, 为默认访问级别, 允许实体被定义模块中的任意源文件访问,但不能被该模块之外的任何源文件访问。通常在定义应用程序或是框架的内部结构时使用。

    public、open

    public 访问和Open 访问, 允许实体被定义模块中的任意源文件访问,同样可以被另一模块的源文件通过导入该定义模块来访问。在指定框架的公共接口时,通常使用 openpublic 访问。

    public 访问只能在当前模块中被继承和子类重写。
    open 访问仅适用于类和类成员,可以在其他模块外被继承和子类重写。

    显式地标记类为 open 意味着你考虑过其他模块使用该类作为父类对代码的影响,并且相应地设计了类的代码。

    访问控制的注意事项

    Swift 中的访问级别遵循一个总体指导准则:实体不可以被更低(限制更多)访问级别的实体定义。

    比如: 一个 public 的变量其类型的访问级别不能是 internal, file-private 或是 private,因为在使用 public 变量的地方可能没有这些类型的访问权限。
    又比如: 函数类型的访问级别由函数成员类型和返回类型中的最严格访问级别决定。一个函数不能比它的参数类型和返回类型访问级别高,因为函数可以使用的环境而其参数和返回类型却不能使用。

    这里简单列举了两个有关访问级别的使用注意事项。想了解有更多详细的注意事项的朋友, 可以查阅我的另外一篇博文: Swift 之访问控制。这里面有代码举例和详细说明, 小编这里不再赘述。

    final

    该修饰符用于修饰类或类中的属性、方法以及下标。如果用它修饰一个类,那么这个类不能被继承。如果用final 修饰类中的属性、方法或下标,那么它们不能在子类中被重写。

    使用final的情况, 是类或方法属性等不希望被继承和重写,具体情况一般是:
    1.类或者方法的功能确实已经完备了, 基本不会再继承和重写。
    2.避免子类继承和修改造成危险。有些方法如果被子类继承重写会造成破坏性的后果,导致无法正常工作,则需要将其标为final加以保护。
    3.保证父类的方法一定被执行, 我们可以把父类的方法定义成final,同时将内部可以继承的部分剥离出来,供子类继承重写。

    还有一中说法, 认为final能改成性能,因为编译器能从final中获取额外的信息,所以可以对类或者方法调用进行优化处理。其实这样优化对性能的提升非常有限,所以如果是为了提升性能, 把所有的属性方法都加上final关键字,也没有多大的作用。

    required

    必要构造器标识符, 修饰符用于修饰类的指定构造器或便利构造器,表示该类所有的子类都必须实现该构造器。在子类实现该构造器时,必须同样使用 required 修饰符修饰该构造器。

    为了要求子类去实现超类的构造器,使用 required 声明修饰符标记超类的构造器。子类实现超类构造器时也必须使用 required 声明修饰符。

    class SomeClass1 {
        required init() {
            // 构造器的实现代码
        }
    }
    

    在子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加override修饰符:

    class SomeSubclass: SomeClass1 {
        required init() {
            // 构造器的实现代码
        }
    }
    

    如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。就像下面的代码, 因为子类继承的构造器能满足必要构造器的要求, 所以子类的必要构造器可以是隐性的。代码如下:

    class SomeClass1 {
        required init() {
            // 构造器的实现代码
        }
    }
    class SomeSubclass: SomeClass1 {
    
    }
    

    你可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 required 修饰符:

    class SomeClass: SomeProtocol {
        required init(someParameter: Int) {
            // 这里是构造器的实现部分
        }
    }
    

    使用 required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。如果类已经被标记为 final,那么不需要在协议构造器的实现中使用 required 修饰符,因为 final 类不能有子类。如果这个类还没有用 final 声明修饰符标记,这个构造器必须用 required 声明修饰符标记。

    这就是为什么在日常开发中, 当我们继承系统某各类去指定一个新的构造器时, 系统总是编译报错, 提示添加如下代码:

    required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    

    这种情况一般会出现在继承了遵守NSCoding protocol的类,比如UIView系列的类、UIViewController系列的类。这是NSCoding protocol定义的,遵守了NSCoding protoaol的所有类必须继承。当我们在子类定义了指定初始化器(包括自定义和重写父类指定初始化器),那么必须显示实现required init?(coder aDecoder: NSCoder),而其他情况下则会隐式继承。

    如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 requiredoverride 修饰符:

    protocol SomeProtocol {
        init()
    }
    class SomeSuperClass {
        init() {
            // 这里是构造器的实现部分
        }
    }
    
    class SomeSubClass: SomeSuperClass, SomeProtocol {
        // 因为遵循协议,需要加上 required
        // 因为继承自父类,需要加上 override
        required override init() {
            // 这里是构造器的实现部分
        }
    }
    

    mutating、nonmutating

    结构体和枚举是值类型。默认情况下,值类型属性不能被自身的实例方法修改。但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择可变(mutating)行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的self属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。

    结构体中用法:

    struct Point {
        var x = 0.0, y = 0.0
        mutating func moveBy(x deltaX: Double, y deltaY: Double) {
            self = Point(x: x + deltaX, y: y + deltaY)
        }
    }
    

    枚举中用法:

    enum TriStateSwitch {
        case off, low, high
        mutating func next() {
            switch self {
            case .off:
                self = .low
            case .low:
                self = .high
            case .high:
                self = .off
            }
        }
    }
    var ovenLight = TriStateSwitch.low
    ovenLight.next()
    // ovenLight is now equal to .high
    ovenLight.next()
    // ovenLight is now equal to .off
    

    在协议中如何使用? 若你定义了一个协议的实例方法需求,想要改变任何采用了该协议的类型实例,只需在协议里方法的定义当中使用 mutating 关键字。这允许结构体和枚举类型能采用相应协议并满足方法要求。

    protocol Togglable {
        mutating func toggle()
    }
    

    Togglable协议的定义中, toggle() 方法使用 mutating 关键字标记,来表明该方法在调用时会改变遵循该协议的实例的状态:

    struct Test: Togglable {
        var time: Int = 0
        
        mutating func toggle() {
            self.time = 33333
        }
    }
    
    var test = Test()
    test.time = 2
    test.toggle()
    // result is 2
    

    如果你在协议中标记实例方法需求为 mutating ,在为类实现该方法的时候不需要写 mutating 关键字。 mutating 关键字只在结构体和枚举类型中需要书写。

    dynamic

    我来告诉你为什么Swift中要使用关键字dynamic。Swift 中的函数可以是静态调用,静态调用会更快。Swift的代码直接被编译优化成静态调用的时候,就不能从Objective-C 中的SEL字符串来查找到对应的IMP了。这样就需要在 Swift 中添加一个关键字 dynamic,告诉编译器这个方法是可能被动态调用的,需要将其添加到查找表中。

    纯Swift类没有动态性,但在方法、属性前添加dynamic修饰可以获得动态性。该修饰符用于修饰任何兼容 Objective-C 的类的成员。访问被 dynamic 修饰符标记的类成员将总是由 Objective-C 运行时系统进行动态派发,而不会由编译器进行内联或消虚拟化。

    继承自NSObject的Swift类,其继承自父类的方法具有动态性,其他自定义方法、属性需要加dynamic修饰才可以获得动态性。而且因为使用动态修饰符标记的声明是使用Objective-C运行时分派的,所以它们必须用objc属性标记。(从Swift 4.0开始, 加dynamic 修饰符时必须是显式的objc了)

    纯Swift类中的dynamic的使用:

    class DynamicSwiftClass {
        var zero = 0
        @objc dynamic var fist = 1
        @objc func dynamicFunc() {
        }
    //    open this code will be error
    //    @objc dynamic var adddd = (0 , 0)
    //    @objc dynamic func someMethod(value: Int) -> (Int, Int) {
    //        return (1, 1)
    //    }
    }
    

    若方法的参数、属性类型为Swift特有、无法映射到Objective-C的类型(如CharacterTuple),则此方法、属性无法添加dynamic修饰, 一旦添加就会编译报错。

    optional

    该修饰符用于修饰协议中的属性、方法以及下标成员,表示符合类型可以不实现这些成员要求。

    可选类型

    Swift 为命名类型 Optional<Wrapped> 定义后缀 ? 作为语法糖 ,其定义在 Swift 标准库中。换句话说,下列两种声明是等价的:

    var optionalInteger: Int? 
    var optionalInteger: Optional<Int>
    

    在上述两种情况下,变量 optionalInteger 都声明为可选整数类型。注意在类型和 ? 之间没有空格。

    类型 Optional<Wrapped> 是有两种情况的, noneSome(Wrapped) ,它代表可能没有值或可能有值。任何类型都可以被显式的声明(或隐式的转换)为可选类型。如果你在声明可选的变量或属性时没有提供初始值,它的值则会默认为 nil

    使用 ! 操作符获解析一个值为 nil 的可选项会导致运行时错误。你也可以使用可选链和可选绑定来有条件地执行对可选表达式的操作。如果值为 nil ,不会执行任何操作并且不会因此产生运行时错误。

    隐式展开可选类型

    Swift 为命名类型 Optional<Wrapped> 定义后缀 ! 作为语法糖 ,其定义在 Swift 标准库中,作为它被访问时自动解析的附加行为。如果你试图使用一个值为 nil 的隐式解析,你会得到一个运行时错误。除了隐式展开的行为之外,下面两个声明是等价的:

    var implicitlyUnwrappedString: String! 
    var explicitlyUnwrappedString: Optional<String>
    

    注意类型与 ! 之间没有空格。
    有了可选项,如果在声明隐式展开可选变量或属性时你不用提供初始值,它的值会默认为 nil 。使用可选链有条件地对隐式展开可选项的表达式进行操作。如果值为 nil ,就不执行任何操作,因此也不会产生运行错误。

    可选的协议

    协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。在协议中使用 optional 关键字作为前缀来定义可选要求。可选要求用在你需要和 Objective-C 打交道的代码中。协议和可选要求都必须带上objc属性。标记 objc 特性的协议只能被继承自 Objective-C 类的类或者 objc 类遵循,其他类以及结构体和枚举均不能遵循这种协议。只能将 optional 修饰符用于被 objc 特性标记的协议。这样一来,就只有类类型可以采纳并符合拥有可选成员要求的协议。

    使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 (Int) -> String 的方法会变成((Int) -> String)?。需要注意的是整个函数类型是可选的,而不是函数的返回值。

    协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 someOptionalMethod?(someArgument) 这样,你可以在可选方法名称后加上 ? 来调用可选方法。

    @objc protocol CounterDataSource {
        @objc optional var fixedIncrement: Int { get }
        @objc optional func incrementForCount() -> Int
    }
    
    class Counter {
        var count = 0
        var dataSource: CounterDataSource?
        func increment() {
            if let amount = dataSource?.incrementForCount?() {
                count += amount
            } else if let amount = dataSource?.fixedIncrement {
                count += amount
            }
        }
    }
    
    class ThreeSource: NSObject, CounterDataSource {
        let fixedIncrement = 3
    }
    
    var counter = Counter()
    counter.dataSource = ThreeSource()
    for _ in 1...4 {
        counter.increment()
        print(counter.count)
    }
    // 3
    // 6
    // 9
    // 12
    

    当没有代理没有实现可选的协议时, dataSource?.incrementForCount?()nil, 只有当代理实现了此协议方法时, amount才会是返回的那个值。

    indirect

    递归枚举是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上indirect来表示该成员可递归, 而且被 indirect 修饰符标记的枚举用例必须有一个关联值。。

    enum ArithmeticExpression {
        case number(Int)
        indirect case addition(ArithmeticExpression, ArithmeticExpression)
        indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
    }
    

    你也可以在枚举类型开头加上indirect关键字来表明它的所有成员都是可递归的:

    indirect enum ArithmeticExpression {
        case number(Int)
        case addition(ArithmeticExpression, ArithmeticExpression)
        case multiplication(ArithmeticExpression, ArithmeticExpression)
    }
    

    上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员additionmultiplication的关联值也是算术表达式。

    下面的代码展示了使用ArithmeticExpression这个递归枚举创建表达式(5 + 4) * 2:

    let five = ArithmeticExpression.number(5)
    let four = ArithmeticExpression.number(4)
    let sum = ArithmeticExpression.addition(five, four)
    let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
    

    使用这个枚举:

    func evaluate(_ expression: ArithmeticExpression) -> Int {
        switch expression {
        case let .number(value):
            return value
        case let .addition(left, right):
            return evaluate(left) + evaluate(right)
        case let .multiplication(left, right):
            return evaluate(left) * evaluate(right)
        }
    }
    print(evaluate(product))
    // 打印 "18"
    

    相关资料:
    Swift关键字总结上篇
    Swift关键字总结下篇

    展开全文
  • C++语言中用于定义类的关键字是( )A.classB.structC.defaultD.sizeof正确答案:A2). 要想使23.78965421的输出为23.7887应用的语句是( )A.cout<<23.78965421;B.cout<C.cout<<23.78965421 -0.001;D....

    6e07e29112a930b329127bfc43af227f.png

    1). C++语言中用于定义类的关键字是( )

    A.class

    B.struct

    C.default

    D.sizeof

    正确答案:A

    2). 要想使23.78965421的输出为23.7887应用的语句是( )

    A.cout<<23.78965421;

    B.cout<

    C.cout<<23.78965421 -0.001;

    D.cout<

    正确答案:C

    3). 下列选项中正确的语句是( )。

    正确答案:D

    答案解析:在选项D中,字符指针s指向字符串常量BOOK!。故选项D正确。

    4). 在32位的计算机中,一个char型数据所占的内存长度的字节是( )

    A.4

    B.1

    C.2

    D.8

    正确答案:B

    5). 静态成员函数没有( )。

    A.返回值

    B.this指针

    C.指针参数

    D.返回类型

    正确答案:B

    答案解析:this指针是系统隐含的用于指向当前对象的指针。由于静态函数是同类中所有对象都共享的函数,在内存中只存在一份,不属于某个对象所有,所以静态函数没有this指针。

    2829f2ca24f0d0090cbba57f30759f23.png

    2017年计算机二级考试C++强化试题及答案5.doc

    下载Word文档到电脑,方便收藏和打印[全文共467字]

    编辑推荐:

    8b95f2eb3d3f7ce4dc3bf1178c74941e.png

    8b95f2eb3d3f7ce4dc3bf1178c74941e.png

    8b95f2eb3d3f7ce4dc3bf1178c74941e.png

    下载Word文档

    展开全文
  • 【单选题】Python文件的后缀名是【单选题】以下选项中,不是Python语言保留字的是【单选...【单选题】Python可以将一条长语句分成多行显示的续行符号是:【多选题】水表的结构有:( )。【判断题】按使用介质的温度分。...
  • Static方法是类方法,先于任何的实例(对象...也就是说你只能用它来调用属于当前对象的方法,或者使用this处理方法中成员变量和局部变量重名的情况. 而且,更为重要的是this和super都无法出现在static修饰的方法中。...
  • 可以在一条语句中声明很多变量(如: var a=1, b=2, c=3…); 未使用值来声明的变量,值是 undefined (如:var a; 代表先声明一个变量,但是没有给它赋值,那该变量的值就是 undefined ); 如果重新声明 ...
  • 全面理解Java内存模型(JMM)及volatile关键字

    万次阅读 多人点赞 2017-06-12 11:25:05
    每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关...
  • 【单选题】以下赋值语句中合法的是【单选题】关于颞下颌关节的运动,说法错误的是 ( )【单选题】颞下颌关节的负重区为 ( )【填空题】在Python中__________表示空类型。【填空题】ls 是一个列表,内容如下: ‪‪‪‪‪...
  • C语言关键字

    千次阅读 2016-04-19 11:14:44
    auto :声明自动变量 一般使用 auto被解释为一个自动存储变量的关键字,也就是申明一块临时的变量内存。auto int a=4;表示a为一个自动存储的临时变量。 作用:C程序是面向过程的,在C代码中会出现大量的函数...
  • C#关键字详解

    千次阅读 2013-03-11 14:18:00
    关键字是对编译器有特殊意义的预定义的保留标识符。 它们能在程序中用作普通标识符,除非在它们前面加上@前缀。    第一部分    一.访问关键字:base,this ~~~~base:访问基类的成员。 用于从派生类中...
  • C#关键字整理

    2014-01-15 17:34:38
    C#关键字整理 一、基本关键字 1. abstract 用法:用来修饰类、方法和属性。 例子:修饰方法:public abstract void MyMethod();  修饰类:abstractclass BaseClass{}; 2. as 用法:使用as运算符执行转换的...
  • C语言关键字详解

    千次阅读 多人点赞 2019-03-09 11:32:38
    在这里先普及一下,在我们C语言中一共有32个关键字,可能有几个在我们学习的过程中太常用,但是大多数都是我们常用的,下来我们先来罗列一下着32个关键字都有什么,再来一一说一下他们的用法: auto int...
  • typeof关键字简介

    2012-10-09 17:32:57
    在内核链表中经常使用到这个关键字,但是在却不是标准C 的32个关键字之一,属于拓展的C关键字吧,和sizeof一样,非常好用,所以到网上转载了...typeof关键字是C语言中的一个新扩展。只要可以接受typedef名称,Sun S
  • Java的关键字

    2019-03-05 09:12:19
    Java关键字是电脑语言里事先定义的,有特别意义的标识符,有时又叫保留字,还有特别意义的变量。Java的关键字对Java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字能用作变量名、...
  • java中50个关键字

    千次阅读 2019-10-31 21:08:27
    关键字和保留字的区别 正确识别java语言的关键字(keyword)和保留字(reserved word)是十分重要的。Java的关键字对java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等。保留...
  • 数仓工具—Hive关键字(11)

    万次阅读 2020-12-26 13:18:55
    关键字是任何一门语言中都要的一些字符,这些字符都有特殊的含义,一般情况下用户能直接使用的,因为编译器对关键字是有特殊处理的。 Hive有一些保留的关键字,我们在执行一些语句时,能将这些关键字作为标识符...
  • js中的关键字和保留字

    千次阅读 2019-02-21 22:50:52
    关键字可用于表示控制语句的开始或结束,或者用于执行特定操作等。关键字也是语言保留的,能用作标识符。以下是ECMAScript的全部关键字: break | do | instanceof | type case | else | new | var catch | ...
  • java中所有关键字

    千次阅读 2018-04-03 16:06:56
    零、关键字一览:abstract continue for new switchassert*** default goto* package synchronizedboolean do if private thisbreak double implements protected throwbyte else import public ...
  • ECMAScript 关键字 ECMA-262 定义了 ECMAScript 支持...根据规定,关键字是保留的,能用作变量名或函数名。 下面是 ECMAScript 关键字的完整列表: break case catch continue default delete do else ...
  • 按照规则,关键字也是语言保留的(注意:也就是说,关键字属于保留字),能用作标识符。以下就是ECMAScript中的全部关键字(带*号上标的是第5版新增的关键字):(按字母顺序排名) break case catch
  • JAVA全部关键字

    千次阅读 多人点赞 2018-04-03 19:52:16
    零、关键字一览:abstract continue for new switchassert*** default goto* package synchronizedboolean do if private thisbreak double implements protected throwbyte else import public ...
  • java中50个关键字以及各自用法大全

    千次阅读 多人点赞 2019-11-10 17:50:38
    关键字和保留字的区别 正确识别java语言的关键字(keyword)和保留字(reserved word)是十分重要的。Java的关键字对java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等。保留字是...
  • java的关键字和保留字

    千次阅读 2018-12-28 22:13:18
    关键字和保留字的区别  正确识别java语言的关键字(keyword)和保留字(reserved word)是十分重要的。Java的关键字对java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等。保留字是为...
  • 2、 class #定义类du的关键字。zhi3、 finally#异常处理使用的关键字,用它可以dao指定始终执行的代码,指定代码在finally里面。4、 return#python 函数返回值 return,函数中一定要有return返回值才是完整的函数。...
  • C++ extern关键字详解

    2013-04-10 16:17:00
    extern   extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。另外,extern也可用来进行...在另外一个文件里用下列语句进行了声明
  • C++常见关键字

    千次阅读 2019-04-22 14:22:08
    可以做左值但是 int* p=&a;是正确的 a是常变量 2.在C++中const修饰的是常量,其值允许改变 int* p=&a;是错误的,C++中编译期将使用该常量的地方替换成了常量的值,普通指针可指向 3.int arr[a] ...
  • c#关键字之详解

    千次阅读 2016-06-25 14:53:08
    关键字是对编译器有特殊意义的预定义的保留标识符。它们能在程序中用作普通标识符,除非在它们前面加上@前缀。    第一部分    一.访问关键字:base,this ~~~~base:访问基类的成员。 用于从派生类中...
  • 嵌入式C语言常用关键字

    千次阅读 2014-11-18 16:52:29
    标准C程序一直由下列部分组成:  1)正文段——CPU执行的机器指令部分,也就是你的程序。一个程序只有一个副本;只读,这是为了防止程序由于意外事故而修改自身指令;  2)初始化数据段(数据段)——在程序中所有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 27,894
精华内容 11,157
关键字:

下列不属于条件语句关键字的是