08--函数(1)

函数入门 

在C语言中,函数意味着功能模块。一个典型的C语言程序,就是由一个个的功能模块拼接起来的整体。也因为如此,C语言被称为模块化语言。

对于函数的使用者,可以简单地将函数理解为一个黑箱,使用者只管按照规定给黑箱一些输入,就会得到一些输出,而不必理会黑箱内部的运行细节。

日常使用的电视机可以被理解为一个典型的黑箱,它有一些公开的接口提供给使用者操作,比如开关、音量、频道等,使用者不需要理会其内部电路,更不需要管电视机的工作原理,只需按照规定的接口操作接口得到结果。

电视机 

对于函数的设计者,最重要的工作是封装,封装意味着对外提供服务并隐藏细节。对于一个封装良好的函数而言,其对外提供服务的接口应当是简洁的,内部功能应当是明确的(模块功能要尽可能独立)。

我们自己在设计函数接口的时候应当尽可能做到 高内聚 、低耦合:

 函数的定义

  • 函数头:函数对外的公开接口
    1. 函数名称:命名规则与跟变量一致,一般取与函数实际功能相符合的、顾名思义的名称。
    2. 参数列表:即黑箱的输入数据列表,一个函数可有一个或多个参数,也可以不需要参数。
    3. 返回类型:即黑箱的输出数据类型,一个函数可不返回数据,但最多只能返回一个数据。
  • 函数体 { }:函数功能的内部实现
  • 语法说明:
    返回类型 函数名称(参数1, 参数2, ……)
    {
        函数体
    }

    如何自定义函数(自己设计函数):

  • 在设计函数之前应该先想清楚该函数具体实现什么功能。
  • 想清楚该函数设计出来之后应该入使用。
  • 进入函数的具体设计阶段
    1. 先思考好如何实现(实现该功能的每一个步骤)
    2. 按照实现想好的步骤填充代码

实例:

#include <stdio.h>
#include <stdbool.h>

int DisplayInt ( int Num )
{ // 函数体的开始

    printf("这里是整型输出函数...\n" );
    
    printf("Num:%d\n" , Num );

    return 123456 ;
} // 函数体的结束

bool DisplayChar( char * msg )
{

    printf("这里是字符串输出函数...\n" );
    
    printf("msg:%s\n" , msg );

    return true ;
}

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

    // 专门用于输出一个整形数据的函数
    int RetVal = DisplayInt( 123 );
    printf("函数DisplayInt返回的数据是:%d\n" , RetVal);


    // 专门用于输出一个字符串数据的函数
    bool B = DisplayChar( "Hello GZ2301" );
    if (B)
    {
        printf("字符串输出函数执行成功。。。\n");
    }
    else{
        printf("字符串输出函数执行失败。。。\n");

    }


    return 0;
}

语法汇总:
   a.当函数的参数列表为 void 时,表示该函数不需要任何参数。
   b.当函数的返回类型为 void 时,表示该函数不返回任何数据。
   c.当函数的返回类型为 void  * 时,表示该函数返回的是一个指针,只不过该指针所指向的类型是不确定的(通用类型地址)。
   d.关键字 return 表示退出函数。
          ①若函数头中规定有返回数据类型,则 return 需携带一个类型与之匹配的数据;
          ②若函数头中规定返回类型为 void,则 return 不需携带参数。

实参与形参

  • 概念:
    • 函数调用中的参数,被称为实参(实际传递的参数),即 arguments

int max = Max ( a , b );

  • 函数定义中的参数,被称为形参(形式参数),即 parameters
  • int Max ( int x  , int y ) 
    {   
    ..... 
     }

  • 实参与形参的关系:
- 实参于形参的 类型 和 个数 以及 顺序 必须一一对应。
- 形参的值由实参初始化。
- 形参与实参位于不同的内存区域(各自在各自函数的栈空间中),彼此独立。

  • 函数示例1:
    • 参数输入两个整型参数
    • 函数功能中计算得到两个整型中较大的值
    • 函数返回相对大的数据
    • 函数内输出他们的和
int Max ( int x  , int y )
{

    // 计算得到较大值
    int m = x > y ? x : y ;

    // 输出较大值
    printf("较大值:%d\n" , m );

    // 输出它们的和
    printf("它们的和:%d\n" , x + y);

    // 返回较大值
    return m ;
}

函数的参数传递

参数值传递: 函数在调用的时候实参是一个数据的值

void  func( int k )
{

    printf("k:%d\n" , k );
    k+= 100 ;
    printf("k:%d\n" , k );


    return ;
}


/主函数片段
int a = 123 ;
func(a);  // 参数的值传递

参数地址传递:函数在调用的时候实参传递的是一个数据的地址

void func1( int *p )  // 该函数的形参是一个指针
{

    printf("*p:%d\n" , *p );
    *p += 500 ;
    printf("*p:%d\n" , *p );

    return ;
}


//主函数的代码片段//
int a = 123 ;
func1(&a); // 调用函数func1的时候传递的是 a 地址

数组作为形参:

数组在形参中出现时,该数组会被退化成一个指针。

 expected ‘int *’ but argument is of type ‘int (*)[5]’  
   28 | void func3( int arr [5] , int Len )  // 编译器善意提醒,形参中的  int arr [5] 实际上一个 ‘int *’
  • 【拓展】函数示例2:
    • 参数输入两个浮点数
    • 函数内把交换两个浮点数
void Swap( float * f1 , float * f2 )
{

    float tmp = *f1 ;

    *f1 = *f2  ;
    *f2 = tmp ; 

    return ;
}

函数指针:

概念: 这是一个指针,该指针指向的数据类型是一个函数的类型 (该指针变量中存储的是一个函数的入口地址)

语法: 函数的返回值类型 (*p) ( 函数的参数列表 ) ;

示例:

#include <stdio.h>

double func( int a , char * b , float f )
{

    double d = a + *b + f ;

    return  d ;
}


int main(int argc, char const *argv[])
{
    // 定义了一个函数指针 , 该指针直指向的类型是 double   ( int a , char * b , float f )
    double (*p)( int a , char * b , float f ); 
    double (*p1)( int , char * , float );  // 一般在定义函数指针时,参数列表中的变量名会省略掉
    p = func ; // 对 函数指针进行赋值 (把函数func 的入口地址写入到指针变量 p 中)


    char c = 'V';
    
    // 使用函数名来调用该函数
    double d = func( 123 , &c , 345.123  );
    printf("d:%lf\n" , d );

    // 如何通过指针p来访问函数
    double d1 = p( 123 , &c , 345.123  );
    printf("d1:%lf\n" , d1 );

    return 0;
}

指针函数:

概念: 这是一个函数,该函数的返回值是一个指针

语法: 类型 * 函数名 ( 参数列表)

int * MemInit(int Len)
{

    int * p = malloc(Len);
    if (p == NULL )
    {
        printf("malloc error");
        return NULL ;
    }
    else {
        printf("malloc succeed!");
    }

    /*
        假设这里是对内存的初始化操作
        ........
    */
    
    return p ;
}

数组指针函数:

        概念:这是一个函数,该函数的返回值是一个数组类型的指针

typedef     int (* RetTyp) [5] ; // 给类型 int (*) [5] 取了一个别名叫 RetTyp

RetTyp MemInit( void )
{

    // 申请一个堆空间的数组
    int (* p) [5] ;
    p = calloc( 5 , sizeof (int) );


    return p ;
}

函数指针数组:

概念: 他是一个数组, 该数组中存放的是 指针 , 这些指针指向的是 函数的入口地址

语法: 函数的返回值类型 * (数组名[5]) ( 参数列表 )

示例:

int func1( float f )
{

    printf("我是函数1 ,f的值:%f\n"  , f );
    
    return 1 ;
}
.....
.......
.........
int main(int argc, char const *argv[])
{

    // 定义了一个数组arr, 该数组有5个元素, 每一个元素都是函数类型地址   
    int  (* arr[5]) ( float )  = { 
            func1 , 
            func2 , 
            func3 , 
            func4 , 
            func5 
        };
    
    for (int i = 0; i < 5; i++)
    {
        // arr[0] = func1 
        int ret_val = arr[i] ( 123.456+i ) ;
        printf("调用arr[%d],他的返回值:%d\n" , i , ret_val);
    }
        
    return 0;
}

函数调用的流程

        函数调用时,进程的上下文会切换到被调函数,当被调函数执行完毕之后再切换回去。

 局部变量与栈内存

  • 局部变量概念:凡是被一对花括号包含的变量,称为局部变量
  • 局部变量特点:
    • 某一函数内部的局部变量,存储在该函数特定的栈内存中
    • 局部变量只能在该函数内可见(作用域),在该函数外部不可见
    • 当该函数退出后,局部变量所占的内存立即被系统回收,因此局部变量也称为临时变量(不要把局部变量的地址进行返回)
    • 函数的形参虽然不被花括号所包含,但依然属于该函数的局部变量
  • 栈内存特点:
    • 每当一个函数被调用时,系统将自动分配一段栈内存给该函数,用于存放其局部变量
    • 每当一个函数退出时,系统将自动回收其栈内存
    • 系统为函数分配栈内存时,遵循从上(高地址)往下(低地址)分配的原则
  • 技术要点:
    • 栈内存相对而言是比较小的,不适合用来分配尺寸太大的变量。
    • return 之后不可再访问函数的局部变量,因此返回一个局部变量的地址通常是错误的。

static修饰的局部变量

概念: 当使用static 修改局部变量时, 是的该局部变量的存储空间从栈空间转移到数据段中(静态数据)

实例:

void func( void )
{
    int a = 0 ;
    
    // 使用 static修饰的 变量b  是的它成为一个静态数据, 它的存储期与程序保持一致,不会因为函数退出而回收
    // 因此存储在静态变量b 中的数据是不会消失的
    // 下一行初始化的语句只会被执行 一次 
    static int b = 0 ;

    printf("a:%d b:%d\n" , a++ , b++ );

}
  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值