浅谈函数(上)
- 函数是什么? 数学中我们常见到函数的概念。但是你了解C语言中的函数吗?
维基百科中对函数的定义:子程序 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
- C语言中函数的分类: 1. 库函数 自定义函数
2.1 库函数: 为什么会有库函数? 1. 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格 式打印到屏幕上(printf)。 2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。 3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。 像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。 那怎么学习库函数呢? 这里我们简单的看看:www.cplusplus.com 或者软件msdn(软件在我的gitee里面有需要可以自取 useful software: 一些学习编程好用的软件 (gitee.com)
例如库函数拿strcpy举例子,首先学习一个新函数我们要查资料,我使用的是msdn查找
从参考资料中我们不难看出它的作用是把后面的source源拷贝到前面的destination目标里面
同时我们平常在阅读文档的时候如果看见null的时候,这里的null代表的是\0,
Libraries指的是库
Return Value(返回值)
Each of these functions returns the destination string. No return value is reserved to indicate an error.(返回的是目标字符串)
Parameters(形参)
strDestination
Destination string(对strDestination的描述,就是目标字符串)
strSource
Null-terminated source string(strSource指的是\0结束的源字符串)
它对应的头文件是string.h
下面的strcpy的实际应用
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef"; //a b c d e f \0
char arr2[] = "xxxxxxxxxxx";//x x x x x x x x x x x
//能把arr1中的abcdef拷贝到arr2中
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
通过调试我们不难得到将arr1的值拷贝给了arr2
这就是创建的两个字符串
这时候我们发现将arr1的\0也一起复制进去了。
例如还有memset这个函数(memory—内存,set—设置【内存设置】)
Size_t??(本质上是unsigned int)
Sizeof(a);sizeof返回的值的类型是size_t(专门为sizeof设计的一个返回值);
Sets buffers to a specified character.
void *memset( void *dest, int c, size_t count );
这个函数的主要意思简单来说就是把dest指向的内存块的且count个字节的内容设置成指定的c值
dest
Pointer to destination
c
Character to set
count
Number of characters
下面是memset的实际应用
int main()
{
char arr[] = "hello world";
//现在希望把hello这五个字母改成五个X
memset(arr, 'X', 5);
printf("%s", arr);
return 0;
}
注意使用memset的时候是以字节为单位的,而且每个字节的内容都是一样的,都是你指定的值。
如果要改h之后的可以memset(arr+1,’x’,5)
当然cplusplus有例子也可以看
这是两个很好的库函数查询工具
http://en.cppreference.com/ (英文版)
http://zh.cppreference.com (中文版)
下面我们来看自定义函数
自定义函数和库函数一样,有函数名,返回值类型和函数参数。
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
函数的参数可以没有也可以有多个,函数的返回值可以有 ,也可以没有。没有就说void
3. 函数的参数
3.1 实际参数(实参): 真实传给函数的参数,叫实参。 实参可以是:常量、变量、表达式、函数等。 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
3.2 形式参数(形参): 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内 存单 元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
当函数不调用的时候,函数所调用的参数不占内存空间,它没有真实存在只是形式上的一种存在。
需要改变地址,就传地址,如果只是比较大小等对值的判断等,不需要传地址。传地址的功能更强大。
所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。
同时函数的形参和实参可以是相同的名字。
注意的是数组在传参的时候传递的不是整个数组。比如使用函数来写二分查找
int binary_search(int arr[], int k)
{
int sz = sizeof(arr) / sizeof(arr[0]);
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
return mid;
}
}
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 0;
scanf("%d", &k);
int ret = binary_search(arr, k);
if (-1 == ret)
printf("找不到\n");
else
printf("找到了,下标是:%d\n");
return 0;
}
这里通过监控可以看出来sz变成了1,按理说sz应该是10.由于数组在传参的时候传过去的不是整个数组,而是数组首元素的地址。所以函数里面的形参int arr[]实际上是int *arr是一个指针,所以sizeof算一个指针大小的时候就是4/8(64位操作系统下是8),所以sz=4/4==1;(当前我们是x86平台)所以以后在函数内部这样传参数是做不到的
正确的应该是
int binary_search(int arr[], int k, int sz)
{
//int sz = sizeof(arr) / sizeof(arr[0]);
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
return mid;
}
}
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 0;
scanf("%d", &k);
int sz = sizeof(arr) / sizeof(arr[0]);
//int ret = binary_search(arr, k);
int ret = binary_search(arr, k, sz);
if (-1 == ret)
printf("找不到\n");
else
printf("找到了,下标是:%d\n",ret);
return 0;
}
4. 函数的调用:
4.1 传值调用 函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
4.2 传址调用 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
//get_max函数的设计
int get_max(int x, int y)
{
return (x > y) ? (x) : (y);
}
int main()
{
int num1 = 10;
int num2 = 20;
int max = get_max(num1, num2);
printf("max = %d\n", max);
return 0;
}
这就是一个简单的带有返回值的比较大小的函数
下面我们看看交换函数以及常见的问题
void Swap1(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int num1 = 1;
int num2 = 2;
Swap1(num1, num2);
printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
return 0;
}
这里我们不难发现num1跟num2没有发生变化
之后我们按F11进入函数
这里我们能够看出函数中x和y’确实传入了,
但是x和y跟num1与num2的地址不一样
函数中的tmp确实把x和y交换了,但是x和y的变化不会影响到num1和num2,x和y有独立的空间,这里传给swap函数的ab叫做实参,传给x y的叫做形参,当实参传给形参的时候,形参是实参的一份临时拷贝,对形参的修改不会影响实参,相当于只是把它的数据拷贝过去了,但是它还是一份独立的变量。怎么改呢?
void swap2(int* pa, int* pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
int main()
{
int a = 1;
int b = 2;
swap2(&a, &b);
printf("Swap1::a = %d b = %d\n", a, b);
return 0;
}
调换成功。这里就是通过指针叫两个建立联系。
或者还可以这样写
int main()
{
int a = 1;
int b = 2;
//swap2(&a, &b);
int* p1 = &a;
int* p2 = &b;
swap2(p1, p2);
printf("Swap1::a = %d b = %d\n", a, b);
return 0;
}
这种写法:p1放的是a的地址,怕p2放的是b的地址,这里也是对实参进行传参,形参对实参进行拷贝,p1,p2的地址被传递过去了,但是这里传的是地址,它就依然能够找到ab,所以能否成功, 不在于形参实参的拷贝,主要是能建立联系,能找到函数就没有bug。形参依然是实参的临时拷贝,就看你拷贝的是什么。
5. 函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
5.1 嵌套调用
函数可以嵌套调用,但是不能嵌套定义。
#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;
}
嵌套定义:
int main()
{
void test();
}
5.2 链式访问
把一个函数的返回值作为另外一个函数的参数。
#include <stdio.h>
#include <string.h>
int main()
{
char arr[20] = "hello";
int ret = strlen(strcat(arr, "bit"));//这里介绍一下strlen函数
printf("%d\n", ret);
return 0;
}
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//结果是啥?
//注:printf函数的返回值是打印在屏幕上字符的个数
return 0;
}
肯定先打印43,完了43返回一个值再打印,所以我们就来查一下printf函数
可以看出printf返回的是int类型。
Return Value
Each of these functions returns the number of characters printed, or a negative value if an error occurs
返回值是每一次你在屏幕上打印的数字的个数。所以打印的结果是4321。