1.函数
1.1c语言中函数的分类:
1.1.1库函数
在开发的过程中每个程序员都可能用的到,并且为了支持可移植性和提高程序的效率,所以c语言的基础库中提供的一系列类似的库函数,为了方便程序员进行软件的开发。
c语言常用的库函数有:
IO函数 |
字符串操作函数 |
内存操作函数 |
时间/日期函数 |
数学函数 |
字符操作函数 |
其他库函数 |
注:使用库函数时必须包含#include对应的头文件。
如何使用库函数:www.cplusplus.com中有详细的介绍可自行查看。
1.1.2自定义函数
自定义函数和库函数一样,有函数名,返回值类型和函数参数,但不一样的是这些都是我们自己来设计的,给了程序员更多的发挥空间。
1.2函数的参数
1.2.1实际参数(实参)
真是传给函数的参数,实参可以是:常量,变量,表达式,函数等。无论实参是何种类型的量,在进行函数调用的时候他们都必须有确定的值,以便把这些值传送给形参。
1.2.2形式参数(形参)
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(形参实例化之后其实就相当于实参的一份临时拷贝)。形式参数当函数调用完后就自动销毁了,因此形式参数只在函数中有效。
1.3函数的调用
1.3.1传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
1.3.2传址调用
把函数外创建变量的内存地址传递给函数参数的一种调用函数的方式,这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
例子:
#include<stdio.h>
void Change(int* px,int* py) //这里的px,py属于形参
{
int z = *px;
*px = *py; //change函数中属于传址调用
*py = z;
}
int add(int x, int y) //这里的x,y属于形参
{
int z = 0;
z = x + y; //add函数中属于传值调用
return z;
}
int main()
{
int a = 0; //这里的a,b,c属于实参
int b = 0;
int c = 0;
scanf("%d %d", &a, &b);
printf("a=%d b=%d\n", a, b);
Change(&a,&b);
printf("a=%d b=%d", a, b);
c = add(a, b);
printf("\nc=%d", c);
return 0;
}
//在这个代码中Change和add属于自定义函数,void和int属于返回值类型
计算100~200之间的素数
#include<stdio.h>
int main()
{
int i = 0;
int count = 0;
for (i = 100; i <= 200; i++)
{
int j = 0;
int ob = 1;
for (j = 2; j <= sqrt(i); j++)
{
if (i % j == 0)
{
ob = 0;
break;
}
}
if (ob == 1)
{
printf("%d ", i);
count++;
}
}
printf("\ncount=%d", count);
return 0;
}
1.4函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合,也就是相互调用。
1.4.1嵌套调用
在函数主体内调用一个子函数时,在这个子函数的主体内可以在进行函数的调用。
#include<stio.h>
void title()
{
printf("##### 1.play #####\n");
printf("##### 0.exit #####\n");
}
int start_play()
{
title();
int b = 0;
scanf("%d", &b);
return b;
}
void game()
{
int a = start_play();
if (a == 1)
{
printf("开始玩游戏");
}
else
{
printf("退出游戏");
}
}
int main()
{
game();
return 0;
}
注:函数可以嵌套调用,但是不能嵌套定义。
1.4.2链式访问
把一个函数的返回值作为另外一个函数的参数。
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "Welcome_to_Beijing";
int len = strlen(arr);
printf("%d\n", len);
printf("%d", (int)strlen(arr)); //通过对比这两个打印的结果一样
return 0;
}
1.5函数的声明和定义
1.5.1函数声明
(1)告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了,因为函数声明只是说有这么一个函数而已,并不包括具体的实施方式。
(2)函数的声明一般出现在函数的使用之前。要满足声明后使用。
(3)函数的声明一般要存放在头文件中。
1.5.2函数定义
函数的定义是指函数的具体实现,交代函数的功能实现。
首先在头文件中建立一个头文件,例如下面的add.h和sub.h文件,用来放函数声明。
//函数的声明
int add(int x, int y);
int sub(int x, int y);
其次在源文件中创建一个源文件,例如下面的add.c和sub.c文件,用来放函数定义。
//函数的定义
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y )
{
return x - y;
}
最后在所创建的主源文件中引用自创的头文件名即可,注意这里用#include引用自创文件时用“ ”即可,例如#include“sub.h”。
#include"add.h"
#include"sub.h"
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int sum = add(a, b);
int dif = sub(a, b);
printf("%d %d", sum, dif);
return 0;
}
注:把这自创函数放在三个文件中写的话可以在大工程中更好的划分个人工作,把他分成几部分来完成,而不是所有人都在主文件中写,同时如果你不想让别人知道你的代码的话还可以把你的函数源文件加密一下,例如下面:
这里以visual studio做的演示。
创建好你的头文件和源文件,然后右击你的文件名点击属性,在属性面板中把配置类型改为静态库然后应用;
确定完后ctrl+f5应用一下,即可在你的对应文件夹中找到一个.lib文件,既是编译成的静态库。
解用,如下:
将你加密好的.lib文件和.h文件复制到主函数的源文件夹内,然后把相应的.h头文件加载进来;
#include<stdio.h>
#include"add.h"
#pragma comment(lib, "2024_8_28_add.lib") //导入静态库,参数为文件类型+文件名字
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int sum = add(a, b);
printf("%d ", sum);
return 0;
}
1.6函数递归
程序调用自身的编程技巧称为递归。递归策略是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归的好处是只需要少量的程序就可以描述出解题过程所需的多次重复计算,大大地减少了程序的代码量(把大事化小)。
1.6.1递归的两个必要条件
1)存在限制条件,当满足这个限制条件的时候,递归便不再继续;
2)每次递归调用之后越来越接近这个限制条件。
例1:将一个无符号整型1234按照顺序依次打印
#include<stdio.h>
void print(unsigned int a)
{
if (a > 9)
{
print(a / 10);
}
printf("%d ", a % 10);
}
int main()
{
unsigned int arr = 0;
scanf("%u", &arr);
print(arr);
return 0;
}
递归解释流程图:
注:如果条件设置不合理的话可能会出现Stack overflow错误,也就是栈溢出。
例2:编写不允许创建临时变量,求字符串的长度
#include<stdio.h>
#include<string.h>
1)带临时变量的写法
int culture(char* p)
{
int count = 0;
while (*p != '\0')
{
count++;
p++;
}
return count;
}
int main()
{
char arr[] = " nihao ";
int len = culture(arr);
printf("%d\n", len);
return 0;
}
2)不带临时变量的写法
int culture(char* p)
{
if (*p != '\0')
{
return 1 + culture(p+1);
}
else
{
return 0;
}
}
int main()
{
char arr[] = " nihao ";
int len = culture(arr);
printf("%d\n", len);
return 0;
}
1.7递归与迭代
1)求n的阶乘(不考虑溢出)
#include<stdio.h>
1)迭代的方式
int fac(int n)
{
int i = 0;
int a = 1;
for (i = 1;i <= n; i++)
{
a *= i;
}
return a;
}
2)递归的方式
int fac(int n)
{
if (n <= 1)
return 1;
else
return n * fac(n - 1);
}
int main()
{
int c = 0;
scanf("%d", &c);
int ret = fac(c);
printf("ret=%d\n", ret);
return 0;
}
2)求第n个斐波那契数列(1, 1, 2, 3, 5, 8, 13, 21……)
#include<stid.h>
1)递归写法
int fib(int x)
{
if (x <= 2)
return 1;
else
return fib(x - 1) + fib(x - 2);
}
2)迭代写法
int fib(int x)
{
int a = 1;
int b = 1;
int c = 0;
while (x >= 3)
{
c = a + b;
a = b;
b = c;
x--;
}
return c;
}
int main()
{
int arr = 0;
scanf("%d", &arr);
int ret =fib(arr);
printf("%d", ret);
return 0;
}
在斐波那契数列的计算中当计算比较大的数时可以发现迭代的算法要比递归快的多,而且使用递归可能会发生栈溢出的情况,这就使得我们在写函数的时候要考虑到这个计算问题。