天天看點

C++提高1 【泛型程式設計】函數模闆 類模闆

【本文謝絕轉載】

【泛型程式設計】

函數模闆

為什麼會有函數模闆

現象:

函數的業務邏輯一樣

函數的參數類型不一樣

【最常用】函數模闆  顯式的調用

【不常用】類型推導

多個參數,參數定義了必須要用

函數模闆,實作int類型數組,char字元串排序:

函數模闆 與 普通函數的本質差別

函數模闆 和 普通函數在一起 的調用型研究:

C++是如何支援函數模闆機制的?

函數模闆機制結論

類模闆

類模闆的定義

類模闆做函數的參數

類模闆的派生成普通類

模闆類的派生成模闆類

複數類,所有函數都寫在類的内部,運算符重載熱身

複數類,所有函數都寫在類的内部, 類模闆

【示範】濫用友元函數的後果--正常的代碼準備

複數類,所有函數都寫在類的内部(一個CPP中):問題抛出

當模闆函數遇到友元函數,問題解決:

濫用友元函數的後果

【結論】:不需要友元函數,不要用友元函數

【結論】模闆類的cpp檔案 與 .h頭檔案分開寫的時候,要把cpp檔案也包含進來

當類模闆中有static成員變量時;從類模闆的實質分析,編譯器你會自動為我們寫成兩個類

類模闆 數組案例

類模闆 結構體案例

作業:

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

【為什麼會有函數模闆】

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
20 10
B A
chunli@http://990487026.blog.51cto.com~/c++$      

現象:

函數的業務邏輯一樣

函數的參數類型不一樣

【最常用】函數模闆  顯式的調用

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
10 20
20 10
A B
B A
chunli@http://990487026.blog.51cto.com~/c++$      

【不常用】類型推導

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
10 20
20 10
A B
B A
chunli@http://990487026.blog.51cto.com~/c++$      

多個參數,參數定義了必須要用

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
10 20
Hello
20 10
A B
Linux
B A
chunli@http://990487026.blog.51cto.com~/c++$      

函數模闆,實作int類型數組,char字元串排序:

chunli@http://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;
}

chunli@http://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 
chunli@http://990487026.blog.51cto.com~/c++$      

函數模闆 與 普通函數的本質差別

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp  && ./run 
69 A 我是普通函數
65 E 我是普通函數
69 69 我是函數模闆
chunli@http://990487026.blog.51cto.com~/c++$      

函數模闆 和 普通函數在一起 的調用型研究:

【補充】在同一作用域,可以發生函數重載

chunli@http://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;
}

chunli@http://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 我是普通函數
chunli@http://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      

類模闆:

類模闆用于實作類所需資料的類型參數化 

類模闆在表示如數組、表、圖等資料結構顯得特别重要,

類模闆的定義

類末班的使用

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a= 11
chunli@http://990487026.blog.51cto.com~/c++$      

類模闆做函數的參數

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a= 11
a= 22
a= 33
chunli@http://990487026.blog.51cto.com~/c++$      

類模闆的派生成普通類

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a=1 b=2      

模闆類的派生成模闆類

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
c=2
c=A
chunli@http://990487026.blog.51cto.com~/c++$      

複數類,所有函數都寫在類的内部,運算符重載熱身

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a= 5 b=9
a=5 b=9 
chunli@http://990487026.blog.51cto.com~/c++$      

複數類,所有函數都寫在類的内部, 類模闆

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a= 5 b=9
進入<<操作符重載 -> a=5 b=9 
chunli@http://990487026.blog.51cto.com~/c++$      

【示範】濫用友元函數的後果--正常的代碼準備

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
進入MySub 函數  
進入<<操作符重載 -> a=-1 b=-1 
chunli@http://990487026.blog.51cto.com~/c++$      

複數類,所有函數都寫在類的内部(一個CPP中):問題抛出

chunli@http://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;
}

chunli@http://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);

chunli@http://990487026.blog.51cto.com~/c++$      

當模闆函數遇到友元函數,問題解決:

chunli@http://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 編譯 不通過:

chunli@http://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);
                       ^
chunli@http://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成員變量時;

從類模闆的實質分析,編譯器你會自動為我們寫成兩個類

chunli@http://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;
}

chunli@http://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 
a3=3
b3=10.5
chunli@http://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*(指針類型)

繼續閱讀