函数
函数是什么
C语言中的函数不同于数学中的函数。维基百科的定义:子程序
在计算机科学中,子程序是一个大型程序中的部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。
一般会有输入参数和返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
C语言中的函数分类:库函数和自定义函数
库函数:C语言本身提供给我们的函数
常用库函数
IO(input output)函数
字符串操作函数(strlen等)
字符操作函数(判断其是否为大小写等等)
内存操作函数(memset等)
时间/日期函数
数学函数
其它库函数
学习 学习运用函数的能力
以strcpy为例。
1、首先我们学会联想猜测,如看到strlen我们会想到拆开成 string length
这里我们就能拆成string copy以形成对其第一印象
2、进入前面的查找网址,在search一栏输入我们想要查找的函数
function表明其为函数
其下绿色字体
char * strcpy ( char * destination, const char * source );
表明了其的基本用法 char*表明了其指针类型 从source给destination拷贝
copy string下面就对其直接作出了解释:
Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
To avoid overflows, the size of the array pointed by destination shall be long enough to contain the same C string as source (including the terminating null character), and should not overlap in memory with source.
下面还有具体的实例可以供我们参考,甚至可以在网上在线运行。
接下来我们进入实战运用:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<string.h>
int main()
{
char arr1[] = "bit";
char arr2[20] = "###########";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
我们成功把数组1的bit拷贝到了数组2
我们可以依葫芦画瓢学习memset
int main()
{
char arr[] = "hello world";
memset(arr, '*', 5);
printf("%s\n", arr);
return 0;
}
我们不需要记住所有的库函数,最重要的是学会查阅的能力!
另外还有查阅推荐:
MSDN(Microsoft Developer Network)微软官方推荐
http://en.cppreference.com可以通过修改前面的en找到中文版
这都是我们的工具库。所有的库函数都有要引用的头文件!
自定义函数
与库函数一样,有函数名、返回值类型和函数参数。但是不一样的是这些都是我们自己来设计的,这里才区分程序员水平高低!
ret_type fun_name(para1,*)
{
statement;
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
花括号内称为函数体,体现了函数的实现。
写一个函数求两者中的较大值
//自写函数 两者中较大值
int max(int x, int y)
{
if (x >= y)
printf("%d\n", x);
else
printf("%d\n", y);
return 0;
}
int main()
{
int a = 100;
int b = 200;
max(a, b);
return 0;
}
最后输出200的值。
交换两个整型变量的值:
//交换两个变量的值
int exchange(int x, int y)
{
int mid = 0;
mid = y;
y = x;
x = mid;
printf("x=%d\ny=%d\n", x, y);
return 0;
}
int main()
{
exchange(100, 200);
return 0;
}
这里我为了图方便也为了展示直接引用exchange后面我都没有规定变量了。一旦我规定a和b变量在这里输出表示的还是x与y…
我们前面可以定义一个void即空的没有返回值的函数:
void swap(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("a=%d b=%d\n", a, b);
swap(a, b);
printf("a=%d b=%d\n", a, b);
return 0;
}
你会发现这个结果根本没有交换,那我们通过监视窗口来看看发生了什么
千万注意,在这一步我们要通过按F11而非F10的方式才能进入函数体内,否则直接就进行完整个函数了。
进入函数体内,我们可以通过右侧监视窗口发现,a、b与相对应的x、y的地址根本不一样,也就是说,在这个函数体内的确完成了x与y的交换,但是这与a和b根本毫无关系,所以这才导致了为什么失效!
看到地址,我们就该想到我们原来学过的指针知识了!
void swap(int* x, int* y)
{
int tmp = 0;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("a=%d b=%d\n", a, b);
swap(&a, &b);
printf("a=%d b=%d\n", a, b);
return 0;
}
这样子就能输出我们想要的结果了!
函数的参数
实际参数:真实传给函数的参数,叫实参。可以是变量、常量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时都必须有确定的值,以便把这些值传给形参
形式参数:形式参数是指函数名后括号中的变量,比如上一个代码中swap之后的两个参数:
int* x, int* y
就是形式参数。形式参数只有在函数被调用过程中才实例化(分配内存单元),所以叫形式参数,调用完之后自动销毁,所以形式参数只在函数中有效。
函数的调用
传值调用
如第一个交换变量的函数
当实参传给形参时,形参其实是实参的一份临时拷贝,对形参的修改不会影响实参,所以第一个我们的交换方法失败了。
传址调用
如第二个交换变量的函数
将函数外部创建变量的内存地址传递给函数参数的一种调用函数的方法。
这种传参方式可以让函数和函数外边的变量建立真正的联系,也就是函数内部可以直接操作函数外部的变量。
判断一个数是否为素数
//判断是否为素数
int judge(int x)
{
int i = 0;
for (i = 2; i <= x; i++)//明显我这个只能判断大于2的数字
{
if (i == x)
return 1;
else
{
if (0 != x % i)
continue;
else
return 0;
}
}
return 0;
}
int main()
{
int x = 0;
scanf("%d", &x);
if (judge(x) == 1)
printf("素数\n");
else
printf("不是素数\n");
return 0;
}
但是我写的这一个也只能用来判断一个大于2的数字。
二分查找(折半查找)
在一个有序数组中查找具体某个数,一个重点内容!
在这里我还要补充一种编程思路,(不属于官方术语哈),我们在编写需要函数来工作的代码时,往往可以先把整个结构流程定下来,即把main里面的先编写完,就可以理清我们究竟需要一个什么函数,再去补充函数体的内容,这样子的思路在多练习之后很合适。
//二分查找
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) / 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 = 7;
int ret = binary_search(arr, k);
if (ret == -1)
{
printf("找不到指定的数字\n");
}
else
{
printf("其下标是:%d\n", ret);
}
return 0;
}
这个思路很清晰,但是你会发现结果还是找不到,与我们预期不符。
我们进入调试窗口,再强调一遍,F10为逐过程分析,F11为逐语句分析,记得进入函数体时要按F11!
明显这一步算完之后sz为2的结果就不符合我们10的预期。
这是因为,传参的过程出现了问题!这个数组只把数组的第一个元素(编号0)即1的地址传了过去!如果把整个数组传递过去造成临时空间浪费过大,本质上接收的arr就是一个指针!
所以我们可以将算长度的这一步放到函数体外一开始就把它解决了,然后函数再加一个变量
//二分查找
int binary_search(int arr[], int k,int sz)
{
int left = 0;
int right = sz - 1;//即元素个数减去一,要注意下标是从零开始的!
while (left <= right)
{
int mid = (left + right) / 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 = 7;
int sz = sizeof(arr) / sizeof(arr[0]);//我们希望通过此来寻找最大的数组序号,通过整个数组的大小除以单个元素大小得到元素个数
int ret = binary_search(arr, k,sz);
if (ret == -1)
{
printf("找不到指定的数字\n");
}
else
{
printf("其下标是:%d\n", ret);
}
return 0;
}
这样问题就得到解决了!
每调用一次函数num加一
//每调用一次函数num加一
void add(int* p)
{
(*p)++;
}
int main()
{
int num = 0;
add(&num);
printf("num=%d\n", num);
add(&num);
printf("num=%d\n", num);
return 0;
}
这个没什么难度,重点在于(*p)++的括号,因为++的优先级高于解引用。记忆!
嵌套调用和链式访问
函数和函数之间是可以有机组合的。
嵌套调用很简单,这里不再赘述。
//链式访问
int main()
{
int len = 0;
len = strlen("abc");
printf("%d\n", len);
printf("%d\n",strlen("abc"));
return 0;
}
第二句printf的语句就把前两句通过链式的形式合并在一起了。即把一个函数的访问值作为另一个函数的参数
函数的声明和定义
当我们这样写,将函数的定义写在入口的下面,就会遇到这样找不到标识符的错误。这时我们就需要做一个提前声明。
//函数的声明
int Add(int, int);
int main()
{
int a = 10;
int b = 20;
int sum = 0;
sum = Add(a, b);//函数的调用
printf("%d\n", sum);
return 0;
}
//函数的定义
int Add(int x, int y)
{
int z = x + y;
return z;
}
这样做就冇问题了!第一行的x与y可以像我这样省略的。但是一般都不会这么写,先写定义就可以省略声明了。
当然,这不是函数声明的真正使用方法。
我们创建一个名为add的头文件,创建一个6.6的cpp文件,把这个函数定义留在6.4.cpp的部分,
把函数声明留在add.h的头文件部分
这下子我们在别的源文件之中也可以使用这个函数了!
但是注意我们首先得用
#include"add.h"
的方式引用头文件,注意库函数我们用的<>这里要用""来进行引用!这下子这个程序就可以直接跑了!这在实际运用中很有意义!
即使关掉那俩文件(别删)依旧是可以继续运行的!