c语言函数的使用
函数的概述
- 一堆代码的集合,用一个标签描述它;标签 -> 函数名
- 函数具备3要素,在定义函数时,必须将3要素告知编译器
- 函数名
- 输入参数
- 返回值
- 如何用指针保存函数
- int fun(int , int , char )
int (*p)(int , int ,char ) - 例如:
#include <stdio.h> int main(void) { int (*p)(const char * ,…); printf("Hello world!"); p = printf; p("+++++++"); return 0; }
- int fun(int , int , char )
- 定义函数,调用函数
int fun(int a,char b){ } int main(void) { fun(10,2); return 0; }
- 函数具备3要素,在定义函数时,必须将3要素告知编译器
函数的参数
基本功能:承上启下
基本实现方式:
调用子函数:
函数名 (要传递的数据); //实参
被调用的子函数:
函数的返回值 函数名 (要接受的数据) //形参
{
……
}
实参 传递-> 形参 传递的形式是 拷贝
如:
#include <stdio.h>
void myfun(int buf)
{
buf = buf + 10;
printf("函数执行值为:%d\n",buf);//30
}
int main(void)
{
int a = 20;
myfun(a);
printf("执行后值为:%d\n",a);//20
return 0;
}
1. 值传递
如实现两数交换
#include <stdio.h>
void myswap(int a,int b);
int main(void)
{
int a = 20;
int b = 30;
printf("Before myswap the a is %d,the b is %d\n",a,b);//20 30
myswap(a,b);
printf("The a is %d,the b is %d\n",a,b);//20 30
}
void myswap(int a,int b)
{
a = a^b;
b = a^b;
a = a^b;
printf("子函数内 a: %d ,b: %d\n",a,b);// 30 20
}
形参是实参的拷贝,值传递只交换了备份的值,并没有实现原值的交换。
即值传递,在子函数中无论对形参做了多么复杂的运算,都不会改变原值;可以保护调用者自己空间的值不被修改。
2. 地址传递
如两数交换
#include <stdio.h>
void myswap(int *a,int *b);
int main(void)
{
int a = 20;
int b = 30;
printf("Before myswap the a is %d,the b is %d\n",a,b);//20 30
myswap(&a,&b);
printf("The a is %d,the b is %d\n",a,b);//30 20
}
void myswap(int *a,int *b)
{
*a = *a^*b;
*b = *a^*b;
*a = *a^*b;
}
地址传递将原值的地址传给子函数,可以对地址中的值进行修改,从而实现原值的交换。
地址传递可以让子函数修改调用者自己空间内的值;
连续空间的传递采用地址传递,节约内存空间
3. 连续空间的传递
- 数组
- 数组名
int a[10];实参:fun(a); 形参: void fun(int *p)
数组传递用地址传递的方式 形参: void fun(int p[10]) 此处的p[10]实际上还是说地址,依然当指针看待
- 数组名
- 结构体
-
结构体变量
struct abc{int a; int b; int c}
struct buf;值传递
实参: fun(buf);
形参: void fun(struct abc a1)
这种方式,要拷贝一个结构体,消耗内存较大地址传递
实参: fun(&buf); 形参: void fun(struct abc *a2)
这种方式不用再拷贝一个结构体,消耗内存较小,节约空间
-
在连续空间中要注意连续空间的读写性,如果只读用修饰型关键字 const 修饰 指针
如: int printf(const chat *format,…); int sprintf(char *str,const char *format,…);
-
空间两大要素:空间首地址、结束标志
根据结束标志的不同分为两类:字符空间、非字符空间- 字符空间 结束标志:’\0’
如:字符长度函数
int strlen(const char *p) { int i = 0; //if(p == NULL); while(p[i]){ i++; } return i; } int main(void) { int len =0; len = strlen("Hello world!"); printf("长度:%d\n",len); return 0; }
注: 在使用strcpy()函数时,可能出现内存泄漏,可使用strncpy()来防止此情况出现;从效率角度来说,对于strlen、strcpy等具体的实现可根据cpu使用汇编来实现
- 非字符空间 结束标志:数量(一般为 字节B)
- 为了处理更多的非字符空间 形参一般用 void *p
如 memcpy(),void *memcpy(void *dest,const void *src,int n)
void *p的使用:int fun(void *p,int len) { unsigned char *tmp = (unsigned char *)p; for(int i = 0;i < len;i++){ ... } return ... }
- 为了处理更多的非字符空间 形参一般用 void *p
- 字符空间 结束标志:’\0’
可认为 char *p | const char *p 为字符空间标识;void *p 为非字符空间、数据空间标识
函数的返回值
承上启下功能的一种形式,地址传递也起到启下的作用
- 基本语法
返回类型 函数名称 (输入列表)
{
return 表达式
}
调用者与被调用者的返回值传递方式:拷贝 - 返回类型
- 基本数据类型
-
一般为 int、char等等,还包括结构体,但如果直接返回结构体,冗余度高,一般使用指针实现
fun() main() int fun1(void) int a=0;
a=fun1();void fun2(int *p) int a=0;
fun2(&a);int *fun1(void) int *p;
p=fun1();void fun2(int **p) int *p;
fun2(&p);
-
- 空间类型(指针)—— 指针作为空间返回的唯一数据类型
- 要保证指针指向合法性
#include <stdio.h> char *fun(void) { char a[] = "Hello world!"; return a; } int main() { char *p; p = fun(); printf("the p is %s\n",p);//此时p指向的a[]在函数执行后被销毁,p指向了未知的东西 return 0; }
必须要保证函数返回的地址所指向的空间合法(不是局部变量)
上面的程序中的子函数可以改为
方式1:char *fun(void) { return "Hello world!"; }
或者
方式2:char *fun(void) { static char a[] = "Hello world!"; return a; }
- 返回空间在函数内部的实现方式
- 只读区 —— 如上子函数修改 方式1
- 静态区 —— 如上子函数修改 方式2
- 堆区 malloc() free()
#include <stdio.h> #include <sring.h> #include <stdlib.h> char *fun(void) { char *a = (char *)malloc(100); strcpy(a,"Hello world!"); return a; } int main(void) { char *p p = fun(); printf("the p is %s\n",p); free(p); return 0; }
- 要保证指针指向合法性
- 基本数据类型