在我学习数据结构的时候,选用了《数据结构(用面向对象方法与C++语言描述)》殷人昆 编著 这本教材。这本书代码较老有一些错误,好多是无法直接在新版本编译器下实现的。

当时我C++模板学的不是很好,比葫芦画瓢照着书上的代码敲时遇到了关于函数模板的问题。以下代码为仿照书上的代码写的,类似这种问题在书上还有很多。

问题引入:

#include<iostream>
#include<cassert>
using namespace std;
template <class T>
struct stackNode//链式栈结点
{T date;stackNode<T>*link;stackNode() {}stackNode(const T&x, stackNode<T>*next = NULL) :date(x), link(next) {}
};template <class T>
class linkStack//链式栈
{//重载<<运算符输出栈中元素friend ostream& operator<<(ostream&os, linkStack<T>&st);//此处出现问题
private:stackNode<T> *top;
public:linkStack() :top(NULL) {}~linkStack() { clear(); }//清空栈bool empty()const { return (top == NULL) ? true : false; }void push(const T&x) { top = new stackNode<T>(x, top); assert(top); }bool pop(T&x){if (empty())return false;x = top->date;stackNode<T>*p = top;top = top->link;delete p;return true;}int  size()const{int n = 0;for (stackNode<T>*k = top; k != NULL; n++, k = k->link);return n;}void clear() { stackNode<T>*p; while (top != NULL) { p = top; top = top->link; delete p; } }
};
template<class T>
ostream& operator<< (ostream&os, linkStack<T>&st)
{if (st.top == NULL)cout << "栈中没有元素" << endl;else{int i; stackNode<T>*p;for (p = st.top, i = 0; p != NULL; p = p->link, i++)os << i << ":" << p->date << endl;}return os;
}int main()
{linkStack<int> lin;int i;int tmp;for (i = 0; i<10; i++)lin.push(i);cout << "栈中有元素:" << lin.size() << endl;for (i = 0; i<5; i++){lin.pop(tmp);cout << i << ":" << tmp << " ";}cout << endl;cout << "栈中有元素:" <<lin.size() << endl;cout << lin << endl;system("pause");return 0;
}

在vs2017上运行报错

需要注意的是大多数模板编译错误在实例化期间报告,也就是好多错误可能在链接的时候才报,这给了我们调试程序一定难度。

在带有gcc编译器的IDE上运行同样会报错,但给出代码的警告提示,可以帮助我们知道错在哪里

||=== Build: Debug in temp (compiler: GNU GCC Compiler) ===|
|17|warning: friend declaration 'std::ostream& operator<<(std::ostream&, linkStack<T>&)' declares a non-template function [-Wnon-template-friend]|
|17|note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)
它的意思是说

friend ostream& operator<<(ostream&os, linkStack<T>&st);

这里声明了一个非模板友元函数,如果这不是你想要的,确保函数模板已经声明了,并且在函数名后面加<>。

为了弄清这个问题,我们需要知道,模板的友元分三类:非模板友元、约束模板友元、非约束模板友元。(这里模板既可指函数模板也可指类模板,以下以函数模板为例)

1.非模板友元:

在模板类中将一个常规函数(类)声明为友元。

#include<iostream>
#include<string>
using namespace std;
template<typename T>
class HasFriend
{
public:friend void counts();//counts()  reports(HasFriend<T>&)一定是非模板函数friend void reports(HasFriend<T>&);HasFriend(const T&i) :item(i) { ct++; }
private:T item;static int ct;
};
template<typename T>
int HasFriend<T>::ct = 0;//静态成员变量类外初始化
void counts()
{cout << "int count:" << HasFriend<int>::ct << endl;cout << "double count:" << HasFriend<double>::ct << endl;
}
//HasFriend<int>类的非模板友元函数
void reports(HasFriend<int>&a)
{cout << "HasFriend<int>:" << a.item << endl;
}
//HasFriend<double>类的非模板友元函数
void reports(HasFriend<double>&a)
{cout << "HasFriend<double>:" << a.item << endl;
}
int main()
{HasFriend<string>de("0");cout << "No object ";counts();HasFriend<double>d(3.1);cout << "After d declared  ";counts();HasFriend<int>i(4);cout << "After i declared  ";counts();reports(d);reports(i);return 0;
}

这里每声明一个实例化对象,编译器就生成对应的类,类中包含对应的友元函数声明,但是不会生成对应的友元函数。本段代码中带HasFriend<int>参数的report()将成为HasFriend<int>类的友元。同样,带HasFriend<double>参数的report()将是HasFriend<int>参数的report()的一个重载函数,是Hasfriend<double>类的友元。

注意,这里类中friend void counts()友元函数声明并没有把它声明为模板函数,而只是使用了一个模板做参数,这意味着必须为要使用的友元定义显示具体化,同时也指出了void counts()为非模板函数,后面不可以把void counts()定义成模板函数。因此,问题引入中,类中已经把重载<<运算符的函数声明为非模板友元函数,后面定义时有把它定义成模板函数显然是错误的。

这段程序中,该模板有一个静态成员ct,这意味着这个类的每一个特定的具体会都有自己的静态成员,这个从程序执行结果中可以看出。

2.模板类的约束模板友元:(一对一友好关系)(VC++ 6.0不支持)

可以修改前一个程序,使友元函数本身成为模板,也就是使类的每一个具体会都获得与友元匹配的具体化。它以下包含三步。

(1)在类定义的前面声明每个约束模板友元函数。

(注意:网上相关的教程好多没有此步骤,不加这个有的编译器能通过,有的不能 )C++ Primer上指出:想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数(When we want to restrict friendship to a specific instantiation, then the class or function must have been declared before it can be used in a friend declaration:):想要将友元关系限定在特定的实例化上,则相关的类或函数必须在其友元声明之前进行声明。

例如:template<typename T> void counts();
template<typename T>  class HasFriend;
template<typename T> void reports(HasFriend<T>&);

(2)在类中将模板声明为友元。例如:

template<typename T>
class HasFriend
{
public:
    friend void counts<T>();//counts()函数没有参数,因此必须使用模板参数语法<T>来指明具体化。
    friend void reports<>(HasFriend<T>&);/*声明中<>指出这是模板具体化,对于report(),<>中T可以省略,因为可以从函数参数推断出模板类型参数*/
    .......
};

(3)为友元提供模板定义。例如:

template<typename T>
void counts(){cout << "template size:" << sizeof(HasFriend<T>) << endl;}
template<typename T>void reports(HasFriend<T>&a){ cout << "template:" << a.item << endl;}

注意:count()函数调用没有可被编译器用来推断出所需具体化的函数参数,所以这些调用需要使用count<T>指明具体化,例如count<int>()。但对于report()调用,编译器可以从参数类型推断出要使用的具体化,不必使用<>格式指明。例如:report(hfi2)或者report<int>(hfi2)。

3.模板类的非约束模板友元(通用和特定的模板友好关系)

模板类的非约束模板友元函数(类)通过在类内部声明模板,可以创建非约束友元函数(类),即每个模板函数(类)具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类型参数是不同的。例如以下程序就使用了非约束模板友元函数。

#include<iostream>
using namespace std;
template<typename T>
class ManyFriends
{
private:T item;
public:ManyFriends(const T&i):item(i){}template<typename C, typename D>friend void show(C&c, D&d);
};
template<typename C, typename D>
void show(C&c, D&d)
{cout << c.item << ", " << d.item << endl;
}
int main()
{ManyFriends<int>hfi1(10);ManyFriends<int>hfi2(20);ManyFriends<double>hfdb(10.5);cout << "hfi1,hfi2: ";show(hfi1, hfi2);cout << "hfdb,hfi2: ";show(hfdb, hfi2);//system("pause");return 0;
}

需要注意的是   void show(C&c, D&d)函数是所有ManyFriends具体化的友元,这个函数可以访问所有ManyFriends具体化产生的类。

对于问题引入中的程序,可以使用模板类的约束模板友元函数,把友元函数声明改为如下声明,并在类前增加前置声明。

template<class T>class linkStack;
template<class T>ostream& operator<<(ostream&os, linkStack<T>&st);
friend ostream& operator<<<>(ostream&os, linkStack<T>&st);

或者也可把友元函数定义为模板类的非约束模板友元函数。对友元的定义做如下修改。

template<class T> friend ostream& operator<<(ostream&os, linkStack<T>&st);

到这里,这个问题就成功解决了。我又想,为何这本书以及其他参考资料上都有好多现在看起来类似错误的代码。于是我便把那段gcc和vc++2017都无法编译通过的代码放到VC++ 6.0上编译运行,还是失败。最后把头文件引用改为#include<iostream.h>发现竟然编译成功,这完全出乎我意料。原来这本书和网上的错误代码好多是VC++ 6.0时期的代码,VC++ 6.0对C++标准库支持不是很好。

参考资料:Stanley B. Lippman  C++ Primer 中文版(第五版)北京:电子工业出版社

Stephen Prata  C++ Primer Plus 中文版(第六版)北京:人民邮电出版社

https://blog.csdn.net/lychee007/article/details/4428161

当类模板遇到了友元函数相关推荐

  1. C++模板学习02(类模板)(类模板语法、类模板与函数模板的区别、类模板中的成员函数创建时机、类模板对象做函数参数、类模板与继承、类模板成员函数类外实现、类模板分文件编写、类模板与友元)

    C++引用详情(引用的基本语法,注意事项,做函数的参数以及引用的本质,常量引用) 函数高级C++(函数的默认参数,函数的占位参数,函数重载的基本语法以及注意事项) C++类和对象-封装(属性和行为作为 ...

  2. 深入篇【C++】类与对象:友元函数与友元类

    深入篇[C++]类与对象:友元函数与友元类 ①.提出问题:重载operator<< ②.解决问题:友元 Ⅰ.友元函数 [特点] Ⅱ.友元类 [特点] ③.总结问题 ①.提出问题:重载ope ...

  3. C++ - 类模板(class template)友元(friend) 的 全部六种形式 及 代码

    类模板(class template)友元(friend) 的 全部六种形式 及 代码 版权所有, 禁止转载, 如有需要, 请站内联系; 本文地址: http://blog.csdn.net/caro ...

  4. 3-8:类与对象下篇——友元函数、友元类和内部类

    文章目录 一:友元函数 (1)友元函数 (2)友元类 二:内部类 一:友元函数 C++运算符重载为我们带来了极大的便利.因此,对于日期类这样的自定义类型,就可以重载运算符<<,然后使用co ...

  5. 类模板中的友元,友元类

    一:让类模板的某个实例成为友元. #include <iostream> #include <vector> #include <list> using names ...

  6. C++ 类模板二(类模版与友元函数)

    http://www.cnblogs.com/zhanggaofeng/p/5661829.html //类模版与友元函数 #include<iostream> using namespa ...

  7. 类模板,多种类型的类模板,自定义类模板,类模板的默认类型,数组的模板实现,友元和类模板,友元函数,类模板与静态变量,类模板与普通类之间互相继承,类模板作为模板参数,类嵌套,类模板嵌套,类包装器

     1.第一个最简单的类模板案例 #include "mainwindow.h" #include <QApplication> #include <QPush ...

  8. 学习笔记-----C++模板类中友元函数重载输出运算符时提示无法解析的外部符号解决方案

    今天在写单向链表实现线性表的时候打算重载输出运算符用来将线性表输出,结果无奈又遇到了问题. 大致代码如下 <pre name="code" class="cpp&q ...

  9. 【C++】模板类的友元函数

    模板类友元函数 模板类的友元函数 参考:https://blog.csdn.net/dreamer_lhs/article/details/53580088 区分:友元是否为函数模板 非模板友元 约束 ...

最新文章

  1. C 多线程编程之在类中使用多线程(thread)的方法
  2. Java Script 之 Promise
  3. OpenCV学习之路之OpenCV安装(VS2013版)
  4. 安装es怎么在后台运行_ES备份索引数据到阿里云OSS
  5. Python Django 查询单个对象API
  6. Linux多线程编程(不限Linux)
  7. F - Colorful Tree(LCA,树上差分,离线处理)
  8. sql:数据操作语言dml
  9. 圣诞主题的图标素材,为节日做好准备
  10. OpenSSL密码库算法笔记——第0章 大整数的表示及相关函数
  11. 栅栏密码--Python解密脚本
  12. 第二章:JAVA编程基础
  13. 如何在TransCAD中制作美观的地图
  14. Spring Security:自动登录(降低安全风险)
  15. 机械革命无法使用U盘启动linux,机械革命笔记本bios设置u盘启动教程
  16. AlphaTensor横空出世!打破矩阵乘法计算速度50年纪录,DeepMind新研究再刷Nature封面,详细算法已开源...
  17. 全球与中国滴眼液和润滑剂市场深度研究分析报告
  18. elasticSearch API
  19. 01- 机器学习经典流程 (中国人寿保费项目) (项目一)
  20. 3D打印显神威:世界首颗3D打印卫星将入轨

热门文章

  1. 个人网站选择支付宝api
  2. JavaScript 编程精解 中文第三版 九、正则表达式
  3. 研究生数学建模竞赛-无人机在抢险救灾中的优化应用
  4. 对话政企CIO:融合对企业网络通信的新意义
  5. 【matlab】雷达成像系列 之 BP(BackProjection,后向投影) 成像算法
  6. 深入linux内核架构--虚拟文件系统VFS
  7. Nginx - 静态网站;负载均衡;静态代理;动静分离;虚拟主机
  8. informatica session中bulk和normal模式
  9. 鸿湖万联与龙芯中科共建“芯片+操作系统”全自主产业生态链
  10. 笔记本CPU低压和标压有什么区别?