C++提高编程(泛型编程与STL技术)

目录

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.1STL诞生

2.2STL基本概念

2.3STL六大组件

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

2.5容器算法迭代器初识

2.5.1vector存放内置数据类型

2.5.2vector存放自定义数据类型

 2.5.3vector容器嵌套容器

3.STL—常用容器

3.1string容器

3.1.1string基本概念

3.1.2string构造函数

3.1.3string赋值操作

3.1.4string字符串拼接

3.1.5string查找和替换

3.1.6string字符串比较

3.1.7string字符提取

3.1.8string插入和删除

3.1.9string子串

3.2vector容器

3.2.1vector基本概念

3.2.2vector构造函数

3.2.3vector赋值操作

3.2.4vector容量和大小

3.2.5vector插入和删除

3.2.6vector数据存取

3.2.7vector互换容器

 3.2.8vector预留空间

3.3deque容器

3.3.1deque容器基本概念

3.2.2deque构造函数

3.3.3deque赋值操作

3.3.4deque大小操作

3.3.5deque插入和删除

3.3.6deque数据存取

3.3.7deque排序

3.4案例——评委打分

3.4.1案例描述

3.4.2实现步骤

3.5stack容器

3.5.1stack基本概念

3.5.2stack容器常用接口

3.6queue容器

3.6.1queue基本概念

3.6.2queue常用接口

3.7list容器

3.7.1list基本概念

 3.7.2list构造函数

3.7.3list赋值和交换

3.7.3list大小操作

3.7.5list插入和删除

3.7.6list数据存取

3.7.7list反转和排序

3.7.8排序案例

3.8set/multiset容器

3.8.1set概念

3.8.2set的构造和赋值

3.8.3set大小和交换

3.8.4set插入和删除

3.8.5set查找和统计

3.8.6set和multiset区别

3.8.7pair队组创建

3.8.8set容器排序

3.9map/multimap容器

3.9.1map基本概念

3.9.2map构造和赋值

3.9.3map大小和交换

3.9.4map容器-插入和删除

3.9.5map查找和统计

3.9.6map容器排序

3.10案例-员工分组

3.10.1案例描述

4 STL函数对象

4.1函数对象

4.1.1函数对象概念

4.1.2函数对象使用

4.2谓词

4.2.1谓词概念

4.2.2一元谓词

4.2.3二元谓词

4.3内建函数对象

4.3.1内建函数对象意义

4.3.2算数仿函数

4.3.3关系仿函数

4.3.4逻辑仿函数

5STL—常用算法

5.1常用遍历算法

5.1.1for_each

5.1.2transform

5.2常用查找算法

5.2.1find

5.2.2find_if

5.2.3adjacent_find

5.2.4binary_search

5.2.5count

5.2.6count_if

5.3常用排序算法

5.3.1sort

5.3.2random_shuffle

5.3.3merge

5.3.4reverse

5.4常用拷贝和替换算法

5.4.1copy

5.4.2replace

5.4.3replace_if

5.4.4swap

5.5常用算术生成算法

5.5.1accumulate

5.5.2fill

5.6常用集合算法

5.6.1set_intersection

5.6.2set_union

5.6.3set_difference


1.模板

1.1模板的概念

模板就是建立通用的模具,大大提高编程的复用性

1.2函数模板

  • C++编程思想主要有面向对象和泛型编程,泛型编程主要利用的技术就是模板
  • 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);
	cout << "a = " << a << ",b = " << b << endl;

	double  c = 1.1;
	double  d= 2.2;
	SwapDouble(c, d);
	cout << "c = " << c << ",d = " << d << endl;

	//利用函数模板交换
	//两种方式使用函数模板
	//1.自动类型推导
	mySwap(a, b);
	mySwap(c, d);
	cout << "a = " << a << ",b = " << b << endl;
	cout << "c = " << c << ",d = " << d << endl;
	///2.显式指定类型
	mySwap<int>(a, b);
	cout << "a = " << a << ",b = " << b << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

1.2.2函数模板注意事项

注意事项:

  1. 自动类型推导,必须推导出一定的数据类型T,才可以使用
  2. 模板必须要确定出T的数据类型,才可以使用
#include <iostream>
using namespace std;

//函数模板注意事项
template<typename 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;
	mySwap(a, b);            //正确
	cout << "a = " << a << ",b = " << b << endl;
	char c = 'c';
	//mySwap(a, c);          //错误,推导不出一致的T类型
}

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

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

总结:

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

1.2.3函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序。
  • 排序规则从大到小,排序算法为选择排序
  • 分别使用char数组和int数组进行测试
#include <iostream>
using namespace std;
//实现通用 对数组进行排序的函数
//规则 从大到小
//算法 选择排序
//测试 char数组 int数组
//排序算法
//交换函数模板
template<class T>
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
//排序函数模板
template<class 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<class 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";
	int num = sizeof(charArr) / sizeof(char);
	mySort(charArr, num);
	printArray(charArr, num);
}
void test02()
{
	//测试int数组
	int intArr[] = { 7,8,5,2,3,4,1 };
	int num = sizeof(intArr) / sizeof(int);
	mySort(intArr, num);
	printArray(intArr, num);
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

1.2.4普通函数与函数模板的区别

普通函数与模板函数的区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显式指定类型的方式,可以发生隐式类型转换
#include <iostream>
using namespace std;
//普通函数与模板函数区别
//1.普通函数调用可以发生隐式类型转换
//2.函数模板 用自动类型推导,不可以发生隐式类型转换
//3.函数模板 用显示指定类型,可以发生隐式类型转换
//普通函数
int myAdd(int a, int b)
{
	return a + b;
}
//函数模板
template<class T>
T myAdd02(T a,T b)
{
	return a + b;
}
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << myAdd(a, b) << endl;
	cout << myAdd(a, c) << endl;     //c隐式转换为int类型

	//自动类型推导
	cout << myAdd02(a, b) << endl;
	//cout << myAdd02(a, c) << endl; //报错, 用自动类型推导,不可以发生隐式类型转换
	cout << myAdd02<int>(a, c) << endl;//用显示指定类型,可以发生隐式类型转换
}
int main()
{
	test01();

	system("pause");
	return 0;
}

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

调用规则如下:

  1. 如果函数模板和普通模板都可以实现,优先调用普通函数
  2. 可以通过空模板参数列表来强制调用函数模板
  3. 函数模板也可以发生重载
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板
#include<iostream>
using namespace std;

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

void myPrint(int a, int b)
{
	cout << "调用的是普通函数!" << endl;
}
template<class T>
void myPrint(T a, T b)
{
	cout << "调用的模板!" << endl;
}
template<class T>
void myPrint(T a,T b,T c)
{
	cout << "重载的模板!" << endl;
}
void test01()
{
	int a = 10;
	int b = 20;
	myPrint(10, 20);        //调用的时普通函数
	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 模板的局限性

局限性:模板的通用性并不是万能的

例如:

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

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

再例如:

template <class  T>
void f(T a, T b)
{
	if(a > b)
	{...... }
}

在上述代码中,如果T的数据类型是像Person这样的自定义数据类型,也无法正常运行。

因此C++为了解决这种问题,提供了模板的重载,可以为这些特定的类型提供具体化的模板

#include <iostream>
#include <string>
using namespace std;
//模板局限性
//模板并不是万能的有些特定数据类型,需要用具体化方式做特殊实现

class Person
{
public:
	Person(string name, int age)
	{
		m_age = age;
		m_name = name;
	}
	string m_name;
	int m_age;
};
//对比两个数是否相等
//普通函数模板
template<class T>
bool myCompare(T &a, T &b)
{
	if (a == b)
		return true;
	else
		return false;
}
//利用具体化的Person版本实现代码,具体化优先调用
template<> bool myCompare(Person &p1,Person &p2)
{
	if ((p1.m_name == p2.m_name) && (p1.m_age == p2.m_age))
		return true;
	else
		return false;
}
void test01()
{
	int a = 10;
	int b = 20;
	bool ret = myCompare(a, b);
	if (ret)
	{
		cout << "a = b" << endl;
	}
	else
	{
		cout << "a != b" << endl;
	}
}
void test02()
{
	Person p1("Tom", 10);
	Person p2("Tom", 10);
	bool ret = myCompare(p1, p2);
	if (ret)
		cout << "p1 = p2" << endl;
	else
		cout << "p1 != p2" << endl;
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

总结:

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

1.3类模板

1.3.1类模板语法

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

语法:

template<typename T>、
类

解释:

template——声明创建模板

typename——表明其后面的符号是一种数据类型,可以用class代替

T——通用的数据类型,名称可以替换,通常为大写字母

示例:

#include <iostream>
#include <string>
using namespace std;
//类模板
template<class NameType,class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		m_age = age;
		m_name = name;
	}
	void showPerson()
	{
		cout <<"Name: "<< this->m_name << "\tAge: " <<this-> m_age << endl;
	}
public:
	NameType m_name;
	AgeType m_age;
};
void test01()
{
	//指定NameType为string类型,AgeType为int类型
	Person<string, int> p1("孙悟空", 999);
	p1.showPerson();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

1.3.2类模板与函数模板区别

类模板与函数模板区别主要有两点:

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数,函数模板中不行
#include <iostream>
using namespace std;

//类模板与函数模板区别
template<class NameType,class AgeType = int>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		m_Name = name;
		m_Age = age;
	}
	void showPerson()
	{
		cout << "Name: " << m_Name << "\tAge: " << m_Age << endl;
	}
	NameType m_Name;
	AgeType m_Age;
};
//1.类模板没有自动类型推导的使用方式
void test01()
{
	//Person p("孙悟空", 1000);错误❌,无法用自动类型推导
	Person<string,int> p("孙悟空", 1000);//正确,只能用显式指定类型
	p.showPerson();
}
//2.类模板在模板参数列表中可以有默认参数
void test02()
{
	Person<string> p("猪八戒", 999);
	p.showPerson();
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

总结:

  1. 类模板没有自动类型推导的使用方式,只能用显式指定类型方式
  2. 类模板在模板参数列表中可以有默认参数

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

类模板中成员函数与普通类中成员函数创建时机是有区别的:

  1. 普通类中成员函数一开始就可以创建
  2. 类模板中的成员函数只有在调用时才创建
#include <iostream>
using namespace std;

//类模板中成员函数创建时机
//类模板中成员函数在调用时才创建
class Person1
{
public:
	//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成
	void showPerson1()
	{
		cout << "Person1 show" << endl;
	}
};
class Person2
{
public:
	void showPerson2()
	{
		cout << "Person2 show" << endl;
	}
};
template<class T>
class MyClass
{
public:
	T obj;
	//类模板中成员函数
	void func1()
	{
		obj.showPerson1();
	}
	void func2()
	{
		obj.showPerson2();
	}
};
void test01()
{
	MyClass<Person1> m;
	m.func1();
	//m.func2();   //编译会出错,说明函数调用才会创建成员函数
	MyClass<Person2> n;
	n.func2();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

1.3.4类模板对象做函数参数

一共有三种传入方式:

  1. 指定传入类型——直接显示对象的数据类型
  2. 参数模板化——将对象中的参数变为模板进行传递
  3. 整个类模板化——将这个对象类型 模板化进行传递
#include <iostream>
using namespace std;
//类模板对象做函数参数
template<class T1,class T2>
class Person
{
public:
	Person(T1 name, T2 age)
	{
		m_age = age;
		m_name = name;
	}
	void showPerson()
	{
		cout << "Name: " << m_name << "\tAge: " << m_age << endl;
	}
	T1 m_name;
	T2 m_age;
};
//1.指定传入类型——直接显示对象的数据类型
void printPerson1(Person<string, int>& p)
{
	p.showPerson();
}
void test01()
{
	Person<string,int> p("孙悟空", 100);
	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("猪八戒", 100);
	printPerson2(p);
}
//3.整个类模板化——将这个对象类型 模板化进行传递
template<class T>
void printPerson3(T & p)
{
	p.showPerson();
	cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
	Person<string, int>p("唐僧", 30);
	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;
//类模板与继承
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()
{
	test02();

	system("pause");
	return 0;
}

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

#include <iostream>
using namespace std;
//类模板成员函数类外实现
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)
{
	m_Name = name;
	m_Age = age;
}
//成员函数类外实现
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
	cout << "Name: " << m_Name << "\tAge: " << m_Age << endl;
}
void test01()
{
	Person<string, int>p("Tom", 20);
	p.showPerson();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

1.3.7类模板份文件编写

问题:

  • 类模板中成员函数创建时机是在调用阶段,导致文件编写时连接不到。

解决:

  • 解决方式1:直接包含.cpp源文件
  • 解决方式2:将声明和实现写在同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

person.hpp中代码

#pragma once
#include <iostream>
using namespace std;
//类模板文件编写问题以及解决方式
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)
{
	m_Age = age;
	m_Name = name;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "Name: " << m_Name << "\tAge: " << m_Age << endl;
}

#include <iostream>
//第一种解决方式,直接包含源文件
//#include "person.cpp"
//第二种解决方式,将.h和.cpp中内容写在一起,将后缀名改为.hpp文件
#include "person.hpp"
using namespace std;
void test01()
{
	Person<string, int>p("Jerry", 18);
	p.showPerson();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

1.3.8类模板与友元

学习目标:

掌握类模板配合友元函数的类内实现和类外实现

  • 全局函数内实现-直接在类内声明友元函数即可
  • 全局函数类外实现-需要提前让编译器直到全局函数的存在
#include <iostream>
using namespace std;
//通过全局函数打印Person信息

//提前让编译器知道Person类的存在
template<class T1,class T2>
class Person;
//类外实现
template<class T1, class T2>
void printPerson2(Person<T1, T2> p)
{
	cout << "类外实现——Name: " << p.m_name << "\tAge: " << p.m_age << endl;
}
template<class T1,class T2>
class Person
{
	//全局函数类内是实现
	friend void printPerson(Person<T1, T2> p)
	{
		cout << "类内实现——Name: " << p.m_name << "\tAge: " <<p. m_age << endl;
	}
	//全局函数类外实现
	//加空模板参数列表
	//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
	friend void printPerson2<>(Person<T1, T2> p);
public:
	Person(T1 name, T2 age)
	{
		m_name = name;
		m_age = age;
	}
private:
	T1 m_name;
	T2 m_age;
};

//1.全局函数在类内实现
void test01()
{
	Person<string, int> p("Tom", 20);
	printPerson(p);
}
//2.全局函数在类外实现
void test02()
{
	Person<string, int> p("Jerry", 30);
	printPerson2(p);
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

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

1.3.9类模板案例

案例描述:实现一个通用数组类,要求如下:

  • 可以对内置数据类型以及自定义数据类型进行存储
  • 将数组中的数据存储到堆区
  • 构造函数可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删除法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素的个数和数组的容量

 示例:

myArray16.hpp代码如下:

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

template<class T>
class MyArray
{
public:
	//有参构造  参数 容量
	MyArray(int Capacity)
	{
		m_Capacity = Capacity;
		m_Size = 0;
		pAddress = new T[Capacity];
	}
	//拷贝构造
	MyArray(const MyArray& arr)
	{
		pAddress = new T[arr.m_Capacity];
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		for (int i = 0; i < m_Size; i++)
		{
			pAddress[i] = arr.pAddress[i];
		}
		
	}
	//operator= 防止浅拷贝问题 a = b = c
	MyArray& operator=(const MyArray& arr)
	{
		//先判断原来堆区是否有数据,如果有先释放
		if (this->pAddress != NULL)
		{
			delete[]this->pAddress;
			this->pAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}
		//深拷贝
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[arr.m_Capacity];
		for (int i = 0; i < m_Size; i++)
		{
			pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}
	//尾插法
	void Push_Back(const T & val)
	{
		//判断容量是否已满
		if (this->m_Capacity == 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 getCapacity()
	{
		return this->m_Capacity;
	}
	//返回数组大小
	int getSize()
	{
		return this->m_Size;
	}
	//析构函数
	~MyArray()
	{
		if (pAddress != NULL)
		{
			delete[] pAddress;
			pAddress = NULL;
		}
	}
private:
	T* pAddress;   //指针指向堆区开辟的真实数组
	int m_Capacity;  //数组容量
	int m_Size;       //数组大小
};

测试代码:

#include <iostream>
using namespace std;
#include "myArray16.hpp"
void printIntArray(MyArray<int> & arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << arr[i] << endl;
	}
}
void test01()
{
	MyArray<int> arr1(5);
	for (int i = 0; i < 5; i++)
	{
		arr1.Push_Back(i);    //利用尾插法向数组中插入数据
	}
	cout << "arr1的打印输出为:" << endl;
	printIntArray(arr1);
	cout << "arr1的容量为:" <<arr1.getCapacity() <<endl;
	cout << "arr1的大小为:" << arr1.getSize()<< endl;
	MyArray<int> arr2(arr1);
	cout << "arr2的打印输出为:" << endl;
	printIntArray(arr2);
	//尾删
	arr2.Pop_Back();
	cout << "arr2的容量为:" << arr2.getCapacity() << endl;
	cout << "arr2的大小为:" << arr2.getSize() << endl;
	cout << "arr2的打印输出为:" << endl;
	printIntArray(arr2);
}
//测试自定义数据类型
class Person
{
public:
	Person() {};
	Person(string name, int age)
	{
		this->m_age = age;
		this->m_name = name;
	}
	string m_name;
	int m_age;
};
void printPersonArray(MyArray<Person>& arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << "Name: " << arr[i].m_name << "\tAge: " << arr[i].m_age << endl;
	}
}
void test02()
{
	MyArray<Person> arr(10);
	Person p1("孙悟空", 999);
	Person p2("韩信", 20);
	Person p3("妲己", 50);
	Person p4("安琪拉", 99);
	Person p5("赵云", 9);
	//将数据插入到数组中
	arr.Push_Back(p1);
	arr.Push_Back(p2);
	arr.Push_Back(p3);
	arr.Push_Back(p4);
	arr.Push_Back(p5);
	//打印数组
	printPersonArray(arr);
	//输出容量和大小
	cout << "arr的容量为:" << arr.getCapacity() << endl;
	cout << "arr的大小为:" << arr.getSize() << endl;
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

2.STL初识

2.1STL诞生

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

2.2STL基本概念

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

2.3STL六大组件

STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器

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

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

容器:置物之所也。

STL容器就是将运用最广泛的一些数据结构实现出来。

常用的数据结构:数组、链表、树、栈、队列、集合、映射表等。

这些容器分为序列式容器关联式容器两种:

  • 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。
  • 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系。

算法:问题之解法也。

有限的步骤,解决逻辑或数学上的问题。

算法分为:质变算法非质变算法

质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝、替换、删除等。

非质变算法:是指运算过程中不会更改区间内的元素的内容。例如查找、计数、遍历、寻找极值等。

迭代器:容器和算法之间粘合剂。

提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器内部表示方式。

每个容器都有自己的专属迭代器。

迭代器非常类似于指针,初学阶段可以先理解迭代器为指针。

迭代器种类:

 常用的容器中迭代器种类为双向迭代器和随机访问迭代器。

2.5容器算法迭代器初识

2.5.1vector存放内置数据类型

容器:vector

算法:for_each

迭代器:vector<int>::iterator

示例:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void myPrint(int val)
{
	cout << val << endl;
}
//vector容器存放内置数据类型
void test01()
{
	//创建了一个vector容器,数组
	vector<int> v;
	//向容器中插入数据
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);

	//通过迭代器访问容器中的数据
	//vector<int>::iterator itBeign = v.begin();//起始迭代器,指向容器中第一个元素
	//vector<int>::iterator itEnd = v.end();  //结束迭代器,指向容器中最后一个元素的下一个位置

	第一种遍历方式
	//while (itBeign != itEnd)
	//{
	//	cout << *itBeign << endl;
	//	itBeign++;
	//}

	//第二种遍历方式
	/*for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << endl;
	}*/
	
	//第三种遍历方式 利用STL中提供的遍历算法
	for_each(v.begin(), v.end(), myPrint);

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

2.5.2vector存放自定义数据类型

#include <iostream>
#include <vector>
using namespace std;
//vector容器中存放自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		m_name = name;
		m_age = age;
	}
	string m_name;
	int m_age;
};
void test01()
{
	vector<Person> v;
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

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

	//遍历容器中的数据
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << "姓名: " << (*it).m_name << "\t年龄:" << (*it).m_age << endl;
	}
	//*it类型就是尖括号内类型 这里*it类型就是Person类型
}
//存放自定义数据类型指针
void test02()
{
	vector<Person*> v;
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

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

	//遍历容器
	for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << "姓名: " << (*it)->m_name << "\t年龄:" << (*it)->m_age << endl;
	}
	//*it类型就是尖括号内类型 这里*it类型就是Person*类型
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

 2.5.3vector容器嵌套容器

#include <iostream>
using namespace std;
#include <vector>
//容器嵌套容器
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)======容器vector<int>
		for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
		{
			cout << *vit << " ";
		}
		cout << endl;
	}
}
int main()
{
	test01();
	system("pause");
	return 0;
}

3.STL—常用容器

3.1string容器

3.1.1string基本概念

本质:string是C++ 风格字符串,而string本质上是一个类

string和char*区别:

  • char *是一个指针
  • string是一个类,类内部封装了char*,管理这个字符串,指一个char*型容器。

特点:

string内部封装了很多成员方法

例如,查找find、拷贝copy、删除delete、替换replace、插入insert

string管理char *所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责

3.1.2string构造函数

构造函数原型:

  • string();      //创建了一个空的字符串,例如string str;
  • string(const char * s);       //使用字符串s初始化
  • string(const string & str);  //使用一个string对象初始化另一个string对象
  • string(int n,char c);       //使用n个字符c初始化
#include <iostream>
using namespace std;
//string的构造函数
void test01()
{
	string s1;
	const char* str = "Hello World";
	string s2(str);
	cout << "s2 = " << s2 << endl;
	string s3(s2);
	cout << "s3 = " << s3 << endl;
	string s4(10, 'a');
	cout << "s4 = " << s4 << endl;
}

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

3.1.3string赋值操作

功能描述:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值