13.3 结构
除了数组之外,Objective-C语言还提供了另一种组合元素的工具。结构就是这种工具,它构成了本节讨论的基础。
假设要在程序中存储日期(比如说7/18/03),它也许用于程序输出的开头或是出于计算需要。存储日期的自然方法就是:将一个月份赋给名为month的整型变量,日期赋给整型变量day,年份赋给整型变量year。那么语句
int month=7,day=18,year=2009;
可以实现该任务,这是完全可接受的方式。但是,如果你的程序还需要存储很多日期,该怎么办?如果可以使用某种方式将这三个变量组合起来就更好了。
在Objective-C语言中,可以定义一个名为date的结构,它包含三个分别代表年、月、日的成分。这种定义的语法相当直观,如下所示:
struct date
{
int month;
int day;
int year;
};
date结构恰好定义了三个整型元素,分别名为month、day和year。在某种意义上说,date的定义指出了Objective-C语言中的新类型,这是由于,随后就可以如下将变量声明为struct date类型了:
struct date today;
还可以如下另外定义一个名为purchaseDate的同类型变量:
struct date purchaseDate;
或者,可以直接在同一行中包含两个定义,如下面一行所示:
struct date today, purchaseDate;
不同于int、float或char型变量,处理结构变量时需要特殊语法。通过指明变量名称,在之后加上句点(称为点运算符)来访问结构成员。举个例子,要将变量today的day值设置为21,可以编写如下语句:
today.day=21;
注意变量名、句点和成员名称之间不允许出现空格。
看一下,我们使用同样的运算符来调用对象的属性。回忆一下,我们可以编写语句
myRect.width=12;
来调用Rectangle对象的setter方法(称为setWidth),给它传递参数值12。这里的意思很明了:编译器确定点运算符左边的是一个结构还是一个对象,然后进行相应的处理。
返回到struct date示例,要将today中的year赋值为2010,可以使用如下表达式
today.year=2010;
最后,要检测month的值是不是等于12,可以使用如下表达式:
if(today.month==12)
next_month=1;
代码清单13-6将前面的讨论合成一个实际的程序。
代码清单13-6
import<Foundation/Foundation.h>
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
struct date
{
int month;
int day;
int year;
};
struct date today;
today.month=9;
today.day=25;
today.year=2009;
NSLog(@“Todays date is%i/%i/%.2i.”,today.month,
today.day, today.year%100);
[pool drain];
return 0;
}
代码清单13-6输出
Todays date is 9/25/09.
main函数的第一条语句定义了名为date的结构,它包含三个整型成员,分别是month、day和year。在第二条语句中,变量today声明为struct date类型。所以,第一条语句只是简单地向Objective-C编译器说明了date结构的外观,并没有在计算机中分配存储空间。第二条语句定义了一个struct date类型的变量,所以导致内存中分配了空间,以便存储结构变量today的三个整型成员。
在赋值完成后,通过调用适当的NSLog语句来显示包含在结构中的值。Today.year除以100的余数是在传递给NSLog函数之前计算的,这样可以使年份只显示09。NSLog中的%.2i格式符号指明了最少显示两位字符,从而强制显示年份开头的0。
谈到表达式的求值时,结构成员遵循的法则和Objective-C语言中普通变量一样。这样,将整型的结构成员除以整数的除法和整数除法一样,如下所示:
century=today.year/100+1;
假设要编写一个简单的程序,它接收今天的日期作为输入数据,并向用户显示明天的日期。第一眼看上去,这似乎是一项非常简单的任务。可以让用户输入今天的日期,然后通过一系列语句计算出明天的日期,如下:
tomorrow.month=today.month;
tomorrow.day=today.day+1;
tomorrow.year=today.year;
当然,对于大多数日期来讲,上面的语句都可以得出正确结果,但是不能正确处理以下两种情况:
·如果今天的日期是一个月的最后一天;
·如果今天的日期是一年的最后一天(即,今天的日期是12月31日)。
确定今天的日期是不是一个月最后一天的一种简便方法是设置对应于每月天数的整型数组。在数组中对查找特定月份就可以得到当月的天数(参见代码清单13-7)。
代码清单13-7
//Program to determine tomorrows date
import<Foundation/Foundation.h>
struct date
{
int month;
int day;
int year;
};
//Function to calculate tomorrows date
struct date dateUpdate(struct date today)
{
struct date tomorrow;
int numberOfDays(struct date d);
if(today.day!=numberOfDays(today))
{
tomorrow.day=today.day+1;
tomorrow.month=today.month;
tomorrow.year=today.year;
}
else if(today.month==12)//end of year
{
tomorrow.day=1;
tomorrow.month=1;
tomorrow.year=today.year+1;
}
else
{
//end of month
tomorrow.day=1;
tomorrow.month=today.month+1;
tomorrow.year=today.year;
}
return(tomorrow);
}
//Function to find the number of days in a month
int numberOfDays(struct date d)
{
int answer;
BOOL isLeapYear(struct date d);
int daysPerMonth[12]=
{31,28,31,30,31,30,31,31,30,31,30,31};
if(isLeapYear(d)==YES&&d.month==2)
answer=29;
else
answer=daysPerMonth[d.month-1];
return(answer);
}
//Function to determine if its a leap year
BOOL isLeapYear(struct date d)
{
if((d.year%4==0&&d.year%100!=0)||
d.year%400==0)
return YES;
else
return NO;
}
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
struct date dateUpdate(struct date today);
struct date thisDay, nextDay;
NSLog(@“Enter todays date(mm dd yyyy):);
scanf(“i%i%i”,&thisDay.month,&thisDay.day,
&thisDay.year);
nextDay=dateUpdate(thisDay);
NSLog(@“Tomorrows date is%i/%i/%.2i.”,nextDay.month,
nextDay.day, nextDay.year%100);
[pool drain];
return 0;
}
代码清单13-7输出
Enter todays date(mm dd yyyy):
2 28 2012
Tomorrows date is 2/29/12.
代码清单13-7输出(重新运行)
Enter todays date(mm dd yyyy):
10 2 2009
Tomorrows date is 10/3/09.
代码清单13-7输出(重新运行)
Enter todays date(mm dd yyyy):
12 31 2010
Tomorrows date is 1/1/10.
即使没有用到该程序中的任何类,仍然导入了Foundation.h这个文件,因为你想要使用BOOL类型,并定义YES和NO。它们定义在Foundation.h文件中。
注意date结构的定义最先出现并且在所有函数之外。这是因为结构定义的行为与变量定义非常类似:如果在特定函数中定义结构,那么只有这个函数知道它的存在。这是一个局部结构定义。如果将结构定义在函数之外,那么该定义是全局的。使用全局结构定义,该程序中随后定义的任何变量(不论是在函数之内或之外)都可以声明为这种结构类型。多个文件共用的结构定义都集中放在一个头文件中,然后想要使用这些结构的文件导入这个头文件。
在main例程中,如下声明
struct date dateUpdate(struct date today);
告知编译器函数dateUpdate使用date结构作为它的参数,并且返回date结构的值。这里并不需要这个声明,因为编译器已经在文件中知道了实际的函数定义。然而,这仍然是好的编程习惯。比如,如果将函数定义和main函数分别放在不同的源文件中,那么在这种情况下,声明将是必要的。
和普通的变量一样(与数组不同),函数对于结构参数的任何更改都不会影响原结构。这些变化只影响到调用该函数时所产生的结构副本。
将日期输入并存储在date结构变量thisDay中之后,如下调用函数dateUpdate:
nextDay=dateUpdate(thisDay);
这个语句调用函数dateUpdate,同时传递date结构变量thisDay的值。在dateUpdate function中,原型声明
int numberOfDays(struct date d);
告诉Objective-C编译器函数numberOfDays返回一个整型值,并且带有一个date结构类型的参数。
语句
if(today.day!=numberOfDays(today))
指明today结构将作为参数传递给函数numberOfDays。在这个函数中,必须进行适当声明来告知系统参数的类型是一个结构,如下所示:
int numberOfDays(struct date d)
函数numberOfDays首先确定该日期是否为闰年并且是否为二月。前一判断是通过调用另一个名为isLeapYear的函数实现的。
函数isLeapYear非常直观。它简单地测试作为参数传递来的date结构中所包含的年份信息,如果是闰年则返回YES,反之返回NO。
一定要理解代码清单13-7中函数调用的层次结构:main函数调用dateUpdate函数,dateUpdate又调用numberOfDays函数,numberOfDays调用函数isLeapYear。
13.3.1 结构的初始化
初始化结构与初始化数组类似,将元素列在一对花括号之中,元素之间以逗号相隔。要将date结构的变量初始化为2011年7月2日,可以使用下面的语句:
struct date today={7,2,2011};
和数组的初始化一样,列出的值可以少于结构中包含的元素个数。所以,语句
struct date today={7};
将today.month初始化为7,但是没有给today.day或者today.year赋初值。在这种情况下,它们的默认初始值是未定义的(undefined)。
在初始化列表中,用下面的表示方式
.member=value
可以以任意顺序初始化结构中指定的成员,如下所示:
struct date today={.month=7,.day=2,.year=2011};
和
struct date today={.year=2011};
最后一个语句只将该结构中的year元素设置为2011。你知道,其余两个元素是未定义的。