C和指针读书笔记(第七章)

1. 真函数是从表达式内部调用的,它必须返回一个值用于表达式的求值。这类函数的return语句必须包含一个表达式。通常表达式的类型就是函数声明的返回类型,只有当编译器可以通过寻常算术转换把表达式的类型转换为正确的类型时,才允许返回类型与函数声明的返回类型不同的表达式


2.

  • 传递给函数的标量参数是传值调用的。
  • 传递给函数的数组参数在行为上就像它们是通过传址调用的那样。

3. 交换调用程序中的两个整数

  • 方法一:本程序希望修改程序传递的参数,但是函数是无效的,因为参数是通过传值调用的,函数所交换的值是实际参数的一份拷贝,原先的参数值并未进行交换。
void swap(int x, int y)
{
    int temp;

    temp = x;
    x = y;
    y = temp;
}
  • 方法二:通过向函数传递指向希望修改的变量的指针,接着函数对话i真使用间接访问操作,修改需要修改的变量。
void swap(int *x, int *y)
{
    int temp;

    temp = *x;
    *x = *y;
    *x = temp;
}

4. C语言strcmp()函数:比较字符串(区分大小写),若参数s1 和s2 字符串相同则返回0。s1 若大于s2 则返回大于0 的值。s1 若小于s2 则返回小于0 的值。


5. 递归函数在每次递归调用后必须越来越接近某种限制条件,当递归函数符合这个限制条件时,它便不再调用自身。
递归函数两个特性:

  • 存在限制条件
  • 每次递归调用后越来越近这个限制条件

示例代码:

#include<stdio.h>

void bin(unsigned int  value);
int main(void)
{

    unsigned int a = 987234;  //随机输入一个数字
    bin(a);  //调用函数

}
void bin(unsigned int  value)
{
    unsigned int quo;

    quo = value/10;
    if(quo != 0){  //判断条件,也是递归调用的终结
        bin(quo);  //递归调用
    }
    putchar(value%10 + '0');
    printf("\n");
}

6. 当函数被调用时,它的变量的空间时创建于运行时堆栈上的,以前调用的函数的变量仍保留在堆栈上,但它们被新函数的变量所掩盖,因此是不能被访问的。当函数递归调用自身时,情况一致,每进行一次新的调用,都将创建一批变量,它们将掩盖递归函数前一次调用所创建的变量


7.
递归和迭代的程序演示:

  • 计算阶乘
#include<stdio.h>

long factorial_recursion(int n);
long factorial_iteration(int n);
int main(void)
{

    int abe = 10;  //指定阶乘的值
    long bresu, cresu;
    bresu = factorial_recursion(abe);  
    cresu = factorial_iteration(abe);
    printf("%ld, %ld", bresu, cresu);
    return 0;

}

long factorial_recursion(int n)  //递归
{
    if( n <= 0 ){
        return 1;
    }
    else{
            return n * factorial_recursion(n - 1);  递归调用
    }
}

long factorial_iteration(int n)  //迭代
{
    int result = 1;
    while(n > 1){  
        result *= n;
        n -= 1;
    }
    return result;
}
  • 计算斐波那契数列:此时递归产生的冗余将会非常大,因为每次递归调用都会触发额外的两个递归调用,冗余计算的数量增加速度非常快,例如在计算Fib(10)时,Fib(3)被计算了21次,而在计算Fib(30)时,Fib(3)被计算了317811次,产生的冗余计算非常大。而在迭代方法中使用简单的循环代替递归可以使效率提高几十万倍。本程序中迭代运行了1.402s,而递归运行了0.317s。
#include<stdio.h>

long Fib_recursion(int n);  //递归
long Fib_iteration(int n);  //迭代
int main(void)
{

    int abe = 30;
    long bresu, cresu;
    bresu = Fib_recursion(abe);
    cresu = Fib_iteration(abe);
    printf("%ld, %ld", bresu, cresu);
    return 0;

}

long Fib_recursion(int n)  //递归
{
    if( n <= 2 )
        return 1;

    return Fib_recursion(n - 1) + Fib_recursion(n - 2);

}

long Fib_iteration(int n)  //迭代
{
    long result;  //结果值
    long previous_result;  //
    long next_older_result;  

    result = previous_result = 1;  //赋初值(代指数列第一位并用于计算第二位)
    while( n > 2 ){
        n -= 1; //相减用于迭代终止
        next_older_result = previous_result;  //前二个数
        previous_result = result;  //前一个数
        result = previous_result + next_older_result;  //相加得结果
    }
    return result;
}

8. 可变参数列表通过宏(宏由预处理器实现)来实现。这些宏定义于stdarg.h头文件,是标准库的一部分。这个头文件声明了一个类型va_list和三个宏va_start,va_arg,va_end。我们可以声明一个类型为va_list的变量,与这几个变量配合使用,访问参数的值。
程序示例:

#include<stdio.h>  
#include<stdarg.h>  //宏定义可变参数列表

float avg(int n_values, ...);  
int main(void)
{
    int nu = 8;  //定义参数个数
    float AVG = avg(nu, 1, 2, 3, 4, 5, 6, 7, 8);  //随机输入nu个参数
    printf("%f", AVG);
}

float avg(int n_values, ...)
{
    //声明一个va_list类型的var_arg的变量,用于访问参数列表的未确定部分
    va_list var_arg;  
    int count;  
    float sum = 0;  

    va_start(var_arg, n_values);  //调用va_start来初始化变量var_arg
    //va_start的第一个参数是va_list的参数名var_arg
    //va_start的第二个是省略号前最后一个有名字的参数
    //初始化过程把var_arg变量设置为指向可变参数列表的第1个参数

    for(count = 0; count < n_values; count += 1){
        //根据n_values循环相加(使用int型)
        sum += va_arg(var_arg, int);  
        //使用va_arg访问参数,这个宏接受两个参数
        //第一个参数是va_list变量
        //第二个参数是参数列表的下一个参数类型(假设为int)
    }

    va_end(var_arg);  
    //访问完最后一个参数调用va_end

    return (sum / n_values);  //平均值
}

对于这些宏,存在两个基本的限制:

  • 这些宏无法判断存在的参数的数量
  • 这些宏无法判断每个参数的类型

故使用命名参数,本程序中命名参数制订了实际传递的参数数量,假定类型为整型。


9. 函数体内没有任何参数,就称为“存根”。return语句不包含返回值,或者函数不包括任何return语句,那么函数就没有返回值,这类函数被称为“过程”。


10. 函数被调用时,编译器看不到关于此函数的声明时,就假定函数返回一个整形值,故返回值不是整型的函数,在调用前进行声明非常重要。对于没有原型的函数,传递给函数的实参将进行缺省参数提升:char,short型转换为int型,float型转换为double型。


11. 函数的参数是通过传值方式传递的,实际所传递的是实参的一份拷贝,修改形参(即实参的拷贝)不会修改调用程序实际传递的参数。数组名也是通过传值调用的,但是传递给函数的是一个指向该数组的指针的拷贝,如果在数组形参中使用了下标引用操作,就引发间接访问,实际访问的是调用程序的数组元素,故在函数中修改数组元素的值实际上修改的是调用程序的数组,这被称为传址调用。
如果向传递标量也具有传址调用的语义,则可以向函数传递指向参数的指针。在函数中使用间接访问来改变这些值。


12. 如果一个递归函数内部所执行的最后一条语句就是调用自身时,就称其为尾部递归,尾部递归改写为循环的形式通常会效率更高。


13. 可变参数列表的原型中,可变部分用省略号代替,命名参数必须以某种形式提示可变部分实际所传递的参数数量,也可提供参数的类型信息,当参数列表中可变部分的参数实际传递给函数时,参数将经历缺省参数提升,可变部分的参数只能从第一个到最后一个依次访问。


2016.9.29

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值