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++知识库 -> C语言学习之路--函数,从知识到实战 -> 正文阅读

[C++知识库]C语言学习之路--函数,从知识到实战

一、前言

  • 本人是一名小白,这一篇是记录我C语言学习中的函数的第一部分的所学所得,仅为简单的认识下C语言中的各个知识。

二、函数是什么?

  • 在数学中,我们通常提到函数,比如y=f(x)。
  • 那么在C语言中呢?
  • 维基百科中对函数的定义:子程序
    在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method,
    subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组
    成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性
    一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
  • 举例:求字符串长度
    在这里插入图片描述

三、C语言中函数的分类

1、库函数

1、为什么会有库函数?

  1. 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想
    把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格
    式打印到屏幕上(printf)。
  2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
  3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。
  • 像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,
    为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员
    进行软件开发。(标准库–提供了C语言的库函数)

2、如何学习库函数?

  • 太多,要学会查
    这里我们简单的看看:www.cplusplus.com
    在这里插入图片描述
  • 简单的总结,C语言常用的库函数都有:

IO函数,input,output函数,输入输出函数,scanf,printf,getchar,putchar
字符串操作函数,strlen,strcmp
字符操作函数,大小写转换,字符分类
内存操作函数,memcpy,memmove,memset
时间/日期函数,time
数学函数,pow,sqrt
其他库函数

3、学习库函数举例

1、strcpy

学习网址:https://cplusplus.com/reference/cstring/strcpy/?kw=strcpy
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 举例
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>//strcpy的头文件

int main()
{
	//char* strcpy(char* destination, const char* source);
	//字符串拷贝的时候
	//hello world
	char arr1[20] = { 0 };
	char arr2[] = "hello world";

	//把arr2中的字符串拷贝到arr1中
	strcpy(arr1, arr2);
	//数组名代表其数组所占内存空间起始位置的地址(不是变量),把arr2中的hello world\0中的字符一个一个拿给arr1
	//检验是否成功
	printf("%s\n", arr1);
	return 0;
}

运行结果
在这里插入图片描述

  • 运用返回值
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>//strcpy的头文件

int main()
{
	//char* strcpy(char* destination, const char* source);
	//字符串拷贝的时候
	//hello world
	char arr1[20] = { 0 };
	char arr2[] = "hello world";

	//运用其返回值(目标空间的起始地址)
	char * ret= strcpy(arr1, arr2);
	printf("%s\n", ret);//从起始地址打印到\0
	return 0;
}

运行结果
在这里插入图片描述

2、memset

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 举例
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>//memset的头文件


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

int main()
{
	//把arr[20] = "hello world"改成arr[20] = "xxxxx world"
	char arr[20] = "hello world";
	printf("%s\n", arr);
	memset(arr, 'x', 5);
	printf("%s\n", arr);
	return 0;
}

运行结果
在这里插入图片描述

  • 注:
    库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。
    这里对照文档来学习上面几个库函数,目的是掌握库函数的使用方法。

3、库函数学习工具总结

  • 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    函数参数

3、举例

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//写出函数找最大值
int get_max(int x,int y)
{
	if (x >= y)
	{
		return x;
	}
	else
	{
		return y;
	}//也可以用return(x>y?x:y)
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int m = get_max(a, b);
	printf("%d\n", m);
	return 0;
}

运算结果
在这里插入图片描述
2.

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

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

int ex(int x, int y)
{
	int tep = 0;
	tep = x;
	x = y;
	y = tep;
}

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

运行结果
在这里插入图片描述

  • 和咱们想的不同,为什么会这样呢?
    我们会发现,a,b(实际参数)传给x,y(形式参数)的时候,都有各自的不同空间。
    当函数调用的时候,实参传递给形参,这时实参只是形参的一份临时拷贝,对形参的改变不影响实参。
  • 我们这样设计
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

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

void ex(int* pa, int* pb)
{
	int tep = 0;
	tep = *pa;
	*pa = *pb;
	*pb = tep;
}//地址不可以交换

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

运行结果
在这里插入图片描述

  • 那么在1中不用这么操作呢?
    只是因为a,b和x,y不用被操作,a.b和x,y的最大值相同。

4、void说明

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//第一个void表示函数调用完后,什么都不用返回
//第一个void不写时默认int,下面没写返回什么的时候,返回取决于编译器,通常是最后一条指令的返回值
//第二个void表示这个函数没有参数,调用的时候不需要传参
void test(void)
{
	printf("haha\n");
}


int main()
{
	test();
	return 0;
}

四、函数的参数

1、 实际参数(实参)

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

2 、形式参数(形参)

  • 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内
    存单元),所以叫形式参数。
  • 形式参数当函数调用完成之后就自动销毁了
  • 因此形式参数只在函数中有效。

五、函数的调用

1、传值调用

  • 函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。(类似求最大值)

2、传址调用

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

3、练习

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

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int is_prime(int x)
{
	int j = 0;
	for (j = 2; j <= x; j++)
	{
		if (x % j == 0)
		{
			break;
		}
	}
	if (j == x)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int main()
{
	int i = 0;
	for (i = 100; i <= 200; i++)
	{
		//设计函数判断
		if (is_prime(i))
		{
			printf("%d ", i);
		}

	}
	return 0;
}

运行结果
在这里插入图片描述

2、写一个函数判断一年是不是闰年。

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int is_leap_year(int x)
{
	if (x % 100 == 0)
	{
		if (x % 400 == 0)
			return 1;
		else
			return 0;
	}
	else
	{
		if (x % 4 == 0)
			return 1;
		else
			return 0;
	}
}

int main()
{
	int y = 0;
	for (y = 1000; y <= 2000; y++)
	{
		if (is_leap_year(y))
		{
			printf("%d ",y);
		}
	}
}

运行结果
在这里插入图片描述

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

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//写一个函数,实现一个整形有序数组的二分查找
int serach(int key,int arr[],int size)
{
	int left = 0;
	int right = size - 1;
	int flag = 0;
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] > key)
		{
			right--;
		}
		else if (arr[mid] < key)
		{
			left++;
		}
		else
		{
			return mid;
			flag = 1;
		}
	}
	if (flag == 0);
	{
		return 0;
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int key = 0;
	int size = 0;
	scanf("%d", &key);
	//printf("%d\n", sizeof(arr));//40,这里计算整个数组的大小,单位是字节
	//printf("%d\n", sizeof(arr[0]));//4,这里计算数组第一个元素的大小,单位是字节
	//strlen只用于字符串
	size = sizeof(arr) / sizeof(arr[0]);
	int ret = serach(key, arr,size);
	if (ret == 0)
	{
		printf("找不到。\n");
	}
	else
	{
		printf("%d\n", ret);
	}
	return 0;
}

运行结果
在这里插入图片描述

4、写一个函数,每调用一次这个函数,就会将 num 的值增加1。

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

void Add(int* p)
{
	*p = *p + 1;
	//(*p)++;
}

int main()
{
	int num = 0;
	Add(&num);
	return 0;
}

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

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

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

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

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
	int Add(int x, int y)//嵌套定义
	{
		return x + y;
	}
	return 0;
}

会报错

2、链式访问

  • 把一个函数的返回值作为另外一个函数的参数。
  • 举例
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
	char arr[] = "abc";
	printf("%d\n", strlen(arr));
	return 0;
}

运行结果
在这里插入图片描述
2.

  • 一个典型
#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    //结果是啥?
    //注:printf函数的返回值是打印在屏幕上字符的个数,一个数字算一个字符,43是两个字符
    return 0;
}

运行结果
在这里插入图片描述

七、函数的声明和定义

1、函数声明

  1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数
    声明决定不了。
  2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用
  3. 函数的声明一般放在头文件中。

2、函数的定义

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

3、举例

  • 假如我们直接将自定义函数放在主函数下面,会发生什么呢?
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

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

	return 0;
}


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

这时我们发现有警告
在这里插入图片描述
这是因为扫描代码是从前往后的,我们可以这样

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//函数的声明,变量的声明类似
int Add(int, int);

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

	return 0;
}

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

也可以这样

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//函数的定义
int Add(int x, int y)
{
	return x + y;
}

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

	return 0;
}

这时警告就消失了,同时我们注意到,函数的定义也是一种特殊的声明
2.

  • 那真正在工程中呢?
    在头文件(add.h)中,放声明
int Add(int, int);

在源文件(add.c)中放函数定义

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

在源文件(test.c)中,放主函数,并加上#include “add.h”

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "add.h"

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int ret = Add(a, b);
	printf("%d\n", ret);
	return 0;
}

运行结果
在这里插入图片描述
此时,我们应该注意到,头文件(add.h)和源文件(add.c)是一个代码模块,这里是一个加法模块。这样有助于工程的分工。

  • 优势:
    1.模块化开发(分工)
    2.代码的隐藏

  • 注意:库函数引用头文件用<>,自己的用""。

八、函数递归

  • 递 - 递推
  • 归 - 回归

1、什么是递归?

  • 程序调用自身的编程技巧称为递归( recursion)。
  • 递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量
  • 递归的主要思考方式在于:把大事化小

2、递归的两个必要条件

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

3、练习

  • 最简单的递归,但也是错误的递归
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

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

在我们调试的时候我们会发现,栈溢出了。每一次调用函数,都会为本次函数
在内存的栈区上调用空间。
在这里插入图片描述
2.

接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

void Print(unsigned int n)
{
	if (n > 9)
	{
		Print(n/10);
	}
		printf("%d ", n % 10);
}

int main()
{
	unsigned int sum = 0;
	scanf("%d", &sum);
	Print(sum);

	return 0;
}

运算结果
在这里插入图片描述
2.

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

有变量版本

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int my_strlen(char* pa)
{
	int count = 0;
	while (*pa != '\0')
	{
		count++;
		pa++;
		//字符指针+1,向后跳1个字符
	    //整型指针 + 1, 向后跳1个整型,就是4个字节
	}
	return count;
}

int main()
{
	char arr[] = "heu";
	//[h e u \0]
	//数组名其实就是数组首个元素的地址
	//
	int ret = my_strlen(arr);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述
没有临时变量版本

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int my_strlen(char* pa)
{
	if (*pa != '\0')
		return 1 + my_strlen(pa + 1);
	else
		return 0;
}

int main()
{
	char arr[] = "heu";
	//[h e u \0]
	//数组名其实就是数组首个元素的地址
	//
	int ret = my_strlen(arr);
	printf("%d\n", ret);
	return 0;
}

运行结果
在这里插入图片描述
3.

求n的阶层

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//递归
int factorial(int n)
{
	if (n == 1)
		return 1;
	else
		return n * factorial(n - 1);
}
//循环
int Fac(int a)
{
	int i = 1;
	int ret_1 = 1;
	for (i = 1; i <= a; i++)
	{
		ret_1 = ret_1 * i;
	}
	return ret_1;

}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = factorial(n);
	printf("%d\n", ret);
	
	int ret_2 = Fac(n);
	printf("%d\n", ret_2);
	return 0;
}

运行结果
在这里插入图片描述
4.

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

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//递归,计算量大
int Fib(int n)
{
	int i = 0;
	if (n <= 2)
	{
		i = 1;
	}
	else
	{
		i = Fib(n - 1) + Fib(n - 2);
	}
}
//迭代(循环);非迭代
int Fibn(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);

	int ret_1 = Fibn(n);
	printf("%d", ret_1);
	return 0;
}

运行结果
在这里插入图片描述

4、递归和迭代

1、迭代出现的问题

  • 在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出)这样的信息。
  • 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一
    直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出

2、如何解决上述的问题:

  1. 将递归改写成非递归。
  2. 使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

3、选择方式

  1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开
    销。

九、总结

  • 本博客记录了我一些对于函数的所学所得,有不足的地方欢迎指出,后续会逐一补充。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:06:35  更:2022-11-05 00:11:55 
 
开发: 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年5日历 -2024/5/19 5:06:45-

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