Tải bản đầy đủ (.pdf) (76 trang)

Giáo án - Bài giảng: LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG NÂNG CAO C++

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (6.87 MB, 76 trang )

LẬP TRÌNH C/C++ NÂNG CAO
Yêu cầu trước khi đọc: học xong Lập trình C/C++ căn bản
BÀI 1: NHẮC LẠI VỀ C/C++
Nh
ập xuất cơ bản
CODE
#definemax(a,b)(a>b)?a:b//khaibáomacro
typedefunsignedintbyte;//
địnhnghĩakiểudữ liệu
constfloatPI=3.14;//khaibáoh
ằngsố
charc;chars[20];
Cách của C
CODE
//khôngdùngscannếumuốnnhậpkhoảngtrắng
gets(s);//cóth
ể nhậpkhoảngtrắng
puts(s);
fflush(stdin);//xóab
ộ đệmnhập
c=getchar();
putchar©;
Cách của C++
CODE
//khôngdùngcin>>nếumuốnnhậpkhoảngtrắng
cin.getline(a,21);//cóth
ể nhậpkhoảngtrắng
cout<<a;
cin.get();//xóabộ đệmnhập
Con trỏ cơ bản
CODE


inta=5,*p;
//p=3;//khonghopvevikhongthegangiatrikieuintchobienkieuint*
//&p=3;//khonghoplevidiachicuaplacodinh
p=&a;//hople,gandiachimaptroden
*p=3;//hople,gangiatritaidiachimaptroden
cout<<p<<endl;//caigidobatki,diachicuaa
cout<<&p<<endl;//caigidobatki,diachicuap
cout<<*p<<endl;//3,dau*lucnaymangynghia"giatritaidiachicua"
Truyền giá trị cho hàm
Trong C có khái niệm con trỏ (pointer) Trong C++ có thêm khái niệm tham chiếu (reference)
CODE
inta;
int&b=a;
Lúc này biến a có một cái nickname là b
Nh
ư vậy có tất cả 3 cách viết hàm và truyền tham số
Cách 1:
CODE
voidadd10(inta)
{
a=a+10;
}
gọi:
add10(n);
Không hiệu quả, a vẫn giữ nguyên giá trị
Cách 2:
CODE
voidadd10(int*a)
{
*a=*a+10;

}
g
ọi:
add10(&n);
Hiệu quả.
Cách
3:
CODE
voidadd10(int&a)
{
a=a+10;
}
g
ọi:
add10(n);
Hiệu quả, tiện hơn cách 2.
Nhập xuất dữ liệu với kiểu mảng số nguyên
CODE
inta[3];
Truyền dữ liệu trực tiếp theo kiểu C, cách 1
CODE
for(inti=0;i<3;++i)scanf("%d",&(*(a+i)));
for(inti=0;i<3;++i)printf("%d",*(a+i));
Truyền dữ liệu trực tiếp theo kiểu C, cách 2
CODE
for(inti=0;i<3;++i)scanf("%d",&a[i]);
for(inti=0;i<3;++i)printf("%d",a[i]);
Truyền dữ liệu trực tiếp theo kiểu C++, cách 1
CODE
for(inti=0;i<3;++i)cin>>*(a+i);

for(inti=0;i<3;++i)cout<<*(a+i);
Truyền dữ liệu trực tiếp theo kiểu C++, cách 2
CODE
for(inti=0;i<3;++i)cin>>a[i];
for(inti=0;i<3;++i)cout<<a[i];
Nhập xuất dữ liệu bằng hàm với kiểu mảng số nguyên
Nhập xuất dữ liệu bằng hàm với kiểu mảng số nguyên theo kiểu C, cách 1
CODE
voidinput(int[]);
input(a);
voidinput(int*a)
{
for(inti=0;i<3;++i)
scanf("%d",&(*(a+i)));
}
voidoutput(int[]);
output(a);
voidoutput(int*a)
{
for(inti=0;i<3;++i)
printf("%d",*(a+i));
}
Nhập xuất dữ liệu bằng hàm với kiểu mảng số nguyên theo kiểu C, cách 2
CODE
voidinput(int[]);
input(a);
voidinput(inta[])
{
for(inti=0;i<3;++i)
scanf("%d",&a[i]);

}
voidoutput(int[]);
output(a);
voidoutput(inta[])
{
for(inti=0;i<3;++i)
printf("%d",a[i]);
}
Nhập xuất dữ liệu bằng hàm với kiểu mảng số nguyên theo kiểu C++, cách 1
CODE
voidinput(int[]);
input(a);
voidinput(int*a)
{
for(inti=0;i<3;++i)
cin>>*(a+i);
}
voidoutput(int[]);
output(a);
voidoutput(int*a)
{
for(inti=0;i<3;++i)
cout<<*(a+i);
}
Nhập xuất dữ liệu bằng hàm với kiểu mảng số nguyên theo kiểu C++, cách 2
CODE
voidinput(int[]);
input(a);
voidinput(inta[])
{

for(inti=0;i<3;++i)
cin>>a[i];
}
voidoutput(int[]);
output(a);
voidoutput(inta[])
{
for(inti=0;i<3;++i)
cout<<a[i];
}
Nhập xuất dữ liệu với kiểu mảng số thực
Cách dùng biến tạm
CODE
floata[2][3],temp;
for(inti=0;i<2;++i)
for(intj=0;i<3;++j)
{
scanf("%f\n",&temp);
a[i][j]=temp;
}
Cách dùng con trỏ
CODE
floata[2][3];float*p;
p=(float*)a;
for(inti=0;i<2*3;++i)
scanf("%f",(p+i));
Nhập mảng số thực 2 chiều bằng cách dùng ép kiểu
CODE
floata[3][2];float*p;p=(float*)a;
for(inti=0;i<3;i++)

for(intj=0;j<2;j++)
scanf("%f",((float*)p+i*2+j));
Xuất mảng số thực 2 chiều bằng cách dùng ép kiểu
CODE
floata[3][2];float*p;p=(float*)a;
for(inti=0;i<3;i++)
for(intj=0;j<2;j++)
printf("%f\n",*(p+i*2+j));
Nhập mảng số thực 2 chiều bằng cách dùng malloc
CODE
float**p;p=(float**)malloc(2);
for(inti=0;i<3;i++)
for(intj=0;j<2;j++)
scanf("%f",(p+i*2+j));
Xuất mảng số thực 2 chiều bằng cách dùng malloc
CODE
float**p;p=(float**)malloc(2);
for(inti=0;i<3;i++)
for(intj=0;j<2;j++)
printf("%f\n",*(p+i*2+j));
Bài này chỉ có giá trị tham khảo, tổng hợp kiến thức.
BÀI 2: NHẮC LẠI VỀ C/C++ (TIẾP THEO)
C
ấu trúc (struct)
Con trỏ cấu trúc (struct pointer)
CODE
structStudent
{
intid;
};

Student*s;
Studentm;
s=&m;
s->id=3;//means(*s).id
cout<<m.id;
Sao chép cấu trúc
CODE
structStudent
{
intid;
char*name;//m
ộtcontrỏ,khôngphảimộtmảng
};
Studenta;
chartemp[20];
cin>>temp;
a.name=newchar[strlen(temp)+1];
strcpy(a.name,temp);//ph
ảidùngbiếntạm
Studentb=a;
strcpy(b.name,a.name);//ph
ảidùngstrcpy,nếukhôngsẽ saochépđịachỉ bộ nhớ
Gọi hàm với cấu trúc
CODE
structStudent{
charname[10];
intid;
};
Studentm[3],a;
m[0]=(Student){"Pete",1};

add(m[0].name,&m[0].id);
Có 4 cách để thêm dữ liệu vào cấu trúc.
Cách 1
CODE
voidadd(charname[],int*place)
{
cin>>name;
cin.get();
cin>>*place;
}
add(a.name,&a.id);
Cách 2
CODE
voidadd(Student&s)
{
cin>>s.name;
cin.get();
cin>>s.id;
}
add10(a);
Cách 3
CODE
voidadd(Student*s)
{
cin>>(*s).name;
cin.get();
cin>>(*s).id;
}
add(&a);
Cách 4

CODE
voidadd(Student*s)
{
cin>>s->name;
cin.get();
cin>>s->id;
}
add(&a);
Toán tử sizeof với struct
CODE
structHello
{
charc;
doubled;
};
sizeof(Mystruct)=12; vì c lấy một 32-bit word (4 byte, không phải 1 byte)
Con trỏ (pointer)
Con trỏ trỏ đến một con trỏ khác
CODE
chara='z';//a='z'vàgiả sử địachỉ củaa=8277
char*p=&a;//p=8277vàgi
ả sử địachỉ củap=6194
char**p2=&p;//p2=6194và
địachỉ củap2sẽ làmộtcáigìđó
Con trỏ void (void pointer)
Con trỏ void dùng để trỏ đến bất cứ một kiểu dữ liệu nào
CODE
voidincrease(void*data,intdataType)
{
switch(dataType)

{
casesizeof(char):
(*((char*)data))++;break;
casesizeof(int):
(*((int*)data))++;break;
}
}
intmain()
{
charc=66;inta=-4;
increase(&c,sizeof(char));
increase(&a,sizeof(int));
}
Con trỏ hàm (function pointer)
Con trỏ hàm dùng để trỏ đến một hàm
CODE
intaddition(inta,intb)
{
returna+b;
}
intsubtraction(inta,intb)
{
returna-b;
}
int(*minuse)(int,int)=subtraction;
intprimi(inta,intb,int(*functocall)(int,int))
{
return(*functocall)(a,b);
}
intmain()

{
intm=primi(7,5,&addition);
intn=primi(20,m,minuse);
cout<<m<<endl;cout<<n<<endl;
return0;
}
Hàm nội tuyến (inline function)
Hàm khai báo với từ khóa inline, trình biên dịch sẽ chèn toàn bộ thân hàm mỗi nơi mà hàm đó được sử dụng. Với cách này, các
hàm inline có t
ốc độ thực thi cực nhanh, nên sử dụng với các hàm thường xuyên phải sử dụng trong chương trình.
CODE
inlinevoiddisplay(char*s)
{
cout<<s<<endl;
}
intmain()
{
display("Hello");return0;
}
Nhập xuất với tập tin
CODE
#include<fstream>
#include<iomanip>
intnumber;
ifstreaminf;ofstreamoutf;
inf.open("input.txt");
outf.open("output.txt");
while(in>>number)
outf<<"Nextis"<<setw(4)<<number<<endl;
inf.close();

outf.close();
Mở một file dùng cho cả nhập và xuất
CODE
fstreamf;
f.open("st.txt",ios::in|ios::out);
mộtsố chế độ haydùng
ios::inngh
ĩalànhậpvào
ios:outngh
ĩalàxuấtratậptintừ đầutậptin
ios::appngh
ĩalàthêmdữ liệuvàotậptin(appending)
Tập tin header
Tạo một tập tin header có tên là myfile.h
#ifndef MYFILE_H
#define MYFILE_H
……
#endif
trong t
ập tin cpp thêm vào dòng
#include "myfile.h"
BÀI 3: NHẮC LẠI VỀ LỚP
C
ơ bản về lớp
CODE
classDate{
intday;
public:
Date(int,inta=1);
intmonth;

voidsetDay(int);
voidoutput();
};
intmain(){
Dated(6);
d.month=3;
d.setDate(25);
d.output();
return0;
}
Date::Date(intday,intmonth){
this->day=day;
this->month=month;
}
voidDate::setDay(intday){
this->day=day;
}
voidDate::output(){
cout<<day<<"/"<<month;
}
Hàm khởi tạo
Chúng ta có thể viết một hàm khởi tạo như thế này
CODE
classStudent
{
stringname;intage;
public:
Student(stringname,intn):name(name),age(n)
{
}

};
Nó tương đương với
CODE
classStudent
{
stringname;intage;
public:
Student(stringname,intn)
{
(*this).name=name;
this->age=n;
}
};
Hàm bạn (friend function)
CODE
classStudent{
public:
intid;
friendboolequal(constStudent&,constStudent&);
};
intmain(){
Students1;s1.id=2;
Students2;s2.id=3;
cout<<equal(s1,s2);
}
boolequal(constStudent&s1,constStudent&s2){
return(s1.id==s2.id);
}
Overload toán tử (operator overload)
Ví dụ dưới sẽ overload toán tử ==

CODE
classStudent{
public:
intid;
friendbooloperator==(constStudent&,constStudent&);
};
intmain(){
Students1;s1.id=2;
Students2;s2.id=3;
cout<<((s1==s2)?"equal":"unequal");
}
booloperator==(constStudent&s1,constStudent&s2){
return(s1.id==s2.id);
}
Overload toán tử nhập và xuất (input >> và output <<)
Mọi người đều biết cin>>a là gọi toán tử nhập cin.operator>>(a) hoặc operator>>(cin,a) Overload 2 toán tử nhập và xuất này hết
s
ức quan trọng về sau. Nhân tiện mỗi khi cấp phát bộ nhớ, dùng xong phải luôn hủy đi để thu hồi lại bộ nhớ đã cấp phát. Vì về sau
game cái
ưu tiên hàng đầu là bộ nhớ, đừng để lại rác.
CODE
classDate{
public:
intday;intmonth;
friendistream&operator>>(istream&,Date&);
friendostream&operator<<(ostream&,constDate&);
};
istream&operator>>(istream&ins,Date&d){
ins>>d.day;
ins>>d.month;

ins.get();//ph
ảixóabộ đệm
returnins;
}
ostream&operator<<(ostream&outs,constDate&d){
outs<<d.day<<"/"<<d.month;
returnouts;
}
intmain(){
Dated;
cin>>d;cout<<d;
Date*dt=newDate;//ph
ảitạoobjectpointer,cấpphátbộ nhớ
cin>>*dt;cout<<*dt;
deletedt;//ph
ảihủyobjectpointer
}
Hàm hủy (destructor)
CODE
classmyclass{
public:
int*p;
myclass();
~myclass();
};
intmain(){
myclassm;
return0;
}
myclass::myclass(){

p=newint;//ph
ảicấpphátbộ nhớ để tránhsegmentationfault
}
myclass::~myclass(){
deletep;
}
Hàm khởi tạo sao chép (copy constructor
CODE
classDate{
public:
intday;intmonth;char*special;
Date(int,int,char*);
Date(constDate&);
~Date(){
delete[]special;//b
ởivìchúngtacấpphátbộ nhớ chonó
}
};
Date::Date(intday,intmonth,char*special){
this->day=day;this->month=month;this->special=special;
}
Date::Date(constDate&d){
this->day=d.day;this->month=d.month;
this->special=newchar[strlen(d.special)+1];//c
ấpphátbộ nhớ chonó
strcpy(this->special,d.special);//ph
ảidùngstrcpyvớichararray
}
intmain(){
Dated1(29,8,"birthday");

Dated2(d1);
cout<<d2.special;
return0;
}
Chú ý về cấp phát bộ nhớ
Ðiều gì sẽ xảy ra khi chúng ta không thể cấp phát bộ nhớ ? Ví dụ chúng ta viết 1 game RTS mà mỗi phe tham chiến có 10 tỉ
quân ?
Gi
ải quyết khi không thể cấp phát bộ nhớ thành công
Chúng ta vẫn thường cấp phát bộ nhớ như sau
CODE
char*p;inti;
cout<<"numberofelementuwant:";
cin>>i;
p=newchar[i+1];
delete[]p;
Nếu chúng ta không thể cấp phát bộ nhớ ? CPP sẽ ném (throw) ra một ngoại lệ. Có 2 cách để xử lí chuyện này
Cách m
ột là dùng từ khóa nothrow. Vì thế CPP vẫn tạo ra một pointer nhưng là 0
CODE
p=new(nothrow)char[i+1];
if(p==0)cout<<"Can'tallocatememory";
Cách hai là bắt cái ngoại lệ ấy, Ðó là ngoại lệ std::bad_alloc
CODE
try{
p=newchar[i+1];
}catch(std::bad_alloc&mae){
cerr<<"failedtoallocatememory"<<mae.what();
exit(1);
}

Cấp phát bộ nhớ trong C
Ðừng có chỉ mê new và delete không thôi, cấp phát với cách của C vẫn phải dùng về sau đấy
CODE
char*p;inti;
printf("numberofelementuwant:");
scanf("%d",&i);
p=(char*)malloc(i+1);
if(p==NULL)exit(1);
free(p);
ho
ặcchúngtacóthể dùngcalloc
p=(char*)calloc(i,sizeof(char));
Toán tử gán (assignment operator)
CODE
classBase{
public:
Base&operator=(constBase&);
friendbooloperator!=(constBase&,constBase&);
private:
char*c;
};
Base&Base::operator=(constBase&src){
if(*this!=src){//toavoidself-assignment
delete[]c;
c=newchar[strlen(src.c)+1];
strcpy(this->c,src.c);
}
return*this;
}
booloperator!=(constBase&b1,constBase&b2){

return(strcmp(b1.c,b2.c));
}
Vàchúngtacóth
ể gọitoántử này
Bases2=s1;
Thừa kế (inheritance)
Trong C có thể sinh ra bug, trong C++ chúng sẽ được thừa kế.
CODE
classBase{
protected:
intid;
Base(intid){
this->id=id;
}
};
classSub:publicBase{
public:
intcode;
Sub(intcode,intid):Base(id){
this->code=code;
}
};
Hàm ảo (virtual function)
Hàm Play trong lớp MusicPlayer là một hàm ảo (virtual function)
CODE
classMusicPlayer{
public:
virtualvoidPlay(){
cout<<"Playonwhat?"<<endl;
}

};
classDVD:publicMusicPlayer{
public:
voidPlay(){
cout<<"PlayonDVD"<<endl;
}
};
intmain(){
MusicPlayerm;m.Play();
DVDd(2);d.Play();
}
Bây giờ chúng ta sẽ làm hàm Play trong lớp MusicPlayer là một hàm thuần ảo (pure virtual function), đồng thời làm lớp MusicPlayer
tr
ở thành một lớp trừu tượng (abstract class), chúng ta sẽ không thể tạo instance của nó được nữa
CODE
classMusicPlayer{
public:
virtualvoidPlay()=0;
};
classDVD:publicMusicPlayer{
public:
voidPlay(){
cout<<"PlayonDVD"<<endl;
}
};
intmain(){
DVDd(2);d.Play();
}
Chúng ta tạo con trỏ để trỏ đến các subclass của nó
CODE

MusicPlayer*m=newDVD(5);m->play();
Chúng ta cung có thể tạo mảng các con trỏ của một lớp trừu tượng
CODE
classMusicPlayer làmộtlớptrừutượng
classDVD:publicMusicPlayer
classCD:publicMusicPlayer
MusicPlayer*m[2];
m[0]=newDVD(5);m[0]->play();
m[1]=newCD("Sony");m[1]->play();
Nhắc lại một chút về mảng các kí tự (char array)
CODE
chardestArray[10];charsrcArray[]="panther";
strcpy(destArray,srcArray);
strcpy(destArray,srcArray,strlen(srcArray));
strcat(s1,s2);//thêm(append)s2vàos2
strncat(s1,s2,n);//thêm(append)nkít
ự đầutiêncủas2vàos1
strlen(char*s);//
độ dài(length)củachararray,khôngbaogồm"endofchararraymaker"
char*a;charb[];strcmp(a,b);//tr
ả về 0nếubằng,-1nếua<b,1nếua>b
atoi,atof,atollconvertm
ộtchararraythànhinteger,floathaylong,3hàmnàytrongstdlib.h
char*s="123.45";
inti=atoi(s);
floatf=atof(s);
Nhắc lại một chút về chuỗi (string)
CODE
usingstd::string;
*kh

ởitạo(constructor)
strings1;strings2("Helloboy");strings3(s2);
strings4(s2,3,4);//saochépt
ừ kítự thứ 3,saochép4kítự
strings5(8,'*');//khởitạochuỗigồmtoàndấu*
*toánt
ử gán(assignment)
strings4=s2;strings5.assign(s3);
*sosánhchu
ỗi(comparestring)
if(s1==s2)//bâygi
ờ cóthể dùng==rồi
if(s1.compare(s2))
*c
ộngchuỗi
strings1,s2;s1+=s2;s1+='o';
s1.append(s2);//ynhus1+=s2
s1.append(s2,3,string::npos);//thêmvàos1t
ừ kítự thứ 3đếnhếts2
s1.insert(7,s2);//thêms2vàosaukít
ự thứ 7củas1
*kíchc
ỡ (capacity)
s.capacity()tr
ả về kíchcỡ tốiđa
ifs.size()=15,s.capacity()=16(16-byte)
ifs.size()=17,s.capacity()=32(two16-byte)
*truyxu
ấtchuỗi
#include<stdexcept>

try{
cout<<s.at(100);
}catch(out_of_range&e){
cout<<"invalidindex";
}
BÀI 4: TEMPLATE
Hàm template
Giả sử chúng ta cần viết một hàm trả về số nguyên lớn nhất giữa 2 số
CODE
intmaximum(inta,intb)
{
return(a>b)?a:b;
}
Rồi đến số thực chúng ta cũng làm như vậy
CODE
doublemaximum(doublea,doubleb)
{
return(a>b)?a:b;
}
Rồi giả sử như với lớp Person chúng ta cũng phải làm như vậy (toán tử > đã được overload)
CODE
Personmaximum(Persona,Personb)
{
return(a>b)?a:b;
}
C++ cung cấp một giải pháp cho vấn đề này, đó là template
CODE
template<classT>Tmaximum(Ta,Tb)
{
return(a>b)?a:b;

}
intmain()
{
inta=7;intb=5;
cout<<maximum(a,b);
return0
}
template với nhiều hơn một kiểu dữ liệu
CODE
template<classT,typenameU>voidfunc(Ta,Ub);
Dùng template với mảng
CODE
template<classT,intsize>voidprint(T(&a)[size])
{
for(inti=0;i<size;i++)cout<<a[i]<<endl;
}
Lớp template (template class)
CODE
template<classT>classpair
{
Tvalues[2];
public:
pair(Tfirst,Tsecond)
{
values[0]=first;values[1]=second;
}
Tgetmaximum();
};
template<classT>Tpair<T>::getmaximum()
{

return(values[0]>values[1])?values[0]:values[1];
}
Trong hàm main
CODE
pair<int>myobject(155,36);
myobject.getmaximum();
Thật tuyệt, đúng không ?
V
ấn đề không đơn giản như vậy.
Đau đầu
Xem lại hàm template dưới đây
CODE
template<classT>Tmaximum(Ta,Tb)
{
return(a>b)?a:b;
}
Ví dụ dưới đây thực ra là đang so sánh địa chỉ bộ nhớ (memory address) của 2 biến a và b
CODE
char*a="hello";char*b="world";
cout<<maximum(a,b);
Ví dụ dưới đây cũng là đang so sánh địa chỉ bộ nhớ (memory address) của 2 biến a và b
div, id: post-25916, class: postcolor
CODE
inta[3],b[3];
cout<<maximum(a,b);
Vậy phải làm sao ?
(Trong l
ập trình, những vấn đề tưởng như nhỏ nhặt thế này thực ra gây đau đầu lắm đó, nhất là khi phải làm dự án từ 1000 words trở lên. Mà đặc biệt riêng lập trình game đụng những chuyện đau đầu này thường xuyên
h
ơn các phân ngành IT khác. Biên dịch thành công, mà tại sao nó … kì cục vầy nè ?)

C
ứu tinh xuất hiện, đó là một tham chiếu mà tham chiếu đến một con trỏ (a reference which refers to a pointer). Đây là dạng đau đầu nhất của tham chiếu.
A reference which refers to a pointer
CODE
int*p;//mộtcontrỏ pbìnhthường
int*&r=p;//thamchi
ếurlànicknamemớicủap
r=newint;//t
ươngđươngvớip=newint
*r=5;//t
ươngđưongvới*p=5
cout<<*p;//t
ương đươngvớicout<<*r
Và như vậy, vấn đề khó khăn với dữ liệu kiểu mảng đã được giải quyết.
CODE
template<classT>T*maximum(T*&a,T*&b)
{
return(*a>*b)?a:b;
}
intmain()
{
char*a="bb";
char*b="aa";
cout<<maximum(a,b);
return0;
}
Lưu ý là chỉ có "một tham chiếu mà tham chiếu đến một con trỏ" và "một con trỏ mà trỏ đến một con trỏ khác", chứ không thề có những khái niệm như "một tham chiếu mà tham chiếu đến một tham chiếu khác" hay
"m
ột con trỏ mà trỏ đến một tham chiếu" đâu nhá.
H

ết khó khăn chưa ? Chưa đâu.
BÀI 5: TEMPLATE (TIẾP)
L
ại đau đầu
Ta muốn viết một chương trình tìm kiếm phần tử trong một mảng. Ta viết như sau
CODE
template<classT>intsearch(Ta[],intn,Tkey)
{
intindex=0;
while(index<n&&a[index]!=key)index++;
if(index==n)return-1;elsereturnindex;
}
Sau đó trong hàm main ta viết
CODE
char*list[]={"zero","one","two"};//thựcralàmảng2chiềuthôi
search(list,3,"two");//
ồ không,lạisosánhmemoryaddressnữarồi
Nhưng lần này vấn đề phức tạp hơn nhiều. Ví dụ nếu là mảng các Person là đụng thêm vấn đề cấp phát bộ nhớ nữa
Giải quyết
Chương trình dưới đây trình bày cách tạo một lớp mảng template, với đủ các chức năng tạo, thêm, truy xuất dữ liệu, toán tử [].
Đặc biệt là giải quyết đau đầu tìm kiếm dữ liệu ở trên vì so sánh memory address. Lưu ý là khi tạo ta phải dùng reference refers to
pointer
để cấp phát bộ nhớ đó
CODE
#include<iostream>
usingnamespacestd;
template<classT>classArray
{
T*array;intsize;
public:

Array(intn);
~Array();
voidsetValue(constT&,intn);//thi
ếtlậpdữ liệu
T&getValue(intn);//truyxu
ấtdữ liệu
voidmakeArray(T*&arr,intn);//t
ạomảng
T&operator[](inti);//toánt
ử []truyxuấtdữ liệumảng
intseek(constT&key);//tìmki
ếmtrongmảnggọihàm
intsearch(constT*list,intsize,constTkey);//tìmki
ếmtrongmảngcósẵn
};
template<typenameT>Array<T>::Array(intn)
{
size=n;
array=newT[size];
}
template<typenameT>Array<T>::~Array()
{
delete[]array;
}
template<typenameT>voidArray<T>::setValue(constT&value,intn)
{
*(array+n)=value;
}
template<typenameT>T&Array<T>::getValue(intn)
{

return*(array+n);
}
template<typenameT>voidArray<T>::makeArray(T*&arr,intn)
{
arr=newT[n];
}
template<typenameT>T&Array<T>::operator[](inti)
{
return*(array+i);
}
template<typenameT>intArray<T>::seek(constT&key)
{
intindex=0;
while((index<size)&&*(array+index)!=key)++index;
if(index==size)return-1;
elsereturnindex;
}
template<typenameT>intArray<T>::search(constT*list,intsize,constTkey)
{
intindex=0;
while((index<size)&&*(list+index)!=key)++index;
if(index==size)return-1;
elsereturnindex;
}
classPerson
{
intage;
public:
Person(){age=0;}
Person(intage){this->age=age;}

intgetAge()const{returnage;}
friendbooloperator!=(constPerson&p1,constPerson&p2)
{
returnp1.getAge()!=p2.getAge();
}
friendostream&operator<<(ostream&os,constPerson&p)
{
os<<p.getAge()<<endl;
returnos;
}
};
intmain()
{
Array<Person>a(3);
a.setValue(Person(5),2);
cout<<a[2];
Person*b;
a.makeArray(b,4);
for(inti=0;i<4;i++)*(b+i)=Person(i+2);
cout<<a.seek(Person(5))<<endl;
cout<<a.search(b,4,Person(4))<<endl;
return0;
}
Có vẻ đã xong. Hết rắc rối rồi.
Ch
ưa. Vẫn còn 2 rắc rối nữa. Bạn hãy thử viết toán tử output << cho một mảng template class hay so sánh giữa hai mảng
template class như trên thử xem.
B
ạn sẽ không viết được đâu nếu không sử dụng cái này: prototype template function (khai báo nguyên mẫu cho hàm template)
(H

ọc mấy cái điên đầu này làm gì nhỉ ? Làm gì à ? Hãy thử cho hai cầu thủ trong một game đá banh đối diện nhau. Họ có bao
nhiêu hành
động có thể làm được lúc đó ? Chuyền bóng ? Lừa bóng ? Đốn ? special Zidane-style skill ? Mike Tyson skill ? Hai mảng
các hành
động ấy phải đem ra mà chọi lẫn nhau. Bởi thế mang tiếng là “Advance C++” nhưng thực ra trong lập trình game vẫn chỉ
là “newbie”)
prototype template function
Chuẩn bị một tập tin tên là “array.h”
CODE
#ifndefARRAY_H
#defineARRAY_H
#include<iostream>
usingnamespacestd;
template<classT>classArray;
template<typenameT>boolequal(constArray<T>&,constArray<T>&);
template<typenameT>ostream&operator<<(ostream&,constArray<T>&);
template<classT>classArray
{
T*array;intsize;
public:
Array(intn);
~Array();
voidsetValue(constT&,intn);
friendboolequal<>(constArray<T>&,constArray<T>&);
friendostream&operator<<<>(ostream&,constArray<T>&);
};
#include"array.cpp"
#endif
Chuẩn bị một tập tin tên là “array.cpp”
CODE

template<typenameT>Array<T>::Array(intn)
{
size=n;
array=newT[size];
}
template<typenameT>Array<T>::~Array()
{
delete[]array;
}
template<typenameT>voidArray<T>::setValue(constT&value,intn)
{
*(array+n)=value;
}
template<typenameT>boolequal(constArray<T>&a1,constArray<T>&a2)
{
returna1.size==a2.size;
}
template<typenameT>ostream&operator<<(ostream&os,constArray<T>&a)
{
for(inti=0;i<a.size;++i)os<<*(a.array+i);
returnos;
}
Cuối cùng là “main.cpp”
CODE
#include"array.h"
classPerson
{
intage;
public:
Person()

{
age=0;
}
Person(intage)
{
this->age=age;
}
intgetAge()const
{
returnage;
}
friendbooloperator!=(constPerson&p1,constPerson&p2)
{
returnp1.getAge()!=p2.getAge();
}
friendostream&operator<<(ostream&os,constPerson&p)
{
os<<p.getAge()<<endl;
returnos;
}
};
intmain()
{
Array<Person>a(3);
a.setValue(Person(24),0);
a.setValue(Person(15),1);
a.setValue(Person(5),2);
cout<<a;
Array<Person>b(3);
cout<<equal(a,b)<<endl;

return0;
}
Giải thích: equal và operator<< đều là hai hàm bạn, do đó để hoạt động cần có sẵn lớp Array. Nhưng lớp Array muốn biên dịch
được phải cần có hai hàm này. Do đó ta phải khai báo prototype của hai hàm này trước. Nhưng vì đây là 2 template function,nên
khi khai báo l
ại prototype của chúng lần thứ hai trong một class template (ở đây là class Array) ta phải có cái kí hiệu này <> Khi
đó là một prototype template function. Khi đó, thay vì tập tin cpp chứa thân hàm include tập tin header chứa nguyên mẫu của
hàm, ta ph
ải làm ngược lại. Kĩ thuật này hiểu và ứng dụng cực kì rắc rối nhưng khổ nỗi lại áp dụng rất nhiều về sau, đặc biệt khi
làm các game l
ớn.
Biên dịch lại mã này với GCC
Không bắt buộc, nhưng nên làm nếu như sau này bạn có định làm việc với game trong môi trường *nix và console. Hãy đem 3 tập
tin này (array.h, array.cpp, main.cpp) và th
ử biên dịch bằng GCC trong Linux thử xem. Nhớ tạo makefile. Trong trường bọn tôi chủ
yếu làm việc bằng GCC và VI trong *nix chứ không phải Window. Việc sử dụng các bộ Visual Studio tuy không bị cấm nhưng
không
được khuyến khích. Và bài tập lẫn bài thi đều phải submit nguyên project kèm makefile để biên dịch trong môi trường *nix
h
ết.
Viết operator overload và copy constructor
Trong phần trước ta đã xem các ví dụ dùng cách “tham chiếu mà tham chiếu đến con trỏ” Trong phần này chúng ta sẽ overload
toán t
ử = và viết copy constructor cũng sử dụng lại cách này, mà không phải dùng đến prototype template function
CODE
#include<iostream>
#include<string>
usingnamespacestd;
template<typenameT>
classArray

{
public:
intsize;
T*elems;
Array(int);
Array(constArray<T>*&);
voidsetValue(constT&,inti);
T&getValue(intn);
Array<T>&operator=(constArray<T>*&);
friendbooloperator!=(constArray<T>&,constArray<T>&);
};
template<typenameT>
Array<T>::Array(intsize)
{
elems=newT[size];
}
template<typenameT>
voidArray<T>::setValue(constT&value,inti)
{
*(elems+i)=value;
}
template<typenameT>
T&Array<T>::getValue(inti)
{
return*(elems+i);
}
template<typenameT>
Array<T>::Array(constArray<T>*&src)
{
size=src.size;

elems=newT[size];
for(inti=0;i<size;i++)
*(elems+i)=*(src.elems+i);
}
template<typenameT>
Array<T>&Array<T>::operator=(constArray<T>*&src)
{
if(*this!=src)//toavoidself-assignment
{
size=src.size;
elems=newT[size];
for(inti=0;i<size;i++)
*(elems+i)=*(src.elems+i);
}
return*this;
}
template<typenameT>
booloperator!=(constArray<T>&a1,constArray<T>&a2)
{
if(a1.size!=a2.size)returntrue;
elsefor(inti=0;i<a1.size;i++)
if(*(a1.elems+i)==*(a2.elems+i))returnfalse;
returntrue;
}
intmain()
{
Array<string>a(2);
a.setValue("hello",0);
a.setValue("world",1);
Array<string>b(3);

b=a;
cout<<b.getValue(0)<<endl;
cout<<b.getValue(1)<<endl;
return0;
}
BÀI 6: TEMPLATE (TIẾP THEO)
Trình biên dịch và template
Trong bài trước chúng ta thấy một điều hơi là lạ, đó là file header array.h có chỉ thị #include file source array.cpp. Tại sao như
vậy ?
Khi trình biên d
ịch gặp template, nó kiểm tra cú pháp, nhưng không biên dịch ngay.
Ví d
ụ nó gặp template<class T> nó không thể biên dịch vì nó không biết kiểu dữ liệu của T.
Khi nó g
ặp instance đầu tiên của template, ví dụ template<int> nó biên dịch và chúng ta có phiên bản với kiểu dữ liệu int của
template.
Khi nó g
ặp instance thứ hai của template, ví dụ template<double> nó cũng lại biên dịch và chúng ta có phiên bản thứ hai của
template, phiên b
ản với kiểu dữ liệu double. Vân vân.
Thông th
ường chúng ta viết định nghĩa lớp và nguyên mẫu các hàm của lớp đó ở file header (đuôi .h) rồi mới viết thân cho các
hàm
đó ở một file source (đuôi .cpp), mà file cpp này include luôn file header đó.
Template ph
ải làm ngược lại. Vì lí do nói trên, cả định nghĩa lớp, nguyên mẫu các hàm lẫn thân của các hàm đó của một lớp
template ph
ải được biên dịch cùng nhau. Do đó khi tách rời định nghĩa của một lớp template ra chứa trong một file header riêng,
file header
đó phải include file source chứa thân các hàm của lớp template đó, rồi một file nào khác muốn dùng template đó phải

include cái file header
đó.
Ở đây còn một phần nữa về export, tôi đã cắt đi. Có nhiều thứ sau này tôi cũng sẽ cắt đi, nhằm giảm tải cho chương trình xuống
đến mức tối thiểu nhất có thể được. Nhưng an tâm là những thứ quan trọng nhất đều có đầy đủ.
Dùng từ khóa nào, class hay typename
Về cơ bản, sự khác biệt giữa chúng là không rõ ràng, cả 2 đều có cùng ý nghĩa và cùng cho kết quả như nhau, bạn muốn dùng từ
khóa nào cũng được.
Nh
ưng có lúc bạn phải dùng từ khóa typename, ví dụ
CODE
template<typenameT>classThing{
T::SubType*ptr;
};
Chúng ta muốn khai báo 1 con trỏ thuộc kiểu SubType của T, nhưng C++ sẽ hiểu là chúng ta muốn nhân giá trị SubType của kiểu
T v
ới ptr Lúc này chúng ta bắt buộc phải dùng từ khóa typename
CODE
template<typenameT>classThing{
typenameT::SubType*ptr;
};
Chuyên môn hóa template (template specialization)
Giả sử ta có một lớp template
template<class T>class pair{…}
Khi ta t
ạo một instance bằng cách khai báo cụ thể kiểu của T, ví dụ là int, tức là ta đã chuyên môn hóa (specialization) lớp
template
đó
pair<int> myobject(155,36);
Đôi khi ta muốn lớp template tạo ra những instance cụ thể để thực hiện những công việc cụ thể riêng đối với một loại dữ liệu cụ
thể nào đó, ta dùng chuyên môn hóa cụ thể (explicit specialization)

Trong ví d
ụ dưới đây ta muốn riêng đối với kiểu dữ liệu cụ thể là int thì lớp template có một hàm trả về phần dư giữa hai số
nguyên, còn với các kiểu dữ liệu khác thì nó trả về 0
CODE
template<classT>
classpair{
Tvalue1,value2;

×