概述
函数:
什么是函数:C语言中函数就是具有某项功能的代码段,它是C语言管理代码的最小单位(英言是function),早期被翻译成函数,就一直沿用了,现在新的编程语言都翻译成方法,与数学中的函数无关。
函数把具有某些功能的若干行代码封装在函数中方便管理代码且方便重复调用,函数会被编译器翻译成二进制指令存储在代码段中,函数名就是函数在代码中的首地址,韩式即是一段数据,也是一种数据类型。
C语言中没有输入、输出语句和内存管理语句,只能使用标准库的函数。
函数的分类:
标准库函数:C语言标准委员会为C语言以函数形式提供一些基础功能,这些函数被封装在libc.so库文件中,使用时需要包含相应的头文件。
系统函数:操作系统为用户提供的一些高级功能,它们以函数调用的形式使用,但它们本质不是函数调用,后续会详细讲解。
第三方库函数:一些开源作者和公司把一些具有特殊的功能代码、算法、数据结构封装成库或SDK,供程序员使用。
自定义函数:程序员为了方便的管理代码、代码复用,而把代码封装成一个个函数。
#include <time.h>
time_t time(time_t *tloc);
功能:获取当前系统时间,它会返回自1970年1月1日 00:00:00 到现在一共过了多少秒(格林时间)
int rand(void)
功能:返回一个随机整数
Void srand(unsigned int seed);
功能:设置随机数的种子
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc,const char* argv[])
{
// 设置随机种子
srand(time(NULL));
// 伪随机数:把所有的整数打乱顺序存储在计算机中,rand就是从序列中获取一个数,默认从第一个位置开始获取,程序运行如果不改获取的位置,那么每次获取的随机数都一样。
for(int i=0; i<10; i++)
{
printf("%d n",rand());
}
return 0;
}
int abs(int j);
功能:求绝对值
double pow(double x);
功能:计算x的y次方
注意:使用pow、sqrt这种数学函数是,需要额外链接数学库,gcc xxx.c -lm
int system(const char *command);
功能:调用系统命令
练习1:随机产生10个[10,99]的整数,按照升序输出。 rand()%(b-a)+a ---> [a,b)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc,const char* argv[])
{
srand(time(NULL));
int arr[10];
for(int i=0,j; i<10; i++)
{
int num = rand() % 90 + 10;
for(j=i; j>0 && num < arr[j-1]; j--)
{
arr[j] = arr[j-1];
}
arr[j] = num;
}
for(int i=0; i<10; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
练习2:随机生成一注双色球彩票号,红球一共6组,每组从1-33中抽取一个,六个互相不重复。然后蓝球是从1-16中抽取一个数字,这整个组成的双色球彩票。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc,const char* argv[])
{
char red[6] = {} , cnt = 0 , i;
srand(time(NULL));
printf("red:");
while(cnt < 6)
{
// 随机生成一个[1,33]之间的整数
int num = rand() % 33 + 1;
// 查询num是否重复
for(i=0; i<cnt; i++)
{
// 如果num在数组出现过则循环提前结束
if(num == red[i])
break;
}
// for循环没有提前结束,num未出现过
if(i == cnt)
{
red[cnt++] = num;
printf("%d ",num);
}
}
printf("blue:%dn",rand()%16+1);
return 0;
}
自定义函数:注意:函数声明要和函数定义放在一起编译,防止出现声明与定义不符的情况,导致执行结果出问题。
什么情况需要封装函数:
1、代码量过多,一般超过50行就要额外再封装一个函数
2、一般代码需要在不同的位置多次执行,封装成函数后方便复用
3、当代码的业务逻辑比较复杂,按功能拆封成函数后,方便清理逻辑
函数声明:------->就是告诉编译器该函数的格式,方便编译器检查调用
返回值类型 函数名(参数列表);
1、函数声明是为了让编译器检查函数调用时提供参数是否正确,如果函数定义出现在调用语句前,函数声明可以省略。
2、函数参数就是函数执行调用者传递的数据,如果函数的功能不需要调用者提供的参数,则在参数列表中写void。
3、如果函数没有需要返回的数据则把返回值类型写为void。
4、如果参数列表时空的,则代表调用者可以使用任意多个任意类型的实参调用该函数,虽然方便但会影响代码的可读性,容易给人造成误会,使用函数执行时不需要调用者传递数据,则参数列表最好写void。
5、如果函数之间出现互相调用的情况,则必须写函数声明。
6、在多文件编程时,一般函数声明写在头文件中,函数定义写在源文件中。
7、根据函数的功能取名字,在Linux系统下函数全部小写,用下划线分隔。
函数定义:
返回值类型 函数名(参数列表)
{
// 函数体
}
函数调用:
类型 变量 = 函数名(实参);
函数执行完毕后,返回值会放置在函数的调用位置,可以直接使用,也可以用变量保存。
自定义函数要注意的问题:
1、函数的命名空间互相独立,可以定义同名的局部变量,再加上实参以赋值方式传递形参,所以就造成函数之间共享变量困难(全局变量、指针)。
2、如果函数有返回值但没有写return语句,调用该函数时依然有返回值。
当调用一个有返回的函数时,系统会为调用者和被调用者约定一个共用空间用于存储返回值,而return语句的作用就是把一个数据存储到这个共用空间,如果没有写return语句,调用者依然会从共用空间读取返回值,只是读取到的数据是随机的。
gcc -Wall -Werror xxx.c 可以防止漏写return语句。
3、当使用数组作为函数的参数时,数组会蜕变成指针,无法使用sizeof计算数组的长度,需要调用者额外提供一个参数作为数组的长度。
void func(int arr[100])
{
printf("%dn",sizeof(arr)); // 结果是4
arr++; // 编译不会报错,因为arr已经蜕变成指针变量,可以自加
}
4、当使用二维数组作为函数的参数时,C语言规则定义二维数组时必须有列数,所以要行、列数在前,数组在后,并且把列数设置给数组。
void func(int row,int col,int arr[][col])
{
}
5、当调用函数时如果该函数没声明,也没有定义,编译器不会立即报错,而是尝试猜测这个函数的格式,根据实参猜测形参列表,返回值猜为int类型,这种猜函数格式的能力叫函数隐式声明。
练习3:实现一个判断素数的函数,调用它打印出100~1000之间所有的素数。
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
bool is_prime(int num)
{
for(int i=2; i<=sqrt(num); i++)
{
if(0 == num%i)
return false;
}
return true;
}
int main(int argc,const char* argv[])
{
for(int i=100; i<1000; i++)
{
if(is_prime(i))
printf("%d ",i);
}
return 0;
}
练习4:输入一个日期,计算出它与2022-4-24之间相隔多少天。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
bool is_leap(short y)
{
return 0==y%4 && 0!=y%100 || 0==y%400;
}
int date_to_days(short year,char month,char day)
{
int sum = day;
// 计算整年的天数
for(int y=1; y<year; y++)
{
sum += 365+is_leap(y);
}
char arr[12] = {0,31,28,31,30,31,30,31,31,30,31,30};
arr[2] += is_leap(year);
// 计算整月的天数
for(int m=1; m<month; m++)
{
sum += arr[m];
}
return sum;
}
int main(int argc,const char* argv[])
{
int year,month,day;
printf("请输入一个日期:");
scanf("%d %d %d",&year,&month,&day);
printf("%dn",abs(date_to_days(year,month,day)-date_to_days(2022,4,24)));
return 0;
}
2048游戏:
二维数组[4][4],整个项目中都可以使用。
for(;;)
{
// 显示数组
// 获取方向键
// 根据方向键移动可合并数组中的数字
// 在数组中随机生成一2
// 判断游戏是否还能继续
}
#include <stdio.h>
#include <getch.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
int arr[4][4];
bool is_move_merge = true;
// 显示数组
void show_arr(void)
{
system("clear");
for(int i=0; i<4; i++)
{
printf("---------------------n");
for(int j=0; j<4; j++)
{
switch(arr[i][j])
{
case 2 ... 8: printf("| %d ",arr[i][j]); break;
case 16 ... 64: printf("| %d ",arr[i][j]); break;
case 128 ... 512: printf("| %d",arr[i][j]); break;
case 1024 ... 8192: printf("|%d",arr[i][j]); break;
default: printf("| "); break;
}
}
printf("|n");
}
printf("---------------------n");
}
// 统计空位置的数量
bool is_null(void)
{
for(int i=0; i<4; i++)
{
for(int j=0; j<4; j++)
{
if(0 == arr[i][j])
return true;
}
}
return false;
}
// 随机生成一个2
void rand_num(void)
{
bool flag = is_null();
while(is_move_merge && flag)
{
int row = rand() % 4;
int col = rand() % 4;
if(0 == arr[row][col])
{
arr[row][col] = 2;
break;
}
}
}
// 移动或合并
void up_move_merge(void)
{
// 按列处理数据
for(int c=0; c<4; c++)
{
// 从第二行开始向上移动或合并
for(int r=1; r<4; r++)
{
// 当数字为0时不需要移动、合并
if(0 == arr[r][c])
continue;
// 向上寻找arr[r][c]最终移动的位置
int d = -1;
for(int m=r-1; m>=0 && (!arr[m][c] || arr[r][c]==arr[m][c]); m--)
{
d = m;
}
// 把arr[row][col]移动到最终位置
if(-1 != d)
{
arr[d][c] += arr[r][c];
arr[r][c] = 0;
is_move_merge = true;
}
}
}
}
void down_move_merge(void)
{
}
void left_move_merge(void)
{
}
void right_move_merge(void)
{
}
// 判断游戏是否结束
bool is_over(void)
{
// 1、没有空位置
// 2、左右相邻的位置没有相同数字
// 3、上下相邻的位置没有相同数字
// 满足以上三个条件游戏结束
}
int main(int argc,const char* argv[])
{
srand(time(NULL));
for(;;)
{
rand_num();
show_arr();
if(is_over())
{
printf("游戏结束!n");
break;
}
is_move_merge = false;
switch(getch())
{
case 183: up_move_merge(); break;
case 184: down_move_merge(); break;
case 185: right_move_merge(); break;
case 186: left_move_merge(); break;
case 'q': return 0;
}
}
return 0;
}
函数之间参数传递:
参数传递:
1、调用函数时,调用者与被调用者是一种值传递,是把实参给形参赋值的过程(内存拷贝),在函数运行过程中如果形参的值被修改与实参无关。
2、当数组作函数的参数时它就会蜕变成指针,不能再使用sizeof计算它的长度,需要额外增加一个参数传递数组长度。
void func(int arr[],size_t len);
3、当二维数组作函数的参数时,数组的行数组可以不确定,但列数必须是确定的,所以要先传递数组的列数、行数变量,再传递二维数组。
void func(int row,int col,int arr[row][col]);
练习1:实现一个排序函数。
#include <stdio.h>
void func(int arr[],size_t len)
{
for(int i=0; i<len; i++)
{
printf("%d ",arr[i]);
}
}
void func_arr(int row,int col,int arr[][col])
{
}
int main(int argc,const char* argv[])
{
int arr[] = {9,3,5,4,3,9,7,6,6,5,5,3,4,4,4,6};
func(arr,sizeof(arr)/sizeof(arr[0]));
int arr2[][3] = {{1,2,3},{2,3,4},{4,5,6}};
func_arr(3,3,arr2);
return 0;
}
返回值传递:
当调用一个带返回值的函数时,调用者和被调用者之间会约定一个位置用于存储返回值,return语句是它一个数据存储到约定的位置,如果被调用函数中没有执行return语句,调用者依然会从约定的位置读取返回值,只是这种情况读取到的是随机数据。
函数递归:
函数自己调用自己的行为叫递归,会形成循环结构,解决一些复杂的问题。
递归与循环语句的区别:
1、函数递归调用时每调用一次就是定义形参数变量、局部变量,它会记录循环过程中的执行状态,需要占用很多内存,并且创建与销毁时耗费很多时间。
2、函数内可以多次调用自己,这种形成的树型的执行结构。
总结:递归比循环语句速度慢、使用的内存多,能使用循环解决的问题,不要使用递归,但递归可以解决循环处理不了的问题,比如需要记录循环过程的状态,解决树型结构的问题。
使用递归要注意的问题:
1、一旦执行递归就会形成死循环,所以要优先考虑如何结束。
2、在递归函数中不能定义过多的变量、数组,以免栈内存不够使用,造成栈崩溃、段错误。
3、解决问题的时间是否能达到上级要求。
练习2:计算出第n项斐波那切数列的值,使用递归解决。
1 1 2 3 5 8 13 21 …
#include <stdio.h>
int func(int n)
{
if(1==n || 2==n)
return 1;
return func(n-1)+func(n-2);
}
int main(int argc,const char* argv[])
{
int n;
scanf("%d",&n);
printf("%dn",func(n));
return 0;
}
练习3:显示出N层汉诺塔的移动过程,这是一种典型的需要递归解决的问题,因为需要在执行过程中记录起始柱、中转柱、目标柱的变化过程。
A B C N个盘子
N-个盘子从A移动到C
N号盘子从A移动到B
N-个盘子从C移动到B
起始柱 中转柱 目标柱
#include <stdio.h>
void han(char src,char dest,char in,int n)
{
if(1 == n)
{
printf("%d号盘子从%c移动到%cn",n,src,dest);
return;
}
han(src,in,dest,n-1);
printf("%d号盘子从%c移动到%cn",n,src,dest);
han(in,dest,src,n-1);
}
int main(int argc,const char* argv[])
{
int n;
scanf("%d",&n);
han('A','B','C',n);
return 0;
}
练习4:在3*3的二维数组中填入1~9,使用数组的使用数组中每行、每列、对角线的和相等。
[1][2][3][4][5][6][7][8][9]
#include <stdio.h>
void array(int arr[],int len,int n)
{
if(n >= len)
{
int sum1 = arr[0]+arr[1]+arr[2];
int sum2 = arr[3]+arr[4]+arr[5];
int sum3 = arr[6]+arr[7]+arr[8];
int sum4 = arr[0]+arr[3]+arr[6];
int sum5 = arr[1]+arr[4]+arr[7];
int sum6 = arr[2]+arr[5]+arr[8];
int sum7 = arr[0]+arr[4]+arr[8];
int sum8 = arr[2]+arr[4]+arr[6];
if(sum1 == sum2 && sum2==sum3 && sum3==sum4 && sum4==sum5 && sum5==sum6 && sum6==sum7 && sum7 == sum8)
{
for(int i=0; i<len; i++)
{
printf("%d ",arr[i]);
if(0 == (i+1)%3)
printf("n");
}
printf("-----------------n");
}
return;
}
array(arr,len,n+1);
for(int i=n+1; i<len; i++)
{
int tmp = arr[i]; arr[i] = arr[n]; arr[n] = tmp;
array(arr,len,n+1);
tmp = arr[i]; arr[i] = arr[n]; arr[n] = tmp;
}
}
int main(int argc,const char* argv[])
{
int arr[] = {1,2,3,4,5,6,7,8,9};
array(arr,9,0);
return 0;
}
练习5:编程解决八皇后问题。
在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
[0] [0][0] [0][1] [0][2] [0][3] [0][4] [0][5] [0][6] [0][7]
[1] [1][0] [1][1] [1][2] [1][3] [1][4] [1][5] [1][6] [1][7]
[2] [2][0] [2][1] [2][2] [2][3] [2][4] [2][5] [2][6] [2][7]
[3] [3][0] [3][1] [3][2] [3][3] [3][4] [3][5] [3][6] [3][7]
[4] [4][0] [4][1] [4][2] [4][3] [4][4] [4][5] [4][6] [4][7]
[5] [5][0] [5][1] [5][2] [5][3] [5][4] [5][5] [5][6] [5][7]
[6] [6][0] [6][1] [6][2] [6][3] [6][4] [6][5] [6][6] [6][7]
[7] [7][0] [7][1] [7][2] [7][3] [7][4] [7][5] [7][6] [7][7]
i代表行 , arr[i]代表列
7-(arr[i]-i)
#include <stdio.h>
#include <stdbool.h>
int cnt = 0;
void array(int arr[],int len,int n)
{
if(n >= len)
{
bool flag1[15] = {} , flag2[15] = {};
for(int i=0; i<len; i++)
{
if(flag1[i+arr[i]] || flag2[7-(arr[i]-i)])
return;
flag1[i+arr[i]] = true;
flag2[7-(arr[i]-i)] = true;
}
for(int i=0; i<8; i++)
{
for(int j=0; j<8; j++)
{
if(arr[i] == j)
printf("@ ");
else
printf("* ");
}
printf("n");
}
printf("----------%d------------n",++cnt);
return;
}
array(arr,len,n+1);
for(int i=n+1; i<len; i++)
{
int tmp = arr[i]; arr[i] = arr[n]; arr[n] = tmp;
array(arr,len,n+1);
tmp = arr[i]; arr[i] = arr[n]; arr[n] = tmp;
}
}
int main(int argc,const char* argv[])
{
int arr[8] = {0,1,2,3,4,5,6,7};
array(arr,8,0);
return 0;
}
最后
以上就是大力指甲油为你收集整理的C语言---函数的全部内容,希望文章能够帮你解决C语言---函数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复