学习笔记源自博览网侯捷大师的C++课程,在原视频及讲义的基础上填充注释。
如有侵权,请联系删除,抱歉。
防卫式声明
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
//0.前置声明 forward declarations
class ostream;
class complex;
complex& __doapl (complex *ths, const complex& r);
//1.类声明 class declarations
class complex{
...
};
//2.类定义 class definition
complex::function ...
#endif
注:等价于
#pragma once
。
示例:复数类-类的声明(类模板、默认参数、初始化列表、常函数、重载运算符、友元函数)
template<typename T> //类模板
class complex{
private:
T re, im; //模板
friend complex& __doapl (complex *ths, const complex& r); //友元函数
public:
complex(T r = 0, T i = 0) : re(r), im(i){
} //默认参数、初始化列表
complex& operator+=(const complex&); //重载运算符
T real() const {
return re; } //成员函数-常函数
T imag() const {
return im; }
};
int main(){
complex c1(2.5, 1.5);
complex c2(1, 2);
...
}
注1:函数若在class body内定义完成,则自动成为inline内联函数。
注2:内联函数仅是对编译器的建议,是否实际为内联函数,由编译器决定。
private
:封装数据成员,在类外无法直接访问
public
:被外界调用的函数,在类外可直接访问
protected
:在子类中可直接访问
//默认参数、初始化列表
complex(T r = 0, T i = 0) : re(r), im(i){
}
//等价写法:赋值(效率相对较差)
complex(T r = 0, T i = 0){
re = r;
im = i;
}
注1:构造函数可有多个,即构造函数的重载。
注2:若某个构造函数已包含默认参数,则构造函数重载时不能产生冲突。
complex(T r = 0, T i = 0) : re(r), im(i){
} //包含默认参数的构造函数
//complex() : re(0), im(0){} //与已有构造函数存在冲突,编译器不知道如何调用
例:
complex c1;
//complex c2();
注3:构造函数可以被
private
修饰,则不能被外部调用创建对象,如单例模式Singleton。
class A{
public:
static A& getInstance(); //对外部提供获取对象的接口
setup(){
...}
private:
A(); //默认无参构造
A(const A& rhs); //拷贝构造
};
A& A::getInstance(){
static A a; //唯一对象
return a;
}
//对象调用
A::getInstance().setup();
常成员函数:不修改类中数据成员的内容,使用const
修饰(中括号()
和花括号{}
之间)。
template<typename T> //类模板
class complex{
public:
//成员函数-常函数
T real() const {
return re; }
T imag() const {
return im; }
};
值传递:pass by value
引用传递:pass by reference(to const)
C语言中,参数传递可使用指针;C++语言中,参数传递建议使用引用。
注1:C++中建议函数参数尽量使用引用传递,但bool/char/short等短数据类型可使用值传递。
注2:传递引用时若不希望参数被修改,函数参数可使用const修饰:
void func(const Object& obj){}
。
引用的本质为指针常量:
T* const p
。指针常量的指针指向不可变;但指针常量指向的值可改变(解引用,重新赋值)。
值传递:return by value
引用传递:return by reference(to const)
注1:C++中建议函数的返回值类型尽量返回引用。
注2:不能返回引用的情况:函数作用域内的局部变量/临时对象。
友元:类中声明的友元,可直接获取类的private数据成员。
template<typename T> //类模板
class complex{
private:
T re, im; //模板
//友元函数的声明
friend complex& __doapl (complex *ths, const complex& r); //友元函数
...
};
//友元函数的定义
inline complex& __doapl(complex *ths, const complex& r){
//自由获取friend的private成员
ths->re += r.re;
ths->im += r.im;
return *ths;
}
注:友元破坏了类的封装性。
相同/同一class的各个对象(objects),互为友元(friends)。
class complex{
private:
double re, im;
public:
complex(double r = 0, double i = 0) : re(r), im(i){
}
//当前对象可直接访问另一对象的private私有数据成员
int func(const complex& param){
return param.re + param.im;
}
};
//相同/同一class的各个对象(objects),互为友元(friends)
int main(){
complex c1(2, 1);
complex c2;
//对象c2可直接访问另一对象c1的private私有数据成员
c2.func(c1);
}
注:任何成员函数,均隐含this指针,指向当前成员函数的调用者。
inline complex& __doapl(complex* ths, const complex& r){
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex& complex::operator+=(const complex &r){
return __doapl(this, r); //this表示当前对象
}
int main(){
complex c1(2, 1);
complex c2(5);
c2 += c1;
...
}
临时对象/匿名对象(temp object):typename()
/* 操作符重载 */
inline complex operator+(const complex& x, const complex& y){
//临时对象/匿名对象
return complex(real(x) + real(y), imag(x) + imag(y));
}
inline complex operator+(const complex& x, double y){
return complex(real(x) + y, imag(x));
}
inline complex operator+(double x, const complex& y){
return complex(x + real(y), imag(y));
}
int main(){
//临时对象/匿名对象
int(7);
complex();
complex(4, 5);
cout << complex(2) << endl;
complex c1(2, 1);
complex c2;
}
#include
ostream& operator<<(ostream& os, const complex& x){
return os << "(" << real(x) << "," << imag(x) << ")";
}
int main(){
complex c1(1, 2);
complex c2(3, 4);
cout << c1 << endl; //(1, 2)
cout << c1 << c2 << endl; //(1, 2)(3, 4)
}
class String{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const {
return m_data};
private:
char* m_data;
};
inline String::String(const char* cstr = 0){
if(cstr){
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
}else{
m_data = new char[1];
*m_data = '\0';
}
}
//拷贝构造函数
/* 同一个类的不同对象之间互为友元,可直接访问private成员 */
inline String::String(const String& str){
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
//拷贝赋值函数
/* 先释放左值;再创建与右值相同大小的空间;最后深拷贝 */
inline String& String::operator=(const String& str){
//检测自我赋值(self assignment)
if(this == &str){
return *this;
}
//1.释放原有空间
delete[] m_data;
//2.创建新空间
m_data = new char[strlen(str.m_data) + 1];
//3.深拷贝
strcpy(m_data, str.m_data);
return *this;
}
//输出函数-重载左移运算符
ostream& operator<<(ostream& os, const String& str){
os << str.get_c_str();
return os;
}
//析构函数
inline String::~String(){
delete[] m_data;
}
具有指针成员的类,必须自定义拷贝构造函数和拷贝赋值函数operator=,否则会导致浅拷贝,并造成内存泄露或重复释放。
//拷贝构造函数
/* 同一个类的不同对象之间互为友元,可直接访问private成员 */
inline String::String(const String& str){
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
//拷贝赋值函数
/* 先释放左值;再创建与右值相同大小的空间;最后深拷贝 */
inline String& String::operator=(const String& str){
//检测自我赋值(self assignment)
if(this == &str){
return *this;
}
//1.释放原有空间
delete[] m_data;
//2.创建新空间
m_data = new char[strlen(str.m_data) + 1];
//3.深拷贝
strcpy(m_data, str.m_data);
return *this;
}
检测自我赋值的作用:
//拷贝赋值函数
/* 先释放左值;再创建与右值相同大小的空间;最后深拷贝 */
inline String& String::operator=(const String& str){
//检测自我赋值(self assignment)
//1.直接返回对象本身,效率高
//2.若未判断是否自我赋值,则释放原有空间后,访问右值时会产生不确定行为
if(this == &str){
return *this;
}
//1.释放原有空间
delete[] m_data;
//2.创建新空间
m_data = new char[strlen(str.m_data) + 1];
//3.深拷贝
strcpy(m_data, str.m_data);
return *this;
}
Stack:存在于某作用域scope的一块内存空间。
Heap:即system heap,由操作系统提供的一块global全局内存空间,程序可动态分配(dynamic allocated),从中获取若干区块(blocks)。
通过动态内存分配获取的堆内存,需由程序员主动释放(delete)。
栈对象的生命周期存在于作用域内,被称为auto object。
析构函数在作用域结束时被自动调用。
class Complex{
...};
...
{
Complex c(1, 2);
}
静态局部对象的生命周期,在作用域结束后仍存在,直到整个程序结束。
class Complex{
...};
...
{
static Complex c(1, 2);
}
全局对象的生命周期,在整个程序结束后才结束,作用域是整个程序(全局作用域)。
class Complex{
...};
...
Complex c(1, 2);
int main()
{
...
}
堆对象的生命周期,在其被delete后结束。
class Complex{
...};
...
{
Complex *p = new Complex(1, 2);
//若未delete,会导致内存泄露
delete p;
}
注:若未delete指向堆对象的指针p,则当作用域结束后,p指向的堆内存未被释放,当指针p本身已被释放,无法再delete,导致内存泄露。
new的作用:先分配内存,再调用构造函数。
Complex* pc = new Complex(1, 2);
/* 编译器内部实现 */
Complex* pc;
//1.分配内存
//new操作符内部调用malloc()函数
void* mem = operator new(sizeof(Complex));
//2.静态类型转换
pc = static_cast<Complex*>(mem);
//3.调用构造函数
//pc调用构造函数,则pc即隐藏的this指针
pc->Complex::Complex(1, 2);
构造函数属于类的成员函数,隐含了this指针。
Complex::Complex(pc, 1, 2);
Complex::Complex(this, 1, 2);
delete的作用:先调用析构函数,再释放内存。
class String{
public:
~String(){
delete[] m_data;
}
...
private:
char* m_data;
}
String* ps = new String("Hello");
...
delete ps;
/* 编译器内部实现 */
//1.调用析构函数
String::~String(ps);
//2.释放内存
//delete操作符内部调用free(ps)函数
operator delete(ps);
注1:VC环境下,每个内存区块的大小一定为16的倍数,否则需要进行填充。
注2:操作系统需要依赖额外空间包含的信息,对内存区块进行回收。
注:Cookie值:十六进制数0x00000041。
实际为0x40,借用最后1位表示申请(1)或回收(0)内存。
倒数第2位:
4*16=64
字节;倒数第1位:1,表示申请内存。
注:在堆区动态创建对象数组时,会使用4字节的内存空间记录数组元素的个数。
delete p
时,只会调用1次析构函数,但不会造成数组的内存泄露,操作系统会根据Cookie值释放动态分配的堆区数组内存。delete p
时,只会调用1次析构函数,会造成指针类型成员的内存泄露,但不会造成数组的内存泄露,操作系统会根据Cookie值释放动态分配的堆区数组内存。注:new对象/变量数组,必须delete对象/变量数组。new/delete不对应,可能造成指针类型成员的内存泄露。
类的成员函数只有1份,但需处理多个不同对象,成员函数通过this指针获取不同需处理的对象。
静态成员:只存在1份。
静态成员函数:只存在1份,不含this指针。只能处理静态成员/数据。
class Account{
public:
static double m_rate;
static void set_rate(const double& x){
m_rate = x; }
};
//静态变量的定义
double Account::m_rate = 8.0;
int main(){
/* 静态成员函数的调用 */
//1.通过类名调用
Account::set_rate(5.0);
//2.通过对象调用
Account a;
a.set_rate(6.0);
}
单例模式-饿汉式:类加载时,初始化对象。
优点:获取对象的速度快;
缺点:类加载较慢。
class A{
public:
//通过静态函数,获取唯一的静态对象
static A& getInstance(){
return a; }
void setup(){
...}
private:
//构造函数私有化,外部无法通过构造函数创建对象
A();
A(const A& rhs);
//静态对象-唯一
static A a;
};
int main(){
//通过类名调用静态成员函数(获取静态对象),进而调用非静态成员函数
A::getInstance().setup();
}
单例模式-懒汉式:延迟加载对象;类加载时,不初始化对象,在调用静态函数时才创建对象。
优点:类加载较快;
缺点:获取对象的速度慢。
class A{
public:
//通过静态函数,获取唯一的静态对象
static A& getInstance(){
//仅当静态函数调用时,才创建唯一的静态对象
//当函数作用域结束时,唯一的静态对象仍存在
static A a;
return a;
}
void setup(){
...}
private:
//构造函数私有化,外部无法通过构造函数创建对象
A();
A(const A& rhs);
};
int main(){
//通过类名调用静态成员函数(获取静态对象),进而调用非静态成员函数
A::getInstance().setup();
}
类模板使用时,必须显式指定具体的泛型类型。
template<typename T> //类模板
class complex{
private:
T re, im; //模板
friend complex& __doap1 (complex *ths, const complex& r); //友元函数
public:
complex(T r = 0, T i = 0) : re(r), im(i){
} //默认参数、初始化列表
complex& operate+=(const complex&); //重载运算符
T real() const {
return re; } //成员函数-常函数
T imag() const {
return im; }
};
int main(){
complex c1(2.5, 1.5);
complex c2(1, 2);
...
}
注1:关键字
class
可替换为typename
。注2:函数模板使用时,不必显式指定具体的泛型类型。编译器会对函数模板进行实参推导/类型推导(argument deduction)。类模板使用时,需显式指定具体的泛型类型。
//函数模板
template<class T>
inline const T& min(const T& a, const T& b){
return b < a ? b : a;
}
class stone{
public:
stone(int w, int h, int we) : _w(w), _h(h), _weight(we){
}
//重载运算符<
bool operator< (const stone& rhs){
return this->_weight < rhs._weight;
}
private:
int _w, _h, _weight;
}
int main(){
stone r1(2, 3), r2(3, 3), r3;
//调用模板函数min,类型推导
//类型推导T为stone类型,进一步调用stone::operator<
r3 = min(r1, r2);
}
语法:
namespace std
{
...
}
using编译指令:使用指定命名空间。使用using编译指令后,可直接访问命名空间的成员,而无需使用::作用域运算符
。
语法:using namespace 命名空间名;
#include
using namespace std;
int main(){
cin >> ... ;
cout << ... ;
return 0;
}
using声明:使用指定命名空间的成员。使用using声明后,可直接访问命名空间的成员,而无需使用::作用域运算符
。
语法:using 命名空间名::成员;
#include
using std::cout;
int main(){
std::cin >> ... ;
cout << ... ;
return 0;
}
#include
int main(){
std::cin >> ... ;
std::cout << ... ;
return 0;
}
类与类之间的关系:
复合/组合:一个类包含另一个类。
具有组合关系的两个类,生命周期相同(同步)。
适配器模式Adapter
/* 利用已有的功能强大的容器deque,构造queue,仅开放部分函数接口 */
template <class T, class Sequence = deque<T>>
class queue{
...
protected:
//底层容器
Sequence c; //等价于 deque c;
public:
//利用底层容器c的操作函数完成
bool empty() const {
return c.empty(); }
size_type size() const {
return c.size(); }
reference front() {
return c.front(); }
reference back() {
return c.back(); }
//deque为双端可进出队列,queue为末端进、首端出(先进先出)
void push(const value_type& x) {
c.push(x); }
void pop() {
c.pop_front(); }
};
组合关系的内存示意图
组合关系下的构造函数/析构函数调用顺序
构造函数的调用:由内向外
Container的构造函数先调用Component的默认构造函数(由编译器完成),再执行本身。
析构函数的调用:由外向内
Container的析构函数先执行本身,再调用Component的析构函数(由编译器完成)。
委托:两个类之间使用指针相连。
具有委托关系的两个类,生命周期不相同(不同步),委托类的对象先被创建,当需要使用被委托类(指针指向的类)时,才创建被委托类的对象。
委托类:对外接口;调用被委托类服务(进行具体实现)。
被委托类:具体实现。
/* Handle */
// file String.hpp
class StringRep;
class String {
public:
String();
String(const char* s);
String(const String& s);
String& operator=(const String& s);
~String();
...
private:
/* pImpl——pointer to implementation */
//Handle / Body
//该指针可指向不同的实现类
StringRep* rep; //指向具体实现的指针
};
/* Body */
// file String.cpp
#include "String.hpp"
namespace {
clase StringRep {
friend class String;
StringRep(const char* s);
~StringRep();
int count;
char* rep;
};
}
String::String(){
...}
pImpl(Handle / Body,委托类/被委托类):编译防火墙,可灵活地指向不同的实现类。
委托类的指针成员指向具体的实现类(被委托类)。
C++的3种继承方式:
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node : public _List_node_base
{
_Tp _M_data;
};
父类的数据成员,被子类完整地继承。
继承关系下的构造函数/析构函数调用顺序
构造函数的调用:由内向外
子类/派生类的构造函数先调用父类/基类的默认构造函数(由编译器完成),再执行本身。
析构函数的调用:由外向内
子类/派生类的的析构函数先执行本身,再调用父类/基类的析构函数(由编译器完成)。
父类/基类的构造函数必须为虚函数,由子类进行重写,否则会导致未定义的行为。
非虚函数(non-virtual):不希望派生类重新定义(重写,override)。
虚函数(virtual):希望派生类重新定义(重写,override),且已有默认定义。
纯虚函数(pure virtual):希望派生类一定重新定义(重写,override),且无默认定义。
class Shape{
public:
//纯虚函数:子类必须重写
virtual void draw() const = 0;
//虚函数:子类可重写
virtual void error(const std::string& msg);
//非虚函数:子类不可重写
int objectID() const;
};
class Rectangle : public Shape{
..};
class Ellipse : public Shape{
..};
class Subject{
int m_value;
/* 委托Delegation:使用指针指向另一个类 */
vector<Observer*> m_views;
public:
//注册
void attach(Observer *obs){
m_views.push_back(obs);
}
void set_val(int value){
m_value = value;
notify();
}
//通知
void notify(){
for(int i = 0; i < m_views.size(); i++){
m_views[i]->update(this, m_value);
}
}
//注销
};
class Observer{
public:
/* 继承Inheritance */
//纯虚函数:子类必须重写
virtual void update(Subject* sub, int value) = 0;
};
Primitive类和Composite类抽象出一个共同基类Component类。
Composite类中vector容器的元素类型vector
,必须使用指向Component类的指针(Component*
),而不能使用Component类的子类对象(Primitive
或Composite
)。
注:vector存储的元素类型必须相同:
基类指针类型(
Component*
)的数据类型大小相同;而基类的子类对象(
Primitive
或Composite
)的数据类型大小可能不同。因此,必须使用
vector
。
基类创建未来可能扩展的子类(对象)。
注:clone()函数不能是静态成员函数。
静态成员函数的调用方式为
类名::静态成员函数()
,需要类名;但子类的类名尚未确定。
原型模式-基类
原型模式-子类
需求:将目标类的对象,转换为指定数据类型。
例:包含分子、分母的分数类,转为double类型。
class Fraction
{
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denoinator(den) {
}
//重载的函数名与目标返回值类型相同,可省略返回值类型
operator double() const {
return (double)(m_numerator / m_denoinator);
}
private:
int m_numerator; //分子
int m_denoinator; //分母
};
int main(){
Fraction f(3, 5);
double d = 4 + f; //调用operator double()函数,将f转为0.6
}
注:可包含多个转换函数,如将整数转换为double/string类型。
注:argument表示实参;parameter表示形参。
class Fraction
{
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denoinator(den) {
}
Fraction operator+(const Fraction& f) {
return Fraction(...);
}
private:
int m_numerator; //分子
int m_denoinator; //分母
};
int main(){
Fraction f(3, 5);
//重载运算符operator+()的右操作数为Fraction对象
//构造函数未使用explicit修饰,操作数4与默认参数可隐式转换为Fraction对象
Fraction d = f + 4; /* 编译正常 */
}
class Fraction
{
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denoinator(den) {
}
//重载的函数名与目标返回值类型相同,可省略返回值类型
operator double() const {
return (double)(m_numerator / m_denoinator);
}
Fraction operator+(const Fraction& f) {
return Fraction(...);
}
private:
int m_numerator; //分子
int m_denoinator; //分母
};
int main(){
Fraction f(3, 5);
//路径1:调用operator double()函数,将f转换为double类型;3/5即0.6与操作数4相加得到4.6,隐式转换为Fraction类型
//路径2:构造函数未使用explicit修饰,操作数4可隐式转换为Fraction对象;再调用operator+()函数
Fraction d2 = f + 4; /* 编译报错:存在歧义ambiguous */
}
class Fraction
{
public:
explicit Fraction(int num, int den = 1)
: m_numerator(num), m_denoinator(den) {
}
//重载的函数名与目标返回值类型相同,可省略返回值类型
operator double() const {
return (double)(m_numerator / m_denoinator);
}
Fraction operator+(const Fraction& f) {
return Fraction(...);
}
private:
int m_numerator; //分子
int m_denoinator; //分母
};
int main(){
Fraction f(3, 5);
/* 单参构造函数使用explicit修饰 */
//1.右操作数4不会被隐式转换为Fraction对象,继而无法调用operator+()函数
//2.调用operator double()将f转换为double,求和后,无法将double转换为Fraction类型
Fraction d3 = f + 4; /* Error:无法将double转换为Fraction类型 */
}
智能指针需要具备指针允许的所有操作。智能指针类的成员一定包含一般指针。
智能指针通用写法:
template<class T>
class shared_ptr
{
public:
//重载运算符 *
T& operator*() const{
return *px;
}
//重载运算符 ->
T* operator->() const{
return px;
}
//智能指针的有参构造函数,使用初始化列表对原指针(成员)进行赋值
shared_ptr(T* p) : px(p) {
}
private:
T* px; //被智能指针包装的指针
long* pn;
...
};
使用场景:
struct Foo
{
...
void method(void) {
... }
};
//使用构造函数初始化,初始化列表使用new FOO对类成员px赋值
shared_ptr<Foo> sp(new Foo);
//使用场景1:原始类Foo的拷贝构造函数
Foo f(*sp);
//使用场景2:调用函数
sp->method();
//等价于px->method();
注:
sp->
相当于px
。特殊:C++中重载运算符
->
后需要继续使用->
作用下去,即sp->
实际应该为sp->->
或px->
。
迭代器表示容器中当前指向的元素的位置。
注1:迭代器相当于智能指针,存在运算符重载。
注2:
link_type node
表示实际指针,指向双向链表的节点;迭代器是包装实际指针的智能指针。
仿函数:类中重载函数调用运算符()
,即重载operator()
。该类创建的对象为函数对象。
()
:函数调用运算符(function call operator)
注:仿函数类的对象,称为函数对象。
select1st类仿函数的调用:
select1st
()(Pair pair) 第1个()表示创建匿名对象;
第2个()表示调用重载的函数调用运算符(),即operator()(…)
标准库中的仿函数:
/* 继承一元操作符类unary_function */
template<class T>
struct identity : public unary_function<T, T> {
const T& operator()(const T& x) const {
return x;
}
};
template<class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
const typename Pair::first_type& operator()(const Pair& x) const {
return x.first;
}
};
template<class Pair>
struct select2nd : public unary_function<Pair, typename Pair::second_type> {
const typename Pair::second_type& operator()(const Pair& x) const {
return x.second;
}
};
/* 继承二元操作符类binary_function */
template<class T>
struct plus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const {
return x + y; // x + y为新创建的局部变量,返回值需要使用T接收,而不能使用引用T&
}
};
template<class T>
struct minus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const {
return x - y; // x - y为新创建的局部变量,返回值需要使用T接收,而不能使用引用T&
}
};
template<class T>
struct equal_to : public binary_function<T, T, T> {
bool operator()(const T& x, const T& y) const {
return x == y;
}
};
template<class T>
struct less : public binary_function<T, T, T> {
bool operator()(const T& x, const T& y) const {
return x < y;
}
};
namespace中可定义变量、函数、结构体和类等。
using namespace std;
//---------------------------
#include
#include //shared_ptr
namespace ns01
{
void test_member_template(){
...
}
} //namespace ns01
//---------------------------
#include
#include
namespace ns02
{
template<typename T>
using Lst = list<T, allocator<T>>;
void test_template_template_param(){
...
}
} //namespace ns02
//---------------------------
/* 测试程序 */
int main(int argc, char** argv)
{
// 多个命名空间之间无任何关联
ns01::test_member_template();
ns02::test_template_template_param();
}
调用者在使用时再指定泛型的具体类型。
template<typename T> //类模板
class complex{
private:
T re, im; //模板
friend complex& __doap1 (complex *ths, const complex& r); //友元函数
public:
complex(T r = 0, T i = 0) : re(r), im(i){
} //默认参数、初始化列表
complex& operate+=(const complex&); //重载运算符
T real() const {
return re; } //成员函数-常函数
T imag() const {
return im; }
};
注1:关键字
class
可替换为typename
。注2:函数模板使用时,不必显式指定具体的泛型类型。编译器会对函数模板进行实参推导/类型推导(argument deduction)。类模板使用时,需显式指定具体的泛型类型。
//函数模板
template<class T>
inline const T& min(const T& a, const T& b){
return b < a ? b : a;
}
class stone{
public:
stone(int w, int h, int we) : _w(w), _h(h), _weight(we){
}
//重载运算符<
bool operator< (const stone& rhs){
return this->_weight < rhs._weight;
}
private:
int _w, _h, _weight;
}
int main(){
stone r1(2, 3), r2(3, 3), r3;
//调用模板函数min,类型推导
//类型推导T为stone类型,进一步调用stone::operator<
r3 = min(r1, r2);
}
成员模板:类模板中的成员(函数/属性)也为模板。
template <class T1, class T2>
struct pair {
typedef T1 first_type;
typedef T2 second_type;
// 成员变量
T1 first;
T2 second;
// 构造函数
pair() : first(T1()) , second(T2()) {
} // 匿名对象T1()、T2()
pair(const T1& a, const T2& b) : first(a), second(b) {
} // 有参构造
/* 成员模板 */
template <class U1, class U2>
pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {
} // 拷贝构造
};
应用场景:STL中的**拷贝构造函数//有参构造函数**常设计为成员模板。
成员模板的泛型类型是类模板的泛型类型的子类。
注:成员模板的泛型类型U1/U2,继承自类模板的泛型类型T1/T2。反之不成立。
class Base1 {
};
class Derived1 : public Base1 {
};
class Base2 {
};
class Derived2 : public Base2 {
};
pair</* U1 */Devired1, /* U2 */Devired2> p;
pair<Base1, Base2> p2(p);
/* 等价写法 */
// 使用成员模板创建匿名对象,用于类的拷贝构造函数
pair</*T1*/Base1, /*T2*/Base2> p2(pair</*U1*/Devired1, /*U2*/Devired2>());
示例:智能指针类成员模板的应用
智能指针子类shared_ptr
有参构造函数的参数(被包装的指针),是智能指针父类__shared_ptr
有参函数构造函数的参数(被包装的指针)的子类。
template<typename _Tp>
class shared_ptr : public __shared_ptr<_Tp>
{
...
/* 成员模板 */
template<typename _Tp1>
// 构造函数
// 子类构造函数shared_ptr的参数__p,对父类构造函数__shared_ptr<_Tp>赋值
explicit shared_ptr(/*Devired*/_Tp1* __p) : __shared_ptr</*Base*/_Tp>(__p) {
}
...
};
/* 指针类的多态 */
Base1 *ptr = new Devired1; // 向上转型up-cast
/* 智能指针类的多态 */
// 使用子类对象
shared_ptr<Base1*> sptr(new Devired1); // 模拟向上转型up-cast
注1:特化的优先级高于泛化。当存在合适的特化版本时,编译器优先调用相应的特化版本。
注2:全特化的所有模板参数均被指定,故模板参数列表为空,即
template <>
。
示例:类模板的泛化和特化
/* 一般的泛化 */
template <class Key>
struct hash {
};
/* 不同版本的特化 */
// 本案例只包含1个模板参数,特化后即全特化
template<> // 全特化的所有参数均被指定,故模板参数列表为空。
struct hash<char> {
size_t operator()(char x) const {
return x;
}
};
template<>
struct hash<int> {
size_t operator()(int x) const {
return x;
}
};
template<>
struct hash<long> {
size_t operator()(long x) const {
return x;
}
};
/* 测试案例 */
// 1.当未指定类型时,则使用泛化的类模板
// 2.当指定特定类型时,则使用相应特化版本的类模板
cout << hash<long>()(1000); // 第1个()表示匿名对象;第2个()表示调用函数调用运算符
偏特化/局部特化(partial specialization):指定部分模板参数的具体类型。
对若干数量的模板参数进行特化(显式指定类型)
注1:偏特化的模板参数,必须从左到右连续指定(如偏特化第1/2/3个),而不能穿插指定(如偏特化第1/3/5个),类似于默认参数必须从参数列表的最右侧往左连续。
注2:偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表,否则报错。
/* 泛化的类模板 */
template<typename T, typename Alloc = .../*默认参数*/> // 模板参数
class vector
{
...
};
/* 偏特化的类模板-个数的偏特化 */
// 通过偏特化/局部特化,模板参数的2个泛型类型变为1个
// 偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表
template<typename Alloc = .../*默认参数*/>
class vector<bool, Alloc> // 偏特化/局部特化:使用指定的bool类型,绑定泛型类型T
{
...
};
将模板参数类型的表示范围缩小:
T
缩小为对应的指针类型T*
;T
缩小为const限定符修饰T const
。示例:类模板的泛化和偏特化
/* 泛化的类模板 */
template<typename T> // 模板参数
class C
{
...
};
/* 偏特化的类模板-范围的偏特化:指针类型偏特化 */
template<typename T> // 模板参数
class C<T*>
{
...
};
// 等价写法
template<typename U> // 模板参数
class C<U*>
{
...
};
/* 偏特化的类模板-范围的偏特化:const偏特化 */
template<typename T> // 模板参数
class C<T const>
{
...
};
/* 测试案例 */
/* 使用泛化版本的类模板 */
C<string> obj1;
/* 使用偏特化版本的类模板-范围的偏:指针类型偏特化 */
C<string*> obj2; // 任意类型T → 任意指针类型T*
/* 使用偏特化版本的类模板-范围的偏:const偏特化 */
C<string const> obj3; // 任意类型T → const类型T const
示例:类模板的泛化、全特化及偏特化的调用顺序
#include
using namespace std;
/* 泛化的类模板 */
template<typename T1, typename T2>
class Test {
public:
Test(T1 a, T2 b) : _a(a), _b(b) {
cout << "泛化的类模板" << endl;
}
private:
T1 _a;
T2 _b;
};
/* 全特化的类模板 */
template<> // 全特化的所有参数均被指定,故模板参数列表为空
class Test<int, double> {
public:
Test(int a, double b) : _a(a), _b(b) {
cout << "全特化的类模板" << endl;
}
private:
int _a;
double _b;
};
/* 偏特化的类模板-个数的偏特化 */
template<typename T2> // 偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表
class Test<int, T2> {
public:
Test(int a, T2 b) : _a(a), _b(b) {
cout << "偏特化的类模板-个数的偏特化" << endl;
}
private:
int _a;
T2 _b;
};
/* 偏特化的类模板-范围的偏特化:指针类型偏特化 */
template<typename T1, typename T2>
class Test<T1*, T2*> {
// 将泛型类型 T 缩小为对应的指针类型 T*
public:
Test(T1* a, T2* b) : _a(a), _b(b) {
cout << "偏特化的类模板-范围的偏特化:指针类型偏特化" << endl;
}
private:
T1* _a;
T2* _b;
};
/* 偏特化的类模板-范围的偏特化:const偏特化 */
template<typename T1, typename T2>
class Test<T1 const, T2 const> {
// 将泛型类型 T 缩小为const限定符修饰 T const
public:
Test(T1 a, T2 b) : _a(a), _b(b) {
cout << "偏特化的类模板-范围的偏特化:const偏特化" << endl;
}
private:
T1 _a;
T2 _b;
};
int main()
{
Test<string, double> test1("hello", 1.1); // 泛化的类模板
Test<int, double> test2(1, 2.2); // 全特化的类模板
Test<int, string> test3(2, "world"); // 偏特化的类模板-个数的偏特化
Test<int*, int*> test4(nullptr, nullptr); // 偏特化的类模板-范围的偏特化:指针类型偏特化
Test<const int, const int> test5(1, 2); // 偏特化的类模板-范围的偏特化:const偏特化
return 0;
}
函数模板只有全特化,没有偏特化。
注:C++存在函数重载:形参列表不同(即参数个数、类型或顺序不同)的同名函数,编译器根据函数类型判断调用的重载函数。若函数模板存在偏特化(如个数的偏特化),会与函数重载产生冲突。
示例:函数模板的泛化及全特化
#include
using namespace std;
// 函数模板只有全特化,没有偏特化
/* 泛化的函数模板 */
template<typename T1, typename T2>
void func(T1 a, T2 b) {
cout << "泛化的函数模板" << endl;
}
/* 全特化的函数模板 */
template<>
void func(int a, double b) {
cout << "全特化的函数模板" << endl;
}
int main()
{
int a = 1;
char ch = 'x';
double b = 2.0;
func(a, ch); // 泛化的函数模板
func(a, b); // 全特化的函数模板
return 0;
}