1、定义
一个完成特定功能的代码模块,可以实现代码的复用。
2、格式
存储类型 数据类型 函数名(参数列表)
{
//功能代码 函数体
}
3、调用
函数调用:函数名(参数);
例如:
#include <stdio.h>
#include <string.h>
//输出 今天不下雨了
int fun()
{
printf("今天不下雨了\n");
}
int main(int argc, char const *argv[])
{
fun();//函数的调用
fun();//函数的调用
fun();//函数的调用
fun();//函数的调用
fun();//函数的调用
return 0;
}
4、函数有一下几种情况:
1)有参数时,正常情况下在小括号中加入参数。
参数分为:形参与实参。
形参:定义在小括号中的参数,用于实现接收实参的数据。
实参:调用函数时写在小括号中的函数,用来实现给形参传递数据。
例如:
#include <stdio.h>
#include <string.h>
//计算两个数的和
int fun(int a,int b)//形参
{
printf("a+b=%d\n",a+b);
}
int main(int argc, char const *argv[])
{
fun(1,2 );//实参
fun(100,2 );//实参
return 0;
}
2)没有参数:参数列表可省略,也可以用void
例如:
#include <stdio.h>
#include <string.h>
//计算两个数的和
int fun(void)//形参
{
}
int main(int argc, char const *argv[])
{
fun();//实参
return 0;
}
3)有返回值:要根据返回值的数据类型定义函数的数据类型
例如:
#include <stdio.h>
#include <string.h>
//计算两个数的和
int fun(void)//形参
{
int a=101;
return a;
}
int main(int argc, char const *argv[])
{
int ret= fun();
printf("%d\n",ret);
return 0;
}
4)没有返回值:数据类型为void,函数内部没有return结构
例如:
void fun(void)//没有返回值
{
int a=101;
printf("12345678");
}
5)定义子函数时可以直接定义在主函数上面,如果定义在主函数下面时需要提前声明函数
函数的声明:存储类型 数据类型 函数名(参数列表)
例如:
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fun(void);
fun();
return 0;
}
int fun(void)//形参
{
int a=101;
printf("12345678");
}
5、return的作用:
1、返回return右边的数据给外界
2、阻断代码的执行
6、函数传参
1)值传递
实参-->形参(单向传递) 修改形参不会影响实参
例如:
#include <stdio.h>
#include <string.h>
// 计算字符串中字符的个数
void fun(int a) // str:字符串 s:字符
{
a = 200;
}
int main(int argc, char const *argv[])
{
int a = 10;
fun(a);
printf("%d\n", a);
return 0;
}
2)地址传递
双向传递 形参的改变会影响实参
例如:
#include <stdio.h>
#include <string.h>
// 计算字符串中字符的个数
void fun(int *a, int *b) // str:字符串 s:字符
{
int c = *a;
*a = *b;
*b = c;
printf("子函数:a=%d b=%d\n",*a,*b);
}
int main(int argc, char const *argv[])
{
int a = 10;
int b = 20;
fun(&a,&b);
printf("主函数:a=%d b=%d\n",a,b);
return 0;
}
3)数组传递
与地址传递相同
#include <stdio.h>
#include <string.h>
void fun(int a[],int n){//int *p
for (int i = 0; i < n; i++)
{
printf("%d ",a[i]);
}
}
int main(int argc, char const *argv[])
{
int a[3]={1,2,3};
fun(a,3);
return 0;
}
7、堆区空间
1)手动开辟堆区空间,用到函数 malloc
头文件: #include <stdlib.h>
void *malloc(size_t size);
功能:开辟堆区空间
参数:size:开辟空间的大小(字节)
返回值:无
例如:
成功:返回开辟成功的首地址
失败:NULL;
int *p = (int *)malloc(sizeof(int) * 4);
if (p == NULL)
{
printf("err\n");
}
printf("%p\n", p);
注意:malloc通常与free搭配使用
2)free
#include <stdlib.h>
void free(void *ptr);
功能:释放堆区空间
参数:ptr:要释放空间的地址
返回值:无
例如:
void *p = malloc(sizeof(int) * 4);
if (p == NULL)
{
printf("err\n");
}
printf("%p\n", p);
free(p);
p=NULL;
堆和栈的主要区别:
1、栈由系统自动分配,而堆是人为申请开辟;
2、栈获得的空间较小,而堆获得的空间较大;
3、栈由系统自动分配,速度较快,而堆一般速度比较慢;
4、栈是连续的空间,而堆是不连续的空间是随机分配的;
注意:
1.手动开辟堆区空间,要注意内存泄漏。当指针指向开辟堆区空间后,又对指针重新赋值,则没有指针指向开辟堆区空间,就会造成内存泄漏。
2.使用完堆区空间后及时释放空间。
例如:
下边的代码将展示内训泄露导致段错误
void fun(char *p)
{
p = (char *)malloc(32);
strcpy(p, "hello");
}
int main()
{
char *m = NULL;
fun(m);
printf("%s\n", m);// 段错误
return 0;
}
经过修改得:
void fun(char **p)
{
*p = (char *)malloc(32);
strcpy(*p, "hello");
}
int main()
{
char *m = NULL;
fun(&m);
printf("%s\n", m);// hello
free(m);
m=NULL;
return 0;
}
解析:调用函数将m的地址上传到子函数中(因为是地址所以用二级指针**p来接收),然后此时p里边存储的就是指针m的地址,然后对*p(也就是对指针m的地址对应的空间)赋值,这样就能把新开辟的空间首地址给到m,然后strcpy就是将hello打印到*p里(也就是打印到了m里)