【IOS 开发学习总结-OC-7.1】C 语言特性——函数

本文深入讲解C语言中的函数概念,包括定义、声明、参数传递机制、递归、数组作为参数等内容,并介绍了内部函数与外部函数的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数

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 修饰 或者不用任何修饰,该函数可以被任何源文件中的函数调用。函数默认都是外部函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值