• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

chongs

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

C语言基础-函数

7.1 函数的定义

7.1.1 函数定义

函数定义同时描述了函数的参数列表和函数体(花括号里的语句)。
如果函数内没有任何语句,那么该函数就叫做存根,他在测试不完整程序十分有用,保持程序在结构上的完整性。
函数声明:K&R声明风格没有参数列表,现在一般不用这种方式。新的声明风格声明的函数又称为函数原型
例子:

/*
	在整型数组中寻找特定的值的存储位置,并返回一个指向该位置的指针。
*/
int *find_int( int key, int array[], int array_len )
{
    int i;
    
    for( i = 0; i < array_len; i++ ){
        if( array[i] == key ) return &array[i];
    }
    return NULL;
}

/*
	在 K&R C 中存在下面函数定义,但是现在尽可能避免使用这种声明。这种声明编译器只会记住函数的返回类型而不会记住参数数量和对应的	 参数类型。上面一种声明都记住。
*/

int *find_int( key, array, array_len )
int key; //形参的参数类型单独列出,在参数列表和函数体之间。
int array;
int array_len;
{
    int i;
    
    for( i = 0; i < array_len; i++ ){
        if( array[i] == key ) return &array[i];
    }
    return NULL;
    
}

7.2 函数声明

7.2.1 函数原型(function prototype)

函数原型例子:int *find_int( int key, int array[], int array_len );
尽量把函数原型都放在一个头文件里面,需要使用时#include该头文件就行。当参数列表没有参数时写上 void

7.3 函数的参数

  1. 其实C语言所有的参数传递都是值传递 ,只是当传递的参数是数组名(其值是一个指针)时,传递给函数的是这个指针的一份副本 。
  2. 数组的下标引用其实是指针间接访问的另一种形式。,所以数组参数传递时行为上就像它们通过传址调用那样。
/*
	对value 进行偶校验
*/
int even_parity( int value, int bits )
{
    int parity = 0;
    while( bits >0 ){
        parity += value & 1; //得到值的位模式中 1 的个数
        value >> 1;
        bits--;
    }
    
    return ( parity % 2 ) == 0;
}

7.4 ADT&&BB

  1. 抽象数据类型(abstract data type),又称黑盒(black box)。
  2. 模块具有功能说明和接口说明,前者说明该模块的任务,后者说明该模块的使用。使用者并不需要知道内部实现的细节。除了接口,用户没有其他方式使用该模块。
  3. 限制对模块的访问是通过合理使用关键字static来实现的,限制对非接口函数的访问。

模块例子:

/*
****头文件 addrlist.h
****地址列表模块:头文件
*/

/*
	模块的函数声明
*/

/*
	数据特征
	各种数据的最大长度,包括末尾的NUL字节和地址的最大数量
*/
#define NAME_LEN 30
#define ADDR_LEN 100
#define PHONE_LEN 11

#define ADDRS_MAX 1000

/*
	接口函数
	给出一个名字,查找对应的地址。
*/

char const *lookup_addr( char const *name );
char const *lookup_phone( char const *name);
/*
****实现函数addrlist.c
****地址列表模块:实现部分
*/

/*
	用于维护一个地址列表的抽象数据类型
*/
#include "addrlist.h"
#include <stdio.h>

/*
	每个地址的三个部分,分别保存于3个数组对应的元素中
	因为用户不能直接访问和模块实现相关的数据,所以用static声明。
*/
static char name[ADDRS_MAX][NAME_LEN];
static char addrs[ADDRS_MAX][ADDR_LEN];
static char phone[ADDRS_MAX][PHONE_LEN];

/*
	在数组中查找名字,返回位置下标,没找到返回-1
*/
static int find_name( char const *name_find )
{
    int entry;
    
    for( entry = 0; entry < ADDRS_MAX; entry++ ){
        if( strcmp( name_find, name[entry]) == 0 ) return entry;
    }
    return -1;
}
/*
	模块访问的两个接口
*/
char const *lookup_addr( char const *name )
{
    int entry = find_name( name );
    if( entry == -1 ) return NULL;
    else			  return addrs[entry];
}

char const *lookup_phone( char const *name)
{
    int entry = find_name( name );
    if( entry == -1 ) return NULL;
    else			  return phone[entry];
}

7.5 递归

C语言运行时堆栈 支持递归函数的实现。
递归需要的两个特性:

  1. 存在限制条件,当符合这个限制条件时递归不再继续。
  2. 每次递归后更接近这个限制条件。

递归函数实例:

/*
	接受一个整型值(无符号),把它转换为字符并打印。
*/
#include <stdio.h>

void binary_to_asccii( unsigned int value )
{
    unsigned int quotient = value / 10;
    if( quotient != 0 ) binary_to_ascii( quotient );
    putchar( value % 10 + '0' );
}

7.5.1 迭代和递归

递归函数调用涉及一些运行时的开销(参数必须压到堆栈中,为局部变量分配内存空间,寄存器的值必须保存等)。一般简单问题通过迭代实现,但是当问题相当复杂,递归的简洁性又可以弥补它的运行时开销。

递归和迭代两个例子:

/*
	用递归的方法实现n的阶乘
*/
long factorial1( int n )
{
    if( n < 0 ) return 1;
    else        return n * factorial1( n - 1 );
}

/*
	迭代实现N的阶乘
*/
long factorial2( int n )
{
    int result = 1;
    
    while( n > 1 ){
        result *= n;
        --n;
    } 
}
/*
	用递归的方法计算第n个斐波那契数
*/
long fibonacci( int n )
{
    if( n <= 2 ) return 1;
    else         return fibonacci( n - 1 ) + fibinacci( n - 2 );
}

/*
	用迭代的方法计算第n个斐波那契数
*/
long fibonacci2( int n )
{
    long result;
    long pre_result;
    long pre_result2;
    
    result = pre_result = 1;
    while( n > 2 ){
        n -= 1;
        pre_result2 = pre_result;
        pre_result = result;
        result = pre_result + pre_result2;
    }
    return result;
}

7.6 可变参数列表

前面函数原型中参数列表一直是以知且固定的,要想一个函数在不同的时候接受不同的参数数量,就要通过宏实现,这些宏定义在<stdarg.h>中。
这个头文件声明了一个类型va_list,和三个宏:va_start,va_arg,va_end。

可变参数列表实例:

/*
	计算指定数量的值的平均值
*/
#include <stdarg.h>

float average( int n_value, ... ) //此处省略号提示这里可能传递数量和类型未确定的参数
{
    va_list   arg_var; //声明了一个va_list类型变量,用于访问参数列表的未确定部分。
    int count;
    float sum = 0;
    /*
    	1、准备访问可变参数
    	2、通过调用va_start来初始化va_list型变量arg_var。
    	3、va_start第一个参数是va_list型变量的名字,第二个参数是省略号前面最后一个有名字的参数。
    	4、这个初始化过程把arg_var变量设置为指向 可变参数部分 的第一个参数。
    */
    va_start( arg_var, n_value );
    
    /*
    	1、添加取自可变参数列表的值。
    	2、为了访问可变参数,需要 va_arg;这个宏接受两个参数:va_list变量arg_var 和 参数列表中下一个参数的类型。
    	3、va_arg获得这个参数的值,并使arg_var指向下一个可变参数。
    */
    for( count = 0; count < n_value; count += 1 ){
        sum += va_arg( arg_var, int );
    }
    
    /*
    	完成处理可变参数
    */
    va_end( arg_var );
    
    return sum / n_value;
}

注意1: 参数列表中至少有一个有名字的参数,如果一个都没有,那么va_start就无法使用。
注意2: va_arg中指定了错误的类型,结果是不可测的。char,short,float类型的值将作为int或double类型的值传递给函数。
注意3: 可变参数部分的参数只能从第一个到最后一个一次访问。

posted on 2024-05-12 21:58  chongss  阅读(9)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3