模板
主要针对C++泛型编程和STL技术做详细解读,探讨C++更深层的使用
模板的概念
模板就是建立通用的模具,提高复用性
可以用PPT模板来思考模板的概念
模板的特点:
- 模板不可以直接使用,他只是一个框架
- 模板的同意并不是万能的
函数模板
C++另一种编程思想成为泛型编程,主要利用的技术就是模板
C++提供两种模板机制 函数模板 和 类模板函数模板语法
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟类型来代表
大致解释虚拟类型:
语法:
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;
}
//函数模板
//也可以不是T 约定俗成一般就用T
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;
cout << "函数模板" << endl;
//利用函数模板来交换
//两种方式使用函数模板
//1、自动类型推导 编译器自己猜测数据类型
mySwap(a, b);
cout << "自动类型推导" << endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << endl;
//显示指定类型
int e = 100;
int f = 200;
mySwap<int>(e, f);//<int>直接告诉函数要调用什么类型
cout << "显示指定类型" << endl;
cout << "e = " << e << endl;
cout << "f = " << f << endl;
cout << endl;
cout << "普通函数" << endl;
int g = 1;
int h = 2;
swapInt(g, h);
cout << "g = " << g << endl;
cout << "h = " << h << endl;
cout << endl;
double c = 1.1;
double d = 2.2;
swapDouble(c, d);
cout << "c = " << c << endl;
cout << "d = " << d << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:
- 函数模板利用关键字template
- 使用函数模板有两种方式:自动类型推导、显示指定类型
- 模板的目的是为了提高复用性,将类型参数化
函数模板注意事项
注意事项:
- 自动类型推导,必须推导出一致的数据类型T,才可以使用
- 模板必须要确定T的数据类型,才可以使用
示例:
#include<iostream>
using namespace std;
//函数模板的注意事项
//typname可以替换成class
//typename是新定标准,class是旧标准;一般最好用新标准而且可读性强
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);//正确,因为a,b的类型相同
//mySwap(a, c);//报错,因为两个类型不同,推导不出一致的T类型
cout << "自动类型推导" << endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << endl;
}
//2、模板必须要确定出T的数据类型,才可以使用
template<typename T>
void func()
{
cout << "func调用" << endl;
}
void test02()
{
//func();//报错,因为没有指定类型,即使函数不需要指定类型
func<int>();
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
总结:
- 使用模板必须确定出通用数据类型,并且能够推导出一致的类型
函数模板案例
案例描述:
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行测试
示例:
#include<iostream>
using namespace std;
//函数模板的注意事项
template<typename T>//函数模板和类模板都可以用typename 或 class,看个人喜好
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++)
{
//认定最大值 比遍历出的数值要小说明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";//无序的字符数组
int num = sizeof(charArr) / sizeof(char);
mySort(charArr, num);
printArray(charArr,num);
}
void test02()
{
//测试int数组
int intArr[] = { 7,5,1,3,9,2,4,6,8 };
int num = sizeof(intArr) / sizeof(int);
mySort(intArr, num);
printArray(intArr, num);
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
普通函数和函数模板的区别
普通函数和函数模板的区别:
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
示例:
#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';//隐式类型转换把字符型变量转成整型变量,读取对应的ASCII码
//普通函数:可以发生隐式类型转换
cout << "普通函数:myAdd01(a, c)" << myAdd01(a, c) << endl;
cout << endl;
//自动类型推导:不会发生隐式类型转换
cout << "普通函数:myAdd02(a, c) 无法推导类型不一致的值,该段报错" << endl;
//cout << myAdd02(a, c) << endl;//无法推导出不一致的类型
cout << endl;
//显示指定类型:可以发生隐式类型转换
cout << "函数模板:myAdd02<int>(a, c) = " << myAdd02<int>(a, c) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T
普通函数与函数模板的调用规则
调用规则如下:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
示例:
如果函数模板和普通函数都可以实现,优先调用普通函数
#include<iostream>
using namespace std;
void my_Print(int a, int b)
{
cout << "调用的是普通函数" << endl;
}
template<typename T>
void my_Print(T a, T b)//函数模板和普通函数可以写的一样,相当于重载
{
cout << "调用的是函数模板" << endl;
}
void test01()
{
int a = 10;
int b = 20;
my_Print(a, b);//这里满足的是规则1,优先调用普通函数
}
int main()
{
test01();
system("pause");
return 0;
}
可以通过空模板参数列表,来强制调用函数模板
#include<iostream>
using namespace std;
void my_Print(int a, int b)
{
cout << "调用的是普通函数" << endl;
}
template<typename T>
void my_Print(T a, T b)
{
cout << "调用的是函数模板" << endl;
}
void test01()
{
int a = 10;
int b = 20;
//通过空模板的参数列表,强制调用函数模板
my_Print<>(a, b);//尖括号就是空模板列表
}
int main()
{
test01();
system("pause");
return 0;
}
函数模板可以发生重载
#include<iostream>
using namespace std;
template<typename T>
void my_Print(T a, T b)
{
cout << "调用的是函数模板" << endl;
}
template<typename T>
void my_Print(T a, T b, T c)
{
cout << "调用的是重载的函数模板" << endl;
}
void test01()
{
int a = 10;
int b = 20;
my_Print(a, b, 100);//调用的是函数模板重载
}
int main()
{
test01();
system("pause");
return 0;
}
如果函数模板可以产生更好的匹配,优先调用函数模板
#include<iostream>
using namespace std;
void my_Print(int a, int b)
{
cout << "调用的是普通函数" << endl;
}
template<typename T>
void my_Print(T a, T b)
{
cout << "调用的是函数模板" << endl;
}
void test01()
{
char c1 = 'a';
char c2 = 'b';
my_Print(c1, c2);//调用的是函数模板
}
int main()
{
test01();
system("pause");
return 0;
}
总结:既然提供了函数模板,最好不要提供普通函数,否则容易出现二义性
模板的局限性
局限性:
- 模板的通用性并不是万能的
例如:
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(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
//姓名
string m_Name;
//年龄
int m_Age;
};
//对比两个数据是否相等的函数
template<typename 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);
cout << "函数模板对普通类型的使用" << endl;
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);
cout << "函数模板对普通类型的使用" << endl;
if (ret)
{
cout << "p1 == p2" << endl;
}
else
{
cout << "p1 != p2" << endl;
}
}
int main()
{
test01();
cout << endl;
test02();
system("pause");
return 0;
}
总结:
- 利用具体化的模板,可以解决自定义类型的通用化
学习模板并不是为了写模板。而是再STL能够运用系统提供的模板
类模板
类模板语法
类模板作用:
建立一个通用类,类中的成员数据可以不具体制定,用一个虚拟的类型来代表
语法:
template<typename T>
类
解释:
template - 声明创建模板
typename - 表面其后面的符号是一种数据类型,可以用class代替;最好用typename
T- 通用的数据类型,名称可以替换,通常为大写字母
示例:
#include<iostream>
#include<string>
using namespace std;
//类模板
template<typename NameType,typename AgeType>//因为下面两个类型是不一样的,所以要指定两个T
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "name: " << this->m_Name << " " << "age: " << this->m_Age << endl;
}
//姓名
NameType m_Name;
//年龄
AgeType m_Age;
};
void test01()
{
Person<string, int> p1("孙悟空", 999);//<>指定类型,然后再赋值
p1.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板
类模板与函数模板区别
类模板与函数模板主要有两点区别:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
示例:
#include<iostream>
#include<string>
using namespace std;
//类模板
template<typename NameType,typename AgeType = int>//<>参数列表可以先指定类型,也就是默认参数(函数模板不可以这样使用)
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "name: " << this->m_Name << " " << "age: " << this->m_Age << endl;
}
//姓名
NameType m_Name;
//年龄
AgeType m_Age;
};
//1、类模板没有自动类型推导使用方式(函数模板不能这样使用,只有类模板才可以)
void test01()
{
//Person p1("孙悟空", 999);//没有<>指定类型,会报错,因为类模板没有自动类型推导
Person<string, int> p1("孙悟空", 999);//这是正确的,只能用显示指定类型
p1.showPerson();
}
//2、类模板在模板参数列表可以有默认参数
void test02()
{
Person<string> p1("猪八戒", 1000);//因为已经有默认参数了所以不写int也可以使用
p1.showPerson();
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
总结:
示例:
#include<iostream>
#include<string>
using namespace std;
//类模板中成员函数的创建时机
class Person1
{
public:
void showPerson()
{
cout << "Person1 show" << endl;
}
};
class Person2
{
public:
void showPerson()
{
cout << "Person2 show" << endl;
}
};
template<typename T>
class MyClass
{
public:
T obj;
//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成
void func1()
{
obj.showPerson1();
}
void func1()
{
obj.showPerson2();
}
};
void test01()
{
MyClas<Person1> m;
m.func1();
//m.func2();//编译会出错,说明函数调用才会去创建成员函数
}
int main()
{
test01();
system("pause");
return 0;
}
类模板对象做函数参数
- 类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
- 指定传入类型 ——直接显示对象的数据类型
- 参数模板化 ——将对象中的参数变为模板进行传递
- 整个类模板化 ——将这个对象类型模板化进行传递
示例:
#include<iostream>
#include<string>
using namespace std;
//类模板对象做函数参数
template<typename T1, typename T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "name: " << this->m_Name << " " << "age: " << this->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<typename T1, typename 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("猪八戒", 90);
printPerson2(p);
}
//3、整个类模板化
template<typename T>//这个就是函数模板
void printPerson3(T& p)
{
p.showPerson();
}
void test03()
{
Person<string, int> p("唐僧", 30);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
总结:
- 通过类模板创建的对象,可以有三种方式向函数中进行传参
-
类模板与继承
当模板碰到阶乘时,需要注意以下几点:
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果向灵活指定父类中T的类型,子类也需要变为类模板
示例:
#include<iostream>
#include<string>
using namespace std;
//类模板与继承
template<typename T>
class Base
{
T m;
};
//class Son :public Base//错误,必须知道父类中的T类型,才能继承给子类
class Son :public Base<int>
{};
void test01()
{
Son s1;
}
//如果想灵活的指定父类中T的类型,子类也需要变为模板
template<typename T1, typename 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;//把类型当成参数一样传递给T1和T2
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
类模板成员函数类外实现
示例:
#include<iostream>
#include<string>
using namespace std;
//类模板中成员函数类外实现
template<typename T1, typename T2>
class Person
{
public:
//成员函数声明
Person(T1 name, T2 age);
/*类内实现
{
this->m_Name = name;
this->m_Age = Age;
}*/
void showPerson();
/*类内实现
{
cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}*/
public:
T1 m_Name;
T2 m_Age;
};
//构造函数 类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)//<T1, T2>体现构造函数这是类模板的类外实现
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()//一定要写模板参数列表<T1, T2>,才能体现类模板
{
cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}
void test01()
{
Person<string, int> P("Tom", 20);//把类型当成参数一样传递给T1和T2
P.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
类模板分文件编写
问题:
- 类模板中成岩函数创建时机是在调用阶段,导致份文件编写时链接不到
解决:
- 方法1:直接包含.cpp源文件
- 方法2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
示例:
第一种解决办法:调用.cpp文件而非.h文件
person.h中代码
#pragma once
#include<iostream>
#include<string>
using namespace std;
//类的声明
template<typename T, typename T2>
class Person
{
public:
//成员函数声明
Person(T1 name, T2 age);
void showPerson();
public:
T1 m_Name;
T2 m_Age;
};
person.cpp中代码
#include"person.h"
//类的实现
//构造函数 类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)//<T1, T2>体现构造函数这是类模板的类外实现
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()//一定要写模板参数列表<T1, T2>,才能体现类模板
{
cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}
#include<iostream>
//第一种解决办法:直接包含源文件
#include"person.cpp"//person.h会导致无法创建函数,。cpp表示直接调用类外成员函数,而且person.cpp已经包含person.h,编译器也看得
using namespace std;
void test01()
{
Person<string, int> P("Jerry", 18);
P.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
第二种解决办法:把实现和声明写在一个头文件里后缀为.hpp的类模板(一般用第二种方法)
person.hpp中代码
#pragma once
#include<iostream>
#include<string>
using namespace std;
//声明和实现都在一个文件里
template<typename T, typename T2>
class Person
{
public:
//成员函数声明
Person(T1 name, T2 age);
void showPerson();
public:
T1 m_Name;
T2 m_Age;
};
//构造函数 类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)//<T1, T2>体现构造函数这是类模板的类外实现
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()//一定要写模板参数列表<T1, T2>,才能体现类模板
{
cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}
#include<iostream>
//第二种解决方法:将.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
类模板与友元
掌握类模板配合友元函数的类内和类外实现
全局函数类内实现 - 直接再类内声明友元即可
全局函数类外实现 - 需要提前让编译器知道全局函数的存在
#include<iostream>
#include<string>
using namespace std;
//提前让编译器知道Person类存在
template<typename T1, typename T2>//Person是类模板所以这个也要加上
class Person;
//类外实现,先让编译器看到,要卸载类之前才能成功
template<typename T1, typename T2>
void printPerson2(Person<T1, T2> p)
{
cout << "类外实现 — 姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
}
//通过全局函数打印Person信息
template<typename T1, typename T2>
class Person
{
//全局函数 类内实现
friend void printPerson(Person<T1, T2> p)
{
cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
}
//全局函数 类外实现
//加空模板参数列表<>
//如果全局函数 是是类外实现,西药让编译器提前知道有这么个模板存在
friend void printPerson2<>(Person<T1, T2> p);
public:
Person(T1 name, T2 age)
{
this->m_Name;
this->m_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", 10);
printPerson2(p);
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别
类模板案例
案例描述:实现一个通用的数组类,要求如下
- 可以多内置数据类型已经自定义数据类型的数据进行存储
- 将数组种的数据存储到堆区
- 构造函数种可以传入数组的容量
- 提供对应的拷贝构造函数以及operator= 防止浅拷贝问题
- 提供尾插法和未删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前袁术的个数和数组的容量
示例:
myArray.hpp文件
//自己通用的数组类
#pragma once
#include<iostream>
using namespace std;
template<typename T>
class MyArray
{
public:
//有参构造 参数 容量
MyArray(int capacity)
{
//cout << "MyArray有参构造调用" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
//拷贝构造
MyArray(const MyArray& arr)
{
//cout << "MyArray拷贝构造调用" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//this->pAddress = arr.pAddress;默认浅拷贝
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将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_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 < this->m_Size; i++)
{
this->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 (this->pAddress != NULL)
{
//cout << "MyArray析构函数调用" << endl;
delete[]this->pAddress;
this->pAddress = NULL;
}
}
private:
T* pAddress;//指针指向堆区的真实数组
int m_Capacity;//数组容量
int m_Size;//数组大小
};
#include<iostream>
#include<string>
#include"MyArray.hpp"
using namespace std;
//打印
void printIntArray(MyArray<int>& arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << arr[i];
}
cout << endl;
}
//测试自定义数据类型
class Person
{
public:
Person() {}
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void test01()
{
MyArray<int> arr1(5);
for (int i = 0; i < 5; i++)
{
//利用尾插法向数组插入数据
arr1.Push_Back(i);
}
cout << "arr1的打印输出为:";
printIntArray(arr1);
cout << "arr1的容量为:" << arr1.getCapacity() << endl;
cout << "arr1的大小为:" << arr1.getSize() << endl;
cout << endl;
MyArray<int> arr2(arr1);
cout << "arr2的打印输出为:";
printIntArray(arr2);
cout << "arr2尾删后:" << endl;
arr2.Pop_Back();
cout << "arr2的容量为:" << arr2.getCapacity() << endl;
cout << "arr2的大小为:" << arr2.getSize() << endl;
cout << endl;
/*测试 拷贝 构造 析构 运算符重载是否正常调用
MyArray<int> arr2(arr1);
MyArray<int> arr3(10);
arr3 = arr1;*/
}
void printPersonArry(MyArray<Person>& arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << "姓名:" << arr[i].m_Name << " " << "年龄:" << arr[i].m_Age << endl;
}
}
void test02()
{
MyArray<Person> arr(10);
Person p1("孙悟空", 999);
Person p2("韩信", 30);
Person p3("妲己", 20);
Person p4("赵云", 25);
Person p5("安其拉", 27);
//将数据插入到数组中
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
arr.Push_Back(p5);
//打印数组
printPersonArry(arr);
//出书容量
cout << "arr的容量为:" << arr.getCapacity() << endl;
//输出大小
cout << "arr的大小为:" << arr.getSize() << endl;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
STL初识
STL诞生
- 长久以来,软件界一直希望建立一种可重复利用的东西
- C++的面向对象和泛型编程思想,目的就是复用性的提升
- 大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
-
STL基本概念
STL(Standard Template Library,标准模板库)
- STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)
- 容器和算法之间通过迭代器进行无缝链接
- STL几乎所有的代码都采用了模板类或这模板函数
STL六大组件
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
- 算法:各种常用的算法,如sort、find、copy、for_each等
- 迭代器:扮演了容器与蒜贩之间的胶合剂
- 仿函数:行为类似函数,可做为算法的某种策略
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
- 空间配置器:负责空间的配置与管理
STL中容器、算法、迭代器
容器:置物之所(存放数据)
STL容器就是将御用最广泛的一些数据结构实现出来
常用数据结构:数组,链表,树,栈,队列,集合,映射表等
这些容器分为序列式容器和关联式容器两种:
- 序列式容器:强调值得排序,序列是容器中的每个元素均有固定的位置
- 关联式容器:二叉树结构,各元素之间没有严格的物理上的循序关系
算法:问题之解法()
有限步骤,解决逻辑或数学上的问题,这门学科叫做算法(Algorithm,算法头文件用的就是这个英文单词)
算法分为:质变算法和非质变算法
- 质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝,替换,删除等等
- 非质变算法:是指运算过程中不会更改区间内元素的内容,例如查找,计算,遍历,虚招极值等等
迭代器:容器和算法之间的粘合剂
提供一种方法,是值能够依序需方每个容器所含的各个元素,二有无需暴露该容器的内部表示方式
每个容器都有自己的专属的迭代器
迭代器使用非常类似与指针,初学阶段可以先理解迭代器为指针
迭代器种类:
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数局的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 对数据的只写访问 | 只写,支持++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 读写操作,并能向前和向后操作 | 读写,支持++、— |
随机访问迭代器 | 读写操作,可以跳跃的方式访问任何数据,最强的迭代器 | 读写,支持++、—、[n]、-n、<、<=、>、>= |
容器算法迭代器初识
vector存放内置数据类型
容器:vector
算法:for_each
(遍历算法)
迭代器:vector<int>::iterator
示例:
#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
void myPrint(int val)
{
cout << val << " ";
}
//vector容器存放内置数据类型
void test01()
{
//创建一个vector容器,数组
vector<int> v;
//向容器中插入数据,push_back()就是尾插
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
//通过迭代器访问容器中的数据
vector<int>::iterator itBegin = v.begin();//起始迭代器 指向容器中第一个元素
vector<int>::iterator itEnd = v.end();//结束迭代器,指向最后一个元素的下一个位置
//第一种遍历方式
cout << "第一种方法:";
while (itBegin != itEnd)
{
cout << *itBegin << " ";
itBegin++;
}
cout << endl;
//第二种遍历方式
cout << "第二种方法:";
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
//第三种遍历方式 利用STL中的遍历算法
cout << "第三种方法:";
for_each(v.begin(),v.end(), myPrint);//用了回调函数的技术,本质就是for循环
}
int main()
{
test01();
return 0;
}