C++提高编程
模板
模板就说简历通用的模具,提高代码复用性
1、模板不可以直接使用,它只是一个框架
2、模板的通用并不是万能
模板机制分为两种机制:函数模板和类模板。
函数模板作用:
建立一个通用函数,起函数返回值类型和形参类型可以不制定,用一个虚拟的类型来代表
语法:
template<typename T>
函数声明或定义
解释:template --- 声明创建模板
typename --- 表明其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
template<typename T>
template<class T>
函数模板的注意事项
模板必须确定出T的数据类型,才可以使用
template<class T> //不能推导出数据类型
void func( ){
cout<<"hello"<<endl;//执行错误。
}
void test02()[
func();//错误
func<int>();
1、自动类型推导,必须推导出一致的数据类型T才可以使用
2、模板必须确定出T的数据类型,才可以使用
函数模板案例
案例:
1、利用函数模板封装成一个排序函数,可以对不同数据类型数组进行排序
2、排序规则从大到小,排序算法为选择排序
3、分别利用char数组和int数组进行测试
普通函数与函数模板的区别:
1、普通函数调用时可以发生自动类型转换(隐式类型转换)
2、普通模板调用时,如果利用自动类型推导,不会发生隐式类型转换
3、如果利用显示指定类型的方式,可以发生隐式的类型转换。
myfunc<int>(a,c);
普通函数与函数模板的调用规则:
1、如果函数模板和普通函数都可以实现,优先调用普通函数。
2、可以通过空模板参数列表来强制调用函数模板 myfunc<>();
3、函数模板也可以发生重载
4、如果函数模板可以产生更好的匹配,优先调用函数模板。如
template <typename T>
void ChoSort(T a,T b) {
cout << "调用函数模板" << endl;
}
void ChoSort(int a,int b){
cout << "调用普通函数" << endl;
}
编译器不做隐私转换。
总结:既然提供了函数模板,最好不要提供普通函数,否则会出现二义性。
模板的局限性
局限性:
模板的通用性并不是万能的
//对比两个数据是否相等。
template<class T>
bool myCompare(T &a, T &b) {
if (a == b) {
return true;
}
else {
return false;
}
}
具体化的模板,针对于自定义数据类型
template<> bool myCompare(Person &p1, Person &p2) {
if (p1.age == p2.age&&p1.name == p2.name) {
return true;
}
else {
return false;
}
}
总结:
1、利用具体化的模板,可以解决自定义类型的通用化
2、学习模板并不是为了些模板,而是在STL能够运用系统的模板
类模板
语法:
template <typename T>
类模板和函数模板的区别:
1、类模板没有自动推导的使用方式
Person<string, int> p1("孙悟空", 99);//只能用显示指定类型。
2、类模板在模板参数列表中可以有默认参数
template<class NameType,class AgeType=int>
类模板成员函数创建时机
类模板对象做函数参数
1、指定传入的类型 --- 直接显示对象的数据类型
2、参数模板化 --- 将对象中的参数变为模板进行传递
3、整个类模板化 --- 将这个对象类型 模板化进行传递
//1、指定传入类型
void printPerson1(Person<string, int> &p) {
p.showPerson();
}
//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 test01() {
Person<string, int>p("孙悟空", 200);
printPerson2(p);
}
//3、整个类模板化
template<class T>
void printPerson(T &p) {
p.showPerson();
cout << "T的数据类型为:" << typeid(T).name() << endl;
}
类模板和继承
当类模板碰到继承,需要注意一下几点:
1、当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型。
2、如果不指定,编译器无法给子类分配内存。
3、如果想灵活指定出父类中T的类型,子类也需要变为类模板。
总结如果父类是类模板,子类需要指定出父类中T的数据类型。
//类成员函数的类外实现
template<class T1,class 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;
}*/
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 << "类内函数" << endl;
}
//总结:类模板中 成员函数在类外实现时,需要加上模板的参数列表
void test() {
Person<string, int> P("tom", 20);
P.showPerson();
}
类模板分文件编写
问题:类模板中成员函数创建时机是在调用阶段,导致部份文件编写时链接不到。
解决:1、直接包含cpp源文件,2、将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制。(第二种 包含源文件.cpp .cpp中包含了头文件.h )
类模板与友元
全局函数类内实现-直接在类内声明友元即可
全局函数类外实现-需要提前在编译器知道全局函数的存在
类模板案例
template<class T>
class MyArray {
private:
T *pAddress;//指针指向堆区开辟的真实数组
int m_Capacity;//数组容量
int m_Size;//数组大小
public:
//有参构造
MyArray(int capacity) {
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
cout << "myArray有参构造" << endl;
}
//拷贝构造
MyArray(const MyArray& arr) {
cout << "myArray拷贝构造" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将原array数据拷贝到现在数组中
for (int i = 0; i < this->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 < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//析构函数
~MyArray() {
cout << "myArray析构构造" << endl;
if (pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
}
}
};
STL基本概念
STL(standardtemplate library,标准模板库)
STL从广义上分为:容器container算法algorithm迭代器iterator
容器和算法之间通过迭代器无缝连接
STL几乎所有的代码都采用了模板类或者模板函数
STL六大组件
STL大体分为六大组件,分别为:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
1、容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
2、算法:各种常用的算法,如sort、find、copy、for_each等
3、迭代器:扮演了容器与算法之间的胶合剂
4、仿函数:行为类似函数,可作为算法的某种策略
5、适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
6、空间配置器:负责空间的配置与管理
容器分为了:
1、序列式容器:强调值的排序,序列式容器中每个元素都有固定的位置 1 3 5 4 2
2、关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系 1 2 3 4 5
算法分为:质变算法和非质变算法
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换、删除
非质变算法:是指运算过程中不会更改区间内的元素的内容。例如查找、计数、寻找极值
迭代器:容器和算法之间的迭代器
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而无需暴露该容器的内部表示方法
每个容器都有自己专属的迭代器。
Vector存放内置数据类型
容器:vector
算法:for_each
迭代器:vector<int>::iterator
示例:
//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("aaa",10);
Person p2("bbb",10);
Person p3("ccc",10);
Person p4("ddd",10);
Person p5("eee",10);
Person p6("fff",10);
//向容器中添加数据
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
v.push_back(p6);
//遍历容器中的数据
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
cout << "姓名:"<<(*it).m_Name << endl;
cout << "姓名:"<<it->m_Name << endl;
cout << "年龄:"<<(*it).m_Age << endl;
cout << "年龄:"<<it->m_Age << endl;
}
}
int main() {
test01();
}
嵌套容器
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++) {
for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
cout << *vit << " ";
}
cout << endl;
}
}
String基本概念
string和char*的区别
char*是一个指针
string 是一个类,类的内部封装了char*,来管理这个字符串,是一个char*型的容器
特点
string类内部封装了很多方法
查找fInd、拷贝copy,删除delete替换replace,插入insert
string管理char*所分配的内存,不用担心复制越界和取值越界,由类内部进行负责。
string构造函数
构造函数原型
string();//创建一个空的字符串 例如string str;
string(const char* s);//使用字符串S初始化
string(const string& str);//使用一个string对象初始化另一个string对象
string(int n,char c);使用n个字符c初始化
总结:灵活运用,没有可比性。
void test04() {
string s1;//默认构造
const char *str = "hello world";
string s2(str);
cout << "s2=" << s2 << endl;
string s3(10, 'a');
cout << "s4=" << s3 << endl;
}
字符串拼接
str3.append(str2,0,2); //0是起始,2是长度 参数2是从那个位置开始截,参数3是截取的个数
字符串查找
rfind和find区别
rfind从右往左
find是从做往右
字符串比较大小
string字符存取
可以修改。
string插入和删除
string字串
Vector
void printVector(vector<int> &v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
//复制string
void test04() {
vector<int>v1;//默认构造 无参构造
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
//通过区间方式构造
vector<int> v2(v1.begin(), v1.end());
printVector(v2);
//n个elem方式构造
vector<int> v3(10, 100);//第一个参数是个数 第二个是元素
printVector(v3);
//拷贝构造
vector<int> v4(v3);
printVector(v4);
}
//vector赋值
void test04() {
vector<int> v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
if (v1.empty()) {//为真 代表容器为空
cout << "v1为空" << endl;
}
else {
cout << "v1不为空" << endl;
}
cout << "v1的容量:" << v1.capacity() << endl;//容量都大于等于size
cout << "v1的容量:" << v1.size() << endl;
v1.resize(15);//如果重新制定的比原来的长了,用0来进行填充新的位置。
v1.resize(15, 10);//用10来填充
v1.resize(5);//比原来短了,超出部分被删除
printVector(v1);
}
vector插入和删除
void test04() {
vector<int> v1;
v1.push_back(5);//尾插
v1.push_back(5);//尾插
v1.push_back(5);//尾插
v1.push_back(5);//尾插
v1.push_back(5);//尾插
printVector(v1);
v1.pop_back();//尾删
printVector(v1);
//插入第一个参数是迭代器
v1.insert(v1.begin(), 100);//在第一个位置插入100
printVector(v1);
//删除第一个参数也是迭代器
v1.erase(v1.begin());//删除第一个元素
printVector(v1);
v1.erase(v1.begin(), v1.end());//从头清空到尾
v1.clear();//全部清空
printVector(v1);
}
Vector数据存取
for (int i = 0; i < v1.size(); i++) {
cout << v1[i] << " "<< v1.at(i) <<endl;
}
cout << "获取第一个元素" << v1.front()<<endl;
cout << "获取最后一个元素" << v1.back()<<endl;
vector互换容器
将vec与本身互换。
reserve vector预留函数
v1.reserve(10000);
总结:如果数据量较大的时候,可以一开始利用reserve预留空间
deque容器基本概念
功能:双端数组,可以对头端进行插入删除操作。
deque与vector区别
1、vector对于头部的插入删除效率低,数据量越大,效率越低。
2、deque相对而言,对头部插入删除的速度比vector更快
3、vector访问元素时的速度会比deque快,这和两者内部实现有关
deque容器的迭代器也支持随机访问
deque构造函数
deque赋值操作
deque大小操作
没有容量的概念。 没有capacity这个方法。
deque插入和删除
deque排序
stack栈
总结
入栈-push
出栈-pop
栈顶-top
判断栈是否为空--empty
返回栈大小--size
Queue队列
一种先进先出的数据结构,他又两个出口
List容器
功能:将数据进行链式存储
STL是一个双向循环链表
优点:动态存储分配,不会造成内存浪费和溢出
缺点:链表灵魂,但是空间指针域和时间遍历额外耗费较大
List有一个重要的性质,插入操作和删除操作都不会造成list迭代器的失效,这在vector中不成立的。
总结:STL和Vector是两个最被使用的容器,各有优缺点。
it++//支持双向
it--
不支持随机访问[] at it=it+1;
L1.reverse();
sort(l1.begin(),l1.end());//支持随机访问
L1.sort(Mycompare);
//所有不支持随机访问迭代器的容器,不可以用标准算法
//不支持随机访问迭代器的容器,内部会提供对应的方法。
bool compareObject(const Person p1,const Person p2) {
if (p1.m_Age - p2.m_Age >= 0)
return true;
else
return false;
}
总结:
对于自定义数据类型,必须指定排序规则,否则编译器不知道如何进行排序
高级排序在排序规则上再进行一次逻辑规则指定。
set/multiset容器
所有元素在插入时会自动排序‘
本质:set/mulset属于关联式容器,底层结构是用二叉树实现
set和multiset区别
set不允许容器中有重复元素
multiset允许容器中有重复元素
set容器和multiset容器区别
pair对组创建
功能描述:
成对出现的数据,利用对组可以返回两个数据
set容器 自定义排序
利用仿函数,可以改变排序规则
map/multimap容器
map<int,int> m;默认构造
m.insert(pair<int,int>(10,10));//插入
m.swap(m1);
m.erase(3) 删除key为3的对
总结:对于自定义数据类型,map必须指定排序规则同set容器。