发布时间:2023-06-14 10:00
C++提高编程:主要针对C++泛型编程和STL技术做详细讲解
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
语法:template
函数声明或定义
解释:
template(模板) — 声明创建模板
typename — 表明其后面的符合是一种数据类型,可以用class代替
T — 通用的数据类型,名称可以代替,通常为大写字母
代码如下(示例):
//交换两个整型函数
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<<endl;//20
cout<<"b="<<b<<endl;//10
//利用函数模板交换
//两种方式使用函数模板
//1.自动类型推导
//myswap(a, b); //因为a是int型,b也是int型。使用编译器推导出T是int型
//2.显示指定类型
myswap<int>(a, b);
cout << "a= " << a << endl;
cout << "b= " << b << endl;
}
总结:
将类型参数化
注意事项:
代码如下(示例):
//利用模板提供通用的交换函数
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类型
cout << "a= " << a << endl;
cout << "b= " << b << endl;
}
//2.模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{
cout << "func函数的调用" << endl;
}
void test02()
{
//func(); 报错,因为这个是模板函数,必须确定T的数据类型
func<int>();
}
案例概述:
代码如下(示例):
template<typename T>
void mysort(T arr[], int len) //选择排序
{
for (int i = 0; i < len; i++)
{
int min = i;
for (int j = i + 1; j < len; j++)
{
if (arr[j] < arr[min]) {
min = j;
}
}
if (min != i)
{
T temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
template<class T>
void myprintf(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << " " ;
}
}
void test01()
{
//测试char数组
char arr[] = "baderc";
int len = strlen(arr); //用sizeof()会把字符数组中的'/0'符合读取进去
mysort<char>(arr, len);
myprintf<char>(arr ,len);
//测试int整型数组
int arr1[] = { 5,8,6,9,1,2 };
int len1 = sizeof(arr1) / sizeof(arr1[0]);
mysort<int>(arr1, len1);
myprintf<int>(arr1, len1);
}
代码如下(示例):
//普通函数
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 = 'a'; // a --- 97
cout << myadd01(a, b) << endl; //30
cout << myadd01(a, c) << endl; //107 编译器将char型数据转换为int型
//cout << myadd02(a, c) << endl; //报错,自动类型推导不会发生隐式类型转换,不会把char型转换为int型
cout << myadd02<char>(a, c) << endl; // k
cout << myadd02<int>(a, c) << endl; //107
}
调用规则如下:
代码如下(示例):
//普通函数
void myprintf(int a, int b)
{
cout << "调用的普通函数" << endl;
}
//函数模板
template<typename T>
void myprintf(T a, T b)
{
cout << "调用的模板" << endl;
}
//函数模板可以重载
template<typename T>
void myprintf(T a, T b, T c)
{
cout << "调用重载的模板" << endl;
}
void test01()
{
int a = 10;
int b = 20;
myprintf(a, b);//允许普通函数和函数模板同时存在,但优先调用普通函数
//通过空模板参数列表,强制调用函数模板
myprintf<>(a, b);
//myprintf(a,b,100);
//4.如果函数模板可以产生更好的匹配,优先调用函数模板
char c1 = 'a';
char c2 = 'b';
myprintf(c1, c2); //调用的模板,因为如果调用函数,还需要隐式类型转换
}
总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
局限性:
template<class T>
void function(T a,T b)
{
a = b;
}
在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现
再例如:
template<class T>
void f(T a, T b)
{
if(a > b){...}
}
如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行
可以进行运算符重载,另一种方法是提供具体化的模板
C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板
代码如下(示例):
//创建一个person类
class person
{
public:
person(string name, int age)
{
this->m_age = age;
this->m_name = name;
}
string m_name; //姓名
int m_age; //年龄
};
//对比两个数据是否相等函数
template<class T>
bool mycompare(T &a, T &b)
{
return a == b;
}
//模板重载,具体化
//这样就能对比自定义类型的数据了
template<> bool mycompare(person& p1, person& p2)
{
return p1.m_name == p2.m_name && p1.m_age == p2.m_age;
}
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("张三", 10);
person p2("李四", 20);
bool ret = mycompare(p1, p2);
if (ret) cout << "p1 == p2" << endl;
else cout << "p1 != p2" << endl;
}
利用具体化的模板,可以解决自定义类型的通用化
学习模板不是为了写模板,而是在STL能够运用系统提供的模板
类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表
语法:
template<typename T>
类
代码如下(示例):
template<typename nametype,typename agetype>
class person
{
public:
person(nametype name, agetype age)
{
this->m_age = age;
this->m_name = name;
}
nametype m_name;
agetype m_age;
};
void test01()
{
person<string, int> p1("张三",10);
cout << "name = " << p1.m_name << " " << "age = " << p1.m_age << endl;
}
类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板
区别有两点:
代码如下(示例):
#include
using namespace std;
#include
//类模板与函数模板的区别
template<class nametype, class agetype = int> //模板中typename可以替换为class
class person
{
public:
person(nametype name, agetype age)
{
this->m_age = age;
this->m_name = name;
}
void showPerson()
{
cout << "name:" << this->m_name << endl
<< "age:" << this->m_age << endl;
}
nametype m_name;
agetype m_age;
};
//1.类模板没有自动类型推导使用方式
void test01()
{
//person p1("张三", 100); //会报错,无法使用自动类型推导
person<string, int> p1("张三", 100); //正确,只能用显示指定类型
p1.showPerson();
}
//2.类模板在模板参数列表中可以有默认参数
void test02()
{
person<string>p2("猪八戒", 999);
p2.showPerson();
}
int main()
{
test01();
test02();
return 0;
}
总结:
类模板中成员函数和普通类中成员函数创建时机是有区别的:
代码如下(示例):
#include
using namespace std;
//类模板中成员函数在调用时才创建
class person1
{
public:
void showperson1()
{
cout << "person1" << endl;
}
};
class person2
{
public:
void showperson2()
{
cout << "person2" << 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(); //编译会出错,说明 函数调用才会去创建成员函数
}
int main()
{
test01();
return 0;
}
总结
学习目标:类模板实例化出的对象,向函数传参的方式
有三种传入方式:
1.指定传入类型 — 直接显示对象的数据类型
2. 参数模板化 — 将对象中的参数变为模板进行传递
3. 整个类模板化 — 将这个对象类型 模板化进行传递
代码如下(示例):
#include
using namespace std;
#include
//类模板对象做函数参数
//写个类模板
template<class T1, class T2>
class person
{
public:
person(T1 name, T2 age)
{
this->m_age = age;
this->m_name = name;
}
void showperson()
{
cout << "姓名:" << this->m_name << endl
<< "年龄:" << this->m_age << endl;
}
T1 m_name;
T2 m_age;
};
//1.指定传入类型
void printfperson1(person<string,int> &p) //直接指定
{
p.showperson();
}
void test01()
{
person<string, int> p1("孙悟空", 100);
printfperson1(p1);
}
//2.参数模板化
template<class T1, class T2>
void printfperson2(person<T1,T2> &p)
{
p.showperson();
cout << " T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02()
{
person<string, int> p2("猪八戒", 90);
printfperson2(p2);
}
//3.整个类模板化
template<class T3>
void printperson3(T3 &p)
{
p.showperson();
}
void test03()
{
person<string, int> p3("唐僧", 80);
printperson3(p3);
}
int main()
{
test01();
test02();
test03();
return 0;
}
总结:
当类模板碰到继承时,需要注意以下几点:
#include
using namespace std;
//类模板与继承
//基类
template<class T>
class base
{
T m;
};
//class son : public base //错误,必须要知道父类中的T类型,才能继承给子类
//c++ 编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class son : public base<int>
{
};
void test01()
{
son s1;
}
//如果想灵活指定父类中T类型,子类也需要变为类模板
template<class T1, class T2>
class son1 :public base<T2>
{
public:
T1 obj;
};
void test02()
{
son1<int, char> S2;
}
int main()
{
test01();
test02();
return 0;
}
总结: 如果父类是类模板,子类需要指定出父类中T的数据类型
代码如下(示例):
#include
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)
{
this->m_Age = age;
this->m_Name = name;
}
// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout<<"姓名: "<< this->m_Name <<" " << "年龄: " << this->m_Age << endl;
}
void test01()
{
Person<string, int> p1("张三", 100);
p1.showPerson();
}
int main()
{
test01();
}
问题:
解决:
代码如下(示例):
// 方式1
// person.h文件
#pragma once
#include
using namespace std;
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_Age = age;
this->m_Name = name;
}
// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout<<"姓名: "<< this->m_Name <<" " << "年龄: " << this->m_Age << endl;
}
// 运用方式1,主函数文件要包含.cpp源文件
// 如果用方式2,将声明和实现同一个文件中,并改后缀名为.hpp,然后主函数文件引入
#include
#include
using namespace std;
#include "person.cpp"
void test01()
{
Person<string, int> p1("张三", 100);
p1.showPerson();
}
int main()
{
test01();
}
实现:类模板配合友元函数的类内和类外实现
全局函数类内实现 - 直接在类内声明友元即可
全局函数类外实现 - 需要提前让编译器知道全局函数的存在
总结:用类内实现就好,用法简单
//类内实现
#include
#include
using namespace std;
// 类模板成员函数类外实现
template<class T1, class T2>
class Person
{
//全局函数 类内实现
friend void printPerson(Person<T1, T2> p)
{
cout << "姓名:" << p.m_Name << "年龄: "<< p.m_Age << endl;
}
public:
Person(T1 name, T2 age)
{
this->m_Age = age;
this->m_Name = name;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01()
{
Person<string, int> p1("张三", 100);
printPerson(p1);
}
int main()
{
test01();
}
#include
#include
using namespace std;
// 提前让编译器知道person类
template<class T1,class T2>
class Person;
// 要让编译器知道有这个全局函数
template<class T1,class T2>
void printPerson(Person<T1, T2> p)
{
cout << "姓名:" << p.m_Name << "年龄: "<< p.m_Age << endl;
}
// 类模板成员函数类外实现
template<class T1, class T2>
class Person
{
//全局函数 类外实现
// 加空模板参数列表
friend void printPerson<>(Person<T1, T2> p);
public:
Person(T1 name, T2 age)
{
this->m_Age = age;
this->m_Name = name;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01()
{
Person<string, int> p1("张三", 100);
printPerson(p1);
}
int main()
{
test01();
}
案例描述:实现一个通用的数组类,要求如下:
#include
using namespace std;
template<class T>
class MyArray
{
public:
// 构造函数 传入容量参数
MyArray(int capacity)
{
cout << "有参构造函数调用" << endl;
this->m_capacity = capacity;
this->m_size = 0;
this->pAddress = new T[this->m_capacity];
}
// 拷贝构造函数
MyArray(const MyArray& arr)
{
cout << "拷贝构造函数调用" << endl;
this->m_capacity = arr.m_capacity;
this->m_size = arr.m_size;
//深拷贝
this->pAddress = new T[arr.m_capacity];
// 将arr中的数据都拷贝过来
for(int i=0; i< arr.m_capacity; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
}
// operator= 防止浅拷贝问题
MyArray& operator=(const MyArray& arr)
{
cout << "运算符=重载函数调用" << 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];
// 将arr中的数据都拷贝过来
for(int i=0; i< arr.m_capacity; 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--;
}
// 通过下标访问数据
T operator[] (int index)
{
return this->pAddress[index];
}
// 返回数据容量
int getCapacity()
{
return this->m_capacity;
}
// 返回数组大小
int getSize()
{
return this->m_size;
}
// 析构函数
~MyArray()
{
cout << "析构函数调用" << endl;
if(this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
}
}
private:
T * pAddress; // 指针指向堆区开辟的真实数组
int m_capacity; // 数组容量
int m_size; // 数组大小
};
void test01()
{
MyArray <int> arr1(5); // 有参构造函数调用
//arr1[0]; // 访问不到,需要重载
MyArray<int> arr2(arr1); // 拷贝构造函数调用
MyArray<int> arr3(100); // 有参构造函数调用
arr3 = arr1; // 运算符=重载函数调用
}
int main()
{
test01();
return 0;
}
#include
using namespace std;
class base
{
public:
base()
{
cout << "自定义类型构造函数" << endl;
}
~base()
{
cout << "自定义类型析构函数" << endl;
}
};
template<class T>
class person1
{
public:
person1()
{
cout << "模板类构造函数" << endl;
}
~person1()
{
cout << "模板类析构函数" << endl;
}
private:
T a;
};
int main()
{
person1<base> p1;
return 0;
}
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
STL容器就是将应用最广泛的一些数据结构实现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表 等
这些容器分为序列式容器和关联式容器两种:
算法分为:质变算法和非质变算法
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
某个容器都有自己专属的迭代器
迭代器使用非常类似于指针
迭代器种类:
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 对数据的只写访问 | 只写,支持++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 读写操作,并能向前和向后操作 | 读写,支持++、– |
随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 | 读写,支持++、–、【n】、-n、<、<=、>、>= |
常用的容器中迭代器种类为双向迭代器,和随机访问迭代器
STL中最常用的容器为Vector,可以理解为数组,下面将学习如何向这个容器中插入数据、并遍历这个容器
容器:vector
算法:for_each
迭代器:vector
#include
#include
#include
using namespace std;
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.begin() 返回迭代器,这个迭代器指向容器中第一个数据
// v.end() 返回迭代器, 这个迭代器指向容器元素的最后一个元素的下一个位置
// vector::iterator 拿到vector这种容器的迭代器类型
vector<int>::iterator pBegin = v.begin(); // 指向第一个数据
vector<int>::iterator pEnd = v.end(); // 指向最后一个数据的下一个位置
// 第一种遍历方式
while(pBegin != pEnd)
{
cout<< *pBegin << endl;
pBegin++;
}
// 第二种遍历方式
for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << endl;
}
// 第三种遍历方式
// 使用STL提供标准遍历算法 需要头文件 algorithm
for_each(v.begin(), v.end(), MyPrint);
}
int main()
{
test01();
return 0;
}
学习目标:vector中存放自定义数据类型,并打印输出
#include
#include
#include
using namespace std;
// 自定义数据类型
class Person
{
public:
Person(string name, int age)
{
this->mAge = age;
this->mName = name;
}
public:
string mName;
int mAge;
};
// 存放对象
void test01()
{
// 创建存放person类型的数据容器
vector<Person> v;
// 创建数据
Person p1("aaa", 111);
Person p2("bbb", 222);
Person p3("ccc", 333);
Person p4("ddd", 444);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
for(vector<Person>::iterator it = v.begin(); it != v.end(); it++)
{
cout<<"Name: " << (*it).mName << " Age: " << (*it).mAge << endl;
}
}
// 存放对象指针
void test02()
{
vector<Person*> v1;
// 创建数据
Person p1("aaa", 111);
Person p2("bbb", 222);
Person p3("ccc", 333);
Person p4("ddd", 444);
v1.push_back(&p1);
v1.push_back(&p2);
v1.push_back(&p3);
v1.push_back(&p4);
for(vector<Person*>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout<<"Name: " << (*it)->mName << " Age: " << (*it)->mAge << endl;
}
}
int main()
{
test01();
test02();
return 0;
}
学习目标:容器中嵌套容器,我们将所以数据进行遍历输出
#include
#include
#include
using namespace std;
// 容器嵌套容器
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);
}
// 将容器元素插入到vector v 中
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
for(vector<int>::iterator it1 = (*it).begin(); it1 != (*it).end(); it1++)
{
cout<< *it1 <<" ";
}
}
}
int main()
{
test01();
return 0;
}