C++提高1 【泛型编程】函数模板 类模板

【本文谢绝转载】

【泛型编程】

函数模板

为什么会有函数模板

现象:

函数的业务逻辑一样

函数的参数类型不一样

【最常用】函数模板  显式的调用

【不常用】类型推导

多个参数,参数定义了必须要用

函数模板,实现int类型数组,char字符串排序:

函数模板 与 普通函数的本质区别

函数模板 和 普通函数在一起 的调用型研究:

C++是如何支持函数模板机制的?

函数模板机制结论

类模板

类模板的定义

类模板做函数的参数

类模板的派生成普通类

模板类的派生成模板类

复数类,所有函数都写在类的内部,运算符重载热身

复数类,所有函数都写在类的内部, 类模板

【演示】滥用友元函数的后果--正常的代码准备

复数类,所有函数都写在类的内部(一个CPP中):问题抛出

当模板函数遇到友元函数,问题解决:

滥用友元函数的后果

【结论】:不需要友元函数,不要用友元函数

【结论】模板类的cpp文件 与 .h头文件分开写的时候,要把cpp文件也包含进来

当类模板中有static成员变量时;从类模板的实质分析,编译器你会自动为我们写成两个类

类模板 数组案例

类模板 结构体案例

作业:

-------------------------------------------------------------------------

【为什么会有函数模板】

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
using namespace std;
void swap1(int &a,int &b)
{
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
}

void swap2(char &a,char &b)
{
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
}

int main()
{
	int a = 10;
	int b = 20;
	swap1(a,b);
	cout <<a <<" "<< b << endl;
	char c = ‘A‘;
	char d = ‘B‘;
	swap2(c,d);
	cout <<c <<" "<< d << endl;

	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
20 10
B A
[email protected]://990487026.blog.51cto.com~/c++$

现象:

函数的业务逻辑一样

函数的参数类型不一样

【最常用】函数模板  显式的调用

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
using namespace std;

//template  //告诉编译器,我要开始泛型编程了
template <typename T>
void myswap(T &a,T &b)
{
	a = a  ^ b;
	b = a  ^ b;
	a = a  ^ b;
}

int main()
{

	int a = 10;
	int b = 20;
	cout <<a <<" "<< b << endl;
	myswap<int>(a,b);
	cout <<a <<" "<< b << endl;
	char c = ‘A‘;
	char d = ‘B‘;
	cout <<c <<" "<< d << endl;
	myswap<char>(c,d);
	cout <<c <<" "<< d << endl;

	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
10 20
20 10
A B
B A
[email protected]://990487026.blog.51cto.com~/c++$

【不常用】类型推导

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
using namespace std;

//template  //告诉编译器,我要开始泛型编程了
template <typename T>
void myswap(T &a,T &b)
{
	a = a  ^ b;
	b = a  ^ b;
	a = a  ^ b;
}

int main()
{

	int a = 10;
	int b = 20;
	cout <<a <<" "<< b << endl;
	myswap(a,b);
	cout <<a <<" "<< b << endl;
	char c = ‘A‘;
	char d = ‘B‘;
	cout <<c <<" "<< d << endl;
	myswap(c,d);
	cout <<c <<" "<< d << endl;

	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
10 20
20 10
A B
B A
[email protected]://990487026.blog.51cto.com~/c++$

多个参数,参数定义了必须要用

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
using namespace std;

//template  //告诉编译器,我要开始泛型编程了
template <typename T,typename str>
void myswap(T &a,T &b,str s)
{
	a = a  ^ b;
	b = a  ^ b;
	a = a  ^ b;
	cout << s << endl;
}

int main()
{

	int a = 10;
	int b = 20;
	cout <<a <<" "<< b << endl;
	myswap(a,b,"Hello");
	cout <<a <<" "<< b << endl;
	char c = ‘A‘;
	char d = ‘B‘;
	cout <<c <<" "<< d << endl;
	myswap(c,d,"Linux");
	cout <<c <<" "<< d << endl;

	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
10 20
Hello
20 10
A B
Linux
B A
[email protected]://990487026.blog.51cto.com~/c++$

函数模板,实现int类型数组,char字符串排序:

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
template <typename T1,typename T2>
void select_sort(T1 array[],T2 len)
{
	if(array == NULL)
	{
		return ;
	}
	for(int i=0;i<len-1;i++)
	{
		int min = i;
		for(int j=i+1;j<len;j++)
		{
			if(array[min] > array[j])
			{
				min = j ;
			}
		}
		int tmp   = array[i];
		array[i]  = array[min];
		array[min]= tmp;
	}
}

template <typename T1,typename T2>
void print_arr(T1 array[],T2 len)
{
        if(array == NULL)
        {
                return ;
        }
        for(int i=0;i<len;i++)
        {
            cout << array[i]  << " ";
        }
	cout << "\n";
}

int main()
{
	int arr[]= {2,1,4,3,6,5,8,7,0,9};
	int n =sizeof(arr)/sizeof(int);
	select_sort<int ,int>(arr,n);
	print_arr<int,int>(arr,n);

	char str[]= "Hello,Linux!";
	int m = strlen(str);
	select_sort<char ,int>(str,m);
	print_arr<char ,int>(str,m);
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
0 1 2 3 4 5 6 7 8 9 
! , H L e i l l n o u x 
[email protected]://990487026.blog.51cto.com~/c++$

函数模板 与 普通函数的本质区别

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

//swap 在 C++是个库函数
template <typename T>
void my_swap(T &a,T &b)
{
	cout <<a << " " <<b<< " 我是函数模板\n";
}

void my_swap(int a,char b)
{
	cout << a << " " << b << " 我是普通函数\n";
}
int main()
{
	int a = 69;
	char c = ‘A‘;
	my_swap(a,c);
	my_swap(c,a);	//会隐式的自动转换
	my_swap(a,a);	//[本质的区别]函数模板,将严格按照类型匹配,不会自动转换
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
69 A 我是普通函数
65 E 我是普通函数
69 69 我是函数模板
[email protected]://990487026.blog.51cto.com~/c++$

函数模板 和 普通函数在一起 的调用型研究:

【补充】在同一作用域,可以发生函数重载

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
/*
	1 函数模板可以像普通函数一样被重载
	2 C++编译器优先考虑普通函数
	3 如果函数模板可以产生一个更好的匹配,那么选择模板
	4 可以通过空模板实参列表的语言限定编译器只通过模板匹配
*/

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

template <typename T>
T Max(T a,T b)		//不要引用类型
{
	cout <<a << " " <<b<< " 我是2号函数模板\n";
	return a>b?a:b;
}
template <typename T>
T Max(T a,T b,T c)
{
	cout <<a << " " <<b<< " 我是3号函数模板,会调用两次max函数\n";
	return Max(Max(a,b),c);
}

int Max(int a,int b)
{
	cout << a << " " << b << " 我是普通函数\n";
	return a>b?a:b;
}

int main()
{
	int a = 1;
	int b = 2;
	Max(a,b);	//C++编译器优先考虑普通函数
	Max<>(a,b);	//可以通过空模板实参列表的语言限定编译器只通过模板匹配
	Max(3.5,4.4);	//优先选择模板函数,因为模板函数能产生更好的匹配
	Max(3.5,4.4,6.8);//只有函数模板能匹配
	Max(‘A‘,66);	//普通函数可以进行隐式转换

	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
1 2 我是普通函数
1 2 我是2号函数模板
3.5 4.4 我是2号函数模板
3.5 4.4 我是3号函数模板,会调用两次max函数
3.5 4.4 我是2号函数模板
4.4 6.8 我是2号函数模板
65 66 我是普通函数
[email protected]://990487026.blog.51cto.com~/c++$

C++是如何支持函数模板机制的?

C++:
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

//swap 在 C++是个库函数
template <typename T>
void my_swap(T &a,T &b)
{
	T c  = 0;
	c = a;
	a = b;
	b = c;
	cout <<a << " " <<b<< " 我是函数模板\n";
}

int main()
{
	int a = 69;
	int b = 20;
	my_swap<int>(a,b);

	char c = ‘C‘;
	char d = ‘D‘;
	my_swap<char>(c,d);

	return 0;
}

编译成汇编:

	.file	"main.c"
.lcomm __ZStL8__ioinit,1,1
	.def	___main;	.scl	2;	.type	32;	.endef
	.text
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB1025:
	.cfi_startproc
	.cfi_personality 0,___gxx_personality_v0
	.cfi_lsda 0,LLSDA1025
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$32, %esp
	call	___main
	movl	$69, 28(%esp)
	movl	$20, 24(%esp)
	leal	24(%esp), %eax
	movl	%eax, 4(%esp)
	leal	28(%esp), %eax
	movl	%eax, (%esp)
LEHB0:
	call	__Z7my_swapIiEvRT_S1_
	movb	$67, 23(%esp)
	movb	$68, 22(%esp)
	leal	22(%esp), %eax
	movl	%eax, 4(%esp)
	leal	23(%esp), %eax
	movl	%eax, (%esp)
	call	__Z7my_swapIcEvRT_S1_
LEHE0:
	movl	$0, %eax
	jmp	L5
L4:
	movl	%eax, (%esp)
LEHB1:
	call	__Unwind_Resume
LEHE1:
L5:
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1025:
	.def	___gxx_personality_v0;	.scl	2;	.type	32;	.endef
	.section	.gcc_except_table,"w"
LLSDA1025:
	.byte	0xff
	.byte	0xff
	.byte	0x1
	.uleb128 LLSDACSE1025-LLSDACSB1025
LLSDACSB1025:
	.uleb128 LEHB0-LFB1025
	.uleb128 LEHE0-LEHB0
	.uleb128 L4-LFB1025
	.uleb128 0
	.uleb128 LEHB1-LFB1025
	.uleb128 LEHE1-LEHB1
	.uleb128 0
	.uleb128 0
LLSDACSE1025:
	.text
	.section .rdata,"dr"
LC0:
	.ascii " \0"
LC1:
	.ascii " \316\322\312\307\272\257\312\375\304\243\260\345\12\0"
	.section	.text$_Z7my_swapIiEvRT_S1_,"x"
	.linkonce discard
	.globl	__Z7my_swapIiEvRT_S1_
	.def	__Z7my_swapIiEvRT_S1_;	.scl	2;	.type	32;	.endef
__Z7my_swapIiEvRT_S1_:
LFB1026:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	pushl	%ebx
	subl	$36, %esp
	.cfi_offset 3, -12
	movl	$0, -12(%ebp)
	movl	8(%ebp), %eax
	movl	(%eax), %eax
	movl	%eax, -12(%ebp)
	movl	12(%ebp), %eax
	movl	(%eax), %edx
	movl	8(%ebp), %eax
	movl	%edx, (%eax)
	movl	12(%ebp), %eax
	movl	-12(%ebp), %edx
	movl	%edx, (%eax)
	movl	12(%ebp), %eax
	movl	(%eax), %ebx
	movl	8(%ebp), %eax
	movl	(%eax), %eax
	movl	%eax, (%esp)
	movl	$__ZSt4cout, %ecx
	call	__ZNSolsEi
	subl	$4, %esp
	movl	$LC0, 4(%esp)
	movl	%eax, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	%ebx, (%esp)
	movl	%eax, %ecx
	call	__ZNSolsEi
	subl	$4, %esp
	movl	$LC1, 4(%esp)
	movl	%eax, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	-4(%ebp), %ebx
	leave
	.cfi_restore 5
	.cfi_restore 3
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1026:
	.section	.text$_Z7my_swapIcEvRT_S1_,"x"
	.linkonce discard
	.globl	__Z7my_swapIcEvRT_S1_
	.def	__Z7my_swapIcEvRT_S1_;	.scl	2;	.type	32;	.endef
__Z7my_swapIcEvRT_S1_:
LFB1027:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	pushl	%ebx
	subl	$36, %esp
	.cfi_offset 3, -12
	movb	$0, -9(%ebp)
	movl	8(%ebp), %eax
	movzbl	(%eax), %eax
	movb	%al, -9(%ebp)
	movl	12(%ebp), %eax
	movzbl	(%eax), %edx
	movl	8(%ebp), %eax
	movb	%dl, (%eax)
	movl	12(%ebp), %eax
	movzbl	-9(%ebp), %edx
	movb	%dl, (%eax)
	movl	12(%ebp), %eax
	movzbl	(%eax), %eax
	movsbl	%al, %ebx
	movl	8(%ebp), %eax
	movzbl	(%eax), %eax
	movsbl	%al, %eax
	movl	%eax, 4(%esp)
	movl	$__ZSt4cout, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
	movl	$LC0, 4(%esp)
	movl	%eax, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	%ebx, 4(%esp)
	movl	%eax, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
	movl	$LC1, 4(%esp)
	movl	%eax, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	addl	$36, %esp
	popl	%ebx
	.cfi_restore 3
	popl	%ebp
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1027:
	.text
	.def	___tcf_0;	.scl	3;	.type	32;	.endef
___tcf_0:
LFB1033:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$8, %esp
	movl	$__ZStL8__ioinit, %ecx
	call	__ZNSt8ios_base4InitD1Ev
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1033:
	.def	__Z41__static_initialization_and_destruction_0ii;	.scl	3;	.type	32;	.endef
__Z41__static_initialization_and_destruction_0ii:
LFB1032:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$24, %esp
	cmpl	$1, 8(%ebp)
	jne	L9
	cmpl	$65535, 12(%ebp)
	jne	L9
	movl	$__ZStL8__ioinit, %ecx
	call	__ZNSt8ios_base4InitC1Ev
	movl	$___tcf_0, (%esp)
	call	_atexit
L9:
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1032:
	.def	__GLOBAL__sub_I_main;	.scl	3;	.type	32;	.endef
__GLOBAL__sub_I_main:
LFB1034:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$24, %esp
	movl	$65535, 4(%esp)
	movl	$1, (%esp)
	call	__Z41__static_initialization_and_destruction_0ii
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1034:
	.section	.ctors,"w"
	.align 4
	.long	__GLOBAL__sub_I_main
	.ident	"GCC: (rev2, Built by MinGW-builds project) 4.8.0"
	.def	__Unwind_Resume;	.scl	2;	.type	32;	.endef
	.def	__ZNSolsEi;	.scl	2;	.type	32;	.endef
	.def	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc;	.scl	2;	.type	32;	.endef
	.def	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c;	.scl	2;	.type	32;	.endef
	.def	__ZNSt8ios_base4InitD1Ev;	.scl	2;	.type	32;	.endef
	.def	__ZNSt8ios_base4InitC1Ev;	.scl	2;	.type	32;	.endef
	.def	_atexit;	.scl	2;	.type	32;	.endef

类模板:

类模板用于实现类所需数据的类型参数化

类模板在表示如数组、表、图等数据结构显得特别重要,

类模板的定义

类末班的使用

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
template <typename T>
class A
{
public:
	A(T a)
	{
		this->a = a;
	}
	void printf()
	{
		cout << "a= "<<a <<endl;
	}
private:
	T a;
};

int main()
{
	//类本身就是一个抽象的
	A <int>a(11);	//模板类是抽象类 必须指定具体的类型,告诉编译器给我分配多少内存
	a.printf();

	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a= 11
[email protected]://990487026.blog.51cto.com~/c++$

类模板做函数的参数

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
template <typename T>
class A
{
public:
	A(T a)
	{
		this->a = a;
	}
	void printf()
	{
		cout << "a= "<<a <<endl;
	}
private:
	T a;
};

//类模板做函数的参数
void useA(A<int> &a)//这里如果来个变量,会进行拷贝构造,引用就不会
{
	a.printf();
}

int main()
{
	A <int>  a(11);	//定义一个变量
	A <int>  b(22),c(33);
	useA(a);	//来模板做函数的参数
	useA(b);
	useA(c);

	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a= 11
a= 22
a= 33
[email protected]://990487026.blog.51cto.com~/c++$

类模板的派生成普通类

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
template <typename T>
class A
{
public:
	A(T a)
	{
		this->a = a;
	}
	void printf()
	{
		cout << "a= "<<a <<endl;
	}
protected:
	T a;
};

class B :public A<int>
{
public:
	B(int a,int b):A<int>(a)
	{
		this->b = b;
	}
	void printf()
	{
		cout << "a="<<a << " b=" << b << " \n";
	}
private:
	int b;
};

int main()
{
	B b(1,2);
	b.printf();
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a=1 b=2

模板类的派生成模板类

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
template <typename T>
class A
{
public:
	A(T a)
	{
		this->a = a;
	}
	void printf()
	{
		cout << "a= "<<a <<endl;
	}
protected:
	T a;
};

template <typename T>
class C:public A<T>
{
public:
	C(T c,T a):A<T>(a)
	{
		this->c = a;
	}
	void printf()
	{
		cout << "c=" << c<< endl;
	}
private:
	T c;
};

int main()
{
	C <int> c1(1,2); 	c1.printf();
	C <char> c2(66,65); 	c2.printf();
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
c=2
c=A
[email protected]://990487026.blog.51cto.com~/c++$

复数类,所有函数都写在类的内部,运算符重载热身

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

class Complex
{
	friend ostream& operator<<(ostream &out,Complex &c);
public:
	Complex(int a = 0,int b = 0)
	{
		this->a = a;
		this->b = b;
	}
	void printf()
	{
		cout << "a= "<<a <<" b="<<b <<endl;
	}
	Complex operator+(Complex &c2)
	{
		//cout << "this->a=" << this->a << " c2.a="<<c2.a<<endl;
		//cout << "this->b=" << this->b << " c2.b="<<c2.b<<endl;
		Complex tmp(this->a + c2.a,this->b+c2.b);
		return tmp;
	}
protected:
	int a;
	int b;
};

//为输出类的信息,实现操作符重载
ostream& operator<<(ostream &out,Complex &c)
{
	cout <<"a="<<c.a<<" b="<<c.b<< " ";
	return out;
}

int main()
{
	Complex c1(2,4);
	Complex c2(3,5);
	Complex c3 = c1 +c2;	c3.printf();
	cout << c3 << endl;	//在得不到cout的源代码下,只能使用友元函数
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a= 5 b=9
a=5 b=9 
[email protected]://990487026.blog.51cto.com~/c++$

复数类,所有函数都写在类的内部, 类模板

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

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

template <typename T>
class Complex
{
	//为输出类的信息,实现操作符重载
	friend ostream& operator<<(ostream &out,Complex &c)
	{
		cout << "进入<<操作符重载 -> ";
        	out <<"a="<<c.a<<" b="<<c.b<< " ";
        	return out;
	}
public:
	Complex (T a ,T b)
	{
		this->a = a;
		this->b = b;
	}
	void printf()
	{
		cout << "a= "<<a <<" b="<<b <<endl;
	}
	Complex operator+(Complex &c2)
	{
		//cout << "this->a=" << this->a << " c2.a="<<c2.a<<endl;
		//cout << "this->b=" << this->b << " c2.b="<<c2.b<<endl;
		Complex tmp(this->a + c2.a,this->b+c2.b);
		return tmp;
	}
protected:
	T a;
	T b;
};

/*
ostream& operator<<(ostream &out,Complex &c)
{
	cout <<"a="<<c.a<<" b="<<c.b<< " ";
	return out;
}
*/
int main()
{
	//要把模板类具体化才能分配内存
	Complex<int> c1(2,4);
	Complex<int> c2(3,5);
	Complex<int> c3 = c1 +c2;	c3.printf();
	cout << c3 << endl;	//在得不到cout的源代码下,只能使用友元函数
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a= 5 b=9
进入<<操作符重载 -> a=5 b=9 
[email protected]://990487026.blog.51cto.com~/c++$

【演示】滥用友元函数的后果--正常的代码准备

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

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

template <typename T>
class Complex
{
	//为输出类的信息,实现操作符重载
	friend ostream& operator<<(ostream &out,Complex &c)
	{
		cout << "进入<<操作符重载 -> ";
        	out <<"a="<<c.a<<" b="<<c.b<< " ";
        	return out;
	}
	friend Complex MySub(Complex& c1,Complex&c2)
	{
		cout << "进入MySub 函数  \n";
		Complex tmp(c1.a - c2.a,c1.b - c2.b);
		return tmp;
	}

public:
	Complex (T a ,T b)
	{
		this->a = a;
		this->b = b;
	}
	void printf()
	{
		cout << "a= "<<a <<" b="<<b <<endl;
	}
	Complex operator+(Complex &c2)
	{
		//cout << "this->a=" << this->a << " c2.a="<<c2.a<<endl;
		//cout << "this->b=" << this->b << " c2.b="<<c2.b<<endl;
		Complex tmp(this->a + c2.a,this->b+c2.b);
		return tmp;
	}
protected:
	T a;
	T b;
};

/*
ostream& operator<<(ostream &out,Complex &c)
{
	cout <<"a="<<c.a<<" b="<<c.b<< " ";
	return out;
}
*/
int main()
{
	//要把模板类具体化才能分配内存
	Complex<int> c1(2,4);
	Complex<int> c2(3,5);
	Complex<int> c4 = MySub(c1,c2);
	cout << c4 << endl;
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
进入MySub 函数  
进入<<操作符重载 -> a=-1 b=-1 
[email protected]://990487026.blog.51cto.com~/c++$

复数类,所有函数都写在类的内部(一个CPP中):问题抛出

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

template <typename T>
class Complex
{
	//为输出类的信息,实现操作符重载
	friend ostream& operator<<(ostream &out,Complex &c);
	friend Complex MySub(Complex& c1,Complex&c2)
	{
		cout << "进入MySub 函数  \n";
		Complex tmp(c1.a - c2.a,c1.b - c2.b);
		return tmp;
	}

public:
	Complex (T a ,T b);
	void printf();
	Complex operator+(Complex &c2);
protected:
	T a;
	T b;
};

/*
	问题的本质是:模板是两次编译,
	第一次生成的函数头 和第二次生成的不一样
*/
template <typename T>
ostream& operator<<(ostream &out,Complex &c)
{
	out <<"a="<<c.a<<" b="<<c.b<< " ";
	return out;
}

template <typename T>
//函数的返回的类型要具体化
//函数的实参也要具体化
Complex<T> Complex<T>:: operator+(Complex<T> &c2)
{
	cout << "进入外部 operator+ \n";
	//Complex<T> tmp(this->a + c2.a,this->b+c2.b);
	Complex tmp(this->a + c2.a,this->b+c2.b);
	return tmp;
}

template <typename T>
Complex<T>::Complex (T a ,T b)
{
	cout << "进入外部Complex函数 a=" <<a<<" b="<< b<< endl; ;
	this->a = a;
	this->b = b;
}

template <typename T>
void Complex<T>::printf()
{
	cout << "进入外部printf函数  ";
	cout << "a="<<a <<" b="<<b <<endl;
}

int main()
{
	Complex<int> c1(2,4);	c1.printf();
	Complex<int> c2(3,5);
	Complex<int> c3 = c1 + c2;
	Complex<int> c4 = MySub(c1,c2);
	cout << c4 << endl;
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
main.cpp:10:52: warning: friend declaration ‘std::ostream& operator<<(std::ostream&, Complex<T>&)’ declares a non-template function [-Wnon-template-friend]
friend ostream& operator<<(ostream &out,Complex &c);

[email protected]://990487026.blog.51cto.com~/c++$

当模板函数遇到友元函数,问题解决:

[email protected]://990487026.blog.51cto.com~/c++$ cat haha.cpp 
#include<iostream>
using namespace std;

template <typename T>
class Complex
{
	friend std::ostream& operator << <T>(std::ostream& os, const Complex<T>& c);
public:
	Complex(T a, T b);
protected:
	T a;
	T b;
};

template <typename T>
std::ostream& operator <<(std::ostream& os, const Complex<T>& c)
{
	os << "a="<<c.a <<" b= "<< c.b<<std::endl;
	return os;
}

template <typename T>
Complex<T>::Complex(T a, T b)
{
	cout << "进入外部Complex函数 a=" << a << " b=" << b << endl;;
	this->a = a;
	this->b = b;
}

int main()
{
	Complex<int> c1(2, 4);
	cout << c1 << endl;
	return 0;
}

VS下编译,通过:
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2.exe
进入外部Complex函数 a=2 b=4
a=2 b= 4

Linux GCC 编译 不通过:

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run haha.cpp && ./run 
haha.cpp: In instantiation of ‘class Complex<int>’:
haha.cpp:33:17:   required from here
haha.cpp:7:23: error: template-id ‘operator<< <int>’ for ‘std::ostream& operator<<(std::ostream&, const Complex<int>&)’ does not match any template declaration
  friend std::ostream& operator << <T>(std::ostream& os, const Complex<T>& c);
                       ^
[email protected]://990487026.blog.51cto.com~/c++$

滥用友元函数的后果

#include<iostream>
using namespace std;

template <typename T>
class Complex;
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2);

template <typename T>
class Complex
{
	friend std::ostream& operator << <T>(std::ostream& os, const Complex<T>& c);
	friend Complex<T> MySub<T>(Complex<T> &c1, Complex<T> &c2);

public:
	Complex(T a, T b);
protected:
	T a;
	T b;
};

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;
}

template <typename T>
std::ostream& operator <<(std::ostream& os, const Complex<T>& c)
{
	os << "a="<<c.a <<" b= "<< c.b<<std::endl;
	return os;
}

template <typename T>
Complex<T>::Complex(T a, T b)
{
	cout << "进入外部Complex函数 a=" << a << " b=" << b << endl;;
	this->a = a;
	this->b = b;
}

int main()
{
	Complex<int> c1(2, 4);
	Complex<int> c2(1, 2);
	Complex<int> c3 = MySub<int>(c1,c2);
	cout << c3 << endl;
	return 0;
}

Linux GCC编译不通过

VS 编译OK
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2.exe
进入外部Complex函数 a=2 b=4
进入外部Complex函数 a=1 b=2
进入外部Complex函数 a=1 b=2
a=1 b= 2

【结论】:不需要友元函数,不要用友元函数

【结论】模板类的cpp文件 与 .h头文件分开写的时候,要把cpp文件也包含进来

当类模板中有static成员变量时;

从类模板的实质分析,编译器你会自动为我们写成两个类

[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp 
#include<iostream>
using namespace std;

template <typename T>
class A
{
public:
	static T a;

};

template <typename T>
T A<T>::a = 0;

int main()
{
	A<int> a1;	a1.a++;
	A<int> a2;	a2.a++;
	A<int> a3;	a3.a++;
	cout << "a3="<<a3.a << "\n";
	A<double> b1;	b1.a += 3.5;
	A<double> b2;	b2.a += 3.5;
	A<double> b3;	b3.a += 3.5;
	cout << "b3="<<b3.a << "\n";
	return 0;
}

[email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a3=3
b3=10.5
[email protected]://990487026.blog.51cto.com~/c++$

类模板 数组案例

1主函数:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

#include "MyVector.cpp"

void main()
{
	MyVector<int> myv1(10);

	for (int i = 0; i<myv1.getLen(); i++)
	{
		myv1[i] = i + 1;
		cout << myv1[i] << " ";
	}
	cout << endl;

	MyVector<int> myv2 = myv1;
	for (int i = 0; i<myv2.getLen(); i++)
	{
		cout << myv2[i] << " ";
	}

	cout << myv2 << endl;

	cout << "hello..." << endl;
	system("pause");
	return;
}

2头文件:MyVector

#include <iostream>
using namespace std;

template <typename T>
class MyVector
{

	friend ostream & operator<< <T>(ostream &out, const MyVector &obj);
public:
	MyVector(int size = 0);  //构造函数
	MyVector(const MyVector &obj); // 拷贝构造函数
	~MyVector(); //析构函数

public:

	T& operator[] (int index);
	// a3 = a2 = a1;
	MyVector &operator=(const MyVector &obj);

public:
	int getLen()
	{
		return m_len;
	}

protected:
	T *m_space;
	int m_len;
};

类函数实现文件 MyVector.cpp

#include <iostream>
using namespace std;
#include "MyVector.h"

template <typename T>
ostream & operator<<(ostream &out, const MyVector<T> &obj)
{
	for (int i = 0; i<obj.m_len; i++)
	{
		out << obj.m_space[i] << " ";
		//out << t1;
	}
	out << endl;
	return out;
}

//MyVector<int> myv1(10);
template <typename T>
MyVector<T>::MyVector(int size)  //构造函数
{
	m_space = new T[size];
	m_len = size;
}

//MyVector<int> myv2  = myv1;
template <typename T>
MyVector<T>::MyVector(const MyVector &obj) // 拷贝构造函数
{
	//根据myv1的大小分配内存
	m_len = obj.m_len;
	m_space = new T[m_len];

	//copy数据
	for (int i = 0; i<m_len; i++)
	{
		m_space[i] = obj.m_space[i];
	}

}

template <typename T>
MyVector<T>::~MyVector() //析构函数
{
	if (m_space != NULL)
	{
		delete[] m_space;
		m_space = NULL;
		m_len = 0;
	}
}

template <typename T>
T& MyVector<T>::operator[] (int index)
{
	return m_space[index];
}

// a3 = a2 = a1;
template <typename T>
MyVector<T> &  MyVector<T>::operator=(const MyVector<T> &obj)
{
	//先把a2的旧的内存释放掉

	if (m_space != NULL)
	{
		delete[] m_space;
		m_space = NULL;
		m_len = 0;
	}

	//根据a1分配内存 
	m_len = obj.m_len;
	m_space = new T[m_len];

	//copy数据
	for (int i = 0; i<m_len; i++)
	{
		m_space[i] = obj[i];
	}
	return *this;  // a2 = a1; 返回给a2 的自身
}
VS环境编译运行:
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2.exe
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10

hello...
请按任意键继续. . .

类模板 结构体案例

类函数实现文件 MyVector.h 内容不变

类函数实现文件 MyVector.cpp内容不变

主函数修改为:

main.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

#include "MyVector.cpp"

class Teacher
{
public:
	Teacher()
	{
		age = 33;
		strcpy(name, "");
	}

	Teacher(char *name, int age)
	{
		this->age = age;
		strcpy(this->name, name);
	}
	void printT()
	{
		cout << name << ", " << age << endl;
	}
private:
	int age;
	char name[32];
};

void main()
{
	Teacher t1("t1", 31), t2("t2", 32), t3("t3", 33), t4("t4", 34);

	MyVector<Teacher> tArray(4);

	tArray[0] = t1;
	tArray[1] = t2;
	tArray[2] = t3;
	tArray[3] = t4;

	for (int i = 0; i<4; i++)
	{
		Teacher tmp = tArray[i];
		tmp.printT();
	}
}

VS编译运行:
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2.exe
t1, 31
t2, 32
t3, 33
t4, 34
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>

作业:

在上面这个程序的基础之上:

1  优化Teacher类, 属性变成 char *panme, 购置函数里面 分配内存

2  优化Teacher类,析构函数 释放panme指向的内存空间

3  优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数

4  优化Teacher类,在Teacher增加 <<

5  在模板数组类中,存int char Teacher Teacher*(指针类型)

时间: 2024-09-30 06:44:59

C++提高1 【泛型编程】函数模板 类模板的相关文章

函数模板 类模板

摘要:学习函数模板的定义,使用:学习类模板的定义和使用. 函数模板: template <typename 类型参数> 返回类型 函数名(模板形参表) { 函数体 } 特点:1.函数模板可以重载(比如形参数量不同的情况). 2.定义的时候,template <typename 类型参数>到下面一个语句之间不允许插入其他语句! 3.如果程序中有和函数模板名称相同的非函数模板函数,则优先调用它. 例子: #include<iostream> using namespace

C++ - 模板类模板成员函数(member function template)隐式处理(implicit)变化

模板类模板成员函数(member function template)隐式处理(implicit)变化 本文地址: http://blog.csdn.net/caroline_wendy/article/details/24233693 指针支持隐式转换(implicit conversion), 在动态绑定中,派生类指针能够转换为基类指针. 可是模板的实例化(instantiations)之间, 是单独存在的, 派生类的实例化的模板(SmartPtr<Derived>), 不能转换为基类实例

3.2 STL中的函数对象类模板

*: STL中有一些函数对象类模板,如下所示: 1)例如要求两个double类型的x 和y 的积,可以: multiplies<double>()(x,y); 该表达式的值就是x*y的值. 2)less是STL中最常用的函数对象类模板,其定义如下: template<class _Tp> struct less { bool oprator()(const _Tp&_x,const _Tp&_y)const { return _c<_y; } } 要判断两个i

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

 1.第一个最简单的类模板案例 #include "mainwindow.h" #include <QApplication> #include <QPushButton> #include <QLabel> template<class T> class run { public: T w; void show() { w.show(); } void settext() { w.setText("A"); }

【C/C++学院】(11)泛型编程/函数模板/类模板

1.泛型编程基础 #include "iostream" using namespace std; void swap(int &a, int &b) { int c; c = a; a = b; b = c; } void swap(float &a, float &b) { float c; c = a; a = b; b = c; } void main() { int a = 1, b = 2; swap(a, b); float a1 = 1,

泛函编程—模板函数_类模板

函数业务逻辑一样,只是函数参数类型不同函数模板的本质:类型参数化——泛型编程 语法: template <typename T> template <class T1,class T2>多个参数类型 类型 函数名(形式参数表) { 语句序列: } 函数模板基础: template是告诉C++编译器,开始泛型编程,看到T,不要随便报错 template <typename T>//一个模板 void myswap(T& a, T& b) { T c; c

函数、类模板

泛型程序设计 算法实现时不指定具体要操作的数据的类型.适用于多种数据结构. 函数模板 Template <class 类型参数1,class类型参数2,…..> 返回值类型 模板名(形参表) { 函数体: } 函数模板可以重载,只要它们的形参表不同即可. C++编译器遵循以下优先顺序: 先找参数完全匹配的普通函数(非由模板实例化而得的函数) 再找参数完全匹配的模板函数 再找实参经过自动类型转换后能匹配的普通函数 上面的都不符合则报错. 可以在函数模板中使用多个类型参数,可以避免二义性. #in

函数模板&amp;类模板

1.函数模板 关键字template总是放在模板的定义与声明的最前面.关键字后面是用逗号分隔的模板参数表,它用尖括号(<>)括起来.该列表是模板参数表,不能为空.模板参数分为:(1) 模板类型参数,代表一种类型:(2) 模板非类型参数,代表一个常量表达式. eg:        template <class Type>        Type min(Type a,Type b)        {              return a<b ? a : b;      

C++笔记(7):泛型编程和模板(函数模板和类模板)

泛型编程和模板 0.泛型编程 1.函数模板 2.类模板 ----------------------------------------------------------------------------------------------------------- 0.泛型编程 所谓泛型就是以独立于任何特定类型的方式编写代码.前面介绍的标准库的容器.迭代器和算法都是泛型编程的具体应用. 模板是泛型编程的基础.使用模板的时候不需要知道模板是如何定义的,但今天我们来介绍如何定义自己的模板类和模