函数的声明和定义
函数声明:
1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,具体是不是存在无关紧要
2.函数声明一般出现在函数调用之前。要满足先声明后使用 。
3.函数定义放在函数调用的前面本身就是一种特殊的函数定义。
4.函数的声明一般放在头文件中。
函数定义:
函数的定义是指函数的具体实现,交代函数的功能实现。
首先我们来看一个例子:写一个函数,完成两个数的相加。
#include<stdio.h> int Add(int num1, int num2);//函数声明 int main() { int num1 = 0; int num2 = 0; scanf("%d %d", &num1, &num2); int ret=Add(num1, num2);//函数调用 printf("%d", ret); return 0; } int Add(int num1, int num2)//函数定义 { return (num1 + num2); }
#include<stdio.h> int Add(int num1, int num2)//函数定义本身就是一种特殊的声明 { return (num1 + num2); } int main() { int num1 = 0; int num2 = 0; scanf("%d %d", &num1, &num2); int ret = Add(num1, num2);//函数调用 printf("%d", ret); return 0; }
实际上,在工程中,代码比较多,函数一般是放在.h头文件中 声明,.c文件中实现的,这种分文件的书写,只要使用头文件即可。#include"add.h"。
""是我们自己创建的头文件。<>是库函数使用的头文件。
那多个文件怎样合到一起实现的呢?
函数的参数
实际参数(实参)
真实传给函数的参数,叫实参。
实参可以是:常量,变量,表达式,函数等。
无论实参是何种类型的量, 在进行函数调用时都必须有确定的值,以便把这些值传给形参。
get_max(3,5);//常量 get_max(a,b);//变量 get_max(a,3); get_max(3,get_max(3,4));//函数 get_max(3,3+5);//表达式
形式参数(形参)
形式参数时指函数名后括号中的变量。
因为形式参数只有在函数被调用的过程中猜实例化(分配内存单元)(开辟空间),
所以才叫形参数。
形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
形参实例化之后其实相当于实参的一份临时拷贝。(上文我们提到过的Swap函数)
函数的调用
传值调用
函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参。见上文Swap例子。
传址调用
传址调用把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。见上文Swap函数例子。
>>>>>>>>>>>>那我们什么时候用传值和传址呢?传址就是传递地址吗?
我们需要修改来自外部的变量时,我们就使用传址。不需要就用传值调用。
传址调用不一定就是传递地址。
void get(int* x, int* y) { // } #include<stdio.h> int main() { int* p1; int* p2; get(p1,p2);//传值调用 return 0; }
所以传递地址不一定时传址调用,还可以是传值调用。
练习
1. 写一个函数可以判断一个数是不是素数。 2. 写一个函数判断一年是不是闰年。 3. 写一个函数,实现一个整形有序数组的二分查找。 4. 写一个函数,每调用一次这个函数,就会将num的值增加1
NO1.打印素数
//题目1 //打印素数和素数的个数 //写一个代码:打印100~200之间的素数用函数封装
//打印100~200的素数 #include<stdio.h> #include<math.h> //是素数返回1 //不是返回0 int is_prime(int x) { int j = 0; for (j = 2; j <= sqrt(x); j++) { if (x % j == 0)//不是素数 return 0; } return 1;// } int main() { int i = 0; //打印100~200之间的奇数 for (i = 101; i <= 200; i += 2)//奇数条件 { if(is_prime(i));//判断是否是素数 //为真就执行,为假不执行 printf("%d ",i); } return 0; }
与此同时,当我们面临真假两个选择时我们还可以选用布尔类型!
布尔类型
//C语言中有一个布尔类型 //C99引入的 //_Bool类型的变量只有2种取值,true和false #include<stdio.h> #include<math.h> #include<stdbool.h> _Bool is_prime(int x) { int j = 0; for (j = 2; j <= sqrt(x); j++) { if (x % j == 0)//不是素数 return false;//0 } return true;//0 }
NO2.打印闰年
//题目2 //打印闰年 //打印1000年到2000年之间的闰年,用函数封装
#include<stdio.h> #include<stdbool.h> _Bool is_leap_year(int x) { if ((x % 4 == 0 && x % 100 != 0) || x % 400 == 0) return true; else return false; } int main() { int i = 0; int count = 0; for (i = 1000; i <= 2000; i++) { if (is_leap_year(i)) { printf("%d ", i); count++; } } printf("\n%d", count); return 0; }
或者
#include<stdio.h> #include<stdbool.h> _Bool is_leap_year(int x) { return ((x % 4 == 0 && x % 100 != 0) || x % 400 == 0); } //逻辑&&||返回值就是正确真1 错误假0
NO3.二分查找
//写一个函数,实现一个整形有序数组的二分查找。
#include<stdio.h> int binary_search(int arr[],int i,int size) { int left = 0; int right = size - 1; while (left <= right) { int mid = (left + right) / 2; if (arr[mid] < i) { //right不变 left = mid + 1; } else if (arr[mid] > i) { //left不变 right = mid - 1; } else { return mid; } } return -1; } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; scanf("%d", &i); int size = sizeof(arr) / sizeof(arr[0]); //实现二分查找 int ret=binary_search(arr,i,size); if (ret == -1) printf("没找到"); else printf("找到了,下标是:%d\n", ret); return 0; }
关于数组作为函数参数传递
以下这种代码为何是错误的❌❌❌?
❓数组名作为函数实参时,向形参(数组名和指针变量)传递的是数组首元素地址。数组退化成了指针,sizeof求的是指针大小。后续学习了数组和指针在详细讲解。
关于mid=(left+right)/2
我们使用left+right,如果left和right的值很大,在整个值容易溢出。
#include<stdio.h> int main() { int right =2147483746;//int容忍最大值2147483747 int left = 2147483746; int mid = (right + left) / 2; printf("%d", mid); return 0; }
防范于未然,那我们就必须换一种方式。
修改之后代码:
int binary_search(int arr[], int i, int size) { int left = 0; int right = size - 1; while (left <= right) { //int mid=(left+right)/2; int mid = left+(right-left)/2; if (arr[mid] < i) { //right不变 left = mid + 1; } else if (arr[mid] > i) { //left不变 right = mid - 1; } else { return mid; } } return -1; }
NO4.调用函数num+1
写一个函数,每调用一次这个函数,就会将num的值增加1
#include<stdio.h> void Add(int* num) { *num = *num + 1; } int main() { int num = 0; Add(&num); printf("%d", num); return 0; }
#include<stdio.h> int Add(int num) { return num + 1; } int main() { int num = 0; num=Add(num); printf("%d", num); return 0; }
函数的嵌套调用
函数和函数之间可以有机结合。
函数可以嵌套调用,但是不能嵌套定义 。
嵌套调用:
#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; }
嵌套定义:❌❌❌
//嵌套定义❌ void test() { int Add(int a, int b) { return a + b; } } #include<stdio.h> int main() { return 0; }
函数的链式访问
把一个函数的返回值作为另外一个函数的参数。
#include <stdio.h> #include <string.h> int main() { char arr[20] = "hello"; char ch[10] = {0}; int ret = strlen(strcat(arr, "bit"));//库函数查询的strcat函数 printf("%d\n", ret); printf("%d\n", strlen(strcpy(ch,arr))); return 0; }
那下文这个代码的输出是多少呢?
#include <stdio.h> int main() { printf("%d", printf("%d", printf("%d", 43))); //结果是啥? return 0; }
---------------------------------------4321.自己动手算一算哦
头文件
最后随着我们使用的函数越来越多,所需要的头文件也越来越多。我们可以自己造一个头文件,把所有头文件添加上去。以后就可以直接使用自己创造的头文件了!!
✔✔✔✔✔感谢大家的阅读,欢迎指正错误和不足!!