C++学习——模板与泛型(1)

一、模板概述

1、所谓泛型编程是以独立于任何特定类型的方式编写代码,使用泛型编程时,我们需要提供具体程序实例所操作的类习惯或者值;

2、模板是泛型编程的基础。模板是创建类或者函数的蓝图或者公式。我们给这些蓝图或者公式提供足够的信息,让这些蓝图或者公式真正的转变具体的类或者函数,这种转变发生在编译时;

3、模板支持 将 类型 作为参数的程序设计方式,从而实现了对泛型程序设计的直接支持。也就是说,C++模板机制允许在定义类、函数时将类型作为参数;

4、模板一般分为函数模板和类模板;

5、函数模板一般放在头文件中;

二、函数模板定义

1、函数模板为了解决重复定义不同类型但相同功能的函数的问题,如:

int funcadd(int i1, int i2)
{
    int addHe = i1 + i2;
    return addHe;
}

double funcadd(double i1, double i2)
{
    double addHe = i1 + i2;
    return addHe;
}

2、函数模板定义:
      1)模板定义是用template关键字开头的,后边跟<>,<>里边叫模板参数列表(模板实参),如果模板参数列表里有多个参数,则用逗号分开,<>里至少有一个模板参数,模板参数前面有个 typename/class(不是用来定义类的) 关键字;

template<typename T>
template<class T>

      如果模板参数列表里有多个模板参数,那就需要用多个 typename/class ,并用逗号分隔;

template<typename T, typename Q>
template<class T, class Q>

      2)模板参数列表里边表示在函数定义中用到的“类型” 或者 “值”,也和函数参数列表类似,我们使用的时候,有的时候需要指定模板实参给他,指定的时候我们要用<>把模板实参包起来。有的时候又不需要我们指定模板实参给他,系统自己能够根据一些信息推断出来;
      3)funcadd这个函数声明了一个名字为T的类型参数。这里注意,T实际是类型,这个T代表什么类型,编译器在编译的时候会根据针对funcadd()的调用来确定;

3、函数模板的使用:
      1)函数模板调用和函数调用区别不大,调用的时候,编译器会根据你调用这个函数模板时的实参去腿短模板参数列表里的参数(形参)的类型,所以需要注意:模板参数有时候是推断出来的(有时是需要我们自己提供),推断的依据是什么?是根据你调用这个函数的时候的实参来推断的;当然有些时候光凭函数实参,比如说类模板,这时候我们就得用<>来主动的提供模板参数了;

三、函数模板的使用

template<typename T>

T funcadd(T i1, T i2)
{
    T addHe = i1 + i2;
    return addHe;
}

int main()
{
    int he = funcadd(3, 1);//系统认为是int,所以编译器能推断出来模板的形参是个int型。
    double he1 = funcadd(3.3f, 1.1f);
    double he1 = funcadd(3.3f, 1);//错误,编译器不知道该推断为int还是double;
    return 0;
}

四、非类型函数模板参数

1、之前定义模板参数的时候,T前面有一个typename/class,这表示T代表一个类型,是一个类型参数;但是在模板参数列表里边,还可以定义非类型参数,非类型参数代表的是一个值;

2、对于函数模板与类模板,模板参数并不局限于类型,普通值也可以作为模板参数。在基于类型参数的模板中,你定义了一些具体的细节来加以确定代码,直到代码被调用时这些细节才被真正的确定。但是在这里,我们面对的是这些细节是值,而不是类型,当要使用基于值的模板时,必须显式地指定这些值,才能够对模板进行实例化。

3、既然非类型参数代表一个值,那么我们肯定不能用typename/class这种关键字来修饰这个值;

template<int a, int b>//a,b表示非类型参数是个整形。

4、当模板被实例化时,这种非类型模板参数的值可以是用户提供的,或者是编译器推断的,都有可能;
      但是,这些值必须都得是常量表达式,因为实例化这些模块是编译器在编译时来实例化的;

template<int a, int b>
int funcadd1()
{
    int he = a + b;
    return he;
}

int main()
{
    int he = funcadd<3, 2>();//显示的指定模板参数,在<>中提供额外的信息;

    int a = 3;
    int he1 = funcaddr<a, 2>;//不行,非类型模板参数的值必须是在编译的时候j就能够确定,因为
                             //实例化函数模板是在编译的时候干的事;
    return 0;
}

5、常见的用法

template <unsigned  a, unsigned  b>
int charscomp(const char (&p1)[a], const char (&p2)[b])
{
    cout << a << " " << b << endl;
    return strcmp(p1, p2);
}

int main()
{
    int result = charscomp("test22", "test") ;
    cout << result << endl;

    return 0;
}

6、总结:模板定义并不会导致编译器生成代码,只有我们调用这个模板时,才使编译器为我们实例化了一个特定版本的函数之后,编译器才会生成代码,编译器生成代码的时候,需要能够找到函数的函数体,所以,函数模板的定义通常都是在.h文件中;

五、类模板概念

1、用类模板来实例化一个特定的类;

2、编译器不能为类模板推断模板参数类型,所以为了使用类模板,我们必须在模板名后边用<>来提供额外的信息,这些信息其实就是对应着模板参数列表里的这些参数;

3、vector<int>是一个类模板,<>中的int为模板参数;

4、类模板可以实现——同一套代码,可以应付不同的数据类型;

5、实例化类模板的时候,必须要有类的全部信息,包括类模板中成员函数的函数体;

六、类模板定义

template<typename T>
class myvector
{
public:
    T *myiterator;//迭代器,vector iterator
public:
    myvector() {};
    myvector<T>& operator=(const myvector<T>&) {};//赋值运算符重载,在类模板内部使用模板名并                        
                                                  //不需要提供模板参数
    //myiterator myBegin() {};
    //myiterator myEnd() {};
public:
    void myFunc() {};//把成员函数的函数提放在类模板定义中,编译器隐士声明为inline
};

int main()
{
    myvector<int> v;
    return 0;
}

1、myvector是类模板名,它不是类名,类模板是用来实例化类(类型)用的,所以myvector<int>才是类名(类型名);

2、一个实例化了的类型总会用<>包含着模板参数;

七、类模板的成员函数

1、类模板成员函数,可以写在类模板定义中{},那么这种写法在类模板定义中的成员函数会被隐式声明为inline函数;

//类模板成员函数写在类模板定义范围之内
template<typename T>
class myvector
{
public:
    void myFunc() {};//把成员函数的函数提放在类模板定义中,编译器隐士声明为inline
};

2、类模板一旦被实例化之后,那么这个模板的每个实例都会有自己版本的成员函数,所以类模板的成员函数具有和这个类模板相同的模板参数(类模板的成员函数是有模板参数的);

3、定义在类模板之外的成员函数必须以关键字template开始,后面接类模板参数列表,同事,在类名后面要用<>把模板参数列表里面的所有模板参数列出来,如果是多个模板参数,用逗号分隔;

//类模板成员函数写在类模板定义范围之外
//普通成员函数定义
template<typename T>
void myvector<T>::myFunc()
{
    ...
}
//构造函数定义
template<typename T>
myvector<T>::myvector()
{
    ...
}
//运算符重载定义
template<typename T>
myvector<T>& myvector<T>::operator=(const myvector&)
{
    return *this;
}

4、一个类模板虽然可能有很多成员函数,但是当你实例化模板之后,如果你后续没有使用到某个成员函数的话,则这个成员函数不会被实例化。说白了就是一个实例化的模板,它的成员函数只有在使用的时候才会被实例化;

八、非类型类模板参数

1、实现

template<typename T, int SIZE>
class MyArray
{
public:
    T *arr[SIZE];
};

int main()
{
    MyArray<int, 5> arr;
    cout << sizeof(arr) << endl;
    return 0;
}
//默认参数
template<typename T, int SIZE = 10>
class MyArray1
{
public:
    T *arr[SIZE];
};

int main()
{
    MyArray<int> arr;
    cout << sizeof(arr) << endl;
    return 0;
}

2、类模板之外定义成员函数:

template<typename T, int SIZE = 10>
class MyArray
{
public:
    T *arr[SIZE];
    void MyFunc();
};

template<typename T, int SIZE>
void MyArray<T, SIZE>::MyFunc()
{
    cout << "SIZE : " << SIZE <<endl;
}

int main()
{
    MyArray<int, 2> arr;
    arr.MyFunc();
    cout << sizeof(arr) << endl;
    return 0;
}

3、非类型模板参数有一定限制:1)浮点型不能做非类型模板参数;2)类类型一般也不允许做非类型模板参数;

九、typename的使用场合

1、函数模板:

template<typename T, int a, int b>//typename标明其后的参数是类型参数,a,b为非类型参数
int funcaddv2(T c)
{
    ... 
}

2、类模板:

template<typename T>//名字为T的模板参数
class myvector //表示myvector里保存的类型为T类型;
{
    ...
}

template<typename T>
myvector<T>::myiterator myvector<T>::mybegin()
{
    ...
}

3、使用类的类型成员,用typename来标识这是一个类型。"::"作用域运算符不仅可以访问类中的静态成员的时候,类名::静态成员名。还可以访问类型成员;

4、类型成员:

template<typename T>
class myvector
{
public:
    typedef T* myiterator;//typedef关键字修饰的是一个类型
    ...
};

5、使用类的类型成员,用typename来标识这是一个类型;

template<typename T>
typename myvector<T>::myiterator myvector<T>::mybegin()
{//typename用来标识myvector<T>::myiterator是一个类型,不能用class替代
    cout << "mybegin" << endl;
}

int main()
{
    myvector<int> m;
    m.mybegin();
    return 0;
}

6、解释::作用域运算符:系统一般将类名::x 的 x认为是静态成员名,所以加上typename关键字告诉编译器x是一个类型

template<typename T>
typename T::size_type getStringSize(T a)//string::size_type是unsigned int
{
    if(a.empty())
    	return 0;
    return a.size();

}

int main()
{
    string myStr = "I Love China!";
    int result = getStringSize(myStr);
    cout << result << endl;
    return 0;
}

x、typename也可以写为class,但这个class的含义与类定义时的class含义不一样;

十、函数指针做其他函数的参数

typedef int (*Func)(int, int);

int myFunc(int a, int b)
{
    cout << "call myFunc" << endl;
    return 1;
}

void testFunc(int j, int m, Func funcptr)
{
    int result = funcptr(j, m);
    cout << result << endl;
}

int main()
{
    testFunc(1, 2, myFunc);
    return 0;
}

十一、函数模板的趣味用法

1、重载函数的类型自动推断;

template<typename T, typename F>
void testFunc(T i1, T i2, F funcptr)
{
    int result = funcptr(i1, i2);
    cout << result << endl;
}

int main()
{
    //testFunc(1, 2, myFunc);
    testFunc(1, 2, myFunc);
    return 0;
}

2、

class Tc
{
public:
    Tc(){ cout << "构造函数执行" << endl;}
    Tc(const Tc& t) { cout << "拷贝构造函数执行"  << endl; }
    //重载()函数调用运算符
    int operator()(int v1, int v2) const
    {
    	cout << "operator()" << endl;
    	return v1 + v2;
    }
};

template<typename T, typename F>
void testFunc(T i1, T i2, F fptr)
{
    T result = fptr(i1, i2);
    cout << result << endl;
}

int main()
{
    Tc tcobj;
    cout << "-----" << endl;
    testFunc(1, 2, tcobj);//调用一次构造函数和一次拷贝构造函数
    testFunc(1, 2, Tc());//只调用一次构造函数,和临时变量很相似
    cout << "-----" << endl;
    return 0;
}

十二、默认模板参数

1、类模板:类模板名后必须跟<>来提供额外信息,<>表示这是一个模板;

template<typename T = string, int SIZE = 5>
class MyArray
{
public:
    void MyFunction();
private:
    T arr[SIZE];
};

int main()
{
    MyArray<> abc;//完全用模板参数的缺省值

    return 0;
}

2、函数模板:老标准只能为类模板提供默认模板参数,c++11新标准可以为函数模板提供默认参数;

class Tc
{
public:
    Tc(){ cout << "构造函数执行" << endl;};
    Tc(const Tc &t){cout << "拷贝构造函数执行" << endl;};
    int operator()(int v1, int v2)
    {
    	cout << "重载()运算符执行" << endl;
    	return v1 + v2;
    }
};

template<typename T, typename F = Tc>
void testFunc(const T i, const T j, F funcPtr = F())
{
    cout << funcPtr(i, j) << endl;
}

int main()
{
    testFunc(1, 2);
    return 0;
}

      1) 同时给模板参数和函数参数提供缺省值;
      2)注意写法 F funcPtr = F();
      3)Tc重载();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值