《C和指针》读书笔记(第七章 函数)

简介: 《C和指针》读书笔记(第七章 函数)

在前面的例子中,我们已经用过了最常见的函数:主函数,其实在C语言还可以灵活地根据需要定义自己的函数,而在C++中,函数类型会更加地丰富。在面向对象的语言中,经常会有方法这个概念,看着比较像,其实作用也是基本一样的,都是对一段代码的抽象。只不过函数是直接传值的,而方法是直接处理对象上的数据,其依赖类或者对象,不能独立存在。

按照惯例,先来看看本章的知识框架(思维导图)。

思维导图

7.1函数定义

在书中并没有对函数比较通俗的定义,其实要说通俗的定义应该就是对某一段代码的抽象,也就是说我们若是想要实现某个功能,就将这些代码写到一起,然后通过语法和其他符号,就构建了一个函数,然后以后若是想反复使用这段代码,直接调用即可。函数定义的语法如下。

类型

函数名(形式参数)

代码块

另外有一个概念需要清楚:如果函数无需向调用程序返回一个值,它就会被省略。这类函数在绝大多数其他语言中被称为过程。这个概念在我学习《操作系统》过程中有提及。

7.2函数声明

一般函数都会有函数声明和函数实现。函数原型和函数声明几乎没有什么区别。因为C语言一般都是从main函数开始执行的,而在调用某函数的时候,我们告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。否则程序就无法顺利运行,因而函数声明应运而生。比方说下面的程序。

#include <stdio.h> 
size_t strlength(char *string);
int main()
{
  int res = 0;
  char a[] = "qwer";
  res = strlength(a);
  if (res)
    printf("The string length is %d\n",res);
  else
    printf("The string length is zero!\n");
  system("pause");
  return 0;
}
size_t strlength(char *string)
{
  int length = 0;
  while (*string++ != '\0')
    length++;
  return length;
}

可以看到在程序的一开始就对strlength函数进行了声明。

7.3函数的参数

函数的参数按照通俗的分类,会分为形参和实参,顾名思义,形参就是“形式”上的参数,实参就是实际调用的时候传入的参数。在C语言中,一般传入的都是实际值的一份拷贝,注意是拷贝

而参数若是数组,则一般会传入数组第一个元素的地址,这个行为被称为“传址调用” 。举个例子。

#include <stdio.h> 
void strexchange(char a[]);
int main()
{
  int res = 0;
  char a[] = "wwww";
  strexchange(a);
  for(int i = 0; i < 4; i++)
    printf("The string elements is %c\n",a[i]);
  system("pause");
  return 0;
}
void strexchange(char a[])
{
  a[0] = 'q';
}

打印输出

可以看到,在strexchange函数中,就可以完成对字符数组第一个元素的修改。所以肯定是“传址调用”。

7.4ADT和黑盒

书中有这样的描述:

C可以用于设计和实现抽象数据类型ADT,abctract data type),因为它可以显示函数和数据定义的作用域,这个技巧也可以被称为黑盒(black box)设计。

其实也比较好理解,也就是说,有时候我们在开发中,并不需要知道函数的具体实现过程,只是单纯想调用它来实现相应的功能。

通过static关键字可以限制在其他文件中直接对其进行访问。所以在其他文件中,只需要关注其功能即可,而不需要关注其体具体的实现过程。

书上有了一个例子,后经过了些许补充:

创建addrlist.h文件,编写如下程序:

#pragma once
#define NAME_LENGTH 20                       //姓名最大长度
#define ADDR_LENGTH 100                      //地址最大长度
#define PHONE_LENGTH 11                      //电话号码最大长度
#define MAX_ADDRESSES 1000                   //地址个数限制
void data_init();
char const *lookup_address(char const *name);
char const *lookup_phone(char const *name);

创建addrlist.c文件,编写如下程序:

#include "addrlist.h"
#include <stdio.h>
#include <string.h>
static char name[MAX_ADDRESSES][NAME_LENGTH];
static char address[MAX_ADDRESSES][ADDR_LENGTH];
static char phone[MAX_ADDRESSES][PHONE_LENGTH];
static int find_entry(char const *name_to_find)
{
  int entry;
  for (entry = 0; entry < MAX_ADDRESSES; entry++)
    if (strcmp(name_to_find, name[entry]) == 0)
      return entry;
  return -1;
}
//给定一个名字,找到对应的地址,如果找不到,则返回空指针
char const *lookup_address(char const *name)
{
  int entry;
  entry = find_entry(name);
  if (entry == -1)
    return NULL;
  else
    return address[entry];
}
char const *lookup_phone(char const *name)
{
  int entry;
  entry = find_entry(name);
  if (entry == -1)
    return NULL;
  else
    return phone[entry];
}
void data_init()
{
  char name_1[NAME_LENGTH] = "zhangsan";
  for (int i = 0; i < NAME_LENGTH; i++)
  {
    name[0][i] = name_1[i];
  }
  char address_1[ADDR_LENGTH] = "shanghai/zhangjiang";
  for (int i = 0; i < ADDR_LENGTH; i++)
  {
    address[0][i] = address_1[i];
  }
}

main.c中编写如下的代码:

#include "addrlist.h"
#include <stdio.h>
#include<stdlib.h>
int main()
{
  static char find_addr[MAX_ADDRESSES] = "zhangsan";
  char const *addr_res = NULL;
  //数据初始化
  data_init();
  addr_res = lookup_address(find_addr);
  if (addr_res == NULL)
    printf("^-^");
  else
  {
    for (int i = 0; i < ADDR_LENGTH; i++)
    {
      if (addr_res[i] != 0)
        printf("%c", addr_res[i]);
      else
        break;
    }
  }
}

运行,打印输出:

可以看到,当我们输入张三的时候,直接查到了张三的住址:上海张江。而此时在main.c文件中,我们并不知道具体的查询过程。所以这样就起到了封装的效果。

7.5 递归

递归是一种非常重要的编程思想,直观地说,就是函数自己调用自己。

关于递归,书上有这样一段描述:

一旦你理解了递归,阅读递归函数最容易的方法不是纠缠它的执行过程,而是相信递归函数会顺利完成它的任务,如果你的步骤正确无误,你的限制条件设置正确,并且每次调用之后更接近限制条件,递归函数总能正确地完成任务。

最最经典的例子当属斐波那契数列了,程序如下:

int fibonacci(int const n)
{
  int sum = 0;
  if (n == 0)  return 0;
  if (n == 1 || n == 2)  return 1;
  return fibonacci(n - 1) + fibonacci(n-2);
}

当然,我们也可以写一个非递归版本的,只是稍微复杂一些:

int fibonacci(int const n)
{
  int f1 = 1, f2 = 1, f3 = 0;
  if (n == 0)  return 0;
  if (n == 1 || n == 2)  return 1;
  for (int i = 3; i <= n; i++)
  {
    f3 = f1 + f2;
    f1 = f2;
    f2 = f3;
  }
  return f3;
}

开始可能会觉得非递归版本好理解一些,但是习惯了之后,会发现递归版本的更加方便。而且在有的时候,使用递归要比循环容易得多,比方说下面这个例子:

  1. 各位相加

给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。返回这个结果。

如果我们用非递归的方法,可能比较难解决,其中一种解决思路如下:

int addDigits(int num){
    int add = 0;
    do
    {   
        add = 0;  
        while(num > 0)
        {
            add += num % 10;
            num /= 10;
        }
        num = add;
    }while(add >= 10);
    return add;
}

也就是当求和的结果大于10的时候继续执行相同的操作,直到小于10,返回计算的结果。但如果我们采用递归,就会变得更加简单:

int addDigits(int num){
    int add = 0;
    while(num > 0)
    {
        add += num % 10;
        num /= 10;
    }
    num = add;
    return add < 10 ? add : addDigits(add);
}

所谓的重复的操作,我们就可以直接递归调用,然后就可以输出想要的结果。

注:要把握递归的深度,且确保递归是可终止的,否则可能会出现堆栈溢出的情况。

7.6 可变参数列表

在实际的项目开发中,我们经常会遇到传递的参数个数未知的情况,这个时候就需要用到可变参数列表。书中有这样一个例子:

//可变参数列表头文件
#include<stdarg.h>
float average(int n_values, ...)
{
  va_list var_arg;
  int count;
  float sum = 0;
  //准备访问可变参数
  va_start(var_arg, n_values);
  //添加取自可变参数的值
  for (count = 0; count < n_values; count++)
  {
    sum += va_arg(var_arg, int);
  }
  //完成处理可变参数
  va_end(var_arg);
  return sum / n_values;
}

也就是一个求平均值的函数,但我们事先并不知道 究竟有多少个数需要求平均值。调用的时候可以这样:

printf("%f\n", average(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

就可以直接打印出算得的结果,打印输出:

注意:可变参数必须从头到尾按照顺序逐个访问。如果在访问了几个可变参数后想半途中止,这是可以的。

从这点上来看,可变参数列表和链表的访问很类似。

总结

C语言也可以用来设计和实现抽象数据类型

递归在熟练以后用起来很方便,但并非在所有情况下都会那么高效,同时要注意由此可能引发的堆栈溢出问题。

---------------------------------------------------------------------------END---------------------------------------------------------------------------

相关文章
|
23天前
|
存储 C语言 C++
如何通过指针作为函数参数来实现函数的返回多个值
在C语言中,可以通过将指针作为函数参数来实现函数返回多个值。调用函数时,传递变量的地址,函数内部通过修改指针所指向的内存来改变原变量的值,从而实现多值返回。
|
23天前
|
存储 搜索推荐 C语言
如何理解指针作为函数参数的输入和输出特性
指针作为函数参数时,可以实现输入和输出的双重功能。通过指针传递变量的地址,函数可以修改外部变量的值,实现输出;同时,指针本身也可以作为输入,传递初始值或状态。这种方式提高了函数的灵活性和效率。
|
1月前
利用指针函数
【10月更文挑战第2天】利用指针函数。
17 1
|
1月前
|
算法 搜索推荐 C语言
【C语言篇】深入理解指针4(模拟实现qsort函数)
【C语言篇】深入理解指针4(模拟实现qsort函数)
23 2
|
5月前
|
机器学习/深度学习 搜索推荐 算法
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
|
2月前
|
Linux
在Linux内核中根据函数指针输出函数名称
在Linux内核中根据函数指针输出函数名称
|
3月前
|
程序员 C语言
指针在函数参数和返回值中的使用
指针在函数参数和返回值中的使用
47 9
|
3月前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
5月前
|
编译器 C++
函数指针和函数对象不是同一类型怎么替换
函数指针和函数对象不是同一类型,为何可替换用作同一函数的参数
|
5月前
|
存储 C语言
C语言的函数返回值和指针
C|函数返回值(区分各类值)和指针(区分各类存储空间)的细节