C语言 函 数
- 函数是什么
- 库函数
- 函数的参数
- 函数的调用
- 函数的嵌套调用和链式调用
- 函数的声明和定义
- 函数递归
函数是什么
具有某种功能的代码块
一般有返回值,提供对过程的封装和细节的隐藏。
分类
库函数
自定义函数
库函数
www.cplusplus.com
http://en.cppreference.com
频繁使用的功能就被写成了库函数
C语言常见的库函数
- IO函数
- 字符串操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
**注:**使用库函数之前需要#include 对应的头文件
自定义函数
比库函数重要自己完成自己需要的功能
函数的组成
返回类型 函数名(){
语句内容;
return 返回值;//有的没有
}
/*两数相加*/
int add(int x, int y){
int z = 0;
z = x+y;
return z;
}
/*两数的较大值*/
int get_max(int x, int y){
return (x>y)?x:y;
}
/*交换两值*/
void Swap(int *x, int *y) {
int temp = 0;//中间变量
temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 10;
int b = 20;
Swap(&a, &b);//这里必须传地址否则不能对a和b进行操作
}
这里如果不使用指针的话相当于重新定义了两个值,从而不是对a和b进行操作。
函数的参数
实参(实际参数)
真实的传给函数的参数是实参。实参必须是确定的量。实参是传给形参的。
实参可以是常量、变量、表达式、函数等等。
形参(形式参数)
在自定义函数时定义的参数,即在函数名后面的()中的参数是形参。形参只在自定义函数中生效。形参只有在函数被调用的时候才会实例化。
形参其实是实参的一份临时拷贝,对形参的修改不会改变实参的。所以要对值操作的时候需要传的是变量的地址
传值调用
函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参。
传址调用
- 传址调用是把函数外面创建的变量的内存地址传递给函数参数的一种调用方式。
- 这种方式让函数与外部的变量建立起真正的联系,也就是函数内部也可以直接操作函数外部的变量。
练习
- 写一个函数判断一个数是不是素数。
#include "math.h"
int is_prime(int n){
for (int i = 2; i < sqrt(n); ++i) {
if(n%i==0) return 0;
}
return 1;
}
int main(){
//100-200之间的素数
for (int i = 100; i < 200; ++i) {
//判断是否是素数
if (is_prime(i)==1) printf("%d ",i);
}
}
- 写一个函数判断一年是不是闰年。
int is_leap_year(int year){
if ((year%4==0&&year%100!=0)||(year%400==0)) return 1;
else return 0;
}
int main(){
for (int year = 1000; year <= 2000; ++year) {
//判断是否是闰年
if (1 == is_leap_year(year)) printf("%d \n",year);
}
return 0;
}
- 写一个函数实现对一个整形有序数组的二分查找。
// ###实际上这里的arr是一个指针
int binary_search(int arr[], int find, int sz) {
int left = 0;
int right = sz - 1; //长度减一
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] < find) left = mid + 1;
else if (arr[mid] > find) right = mid - 1;
else return mid;
}
return -1;
}
int main() {
//在一个有序数组中查找具体的某个值,找到返回这个值的下标没找到返回-1;
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int find = 7;
int sz = sizeof(arr) / sizeof(arr[0]);
//这里传递数组传递过去的是首元素的地址
int res = binary_search(arr, find, sz);
if (res == -1) printf("找不到指定的数字\n");
else printf("找到了,下标是:%d", res);
return 0;
}
- 写一个函数,每调用一次这个函数就会将num的值加一。
void UpNum(int* p){
*p+=1;
}
int main(){
int num = 0;
UpNum(&num);
printf("%d ",num);
UpNum(&num);
printf("%d ",num);
UpNum(&num);
printf("%d ",num);
return 0;
}
函数的嵌套调用和链式访问
函数和函数之间可以结合使用。
嵌套使用
函数套用函数
void print_new_line(){
printf("hehe\n");
}
void print_three_line(){
for (int i = 0; i < 3; ++i) new_line();
}
int main(){
three_line();
return 0;
}
链式访问
把一个函数的返回值作为另一个函数的参数。
printf("%d \n",strlen("abcdef"));
int main(){
char a[20] = "hello";
//链式访问
int res = strlen(strcat(a,"world"));//strcat()连接字符串
printf("%d\n",res);
return 0;
}
函数声明
- 告诉你的编译器有个函数(函数名,参数,返回值类型)。但是存在与否不重要了。
- 函数的声明是出现在函数使用之前的,要满足先声明后使用。
- 函数的声明一般是在头文件中。
当函数的定义在main函数之后的时候直接编译是不能通过的。这时就需要函数的声明了。
//函数的声明
int Add(int, int);
int main(){
int a=20,b=30;
int sum = 0;
sum = Add(a,b);
printf("%d",sum);
return 0;
}
//函数的定义
int Add(int x,int y){
int z = x+y;
return z;
}
在做开发的时候函数的定义在自定义.c文件中。函数的声明写在头文件.h文件中。
以下是文件中的存放即代码示例
add.h中
#ifndef __ADD_H__
#define __ADD_H__
int Add(int x,int y); //函数的声明
#endif//
add.c中 //功能模块
int Add(int x, int y){
int z = x+ y;
return z;
}
main.c中//主模块
#include <stdio.h>
#include "add.h" //引用库函数用的是<>,引用自定义的头文件用的是""
int main(){
int a=20,b=30;
int sum = 0;
sum = Add(a,b);
printf("%d",sum);
return 0;
}
分模块写代码效率高
.h文件放置函数的声明。.c文件放置函数具体实现的功能。
函数的递归
程序调用自身的编程技巧叫递归。通常用来将大型复杂的问题转化为与原问题相似的规模较小的问题来求解。大大减少程序的代码量。递归主要思考方式是大事化小事。
递归的两个必要条件
- 一定要存在限制条件,满足这个限制条件的时候,递归就不再进行了。
- 每次递归之后越来越接近这个限制条件。
一个简单的递归函数
int main(){
printf("hehehe\n");
main();
return 0;
}
递归常见的问题----->>>>栈溢出
在每一次函数调用时候都要在栈区申请内存空间。eg:上面函数执行main()时申请一次,执行printf()申请一次,main()又申请一次。当申请到栈区耗干的时候。抛出错误:stack overflow(栈溢出了)。
栈溢出的原因一般是因为没有终止条件。
https://stackoverflow.com/ (程序员的知乎)。
通过练习理解递归
练习1:
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:输入:1234 输出:1 2 3 4.
void print_number(int n){
/*思路:print_number(123)+printf(4)
* print_number(12)+printf(3)*
* print_number(1)+printf(2)*
* printf(1)*/
if (n>=10) print_number(n/10); //-------->>>>>终止条件
printf("%d ",n%10); //-------->>>>>每一次要执行的功能
}
int main(){
unsigned int num = 0;
scanf("%d",&num);
print_number(num);//递归函数
return 0;
}
练习2:
编写一个函数,求字符串长度。
要求:不建立新的内存空间。
/* ******错误示范 错误示范 错误示范****** */
int my_strlen(char* str) //这里接受第一个元素的地址
{
int len = 0;
while (*str != '\0'){
len++;
str++;
}
return len;
}
int main(){
char arr[] = "hello";
int len = my_strlen(arr);//这里传参数传的是第一个元素的地址
printf("strlen = %d",len);
return 0;
}
/* ************ 正 解 ************ */
int my_strlen(char* str) //这里接受第一个元素的地址
{
if (*str!='\0')
return 1+ my_strlen(str+1);
return 0;
}
int main(){
char arr[] = "hello";
int len = my_strlen(arr);//这里传参数传的是第一个元素的地址
printf("strlen = %d",len);
return 0;
}
递归的两个必要条件
-
- 一定要存在限制条件,满足这个限制条件的时候,递归就不再进行了。
-
- 每次递归之后越来越接近这个限制条件。
递归与迭代
练习3:
求n的阶乘。
int Facl(int n){
int res = 1;
for (int i = 1; i <= n; ++i) {
res *=i;
}
return res;
}
int Facl(int n){
if (n<=1) return 1;
else return n* Facl(n-1);
}
int main(){
int n = 0;
int res = 0;
scanf("%d",&n);
res = Facl(n);
printf("%d\n",res);
return 0;
}
练习4 :
求第n个斐波那契数。
注:斐波那契数列:1 1 2 3 5 8 13 21 34 55 … 第n项的值为前两项之和。
int Fib(int n){
if (n<=2) return 1;
else return Fib(n-1)+ Fib(n-2);
}
int main(){
int n;
scanf("%d",&n);
int res = Fib(n);
printf("%d",res);
return 0;
}
斐波那契数列的优化。
优化思想:
# 每次的重复计算太多这样就想着把他存起来。
# 每次计算其实只需要n-2和n-1项,我们将其叫为first,second
这样计算斐波那契数就是first+second
int Fib(int first,int second,int n){ //---->>>需要修改参数的量
if (n<=2) return 1;
if (n==3) return first+second;
else return Fib(second,first+second,n-1);
}
int main(){
int n;
scanf("%d",&n);
int res = Fib(1,1,n);
printf("%d",res);
return 0;
}
注:TTD开发方式:测试驱动开发–先看整体的流程。再开发细节的功能。
递归和迭代
无论使用什么方式写代码异地昂要正确。问题就是有可能会出现栈溢出。
每次的重复计算太多这样就想着把他存起来。
每次计算其实只需要n-2和n-1项,我们将其叫为first,second
这样计算斐波那契数就是first+second
~~~c
int Fib(int first,int second,int n){ //---->>>需要修改参数的量
if (n<=2) return 1;
if (n==3) return first+second;
else return Fib(second,first+second,n-1);
}
int main(){
int n;
scanf("%d",&n);
int res = Fib(1,1,n);
printf("%d",res);
return 0;
}
注:TTD开发方式:测试驱动开发–先看整体的流程。再开发细节的功能。
递归和迭代
无论使用什么方式写代码异地昂要正确。问题就是有可能会出现栈溢出。