IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 3. 函数 -> 正文阅读

[C++知识库]3. 函数

函数


本章主要掌握函数的基本使用和递归

  • 函数是什么
  • 库函数
  • 自定义函数
  • 函数参数
  • 函数调用
  • 函数的嵌套调用和链式访问
  • 函数的声明和定义
  • 函数递归

1. 函数是什么?

函数的定义:子程序

  1. 负责完成某项特定任务,具备相对的独立性。

  2. 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。

2. 函数的分类:

  1. 库函数
  2. 自定义函数

库函数

作用:提高可移植性和提高程序的效率

学习库函数:http://www.cplusplus.com

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

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

  • IO函数(printf、scanf、getchar、putchar)
  • 字符串操作函数(strlen、strcmp、strcpy、strcat)
  • 字符操作函数(tolower、toupper(大小写转换))
  • 内存操作函数(memcpy、memset、memmove、memcmp)
  • 时间/日期函数(time)
  • 数学函数(sqrt、abs、fabs、pow)
  • 其他库函数

略读库函数

image-20210722162803027

strcpy函数

char * strcpy ( char * destination, const char * source );
//strcpy
#include <stdio.h>
#include <string.h>

int main()
{
    //arr[2]拷贝进arr[1]
	char arr1[20] = "xxxxxxxxx";//目标空间
	char arr2[] = "hello";

	char* ret = strcpy(arr1, arr2);

	printf("%s\n", arr1);//目标空间的起始地址
	printf("%s\n", ret);//接收返回值--->目标空间的起始地址
						
    //求字符串长度不包含‘\0’,但是内存占空间
	return 0;
}

image-20210819214708619

打印arr[1]内容,不显示‘xxxx’?

因为’\0’是字符串结束标志,打印遇到’\0’就会停止

memset函数

 void * memset ( void * ptr,int value, size_t num );

image-20210819220325697

image-20210819220305628

//memset
//以字节为单位初始化
#include <stdio.h>
#include <string.h>
int main()
{
	char arr[] = "hello bit";//将数组内容变为xxxxx bit
	int n = 5;
	char *ret = (char*)memset(arr, 'x', n);//函数为int类型,但传字符时,字符的ASCII值是整型
	printf("%s\n", ret);//字符串起始地址

	return 0;
}


#include <stdio.h>
#include <string.h>
int main()
{
	int arr[10] = { 0 };
	memset(arr, 1, 5*sizeof(int));//向后初始化20个字节
  
	return 0;
}

再次强调:

一个int空间占4byte,memset函数以字节为单位初始化,将int空间每个字节初始化为1,所以int整型整体是不会初始化为1的

image-20210824222147534

image-20210824222356076

注:

是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。

自定义函数

所有更加重要的是自定义函数

函数的组成:

ret_type fun_name(para1, * )
{
 statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1    函数参数

写一个函数可以找出两个整数中的最大值。

//函数 - 自定义函数
//x,y--形式参数
//a,b--实际参数

#include <stdio.h>
int get_max(int x, int y)
{
	return (x > y ? x : y);//返回的值类型和函数返回类型保持统一
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);

	//求2个数的较大值
	int max = get_max(a, b);//接收变量max的类型也与函数返回类型保持一致,若不一致,进行强制类型转换
	printf("max = %d\n", max);
	return 0;
}

写一个函数可以交换两个整形变量的内容

//不用函数:
#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 0;

	printf("交换前:a=%d b=%d\n", a, b);
	c = a;
	a = b;
	b = c;
	printf("交换后:a=%d b=%d\n", a, b);

	return 0;
}
//函数错误举例:
void Swap(int x, int y)
{
	int z = 0;
	z = x;
	x = y;
	y = z;
}

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;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s6oAtHWJ-1630758351350)(C:\Users\weichen\AppData\Roaming\Typora\typora-user-images\image-20210903162044795.png)]

image-20210903162524432

实参a和b,传给形参x,y的时候,形参将是实参的一份临时拷贝

改变形参变量x,y,是不会影响实参a和b

//指针变量指向地址
int main()
{
	int a = 10;

	int* pa = &a;
	*pa = 20;

	printf("%d\n", a);
	return 0;
}
//正确示例
void Swap1(int x, int y)//传值调用
{
	int z = 0;
	z = x;
	x = y;
	y = z;
}

void Swap2(int* px, int* py)//传址调用
{
	int z = 0;

	z = *px;
	*px = *py;
	*py = z;
}

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\n", a, b);
	Swap2(&a, &b);
	Swap1(a, b);
	printf("交换后:a=%d b=%d\n", a, b);

	return 0;
}

image-20210903162707081

image-20210903165204769

函数需要改变外部内存的值,采用指针形式

3. 函数的参数

实际参数(实参):

真实传给函数的参数,叫实参。

实参可以是:常量、变量、表达式、函数等。

无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

形式参数(形参):

形式参数是指函数名后括号中的变量

形式参数只有在函数被调用的过程中才实例化(分配内存单元)

形式参数当函数调用完成之后就自动销毁了,形式参数只在函数中有效

4. 函数的调用:

传值调用

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

传址调用

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

让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

练习

  1. 写一个函数可以判断一个数是不是素数
//is_prime();函数
//如果是素数 返回1
//不是素数, 返回0
#include <stdio.h>
#include <math.h>

//判断n是否为素数
int is_prime(int n)
{
	//试除法
	//2~n-1
	//2~sqrt(n)
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
		if (n % j == 0)
			return 0;
	}
	return 1;
}

int main()
{
	//100-200之间的素数
	int i = 0;
	int cnt = 0;
	for (i = 100; i <= 200; i++)
	{
		//判断i是否为素数
		if (is_prime(i) == 1)
		{
			printf("%d ", i);
			cnt++;
		}
	}
	printf("\ncount = %d\n", cnt);
	return 0;
}

image-20210903200115171

  1. 写一个函数判断一年是不是闰年
//函数主体
//1000-2000 闰年
int main()
{
	int y = 0;
	for (y = 1000; y <= 2000; y++)
	{
		//判断y是否为闰年
		//函数
		//是闰年,返回1
		//不是闰年,返回0
		is_leap_year()
	}
	return 0;
}
//最常用
int is_leap_year(int y)
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return 1;
	else
		return 0;
}

//最简
int is_leap_year(int y)
{
	return (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0));
}


int main()
{
	int y = 0;
	for (y = 1000; y <= 2000; y++)
	{
		//判断y是否为闰年
		//函数
		//是闰年,返回1
		//不是闰年,返回0
		if (is_leap_year(y) == 1)
		{
			printf("%d ", y);
		}
	}

	return 0;
}
  1. 写一个函数,实现一个整形有序数组的二分查找
//第一种写法
#include <stdio.h>
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[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 17;
	//计算数组的元素个数
	int sz = sizeof(arr) / sizeof(arr[0]);

	//找到了,返回下标
	//找不到,返回-1
	int ret = binary_search(arr, k, sz);//TDD - 测试驱动开发
	
	if (ret == -1)
		printf("找不到\n");
	else
		printf("找到了,下标是:%d\n", ret);

	return 0;
}
//第二种写法
int binary_search(int arr[], int k, int left, int right)
{
	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[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 3;
	//计算数组的元素个数
	int sz = sizeof(arr) / sizeof(arr[0]);

	//找到了,返回下标
	//找不到,返回-1
	int ret = binary_search(arr, k, 0, 9);//TDD - 测试驱动开发

	if (ret == -1)
		printf("找不到\n");
	else
		printf("找到了,下标是:%d\n", ret);

	return 0;
}
  1. 写一个函数,每调用一次这个函数,就会将num的值增加1
//第一种方法
void Add(int* p)
{
	*p = *p + 1;
}

int main()
{
	int num = 0;

	Add(&num);
	printf("%d\n", num);//1
	Add(&num);
	printf("%d\n", num);//2
	Add(&num);
	printf("%d\n", num);//3

	return 0;
}
//第二种方法
int Add(int n)
{
	return n + 1;
}

int main()
{
	int num = 0;

	num = Add(num);
	printf("%d\n", num);//1

	num = Add(num);
	printf("%d\n", num);//2
	
	num = Add(num);
	printf("%d\n", num);//3

	return 0;
}

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

嵌套调用

函数可以嵌套调用,但是不能嵌套定义

int main()
{
	//嵌套定义是不支持的 - err
	void test()
	{
		printf("hehe\n");
	}
	return 0;
}
#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;
}

链式访问

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

#include <stdio.h>
#include <string.h>
int main()
{
    //1
	int len = strlen("abc");
	printf("%d\n", len);
    
    //2
	printf("%d\n", strlen("abc"));//链式访问

    //1
	char arr1[20] = "xxxxxx";
	char arr2[20] = "abc";
    strcpy(arr1, arr2)

    printf("%s\n", arr1);
    
    //2
    char arr1[20] = "xxxxxx";
	char arr2[20] = "abc";
	printf("%s\n", strcpy(arr1, arr2));
    
	return 0;
}

printf函数返回的是打印在屏幕上的字符的个数

如果发生错误,将返回负数

#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    //4321
    return 0;
}

执行过程

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

先打印43,返回值2

printf("%d", printf("%d", 2))

打印2,返回值1

printf("%d", 1)

打印1

6. 函数的声明和定义

函数声明:

  1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,无关紧要。

  2. 函数的声明一般出现在函数的使用之前,满足先声明后使用

  3. 函数的声明一般放在头文件

//写法一
#include <stdio.h>
int Add(int x, int y)
{
	int z = x + y;
	return z;
}

int main()
{
	int a = 10;
	int b = 20;
	
	//加法
	int ret = Add(a, b);
	
	printf("%d\n", ret);

	return 0;
}
//写法二
#include <stdio.h>
int Add(int x, int y);//先声明,后使用

int main()
{
	int a = 10;
	int b = 20;

	//加法
	int ret = Add(a, b);

	printf("%d\n", ret);

	return 0;
}

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

add.h文件里放声明

方式一:首行附

#pragma once

使得test.c内只包含一次add.h头文件内容

方式二:

#ifndef __ADD_H__
#define __ADD_H__
//函数的声明
int Add(int x, int y);
#endif 

两者完全等价

工程实际操作

image-20210904104453975

image-20210904104529542

image-20210904104641030

函数定义:

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

test.h的内容 放置函数的声明

test.c的内容 放置函数的实现

模块化设计

  1. 多人协作

  2. 封装和隐藏

例:

开发需要保护的模块“add”

image-20210904111454120

配置类型 .lib

image-20210904111732102

image-20210904111800484

image-20210904111830802

交付第三方使用:

add.h

add.lib

image-20210904112506484

image-20210904112659122

导入静态库

#include <stdio.h>
#include "add.h"
//导入静态库
#pragma comment(lib, "add.lib")

int main()
{
	int a = 10;
	int b = 20;
	
	//加法
	int ret = Add(a, b);
	
	printf("%d\n", ret);

	return 0;
}

7. 函数递归

程序调用自身的编程技巧称为递归( recursion)。

一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法

通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解

递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归的主要思考方式:把大事化小

递归的两个必要条件

  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件

image-20210904133325405

练习

练习1

接受一个整型值(无符号),按照顺序打印它的每一位。

例如:

输入:1234,输出 1 2 3 4.

image-20210904131843446

image-20210904133709661

image-20210904133430757

//函数递归
void print(unsigned int n)
{
	if (n > 9)
	{
		print(n / 10);
	}
	printf("%d ", n % 10);
}

int main()
{
	//接受一个整型值(无符号),按照顺序打印它的每一位
	unsigned int num = 0;
	scanf("%u", &num);//1234
	print(num);
	return 0;
}

image-20210904140004141

PS:涉及函数栈帧的创建和销毁

练习2

编写函数不允许创建临时变量,求字符串的长度。

//库函数
#include <stdio.h>
#include <string.h>
int main()
{
	//求字符串长度
	char arr[10] = "abcd";

	int len = strlen(arr);//6
	printf("%d\n", len);

	return 0;
}

image-20210904141124546

//自定义函数
#include <stdio.h>
#include <string.h>
//写法一
int my_strlen(char* s)
{
	int count = 0;
	while (*s != '\0')
	{
		count++;
		s++;
	}
	return count;
}

int main()
{
	//求字符串长度
	char arr[10] = "abcd";
	//数组名arr是数组首元素的地址 - char*

	int len = my_strlen(arr);//4
	printf("%d\n", len);

	return 0;
}

image-20210904141524267

//递归写法
int my_strlen(char* s)
{
	if (*s != '\0')
	{
		return 1 + my_strlen(s+1);
	}
	else
	{
		return 0;
	}
}

int main()
{
	//求字符串长度
	char arr[10] = "abcd";
	//数组名arr是数组首元素的地址 - char*

	int len = my_strlen(arr);//4
	printf("%d\n", len);

	return 0;
}

image-20210904163329114

image-20210904165119001

递归与迭代

练习3

求n的阶乘。(不考虑溢出)

//循环迭代写法
int Fac1(int n)
{
	int i = 0;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret *= i;
	}
	return ret;
}

//递归
int Fac(int n)
{
	if (n <= 1)
		return 1;
	else
		return n* Fac(n - 1);
}

int main()
{
	int n = 0;
	scanf("%d", &n);//3
	//求n的阶乘
	int ret = Fac(n);
	printf("%d\n", ret);

	return 0;
}

练习4

求第n个斐波那契数。(不考虑溢出)

特点:前2个数字之后等于第3个数字

//递归---并不适合
int count = 0;

int Fib1(int n)
{
	if (n == 3)
	{
		count++;//计算Fib1(3)被计算的次数
	}
    
	if (n <= 2)
		return 1;
	else
		return Fib1(n - 1) + Fib1(n - 2);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib1(n);
	printf("%d\n", ret);

	//printf("count = %d\n", count);
	return 0;
}
//迭代(循环)
int Fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;

	while (n > 2)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\n", ret);

	return 0;
}

函数递归的几个经典题目(自主研究):

  1. 汉诺塔问题
  2. 青蛙跳台阶问题
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-05 10:37:43  更:2021-09-05 10:39:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 20:20:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计