c++提高编程
针对c++的泛型编程和STL技术做详细讲解,
1.模板
1.1模板的概念
模板就是建立通用的模板,大大提高复用性
- 模板不可以直接使用,只是一个框架
- 模板不是万能的
1.2函数模板
- c++另一种编程思想称为泛型编程,主要利用的技术模板
- c++提供两种模板机制:函数模板和类模板
1.2.1 函数模板语法
函数模板作用:
建立一个通用函数,其返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表;
template<typename T>;
函数声明或定义。
例如void func(int a);//void 和 int 都可以先不用写。
template:声明函数模板;
typename:表明后面的符号是一种数据2类型,可以用class代替
T:通用的数据类型,明长城可以替换。
template<typename T>//声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void swap(T &a,T &b)
{
T tmp = a;
a = b;
b = tmp;
}
void test()
{
//1.自动类型推到
swap(a,b);//自动推导出T是int类型。
//2.显示指定类型
swap<int>(a,b);//<>模板参数列表。
}
1.2.2函数模板注意事项
- 自动类型推到,必须推导出一致的数据类型T才可以使用
char c;
int a;
swap(a,c)//推导不出一致的T;
- 模板必须要确定T的数据类型才可以使用。
template<typename T>
void func()
{
cout<<"调用func函数"<<endl;
}
void test()
{
func();//会报错没有指定T。这里不能用自动推导
func<int>();//这里只能用显式指定类型来调用函数因为函数内部没有形参。
}
1.2.4普通函数与函数模板区别。
就是你如果输入一个传入的参数类型不是他要的,函数回自己强制转换称他要的类型
引用不能够发生类型转换!!
- 普通函数调用可以发生隐式类型转换。
//普通函数
int add(int a,int b)//引用不能够发生类型转换!!
{
return a+b;
}
void test()
{
int a = 10;
int b = 100;
char c = 'a';//char类型变成了int类型
cout<<add(a,c)<<endl;//得数为107
}
- 函数模板用自动类型推导不可以发生隐式类型转换。
//函数模板
template<typename T>
T add(T a,T b)
{
return a+b;
}
void test()
{
int a = 10;
int b = 100;
char c = 'a';
cout<<add(a,c)<<endl;//会报错因为无法确定T是什么类型是char还是int
//自动类型推导,必须推导出一致的T才可以使用
}
- 函数模板用显式指定类型可以发生隐式类型转换。
template<typename T>
T add(T a,T b)
{
return a+b;
}
void test()
{
int a = 10;
int b = 100;
char c = 'a';
cout<<add<int>(a,c)<<endl;//不会报错因为已经明确指出T是int类型
}
1.2.5普通函数和函数模板的调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数默模板
void printf(int a,int b)
{
cout<<"调用普通函数"<<endl;
}
template<class T>
void printf(T a,T b)
{
cout<<"调用函数模板"<<endl;
}
template<class T>
void printf(T a,T b,T c)//实现函数模板的重载参数多了一个。
{
cout<<"调用重载函数模板"<<endl;
}
void test()
{
int a = 1;
int b = 289;
printf(a,b);//这里会输出调用普通函数。
//如果把普通函数注释掉只有声明没有实现
//那就会报错(因为一定调用上面)
//利用空模板参数列表调用
printf<>(a,b);//这里会调用函数模板
printf(a,b,100);//这里一定会调用重载因为只有重载有三个参数。
char d = 'd';
char f = 'f';
printf(d,f);//这里会调用函数模板因为可以不用进行隐式转换。
}
1.2.6模板的局限性
模板通用不是万能的。
如果在赋值模板中传入数则无法进行
因此c++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板。
class person
{
public:
person(string na,int a)
{
name = na;
age = a;
}
string name;
int age;
}
template<class T>
bool compare(T &a,T &b)
{
if(a==b)
return true;
return false;
}
//利用具体化的person版本会被优先调用
template<>bool compare(person &a,person &b)//前面加template是为了说明这是一个具体化的过程
{
if(a.name==b.name&&a.age==b.age)
return true;
return false;
}
void test()
{
person p1("张三",10);
person p2("张三",10);
compare(p1,p2);//会优先执行重载函数。
}
有些自定义类型无法通用化运行
学模板不是为了写模板是为了会用系统模板
1.3类模板
1.3.1 类模板语法
类模板作用:
- 建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来实现。
//语法
template<class T>
类
template<class nametype,class agetype>//有两个类型就可以有不同的参数类型。
class person
{
public:
person(nametype na,agetype a)
{
name = na;
age = a;
}
nametype name;
agetype age;
}
void test()
{
person<string,int>p1("张三",19);//<>中的是模板参数列表。
}
1.3.2类模板和函数模板的区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数。
//类模板与函数模板的区别
template<class nametype,class agetype = int >//=int使用了默认参数之后的调用可以不写int类型
class person
{
public:
person(string na,int a)
{
name = na;
age = a;
}
void showperson()
{
cout<<"name:"<<name<<"age:"<<age<<endl;
}
nametype name;
agetype age;
}
void test()
{
person p("孙悟空",1000);//会报错因为无法自动类型推导
person<string,int >("孙悟空",1000);//不会报错因为使用了显示类型转换
}
1.3.3类模板中的成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中成员函数在调用时才创建
class person1
{
public:
void showperson1()
{
cout<<"person1show"<<endl;
}
};
class person2
{
public:
void showperson2()
{
cout<<"person2show"<<endl;
}
};
template<class T>
class myclass
{
public:
T obj;
//类模板中的成员函数一开始无法创建是因为obj一开始无法确定数据类型
void func1()
obj.showperson1();
void func2()
obj.showperson2();
};
1.3.4类模板对象做函数参数
类模板实例化出的对象向函数传参的方式
- 指定传入的类型:直接显示都西昂的数据类型
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将这个对象模型模板化进行传递
template<class t1,class t2>
class person
{
public:
person(string na,int a)
{
name = na;
age = a;
}
void showperson()
{
cout<<"name:"<<name<<"age:"<<age<<endl;
}
nametype name;
agetype age;
};
void printf(person<string,int>&p)//这里是指定传入类型
{
p.showperson();
}
//参数模板化:其实是函数模板
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; //typeid().name()会输出类型的名字
}
//把整个类模板化了
template<class T>
void printfperson3(T &p)
{
p.showperson();
}
void test01()
{
person<string,int>p("孙悟空",1000);
//这不是类的自动类型推导,而是函数的自动类型推导
printf(p);
}
void test02()
{
person<string,int>p2("猪八戒",18989);
//这不是类的自动类型推导,而是函数的自动类型推导
printfperson2(p2);
}
void test03()
{
person<string,int>p3("唐僧",18);
printfperson3(p3);
}
//int是基本类型,string不是
1.3.5类模板与继承
- 当子类继承的父类你是一个类模板时,子类在申明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变成类模板
因为子类不是类模板必须要知道父类中T的数据类型才能知道子类到底需要多大的内存。
class son1:public father<int>
{
//子类不是模板时必须给具体的父亲中变量的值
};
template<class t1,class t2>
class son2:public father<t2>//子类是某个类模板就不需要把父亲中得知的类型具体化可以实现灵活指出
{
}
void test01()
{
son2<int,char>child;
}
//准确来说不是儿子决定父亲,是父亲是啥放一边,等你要儿子的时候,先把父亲确定再创建儿子
1.3.6类模板成员函数的类外实现
类外实现就是只在类内写函数声明
首先要加上作用域其次要加上template<class1,class t2>
person<t1,t2>::person(t1 name,t2 age)//如果没有<t1,t2>那就只是普通成员函数的类外实现
加上<t1,t2>是为了表明这是一个类模板的构造函数
{
//person构造函数类外实现模板
}
//成员函数的类外实现
template<class t1,class t2>·//每个成员函数类外实现之前都要加这表表明这个是类模板成员函数实现。
类模板成员函数的类外实现一定要加类模板参数列表
即 类型 person<int,char>::函数名(参数列表)
1.3.7类模板分文件编写
掌握类模板成员函数分为缉拿编写产生的问题以及解决方式
类模板中成员函数创建实际是在调用阶段,导致分文件便携式链接不到
- 直接包含.cpp头文件
- 将声明和实现写到同一个文件中,并肩更改后缀名为.hpp,hpp是约定的名称,并不是强制
//person.hpp内容
都写在一个文件里面hpp表明类模板
都写到头文件里。
如果只包含.h头文件是编译器不会生成成员函数
.h包含在.cpp中所以包含.cpp就够了。
1.3.8类模板与友元
掌握类模板配合友元函数的类内和类外实现
全局函数类内实现,直接在类内声明友元
全局函数类外实现,需要提前让编译器知道全局函数的存在。
printPerson函数不加friend时是一个私有的成员函数,但加了friend之后就变成了一个全局函数,因为自身的成员函数默认就可以调用自身的成员属性不需要友元,反之需要友元的都不是成员函数
//也要让编译器提前知道有person这个模板的存在。
template<class T1,class T2>
class person;
//如果全局函数是类外实现的需要让编译器提前知道这个函数的存在
template<class T1,class T2>
void printfperosn2(person<T1,T2>p)//这里函数名前不用加作用域是因为这是一个全局函数不是成员函数。
{
cout<<"调用二号打印函数"<<endl;
//友元可以调用类内私有属性
}
//类内实现
template<classT1,class T2>
class person
{
friend void printfperson(person<T1,T2> P)
{
cout<<"调用打印函数"<<endl;
}
friend void printfperson2<>(person<T1,T2> P2);//这只是一个普通函数的声明,下方的类外实现是一个函数模板的函数实现要使这里变成函数模板的函数声明就要加一个空模板参数列表
public:
person(T1 na,T2 a)
{
name = na;
age = a;
}
private:
T1 name;
T2 age;
};
尾删法其实只需要把size-1使用户无法访问到数据就可以。
T& operator[](int index)//对[]进行重载则可以返回某一下标的数组值
{
return this->array[index];
}