const指针与函数模板的小问题

    最近在写c++pp的一道练习题时发现了一些有趣的问题,在此分享一下。

    题目考察的主要是函数模板及其显式具体化相关知识,在此为节省空间就不放出来了,以下是我最开始写成的样子

#include <iostream>
#include <cstring>


using namespace std;

template<typename T> T maxn (T [], int);
template<> char * maxn (char * [], int);

int main()
{
	int a[6]{0,1,2,3,4,-5,};
	double b[4]{4.242,2.0454,45,3.46};
	char * c[5]{"asddad415","fa5f4d5sd","vdvd2vd2v2dvd2vd2x","svs","46dsv4dsvsv"};
	cout << " The biggest value in array a: " << maxn(a,6) << endl;
	ios_base::fmtflags initial;
	initial = cout.setf(ios_base::showpoint);
	cout << " The biggest value in array b: " << maxn(b,4) << endl;
	cout.setf(initial);
	cout << " The biggest value in array c: " << maxn(c,5) << endl;
	return 0;
}

template<typename T> T maxn (T a[], int lenth)
{
	T ret = a[0];
	for (int i = 1;i<lenth;i++)
	{
		if(ret < a[i])
			ret = a[i];
	}

	return ret;
}

template<> char * maxn (char * a[], int num)
{
	char *ret = a[0];
	for(int i = 1;i<num;i++)
	{
		if(strlen(ret) < strlen(a[i]))
			ret = a[i];
	}

	return ret;
}

    编译通过了,没有error,但奇怪的是出来一堆warning


    原来是因为我把一组常量的地址赋给了非const指针,这在ISO C++标准里是被禁止的。很明显,虽然在这个程序里这样做并不会出现什么问题,然而如果添加如*c[0] = "0"的语句的话,这在语法上是行得通的,然而造成的后果也是显而易见的。
    看来此处是有必要进行修改的。改法无非两种,第一种很简单,使用const_cast进行类型转换就搞定了,这里不再赘述;至于第二种,那就是把指针变量声明为指向常量的就行了。


const char * c[5]{"asddad415","fa5f4d5sd","vdvd2vd2v2dvd2vd2x","svs","46dsv4dsvsv"};

    嗯,这样就一个warning也没有了,运行一下试试


    奇怪了,明明一个错误也没有,为什么结果反倒不正确了?
    在此进行debug后(此处省略过程),终于发现问题出在哪里了:第三次,也就是用指针数组做参数的函数调用,call的是模板而非显式具体化的形式。
    也就是说编译器没有调用我们期待的char *的具体化函数,而是选择自己进行隐式具体化一个更加匹配的形式,调用了模板函数的定义。在模板定义里:

template<typename T> T maxn (T a[], int lenth)
{
	T ret = a[0];
	for (int i = 1;i<lenth;i++)
	{
		if(ret < a[i])
			ret = a[i];
	}

	return ret;
}

    此处是对数组里元素的值进行比较,而不是我们在char*的具体化定义里的对元素的长度进行比较,因此会输出这样的结果。
    那么为什么编译器会认为我们具体化的char*函数不是最佳匹配呢?请看原型:

template<> char * maxn (char * [], int);

    原型声明参数是指向非const数据的指针数组,而实际调用中我们传递的是一个指向const数据的指针数组,编译器并不能将两者进行

精确类型匹配。再看函数模板原型:

template<typename T> T maxn (T [], int);

    显然,编译器通过匹配隐式具体化了一个const char *的定义,而非匹配的char *的具体化。

    既然如此,那我们直接显式具体化一个const char *的不就行了吗?

template<> const char * maxn (const char * a[], int num)
{
	int flag = 0;
	for(int i = 0;i<num-1;i++)
	{
		for(int j = i+1;j<num-1-i;j++)
		{
			if(strlen(a[i]) < strlen(a[i+j]))
			{
				flag = i+j;
				i = flag-1;
				break;
			}
		}		
	}

	return a[flag];
}


    如此,编译运行


    搞定。

    然而,在这个过程中我又产生了一个新的想法:如果将模板函数本身就声明为传递const 数组,这样是否可行呢?因为在本题中我们并不需要对数据进行修改,因此添加const也是有一定必要的。说做就做

template<typename T> T maxn (const T [], int);
template<> const char * maxn (const char * [], int);

    编译试试


    嗯?为什么 我们的具体化原型会找不到匹配的模板呢?再仔细看看两个的区别,原来又是参数的问题:模板函数里声明的是const T [],也就是不可修改的T数组,不可修改指的是数组的地址和指向的值都不可修改。而具体化原型里是const char * [],也就是指向常量的指针的数组,但是指针本身仍是变量,是可以进行修改的,想要让指针本身也不能修改,还需要在*后加上一个const

template<typename T> const T maxn (const T [], int);
template<> const char * const maxn (const char * const [], int);

    以上是修改后的原型,相应的定义里也同样进行修改。编译运行,没有错误。这样就完成了修改。

    以上只是模板函数及其显式具体化与const指针的基础知识,看起来十分简单,但实际在基础并不牢靠的我手里花费了大概半天才搞清楚其中的道理,期间我也曾尝试过使用typedef将char *替换为另一个名字,这样做导致的是这种const数组确实其地址以及数组里的元素本身,即指针本身是不可修改的,而指针所指向的数据是可以进行修改的,但我们知道这个别名替代的是指针,在指针前面添加const也就说明指针指向的值不可修改,因此用别名声明一个const指针数组在编译器看来是没有语法问题的,但如果试图将指针所指向的值进行修改,程序就会崩溃;而因为typedef与预编译指令不同不是简单的替换,我们也不能在替换后的别名后添加const来限制。同时在本题中,也会因为编译器认为没有匹配的显式具体化原型而隐式具体化一个新的定义,导致的结果与前面所讲的一样,因此typedef的做法是不可取的。

    最后还有一种简单粗暴的方法,要什么显式具体化,直接重载一个传递const char *的maxn函数就行了。当然,这就不再本篇的讨论范围里了。

    本篇博客主要是记录我在练习中犯过的一些错误,以示警钟,作为一个语言初学者,文中的错误及暧昧之处较多,望指正。

    以上。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值