C++复习的长文指南(二)

news2024/11/15 15:32:05

C++复习的长文指南(二)

  • 一、面向对象基础知识
    • 5. 文件操作
      • 5.1文本文件
        • 5.1.1写文件
        • 5.1.2读文件
      • 5.2 二进制文件
        • 5.2.1 二进制文件
        • 5.2.2 二进制读文件
    • 6. c++面向对象的个人心得
      • 开发流程
      • 6.1
      • 6.2
      • 6.3
      • 6.4
      • 6.5
      • 注意细节
      • 6.1
      • 6.2
      • 6.3
  • 二、泛型编程
    • 1. 模板
      • 1.1 模板的概念
      • 1.2 函数模板
        • 1.2.1 函数模板语法
        • 1.2.2 函数模板注意事项
        • 1.2.3 函数模板案例
        • 1.2.4 普通函数和函数模板的区别
        • 1.2.5 普通函数与函数模板的调用规则
        • 1.2.6 模板的局限性
      • 1.3 类模板
        • 1.3.1 类模板语法
        • 1.3.2 类模板和普通模板区别
        • 1.3.3 类模板中成员函数创建时机
        • 1.3.4 类模板对象做参数
        • 1.3.5 类模板与继承
        • 1.3.6 类模板成员函数类外实现
        • 1.3.7 类模板分文件编写
        • 1.3.8 类模板与友元
        • 1.3.9 类模板案例
    • 2. STL初始
      • 2.1 STL的诞生
      • 2.2 STL基本概念
      • 2.3 STL六大组件
      • 2.4 STL中容器、算法、迭代器
      • 2.5 容器算法迭代器初识
        • 2.5.1 vector存放内置数据类型
        • 2.5.2 vector存放自定义数据类型
        • 2.5.3 vector容器嵌套容器

一、面向对象基础知识

5. 文件操作

程序运行时产生的数据都属于临时数据,程序—旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件<fstream >
文件类型分为两种:
1.文本文件:文件以文本的ASCII码形式存储在计算机中
2.二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
1.ofstream:写操作
2.ifstream:读操作
3.fstream :读写操作

5.1文本文件

5.1.1写文件

写文件步骤如下:
1.包含头文件
#include <fstream>
2.创建流对象
ofstream ofs;
3.打开文件
ofs.open(“文件路径",打开方式);
4.写数据
ofs <<“写入的数据”;
5.关闭文件
在这里插入图片描述
注意:文件打开方式可以配合使用,利用|操作符
例如:用二进制方式写文件ios : : binary | ios : : out
总结:
文件操作必须包含头文件fstream
读文件可以利用ofstream ,或者fstream类
打开文件时候需要指定操作文件的路径,以及打开方式
利用<<可以向文件中写数据
操作完毕,要关闭文件

#include<iostream>
using namespace std;
#include<fstream> // 头文件的包含
// 文本文件 写文件
void test01() 
{
	// 1.包含头文件
	// 2.创建流对象
	ofstream ofs; // 创建输出流对象

	// 3.指定打开方式
	ofs.open("test.txt", ios::out);

	// 4.写内容
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;

	// 5.关闭文件
	ofs.close();
}
int main()
{
	test01();
	system("pause");
	return 0;
}
5.1.2读文件

读文件与写文件步骤相似,但是读取方式相对于比较多
读文件步骤如下:
1.包含头文件
#include <fstream>
2.创建流对象
ifstream ifs;
3.打开文件并判断文件是否打开成功ifs.open(“文件路径”,打开方式);
4.读数据
四种方式读取
5.关闭文件ifs.close();

#include<iostream>
using namespace std;
#include<fstream> // 头文件的包含
#include<string>
// 文本文件 读文件
void test01() 
{
	// 1.包含头文件
	// 2.创建流对象
	ifstream ifs; // 创建输入流对象

	// 3.打开文件 并且判断是否打开成功
	ifs.open("test.txt", ios::in);
	if (! ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	// 4.读数据
	// 第一种
	/*char buf[1024] = { 0 };
	while (ifs >> buf)
	{
		cout << buf << endl;
	}*/
	
	// 第二种
	/*char buf[1024] = { 0 };
	while (ifs.getline(buf, sizeof(buf)))
	{
		cout << buf << endl;
	}*/

	// 第三种
	/*string buf;
	while (getline(ifs, buf))
	{
		cout << buf << endl;
	}*/

	// 第四种,1次只读1个字符
	char c;
	while ( (c = ifs.get()) != EOF)  // EOF end of file 文件尾
	{
		cout << c;
	}
	// 5.关闭文件
	ifs.close();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:
1.读文件可以利用ifstream ,或者stream类
2.利用is_open函数可以判断文件是否打开成功
3.close关闭文件

5.2 二进制文件

二进制的方式对文件进行读写操作
打开方式要指定为ios:binary

5.2.1 二进制文件

二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer ,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include<iostream>
using namespace std;
#include<fstream> // 头文件的包含
#include<string>
// 二进制文件 写文件

class Person
{
public:
	char m_Name[64]; // 姓名
	int m_Age; // 年龄
};
void test01() 
{
	// 1.包含头文件
	// 2.创建流对象
	ofstream ofs;
	// 或者直接
	//ofstream ofs("person.txt", ios::out | ios::binary);
	// 3.打开文件
	ofs.open("person.txt", ios::out | ios::binary);
	// 4.写文件
	Person p = { "张三", 18 };

	// 如果直接&p,返回的应该是Person*,所以再强转const char*
	ofs.write((const char*)&p, sizeof(Person));
	// 5.关闭文件
	ofs.close();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:
1.文件输出流对象可以通过write函数,以二进制方式写数据

5.2.2 二进制读文件

二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include<iostream>
using namespace std;
#include<fstream> // 头文件的包含
#include<string>
// 二进制文件 读文件

class Person
{
public:
	char m_Name[64]; // 姓名
	int m_Age; // 年龄
};
void test01() 
{
	// 1.包含头文件
	// 2.创建流对象
	ifstream ifs;
	// 3.打开文件,并判断文件是否正常打开
	ifs.open("person.txt", ios::in | ios::binary);
	if (! ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}

	// 4.写文件
	// 将数据读到Person中,因为Person是自定义数据类型,
	Person p;
	ifs.read((char*)&p, sizeof(p));
	cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
	// 5.关闭文件
	ifs.close();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:
文件输入流对象可以通过read函数,以二进制方式读数据

6. c++面向对象的个人心得

开发流程

6.1

1.一般进行分文件编写,.h头文件进行类申明.cpp文件进行类的具体实现

6.2

2.一般进行面向对象的编程,封装、继承、多态都用到。比如以经典的“职工管理系统”为例,那么该系统有:职工、经理和老板3种身份,以及“职工管理系统”的增删改查等用户功能操作需要单独定义1个管理系统类(也是分开申明和实现)。

6.3

3.那既然要用到面向对象多态肯定定义一个职工父类(基类),然后分别定义职工、经理和老板3种身份的子类继承父类,并重写父类的虚函数父类指针指向子类,不同子类调用相同的函数实现不同的功能。
4.也就是说,最终分别进行基类、3种身份的子类、1个管理系统类.h头文件申明,再进行各自的.cpp文件实现。职工间的父子关系用到多态,其余的职工数据的增删改查、文件保存等全部放在管理系统类实现功能。最后,还有1个main函数入口,通常主函数实例化类对象进行调用对应不同功能的函数接口。

6.4

5.最后根据选择判断语句,进行调用管理系统类对象的不同成员函数就行(增删改查等)
ps:职工父类(基类)、3种身份的子类的类,说到底在c++中还是不同的自定义类型,最终还是通过父类指针指向子类等操作进行职工维护的。

6.5

6.最后,因为程序运行时产生的数据都属于临时数据,哪怕是堆区数据,程序—旦运行结束都会被释放,我们,通过文件可以将数据持久化

注意细节

6.1

一般,像管理系统类,肯定要有1个记录当前系统已经存放的职工人数整型成员变量,以及职工父类(基类)的自定义数据类型数组指针(一般是数组指针,涉及多个职工,肯定用数组进行维护,并实现不同职工的多态
ps:每次进行完增删改查操作后,一定要对类内成员变量进行数据更新,比如这里的m_Empnum进行+1,数组指针指向进行更新等。
在这里插入图片描述

6.2

一般,虽然类的构造函数析构函数,编译器会自动提供空实现,但是一般我们进行重写
很明显,用构造函数进行变量初始化,比如整型变量置0指针指向空地址
在这里插入图片描述

析构函数就是用来释放开辟到堆区的数据,一般不会将数据放在栈区,因为当函数执行结束,编译器会自动回收,下次再执行就会出问题。
在这里插入图片描述

6.3

正常,在类内,成员属性等建议使用this指针这个习惯。

二、泛型编程

主要针对C++泛型编程STL技术做详细讲解,探讨C++更深层的使用

1. 模板

1.1 模板的概念

模板就是建立通用的模具,大大提高复用性
例如生活中的模板
—寸照片模板:
在这里插入图片描述

在这里插入图片描述
模板的特点:
1.模板的通用性很强,但是不可以直接使用,只是一个框架
2.也并非万能,比如证件照只能针对证件照,做不了别的风格照片。

1.2 函数模板

1.C++另一种编程思想称为泛型编程,主要利用的技术就是模板
2.C++提供两种模板机制:函数模板类模板

1.2.1 函数模板语法

函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
语法:

template<typename T>
函数申明或定义

解释:
template :声明创建模板
typename :表面其后面的符号是一种数据类型,可以用class代替
T:通用的数据类型,名称可以替换,通常为大写字母

#include<iostream>
using namespace std;

// 交换整型数据
void swapInt(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

// 交换浮点型数据
void swapDouble(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}

// 利用模板提供通用的交换函数
template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型

void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test01()
{
	int a = 10;
	int b = 20;
	swapInt(a, b);

	// 利用模板实现交换
	// 1.自动类型推导
	mySwap(a, b);

	// 2.显示指定类型
	mySwap<int>(a, b);

	double c = 10.2;
	double d = 20.2; 
	swapDouble(c, d);



}
int main()
{

	system("pause");
	return 0;
}

总结:
1.函数模板利用关键字template
2.使用函数模板有两种方式:自动类型推导、显示指定类型
3.模板的目的是为了提高复用性,将数据类型参数化

1.2.2 函数模板注意事项

注意事项:
1.自动类型推导,必须推导出一致的数据类型T,才可以使用
2.模板必须要确定出T的数据类型,才可以使用

#include<iostream>
using namespace std;

// 函数模板注意事项:
template<class T> // typename可以替换成class

void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

// 1、自动类型推导,必须推导出一致的数据类型T才可以使用
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';
	mySwap(a, b); // 正确
	///mySwap(a, c); // 错误, 推导不出一致的T类型

}
// 2、模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{
	cout << "func()的调用" << endl;
}

void test02()
{
	//func(); // 错误
	func<int>(); // 正确,因为编译器自动推导不出类型,只能进行显示指定类型
}
int main()
{

	system("pause");
	return 0;
}

总结:
1.使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型。

1.2.3 函数模板案例

案例描述:
1.利用函数模板封装—个排序的函数,可以对不同数据类型数组进行排序
2.排序规则从大到小,排序算法为选择排序
3.分别利用char数组和int数组进行测试

#include<iostream>
using namespace std;

template<typename T>
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
// 排序算法
template<typename T>
void mySort(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i; // 认定最大值的下标

		for (int j = i + 1; j < len; j++)
		{
			// 认定的最大值比遍历出来的小
			if (arr[max] < arr[j])
			{
				max = j; //更新最大值下标
			}
		}

		if (max != i)
		{
			// 交换max和i元素
			mySwap(arr[max], arr[i]);

		}
	}
}

// 提供打印模板
template<typename T>
void printArray(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}
void test01()
{
	// 测试char数组
	char charArr[] = "badcfe";
	mySort(charArr, sizeof(charArr) / sizeof(charArr[0]));
	printArray(charArr, sizeof(charArr) / sizeof(charArr[0]));
}

void test02()
{
	// 测试int数组
	int IntArr[] = { 17,1, 2,5,7,10 };
	mySort(IntArr, sizeof(IntArr) / sizeof(IntArr[0]));
	printArray(IntArr, sizeof(IntArr) / sizeof(IntArr[0]));
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}
1.2.4 普通函数和函数模板的区别

普通函数与函数模板区别:
1.普通函数调用时可以发生自动类型转换(隐式类型转换)
2.函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
3.如果利用显示指定类型的方式,可以发生隐式类型转换

#include<iostream>
using namespace std;

// 普通函数与函数模板区别
// 1、普通函数调用可以发生隐式类型转换
// 2、函数模板用自动类型推导,不可以发生隐式类型转换
// 3、函数模板用显示指定类型,可以发生隐式类型转换


// 普通函数
int myAdd01(int a, int b)
{
	return a + b;
}

// 函数模板
template<typename T>
T myAdd02(T a, T b)
{
	return a + b;
}

void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c'; //asicc码,a-97
	cout << myAdd01(a, b) << endl;
	cout << myAdd01(a, c) << endl;  // 10 + 99

	// 自动类型推导, 不会发生隐式类型转换
	cout << myAdd02(a, b) << endl;
	//cout << myAdd02(a, c) << endl;

	//显示指定类型, 会发生隐式类型转换
	cout << myAdd02<int>(a, c) << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:
建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

1.2.5 普通函数与函数模板的调用规则

调用规则如下:
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配,优先调用函数模板

#include<iostream>
using namespace std;

// 普通函数与函数模板调用规则

//void myPrint(int a, int b);


void myPrint(int a, int b)
{
	cout << "普通函数的调用" << endl;
}


template<typename T>
void myPrint(T a, T b)
{
	cout << "模板的调用" << endl;
}

template<typename T>
void myPrint(T a, T b, T c)
{
	cout << "模板重载的调用" << endl;
}

void test01()
{
	int a = 10;
	int b = 20;
	//myPrint(a, b);

	// 如果myPrint()函数只留下申明,没有实现会报错
	// 通过空模板参数列表,强制调用函数模板
	// 哪怕是myPrint()函数有了实现,空模板参数列表也会强制调用函数模板
	myPrint<>(a, b);

	myPrint<>(a, b, 100);

	// 如果函数模板产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。

1.2.6 模板的局限性

局限性:
1.模板的通用性并不是万能的
例如:

template<typename T>
void f(T a, T b)
{
	a = b;
}

在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了。
再例如:

template<typename T>
void f(T a, T b)
{
	if (a > b)
	{
		//
	}
}

在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行。
因此,C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

#include<iostream>
using namespace std;

class Person
{
public:
	Person(int age, string name)
	{
		this->m_Age = age;
		this->m_Name = name;
	}

	int m_Age;
	string m_Name;
};
template<typename T>
bool Compare(T& a, T& b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return false;
	}
}

// 利用具体化的Person的版本代码,具体化优先调用
template<> bool Compare(Person& p1, Person& p2)
{
	if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name)
	{
		return true;
	}
	else
	{
		return false;
	}
}
void test01()
{
	Person p1(10, "Tom");
	Person p2(10, "Tom");

	bool ret = Compare(p1, p2);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:
1.利用具体化的模板,可以解决自定义类型的通用化
2.学习模板并不是为了写模板,而是在STL能够运用系统提供的模板

1.3 类模板

1.3.1 类模板语法

类模板作用:
建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟类型来代表。
语法:

template<typename T>

解释:
template:声明创建模板
typename:表面其后面的符号是一种数据类型,可以用class代替
T:通用的数据类型,名称可以替换,通常为大写字母

#include<iostream>
using namespace std;
#include<string>


template<class NameType, class AgeType = int>
class Person
{
public:

	Person(NameType name, AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	void showPerson()
	{
		cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

void test01()
{
	Person<string, int> p("张三", 19); 
	p.showPerson();

}

int main()
{
	test01();
	system("pause");
	return 0;
}

总结:类模板和函数模坂语法相似,在声明模板template后面加类,此类称为类模板

1.3.2 类模板和普通模板区别

类模板与函数模板区别主要有两点:
1.类模板没有自动类型推导的使用方式
2.类模板在模板参数列表中可以有默认参数

#include<iostream>
using namespace std;
#include<string>

// 类模板和普通模板区别

template<class NameType, class AgeType = int>
class Person
{
public:

	Person(NameType name, AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	void showPerson()
	{
		cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

// 1.类模板没有自动类型推导的使用方式
void test01()
{
	//Person p("张三", 19); // 错误,无法用自动类型推导
	Person<string, int> p("张三", 19); // 正确,只能用显示指定类型
	p.showPerson();

}
// 2.类模板在模板参数列表中可以有默认参数
void test02()
{
	Person<string> p2("张三", 19); 
	p2.showPerson();

}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

总结:
1.类模板使用只能用显示指定类型方式
2.类模板中的模板参数列表可以有默认参数

1.3.3 类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:
1.普通类中的成员函数一开始就可以创建
2.类模板中的成员函数在调用时才创建

#include<iostream>
using namespace std;
#include<string>

// 类模板中成员函数创建时机
// 类模板中成员函数在调用时才去创建

class Person1
{
public:
	void showPerson1()
	{
		cout << "Person1 show" << endl;
	}
};

class Person2
{
public:
	void showPerson2()
	{
		cout << "Person2 show" << endl;
	}
};

template<class T>

// 类模板中成员函数在调用时才去创建,是因为创建时编译器压根不知道T这个数据类型
class MyClass
{
public:
	T obj;

	// 类模板中成员函数
	void func1()
	{
		obj.showPerson1();
	}
	void func2()
	{
		obj.showPerson2();
	}

};

void test01()
{
	MyClass<Person1> m1;
	m1.func1();


	MyClass<Person2> m2;
	m2.func2();

}

int main()
{
	test01();
	system("pause");
	return 0;
}

总结:类模板中的成员函数并不是—开始就创建的,在调用时才去创建

1.3.4 类模板对象做参数

学习目标:
类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
1.指定传入的类型:直接显示对象的数据类型
2.参数模板化:将对象中的参数变为模板进行传递
3.整个类模板化:将这个对象类型模板化进行传递

#include<iostream>
using namespace std;
#include<string>

// 类模板对象做函数参数
template<class NameType, class AgeType = int>
class Person
{
public:

	Person(NameType name, AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	void showPerson()
	{
		cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

// 1、指定传入类型
void printPerson1(Person<string, int>& p)
{
	p.showPerson();
}
void test01()
{
	Person<string, int> p("张三", 19); 
	printPerson1(p);
}
// 2、参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p)
{
	p.showPerson();
	cout << "T1 的类型为:" << typeid(T1).name() << endl;
	cout << "T2 的类型为:" << typeid(T2).name() << endl;
}
void test02()
{
	Person<string, int> p("李四", 29);
	printPerson2(p);
}

// 3、整个类模板化
template<class T>
void printPerson3(T& p)
{
	p.showPerson();
	cout << "T 的类型为:" << typeid(T).name() << endl;
}
void test03()
{
	Person<string, int> p("王五", 39);
	printPerson3(p);
}
int main()
{
	//test01();
	//test02();
	test03();
	system("pause");
	return 0;
}

总结:
1.通过类模板创建的对象,可以有三种方式向函数中进行传参
2.使用比较广泛是第一种:指定传入的类型

1.3.5 类模板与继承

当类模板碰到继承时,需要注意一下几点:
1.当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
2.如果不指定,编译器无法给子类分配内存
3.如果想灵活指定出父类中T的类型,子类也需变为类模板

#include<iostream>
using namespace std;
#include<string>

// 类模板与继承
template<class T>
class Base 
{
	T m;
};

//class Son : public Base // 错误,必须要知道父类中的T类型,才能继承给子类
class Son : public Base<int>
{

};

void test01()
{
	Son s1;
}


// 如果想灵活指定父类中T类型,子类也需要变类模板
template<class T1, class T2>
class Son2 : public Base<T2>
{
public:
	Son2()
	{
		cout << "T1 的类型:" << typeid(T1).name() << endl;
		cout << "T2 的类型:" << typeid(T2).name() << endl;
	}
	T1 obj;
};

void test02()
{
	Son2<int, char> s2;
}
int main()
{
	//test01();
	test02();
	
	system("pause");
	return 0;
}

总结:如果父类是类模板,子类需要指定出父类中T的数据类型

1.3.6 类模板成员函数类外实现

学习目标:能够掌握类模板中的成员函数类外实现

#include<iostream>
using namespace std;
#include<string>

template<class T1, class T2>
class Person
{
public:

	Person(T1 name, T2 age);
	
	void showPerson();
	

	T1 m_Name;
	T2 m_Age;
};

// 构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}

// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}

void test01()
{
	Person<string, int> p1("张三", 19);
	p1.showPerson();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:类模板中成员函数类外实现时,需要加上模板参数列表

1.3.7 类模板分文件编写

学习目标:
1.掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:
1.类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
1.解决方式1:直接包含.cpp源文件
2.解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
示例:
首先,新建person.h头文件,
在这里插入图片描述

#pragma once
#include<iostream>
using namespace std;
#include<string>

template<class T1, class T2>
class Person
{
public:

	Person(T1 name, T2 age);

	void showPerson();


	T1 m_Name;
	T2 m_Age;
};

接着,新建person.cpp源文件,
在这里插入图片描述

#include"person.h"
// 构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}

// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}

上述代码看似没问题,会报错的。因为,类模板中成员函数一开始是不会创建,类模板对象调用时创建,所以包含"person.h"头文件时,编译器没有见到过T这些数据类型,所以链接不到。
解决方法1:
main函数中更改为:
直接包含源文件

#include<iostream>
using namespace std;
#include<string>
#include"person.cpp"

void test01()
{
	Person<string, int> p1("张三", 19);
	p1.showPerson();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

解决方法2:
将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
在这里插入图片描述

#pragma once
#include<iostream>
using namespace std;
#include<string>

template<class T1, class T2>
class Person
{
public:

	Person(T1 name, T2 age);

	void showPerson();


	T1 m_Name;
	T2 m_Age;
};

// 构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}

// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}

总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

1.3.8 类模板与友元

学习目标:
1.掌握类模板配合友元函数的类内和类外实现
全局函数类内实现:直接在类内声明友元即可
全局函数类外实现:需要提前让编译器知道全局函数的存在

#include<iostream>
using namespace std;
#include<string>

// 通过全局函数打印Person信息

// 提前让编译器知道Person类存在
template<class T1, class T2>
class Person;


// 类外实现
template<class T1, class T2>
void printPerson2(Person<T1, T2> p)
{
	cout << "类外实现--姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;

}


template<class T1, class T2>
class Person
{
public:
	// 全局函数类内实现
	friend void printPerson(Person<T1, T2>& p)
	{
		cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;

	}
	// 全局函数类外实现
	// 需要加1个空模板参数列表<>,因为不加这就是一个普通函数申明,而下方类外实现是函数模板的实现
	// 如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
	friend void printPerson2<>(Person<T1, T2>p);


	Person(T1 name, T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

private:
	T1 m_Name;
	T2 m_Age;
};


// 全局函数在类内实现的测试
void test01()
{
	Person<string, int> p1("张三", 19);
	printPerson(p1);
}

// 全局函数在类外实现的测试
void test02()
{
	Person<string, int> p2("李四", 29);
	printPerson2(p2);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别

1.3.9 类模板案例

在这里插入图片描述
先实现.hpp头文件:

// 自己的通用数组类
#pragma once
#include<iostream>
using namespace std;

template<class T>
class MyArray
{
public:
	// 有参构造
	MyArray(int Capcity)
	{
		cout << "MyArray有参构造" << endl;
		this->m_Capcity = Capcity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capcity];
	}

	// 拷贝构造
	MyArray(const MyArray& arr)
	{
		cout << "MyArray拷贝构造" << endl;
		// 编译器提供的默认拷贝构造如下
		this->m_Capcity = arr.m_Capcity;
		this->m_Size = arr.m_Size;
		//this->pAddress = arr.pAddress;

		// 主要问题就是指针指针指向的内存会被重复释放
		// 用深拷贝解决
		this->pAddress = new T[arr.m_Capcity];

		// 将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	// operator= 防止浅拷贝
	MyArray& operator=(const MyArray& arr)
	{
		cout << "MyArray  operator=" << endl;
		// 先判断原来堆区是否有数据,如果有先释放
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Capcity = 0;
			this->m_Size = 0;

		}

		this->m_Capcity = arr.m_Capcity;
		this->m_Size = arr.m_Size;
		// 深拷贝
		this->pAddress = new T[arr.m_Capcity];

		// 将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}

		return *this;

	}

	// 尾插法
	void push_Back(const T& val)
	{
		// 判断容量是否已满
		if (this->m_Capcity == this->m_Size)
		{
			return;
		}
		this->pAddress[this->m_Size] = val; // 在数组末尾插入数据
		this->m_Size++; //更新数组大小
	}

	// 尾删法
	void pop_Back()
	{
		// 让用户访问不到最后一个元素,即为尾删,逻辑删除
		if (this->m_Size == 0)
		{
			return;
		}
		this->m_Size--;

		

	}

	// 通过下标方式访问数组中的元素 arr[0] = 100
	T& operator[](int index)
	{
		return this->pAddress[index];
	}

	// 返回数组容量
	int get_Capcity()
	{
		return this->m_Capcity;
	}
	// 返回数组大小
	int get_Size()
	{
		return this->m_Size;
	}

	// 析构函数
	~MyArray()
	{
		cout << "MyArray析构函数" << endl;
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;

		}
	}
private:
	T* pAddress;  // 指针指向堆区开辟的真实数组

	int m_Capcity;// 数组容量

	int m_Size; // 数组大小
};

再实现main():

#include<iostream>
using namespace std;
#include "MyArray.hpp"

void printArray(MyArray<int>& arr)
{
	for (int i = 0; i < arr.get_Size(); i++)
	{
		cout << arr[i] << endl;
	}
}
void test01()
{
	MyArray<int>arr1(5);
	// 调用尾插法
	for (int i = 0; i < 5; i++)
	{
		arr1.push_Back(i);

	}
	// 打印
	printArray(arr1);
	cout << "arr1的容量为:" << arr1.get_Capcity() << endl;
	cout << "arr1的大小为:" << arr1.get_Size() << endl;


	MyArray<int>arr2(arr1);
	printArray(arr2);

	// 尾删
	arr2.pop_Back();
	cout << "尾删后" << endl;
	printArray(arr2);
	cout << "arr2的容量为:" << arr2.get_Capcity() << endl;
	cout << "arr2的大小为:" << arr2.get_Size() << endl;

	MyArray<int>arr3(100);
	arr3 = arr1;
}

// 测试自定义数据类型
class Person
{
public:
	Person() {};

	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	~Person()
	{

	}
	string m_Name;
	int m_Age;
};
void printPersonArray(MyArray<Person>& arr)
{
	for (int i = 0; i < arr.get_Size(); i++)
	{
		cout << "姓名:" << arr[i].m_Name << "年龄:" << arr[i].m_Age << endl;
	}
}
void test02()
{
	MyArray<Person> arr(10);
	Person p1("孙悟空", 99);
	Person p2("八戒", 9);
	Person p3("沙僧", 22);

	//将数据插入到数组中
	arr.push_Back(p1);
	arr.push_Back(p2);
	arr.push_Back(p3);

	// 打印
	printPersonArray(arr);
}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

总结:能够利用所学知识点实现通用的数组。

2. STL初始

2.1 STL的诞生

1.长久以来,软件界一直希望建立—种可重复利用的东西
2.C++的面向对象泛型编程思想,目的就是复用性的提升
3.大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
4.为了建立数据结构和算法的一套标准,诞生了STL

2.2 STL基本概念

1.STL(Standard Template Library,标准模板库)
2.STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)
3.容器和算法之间通过迭代器进行无缝连接。
4.STL几乎所有的代码都采用了模板类或者模板函数

2.3 STL六大组件

STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
1.容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
2.算法:各种常用的算法,如sort、find、copy.for_each等
3.迭代器:扮演了容器与算法之间的胶合剂。
4.仿函数:行为类似函数,可作为算法的某种策略。
5.适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
6.空间配置器:负责空间的配置与管理。

2.4 STL中容器、算法、迭代器

1.容器:置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表
这些容器分为序列式容器关联式容器两种:
序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
2.算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)算法分为:质变算法非质变算法
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
3.迭代器:容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
ps:算法需要通过迭代器才能访问容器
迭代器种类:
在这里插入图片描述
常用的容器中迭代器种类为双向迭代器,和随机访问迭代器

2.5 容器算法迭代器初识

了解STL中容器、算法、迭代器概念之后,我们利用代码感受STL的魅力
STL中最常用的容器为Vector,可以理解为数组,下面我们将学习如何向这个容器中插入数据、并遍历这个容器

2.5.1 vector存放内置数据类型

容器:vector
算法:for_each
迭代器:vector : :iterator

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm> //标准算法头文件

// vector存放内置数据类型

void myPrint(int val)
{
	cout << val << endl;
}
void test01()
{
	// 创建一个vector容器(数组)
	vector<int> v;

	// 向容器中插入数据
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);

	// 通过迭代器访问容器中的数据
	// 
	// 第1种遍历方式
	//vector<int>::iterator itBegin = v.begin(); // 起始迭代器,指向容器中第一个元素
	//vector<int>::iterator itEnd = v.end(); // 结束迭代器,指向容器中最后一个元素的下一个位置
	//while (itBegin != itEnd)
	//{
	//	cout << *itBegin << endl;
	//	itBegin++; 
	//} 

	// 第2种遍历方式
	/*for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << endl;
	}*/

	// 第3种遍历方式,利用STL中提供的遍历算法
	for_each(v.begin(), v.end(), myPrint);
}
int main()
{
	test01();
	
	system("pause");
	return 0;
}
2.5.2 vector存放自定义数据类型

学习目标: vector中存放自定义数据类型,并打印输出

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm> //标准算法头文件

// vector存放自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};
void test01()
{
	vector<Person> v;
	Person p1("张三", 10);
	Person p2("李四", 20);
	Person p3("王五", 30);

	// 向容器中添加数据
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);

	// 遍历容器中的数据
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << "姓名:" << (*it).m_Name << "年龄:" << (*it).m_Age << endl;

		// ->也可以
		cout << "姓名:" << it->m_Name << "年龄:" << it->m_Age << endl;
		
	}
}

// 存放自定义数据类型指针
void test02()
{
	vector<Person*> v;
	Person p1("张三", 10);
	Person p2("李四", 20);
	Person p3("王五", 30);

	// 向容器中添加数据
	// 返回是Person*,要加去址符
	v.push_back(&p1);
	v.push_back(&p2);
	v.push_back(&p3);
	 
	// 遍历容器
	for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++)
	{
		// *it返回的Person*,指针通过->访问成员
		cout << "姓名:" << (*it)->m_Name << "年龄:" << (*it)->m_Age << endl;

	}
}
int main()
{
	//test01();
	test02();
	
	system("pause");
	return 0;
}
2.5.3 vector容器嵌套容器

学习目标:容器中嵌套容器,我们将所有数据进行遍历输出

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm> //标准算法头文件

// 容器嵌套容器

void test01()
{
	vector<vector<int>> v;

	// 创建小容器
	vector<int> v1;
	vector<int> v2;
	vector<int> v3;
	vector<int> v4;

	// 向小容器中添加数据
	for (int i = 0; i < 4; i++)
	{
		v1.push_back(i + 1);
		v2.push_back(i + 2);
		v3.push_back(i + 3);
		v4.push_back(i + 4);
	}

	// 将小容器插入到大容器中
	v.push_back(v1);
	v.push_back(v2);
	v.push_back(v3);
	v.push_back(v4);

	// 通过大容器遍历数据
	for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++)
	{
		// (*it)是小容器,还得做遍历
		for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
		{
			// *vit是int可以直接输出了
			cout << *vit << " ";
		}
		cout << endl;
	}
}

int main()
{
	test01();
	
	system("pause");
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1974072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GRFB UNet——基于多尺度注意网络盲道检测算法实现与模型C++部署

1. 概述 盲道是视障人士安全出行的重要辅助设施。识别盲道的形状和位置&#xff0c;对于增强视障人士的自主移动能力至关重要&#xff0c;而视觉分割技术正是应对这一挑战的有效工具。为了显著提升盲道分割的精确度和稳定性&#xff0c;本文提出了一种创新的分割方法&#xff…

OpenShift 4 - 用 oc-mirror 为离线 OpenShift 集群的 Mirror Registry 同步容器镜像

《OpenShift / RHEL / DevSecOps 汇总目录》 本文适合 OpenShift 4.11 及其以上版本。 文章目录 在离线环境中用 OpenShift 准备 Mirror Registry环境说明向隔离环境复制镜像准备节点环境bastion 节点操作support 节点操作 网络完全隔离环境-复制镜像bastion 节点操作support …

[图解]掉杠·above...duty -《分析模式》漫谈20

1 00:00:01,650 --> 00:00:05,120 今天我们来说一下《分析模式》和掉杠 1 00:00:00,480 --> 00:00:02,800 还是前言这里&#xff0c;有一句话 2 00:00:02,810 --> 00:00:04,850 I will mention 3 00:00:04,860 --> 00:00:05,250 that 4 00:00:05,680 --> 00…

【Golang 面试 - 进阶题】每日 3 题(十四)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

实战:MySQL数据同步神器之Canal

1.概叙 场景一&#xff1a;数据增量实时同步 项目中业务数据量比较大&#xff0c;每类业务表都达到千万级别&#xff0c;虽然做了分库分表&#xff0c;每张表数据控制在300W以下&#xff0c;但是效率还是达不到要求&#xff0c;为了提高查询效率&#xff0c;打算使用ES进行数…

Java面试题--JVM大厂篇之破解Java性能瓶颈!深入理解Parallel GC并优化你的应用

目录 引言&#xff1a; 正文&#xff1a; 1. 理解Parallel GC的工作原理 2. 配置Parallel GC 3. 监控和分析GC日志 4. 常见调优技巧 5. 持续迭代和优化 结束语&#xff1a; 补充考虑 1. 综合考虑吞吐量与响应时间 2. 评估和优化垃圾回收频率 3. 动态调整与自适应策…

定期自动巡检,及时发现机房运维管理中的潜在问题

随着信息化技术的迅猛发展&#xff0c;机房作为企业数据处理与存储的核心场所&#xff0c;其运维管理的复杂性和挑战性也与日俱增。为确保机房设备的稳定运行和业务的连续性&#xff0c;运维团队必须定期进行全面的巡检。然而&#xff0c;传统的手工巡检方式不仅效率低下&#…

【卷积神经网络】基于CIFAR10数据集实现图像分类【构建、训练、预测】

文章目录 1、内容简介2、CIFAR10 数据集2.1、数据集概述2.2、代码使用2.2.1、查看数据集基本信息2.2.2、数据加载器2.2.3、完整代码 3、搭建图像分类网络&#x1f53a;3.1、网络结构⭐3.2、代码构建网络⭐ 4、编写训练函数4.1、多分类交叉熵损失函数&#x1f53a;4.2、Adam&…

泛微开发修炼之旅--41Ecology基于触发器实现增量数据同步(人员、部门、岗位、人员关系表、人岗关系表)

一、需求背景 我们在项目上遇到一个需求&#xff0c;需要将组织机构数据&#xff08;包含人员信息、部门信息、分部信息、人岗关系&#xff09;生成的增量数据&#xff0c;实时同步到三方的系统中&#xff0c;三方要求&#xff0c;只需要增量数据即可。 那么基于ecology系统&a…

【C++高阶】:C++11的深度解析上

✨ 心似白云常自在&#xff0c;意如流水任东西 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f4…

数说故事|引爆社媒的森贝儿IP,品牌如何实现流量变现?

以可爱、雅痞、贱萌......的外表加魔性舞姿出圈的可爱小狗——森贝儿贵宾犬Milo&#xff0c;用“可爱微怒”的表情演绎着当代打工人的“疯态”&#xff0c;并迅速晋升成不少打工人高频使用的表情包。 最近几年&#xff0c;“萌系”爆款IP频出&#xff0c;用小动物的形象、可爱…

一键生成视频并批量上传视频抖音、bilibili、腾讯(已打包)

GenerateAndAutoupload Github地址&#xff1a;https://github.com/cmdch2017/GenerateAndAutoupload 如何下载&#xff08;找到最新的release&#xff09; https://github.com/cmdch2017/GenerateAndAutoupload/releases/download/v1.0.1/v1.0.1.zip 启动必知道 conf.py …

Redis学习[5] ——Redis过期删除和内存淘汰

六、Redis过期键值删除 6.1 Redis的过期键值删除策略 6.1.1 什么是过期键值删除&#xff1f; Redis中是可以对key设置过期时间的&#xff0c;所以需要有相应的机制将已过期的键值对删除&#xff0c;也就是**过期键值删除策略。Redis会用一个过期字典&#xff08;expires dic…

如何改网络的ip地址:实用方法与步骤解析

在数字化时代&#xff0c;网络IP地址作为设备在互联网上的唯一标识&#xff0c;其重要性不言而喻。然而&#xff0c;在某些特定场景下&#xff0c;如网络测试、隐私保护或突破地域限制等&#xff0c;我们可能需要更改网络IP地址。那么&#xff0c;如何安全、有效地实现这一操作…

学习日志:update 没加索引会锁全表

文章目录 前言一、为什么会发生这种的事故如何避免这种事故的发生&#xff1f;总结 前言 在线上执行一条 update 语句修改数据库数据的时候&#xff0c;where 条件没有带上索引&#xff0c;导致业务直接崩了 为什么会发生这种的事故&#xff1f; 又该如何避免这种事故的发生&a…

html+css練習:iconfont使用

1.網址地址&#xff1a;https://www.iconfont.cn/search/index 2.註冊登錄&#xff0c;將需要的圖標添加到購物車 3.下載代碼 4.下載后的代碼有一個html頁面&#xff0c;裡面有詳細的使用方式

Linux进程间通信学习2

文章目录 共享内存信号信号概述以及种类信号的处理信号相关函数&#xff08;简单&#xff09;运用小demo实现ctrlc无法终止进程使用kill函数在程序内部实现一个进程杀死另外一个进程 信号相关函数高级版运用函数小demo 信号量信号量相关函数运用小demo: 共享内存 相比于前三个…

基于微信小程序的宠物服务平台(系统源码+lw+部署文档+讲解等)

文章目录 目录 详细视频演示 系统详细设计截图 微信小程序系统的实现 1.1系统前台功能的实现 2.1微信小程序开发环境搭建 2.2微信开发者工具 2.3程序应用相关技术和知识 2.3.1小程序目录结构以及框架介绍 2.3.2 Java技术 2.3.3 MySQL数据库 2.3.4 SSM框架 源码获…

构建铁路安全防线:EasyCVR视频+AI智能分析赋能铁路上道作业高效监管

一、方案背景 随着我国铁路特别是高速铁路的快速发展&#xff0c;铁路运营里程不断增加&#xff0c;铁路沿线的安全环境对保障铁路运输的安全畅通及人民群众的生命财产安全具有至关重要的作用。铁路沿线安全环境复杂多变&#xff0c;涉及多种风险因素&#xff0c;如人员入侵、…

函数递归超详解!

目录 1.什么是递归调用&#xff1f; 直接调用 间接调用 2.什么是递归&#xff1f; 3.递归举例 3.1求n!的阶乘 3.1.1.非递归法 3.1.2.递归法 3.1.2.1分析和代码实现 3.2顺序打印一个整数的每一位 3.2.1分析和代码实现 4.递归与迭代 4.1举例&#xff1a;斐波那契数列 …