第三章:C语言特性

创建C函数
#include 
1、create OS X中的Command Line Tool 并且选择C生成main.c文件
2、在Xcode5中创建OS X中C and C++中的C File
在Xcode6中创建OS X中Source中的C File
#import 
1、在Xcode5中create OS X中的Command Line Tool 并且选择Foundation生成main.m文件
  在Xcode6中create OS X中的Command Line Tool 并且选择Objection-C生成main.m文件
2、在Xcode6中创建OS X中Source中的Objection-C File

一、函数

1、定义函数

定义函数:先定义函数,在调用函数—>函数定义位于函数调用的前面
定义函数的语法格式:函数返回值类型 函数名(形参列表)

{
     //由零条到多条可执行性语句组成的函数;
}

例如:

void initBoard(){}

  • (1)函数返回值类型:返回值类型可以是Objective_C语言中允许的任何数据类型,包括基本类型和指针类型等。如果声明,函数体中应该有一个有效的return语句,起返回一个变量或者一个表达式,但是这个变量或者表达式的类型必须与此处声明的类型匹配。
  • (2)函数名:从语法角度看,函数名只要是一个合法的标识符即可。另外:第一个单词字母小写,后面的每个单词首字母大写,其他字母全部小写,单词与单词之间无须使用任何分隔符。
  • (3)形参列表:用于定义该函数可以接受的参数,形参列表由零组到多组“参数类型 参数名”组合而成,多组参数之间以英文字符(,)隔开,形参类型和形参名之间以英文空格隔开。

    注意:
    (1)如果声明函数时指定的返回值类型与return语句实际返回的数据类型不匹配,此时将以声明函数时指定的返回值类型为准,系统将把return实际返回的值转换成声明函数时指定的类型。
    (2)如果被调用函数没有retuen语句,该函数并不是真正没有返回值,它只是返回一个不确定的,不一定有用的值。因此,如果我们希望一个函数没有返回值,一定要明确的用void来声明没有返回值。
    

2、函数声明

函数声明:函数声明来指定该函数的形参类型和返回值类型—>被调用函数位于后面或者函数定义在另一个源文件中;
函数声明形式:

  • (1)、只声明函数的返回值类型、函数名、形参列表的形参类型,不保留形参名;
    例如:

    void printMsg(NSString *,int);
    
  • (2)、声明函数的返回值类型、函数名、完整的形参类型,包括形参名;
    例如:

    void printMsg(NSString *msg,int loopNum);
    

提示:对于第二种形式,实际上就是函数定义部分做的函数体(花括号及花括号里的所有代码)部分舍弃剩下的部分,并在后面添加一个分号。

  • 3、函数的参数传递机制:如果声明函数时包含了形参声明,则调用函数时必须给这些形参指定参数值,调用函数时,实际传给形参的参数值也称为实参。

    • (1)、值传递:Objective-C里函数的参数传递方式,将实际参数值的副本(复制品)传入函数内,而参数本身不会受到任何影响。
    • (2)、值传递实质:当系统开始执行函数时,系统为形参执行初始化,就是把实参变量的值赋给函数的形参变量,函数里操作的并不是实际的实参变量。

4、递归函数

递归函数:函数体调用自身。
例如:

int fn(int n){
    if (n == 0) {
        return 0;
    }else if(n == 1){
        return 1;
    }else{
        return 2 * fn(n - 1) + fn(n - 2);
    }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%d",fn(10));
    }
    return 0;
}

5、数组作为函数参数

  • (1)、声明函数时必须指定数组类型的形参,此时数组类型的形参既可指定长度,也可以不指定长度,如果声明函数时形参是多维数组,则只有最左边的维数可以省略;
  • (2)、当数组作为函数的参数时,声明函数的形参类型与调用函数时传入的实参类型必须保持一致。
    例如:

    double avg(int array[]){}
    

6、内部函数和外部函数

内部函数:定义函数时使用static修饰,改函数只能被当前源文件中的其他函数所调用。
优点:具有更好地内聚性,他可以保证函数只有在该源文件中起作用,从而避免了多个源文件总重名函数的冲突问题。
外部函数:定义函数时使用extern修饰,或不适用任何修饰符修饰。它可以被任何源文件中的函数调用。
例如

在PrintFunctionLib.m文件中

#import 
void printRect(int height,int width){
    //控制打印height行
    for (int i = 0; i < height; i ++) {
        //控制每行打印width个星号
        for (int j = 0; j < width; j ++) {
            printf("*");
        }
        printf("\n");
    }
}
//定义外部函数
void printTriangle(int height){
    //控制打印height行
    for (int i = 0; i < height; i ++) {
        //控制打印height - 1 - i个空格
        for (int j = 0; j < height - 1 - i; j ++) {
            printf(" ");
        }
        //控制打印2*i + 1个星号
        for (int j = 0; j < 2 * i + 1; j ++) {
            printf("*");
        }
        printf("\n");
    }
}

在main.m文件中

#import 
//声明两个外部函数
void printRect(int,int);
void printTriangle(int);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //调用两个函数
        printRect(5, 10);
        printTriangle(7);
    }
    return 0;
}

二、局部变量与全局变量

局部变量:在函数内部定义的变量,只在该函数内部有效,只在该函数内部才能使用它们,在函数外部无法访问这些变量。
全局变量:定义在函数外部的变量,可以被源文件中的所有函数访问,作用域:从定义该函数的位置开始,到该源程序结束。

1、局部变量

根据定义形式不同,分三种

  • 第一种:形参:在定义函数签名时定义的变量,形参的作用域在整个函数内有效。
  • 第二种:函数局部变量:在函数体内定义的局部变量,作用域:从定义该变量的地方生效,到该函数结束时失效。
  • 第三种:代码块局部变量:在代码块中定义的局部变量,作用域:从点你定义该变量的地方生效,到该代码块结束时失效。

2、全局变量

注意:
(1)在一个函数内部,如果全局变量和局部变量同名,局部变量将会覆盖全局变量,这就意味着在该函数内部,全局变量将会失效。
(2)任何函数对全局变量所做的修改,其它函数都会受到影响,其它函数读取全局变量的值时,也是被修改后的值。

3、外部全局变量和内部全局变量

外部全局变量:C语言中允许访问其他源程序中定义的全局变量,允许被其他源程序访问的全局变量称为外部全局变量。
内部全局变量:使用static修饰的全局变量被称为内部全局变量。
注意:在使用外部全局变量的时候,需要用extern声明
例如:

在PrintFunctionLib.m文件中

#import 
//定义全局变量
int count = 0;
//定义一个函数
void change(){
    NSLog(@"count的值为:%d",count);
    count = 20;
}

在main.m文件中

#import 
//声明外部函数
void change();
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //声明外部局部变量
        extern int count;
        change();//调用外部函数
        NSLog(@"%d",count);
        count = 50;//对外部变量赋值
        change();
    }
    return 0;
}

输出:

    count的值为:0
    20
    count的值为:50

4、动态存储与静态存储

(1)、C程序运行内存来说:分为3部分:程序区、静态存储区和动态存储区。

  • 动态存储:程序在运行期间根据需要动态分配内存的存储方式。
  • 静态存储:程序在运行开始就分配固定内存的存储方式。
  • 静态存储区:静态存储区的变量会在程序运行开始时分配内存,知道程序运行结束才释放内存,在程序运行的过程中,静态存储区的变量总是占据固定的内存。
    • 全局变量:无论是内部全局变量,还是外部全局变量,它们都被保存在静态存储区内。
    • static修饰的局部变量。
  • 动态存储区:动态存储区的变量所在的存储空间是动态分配的,当程序多次调用同一个函数时,该函数内的局部变量(非static修饰的变量)每次都会动态分配内存空间,每次函数结束就会自动释放这些内存,这种分配和释放都是动态的。
    • 函数的形参变量;
    • 非static的局部变量;
    • 函数执行的现场数据以及返回地址等。

(2)、C语言支持的几种存储类型:

  • auto:指定该变量采用自动存储机制,局部变量默认采用这种机制;
  • static:指定将局部变量存放到静态存储区,这样该变量所占的空间将会一直保存,知道程序退出;
  • register:指定将该变量存放到寄存器内;
  • extern:用于声明外部变量。
  • register:该关键字指定将变量的值存入寄存器—无须将变量存入内存,从而避免CPU频繁的读写内存,因此可以对那些频繁使用的局部变量使用register修饰。

注意:
(1)计算机中寄存器的数量是有限的,不能定义任意多个寄存器变量。
(2)不同的系统对register局部变量的处理是不同的,许多系统并不会真正的把register局部变量存入寄存器,他们依然把register局部变量当成auto局部变量处理。

三、预处理

    特征:
    (1)、预处理命令必须以#开头。
    (2)、预处理命令通常位于程序开头部分。

1、使用#define、#undef执行宏定义

#define 的作用就是为字符串起一个名字,且一般使用所有字母大写的形式。

例如:

#define PI 3.1415926
#define TEO_PI PI * 2
注意:
(1)、宏定义并不是C语句,因此不用在宏名称与字符串之间使用=进行赋值,而且宏定义也无需使用分号结束;
(2)、宏定义不是变量,他甚至不是常量,因此不能尝试对宏名称进行赋值;
(3)、编译器处理宏定义只是进行“查找、替换”——将所有出现宏名字的地方替换成该宏对应的字符串,要保证宏定义是正确地。

PS:如果希望提前结束宏定义则使用下面语句:

    #undef PI

2、带参数的宏定义

定义参数宏的语法:#define 宏名称(参数列表) 字符串

#define PI 3.1415926
#define GIRTH(r) PI * 2 * r//直接使用前面已有的PI来定义新的宏

此时需注意:#define GIRTH(r) PI * 2 * (r)

3、使用#ifdef、#ifndef、#else、#endif执行条件编译

C语言支持两组条件编译指令
第一组条件编译指令:#ifdef、#ifndef、#else、#endif
语法格式:

  • (1)、
#ifdef 宏名称
               //任意语句
        #endif

表示:如果定义指定宏,则执行#ifdef和#endif之间的语句

  • (2)、
#ifdef 宏名称
    //任意语句
#else
    //任意语句
#endif

表示:如果定义了指定宏,则执行#ifdef和#else之间的语句;否则执行#else和#endif之间的语句。

  • (3)、#ifndef 宏名称
//任意语句
#endif

表示:如果没有定义指定宏,则执行#ifdef和#endif之间的语句

  • (4)、
#ifndef 宏名称
    //任意语句
#else
    //任意语句
#endif

上面表示:如果没有定义了指定宏,则执行#ifdef和#else之间的语句;否则执行#else和#endif之间的语句。

    作用:
    (1)、源程序可以不必做任何修改就可自动适应不同的设备;
    (2)、控制是否输出调试信息。

例如:
第一种:

#ifdef DEBUG
    # define DLog(...) NSLog(__VA_ARGS__)
#else
    # define DLog(...) /* */
#endif
#define ALog(...) NSLog(__VA_ARGS__)//定义输出

第二种:

#ifndef __OPTIMIZE__
# define debug 1
# define NSLog(...) NSLog(__VA_ARGS__)
#else
# define debug 0
# define NSLog(...) {}
#endif

4、使用#if、#elif、#else、#endif执行条件编译

第二组条件编译指令:#if、#elif、#else、#endif
语法格式:

#if 表达式
          //任意语句
#elif 表达式
          //任意语句
          //可以有零个或者多个#elif语句
          //最后的#else也可以省略
#else
          //任意语句
#endif

注意:表达式中的条件要么是常量表达式,要么就是基于已有宏的表达式。

5、#include与#import

C语言:提供了#include来导入其他源程序;
OC语言:提供了#import来导入其他源程序;

    #include作用:将指定的源代码插入到当前源代码中。
    #import:
    (1)、一般情况下导入自定义的源程序都是使用双引号来包含源文件,这告诉预处理程序将会到一个或多个路径下(通常首先搜索当前文件所在路径,也可通过Xcode的项目设置来设置预处理程序的搜索路径)搜索指定的源文件。
    例如:
    #import "RootViewController.h"
    (2)、如果将要包含的源程序放在<和>之间,用于告诉大家系统只在特定的“系统”头文件路径中搜索被导入的文件,而不再当前路径搜索。
    #import 

总之:
如果导入用户自定义的源文件,在#import后使用双引号来包含源文件的文件名;
如果要导入系统的源文件,则在#import后使用<和>来包含源文件的文件名。

四、指针

1、取得变量方式:

  • (1)、通过该变量来访问它的值;//直接访问
  • (2)、到该变量的存储位置去取他的值。//间接访问

2、指针变量及其基本用法

  • 格式:类型* 变量名;

    注意:*代表一个指针变量,整个语法代表定义一个指向类型变量的指针变量,指针变量不能保存普通的值,只能保存指针(也就是变量的对象);
    
  • 基本运算符:(*(&a))== a;

    • (1)、&:取地址运算符。这是一个单目运算符,后面通常紧跟一个变量,该运算符用于读取该变量地址的保存地址。
    • (2)、*:取变量运算符。这是一个单目运算符,后面通常金粉一个指针变量,改运算符用于读取该指针变量所指的变量。
  • 规则:
    • (1)、定义指针变量时,必须用*来标识定义一个指针变量;
    • (2)、C语言是强类型的语言,所有的指针变量必须先声明,后使用,而且一旦声明了指针变量的类型,那么这种类型的变量只能指向对应类型的变量。比如:int* p;语句声明的变量p只能指向int型变量。

3、指针变量作为函数参数

注意: 
(1)、如果使用函数参数是普通类型来的变量,函数中对变量所做的任何修改都不会影响变量本身。
(2)、如果程序需要在函数中对变量本身的值进行修改,则需要将变量的指针(即地址)传入函数,并在函数中对该指针所做的变量进行修改,这样既可改变原来本身的值。

提示:将数组变啦ign作为参数传入函数时,在函数中对数组元素所做的修改也会影响原数组元素的值,这是因为数组变量本身也是指针。

五、指针和数组

1、指向数组的指针变量

首地址:数组中的第一个元素的地址被称为数组的首地址,数组的首地址会被当成数组的地址。C语言规定,数组变量的本质就是一个指针常量,保存了指向第一个数组元素的指针。
获取数组首地址的方式:

  • (1)、int* p = &arr[0];将第一个数组元素的地址赋值给指针变量p;
  • (2)、int* p = arr; 将数组变量保存的地址赋值给指针变量p;

2、指针运算

指针变量存在的几种赋值方式:

  • (1)、p = &a; 将一个已有变量的内存地址赋给指针变量p;
  • (2)、p = &arr[0] 将某个数组元素的内存地址赋给指针变量p;
  • (3)、p = arr; 将arr数组的首地址赋给指针变量p;
  • (4)、p = pt; 将指针变量pt中保存的地址赋值给指针变量p;
    除了赋值以外,还支持以下运算
  • (5)、指针变量加(或减)一个整数:当指针变量加或减n时,代表将该指针的地址加或减n*变量大小个字节。举例来说:对于int* p;类型的变量,假如当前p变量中保存的地址为0x00010000,p+3代表地址为0x0001000C(假设int型变量所占的内存空间为4字节);对于char* pt;类型的变量,假如当前pt变量中保存的地址为0x00020001,pt+5代表的地址为0x00010006(假设char型变量所占的内存空间为1字节);
  • (6)、当两个指针变量指向同一个数组的元素时,两个指针变量可以相减:两个指针变量相减返回两个指针所指数组之间元素的个数。如果两个指针不指向同一个数组,那么两个指针变量相减没有任何意义。
  • (7)、当两个指针变量指向同一个数组的元素时,两个指针变量可以比较大小:指向前面的数组元素的指针小于指向后面的数组元素的指针。需要指出的是,如果两个指针不知想同一个数组,那么两个指针变量比较大小没有任何意义。
  • (8)、假如arr是数组,那么arr+i代表第i+1个元素的地址,因此arr+i与&a[i]是等价的。
  • (9)、假如arr是数组,那么(arr+i)代表&(&arr[i]),即(arr+i)与arr[i]是等价的。

3、数组变量作为函数参数

将数组变量作为参数的本质:将指针变量作为参数。
当把数组变量作为参数传入函数时,只是把该数组变量的值(指向数组的指针)传入函数,并不是将数组本身传入函数,因此,传入函数的数组变量依然指向所有的数组。在函数中对数组变量所指的数组所做的修改将会影响原有的数组的元素。

4、指向多维数组的指针变量

注意:
(1)、对已一维数组,arr[i]与*(arr + i)等价;
(2)、对于二维数组,arr[i] + 2与*(arr + i) + 2等价;

对于二维数组float arr[3][4];有如下定义

  • (1)、arr:代表二维数组名,数组的首地址,地址值为0x00010001;
  • (2)、arr[0]、arr、(arr + 0):代表二维数组的第一个数组的数组名,即二维数组的第一个数组元素的首地址,地址值为0x00010001;
  • (3)、arr +1、&arr[1]:代表二维数组的第二个数组的首地址,地址值为0x000100F1;
  • (4)、arr[1]、*(arr + 1):代表二维数组的第二个数组的数组名,也就是二维数组的第二个数组的首地址,地址值为0x000100F1
  • (5)、arr[1] + 2、*(arr + 1)+ 2、&arr[1][2]:代表二维数组的第二个数组的第三个元素的首地址,地址值为0x000100F9;
  • (6)、(arr[1] + 2)、(*(arr + 1)+ 2)、arr[1][2]:代表二维数组的第二个数组的第三个元素,此时就是该数组元素的值,不在是地址值。
    在这里有一个不好理解的地方:arr和*arr等价 都是数组的首地址。

六、字符串与指针

1、使用字符指针标识字符串

char* str = "I Love iOS";
NSLog(@"%s",str);//I Love iOS
str += 7;
NSLog(@"%s",str);//iOS

2、字符指针作为函数参数

void copyString(char* to,char* from){
    while (*from) {
        *to++ = *from++;
    }
    *to = '\0';
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        char* str = "www.cratit.org";
        char dest[100];
        copyString(dest,str);
        NSLog(@"%s",dest);//www.cratit.org
        copyString(dest,"Objective-C isFuncy!");
        NSLog(@"%s",dest);//Objective-C isFuncy!
    }
    return 0;
}
注意:
(1)、字符数组底层真正存放了所有的字符,每个字符对应一个数组元素;而字符指针指向字符数组时只存放字符数组的首地址。
(2)、字符数组只能定义时赋值。
例如:
正确地:    
    char str[100] = "www.fkjava.org";
错误的:
    char str[100];
    str = "www.fkjava.org";
另外:字符指针完全可以重复赋值
    char* str = "www.fkjava.org”;
也支持
    char* str;
    str = "www.fkjava.org"
 (3)、定义字符数组时,程序会为每个数组分配内存空间,但定义字符指针变量时,程序只是定义一个指针变量,该指针所指向的内存单元是不确定的。

七、函数与指针

1、用函数指针变调用函数

使用函数指针的步骤如下:

  • (1)、定义函数指针变量。
    格式如下:

    函数返回值类型(* 指针变量名)();
    
  • (2)、将任何已有的函数入口赋值给函数指针变量
    例如:fnpt = avg;

    注意:
    1)C语言允许将任何已有的函数赋值给函数指针变量,因此,同一个函数指针变量在不同的时间可指向不同的函数;
    2)为函数指针变量赋值时,只要给出函数名即可,无须在函数后使用括号,也无须传入参数。注意是讲函数入口赋给指针变量,而不是调用函数后将返回的结果赋给函数指针变量。
    
  • (3)、使用函数指针变量来调用函数。
    使用函数指针变量调用函数的语法格式为:

    (*函数指针变量)(参数);
    

注意:必须先用()把*函数指针变量括起来,用于保证获取该指针变量所指的函数,然后执行函数调用。

//最大值
int max(int* data,int len){
    int max = *data;
    for (int* p = data; p < data + len; p++) {
        if (*p > max) {
            max = *p;
        }
    }
    return max;
}
//平均值
int avg(int* data,int len){
    int sum = 0;
    for (int* p = data; p < data + len; p ++) {
        sum += *p;
    }
    return sum/len;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int data[] = {20,12,8,36,24};
      //定义函数指针变量
        int(*fnpt)() = max;
        NSLog(@"最大值:%d",(*fnpt)(data,5));
        fnpt = avg;
        NSLog(@"最大值:%d",(*fnpt)(data,5));
    }
    return 0;
}

2、用函数指针变量作为函数参数

void map(int* data,int len,int(*fn)()){
    //采用指针遍历data数组的元素
    for (int* p = data; p < data + len; p ++) {
        //调用fn函数(fn函数是动态传入的)
        printf("%d\n",(*fn)(*p));
    }
}
int noChanage(int val){
    return val;
}
//定义一个计算平方的函数
int square(int val){
    return val*val;
}
//定义一个计算m³的函数
int cube(int val){
    return val*val*val;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int data[] = {20,12,8,36,24};
        //下面程序代码3次调用map()函数,每次调用时传入不同的函数
        map(data,5,noChanage);
        NSLog(@"计算数组元素的平方");
        map(data,5,square);
        NSLog(@"计算数组元素的立方");
        map(data,5,cube);
    }
    return 0;
}

3、返回指针的函数

为了保证函数返回的指针是有效的,有两种方式:

  • (1)、如果函数的指针指向被调用函数中声明的局部变量,该局部变量应该使用static修饰。
  • (2)、让函数返回的指针指向暂时不会被释放的数据,如指向main()函数中的变量,只有等main()函数执行完成时,main函数中的变量才会释放,因此,在main()函数结束之前,函数返回的指针是安全的。

八、指针数组和指向指针的指针

优先级:()>[]>*
数组指针是指向数组首元素的地址的指针,其本质为指针。
指针数组是数组元素为指针的数组,其本质为数组。

1、指针数组与main()函数形参

声明指针数组语法:

类型* 数组变量[长度];由于[]运算符的优先级比*优先级高,因此,数组变量先与后面的[]结合成数组形式,前面的“类型*”则用于指定多个数组元素类型,且声明每个数组元素都是指针。

切记:不要写成这个样子
类型 (变量名)[长度];(变量名)先形成一个整体,代表一个指针变量,该指针变量指向一维数组,因此表示定义一个指向一维数组的指针变量。

void sort(char* names[],int n){
    char* tmp;
    for (int i = 0; i < n - 1; i ++) {
        //用第i个字符串 依次与后面的每个字符串相比
        for (int j = i + 1; j < n; j ++) {
            //如果names[i]的字符串大于names[j]的字符串,交换他们
            //就可以保证第i个位置的字符串总比后面的所有字符串小
            if (strcmp(names[i], names[j])) {
                tmp = names[i];
                names[i] = names[j];
                names[j] = tmp;
            }
        }
    }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int nums = 5;
        char* strs[] = {"Objective-C","iOS","Java","Ajax","Android"};
        sort(strs,nums);//对字符串排序
        for (int i = 0; i < nums; i ++) {
            NSLog(@"%s",strs[i]);
        }
    }
    return 0;
}

2、指向指针变量的指针

声明指向指针变量的指针语法格式:

类型** 变量名;

九、结构体

1、定义结构体

定义结构体:struct是个关键字,成员列表中可以定义任意多个成员变量这些成员变量即可是基本类型,也可以是结构体类型。

struct 结构体类型名
{
     //成员列表
};

例如:

struct point
{
    int x;
    int y;
};
  • (1)、先定义结构体类型,在定义结构体变量
    格式如下:

    struct 结构体名 变量名;
    

例如:

struct point p1;

注意:每次定义变量时都需要使用struct关键字,比较繁琐,现有两种方法解决:

第一种:使用#define预编译指令

#define POINT struct point
POINT p1;

第二种:使用typedef为已有的结构体类型定义新名称。

  • (2)、同时定义结构体类型和结构体变量
    语法格式如下:

    struct 结构体名
    {
         //成员列表;
    }结构体变量1,结构体变量2...;
    

    例如:

struct point{
    int x;
    int y;
}p1,p2;

2、typedef语句:为已有的数据类型另起一个名称。

语法格式:

typedef 已有类型 新名称;
typedef int Counter;
Counter i,j;

定义结构体

struct CGSize {
  CGFloat width;
  CGFloat height;
};
typedef struct CGSize CGSize;

3、初始化结构体变量

  • (1)、
struct point{
    int x;
    int y;
}p1 = {12,21};
  • (2)、
struct point p2 = {12,321};
  • (3)、
p2.x = 13;
p2.y = 31;

4、结构体数组

struct point points[] = { {12,321},
    {321,443},
    {1,3},
};
points[1].x = 123;
points[1].y = 123;

5、枚举

如下

enum DAY
{
    MON=1,
    TUE,
    WED,
    THU,
    FRI,
    SAT,
    SUN
};
  • (1) 枚举型是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开。
  • (2) DAY是一个标识符,可以看成这个集合的名字,是一个可选项,即是可有可无的项。
  • (3) 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。
  • (4) 可以人为设定枚举成员的值,从而自定义某个范围内的整数。
  • (5) 枚举型是预处理指令#define的替代。
  • (6) 类型定义以分号;结束。

十、块(Block)

1、块的基本语法

定义快的语法格式

^[块返回值类型] (形参类型1 形参1,…)
{
     //块执行体
}

定义块与定义函数的区别:

  • (1)、定义块必须以^开头;
  • (2)、定义块得返回值类型可以省略,而且经常都会省略块的返回值类型;
  • (3)、定义块无须指定名字;
  • (4)、如果块没有返回值,块无须带参数,通常建议使用void作为占位符。

注意:如果多次调用已经定义的块,那么程序应该将该块赋给一个块变量,定义块变量的语法格式如下:

块返回值类型(^块变量名)(形参类型1,…);

例如:

int  (^printStr)(int , int ) = ^int(int a,int b)
{
     NSLog(@"我正在学习Objective_C的块");
     return a + b;
};
NSLog(@"sum = %d",printStr(3,4));//sum = 7

2、块与局部变量

块可以访问程序中局部变量的值,当块访问局部变量的值时,不允许修改局部变量的值;

例如

int num = 20;
void  (^printStr)() = ^void(void)
{
    //num = 30;不能对num修改
    NSLog(@"num = %d",num);//num = 20;
};
num = 45;
printStr();

输出20的原因:当程序使用块访问局部变量时,系统在定义块时就会把局部变量的值保存在块中,而不是等到执行时采取访问、获取局部变量的值。
修改如下:

__block int num = 20;
void  (^printStr)() = ^void(void)
{
    NSLog(@"num = %d",num);//num = 45;
    num = 30;//允许修改局部变量
    NSLog(@"num = %d",num);//num = 30;
};
num = 45;
printStr();
NSLog(@"num = %d",num);//num = 30;

3、直接使用块作为参数

4、使用typedef定义块变量类型

用途:
(1)、复用块变量类型,使用块变量类型可以重复定义多个块变量;
(2)、使用块变量类型定义函数参数这样即可定义带块参数的函数。

语法格式如下:

typedef 块返回值类型(^块变量类型)(形参类型1,...);

例如:

typedef void(^FinishBlock)(NSData* data);
typedef void(^FailedBlock)();
@property (nonatomic, copy) FinishBlock finishBlock;
@property (nonatomic, copy) FailedBlock failedBlock;
self.finishBlock(_mData);
self.failedBlock();

再例如:

typedef void (^FKPrintBlock) (NSString *);
typedef void (^FKPrintBlock) (NSString *);
FKPrintBlock print = ^(NSString *info)
{
    NSLog(@"info = %@",info);//info = C;
};
print(@"C");

你可能感兴趣的:(疯狂iOS(上))