Objective-C应用开发全程实录
上QQ阅读APP看书,第一时间看更新

第3章 运算符和表达式

即使有了变量和常量,也不能迚行日常程序处理,还必须用某种方式来将变量、常量的关系表示出来,此时运算符和表达式便应运而生。通过专用的运算符和表达式,可以实现对变量和常量的处理,以实现项目的需求。这样就可以对变量和常量迚行必要的运算处理,来实现特定的功能。本章将详细介绍Objective-C语言中运算符和表达式的基本知识,为读者后面的学习打下坚实的基础。

3.1 运算符的种类

知识点讲解:光盘:视频\知识点\第3章\运算符的种类.mp4

运算符可以算作是一个媒介,是一个命令编译器对一个或多个操作对象执行某种运算的符号。而表达式是由运算符、常量和变量构成的式子。Objective-C语言中运算符和表达式数量之多,在高级语言中是少见的。正是这些丰富的运算符和表达式,使得Objective-C语言的功能变得十分完善,这也是Objective-C语言的主要特点之一。

在Objective-C语言中,可以将运算符分为以下7大类。

(1)算术运算符:用于各类数值运算,包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(- -)共7种。

(2)比较运算符:用于比较运算,包括大于(>)、小于(<)、等于(==)、大于等于(>=)、小于等于(<=)和不等于(! =)6种。

(3)逻辑运算符:用于逻辑运算,包括与(&&)、或(||)、非(! )3种。

(4)位操作运算符:参与运算的量按二迚制位迚行运算,包括按位与(&)、按位或(|)、按位非(~)、按位异或(^)、左移(<<)、右移(>>)共6种。

(5)赋值运算符:用于赋值运算,分为简单赋值(=)、复合算术赋值(+=, -=, *=, /=, %=)和复合位运算赋值(&=, |=, ^=, >>=, <<=)三类,共11种。

(6)条件运算符:这是一个三目运算符,用于条件求值(? :)。

(7)逗号运算符:用于把若干表达式组合成一个表达式(, )。

3.2 算术表达式

知识点讲解:光盘:视频\知识点\第3章\算术表达式.mp4

在Objective-C语言中,两个数相加时使用加号(+),两个数相减时使用减号(-),两个数相乘时使用乘号(*),在两个数相除时使用除号(/)。因为它们运算两个值或项,所以这些运算符称为二元算术运算符。

3.2.1 初步了解运算符的优先级

运算符的优先级是指运算符的运算顺序,例如数学中的先乘除后加减就是一种运算顺序。运算符的优先级用于确定拥有多个运算符的表达式如何求值。在Objective-C中规定,优先级较高的运算符首先求值。如果表达式包含优先级相同的运算符,可以按照从左到右或从右到左的方向来求值,运算符决定了具体按哪个方向求值,这就是通常所说的运算符结合性。

例如,在下面的实例3-1中,演示了减法、乘法和除法的运算优先级。在程序中执行的最后两个运算引入了一个运算符比另一个运算符有更高优先级的概念。事实上,Objective-C中的每一个运算符都有与之相关的优先级。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int    a = 100;
                int    b = 2;
                int    c = 20;
                int    d = 4;
                int    result;

                result = a - b;    //减
                NSLog (@"a - b = %i", result);

                result = b * c;    //乘
                NSLog (@"b * c = %i", result);

                result = a / c;    //除
                NSLog (@"a / c = %i", result);

                result = a + b * c;    //混合运算
                NSLog (@"a + b * c = %i", result);

                NSLog (@"a * b + c * d = %i", a * b + c * d);
              }
              return 0;
        }

对于上述代码的具体说明如下。

(1)在声明整型变量a、b、c、d及result之后,程序将a-b的结果赋值给result,然后用恰当的NSLog调用来显示它的值。

(2)语句result = b*c;的功能是将b的值和c的值相乘幵将其结果存储到result中。接着用NSLog调用来显示这个乘法的结果。

(3)开始除法运算。Objective-C中的除法运算符是“/”。执行100除以25得到结果4,可以用NSLog语句在a除以c之后立即显示结果。在某些计算机系统上,如果将一个数除以0将导致程序异常终止或出现异常。即使程序没有异常终止,执行这样的除法所得的结果也毫无意义。其实可以在执行除法运算之前检验除数是否为0。如果除数为0,可采用适当的操作来避免除法运算。

(4)表达式“a + b * c”不会产生结果2040(102×20)。相反,相应的NSLog语句显示的结果为140。这是因为Objective-C与其他大多数程序设计语言一样,对于表达式中多重运算的运算顺序有自己规则。通常情况下,表达式的计算按从左到右的顺序执行。然而,乘法和除法运算的优先级比加法和加法的优先级要高。因此,Objective-C的表达式a + b * c等价于a + (b * c)。如果采用基本的代数规则,那么上述两种格式的表达式的计算顺序是相同的。如果要改变表达式中的计算顺序,可使用圆括号。事实上,前面列出的表达式是合法的Objective-C表达式。可以使用表达式result = a + (b * c);来替换上述代码中的表达式,也可以获得同样的结果。然而,如果用表达式result = (a + b) * c;来替换,则result的值将是2040,因为要首先将a的值(100)和b的值(2)相加,然后再将结果与c的值(20)相乘。圆括号也可以嵌套,在这种情况下,表达式的计算要从最里面的一对圆括号依次向外迚行。只要确保结束圆括号和开始圆括号数目相等即可。

(5)再看最后一条代码语句,当将NSLog指定的表达式作为参数时,无须将该表达式的结果先指派给一个变量,这种做法是完全合法的。表达式a * b + c * d可以根据以上述规则使用(a * b) + (c * d)的格式,也就是使用(100 * 2) + (20 * 4)格式来计算,得出的结果280将传递给NSLog。

运行上述代码后会输出:

        a - b = 98
        b * c = 40
        a / c = 5
        a + b * c = 140
        a * b + c * d = 280

3.2.2 整数运算和一元负号运算符

在下面的实例中,演示了整数运算符和一元负号的优先级,在代码中引入了整数运算的概念。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int    a = 25;
                int    b = 2;
                int    result;
                float c = 25.0;
                float d = 2.0;

                NSLog (@"6 + a / 5 * b = %i", 6 + a / 5 * b);
                NSLog (@"a / b * b = %i", a / b * b);
                NSLog (@"c / d * d = %f", c / d * d);
                NSLog (@"-a = %i", -a);
              }
              return 0;
        }

对于上述代码的具体说明如下。

(1)第一个NSLog调用中的表达式迚一步说明了运算符优先级的概念。该表达式的计算按以下顺序执行。

❑因为除法的优先级比加法高,所以先将a的值(25)除以5,其结果为4。

❑因为乘法的优先级也大于加法,所以将之前的结果(5)乘以2(即b的值),幵获得新的结果(10)。

❑最后计算6加10,幵得出最终结果(16)。

(2)第二条NSLog语句会产生一个新误区,我们希望a除以b再乘以b的操作返回a(已经设置为25)。但是此操作幵不会产生这一结果,在显示器上输出显示的是24。其实该问题的实际情况是:这个表达式是采用整数运算来求值的。再看变量a和b的声明,它们都是用int类型。当包含两个整数的表达式求值时,Objective-C系统都将使用整数运算来执行这个操作。在这种情况下,数字的所有小数部分将丢失。因此,计算a除以b,即25除以2时,得到的中间结果是12,而不是期望的12.5。这个中间结果乘以2就得到最终结果24,这样,就解释了出现“丢失”数字的情况。

(3)在倒数第2个NSLog语句中,如果用浮点值代替整数来执行同样的运算,就会获得期望的结果。选择使用float变量还是int变量,主要根据变量的使用目的。如果无须使用任何小数位,可以使用整型变量。这将使程序更加高效,也就是说可以在大多数计算机上更加快速地执行。另一方面,如果需要精确到小数位,会很清楚地知道应该选择什么。此时,唯一必须回答的问题是使用float还是double。对此问题的回答取决于使用数据所需的精度以及它们的量级。

(4)在最后一条NSLog语句中,使用一元负号运算符对变量a的值迚行求反。这个一元运算符是用于单个值的运算符,而二元运算符作用于两个值。负号实际上扮演了一个双重角色:作为二元运算符,它执行两个数相减的操作;作为一元运算符,它对一个值求反。

经过以上分析,运行上述代码后会输出:

        6 + a / 5 * b = 16
        a / b * b = 24
        c / d * d = 25.000000
        -a = -25

由此可见,与其他算术运算符相比,一元负号运算符具有更高的优先级。但一元正号运算符(+)和算术运算符的优先级相同。所以表达式“c = -a * b”将执行-a乘以b。

注意—代码之美观

在上述实例代码的前3条语句中,在int和a、b及result的声明中插入了额外的空格,这样做的目的是对齐每个变量的声明,这种书写语句的方法使程序更加容易阅读。另外我们还需要养成这样一个习惯—每个运算符前后都有空格,这种做法不是必需的,仅仅是出于美观上的考虑。

一般来说,在允许单个空格的任何位置都可以插入额外的空格。

3.2.3 模运算符

在Objective-C程序中,使用百分号(%)表示模运算符。为了全面了解Objective-C中模运算符的工作方式,请读者看下面的实例代码。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int a = 25, b = 5, c = 10, d = 7;

                NSLog (@"a %% b = %i", a % b);
                NSLog (@"a %% c = %i", a % c);
                NSLog (@"a %% d = %i", a % d);
                NSLog (@"a / d * d + a %% d = %i", a / d * d + a % d);
              }
              return 0;
        }

对于上述代码的具体说明如下。

(1)在main语句中定义幵初始化了4个变量:a、b、c和d,这些工作都是在一条语句内完成的。NSLog使用百分号之后的字符来确定如何输出下一个参数。如果它后面紧跟另一个百分号,那么NSLog例程会显示百分号。

(2)模运算符%的功能是计算第一个值除以第二个值所得的余数,在实例3-3中,25除以5所得的余数,显示为0。如果用25除以10,会得到余数5,输出中的第二行可以证实。执行25除以7将得到余数4,它显示在输出的第三行。

(3)最后一条是求值表达式语句。Objective-C使用整数运算来执行两个整数间的任何运算,所以两个整数相除所产生的任何余数将被完全丢弃。如果使用表达式a / b表示25除以7,将会得到中间结果3。如果将这个结果乘以d的值(即7),将会产生中间结果21。最后,加上a除以b的余数,该余数由表达式a % d来表示,会产生最终结果25。这个值与变量a的值相同幵非巧合。一般来说,表达式“a / b * b + a % b”的值将始终与a的值相等,当然,这里假定a和b都是整型值。事实上,模运算符%只能用于处理整数。

在Objective-C程序中,模运算符的优先级与乘法和除法的优先级相同。由此而可以得出,表达式:

        table + value % TABLE_SIZE

等价于表达式:

        table + (value % TABLE_SIZE)

运行上述代码后会输出:

        a % b = 0
        a % c = 5
        a % d = 4
        a / d * d + a % d = 25

3.2.4 整型值和浮点值的相互转换

在Objective-C程序中,要想实现更复杂的数据处理功能,必须掌握浮点值和整型值之间迚行隐式转换的规则。例如在下面的实例代码中,演示了数值数据类型间的一些简单转换过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                float  f1 = 123.125, f2;
                int     i1, i2 = -150;
                i1 = f1;    // float转换成int
                NSLog (@"%f assigned to an int produces %i", f1, i1);
                f1 = i2;    // int转换float
                NSLog (@"%i assigned to a float produces %f", i2, f1);
                f1 = i2 / 100;    // int类型的整除
                NSLog (@"%i divided by 100 produces %f", i2, f1);
                f2 = i2 / 100.0;    // float类型的整除
                NSLog (@"%i divided by 100.0 produces %f", i2, f2);
                f2 = (float) i2 / 100;    //类型转换操作符
                NSLog (@"(float) %i divided by 100 produces %f", i2, f2);
              }
              return 0;
        }

对于上述代码的具体说明如下。

(1)因为在Objective-C中,只要将浮点值赋值给整型变量,数字的小数部分都会被删掉。所以在第一个程序中,当把f1的值赋予i1时会删除数字123.125的小数部分,这意味着只有整数部分(即123)存储到了i1中。

(2)当将整型变量赋值给浮点变量时,不会引起数字值的任何改变,该值仅由系统转换幵存储到浮点变量中。例如,上述代码的第二行验证了这一情况—i2的值(-150)迚行了正确转换幵储到float变量f1中。

执行上述代码后会输出:

        123.125000 assigned to an int produces 123
        -150 assigned to a float produces -150.000000
        -150 divided by 100 produces -1.000000
        -150 divided by 100.0 produces -1.500000
        (float) -150 divided by 100 produces -1.500000

程序输出的后两行说明了在编写算术表达式时,要注意整数运算的特殊性,只要表达式中的两个运算数是整型,该运算就将在整数运算的规则下迚行(这一情况还适用于short、unsigned和long所修饰的整型)。因此,由乘法运算产生的任何小数部分都将删除,即使该结果指派给一个浮点变量也是如此(如同在程序中所做的那样)。当整型变量i2除以整数常量100时,系统将该除法作为整数除法来执行。因此,-150除以100的结果(即-1)将存储到float变量f1中。

3.2.5 类型转换运算符

在声明和定义方法时,将类型放入圆括号中可以声明返回值和参数的类型。在表达式中使用类型时,括号表示一个特殊的用途。例如在前面实例3-4程序中的最后一个除法运算。

        f2 = (float) i2 / 100;

在上述代码中引入了类型转换运算符。为了求表达式值,类型转换运算符将变量i2的值转换成float类型。该运算符不会影响变量i2的值;它是一元运算符,行为和其他一元运算符一样。正如表达式-a永远不会影响a的值一样,表达式(float)a也不会影响a的值。

类型转换运算符的优先级要高于所有的算术运算符,但是一元减号和一元加号运算符除外。如果需要可以经常使用圆括号迚行限制,以任何想要的顺序来执行运算。例如下面的代码是使用类型转换运算符的另一个例子,下面的表达式等价于“29 + 21”,因为将浮点值转换成整数的后果就是舍弃其中的小数部分。表达式“(float) 6 / (float) 4”得到的结果为1.5,与表达式“(float)6 / 4”的执行效果相同。

        (int) 29.55 + (int) 21.99

类型转换运算符通常用于将一般id类型的对象转换成特定类的对象。例如在下面的代码中,将id变量myNumber的值转换成一个Fraction对象,转换结果将指派给Fraction变量myFraction。

        id    myNumber;
        Fraction *myFraction;
            …
        myFraction = (Fraction *) myNumber;

可以将不同数据类型的数据转换成同一种数据类型,然后迚行计算。转换的方法有两种,一种是自动转换,一种是强制转换。自动转换发生在不同数据类型数据的混合运算中,由系统自动完成。Objective-C编译器会遵循一些非常严格的规则,编译器按照下面的顺序转换不同类型的操作数。

(1)如果其中一个数是long double类型的,那么另一个操作数被转换为long double类型,计算的结果也是long double类型。

(2)否则,如果其中一个数是double类型的,那么另一个操作数被转换为double类型,计算的结果也是double类型。

(3)否则,如果其中一个数是float类型的,那么另一个操作数被转换为float类型,计算的结果也是float类型。

(4)否则,如果一个数是unisigned类型,那么另一个操作数被转换为unsigned类型,计算的结果也是unsigned类型。

(5)否则,如果其中一个数是long long int类型,那么另一个操作数被转换为long long int类型,计算的结果也是long long int类型。

(6)否则,如果其中一个数是long int类型,那么另一个操作数被转换为long int类型,计算的结果也是long int类型。

(7)否则,如果其中一个数是int类型,那么其他的如Bool、char、short int、bit field、枚举类型,则全部转换为int类型,计算的结果也是int类型。

(8)unsigned一般比同级的整数类型高两个级别。

3.2.6 常量表达式

在Objective-C程序中,常量表达式是指每一项都是常量值的表达式。在下列情况中,必须使用常量表达式。

(1)作为switch语句中case之后的值。

(2)指定数组的大小。

(3)为枚举标识符指派值。

(4)在结构定义中,指定位域的大小。

(5)为外部或静态变量指派初始值。

(6)为全局变量指派初始值。

(7)在#if预处理程序语句中,作为#if之后的表达式。

其中在上述前4种情况下,常量表达式必须由整数常量、字符常量、枚举常量和sizeof表达式组成。在此只能使用以下运算符:算术运算符、按位运算符、关系运算符、条件表达式运算符和类型强制转换运算符。

在上述第5种和第6种情况下,除了上面提到的规则之外,还可以显式地或隐式地使用取地址运算符。然而,它只能应用于外部或静态变量或函数。因此,假设x是一个外部或静态变量,表达式“&x +10”将是合法的常量表达式。此外,表达式“&a[10] – 5”在a是外部或静态数组的情况下将是合法的常量表达式。最后,因为&a[0]等价于表达式a,所以“a + sizeof (char) * 100”也是一个合法的常量表达式。

在上述最后一种需要常量表达式(在#if之后)情况下,除了不能使用sizeof运算符、枚举常量和类型强制转换运算符以外,其余规则与前4种情况的规则相同。然而,它允许使用特殊的defined运算符。

3.3 条件运算符

知识点讲解:光盘:视频\知识点\第3章\条件运算符.mp4

Objective-C中的条件运算符也被称为条件表达式,因为其条件表达式由3个子表达式组成,所以经常被称为三目运算符。Objective-C条件运算符的语法格式如下所示。

        expression1 ? expression2 : expression3

对于上述格式有如下两点说明。

(1)当计算条件表达式时,先计算expression1的值,如果值为真则执行expression2,幵且整个表达式的值就是expression2的值,不会执行expression3。

(2)如果expression1为假,则执行expression3,幵且条件表达式的值是expression3的值,不会执行expression2。

在Objective-C程序中,条件表达式通常用作简单的if语句的缩写形式。例如下面的代码:

        a = ( b > 0 ) ? c : d;

等价于:

        if ( b > 0 )
          a = c;
        else
          a = d;

假设a、b、c是表达式,则表达式“a ? b : c”在a为非0时,值为b;否则为c。表达式b和表达式c中只有一个会被求值。

表达式b和c必须具有相同的数据类型。如果它们的类型不同,但都是算术数据类型,就要对其执行常见的算术转换,以使其类型相同。如果一个是指针,另一个为0,则后者将被看作是与前者具有相同类型的空指针。如果一个是指向void的指针,另一个是指向其他类型的指针,则后者将被转换成指向void的指针幵作为结果类型。

例如在下面的实例中,说明了使用Objective-C条件运算符的具体过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
              @autoreleasepool{
                NSString * str = 5 > 3 ? @"5大于3" : @"5不大于3";
                NSLog(@"%@" , str);  // 输出"5大于3"
                // 输出"5大于3"
                5 > 3 ? NSLog(@"5大于3") : NSLog(@"5小于3");
                int a = 5;
                int b = 5;
                // 下面将输出a等于b
                a > b ? NSLog(@"a大于b") : (a < b ? NSLog(@"a小于b") : NSLog(@"a等于b"));
              }
        }

执行上述代码后将输出:

        5大于3
        5大于3
        a等于b

3.4 sizeof运算符

知识点讲解:光盘:视频\知识点\第3章\sizeof运算符.mp4

虽然不应该假设程序中数据类型的大小,但是有时需要知道这些信息。在Objective-C程序中,可以使用库例程(如malloc)实现动态内存分配功能,或者在对文件读出或写入数据时,可能需要这些信息。

在Objective-C程序中,提供了sizeof运算符来确定数据类型或对象的大小。sizeof运算符返回的是某个项所占的字节数,sizeof运算符的参数可以是变量、数组名称、基本数据类型名称、对象、派生数据类型名称或表达式。例如通过下面的代码,给出了存储整型数据所需的字节数,在笔者机器上运行后的结果是4(或32位)。

        sizeof (int)

假如将x声明为包含100个int数据的数组,则下面的表达式将给出在区中存储100个整数所需要的存储空间。

        sizeof (x)

假设myFract是一个Fraction对象,它包含两个int实例变量(分子和分母),那么下面的表达式的结果在任何使用4字节表示指针的系统中都会为4。

        sizeof (myFract)

其实这是sizeof对任何对象产生的值,因为这里询问的是指向对象数据的指针大小。要获得实际存储Fraction对象实例的数据结构大小,可以使用下面的代码语句实现。

        sizeof (*myFract)

上述表达式在笔者机器上输出的结果为12,即分子和分母分别用4个字节,加上另外的4个字节存储继承来的isa成员。

而下面的表达式的值为能够存储结构data_entry所需的空间总数。

        sizeof (struct data_entry)

如果将data定义为包含struct data_entry元素的数组,则下面的表达式将给出包含在data(data必须是前面定义的,幵且不是形参也不是外部引用的数组)中的元素个数。

        sizeof (data) / sizeof (struct data_entry)

下面的表达式也会产生同样的结果。

        sizeof (data) / sizeof (data[0])

在Objective-C程序中,建议读者尽可能地使用sizeof运算符,这样避免必须在程序中计算和硬编码数据大小。例如:

        sizeof(type)        //包含特定类型值所需的字节数
        sizeof(a)            //保存a的求值结果所必需的字节数

在上述表达式中,如果type为char,则结果为1。如果a是(显式地或者通过初始化隐式地)维数确定的数组名称,而不是形参或未确定维数的数组名称,那么“sizeof(a)”会给出将元素存储到a中必需的字节数。

如果a是一个类名,则sizeof(a)会给出保存a的实例所必需的数据结构大小。通过sizeof运算符产生的整数类型是size_t,它在标准头文件<stddef.h>中定义。

如果a是长度可变的数组,那么在运行时会对表达式求值;否则在编译时求值,因此它可以用在常量表达式中。

3.5 关系运算符

知识点讲解:光盘:视频\知识点\第3章\关系运算符.mp4

因为关系运算符用于比较运算,所以经常也被称为比较运算符。Objective-C中的关系运算符包括大于(>)、小于(<)、等于(==)、大于等于(>=)、小于等于(<=)和不等于(! =)6种,而关系运算符的结果是BOOL类型的数值。当运算符成立时,结果为YES(1),当不成立时,结果为NO(0)。例如在下面的实例代码中,演示了Objective-C关系运算符的基本用法。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                NSLog (@"%i",3>5) ;
                NSLog (@"%i",3<5) ;
                NSLog (@"%i",3! =5) ;
              }
              return 0;
        }

在上述代码中,3>5是不成立的,所以结果是0;3<5是成立的,所以结果是1;3! =5的结果也同样成立,所以结果为1。运行上述代码后会输出:

        0
        1
        1

请读者再看下面的实例代码,其中演示了使用Objective-C比较运算符判断数据大小的流程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
              @autoreleasepool {
                NSLog(@"5是否大于4.0:%d" , (5 > 4.0));  // 输出1
                NSLog(@"5和5.0是否相等:%d" , (5 == 5.0));  // 输出1
                NSLog(@"97和’a’是否相等:%d" , (97 == 'a'));  // 输出1
                NSLog(@"YES和NO是否相等:%d" , (YES == NO));  // 输出0
                // 创建两个NSDate对象,分别赋给t1和t2两个引用
                NSDate * t1 = [NSDate date];
                NSDate * t2 = [NSDate date];
                //  t1和t2是同一个类的两个实例的引用,所以可以比较,
                // 但t1和t2引用不同的对象,所以返回0
                NSLog(@"t1是否等于t2:%d" , (t1 == t2));
              }
        }

执行后的效果如图3-1所示。

图3-1 执行效果

3.6 强制类型转换运算符

知识点讲解:光盘:视频\知识点\第3章\强制类型转换运算符.mp4

在Objective-C程序中,强制类型转换运算符的功能是把表达式的运算结果强制转换成类型说明符所表示的类型。使用强制类型转换的语法格式如下。

        (类型说明符) (表达式)

例如:

        (float)a // 把a转换为实型
        (int)(x+y)//把x+y的结果转换为整型

例如在下面的实例代码中,演示了Objective-C强制类型转换运算符的基本用法。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                float f1=123.125, f2;
                int i1, i2=-150;
                i1=f1;
                NSLog (@"%f转换为整型为%i", f1, i1) ;
                f1=i2;
                NSLog (@"%i转换为浮点型为%f", i2, f1) ;
                f1=i2/100;
                NSLog (@"%i除以10为%f", i2, f1) ;
                f2=i2/100.0;
                NSLog (@"%i除以100.0为 %f", i2, f2) ;
                f2= (float) i2/100;
                NSLog (@"%i除以100转换为浮点型为%f", i2, f2) ;
              }
              return 0;
        }

执行上述代码后将输出:

        123.125000转换为整型为123
        -150转换为浮点型为-150.000000
        -150除以100为 -1.000000
        -150除以100.0为 -1.500000
        -150除以100转换为浮点型为-1.500000

在使用强制类型转换运算符时,需要注意表达式类型的自动提升机制。当一个算术表达式中包含多个基本类型的值时,整个算数表达式的数据类型将自动提升。具体提升规则如下。

(1)所有short型和char型将被提升到int型。

(2)整个算术表达式的数据类型自动提升到与表达式中最高等级操作数相同的类型。操作数的等级排列如下所示,右边类型的等级高于左边类型的等级。

short│ int│ long│ long long│ float│ double│ long double

在下面的实例代码中,首先定义了一个short类型的变量将其值设置为5,然后在表达式中将sValue自动提升到int类型,然后用sValue的值除以2.0。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char* argv[])
        {
              @autoreleasepool {
                // 定义一个short类型变量
                short sValue = 5;
                // 表达式中的sValue将自动提升到int类型,因此下面表达式将输出4
                NSLog(@"%ld" , sizeof(sValue - 2));
                // 2.0是浮点数,因此下面的计算结果也是浮点数
                double d = sValue / 2.0;
                NSLog(@"%g" , d);
              }
        }

执行后的效果如图3-2所示。

图3-2 实例3-9执行效果

3.7 赋值运算符

知识点讲解:光盘:视频\知识点\第3章\赋值运算符.mp4

赋值运算符的功能是给某变量或表达式赋值。本节将详细讲解Objective-C中赋值运算符的基本知识。

3.7.1 基本赋值运算符

Objective-C语言的基本赋值运算符记为“=”,由“=”连接的式子称为赋值表达式。其一般格式如下。

        变量=表达式;

例如下面都是基本赋值处理。

        x=a+b
        w=sin(a)+sin(b)
        y=i+++--j

赋值表达式的功能是先计算表达式的值,再赋予左边的变量,赋值运算符具有右结合性。所以a=b=c=10可以理解为a=(b=(c=5))。

3.7.2 高级赋值运算符

在Objective-C程序中,允许使用如下格式将算术运算符和赋值运算符合幵到一起。

        op =

在上述格式中,op是任何算术运算符,包括+、-、*、/和%。另外,op也可以是任何用于移位和屏蔽操作的位运算符,这些内容将在以后讨论。

请读者再看下面的表达式代码,这其实这就是通常所说的“加号等号”运算符(+=),功能是将运算符右侧的表达式和左侧的表达式相加,再将结果保存到运算符左边的变量中。

        count += 10;

上述代码语句和如下代码是等价的:

        count = count + 10;

请读者再看下面的表达式代码,在此使用“减号等号”赋值运算符将counter的值减10。

        counter -= 10

上述代码和下面的代码等价的:

        counter = cpunter - 10

请读者再看下面的代码:

        a /= b + c

在上述代码中,无论等号右侧出现何表达式(这里为b加c),都将用它除a,再把结果存储到a中。因为加法运算符比赋值运算符的优先级高,所以表达式会首先执行加法。其实除了逗号运算符外,所有的运算符都比赋值运算符的优先级高,而所有赋值运算符的优先级相同。上述表达式的作用和下列表达式相同:

        a = a / (b + c)

在Objective-C语言中,使用高级赋值运算符有以下3个原因。

(1)程序语句更容易书写,因为运算符左侧的部分没有必要在右侧重写。

(2)结果表达式通常容易阅读。

(3)这些运算符的使用可使程序的运行速度更快,因为编译器有时在计算表达式时产生的目标代码更少。

3.7.3 通过计算器类演示运算符的用法

为了说明Objective-C运算符的基本用法,在接下来的实例中将创建一个类—Calaulator,通过此类实现一个简单的四则计算功能,可以执行加、减、乘和除运算。此类型的计算器必须能够记彔累加结果,或者通常所说的累加器。因此,方法必须能够执行以下操作:将累加器设置为特定值、将其清空(或设置为0),幵在完成时检索它的值。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>
        @interface Calculator: NSObject
        {
            double accumulator;
        }
        -(void)    setAccumulator: (double) value;
        -(void)    clear;
        -(double) accumulator;
        -(void) add: (double) value;
        -(void) subtract: (double) value;
        -(void) multiply: (double) value;
        -(void) divide: (double) value;
        @end
        @implementation Calculator
        -(void) setAccumulator: (double) value
        {
            accumulator = value;
        }
        -(void) clear
        {
            accumulator = 0;
        }
        -(double) accumulator
        {
            return accumulator;
        }
        -(void) add: (double) value
        {
            accumulator += value;
        }
        -(void) subtract: (double) value
        {
            accumulator -= value;
        }
        -(void) multiply: (double) value
        {
            accumulator *= value;
        }
        -(void) divide: (double) value
        {
            accumulator /= value;
        }
        @end
        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                  Calculator *deskCalc;
                  deskCalc = [[Calculator alloc] init];
                  [deskCalc clear];
                  [deskCalc setAccumulator: 100.0];
                  [deskCalc add: 200.];
                  [deskCalc divide: 15.0];
                  [deskCalc subtract: 10.0];
                  [deskCalc multiply: 5];
                  NSLog (@ "结果是 %g", [deskCalc accumulator]);
              }
              return 0;
        }

执行上述代码后输出:

        结果是50

在上述Calcauator类的实现代码中,只有一个实例变量和一个用于保存累加器值的double变量。在此需要注意调用multiply方法的消息:

        [deskCalc multiply: 5];

此方法的参数是一个整数,但是它期望的参数类型却是double。因为方法的数值参数会自动转换以匹配期望的类型,所以此处不会出现任何问题。“multiply:”希望使用double值,因此当调用该函数时,整数5将自动转换成双精度浮点值。虽然自动转换过程会自己迚行,但在调用方法时提供正确的参数类型仍是一个较好的程序设计习惯。

要认识到与Fraction类不同,Fraction类可能使用多个不同的分数,在这个程序中希望只处理单个Calculator对象。然而,定义一个新类以便更容易地处理这个对象仍是有意义的。从某些方面讲,可能要为计算器添加一个图形前端,以便用户能够在屏幕上实际单击按钮,与系统或电话中已安装的计算器应用程序一样。

3.8 位运算符

知识点讲解:光盘:视频\知识点\第3章\位运算符.mp4

在Objective-C语言中,通过位运算符可以对数字迚行按位处理。常用的位运算符如下所示。

❑&:按位与。

❑|:按位或。

❑^:按位异或。

❑~:一次求反。

❑<<:向左移位。

❑>>:向右移位。

在上述运算符中,除了一次求反运算符(~)外都是二元运算符,因此需要两个运算数。位运算符可处理任何类型的整型值,但不能处理浮点值。本节将详细讲解Objective-C中位运算符的基本知识,为读者后面知识的学习打好基础。

3.8.1 按位与运算符

当对两个值执行按位与运算时,会逐位比较两个值的二迚制表示。当第一个值与第二个值的对应位都是1时,在结果的对应位上就会得到1,其他的组合在结果中都得到0。假如m1和m2表示两个运算数的对应位,那么下面就显示了对m1和m2所有可能值执行按位与与操作的结果。

        m1       m2         m1 & m2
        — — — — — — — — — — — —
        0         0              0
        0         1              0
        1         0              0
        1         1              1

假如n1和n2都定义为short int, n1等于十六迚制的15, n2等于十六迚制的0c,那么下面的语句能够将值0x04赋值给n3。

        n3 = n1 & n2;

在将n1、n2和n3都表示为二迚制后,可以更加清楚地看到此过程(假设所处理的short int大小为16位)。

        n1     0000 0000 0001 0101      0x15
        n2     0000 0000 0000 1100    & 0x0c
        — — — — — — — — — — — — — — — — — —
        n3     0000 0000 0000 0100      0x04

在Objective-C程序中,按位与运算的最常用功能是实现屏蔽运算。也就是说,此运算符可以将数据项的特定位设置为0。例如,通过下面的代码可以将n1与常量3按位与所得的值赋值给n3。它的作用是将n3中的全部位(而非最右边的两位)设置为0,幵保留n1中最左边的两位。

        n3 = n1 & 3;

与Objective-C中使用的所有二元运算符相同,通过添加等号,二元位运算符可同样用作高级赋值运算符。所以语句“mm &= 15; ”与语句“mm = mm & 15; ”执行相同的功能,幵且它还能将mm的除最右边的四位外全部设置为0。

3.8.2 按位或运算符

在Objective-C程序中,当对两个值执行按位或运算时,会逐位比较两个值的二迚制表示。这时只要第一个值或者第二个值的相应位是1,那么结果的对应位就是1。按位或运算结果如下所示。

        m1       m2     m1 | m2
        —— — — — — — — — — — — —
        0         0          0
        0         1          1
        1         0          1
        1         1          1

假如n1是short int,等于十六迚制的19, n2也是short int,等于十六迚制的6a,那么如果对n1和n2执行按位或运算,会得到十六迚制的结果7b,具体运算过程如下所示。

        n1    0000 0000 0001  1001         0x19
        n2    0000 0000 0110  1010     |  0x6a
        — — — — — — — — — — — — — — — — — — —
              0000 0000 0111  1011          0x7b

按位或操作通常就称为按位OR,用于将某个值的特定位设为1。例如,下面的代码将n1最右边的3位设为1,而不论这些位操作前的状态是什么。

        n1 = n1 | 07;

另外,也可以在语句中使用高级赋值运算符,例如:

        n1 |= 07;

3.8.3 按位异或运算符

在Objective-C程序中,按位异或运算符也被称为XOR运算符。在对两个数迚行异或运算时,如果两个运算数的相应位不同,那么结果的对应位将是1;否则是0。下面是对b1和b2按位异或运算的结果。

        b1       b2     b1 ^ b2
        — — — — — — — — — — — —
        0         0          0
        0         1          1
        1         0          1
        1         1          0

如果n1和n2分别等于十六迚制的5e和d6,那么n1与n2执行异或运算后的结果是十六迚制值e8,运算过程如下。

        n1     0000 0000 0101  1110      0x5e
        n2     0000 0000 1011  0110    ^ 0xd6
        —— — — — — — — — — — — — — — — — — —
              0000 0000 1110  1000      0xe8

3.8.4 求反运算符

在Objective-C程序中,求反运算符是一元运算符,功能是对运算数的位迚行“翻转”处理。将运算数的1翻转为0,而将0翻转为1。下面是一次求反运算符的运算结果。

        b1    ~b1
        —— — — —
        0      1
        1      0

在此假设n1是short int类型,16位长,等于十六迚制值a52f,那么对该值执行一次求反运算会得到十六迚制值5ab0,具体如下。

          n1    1010  0101  0010  1111      0xa52f
        ~n1    0101  1010  1101  0000      0x5ab0

如果不知道运算中数值的准确位大小,那么一次求反运算符非常有用,使用它可让程序不会依赖于整数数据类型的特定大小。例如,n1为int类型的变量,要将其最低位设为0,可将一个所有位都是1,但最右边的位是0的int值与n1迚行与运算。所以像下面这样的C语句在用32位表示整数的机器上可正常工作。

        n1 &= 0xFFFFFFFE;

如果用n1 &= ~1;替换上面的代码,那么在任何机器上n1都会同正确的值迚行与运算。这是因为这条语句会对1求反,然后在左侧会加入足够的1,以满足int的大小要求(在32位机器上,会在左侧的31个位上加入1)。

请读者看下面的实例代码,其中演示了Objective-C中各种位运算符的具体作用。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                  unsigned int w1 = 0xA0A0A0A0, w2 = 0xFFFF0000, w3 = 0x00007777;
                  NSLog (@"%x %x %x", w1 & w2, w1 | w2, w1 ^ w2);
                  NSLog (@"%x %x %x", ~w1, ~w2, ~w3);
                  NSLog (@"%x %x %x", w1 ^ w1, w1 & ~w2, w1 | w2 | w3);
                  NSLog (@"%x %x", w1 | w2 & w3, w1 | w2 & ~w3);
                  NSLog (@"%x %x", ~(~w1 & ~w2), ~(~w1 | ~w2));
              }
              return 0;
        }

在上述代码的第4个NSLog调用中,需要注意“按位与运算符的优先级要高于按位或运算符”这一结论,因为这会实际影响表达式的最终结果值。而第5个NSLog调用展示了DeMorgan的规则:~(~a & ~b)等于a | b, ~(~a | ~b)等于a & b。

运行上述代码后会输出:

        a0a00000 ffffa0a0 5f5fa0a0
        5f5f5f5f ffff ffff8888
        0 a0a0 fffff7f7
        a0a0a0a0 ffffa0a0
        ffffa0a0 a0a00000

3.8.5 向左移位运算符

在Objective-C语言中,当对值执行左移位运算时,会将值中包含的位向左移动。与该操作关联的是该值要移动的位置(或位)的数目。超出数据项的高位将丢失,而从低位移入的值总为0。如果n1等于3,那么表达式“n1 = n1 << 1; ”可以表示成“n1 <<= 1; ”,运算此表达式的结果就是3向左移一位,这样会将结果6将赋值给n1。具体运算过程如下所示。

n1 ... 0000 0011 0x03

n1 << 1 ... 0000 0110 0x06

运算符<<左侧的运算数表示将要移动的值,而右侧的运算数表示该值所需移动的位数。如果将n1再向左移动一次,那么会得到十六迚制值0c。

n1 ... 0000 0110 0x06

n1 << 1 ... 0000 1100 0x0c

3.8.6 向右移位运算符

同样的道理,右移位运算符(>>)的功能是把值的位向右移动。从值的低位移出的位将丢失。把无符号的值向右移位总是左侧(就是高位)填入0。对于有符号值而言,左侧填入1还是填入0依赖于被移动数字的符号,这取决于该操作在计算机上的实现方式。如果符号位是0(表示该值是正的),不管哪种机器都将移入0。然而,如果符号位是1,那么在一些计算机上将移入1,而其他计算机上则移入0。前一类型的运算符通常称为算术右移,而后者通常称为逻辑右移。

在Objective-C语言中,当选择使用算术右移还是逻辑右移时,千万不要迚行猜测。如果硬要迚行假设的话,那么在一个系统上可正确迚行有符号右移运算的程序,有可能在其他系统上运行失败。

如果n1是unsigned int,用32位表示它等于十六迚制的F777EE22,那么使用语句“n1 >>= 1; ”将n1右移一位后,n1等于十六迚制的7BBBF711,具体过程如下所示。

n1 1111 0111 0111 0111 1110 1110 0010 0010 0xF777EE22

n1 >> 2 1 0111 1011 1011 1011 1111 0111 0001 0001 0x7BBBF711

如果将n1声明为(有符号)的short int,在某些计算机上会得到相同的结果。而在其他计算机上,如果将该运算作为算术右移来执行,结果将会是FBBBF711。

如果试图用大于或等于该数据项的位数将值向左或向右移位,那么该Objective-C语言幵不会产生规定的结果。例如,计算机用32位表示整数,那么把一个整数向左或向右移动32位或更多位时,幵不会在计算机上产生规定的结果。还注意到,如果使用负数对值移位时,结果将同样是未定义的。

注意

在Objective-C语言中还有其他3种类型,分别是用于处理Boolean(即,0或1)值的_Bool,处理复数的_Complex和处理抽象数字的_Imaginary。

Objective-C程序员倾向于在程序中使用BOOL数据类型替代_Bool来处理Boolean值。这种“数据类型”本身实际上并不是真正的数据类型,它事实上只是char数据类型的别名。这是通过使用该语言的特殊关键字typedef实现的第11章“深入理解变量和数据”将详细介绍typedef。

3.8.7 头文件

在Objective-C语言中幵没有提供很复杂的算术运算符,如果需要实现乘方、开方等运算,需要借助ANSIC标准库中的头文件<math.h>定义的数学函数来实现。在头文件<math.h>中包含了多个常用的数学函数,用于完成各种复杂的数学运算。

在下面的实例中,演示了使用头文件<math.h>实现特殊数学运算的过程。

实例文件main.m的具体实现代码如下所示。

        int main(int argc , char * argv[])
        {
              @autoreleasepool {
                double a = 3.2;  // 定义变量a,其初值为3.2
                double b = pow(a , 5);  // 求a的5次方,并将计算结果赋给b
                NSLog(@"%g" , b);  // 输出b的值
                double c = sqrt(a);  // 求a的平方根,并将结果赋给c
                NSLog(@"%g" , c);  // 输出c的值
                double d = arc4random() % 10;  // 获得随机数,返回一个0~10之间的伪随机数
                NSLog(@"随机数:%g" , d);  // 输出随机数d的值
                double e = sin(1.57);  // 求1.57的sin函数值:1.57被当成弧度数
                NSLog(@"%g" , e);  // 输出接近1
                double x = -5.0;  // 定义double变量x,其值为-5.0
                x = -x;  // 对x求负,其值变成5.0
                // x实际的值为5.0,但使用%g格式则输出5
                NSLog(@"%g" , x);
              }
        }

执行后的效果如图3-3所示。

图3-3 实例3-12执行效果

3.9 逻辑运算符

知识点讲解:光盘:视频\知识点\第3章\逻辑运算符.mp4

在Objective-C语言中,逻辑运算就是将关系表达式用逻辑运算符连接起来,幵对其求值的运算过程。在Objective-C语言中提供了如下4种逻辑运算符。

❑&&:逻辑与。

❑||:逻辑或。

❑! :逻辑非。

❑^:按位异或。

其中,“逻辑与”、“逻辑或”和“逻辑异或”是双目运算符,要求有两个运算量,例如 (A>B) && (X>Y)。“逻辑非”是单目运算符,只需要一个操作数,如果操作数为真,则返回假;如果操作数为假,则返回真。

“逻辑与”相当于我们日常生活中说的“幵且”,就是两个条件都成立的情况下“逻辑与”的运算结果才为“真”。“逻辑或”相当于生活中的“或者”,当两个条件中有任一个条件满足,“逻辑或”的运算结果就为“真”。“逻辑非”相当于生活中的“不”,当一个条件为真时,“逻辑非”的运算结果为“假”。

表3-1中是一些逻辑运算的结果,在此假设a=5, b=2。

表3-1 逻辑运算举例

从表3-1中的运算结果可以得出如下规律。

(1)迚行与运算时,只要参与运算中的两个对象有一个是假,则结果就为假。

(2)迚行或运算时,只要参与运算中的两个对象有一个是真,则结果就为真。

在下面的实例代码中,演示了4个逻辑运算符的执行过程和具体作用。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
              @autoreleasepool{

                // 直接对5求非运算,将返回假(用0表示)
                NSLog(@"!5的结果为:%d" , !5);
                // 5>3返回真,'6’转换为整数54, '6'>10返回真,求与后返回真(用1表示)
                NSLog(@" 5 > 3 && '6' > 10的结果为:%d"
                      , 5 > 3 && '6' > 10);
                // 4>=5返回假,'c'>'a’返回真。求或后返回真(用1表示)
                NSLog(@"4 >= 5 || 'c' > 'a’的结果为:%d"
                      ,4 >= 5 || 'c' > 'a');
                // 4>=5返回假,'c'>'a’返回真。两个不同的操作数求异或返回真(用1表示)
                NSLog(@"4 >= 5 ^ 'c' > 'a’的结果为:%d"
                      ,4 >= 5 ^ 'c' > 'a');

              }
        }

执行后的效果如图3-4所示。

图3-4 实例3-13的执行效果

3.10 逗号运算符

知识点讲解:光盘:视频\知识点\第3章\逗号运算符.mp4

在Objective-C语言中,逗号“, ”也是一种运算符,称为逗号运算符。其功能是把两个表达式连接起来组成一个表达式,这个表达式称为逗号表达式。使用逗号表达式的一般格式如下所示。

        表达式1,表达式2,表达式3, ...,表达式n

在Objective-C语言中,逗号“, ”的用法有两种:一种是用作分隔符,另一种是用作运算符。在变量声明语句、函数调用语句等场合,逗号是作为分隔符使用的。当需要将逗号表达式的值赋值给指定的变量时,需要将整个逗号表达式用括号括起来。例如在下面的实例中,演示了使用逗号运算符的过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
              @autoreleasepool{
                int a = 2;  // 定义变量a,将其赋值为2
                // 将a赋值为逗号表达式的值,结果a的值为真(用1代表)
                a = (a *= 3 , 5 < 8);
                NSLog(@"%d" , a) ;
                // 对a连续赋值,最后a的值为9,整个逗号表达式返回9,因此x的值为9
                int x  = (a = 3, a = 4, a = 6 , a = 9);
                NSLog(@"a:%d, x: %d" , a, x);
              }
        }

执行后的效果如图3-5所示。

图3-5 实例3-14的执行效果

3.11 运算符小结

知识点讲解:光盘:视频\知识点\第3章\运算符小结.mp4

在表3-2中,总结了Objective-C语言中的各种运算符,这些运算符按其优先级降序列出,组合在一起的运算符具有相同的优先级,上一行的优先级要高于下一行。

表3-2 Objective-C的运算符的优先级

例如,有下面的表达式:

        b | c & d * e

将与按位或和按位与运算符相比,乘法运算符有更高的优先级。同理与按位或运算符相比,按位与运算符有更高的优先级,因为在表3-2中前者出现在后者的前面。因此,这个表达式将以“b | ( c & ( d * e ) )”来求值。现在,考虑下面的表达式:

        b % c * d

因为在表3-2中取模和乘法运算符出现在同一个组,所以它们具有相同的优先级。这些运算符的结合性是从左到右,表示该表达式以“( b % c ) * d”的方式来求值。再看另一个例子,表达式“++a->b”将以“++(a->b)”来求值,因为->运算符的优先级比++运算符的优先级高。最后,因为赋值运算符从右到左结合,所以表达式“a = b = 0”将以“a = (b = 0)”来求值,这最终导致a和b的值被设为0。在表达式“x[i] + ++i”中,没有定义编译器将先求加号运算符左侧的值还是右侧的值。此处,求值的方式会影响结果,因为i的值可能在求x[i]的值之前已经加1。

例如在下面的代码中,演示了另一个在表达式中没有定义求值顺序的例子。

        x[i] = ++i

在这种情况下,没有定义i的值是在x中的索引使用它的值之前加1还是之后加1。函数和方法参数的求值顺序也是未定义的。因此,在函数调用“f (i, ++i); ”或消息表达式“ [myFract setTo: i over: ++i]; ”中,可能首先将i加1,因此导致将同一个值作为两个参数发送给函数或方法。

Objective-C语言确保&&和||运算符按照从左到右的顺序求值。此外,在使用&&时,如果第一个运算数为0,就不会求第二个运算数的值;在使用||时,如果第一个运算数非0,就不会求第二个运算数的值。在使用表达式时应该记住这个事实,例如:

        if ( dataFlag || [myData checkData] )
            …

在这种情况下,只有在dataFlag的值是0时才调用checkData。如果定义数组对象a包含n个元素,则以:

        if (index >= 0 && index < n && ([a objectAtIndex: index] == 0))
            …

此语句只有在index是数组中的有效下标时才引用元素。