C语言笔记(函数篇一)

希望文章能对你有所帮助,有不足的地方请在评论区留言指正,一起交流学习!

目录

一、函数的概述

二、函数的分类

2.1库函数

2.2 自定义函数

三、形参和实参

3.1实际参数

3.2形式参数

3.3实参和形参的联系

四、函数的调用和链式访问

4.1 传值调用和传址调用

4.2 嵌套调用

4.3链式访问

五、函数的声明和定义

5.1 函数的声明

5.2 函数的定义

六、补充

6.1函数的返回值问题


一、函数的概述

       在C语言中,每一个工程文件只能有一个主函数(main函数),其他的函数就叫做子函数。

       子函数就是⼀个完成某项特定的任务的⼀⼩段代码。这段代码是有特殊的写法和调⽤⽅法的。
C语⾔的程序其实是由⽆数个⼩的函数组合⽽成的,⼀个⼤的计算任务可以分解成若⼲个较⼩的函数(对应较⼩的任务)完成。同时⼀个函数如果能完成某项特定任务的话,这个函数也是可以
复⽤的,提升了开发软件的效率。

子程序就是对可以完成某些特定功能的代码进行封装,在调取和使用的时候更加的方便。

二、函数的分类
2.1库函数

1.什么是库函数

        像printf、scanf都是库函数,库函数也是函数,这些函数已经是现成的,可以直接使用,频繁大量使用的函数。

2.库函数的由来

       为了提高开发的效率,增强代码的复用性和移植性,各种编译器的标准库中提供了⼀系列的库函数。

3.如何学习库函数

可以在这里查看相关库函数的返回值,包含的头文件,输入参数。

4.库函数的分类

  • IO函数
  • 字符串操作函数
  • 字符操作函数
  • 内存操作函数
  • 时间/日期函数
  • 数学函数
  • 其他库函数

5.常用的单词 

      Return Value 返回值     Parameters  参数  Pointer  指针

     例如 

     char * strcpy ( char * destination, const char * source );

     strcpy的使用  char * 函数的返回值     strcpy 函数的名字

( char * destination, const char * source ) 函数要输入的函数

意思是:将指针变量 source指向的数组复制到指针变量denstination所指向的数组中

6.举例

        运用 strcpy函数将一个数组中的字符复制到另一个字符。

#include <stdio.h>
#include <string.h> //strcpy的头文件是 string.h
int main()
{
	char arr1[20] = { 0 };
	char arr2[] = "hello world";

	strcpy(arr1, arr2);        //将数组arr2中的内容复制到arr1中
	char* ret= strcpy(arr1, arr2);  //创建一个指针变量查看返回值
	printf("%s\n", arr1);     //数组的名称是数组首个字符的地址
	printf("%s", ret);        
	return 0;
}
2.2 自定义函数

        库函数的功能有限,自定义函数是程序员最大的发挥空间。

1.自定义函数的组成

1 ret_type fun_name(形式参数)
2 {
3
4 }
• ret_type 是函数返回类型
• fun_name 是函数名
• 括号中放的是形式参数
• {}括起来的是函数体

2.举例

找两个整型数据的最大值的自定义函数。 

#include <stdio.h>
int get_max(int a,int b)       
{
	return (a > b ? a : b);// 判断a>b是否成立,大于返回值为a,小于返回值为b
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int ret = get_max(a, b); //ret来接受自定义函数的返回值
	printf("%d", ret);
	return 0;
}

运行结果如下

3.函数名命名

        函数名字的定义应该是要有实际意义,例子中 get_max 两个单词之间最好用下划线隔开,更加容易辨别,也可以标注为 GetMax首个字符大小形式,不要两者交叉使用类如Get_Max,建议使用前两种的命名方式。

例如

is_prime(  )           get_max (  ) 

IsPrime (  )           GetMax   (  )

命名上应该比较好辨识并且具有意义,且不可以中英混杂类如shi_prime。

4.函数的返回值

void Max(void)
{

}

其中没有返回值,返回值为空,建议使用void标注清楚,参数位置也应该标注。如果未标注返回值类型的情况下,编译器会默认为返回值为整型。下面还会再次提到。

三、形参和实参
3.1实际参数

        真实传递给函数的参数,实参可以是:常量,变量,表达式,函数等。无论实参是何种类型,在进行调用的时候,他们必须是确定值。

根据上面求两个数大小的程序中可以创建的类型

get_max(2,3);
get_max(a,b);
get_max(a+2,3);
get_max(get_max(5,6),3);
3.2形式参数

        形式参数是指函数名口号中的变量,因为形式变量参数只有在函数被调用的过程中才实例化,所以叫做形式参数。

        形式参数当函数调用完成之后就自动销毁了,因此形式参数仅仅在定义的函数中有效。

        int get_max(int a,int b) 中的a和b就是此函数的形式函数。形式参数的生命周期仅仅是自定义函数的函数体部分。

3.3实参和形参的联系

        虽然我们提到了实参是传递给形参的,他们之间是有联系的,但是形参和实参各⾃是独⽴的内存空间。
        这个现象是可以通过调试来观察的。请看下⾯的代码和调试演⽰:

        我们在调试的时候可以观察到,x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不⼀样的,所以我们可以理解为形参是实参的⼀份临时拷⻉。

       形参是在函数调用的时候才实例化,才开辟内存空间

四、函数的调用和链式访问
4.1 传值调用和传址调用

1.传值调用

函数的形参和实参分别占用不同内存块,对形参的修改不会影响实参。

对上面的get_max的程序进行修改。分别在主函数和子函数中添加了指针变量的输出。可以更加直观的看出形参和实参的内存块的不同

#include <stdio.h>
int get_max(int a, int b)
{    
	int * pa = &a;         //创建指针变量,将整型变量的首个地址符存入pa中
	printf("子函数中:%p a=%d\n", pa,a);//%p用来指针变量的输出
	return (a > b ? a : b);// 判断a>b是否成立,大于返回值为a,小于返回值为b
}

int main()
{
	int a = 1;
	int b = 2;
	int * pa = &a;
	printf("主函数中:%p a=%d\n", pa,a);
	int ret = get_max(a, b); //ret来接受自定义函数的返回值
	printf("%d\n", ret);
	return 0;
}

运行结果如下:可以看出该程序仅仅只是将主函数中变量的值传递给了子函数,相当于在子函数中重新建立新的变量,在内存中开辟了新的空间用来放置传递过来的值。

这里存在的疑问主函数的变量和子函数中的变量命名的是否是可以一样的

        答案是可以的,因为子函数中的形式变量和函数体中的变量在子函数运行完成的情况下,生命周期也就到头了,变量将自动销毁。

2.传址调用

        传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式。

        这种传参的方式可以让函数和函数外边的变量建立起真正的联系,可以理解在子函数内部可以操作函数外部的变量

        也就是说要想要在子函数中要改变主函数的中的内容需要传送地址。如下Swap1函数采用的传值调用,Swap2采用的是传址调用。

Swap1

void Swap1(int a, int b)
{
	int tmp = 0;
	tmp = a;			//tmp = a;
	a = b;				//a = b;
	b = tmp;			//b = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a = %d b = %d\n", a, b);
	Swap1(&a, &b);
	printf("Swap1:交换后:a = %d b = %d\n", a, b);
	return 0;
}

在这里数入a=6;b=5;可以看出这里并没有交换完成

Swap2

void Swap2(int* pa, int* pb)
{
	int tmp = 0;
	tmp = *pa;    //tmp = a;
	*pa = *pb;    //a = b;
	*pb = tmp;    //b = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a = %d b = %d\n", a, b);
	Swap2(&a, &b);
	printf("Swap2:交换后:a = %d b = %d\n", a, b);
	return 0;
}

在这里数入a=6;b=5;可以看出这里变量的值交换完成

这里的Swap1函数为什么不能完成交换呢?

        Swap1的交换仅仅只是将主函数中的值的大小传递过去了,子函数会产生新的参数变量,形式参数的作用寿命仅仅是在子函数的局部,无法传输到主函数中。Swap2中的子函数的接受的变量是指针变量,在进行解指针变量(*pa)的操作后,(*pa,*pb)仍然是主函数中的变量(a,b),没有新的变量产生(除去地址变量)。如下面的程序

void Swap2(int* pa, int* pb)
{
	int tmp = 0;
	printf("子函数\n");
	printf("a;%p\n", pa);
	printf("b;%p\n", pb);
	tmp = *pa;    //tmp = a;
	*pa = *pb;    //a = b;
	*pb = tmp;    //b = tmp;
}
int main()
{
	int a = 5;
	int b = 6;
	printf("交换前:a = %d b = %d\n", a, b);
	printf("a:%p\n", &a);
	printf("b:%p\n", &b);
	Swap2(&a, &b);
	printf("Swap2:交换后:a = %d b = %d\n", a, b);
	return 0;
}

运行结果如下:表明在传址调用的情况下,新的变量是子函数中指针变量,a和b仍然是和主函数中的变量。

表明在相对主函数中的内容进行修改的情况下要采用传址调用的方式。

4.2 嵌套调用

函数的嵌套调用和语句中的调用类似,将一个子函数装进另一个函数中。

需要注意!!!

函数可以嵌套调用,但是不可以嵌套定义,因为每一个子函数都是平等的且独立的。

例子

int main()
{
	printf("hehe\n");
	main();
	return 0;
}

主函数嵌套主函数的形式,这个是个错误的示范,程序会无限的循环直到栈溢出,就是内存不够用了,程序直接崩掉了。

运行结果

stack overflow 栈溢出 

4.3链式访问

将一个函数的返回值作为另一个函数的参数。

printf("%d", (printf("%d", printf("%d", 43))));

上述就是一个链式访问,里层函数的返回值作为外层函数的参数,输出的结果是4321。

五、函数的声明和定义
5.1 函数的声明
  • 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,无关紧要。
  • 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
  • 函数的声明一般要放在头文件中的。

1.在简单的程序中,自定义函数往往会放在主函数的上面如下

int Add(int x,int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 50;
	int c = Add(a, b);
	printf("%d", c);
	return 0;
}

这种时候函数不用声明就可以使用了。

2.自定义函数在主函数的下方

int main()
{
	int a = 10;
	int b = 50;
	int c = Add(a, b);
	printf("%d", c);
	return 0;
}
int Add(int x, int y)
{
	return x + y;
}

        如果子函数放在了主函数的下方就会读取不到?

        在编译器中的程序运行是从上向下的顺序,如果子函数放在下面会存在警告,如图:

        这种时候就需要声明了,需要提前告诉程序我使用这个函数了,就是在要使用程序的之前,让程序提前从内存中调出。在上述的程序中加入下面的任意一句就可以了。        

        因为不是传递参数所以可以不用标注变量

        函数的声明下面的是两种

int Add(int x ,int y)
int Add(int  , int  )

3.在工程文件中

        在工程文件中,创建.h头文件结合专用的函数源文件.c一起使用 。

        在上述例子中,中间的源文件是程序运行的主体,左侧放置子函数的源文件,右侧是对子函数声明的头文件。

        这种工程文件的使用实现了程序的模块化开发;可以做到代码的隐藏

这里我们举一个详细例子来讲解:假如我们要写一个简单的加减乘除的计算器

步骤一.先创建程序主题

这个程序是输出程序的主体。

#include <stdio.h>
#include "calculator.h"
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int c = Add(a, b);
	printf("%d", c);
	return 0;
}

步骤二 建立子函数源文件和.h头文件

其中需要编写的程序是要用到的子函数。

int Add(int x, int y)
{
	return x + y;
}

在这个文件中我们需要写子函数的声明。

int Add(int x, int y);

运行结果如下

剩余三个运算大家可以自己试一试写写。.

5.2 函数的定义

函数的定义是指函数的具体实现,交待函数的功能实现。

函数的定义不可以嵌套使用。

int Add(int x, int y)
{
	return x + y;
}

        上述就是一个加法函数的简单的定义,知道程序是用来干什么的,我们就如何编写就是定义了。自定义函数,就是自己决定子函数可以干什么。

六、补充
6.1函数的返回值问题

在函数的设计中,函数中经常会出现return语句,这⾥讲⼀下return语句使⽤的注意事项。
• return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式
的结果。
• return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
• return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
• return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
• 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。

return 0;
return x+y;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值