学习C++第二十三课--使用typename的场合、函数模板、默认模板参数与趣味写法分析笔记

一、typename的使用场合

 

typename主要用来表明“其后面跟的是一个类型”。

(1)在模板定义里,表明其后的模板参数是类型参数。

看一下函数模板的定义:

template<typename T,int a,int b> //可以这样写template<class T,int a,int b>
int funcaddv2(T c){...}

再看一个类模板的定义:

template<typename T>  //可以这样写template<class T>
class myvector{...};

在这里,typename也可以写成class,这两个关键字在这里的功能一样。但是需要注意的是,这里如果写成class,和定义时用的class完全是两个不同的含义。

(2)用typename标明这是一个类型(类型成员)。

“::”是作用域运算符,当访问类中的静态成员变量时需要用到,即“类名::静态成员变量名”。例如,定义一个类的静态成员变量并赋予初值:“int Time::mystatic = 15;”。

(3)::还可以用来标明类型成员。

如下例子,在.h头文件中声明:

template<typename T>
class myvector
{
public:
	typedef T* myiterator;

public:
	myvector();
	myvector& operator=(const myvector&);
public:
	myiterator mybegin();
	myiterator myend();
public:
	void myfunc();
};

在.cpp中这样定义:

template<typename T>
myvector<T>::myvector()//实例化构造函数
{

}

template<typename T>
void myvector<T>::myfunc()//实例化构造函数
{
    cout << "类模板成员函数!" << endl;
}

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

}

int main()
{
    myvector <int> tmpvector1;
    tmpvector1.myfunc();

    myvector <double> tmpvector2;
    tmpvector1.myfunc();

    myvector <string> tmpvector3;
    tmpvector1.myfunc();

    return 0;
}

我们着重看这一句:

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

这段代码首先要强调作用域运算符“::”的第二个用法--访问类型成员。myiterator 是一个类型,mybegin返回的就是这种类型。所以,在类模板声明之外要书写这种类型就要用到“::”,就是上面看到的myvector<T>::myiterator写法代表一个类型(T*类型)。

我们可以看到,在myvector<T>::myiterator之前,额外用到一个typename。首先,普通类型用不到,类模板才会用到。

编译器在遇到myvector<T>::myiterator时,不确定表示的是一个类型还是一个静态成员变量,而C++在默认情况下通过作用域运算符访问的是静态成员变量而不是类型,所以加typename是为了告诉编译器myiterator是一个类型,所以在其前面用typename来修饰。

但是需要注意的是:这里template不能用class来替换。

(4)用于修饰函数模版返回值类型。

template<typename T>
typename T::size_type getLenth(const T&c)//前面要加typename,否则报错
{
    //这些诸如empty、size等成员函数,在函数模版未被实例化之前,谁也不知道这些成//员函数到底写得对还是不对,但一旦被实例化,自然可知
    if(c.empty())
    {
       return 0;
}
return c.size();
}

在main主函数中,加入如下代码:

string mytest="I LOVE CHINA!";
//size_type类似unsigned int 类型,定义于string类中,一般和string配套使用,考虑到各种//机器中数据类型的差异,所以引入这个类型,保证程序与实际的机器匹配的目的。
string::size_type size2 = getLenth(mytest);
cout<<size2<<endl;//13

二、函数指针作为其他函数的参数和把函数指针改写成函数模板

下面写一个普通函数:

int fc(int tmp1,int tmp2)
{
   //......
   return tmp1+tmp2;
}

在.h头文件中声明一个函数指针类型:

typedef int(*funType)(int,int);

下面写一个函数,把funType作为参数传进去:

void testfunc(int i,int j,funType funcpoint)
{
   int result = funcpoint(i,j);
   cout<<result<<endl;
}

在main主函数中加入如下代码:

testfunc(10,20, fc);//等于30

因为拿到了函数fc的函数指针,所以可以通过这个指针调用函数fc。

把testfunc函数改写成函数模板如下:

template<typename T,typename F>
void testfunc(const T & i,const T & j,F funcpoint)
{
    cout<<funcpoint(i,j)<<endl;
}

可以看到:

系统通过第一个参数10和第二个参数20,推断出testfunc的模板参数T是int类型,推断出模板参数F是函数指针类型,所以funcpoint就是函数指针,从而可以直接使用funcpoint来进行函数调用。

现在引入“可调用对象”的概念,这里先简单了解一下,如果一个类,重载了“()”运算符,那么如果生成了该类的一个对象,就可以用“对象名(参数.......)”的方式来使用该对象,看起来就像函数调用一样,那么用这个类生成的对象就是一种可调用对象(可调用对象很多种,这里先只说这一种)。

现写一个可调用对象所代表的类,只要重载“()”运算符即可,如下:

class kuohao 
{
public:
    kuohao() { cout << "构造函数执行!" << endl; }
    kuohao(const kuohao& c) { cout << "拷贝构造函数执行!" << endl; }
    int operator()(int v1, int v2)const
    {
        return v1 + v2;
    }
};

在main主函数中这样调用:

kuohao kh;
testfunc(12, 25, kh);//这里调用拷贝构造函数

程序运行如下:

 看上面主函数的第2行代码,函数testfunc的第3个参数传递进去了一个kh对象,系统推断模板参数F的类型应该为kuohao(类类型),因此testfunc函数模板这里会调用kh类的拷贝构造函数来生成一个叫作funcpoint的kuohao类型的对象。

经过调试得知:

代码执行到“cout<<funcpoint(i,j)<<endl;”实际执行的就是可调用对象,也就是kuohao类中重载的“()”运算符,所以这行代码打印出来的结果为37。

可以把“testfunc(12, 25, kh);”改为“testfunc(12, 25, kuohao());”,可以看到,这里并没有执行kuohao类的拷贝构造函数,只执行了一次kuohao类的构造函数。这说明系统推断F类型应该为kuohao(类类型),然后直接把代码kuohao()生成的临时对象构造到funcpoint对象形参中去,这样节省了一次拷贝构造函数的调用,也节省了一次对析构函数的调用。

总结:

同一个函数模版testfunc,根据传递进去的参数不同,就能够推断出不同的类型,上面的延时推断出了两种类型:

(1)推断出是函数指针;

(2)推断出的是一个对象,而且是一个可调用对象。

三、默认模板参数

1、类模板

类模板名后面必须用尖括号“<>”来提供额外信息,这和函数模板不一样,函数模板有很多时候不需要用“<>”(编译器能够推断出类型)的,而类模板中的尖括号“<>”必须有,“<>”表示类必须是从一个类模板实例化而来,所以“<>”有一个表示“这是一个模板”的强调作用。

如果一个模板参数也给一个默认值,代码如下:

template<typename T,int size = 10>
class myarray
{
private:
	T arr[size];
};

注意:如果某个模板参数有默认值,那么从这个有默认值的模板参数开始,后面的所有模板参数都得有默认值(这一点和函数的形参默认值规则一样)。

调用的时候,如果完全用默认值,则可以直接使用一个空的尖括号(空的尖括号不能省):

myarray< >abc;

一般情况下,在程序中遇到这种类名后面带“<>”的形式,都表示这是一个类模板并且使用的是默认模板参数。

如果想提供一个模板参数,而另外一个模板参数要用默认值,可以这样写代码:

myarray<int a=10,b=20>def; 等价于  myarray<int>def;

2、函数模板

老的C++标准只允许为类模板提供默认模板参数,C++11新标准也可以为函数模板提供默认模板参数。

假如需要按如下要求进行函数调用:

testfunc(3,4);

可以看到,最后一个模版参数没有提供,需要给函数模版testfunc提供默认参数,让testfunc能够实现调用kuohao类里重载的“()”的能力,修改testfunc函数模版如下:

template<typename T, typename F = kuohao>
//F funcpoint = F()等价kuohao funcpoint = kuohao()
void testfunc(const T& i, const T& j, F funcpoint = F())
{
cout << funcpoint(i, j) << endl;
}

注意:

这里不但为模板参数F提供默认参数kuohao(类名),还为函数模板testfunc的第三个形参提供默认值,注意这个默认值的写法F functest=F()。这个等于默认在这里构造了一个临时的类kuohao的对象,直接构造到funcpoint所代表的空间,现在funcpoint就是一个类kuohao的对象。

有几点需要说明:

(1)必须同时为模板参数和函数参数指定默认值,一个也不能少,否则语法通不过且语义也不完整。

(2)kuohao类必须重载“()”运算符,必须保证funcpoint是一个可调用对象,否则代码运行“cout<<funcpoint(i,j)<<endl;”编译时会报错。

(3)一旦给函数提供了正常参数,默认参数就不起作用了,例如:testfunc(3,4,fc);

如果给函数模板testfunc提供不是类包含的默认函数,也保证代码testfunc(3,4);能正常运行,代码如下:

template<typename T, typename F = funType>
void testfunc(const T& i, const T& j, F funcpoint = fc)
{
     cout << funcpoint(i, j) << endl;
}

默认模板参数F是一个函数指针类型(funType),函数参数funcpoint=fc中的fc是函数名,代表耗时首地址。

2022.08.11结。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值