1、函数的分类
- (1)库函数
- (2)自定义函数
1、库函数
(1)常用的库函数
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
(2)库函数学习工具
- (1)MSDN(Microsoft Developer Network)
- (2)www.cplusplus.com
- (3)http://en.cppreference.com (英文版)
- (4)http://zh.cppreference.com (中文版)
(3)库函数使用举例
1️⃣strcpy库函数
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[20] = "XXXXXXXXXXX";
char arr3[20];
strcpy(arr2, arr1); //将arr1中所存内容(abcdef\0)拷贝到arr2中
strcpy(arr3, "Happy New Year");
printf("arr1:%s\n", arr1); // arr1:abcdef
printf("arr2:%s\n", arr2); // arr2:abcdef(遇到\0结束打印,不会打印后面的X)
printf("arr3:%s\n", arr3); // arr3:Happy New Year
return 0;
}
2️⃣memset库函数
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
char arr1[] = "abcdefghijk";
memset(arr1, '*', 5);
printf("%s\n", arr1); //*****fghijk
return 0;
}
2、自定义函数
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//实现一个函数来交换两个整数的内容
void swap(int* pa, int* pb)
{
int temp = *pa; // temp = a
*pa = *pb; // a = b
*pb = temp; // b = temp
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a = %d , b = %d\n", a, b);
swap(&a, &b);
printf("交换后:a = %d , b = %d\n", a, b);
return 0;
}
2、函数的参数
1、实际参数(实参)
- (1)真实传给函数的参数,叫实参。
- (2)实参可以是:常量、变量、表达式、函数等。
- (3)无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
2、形式参数(形参)
- (1)形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。
- (2)形式参数当函数调用完成之后就自动销毁了,因此形式参数只在函数中有效。
【注】 实参传递给形参的时候,形参是实参的一份临时拷贝,对形参的修改不影响实参。
3、函数的调用
1、传值调用
注:函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int get_max(int x, int y)
{
if (x > y)
return x;
else
return y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
int max = get_max(a, b);
printf("%d\n", max);
return 0;
}
2、传址调用
- (1)传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
- (2)这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
- (3)如果函数内部想要改变外部的值,可以传址调用。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//实现一个函数来交换两个整数的内容
void swap(int* pa, int* pb) //pa、pb是形参
{
//通过 pa 和 pb,远程操控 a 和 b 与 temp 进行交换
int temp = *pa; // temp = a
*pa = *pb; // a = b
*pb = temp; // b = temp
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b); //&a、&b是实参
printf("交换前:a = %d , b = %d\n", a, b);
swap(&a, &b);
printf("交换后:a = %d , b = %d\n", a, b);
return 0;
}
4、函数的嵌套调用和链式访问
1、嵌套调用
- (1)函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
- 【注】函数可以嵌套调用,但是不能嵌套定义。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void new_line()
{
printf("haha\n");
}
void three_line()
{
int i = 0;
for (i = 0; i < 3; i++)
{
new_line(); //three_line函数调用了new_line函数,实现了函数的嵌套调用
}
}
int main()
{
three_line();
return 0;
}
【注】:函数可嵌套使用,但不可嵌套定义。(每个函数要单独定义)
// 嵌套定义例如:
void A()
{
void B()
{
}
}
2、链式访问
- 把一个函数的返回值作为另外一个函数的参数。
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43))); //4321
return 0;
}
【分析】:该题屏幕上先打印43,第一个 printf 返回的是2;再打印2,第二个 printf 返回的是1;最后再打印1。
注:printf 函数的返回值是打印在屏幕上的数字个数,scanf 函数的返回值是赋值成功变量的个数。
5、函数的声明和定义
1、函数的声明
- (1)告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
- (2)函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- (3)函数的声明一般要放在头文件中。
2、函数的定义
- (1)函数的定义是指函数的具体实现,交待函数的功能实现。
- (2)当函数定义没有返回值类型时,编译器默认为 int 型。若函数明确没有参数,则参数列表应该写void,不要空着不写!
6、函数递归
1、什么是递归
- (1)程序调用自身的编程技巧称为递归( recursion)。
- (2)递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
- (3)递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
- (4)递归的主要思考方式在于:大事化小
2、递归的两个必要条件
- (1)存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- (2)每次递归调用之后越来越接近这个限制条件。
3、递归练习
(1)接受一个无符号整型值,按顺序打印它的每一位
练习1:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//递归方式实现打印一个(无符号)整数的每一位
//输入:1234,输出 1 2 3 4
// %u —— 无符号整数
// %d —— 有符号整数
void Print(unsigned int n)
{
if (n > 9)
{
Print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 0;
scanf("%u", &num); //%u —— 无符号整数
Print(num);
return 0;
}
(2)编写函数不允许创建临时变量,求字符串的长度
练习2:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//递归和非递归分别实现strlen
//编写函数不允许创建临时变量,求字符串的长度
//非递归:
int Strlen(char* arr)
{
int count = 0; //创建变量了
while (*arr != '\0')
{
count++;
arr++;
}
return count;
}
//递归:
int My_Strlen(char* str) //str放的是a的地址
{
if (*str != '\0')
{
return 1 + My_Strlen(str + 1); //str + 1是的下一个元素的地址
}
else
{
return 0;
}
}
int main()
{
char arr[] = "abcdef";
printf("%d\n", Strlen(arr)); //数组名是数组首元素地址
printf("%d\n", My_Strlen(arr));
return 0;
}
7、递归与迭代
循环是迭代的一种
(1)求n的阶乘(不考虑溢出)
练习1:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//递归和非递归分别实现求n的阶乘(不考虑溢出的问题)
//非递归(迭代):
int Mul(int n)
{
int ret = 1;
for (int i = 1; i <= n; i++)
{
ret = ret * i;
}
return ret;
}
//递归:
int My_mul(int n)
{
if (n <= 1)
return 1;
else
return n * My_mul(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d的阶乘为:%d\n", n, Mul(n));
printf("%d的阶乘为:%d\n", n, My_mul(n));
return 0;
}
(2)求第n个斐波那契数(不考虑溢出)
练习2:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//递归和非递归分别实现求第n个斐波那契数
//斐波那契数列:1 1 2 3 5 8 13 21...
/*
方法一:递归
int fib(int n)
{
if (n <= 2)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
*/
//方法二:迭代
int fib(int n)
{
int a = 1;
int b = 1;
int c = a + b;
if (n <= 2)
{
return 1;
}
else
{
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = fib(n);
printf("%d\n", ret);
return 0;
}
提示:
- (1)许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
- (2)但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
- (3)当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。