自定义函数:
有两情况适合把代码封装成自定义函数: 1、代码量过多,一般代码量超过50行就要考虑封装成函数,方便管理代码,提高代码的安全性(程序员平均每50行会出现一个BUG)。 2、如果一个代码需要在不同位置多次执行,为了防止出现代码冗余,就要把它封装成函数,方便重复使用,也能降低可执行文件的大小。
函数声明:
返回值类型 函数名(参数列表);
1、根据函数的功能为函数取名字,在Linux系统下函数名全部小写,多个单词用下划线分隔。 2、参数列表,指的是函数执行时,调用者需要传递它的数据,此时重点关注的是参数的类型,在函数声明时可以忽略参数的名,如果函数执行时不需要调用者传递数据则写void。
返回值类型 函数名(类型名 形参名1,类型名 形参名2,...); 返回值类型 函数名(int n1,int n2,...); 返回值类型 函数名(void);
3、返回值类型,指的是函数的执行结果是什么类型的数据,如果函数没有返回值,则写void。 4、函数声明就是告诉编译器该函数的格式,方便编译器检查调用者的使用是否正确。
5、一般函数声明放在main函数之前
函数定义:
返回值类型 函数名(类型 参数名) { // 函数体 } // 如果函数的定义出现在调用之前,函数声明可以省略
函数调用:
函数名(实参);
1、调用者会把实参赋值给形参变量。 2、函数的返回值会放置在调用位置,可立即使用,也可用变量保存。
练习3:实现一个判断是否是素数的函数,调用它打印出100-1000之间的所有素数
#include <stdio.h> #include <stdbool.h> bool is_prime(int num); int main(int argc,const char* argv[]) { for(int i=100; i<1000; i++) { if(is_prime(i)) printf("%d ",i); } bool flag = is_prime(9999); } bool is_prime(int num) { for(int i=2; i<=num/2; i++) { if(0 == num%i) return false; } return true; }
作业2:输入两个日期yyyy-mm-dd,计算相差多少天,考虑封装函数
#include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <stdlib.h> // 判断闰平年 bool is_leap(uint16_t year); // 计算日期天数函数 uint32_t date_to_days(uint16_t year,uint8_t month,uint8_t day); int main(int argc,const char* argv[]) { uint16_t y = 0; uint8_t m = 0,d = 0; printf("请输入一个日期(yyyy-mm-dd)"); scanf("%hu-%hhu-%hhu",&y,&m,&d); uint32_t day = date_to_days(y,m,d); printf("请再输入一个日期(yyyy-mm-dd)"); scanf("%hu-%hhu-%hhu",&y,&m,&d); printf("间隔了%d天\n", abs((int)date_to_days(y,m,d)-(int)day)); } // 计算日期天数函数 uint32_t date_to_days(uint16_t year,uint8_t month,uint8_t day) { uint32_t sum = day; for(int y=1; y<year; y++) { sum += 365 + is_leap(y); } char arr[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; arr[1] += is_leap(year); for(int m=0; m<month-1; m++) { sum += arr[m]; } return sum; } // 判断闰平年 bool is_leap(uint16_t year) { return 0==year%4 && 0!=year%100 || 0==year%400; }
自定义函数要注意的问题:
1、函数的命名空间互相独立,函数之间传参是单向值传递(实参给形参赋值),所以两个函数之间不能通过传参共享变量。
#include <stdio.h> int func(int num) { printf("func:%d\n",num); num = 100; printf("func:%d\n",num); printf("func:%p\n",&num); return 0; } // 实现一个交换两个变量的值的函数 void swap(int n1,int n2) { printf("swap:n1:%d n2:%d\n",n1,n2); int temp = n1; n1 = n2; n2 = temp; printf("swap:n1:%d n2:%d\n",n1,n2); } int main(int argc,const char* argv[]) { int num = 10; printf("main:%d\n",num); printf("main:%p\n",&num); func(num); printf("main:%d\n",num); int num1 = 10,num2 = 20; printf("num1:%d num2:%d\n",num1,num2); swap(num1,num2); printf("num1:%d num2:%d\n",num1,num2); }
2、C语言中如果函数的参数列表是空的,则意味着该函数提供任意类型、多个参数都可以调用,容易给调用者造成误会,影响代码的可读性,如果函数执行时不需要调用者传递数据则参数列表要写void,不要空着。
#include <stdio.h> int func(void) { printf("func\n"); } int main(int argc,const char* argv[]) { func(); //func(10); //func(3.14,10); //func(3.14,"hehe",10); }
3、如果函数有返回值但没有写return语句,调用该函数时依然有返回值。 当调用一个有返回值的函数时,系统会为调用者和被调用者约定一个空间用于存储返回值,而return语句的作用就是把一个数据存储到这个空间,如果没有写return语句,调用者依然会从共用空间读取返回值,只是读取到的数据是随机的。
#include <stdio.h> int func(void) { //return 10; } int main(int argc,const char* argv[]) { int num = func(); printf("%d\n",num); }
gcc -Wall -Werror xxx.c 可以防止漏写return语句。
xxx.c:x:x: error: control reaches end of non-void function [-Werror=return-type]
4、当使用数组作为函数的参数传递时,它的长度信息就丢失了,(数组会蜕变成指针),无法使用sizeof计算数组的长度,需要调用者额外提供一个参数作为数组的长度。
void show_arr(int arr[],size_t len) { for(int i=0; i<len; i++) { printf("%d ",arr[i]); } } int main(int argc,const char* argv[]) { int arr[] = {1,2,3,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5}; show_arr(arr,sizeof(arr)/sizeof(arr[0])); }
练习4:定义一个函数,功能是对一个数组进行升序排序
5、当函数中使用数组作为参数传递是,是"址传递" ,是可以被函数所共享数组
6、当使用二维数组作为函数的参数时,C语言规则定义二维数组时必须有列数,所以要行、列数在前,数组在后,并且把列数设置给数组。
void show_arr(int row,int col,int arr[][col]) { for(int i=0; i<row; i++) { for(int j=0; j<col; j++) { printf("%d ",arr[i][j]); } printf("\n"); } } int main(int argc,const char* argv[]) { int arr[2][5] = { {1,2,3,4,5}, {2,3,4,5,6} }; show_arr(2,5,arr); return 0; }