函数
一、常见库函数
1、strcpy
格式:char* strcpy ( char * destination, const char * source );
返回值:destination (目标指针指向数组的首地址)
**将源指针指向的C字符串复制到目标指针指向的数组中,包括终止零字符( 并在这一点上停止复制 )。**为避免溢出,目标指向的数组的大小应足够长,以包含与源相同的C字符串( 包括终止零字符 ),并且目标指针指向的地址与源指针指向的地址不能相同。
/* strcpy example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[]="Sample string";
char str2[40];
char str3[40];
strcpy (str2,str1);
strcpy (str3,"copy successful");
printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3);
return 0;
}
/*
str1: Sample string
str2: Sample string
str3: copy successful
*/
2、strcat
格式:char* strcat ( char * destination, const char * source );
返回值:destination (目标指针指向数组的首地址)
把源字符串复制拼接到目的字符串后面。目的字符串中的终结空字符由源字符串的第一个字符覆盖,在目的字符串和源字符串连接起来的新字符串的末尾添加一个空字符,并且目标指针指向的地址与源指针指向的地址不能相同。
/* strcat example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[80];
strcpy (str,"these ");
strcat (str,"strings ");
strcat (str,"are ");
strcat (str,"concatenated.");
puts (str);
return 0;
}
/*
these strings are concatenated.
*/
3、memset
格式:void * memset ( void * ptr, int value, size_t num );
返回值:ptr (所操作的数组的首地址)
将ptr指向的数组的前num个字节(按字节赋值)都替换成value对应的二进制码(只取后8位,超过8位的高位被忽略)。
注意,这里ptr所指向的数组尽量为char数组,这样由于一个字节刚好可以放一个char型数据的二进制码,所以不会出现数据错乱问题。
/* memset example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "almost every programmer should know memset!";
memset (str,'-',6);
puts (str);
return 0;
}
/*
------ every programmer should know memset!
*/
如果要对int数组进行替换,只能替换为 0(二进制码00000000,00000000,00000000,00000000)
-1(二进制码11111111,11111111,11111111,11111111)
例:把数组a的五个值都置为0
int a[5] = { 1,2,3,4,5 };
memset(a, 0, sizeof(a));
for (int i = 0; i < 5; i++)
{
printf("%d ", a[i]);
}
因为memset函数是按字节赋值,所以并不能把数组全赋值成任意的一个值,例如如下代码就有陷阱:
int a[5] = { 1,2,3,4,5 };
memset(a, 1, sizeof(a));
for (int i = 0; i < 5; i++)
{
printf("%d ", a[i]);
}
不了解memset函数的人可能会认为把a数组的五个值都置为了1,但是前面说到memset函数是按字节赋值,int型的变量是四个字节,共32位,所以会把a[0]~a[4]都置为 00000001,00000001,00000001,00000001,熟悉计算机体系结构的人会知道计算机表示数是以补码的形式,该数的值为1+28+216+2^24=16843009,并不是1!
二、自定义函数
1、格式:
返回值类型 函数名 (形参列表)
{
函数体;
}
2、形参与实参
2.1 形参
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
2.2 实参
真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
int get_max(int x, int y)//int x,int y就是形参
{
return (x>y)?(x):(y);
}
int main()
{
int num1 = 10;
int num2 = 20;
int max = get_max(num1, num2); //num1,num2就是实参
printf("max = %d\n", max);
return 0;
}
3、传值与传址
3.1 传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。传值函数调用时,系统会在内存中创建出与实参相对应的形参(数据类型对应,数据个数对应),然后对形参进行函数体的操作,这些操作只能改变形参的值,对实参不起作用。
3.2 传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。**即直接将实参(是内存中的地址)传给形参(是指针变量),形参和实参指向内存块中同一个地址,当函数体通过形参对(地址在内存中对应的)数据操作时,相当于通过实参进行了操作。**这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操作函数外部的变量。
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}//传值
void Swap2(int *px, int *py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}//传址
int main()
{
int num1 = 1;
int num2 = 2;
Swap1(num1, num2);
//只操作了形参,实参不受影响。且形参在函数调用后就销毁,所以本代码执行后内存中不会发生改变。
printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
Swap2(&num1, &num2);
//通过形参对地址所对应的数据进行操作,内存中数据发生了改变。即使函数调用完形参(指针变量)被销毁,但实参对应的数据已经改变。
printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
return 0;
}
/*
Swap1::num1 = 1 num2 = 2
Swap2::num1 = 2 num2 = 1
*/
4、数组在函数中的传参
4.1 传参格式
//方法一
void Function(int *param)
{
. . .
}
//方法二
void myFunction(int param[])
{
. . .
}
//方法三
void myFunction(int param[10])
{
. . .
}
4.2 注意事项
-
三种方法中,int *param,int param[],int param[10]实际上传入函数的都是数组的首地址(相当于只将数组第一个元素传入了函数),并未传入数组的大小。
-
int param[10]的10没有意义,形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。
-
形参数组和实参数组的类型必须一致,否则将引起错误。
4.3传参机理
数组名就是数组的首地址,用数组名作函数参数时所进行的传送只是地址的传送,也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实实在在的数组。实际上是形参数组和实参数组为同一数组,拥有同一段内存空间。
图中设a为实参数组,类型为int。a占有以2000为首地址的一块内存区;b为形参数组名。当发生函数调用时,进行地址传送,把实参数组a的首地址传送给形参数组名b,于是b也取得该地址2000。于是a,b两数组共同占有以2000为首地址的一段连续内存单元。从图中还可以看出a和b下标相同的元素实际上也占有相同的一块内存(整型数组每个元素占4个字节),例如a[0]和b[0]都占用2000、2001、2002、2003四个字节。
4.4 示例
如果函数中需要数组大小,则需将数组大小作为额外参数参入函数,如下例中的int size
int search(int ar[], int k,int SIZE)//SIZE作为数组大小,传入函数
{
int left = 0;
int right = SIZE - 1;
int mid = 0;
while (left <= right)
{
mid = (left + right) / 2;
if (ar[mid] > k)
{
right = mid - 1;
}
else if (ar[mid] < k)
{
left = mid + 1;
}
else
{
return mid;
}
}
return -1;
}//函数的作用是实现二分法对有序数组的查找
int main()
{
int a[10] = { 0,1,2,4,7,13,24,25,28,30 };
int k = 24;//k为目标值
int size = sizeof(a)/sizeof(a[0]);
if ((search(a, k,size)) != -1)
printf("找到了,目标值下标为%d", (search(a, k,size)));
else
printf("未找到目标值");
return 0;
}
三、函数的递归
1、基本思想
程序调用自身的编程技巧叫做递归。 递归的主要思考方式在于:把大事化小。
递归的两个必要条件:
-
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
-
每次递归调用之后越来越接近这个限制条件。
函数的每次递归调用,系统都会在栈区分配一块空间存储递归所调用函数的数据。系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
即使满足上述两个条件,如果函数递归执行次数过多,也可能会导致栈区空间不够而出现栈溢出问题。
2、递归示例
2.1 按位数依次输出正整数
-
编写函数将整数按位依次输出,例:1234 输出:1 2 3 4
a)如果n只有个位数,直接输出n
b)如果n的位数超过个位,通过n%10得到个位数字,通过(n/10)%10得到十位数字,通过((n/10)/10)%10得到百位数字……
void print(int n)
{
if(n>9)
{
print(n/10);
printf("%d\t",n%10);
}
else
printf("%d\t",n);
}
int main()
{
int a = 0;//初始化a
scanf("%d",&a);//输入要输出的值:1234
print(a);//调用函数print
return 0;
}
- 程序执行流程
2.2 求任一非负整数的阶乘
n!的定义(n为非负整数)
a) 0!=1
b) 若n>0,则n!=n*(n- 1) !
例如,5 的阶乘5!可以通过5X4!求得。
4!则可以通过4X3!求得。
3! 由3x2!得到,2! 由2x1!得到,1! 由1x0!得到。
根据定义,0!等于1。最终求出5的阶乘。
int fac(int n)
{
if(n>0)
return n*fac(n-1);
else
return 1;
}
int main()
{
printf("3!=%d",fac(3));
return 0;
}
fac(3);执行时将n=3传入fac函数
int fac(int n)//n=3
{
if(n>0)//满足条件,执行if语句
return n*fac(n-1);//为了进行3*fac(3-1)的计算,将2传入fac函数,先求fac(2)
else
return 1;
}
int fac(int n)//n=2
{
if(n>0)//满足条件,执行if语句
return n*fac(n-1);//为了进行2*fac(2-1)的计算,将1传入fac函数,先求fac(1)
else
return 1;
}
int fac(int n)//n=1
{
if(n>0)//满足条件,执行if语句
return n*fac(n-1);//为了进行1*fac(1-1)的计算,将0传入fac函数,先求fac(0)
else
return 1;
}
int fac(int n)//n=0
{
if(n>0)//不满足条件,执行else语句
return n*fac(n-1);
else
return 1;//返回值为1,将返回值传给上一层函数的fac(0)
}
//收到函数fac(0)返回值1,函数fac(1)返回1*fac(0),即1*1=1
//收到函数fac(1)返回值1,函数fac(2)返回2*fac(1),即2*1=2
//收到函数fac(2)返回值2,函数fac(3)返回3*fac(2),即3*2=6
//进而求出3!=6
2.3 求字符串长度
编写函数strlength求字符串的长度,不允许创建临时变量。
从字符串数组第一个元素统计,若元素为’\0’,则返回0,若元素不为’\0’,则返回(1+以下一个元素为首元素的后段字符串的长度);
a) *str == ‘\0’ 时,返回 0;
b) *str != ‘\0’ 时, 返回 1 + strlength( *(str+1));
int strlength(char str[])
{
if(*str != '\0')
return 1 + strlength( str+1);
else
return 0;
}
int main()
{
char a[] = "hey!";
printf("length = %d",strlength(a));
return 0;
}
/* 递归过程
strlength("hey!\0");
1+strlength("ey!\0");
1+1+strlength("y!\0");
1+1+1+strlength("!\0");
1+1+1+1+strlength("\0");
*/
;
b) *str != ‘\0’ 时, 返回 1 + strlength( *(str+1));
int strlength(char str[])
{
if(*str != '\0')
return 1 + strlength( str+1);
else
return 0;
}
int main()
{
char a[] = "hey!";
printf("length = %d",strlength(a));
return 0;
}
/* 递归过程
strlength("hey!\0");
1+strlength("ey!\0");
1+1+strlength("y!\0");
1+1+1+strlength("!\0");
1+1+1+1+strlength("\0");
*/