函数
C是结构化的语言,函数就是 C 的最小单位。C 与 OC 程序入口都是 main()函数,该函数是系统能识别的特殊函数。其他函数地位平等,能相互调用。
定义函数
定义函数的语法格式:
函数返回值类型 函数名(形参列表)
{
//0到多条可执行语句
}
格式说明:
- 返回值类型:可以 OC 允许的任何数据类型。如果声明了返回值类型,函数体内应有一个有效 的 return 语句,用于返回与返回值类型匹的变量或表达式(注意:如果返回的数据类型不匹配,系统将 return 的实际值转换为声明函数指定的类型)。如果没有返回值,返回值类型要设置为 void, 否则,系统默认该函数的返回值类型为 int。
- 函数名:从语法和程序可读性上看,函数名应该由一个或多个有意义的单词连接而成。第一个单词字母小写,后面的字母首字母大写其他小写,单词之间无需使用间隔符。objective-c建议函数名添加公司前缀, 比如 NSLog。
- 形参列表:用于定义函数可以接收的参数。由0到多组”参数类型 形参名”组合而成。多组参数间用英文逗号隔开。定义之后,调用该函数时必须传入对应的值——谁调用,谁负责传值。
示例代码:
// 定义一个函数,声明一个形参,返回值为NSString *类型
NSString * sayHi(NSString * name)
{
NSLog(@"===正在执行sayHi函数===");
return [NSString stringWithFormat:@"%@,您好!" , name];
}
函数声明
如果被调用的函数在调用者的后面,或者在其他的源文件中,要使用函数声明,告诉编译器,该函数是存在的,并告诉编译器该函数的形参列表及返回值类型。
函数声明有2种形式:
#import <Foundation/Foundation.h>
// 声明函数,可用以下两种方式的任意其中之一
// void printMsg(NSString * msg , int loopNum);
//只声明函数的返回值类型,函数名,形参列表的形参类型,不保留形参名
void printMsg(NSString * , int);
int main(int argc , char * argv[])
{
@autoreleasepool{
printMsg(@"fkjava.org" , 5);
}
}
//声明函数的返回值类型,函数名,完整的形参列表,包括形参名
void printMsg(NSString * msg , int loopNum)
{
for (int i = 0 ; i < loopNum ; i++)
{
NSLog(@"%@" , msg);
}
}
函数的参数传递机制
如果声明函数中包括形参声明,调用函数时必须给形参指定的参数值。调用函数时实际传给形参的参数,称之为实参。
那么问题来了,objective-c 的实参是如何传入函数的呢?
objective-c里函数的参数传递方式只有一种:值传递。也就是,将实际参数值的副本传入函数内,而实际参数本身不受影响。
对于 指针类型的变量, objective-c也是采用的值传递方式。示例代码:
#import <Foundation/Foundation.h>
// 定义一个DataWrap类
@interface DataWrap : NSObject
// 为DataWrap定义a、b两个属性
@property int a;
@property int b;
@end
@implementation DataWrap
// 合成a、b两个属性
@synthesize a, b;
@end
void swap(DataWrap* dw)
{
// 下面三行代码实现dw的a、b两个属性值交换。
// 定义一个临时变量来保存dw对象的a属性的值
int tmp = dw.a;
// 把dw对象的b属性值赋给a属性
dw.a = dw.b;
// 把临时变量tmp的值赋给dw对象的b属性
dw.b = tmp;
NSLog(@"swap函数里,属性a的值是:%d;属性b的值是:%d"
, dw.a , dw.b);
// 把dw直接赋为null,让它不再指向任何有效地址。
dw = nil;
}
int main(int argc , char * argv[])
{
@autoreleasepool{
DataWrap* dw = [[DataWrap alloc] init];
dw.a = 6;
dw.b = 9;
swap(dw);
NSLog(@"交换结束后,属性a的值是:%d;属性b的值是:%d"
, dw.a , dw.b);
}
}
执行程序后,运行结果:
swap函数里,属性a的值是:9;属性b的值是:6
交换结束后,属性a的值是:9;属性b的值是:6
对这部分如果不了解,可以参见李刚著的《疯狂 ios 第一卷》P101-103。
递归函数
一个函数体内调用它自身时,被称为函数递归。
是一种无需循环控制的隐式循环。示例代码:
#import <Foundation/Foundation.h>
int fn(int n)
{
if (n == 0)
{
return 1;
}
else if (n == 1)
{
return 4;
}
else
{
// 函数中调用它自身,就是函数递归
return 2 * fn(n - 1) + fn(n - 2);
}
}
int main(int argc , char * argv[])
{
@autoreleasepool{
//输出fn(10)的结果
NSLog(@"%d" , fn(10));
}
}
递归函数最重要的规定:递归一定要向已知方向递归。
如果递归不向已知方向递归,就会变成无穷递归,类似于死循环。
递归很有用。例如:我们希望遍历某个路径下的所有文件,但这个路径下的文件的深度未知,可以使用递归的方法解决。系统可以定义一个函数,该函数接收一个文件路径作为参数,该参数可遍历出当前路径下所有的文件和文件路径——该函数中再次调用该函数本身来处理该路径下所有的文件路径。
数组作为函数参数
示例代码:
#import <Foundation/Foundation.h>
// 定义一个函数,该函数的形参为两个int型变量
int big(int x , int y)
{
// 如果x>y返回1;如果x<y返回-1,如果x==y,返回0
return x > y ? 1 : (x < y ? -1 : 0);
}
int main(int argc , char * argv[])
{
@autoreleasepool{
int a[10] , b[10];
// 采用循环读入10个数值作为第一个数组元素的值
NSLog(@"输入第一个数组的10个元素:");
for(int i = 0 ; i < 10 ; i++)
{
scanf("%d" , &a[i]);
}
// 采用循环读入10个数值作为第二个数组元素的值
NSLog(@"输入第二个数组的10个元素:");
for(int i = 0 ; i < 10 ; i++)
{
scanf("%d" , &b[i]);
}
int aBigCount = 0;
int bBigCount = 0;
int equalsCount = 0;
// 采用循环依次比较a、b两个数组的元素
// 并累计他们的比较结果
for(int i= 0 ; i < 10 ; i++)
{
NSLog(@"%d , %d" , a[i], b[i]);
if(big(a[i] , b[i]) == 1)
{
aBigCount ++;
}
else if(big(a[i] , b[i]) == -1)
{
bBigCount ++;
}
else
{
equalsCount ++;
}
}
NSLog(@"a数组元素更大的次数%d, b数组元素更大的次数为:%d , 相等次数为:%d"
, aBigCount , bBigCount, equalsCount);
NSString * result = aBigCount > bBigCount ?
@"a数组更大": (aBigCount < bBigCount ? @"b数组更大" : @"两个数组相等");
NSLog(@"%@" , result);
}
}
数组本身就相当于普通变量。上面程序中调用 big() 函数时传入2组数组元素,此时数组元素仅当做普通变量即可。
C 语言还允许将数组变量(本质上是一个指针)传入函数,当使用数组变量作为参数时,要注意如下2点:
1.声明函数时必须指定数组类型的形参,此时数组类型的形参既可以指定长度,也可不指定长度。如果声明函数时,形参是多维数组,则只有最左边的维数可以省略。
2.当数组作为函数的参数时,声明函数的形参类型与调用函数时传入的实参类型必须保持一致。
示例代码:从键盘获取10个考试成绩,计算平均成绩。
#import <Foundation/Foundation.h>
// 定义一个函数,该函数的形参为数组
double avg(int array[10])
{
int sum = 0;
// 采用循环经数组所有元素累加起来
for(int i = 0 ; i < 10 ; i++)
{
sum += array[i];
}
return sum / 10.0;
}
int main(int argc , char * argv[])
{
@autoreleasepool{
int scores[10];
// 采用循环读入10个数值作为scores数组元素的值
NSLog(@"请输入10个成绩:");
for(int i = 0 ; i < 10 ; i++)
{
scanf("%d" , &scores[i]);
}
// 直接将数组变量作为参数传入函数
NSLog(@"平均成绩为:%g" , avg(scores));
}
}
上面的程序定义函数时声明指定了数组形参的长度,但大部分定义数组时并不指定长度,这样可以更加灵活。
与传入普通变量不同的是:传入数组变量作为参数的实质就是传入一个指针,该指针指向数组的首地址。因此函数中对数组元素的改变会对数组本身有影响。如下对数组进行冒泡排序的示例代码:
#import <Foundation/Foundation.h>
// 定义一个函数,该函数返回NSString
void bubbleSort(int nums[] , unsigned long len)
{
// 控制本轮循环是否有发生过交换
// 如果没有发生交换,那么说明该数组已经处于有序状态,可以提前结束排序
BOOL hasSwap = YES;
for (int i = 0; i < len && hasSwap; i++)
{
// 将hasSwap设为NO
hasSwap = NO;
for (int j = 0 ; j < len - 1 - i ; j++)
{
// 如果nums[j]大于nums[j + 1],交换它们
if(nums[j] > nums[j + 1])
{
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
// 本轮循环发生过交换,将hasSwap设为YES
hasSwap = YES;
}
}
}
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
// 随便给出一个整数数组
int nums[] = {12 , 2, 23, 15 , -20 , 14};
// 计算数组的长度
int len = sizeof(nums) / sizeof(nums[0]);
// 调用函数对数组排序
bubbleSort(nums , len);
// 采用遍历,输出数组元素
for(int i = 0 ; i < len ; i++)
{
printf("%d," , nums[i]);
}
// 输出换行
printf("\n");
}
}
bubbleSort() 函数对数组元素所做的修改会对 main() 函数中定义的数组产生影响。
内部函数与外部函数
函数本质上是全局性的,因为一个函数总可以被其他函数调用。根据函数是否可以被其他源文件调用,分为外部函数和内部函数。
内部函数:
定义函数时用 static 修饰,该函数只能被当前源文件中的其他函数所调用。
外部函数:
定义函数时用 extern 修饰 或者不用任何修饰,该函数可以被任何源文件中的函数调用。函数默认都是外部函数。