笔记整理于B站鹏哥C语言要继续加油!
目录
1.函数是什么
2. C语言中函数的分类:
2.1 库函数:
简单的总结, C 语言常用的库函数都有:IO 函数 printf scanf getchar putchar字符串操作函数 strcmp strlen字符操作函数 toupper内存操作函数 memcpy memcmp memset时间 / 日期函数 time数学函数 sqrt pow其他库函数
strcpy
char * strcpy ( char * destination, const char * source );
memset
void * memset ( void * ptr, int value, size_t num );
2.1.1 如何学会使用库函数?
2.2 自定义函数
ret_type fun_name ( para1 , * ){statement ; // 语句项}ret_type 返回类型fun_name 函数名para1 函数参数
#include <stdio.h>
//get_max函数的设计
int get_max(int x, int y)
{
/*
int z = 0;
if (x > y)
z = x;
else
z = y;
return z;
*/
return (x>y)?(x):(y);
}
int main()
{
int num1 = 10;
int num2 = 20;
int max = get_max(num1, num2);
printf("max = %d\n", max);
return 0;
}
#include <stdio.h>
//实现成函数,但是不能完成任务
void Swap1(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
//正确的版本
void Swap2(int *px, int *py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int num1 = 1;
int num2 = 2;
Swap1(num1, num2);
printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
Swap2(&num1, &num2);
printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
return 0;
}
//函数返回类型的地方写成:void,表示这个函数不返回任何值,也不需要返回
运行结果:
向函数传送地址与数值区别,传数值形参需要在开辟地址空间,传送地址时使用指针
什么情况设计函数时传地址回去,函数内部与函数外部如果需要建立联系使用地址,单纯数值不用传地址。return 不能同时返回2个值,只能返回一个值。
3. 函数的参数
3.1 实际参数(实参):
真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
3.2 形式参数(形参):
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
上面 Swap1 和 Swap2 函数中的参数 x,y,px,py 都是形式参数。在main函数中传给 Swap1 的 num1 ,num2 和传给 Swap2 函数的 &num1 , &num2 是实际参数。
代码对应的内存分配如下:
4. 函数的调用:
4.1 传值调用
4.2 传址调用
4.3 练习
#include<stdio.h>
//1. 写一个函数可以判断一个数是不是素数。
int is_prime(int n)
{
//2 ->n-1之间的数字
int j = 0;
for (j = 2; j < n; j++)
{
if (n % j == 0)
{
return 0;
}
}
return 1;
}
int main()
{
//100-200之间的素数
int i = 0;
int count = 0;
for (i = 100; i <= 200; i++)
{
//判断i是否为素数
if (is_prime(i) == 1)
{
count++;
printf("%d ", i);
}
}
printf("\ncount=%d\n", count);
}
//如果是闰年返回1,不是闰年返回0
//一个函数如果不写返回值类型默认返回int
int is_leap_year(int y)
{
能被4整除但是不能被100整除或者可以被400整除
if ((y % 4 == 0 && y % 100 != 0)|| (y % 400 == 0))
//{
// return 1;
//}
//else
//{
// return 0;
//}
return ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0));
}
int main()
{
int y = 0;
for (y = 1000; y <= 2000; y++)
{
if (is_leap_year(y) == 1)
{
printf("%d ", y);
}
}
return 0;
}
//二分查找
int binary_search(int a[],int k,int s)//形参与实参可以相同也可以不同,a[]告诉我们是一个数组但是里面有几个元素不重要,a[]实际上是个数组首元素的地址相当于指针*a
{
int left = 0;
int right = s - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (a[mid] > k)
{
right = a[mid] - 1;
}
else if (a[mid] < k)
{
left = a[mid] + 1;
}
else
{
return mid;
}
}
return -1;//找不到
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int key = 7;
int sz = sizeof(arr) / sizeof(arr[0]);//数组元素个数=数组总大小/数组中一个元素的大小
//找到了就返回找到位置的下标
//找不到返回-1
//数组arr传参,实际传递的不是数组本身,仅仅传过去了数组首元素的地址
int ret = binary_search(arr,key,sz);//能代表数组的只有数组名
if (ret==-1)
{
printf("找不到\n");
}
else {
printf("找到了,下标是:%d\n", ret);
}
return 0;
}
4. 写一个函数,每调用一次这个函数,就会将 num 的值增加1。
int main()
{
int num = 0;
//调用函数,使得num每次增加1
return 0;
}
void Add(int* p)
{
(* p)++;
}
int main()
{
int num = 0;
Add(&num);//如果改变实参只能传个地址过去,改变地址 &num传递地址
printf("%d\n", num);//1
Add(&num);
printf("%d\n", num);//2
Add(&num);
printf("%d\n", num);//3
return 0;
}
5. 函数的嵌套调用和链式访问
5.1 嵌套调用
#include <stdio.h>
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i = 0;
for(i=0; i<3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
5.2 链式访问
把一个函数的返回值作为另外一个函数的参数。
#include <stdio.h>
#include <string.h>
int main()
{
char arr[20] = "hello";
int ret = strlen(strcat(arr,"bit"));//这里介绍一下strlen函数
printf("%d\n", ret);
return 0;
}
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//结果是啥?
//注:printf函数的返回值是打印在屏幕上字符的个数
return 0;
}
#include<string.h>
int main()
{
int len = strlen("abc");
printf("%d\n", len);
//链式访问 - strlen返回值做了printf函数的参数
printf("%d\n", strlen("abc")); //把一个函数的返回值作为另外一个函数的参数。
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "bit";
//strcpy(arr1, arr2);//arr2内容拷贝到arr1,strcpy函数的返回值做了printf的参数
printf("%s\n", strcpy(arr1, arr2));
return 0;
}
printf函数 :函数返回的打印在屏幕上的字符的个数
printf函数返回的是其打印在屏幕上字符的个数
int main()
{
printf("%d", printf("%d", printf("%d", 43)));//结果:4321
// printf("%d", 43)打印出43,返回值是2个字符
//printf("%d", printf("%d", 43))打印出2 ,返回1个字符
//printf("%d", printf("%d", printf("%d", 43)))打印出1
}
6. 函数的声明和定义
6.1 函数声明:
int main()
{
int a = 10;
int b = 20;
//函数声明一下-告知
int Add(int, int);//int Add(int x, int y);//只需要函数名、函数参数、参数个数、参数类型、函数返回类型
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
//函数的定义
int Add(int x, int y)
{
return x + y;
}
//函数的定义
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
//函数声明一下-告知
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
6.2 函数定义:
#ifndef __TEST_H__#define __TEST_H__// 函数的声明int Add ( int x , int y );#endif //__TEST_H__
#include "sub.h"
//.lib文件为二进制静态库
//导入静态库
#pragma comment(lib,"sub.lib")
这种分文件的书写形式,在三字棋和扫雷的时候,再教学生分模块来写。
7. 函数递归
7.1 什么是递归?
程序调用自身的编程技巧称为递归( recursion )。也就是说函数自己调用自己。int main() { printf("haha\n"); //结果死循环 main(); return 0; }
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的 一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的主要思考方式在于:把大事化小
7.2 递归的两个必要条件
7.2.1 练习1:(画图讲解)
#include <stdio.h>
void print(int n)
{
if(n>9)
{
print(n/10);
}
printf("%d ", n%10);
}
int main()
{
int num = 1234;
print(num);
return 0;
}
#include <stdio.h>
void print(unsigned int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 1234;
scanf("%u", &num);//1234
print(num);
return 0;
}
递归逻辑:栈操作
栈溢出问题:每一个函数的调用都要在栈区上给自己申请一片空间
每个函数的栈帧空间把内存分配给局部变量,空间不足会栈溢出
不能写死递归,都有跳出空间,每次递归逼近跳出条件,其次,递归层次不能太深
程序员的知乎:www.stackoverflow.con
7.2.2 练习2:(画图讲解)
#include<string.h>
int my_strlen(char* str) //str是个指针,str指向数组arr的首原地址也就是b
{
int count = 0;
while(*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "bit";
//['b']['i']['t']['\0'] \0是字符串的结束标志,我们在计算字符串时不计算它
//模拟实现一个strlen函数
printf("%d\n", my_strlen(arr));//数组名arr相当于首元素的地址-也就是字符b的地址,所以写成char* str
return 0;
}
#include<string.h>
int my_strlen(char* str) //str是个指针,str指向数组arr的首原地址也就是b
{
if (*str != '\0') //* str 解引用拿到的是 b i t,如果是str 那么拿到的是个地址
return 1 + my_strlen(str + 1);
else
return 0;
}
int main()
{
char arr[] = "bit";
//['b']['i']['t']['\0'] \0是字符串的结束标志,我们在计算字符串时不计算它
//模拟实现一个strlen函数
printf("%d\n", my_strlen(arr));//数组名arr相当于首元素的地址-也就是字符b的地址,所以写成char* str
return 0;
}
#incude <stdio.h>
int Strlen(const char*str)
{
if(*str == '\0')
return 0;
else
return 1+Strlen(str+1);
}
int main()
{
char *p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}
遵循从哪里来回哪里去原则
str++ 与++str传进后str值变了,str+1传进后没有改变str值
7.3 递归与迭代
7.3.1 练习3:
int main()
{
int n = 0;
scanf("%d", &n);
int i = 0;
int ret = 1;
//迭代
//循环是迭代的一种
for (i = 1; i <= n; i++)
{
ret = ret * i;
}
printf("%d\n", ret);
return 0;
}
int factorial(int n)
{
if(n <= 1)
return 1;
else
return n * factorial(n-1);
}
有一些功能:可以使用迭代的方式实现,也可以使用递归
7.3.2 练习4:
int count = 0;//全局变量
int Fib(int n)
{
//统计第3个斐波那契数的计算机次数
if (n == 3)
count++;
if (n <= 2)
return 1;
else
return Fib(n-1)+Fib(n-2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n%d" , ret,count++);
return 0;
}
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
int count = 0;//全局变量
int fib(int n)
{
if(n == 3)
count++;
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 = 1;
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;
}
//求n的阶乘
int factorial(int n)
{
int result = 1;
while (n > 1)
{
result *= n ;
n -= 1;
}
return result;
}
//求第n个斐波那契数
int fib(int n)
{
int result;
int pre_result;
int next_older_result;
result = pre_result = 1;
while (n > 2)
{
n -= 1;
next_older_result = pre_result;
pre_result = result;
result = pre_result + next_older_result;
}
return result;
}
函数递归的几个经典题目(自主研究):1. 汉诺塔问题2. 青蛙跳台阶问题 -- 斐波那契数列问题