C语言函数超详细讲解上篇

目录
  • 前言
  • 1、函数是什么?
  • 2、C语言中函数的分类
    • 2.1 库函数
      • 2.1.1 如何学会使用库函数
      • 2.1.2 自定义函数
  • 3、函数的参数
    • 3.1 实际参数(实参)
    • 3.2 形式参数(形参)
  • 4、函数的调用
    • 4.1 传值调用
    • 4.2 传址调用
    • 4.3 练习
      • 4.3.1 判断一个数是不是素数
      • 4.3.2 判断一年是不是闰年
      • 4.3.3 二分查找
      • 4.3.4 数值自增增加1
  • 5、函数的嵌套调用和链式访问
    • 5.1 嵌套调用
    • 5.2 链式访问
  • 总结

前言

本文主要学习函数的相关内容。

1、函数是什么?

维基百科中对函数的定义:子程序

  • 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method,subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
  • 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。

2、C语言中函数的分类

  • 库函数
  • 自定义函数

2.1 库函数

  • 在学习C语言编程时,总是在一个代码编写完成之后,想把这个结果打印到屏幕上看看。这时使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。
  • 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)
  • 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)

上面的函数不用自己编写,直接可以调用。为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。

C语言常用的库函数都有:

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

使用库函数,必须包含 #include 对应的头文件。

2.1.1 如何学会使用库函数

推荐查询工具官网:

MSDN(Microsoft Developer Network)

www.cplusplus.com

http://en.cppreference.com(英文版)

http://zh.cppreference.com(中文版)

2.1.2 自定义函数

自定义函数和库函数一样,有函数名,返回值类型和函数参数。这些都是我们自己来设计,函数的组成:

ret_type fun_name(para1, * )
{
	statement;//语句项
}
//ret_type 返回类型
//fun_name 函数名
//para1 函数参数
//举例:写一个函数可以找出两个整数中的最大值
//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;
}

3、函数的参数

3.1 实际参数(实参)

  • 真实传给函数的参数,叫实参
  • 实参可以是:常量、变量、表达式、函数等
  • 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参

3.2 形式参数(形参)

  • 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数
  • 形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效
//举例2:写一个函数可以交换两个整形变量的内容
//实现成函数,但是不能完成任务
int exchange1(int x, int y)
{//当实参传给形参时候,形参是实参的一份临时拷贝,
	//对形参的修改不会影响实参
	int temp = x;
	x = y;
	y = temp;
}
//正确的版本
int exchange2(int* pa, int* pb)//定义指针,接收地址
{
	int temp = *pa;
	*pa = *pb;
	*pb = temp;
}
int main()
{
	int a = 3;
	int b = 5;
	exchange1(a, b);//传参是值
	printf("exchange1::a = %d b = %d\n", a, b);//交换前

	exchange2(&a, &b);//传参是地址
	printf("exchange2::a = %d b = %d\n", a, b);//交换后的
	//传入地址,自定义的形参和实参联系更加紧密,能改变地址存储的数值
	//此时,形参的地址与实参的地址是一样的
	return 0;
}

上面代码中, exchange1和 exchange2函数中的参数 x,y,pa,pb 都是形式参数。在main函数中传给 exchange1的 a ,b 和传给 exchange2函数的 &a ,&b是实际参数。

运行结果如下所示,exchange1并没有起到预想的交换数值的作用,exchange2可以。

通过监视变量发现:

名称 意义
a 5 数值为5
&a 0x113f8e4 数值a的地址
x 5 形参x数值为5
&x 0x113f800 形参x的地址
pa 0x113f8e4 形参pa数值为a的地址
  • 变量a在内存中开辟了空间,地址是0x113f8e4
  • 函数exchange1将实参a传递给形参x,x的数值也是5,但此时形参x另外重新再内存中开辟了空间,地址 0x113f800,形参和实参的地址是不一样的
  • 函数exchange2将实参a传递给形参pa,pa的值是变量a的地址0x113f8e4,这个地址里面存放变量a的数值5。

这里可以看到 exchange1函数在调用的时候, x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。所以我们可以简单的认为:

形参实例化之后其实相当于实参的一份临时拷贝。形参x、y的值发生交换,但是不影响实参a、b的值。一般性的只是使用数值大小,利用形参传值就可以了,传值表明,形参和实参的关系肤浅,仅限于表面数值的拷贝。

如果需要对主函数的实参值进行操作,比如交换,此时形参需传地址,功能更为强大。传地址表明,形参和实参的关系更深一步,直接可以通过地址修改实参的数值。

4、函数的调用

4.1 传值调用

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

4.2 传址调用

  • 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式
  • 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量

4.3 练习

4.3.1 判断一个数是不是素数

写一个函数可以判断一个数是不是素数

//返回1 表示是素数
//返回-1 表示不是素数

//写法1  2-n-1 试除法
int  is_sushu(int a)
{//素数就是除1和自身外,不能被其他数整除
	for (int i = 2; i < a-1; i++)
	{//用2-n-1的数一一试除
		if (a%i==0)
		{//若有能除的,直接返回-1,跳出后面的循环了
			return -1;//表明不是素数,
		}
	}//break退出循环会调到这里
	//return返回时直接退出这个函数了,返回到主函数了,级别更高
	return 1;
}

int main()
{
	int a = 0;
	scanf("%d", &a);
	int num = is_sushu(a);
	if (num==1)
	{
		printf("%d: 是素数", a);
	}
	else
	{
		printf("%d:不是素数", a);
	}
	return 0;
}

//写法2  2-sqrt(n) 试除法,速度更快
#include<math.h>
int  is_sushu(int num)
{
	for (int i = 2; i <=sqrt(num); i++)
	{
		if (num%i == 0)
		{
			return -1;
		}
	}
	return 1;
}

int main()
{
	int num = 0;
	for (int num = 100; num < 201; num++)
	{
		if ((is_sushu(num)) == 1)
		{
			printf("%d ", num);
		}
	}
	return 0;
}

4.3.2 判断一年是不是闰年

写一个函数判断一年是不是闰年

//闰年时是4的倍数且不是100的倍数,或者是400的倍数
int  is_run_nian(int y)
{
		if (((num%4 == 0)&& (num%100 != 0))|| (num % 400 == 0))
		{
			return 1;
		}
	//return ((num%4 == 0)&& (num%100 != 0))|| (num % 400 == 0);//简写

}
int main()
{
	int num = 0;
	for (int num = 1000; num <= 2000; num++)
	{
		if ((is_run_nian(num)) == 1)
		{
			printf("%d ", num);
		}
	}
	return 0;
}

4.3.3 二分查找

写一个函数,实现一个整形有序数组的二分查找

//找到了就返回下标
//找不到返回-1
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 0;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int left = 0;//左下标
	int right = sz - 1;//右下标

	scanf("%d", &k);
	while (left<=right)
	{//每次left right mid都要更新
		int mid = left + (right - left) / 2;
		if (arr[mid] > k)
		{//寻找的数值在左半边,所以左不动,右边动
			right = mid - 1;
		}
		else if (arr[mid] < k)
		{//寻找的数值在右半边,所以左边动,右不动
			left = mid + 1;
		}
		else
		{
			printf("找到了:%d ", mid);
			break;
		}
	}
	if (left>right)//循环结束了
	{
		printf("找不到");
	}
	return 0;
}

可以将二分法进行改进,写成独立的函数,让函数功能单一化:

int is_erfen(int arr[],int k, int sz)
{//数组传递参数进来就是数组首元素的地址,并不是整个数组
	//地址的大小是4或8个字节,前面有讲到过的
//x86平台中,地址占4个字节,sizeof(arr)是4, 而不是40了
	//int sz = sizeof(arr) / sizeof(arr[0]);//sz要在主函数计算,
	int left = 0;
	int right = sz - 1;
	while (left <= right)
	{//每次left right mid都要更新
		int mid = left + (right - left) / 2;
		if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else
		{
			return mid;
		}
	}
	return -1;
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 0;
	int i = 0;
	scanf("%d", &k);
	//主函数中  按数组名找到整个数组,求出占用字节是40
	int sz = sizeof(arr) / sizeof(arr[0]);
	int num = is_erfen(arr, k, sz);

	if (num ==-1)
	{
		printf("找不到");
	}
	else
	{
		printf("找到了:%d ", num);
	}
	return 0;
}

在上面的函数中需要注意,数组的大小必须在主函数中计算,下面代码可说明在主函数中和其他函数中求数组长度的区别:

int erfen(int arr[])
{//数组传递进来,只能是首地址,这个arr数组里只有{1},长度为4
	int sz1 = sizeof(arr) / sizeof(arr[0]);,//4/4 = 1求出只有1个元素
	printf("erfen求数组大小,sz1:%d\n ", sz1);
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//数组长度为40
	int sz = sizeof(arr) / sizeof(arr[0]);//40/4 = 10 求出数组10个元素
	printf("main中求数组大小,sz:%d\n ", sz);
	erfen(arr);
	return 0;
}

按F10进入调试界面,按F11,可观察到:

  • 主函数中sizeof(arr)长度是40,数组中包含10个元素。
  • 而在函数erfen中,sizeof(arr)长度是4,数组中包含1个元素,就是首元素{ 1 }。

由此,可知道,参数里传递数组时,实际传递的数组就是数组的地址,也是数组首元素的地址。数组名传递进来,只能是首地址,这个arr数组里只有首元素{1},长度为4。整个数组是传递不了的。因此,必须在主函数里求取数组的长度。

因为传递的参数是地址,所以erfen中也可以定义指针来接受数组:

//int erfen(int arr[])//接受数组,只能接受一个首元素
int erfen(int* arr)
{//数组传递进来,只能是首地址,这个arr数组里只有{1},长度为4
	int sz1 = sizeof(arr) / sizeof(arr[0]);,//4/4 = 1求出只有1个元素
	printf("erfen求数组大小,sz1:%d\n ", sz1);
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//数组长度为40
	int sz = sizeof(arr) / sizeof(arr[0]);//40/4 = 10 求出数组10个元素
	printf("main中求数组大小,sz:%d\n ", sz);
	erfen(arr);
	return 0;
}

运行结果一样

4.3.4 数值自增增加1

写一个函数用一次这个函数,就会将 num 的值增加1

int add(int* p)
{
	(*p)++;//参数传地址,可以操作实参的值
}
int main()
{
	int a = 10;
	add(&a);//传地址
	printf("%d\n", a);
	add(&a);
	printf("%d\n", a);
	add(&a);
	printf("%d\n", a);
	add(&a);
	printf("%d\n", a);
	return 0;
}

5、函数的嵌套调用和链式访问

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。

5.1 嵌套调用

//套娃
void newline()
{
	printf("hehe\n");
}
void threeline()
{
	int i = 0;
	for (int i = 0; i < 3; i++)
	{
		newline();
	}
}
int main()
{
	threeline();
	newline;
	return 0;
}

5.2 链式访问

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

int main()
{
	char arr[20] = "hello";
	int ret = strlen(strcat(arr, "bit"));
	printf("%d\n", ret);
	return 0;
}
int main()
{
	printf("%d", printf("%d", printf("%d", 43)));//输出4321,
	//前面是打印43 2是返回两个字符(因为4 、3是两个字符)
	//1是返回1个字符(2是1个字符)

	return 0;
}

输出结果见下图:

总结

本文主要介绍了函数相关的知识,下一篇接着介绍函数的内容。

到此这篇关于C语言函数超详细讲解上篇的文章就介绍到这了,更多相关C语言 函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言自定义函数的实现

    函数是一段可以重复使用的代码,用来独立地完成某个功能,它可以接收用户传递的数据,也可以不接收.接收用户数据的函数在定义时要指明参数,不接收用户数据的不需要指明,根据这一点可以将函数分为有参函数和无参函数. 将代码段封装成函数的过程叫做函数定义. C语言无参函数的定义 如果函数不接收用户传递的数据,那么定义时可以不带参数.如下所示: dataType functionName(){ //body } dataType 是返回值类型,它可以是C语言中的任意数据类型,例如 int.float.char

  • 深入解析C语言中函数指针的定义与使用

    1.函数指针的定义     函数是由执行语句组成的指令序列或者代码,这些代码的有序集合根据其大小被分配到一定的内存空间中,这一片内存空间的起始地址就成为函数的地址,不同的函数有不同的函数地址,编译器通过函数名来索引函数的入口地址,为了方便操作类型属性相同的函数,c/c++引入了函数指针,函数指针就是指向代码入口地址的指针,是指向函数的指针变量. 因而"函数指针"本身首先应该是指针变量,只不过该指针变量指向函数.这正如用指针变量可指向整形变量.字符型.数组一样,这里是指向函数.C在编译时

  • C语言中函数的声明、定义及使用的入门教程

    对函数的"定义"和"声明"不是一回事.函数的定义是指对函数功能的确立,包括指定函数名,函数值类型.形参及其类型以及函数体等,它是一个完整的.独立的函数单位.而函数的声明的作用则是把函数的名字,函数类型以及形参的类型.个数和顺序通知编译系统,以便在调用该函数时进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体.--谭浩强 ,<C程序设计>(第四版),清华大学出版社,2010年6月,p182 这段论述包含了许多概念性错误,这

  • C语言菜鸟基础教程之自定义函数

    先动手编写程序: #include <stdio.h> int add(int x, int y) { int z = x + y; return z; } int main() { int a = 1; int b = 2; int c = add(a, b); printf("c = %d\n", c); return 0; } 运行结果: c = 3 程序分析: (1) 函数定义的形式为: 类型 函数名称(类型 形式参数,--) { 函数体 } (2) 对应于咱们的程

  • C语言函数基础教程分类自定义参数及调用示例详解

    目录 1.  函数是什么? 2.  C语言中函数的分类 2.1 库函数 2.1.1 为什么要有库函数 2.1.2 什么是库函数 2.1.3 主函数只能是main()吗 2.1.4常见的库函数 2.2 自定义函数 2.2.1自定义函数是什么 2.2.2为什么要有自定义函数 2.2.3函数的组成 2.2.4 举例展示 3. 函数的参数 3.1 实际参数(实参) 3.2  形式参数(形参) 4. 函数的调用 4.1 传值调用 4.2  传址调用 4.3 练习 4.3.1. 写一个函数判断一年是不是闰年

  • C语言函数超详细讲解上篇

    目录 前言 1.函数是什么? 2.C语言中函数的分类 2.1 库函数 2.1.1 如何学会使用库函数 2.1.2 自定义函数 3.函数的参数 3.1 实际参数(实参) 3.2 形式参数(形参) 4.函数的调用 4.1 传值调用 4.2 传址调用 4.3 练习 4.3.1 判断一个数是不是素数 4.3.2 判断一年是不是闰年 4.3.3 二分查找 4.3.4 数值自增增加1 5.函数的嵌套调用和链式访问 5.1 嵌套调用 5.2 链式访问 总结 前言 本文主要学习函数的相关内容. 1.函数是什么?

  • C语言操作符超详细讲解上篇

    目录 前言 1.操作符的分类 2.算术操作符 3.移位操作符 3.1 左移操作符 3.1.1 正数左移1位 3.1.2 负数左移1位 3.2 右移操作符 3.2.1 正数右移1位 3.2.2 负数右移1位 3.3 移位操作符说明 4.位操作符 4.1 练习 1 4.2 练习 2 总结 前言 操作符主要内容包括:各种操作符的介绍,用表达式求值. 1.操作符的分类 算术操作符 移位操作符 位操作符 赋值操作符 单目操作符 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用.函数调用和结构成员

  • C语言指针超详细讲解上篇

    目录 前言 1.指针是什么 1.1 指针变量 1.2 指针是内存中一个最小单元的编号 2.指针和指针类型 2.1 指针±类型 2.2 指针的解引用 2.2.1 int* 类型的解引用 2.2.2 char* 类型的解引用 3.野指针 3.1 野指针成因 3.1.1 指针未初始化 3.1.2 指针越界访问 3.1.3 指针指向的空间释放 3.2 如何规避野指针 总结 前言 本文开始指针相关内容的学习,主要内容包括: 指针是什么 指针和指针类型 野指针 指针运算 指针和数组 二级指针 指针数组 1.

  • C语言函数超详细讲解下篇

    目录 前言 函数的声明和定义 函数声明 函数定义 举例 简单的求和函数 把加法单独改写成函数 添加函数声明 带头文件和函数声明 静态库(.lib)的生成 静态库文件的使用方法 函数递归 什么是递归? 递归的两个必要条件 练习1 一般方法 递归的方法 练习2 一般方法 递归方法 练习3 一般方法 递归方法 练习4 一般方法 递归方法 递归与迭代 递归隐藏的问题 如何改进 选递归还是迭代 总结 前言 紧接上文,继续学习函数相关内容. 函数的声明和定义 函数声明 告诉编译器有一个函数叫什么,参数是什么

  • C语言数据的存储超详细讲解上篇

    目录 前言 1.数据类型介绍 类型的基本归类 2.整形在内存中的存储 2.1 原码.反码.补码 2.2 大小端介绍 2.2.1 什么是大小端 2.2.2 大端和小端意义 2.2.3 写程序判断字节序 总结 前言 本文开始学习C语言进阶的内容了,进阶内容,是在基础阶段的内容上进行拓展,有的知识点,在基础阶段也已经学过.在进阶内容中,将从更深层次的角度去理解学习,本文主要内容包括: 数据类型详细介绍 整形在内存中的存储:原码.反码.补码 大小端字节序介绍及判断 浮点型在内存中的存储解析 1.数据类型

  • C语言数组超详细讲解下篇扫雷

    目录 前言 1.扫雷是什么? 2.程序框架 2.1 主函数 2.2 函数menu 2.3 函数game 2.3.1 函数init_board 2.3.2 函数show_board 2.3.3 函数set_mine 2.3.4 函数find_mine 2.3.5 函数get_mine_count 3.头文件.h 4.游戏试玩 总结 前言 本文接着复习前面所学知识,以扫雷游戏为例. 1.扫雷是什么? 百度百科:<扫雷>是一款大众类的益智小游戏,于1992年发行.游戏目标是在最短的时间内根据点击格子

  • C语言数据结构超详细讲解单向链表

    目录 1.链表概况 1.1 链表的概念及结构 1.2 链表的分类 2. 单向链表的实现 2.1 SList.h(头文件的汇总,函数的声明) 2.2 SList.c(函数的具体实现逻辑) 2.2.1 打印链表 2.2.2 搞出一个新节点(为其他函数服务) 2.2.3 链表尾插 2.2.4 链表头插 2.2.5 链表尾删 2.2.6 链表头删 2.2.7 查找节点 2.2.8 在pos位置之前插入 2.2.9 在pos位置之后插入 2.2.10 删除pos位置 2.2.11 删除pos之后位置 2.

  • C语言操作符超详细讲解下篇

    目录 前言 赋值操作符 单目操作符 单目操作符介绍 sizeof 和 数组 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用与函数调用和结构成员 [ ] 下标引用操作符 ( ) 函数调用操作符 访问一个结构的成员 表达式求值 隐式类型转换-整形提升 算术转换 操作符的属性 总结 前言 本文接着学习操作符的内容. 赋值操作符 赋值操作符就是能够重新赋值 int weight = 120;//体重 weight = 89;//不满意就赋值 double salary = 10000.0; s

  • C语言数组超详细讲解中篇三子棋

    目录 前言 1.三子棋是什么? 1.1 百度百科 1.2 游戏编程准备工作 2. 程序实现 2.1 搭建程序框架 2.2 模块化编程 2.2.1 源文件test.c 2.2.2 源文件play.c 2.2.3 头文件play.h 2.3 程序实现—拓展play函数 2.3.1 棋盘初始化与打印函数 2.3.2 玩家下棋函数 PlayMover 2.3.3 电脑下棋函数 ComputerMove 2.2.4 判断赢家函数 WhoIsWin 总结 前言 本文主要是对前面所学内容进行复习和练习,学习内

  • C语言数组超详细讲解上

    目录 前言 1.一维数组的创建和初始化 1.1 一维数组的创建 1.2 一维数组的初始化 1.3 一维数组的使用 1.4 一维数组在内存中的存储 2.二维数组的创建和初始化 2.1 二维数组的创建 2.2 二维数组的初始化 2.3 二维数组的使用 2.4 二维数组在内存中的存储 3.数组越界 4.数组作为函数参数 4.1 冒泡排序函数的错误设计 4.2 数组名是什么? 4.3 对数组名的用法进行总结 4.4 冒泡排序函数的正确设计 总结 前言 本文主要介绍数组相关的内容,主要内容包括: 一维数组

随机推荐