demo 1

#include <iostream>
#include <cstdio>
using namespace std;

//template <typename T>
class Complex
{
	friend ostream &operator << (ostream &out, Complex &c2);

public:
	Complex(int a, int b) : a(a), b(b) {}

	Complex operator + (Complex &c2)
	{
		Complex tem(a + c2.a, b + c2.b);
		return tem;
	}

	void printCom()
	{
		cout << a << " + " << b << "i\n";
	}
private:
	int a;
	int b;
};

ostream &operator << (ostream &out, Complex &c2)
{
	out << c2.a << " + " << c2.b << "i\n";
	return out;
}

int main()
{
	Complex c1(1, 2);
	c1.printCom();
	Complex c2(3, 4);
	c2.printCom();
	Complex c3 = c1 + c2;
	c3.printCom();

	cout << c3 << endl;

	return 0;
}

上述是一个简单的复数类,并重载了+运算符和 << 运算符。

下面拓展,变成模板类:

demo 2

#include <iostream>
#include <cstdio>
using namespace std;

template <typename T>
class Complex
{
	friend Complex& mySub(Complex &c1, Complex &c2) // 写在里面没什么问题
	{
		Complex tmp(c1.a - c2.a, c1.b - c2.b);
		return tmp;
	}

	friend ostream &operator << (ostream &out, Complex &c2)
	{
		out << c2.a << " + " << c2.b << "i\n";
		return out;
	}

public:
	Complex(T a, T b) : a(a), b(b) {}

	Complex operator + (Complex &c2)
	{
		Complex tem(a + c2.a, b + c2.b);
		return tem;
	}

	void printCom()
	{
		cout << a << " + " << b << "i\n";
	}
private:
	T a;
	T b;
};

// 运算符重载的正规写法
// 重载 << >> 只能用友元函数,其他运算符重载都要写成成员函数,不要滥用友元函数

/* 这部分定义必须写进类的内部
ostream &operator << (ostream &out, Complex &c2)
{
	out << c2.a << " + " << c2.b << "i\n";
	return out;
}
*/

int main()
{
	// 需要把模板类具体化之后才能定义对象,C++编译器需要分配内存
	Complex<int> c1(1, 2);
	c1.printCom();
	Complex<int> c2(3, 4);
	c2.printCom();
	Complex<int> c3 = c1 + c2;
	c3.printCom();
	cout << c3 << endl;

	// 滥用友元函数
	{
		Complex<int> c3 = mySub(c1, c2);
		cout << c3;

	}

	return 0;
}

这样看起来问题也不大。

继续,把所有成员函数都放到类外部,先还是写在同一个cpp文件中:

demo 3

#include <iostream>
#include <cstdio>
using namespace std;

template <typename T>
class Complex; // 解决mySub友元函数的滥用

template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2); // 解决mySub友元函数的滥用

template <typename T>
class Complex
{
	// 这个友元函数是滥用
	friend Complex<T> mySub<T>(Complex<T> &c1, Complex<T> &c2); // 写在里面没什么问题

	// 这样55行会报错
	//friend ostream &operator << (ostream &out, Complex &c2);

	// 解决方案
	friend ostream &operator << <T> (ostream &out, Complex &c2);

public:
	Complex(T a, T b);
	Complex operator + (Complex &c2);
	void printCom();

private:
	T a;
	T b;
};

// 构造函数拿到写在类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
	this->a = a;
	this->b = b;
}

template <typename T>
void Complex<T>::printCom()
{
	cout << a << " + " << b << "i\n";
}

// 注意函数参数和函数返回值都需要进行类型具体化
template <typename T>
Complex<T> Complex<T>::operator + (Complex<T> &c2)
{
	Complex tem(a + c2.a, b + c2.b);
	return tem;
}

// 报错的本质:模版是两次编译运行的,第一次生成的函数头和第二次生成的函数头不一样
// 友元函数实现 << 运算符重载
template <typename T>
ostream &operator << (ostream &out, Complex<T> &c2)
{
	out << c2.a << " + " << c2.b << "i\n";
	return out;
}
// 报错
/*
1>templateComplex2.obj : error LNK2019: 无法解析的外部符号
"class std::basic_ostream<char,struct std::char_traits<char> >
& __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> >
&,class Complex<int> &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Complex@H@@@Z),
该符号在函数 _main 中被引用
*/

//////////////////////////////////////////////////////
// 滥用友元函数
template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
	Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
	return tmp;
}
// 报错。。。。。。

// 解决方案,做前置声明

int main()
{
	// 需要把模板类具体化之后才能定义对象,C++编译器需要分配内存
	Complex<int> c1(1, 2);
	c1.printCom();
	Complex<int> c2(3, 4);
	c2.printCom();
	Complex<int> c3 = c1 + c2;
	c3.printCom();
	cout << c3 << endl;

	// 滥用友元函数
	{
		Complex<int> c3 = mySub(c1, c2);
		cout << c3;

	}

	return 0;
}

demo 3出现了几个错误,代码中都注释了,尤其友元函数的滥用一定要注意,千万别再不能用友元函数的地方用友元函数,demo 3中出现的问题还都解决了,可是当把类写到.cpp和.h文件中还会出现新的问题,先总结在同一文件下:

所有的类模板函数写在类的外部,在一个cpp中

//构造函数 没有问题
//普通函数 没有问题
//友元函数:用友元函数重载 << >>
//	friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ;
//友元函数:友元函数不是实现函数重载(非 << >>)
//1)需要在类前增加 类的前置声明 函数的前置声明
template<typename T>
class Complex;
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);

//2)类的内部声明 必须写成:
friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
//3)友元函数实现 必须写成:
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
	Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
	return tmp;
}
//4)友元函数调用 必须写成
Complex<int> c4 = mySub<int>(c1, c2);
cout << c4;

结论:友元函数只用来进行左移友移操作符重载。

归纳以上的介绍:可以这样声明和使用类模版:

1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。

2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。

3) 在类声明前面加入一行,格式为:

template <class 虚拟类型参数>

如:

template <class numtype> //注意本行末尾无分号

class Compare

{…}; //类体

4) 用类模板定义对象时用以下形式:

类模板名<实际类型名> 对象名;

类模板名<实际类型名> 对象名(实参表列);

如:

Compare<int> cmp;

Compare<int> cmp(3,7);

5) 如果在类模板外定义成员函数,应写成类模板形式:

template <class 虚拟类型参数>

函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}

关于类模板的几点说明:

1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:

template <class T1,class T2>

class someclass

{…};

在定义对象时分别代入实际的类型名,如:

someclass<int,double> obj;

2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。

3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

类模板语法知识体系梳理(包含大量常犯错误demo,尤其滥用友元函数的错误)的更多相关文章

  1. C++ 类模板基础知识

    类模板与模板类 为什么要引入类模板:类模板是对一批仅仅成员数据类型不同的类的抽象,程序员只要为这一批类所组成的整个类家族创建一个类模板,给出一套程序代码,就可以用来生成多种具体的类,(这类可以看作是类 ...

  2. Android7_安卓的知识体系梳理

    最近梳理了一下安卓的知识体系,先构建一个整体性的认知,也作为以后的学习路线的依据. [一.从原理角度出发]1.Activity生命周期和启动模式2.View的事件体系与工作原理3.四大组件的工作过程4 ...

  3. C++_进阶之函数模板_类模板

     C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...

  4. C++复习:函数模板和类模板

    前言 C++提供了函数模板(function template).所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表.这个通用函数就称为函数模板.凡是函数体 ...

  5. c++11-17 模板核心知识(二)—— 类模板

    类模板声明.实现与使用 Class Instantiation 使用类模板的部分成员函数 Concept 友元 方式一 方式二 类模板的全特化 类模板的偏特化 多模板参数的偏特化 默认模板参数 Typ ...

  6. C++语法小记---类模板

    类模板 类模板和函数模板类似,主要用于定义容器类 类模板可以偏特化,也可以全特化,使用的优先级和函数模板相同 类模板不能隐式推倒,只能显式调用 工程建议: 模板的声明和实现都在头文件中 成员函数的实现 ...

  7. 从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!

    前言 见解有限,如有描述不当之处,请帮忙指出,如有错误,会及时修正. 为什么要梳理这篇文章? 最近恰好被问到这方面的问题,尝试整理后发现,这道题的覆盖面可以非常广,很适合作为一道承载知识体系的题目. ...

  8. github上最全的资源教程-前端涉及的所有知识体系

    前面分享了前端入门资源汇总,今天分享下前端所有的知识体系. 个人站长对个人综合素质要求还是比较高的,要想打造多拉斯自媒体网站,不花点心血是很难成功的,学习前端是必不可少的一个环节, 当然你不一定要成为 ...

  9. github上最全的资源教程-前端涉及的所有知识体系【转】

    github上最全的资源教程-前端涉及的所有知识体系[转自:蓝猫的博客] 综合类 综合类 地址 前端知识体系 http://www.cnblogs.com/sb19871023/p/3894452.h ...

随机推荐

  1. 使用Java可以做得一些事

    安卓 Web JSP使用Echarts的最简单的例子 微信 wechat4j weixin-java-tools weixin4j 网络服务器

  2. 为什么内部类访问的外部变量需要使用final修饰

    因为生命周期的原因.方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象.首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而 ...

  3. 重温java基础

    Java标识符 Java所有的组成部分都需要名字.类名.变量名以及方法名都被称为标识符. 关于Java标识符,有以下几点需要注意: 所有的标识符都应该以字母(A-Z或者a-z),美元符($).或者下划 ...

  4. Android简易实战教程--第三十九话《Chronometer实现倒计时》

    Android提供了实现按照秒计时的API,今天就是用这个API实现简单的倒计时. 来个布局: <?xml version="1.0" encoding="utf- ...

  5. Gradle 1.12用户指南翻译——第五十二章. Maven 插件

    本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见:http://blog.csdn.net/column/details/gradle-translation.html翻译项目请关注Github上 ...

  6. Excel 数据验证宏

    Sub 宏1() ' ' 宏1 宏 ' ' With Selection.Validation .Delete .Add Type:=xlValidateList, AlertStyle:=xlVal ...

  7. github pages + Hexo + 域名绑定搭建个人博客

    环境 Windows 10(64 位) Git-2.7.4-64-bit node-v4.4.7-x64 如果上述软件已经安装的,跳过,没有安装的下载安装. 1,git下载安装(https://git ...

  8. 剑指Offer——笔试题+知识点总结

    剑指Offer--笔试题+知识点总结 情景回顾 时间:2016.9.23 12:00-14:00 19:00-21:00 地点:山东省网络环境智能计算技术重点实验室 事件:笔试 注意事项:要有大局观, ...

  9. 关于React Native 安卓首屏白屏优化

    问题描述 在android中,当点击某个rn模块的入口按钮,弹出rn的activity到rn的页面展现出来的过程中,会有很明显的白屏现象,不同的机型不同(cpu好的白屏时间短),大概1s到2s的时间. ...

  10. FFmpeg源代码简单分析:内存的分配和释放(av_malloc()、av_free()等)

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...