C语言初阶③(函数)知识点+编程作业(递归模拟strlen,pow)

简介: C语言初阶③(函数)知识点+编程作业(递归模拟strlen,pow)

一.函数

上一专栏我们学过函数,我们来补充一点知识。

数学中,f(x) = 2*x+1、f(x, y) = x + y 是函数...

在计算机中,函数是一个大型程序中的某部分代码,由一个或多个语句块组成;


它负责完成某项特定任务,并且相较于其他代码,具备相对的独立性;


注意事项:


1. 函数设计应追求“高内聚低耦合”;


(即:函数体内部实现修改了,尽量不要对外部产生影响,否则:代码不方便维护)


2. 设计函数时,尽量做到谁申请的资源就由谁来释放;


3. 关于return,一个函数只能返回一个结果;


4. 不同的函数术语不同的作用域,所以不同的函数中定义相同的名字并不会造成冲突;


5. 函数可以嵌套调用,但是不能嵌套定义,函数里不可以定义函数;


7. 函数的定义可以放在任意位置,但是函数的声明必须放在函数的使用之前;


建议:


1. 函数参数不宜过多,参数越少越好;


2. 少用全局变量,全局变量每个方法都可以访问,很难保证数据的正确性和安全性;

  1. 主函数

注意事项

1. C语言规定,在一个源程序中,main函数的位置可任意;

2. 如果在主函数之前调用了那些函数,必须在main函数前对其所调用函数进行声明,

或包含其被调用函数的头文件

2.库函数

为什么会有库函数?

“库函数虽然不是业务性的代码,但在开发过程中每个程序员都可能用得到,

为了支持可移植性和提高程序的效率,所以C语言基础库中提供了库函数,方便程序员进行软件开发”

注意事项:库函数的使用必须要包含对应的头文件;

3.自定义函数

何为自定义函数?

“顾名思义,全部由自己设计,赋予程序员很大的发挥空间”

自定义函数和其他函数一样,有函数名、返回值类型和函数参数;

1. ret_type 为返回类型;

2. func_name 为函数名;

3. paral 为函数参数;

4.函数的参数

实际参数(实参)

1. 真实传给函数的参数叫实参(实参可以是常量、变量、表达式、函数等);

2. 无论实参是何种类型的量,进行函数调用时,必须有确定的值,以便把这些值传送给形参;

形式参数(形参)

1. 形参实例化后相当于实参的一份临时拷贝,修改形参不会改变实参;


2. 形式参数只有在函数被调用的过程中才实例化;


3. 形式参数在函数调用完后自动销毁,只在函数中有效;


注意事项:


1. 形参和实参可以同名;


2. 函数的形参一般都是通过参数压栈的方式传递的;


3. “形参很懒”:形参在调用的时才实例化,才会开辟内存空间;

5.函数的调用

传值调用

1. 传值调用时,形参是实参的一份临时拷贝;

2. 函数的形参和实参分别占用不同内存块,对形参的修改不会影响实参;

3. 形参和实参使用的不是同一个内存地址;

传址调用

1. 传址调用时可通过形参操作实参;

2. 传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式;

3. 使函数内部可以直接操作函数外部的变量(让函数内外的变量建立起真正的联系);

相关知识:

链式访问:

把一个函数的返回值作为另外一个函数的参数。

函数可以嵌套调用,但是不能嵌套定义。

函数声明:

1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,

函数声明决定不了。

2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用

3. 函数的声明一般要放在头文件中的。

函数定义:

函数的定义是指函数的具体实现,交待函数的功能实现。

test.h的内容

放置函数的声明

test.c的内容

放置函数的实现

这种分文件的书写形式,才是程序员用的。据说其中一个原因是为了不给买家看到源码/抄袭。

二.函数的递归

递归的定义

程序调用自身称为递归(recursion)

1. 递归策略只需要少量的程序就可以描述解题过程所需要的多次重复计算,大大减少代码量;

2. 递归的主要思考方式在于:把大事化小;

注意事项:

1. 存在跳出条件,每次递归都要逼近跳出条件;

2. 递归层次不能太深,避免堆栈溢出

什么是递归?

程序调用自身的编程技巧称为递归( recursion)。

递归做为一种算法在程序设计语言中广泛应用。

一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,

它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,

递归策略

只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归的主要思考方式在于:把大事化小

递归的两个必要条件

存在限制条件,当满足这个限制条件的时候,递归便不再继续。

每次递归调用之后越来越接近这个限制条件

三 . 编程练习:

1.函数判断素数

实现一个函数,判断一个数是不是素数。

利用上面实现的函数打印100到200之间的素数。

#include<stdio.h>
#include<math.h>
int is_prime(int n)
{
    int i = 0;
    for (i = 2; i <= sqrt(n); i++)
    {
        if (0 == n % i)
        {
            return 0;
        }
    }
    return 1;
}
int main()
{
    for (int i = 100;i <= 200;i++)
    {
        if (is_prime(i))
            printf("%d\n", i);
    }
    return 0;
}

2. 实现函数判断year是不是润年。

#include<stdio.h>
int is_leap_year(int year)
{
    if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
        return 1;
    else
        return 0;
}
int main()
{
    int year;
    scanf("%d", &year);
    if (is_leap_year(year))
        printf("%d是闰年\n", year);
    else
        printf("%d不是闰年\n", year);
    return 0;
}

3. 实现一个函数来交换两个整数的内容。(涉及指针)

#include<stdio.h>
void swap(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
int main()
{
    int x = 0, y = 0;
    scanf("%d%d", &x, &y);
    printf("交换前:%d %d\n", x, y);
    swap(&x, &y);
    printf("交换后:%d %d\n", x, y);
    return 0;
}

4.乘法口诀表

实现一个函数,打印乘法口诀表,口诀表的行数和列数自己指定

如:输入9,输出9*9口诀表,输出12,输出12*12的乘法口诀表。

#include<stdio.h>
void print_multiplication_table(int n)
{
    for (int i = 1;i <= n;i++)
    {
        for (int j = 1;j <= i;j++)
        {
            printf("%d*%d=%2d ", j, i, j * i);
        }
        printf("\n");
    }
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    print_multiplication_table(n);
    return 0;
}

5.递归打印无符号整形

接受一个整型值(无符号),按照顺序打印它的每一位。

例如:

输入:1234,输出 1 2 3 4

#include<stdio.h>
void print(unsigned int n)
{
    if (n > 9)
    {
        print(n / 10);
    }
    printf("%u ", n % 10);
}
int main()
{
    unsigned int n = 0;
    scanf("%u", &n);
    print(n);
    return 0;
}

6.非递归和递归模拟实现strlen(strlen求长度不包括\0)

编写一个函数求字符串的长度。和编写另一个函数不允许创建临时变量,求字符串的长度。

#include<stdio.h>
#include<string.h>
int my_strlen1(const char* str)
{
    int k = 0;
    while (*str != '\0')
    {
        k++;
        str++;
    }
    return k;
}
int my_strlen2(const char* str)
{
    if (*str != '\0')
        return 1 + my_strlen2(str + 1);
    else
        return 0;
}
    //左下右上转圈地看
    //my(hello)
    //my(hello)     1+my(ello) =5
    //my(ello)      1+my(llo)  =4
    //my(llo)       1+my(lo)   =3
    //my(lo)        1+my(o)    =2
    //my(o)  return 1+my(\0)   =1
    //my(\0):return 0;
int main()
{
    char arr[] = "hello";
    printf("%d\n", my_strlen1(arr));
    printf("%d\n", my_strlen2(arr));
    return 0;
}

7.迭代和递归求n的阶乘(循环是一种迭代)

求n的阶乘。(不考虑溢出)

#include<stdio.h>
int fac1(int n)
{
    int r = 1;
    for (int i = 1;i <= n;i++)
    {
        r *= i;
    }
    return r;
}
int fac2(int n)
{
    if (n == 0)
        return 1;
    else
        return n*fac2(n - 1);
    //n=4时
    //fac2(4):return 4*fac(3);      4*2*1
    //fac2(3):return 3*fac(2);      3*2*1
    //fac2(2):return 2*fac(1);      2*1
    //fac1(0):return 1;
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    printf("%d的阶乘为:%d\n", n, fac1(n));
    printf("%d的阶乘为:%d\n", n, fac2(n));
    return 0;
}

以下问题用递归可能不是最好的方法,但只是锻炼写递归的能力

8.迭代和递归求前n个斐波那契数。(不考虑溢出)

此题用递归如果n很大,比如50,计算器要算很久,因为要调用很多次

可以试试两个函数在n=45时的速度比较,很有趣。

#include<stdio.h>
//求得数据过大时可以把int换成long long
int fib1(int n)
{
    int r = 1, arr[95] = { 0,1,1 };//定义数组长度为95(比90稍多一点)
    for (int i = 3;i <= n;i++)
    {
        arr[i] = arr[i - 1] + arr[i - 2];
    }
    return arr[n];
}
int fib2(int n)
{
    if (n == 1 || n == 2)
        return 1;
    else
        return fib2(n - 1) + fib2(n - 2);
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    for (int i = 1;i <= n;i++)
    {
        printf("第%d个斐波那切数为%d\n",i, fib1(i));
    }
    return 0;
}

9.递归实现字符串逆序

【题目内容】

编写一个函数 reverse_string(char * string)(递归实现)

实现:将参数字符串中的字符反向排列,不是逆序打印。

要求:不能使用C函数库中的字符串操作函数。

比如:

char arr[] = "abcdef";

逆序之后数组的内容变成:fedcba

#include<stdio.h>
#include<string.h>
int my_strlen2(const char* str)
{
    if (*str != '\0')
        return 1 + my_strlen2(str + 1);
    else
        return 0;
}
void reverse_string(char* str,int left, int right)
{
    if (left < right)
    {
        int tmp = str[left];
        str[left] = str[right];
        str[right] = tmp;
        reverse_string(str, left + 1, right - 1);
    }
}
int main()
{
    char arr[20] = { 0 };
    scanf("%s", arr);
    int left = 0, right = my_strlen2(arr) - 1;
    printf("逆序前:%s\n", arr);
    reverse_string(arr, left, right);
    printf("逆序后:%s\n", arr);
    return 0;
}

10 .递归计算一个数的每位之和

【题目内容】

写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和

例如,调用DigitSum(1729),则应该返回1+7+2+9,它的和是19

输入:1729,输出:19

#include<stdio.h>
int  digit_sum(int n)
{
    if (n > 9)
        return n % 10 + digit_sum(n / 10);
    else
        return n;
}
//d(1234):4+d(123)
//d(123):3+d(12)
//d(12):2+d(1)
//d(1):1
int main()
{
    unsigned int n = 0;
    scanf("%d", &n);
    printf("%d", digit_sum(n));
    return 0;
}

11 .递归实现n的k次方

#include<stdio.h>
double my_pow(int n, int k)
{
    if (k == 0)
        return 1;
    else if (k > 0)
        return n * my_pow(n, k - 1);
    else
        return 1.0 / (my_pow(n, -k));
    //p(3,4):return 3*p(3,3);   81
    //p(3,3):return 3*p(3,2);   27
    //p(3,2):return 3*p(3,1);   9
    //p(3,1):return 3*p(3,0);   3
    //p(3,0):return 1;
}
int main()
{
    int n = 0, k = 0;
    scanf("%d%d", &n, &k);
    printf("%lf\n", my_pow(n, k));
    return 0;
}

目录
相关文章
|
25天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
53 7
|
1月前
|
NoSQL C语言 索引
十二个C语言新手编程时常犯的错误及解决方式
C语言初学者常遇错误包括语法错误、未初始化变量、数组越界、指针错误、函数声明与定义不匹配、忘记包含头文件、格式化字符串错误、忘记返回值、内存泄漏、逻辑错误、字符串未正确终止及递归无退出条件。解决方法涉及仔细检查代码、初始化变量、确保索引有效、正确使用指针与格式化字符串、包含必要头文件、使用调试工具跟踪逻辑、避免内存泄漏及确保递归有基准情况。利用调试器、编写注释及查阅资料也有助于提高编程效率。避免这些错误可使代码更稳定、高效。
216 12
|
1月前
|
C语言
c语言回顾-函数递归(上)
c语言回顾-函数递归(上)
31 2
|
1月前
|
C语言
C语言学习笔记-知识点总结上
C语言学习笔记-知识点总结上
74 1
|
1月前
|
C语言
c语言回顾-函数递归(下)
c语言回顾-函数递归(下)
37 0
|
1月前
|
Serverless 编译器 C语言
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
|
2月前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
2月前
|
Linux C语言
C语言 多进程编程(七)信号量
本文档详细介绍了进程间通信中的信号量机制。首先解释了资源竞争、临界资源和临界区的概念,并重点阐述了信号量如何解决这些问题。信号量作为一种协调共享资源访问的机制,包括互斥和同步两方面。文档还详细描述了无名信号量的初始化、等待、释放及销毁等操作,并提供了相应的 C 语言示例代码。此外,还介绍了如何创建信号量集合、初始化信号量以及信号量的操作方法。最后,通过实际示例展示了信号量在进程互斥和同步中的应用,包括如何使用信号量避免资源竞争,并实现了父子进程间的同步输出。附带的 `sem.h` 和 `sem.c` 文件提供了信号量操作的具体实现。
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
6天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
21 6