Lập trình

Lập trình

Thứ Sáu, 13 tháng 6, 2014

Hàm tạo sao chép (Copy Constructor) : Định nghĩa , cách thức hoạt động cơ bản


Trong luc lập trình với lớp rất nhiều khi ta cần tạo một đối tượng mới có dữ liệu giống với những
đối tượng đã được thành lập từ trước , để giải quyết vấn đề này c++ có cách thức : Hàm tạo sao chép (Copy Constructor)

Ta đã có những cách khai báo đối tượng như sau :
Ten_class dt1,dt2;
TenClass * p = new TenClass;

Và đây là cách khai báo đối tượng theo kiểu Hàm tạo sao chép (Copy Constructor) :
TenClass dt3; // Tạo đối tượng dt3.
TenClass dt4(dt3); // Tạo dt4 theo dt3

Khi gặp dòng trên chương trình tạo ra một đối tượng dt4 ,cấp phát bộ nhớ cho nó kiểu TenClass
và sao chép toàn bộ dữ liệu(từng bit) của dt3 vào dt4 , người ta gọi đó là hàm tạo sao chép mặc định
. Ta thấy trong đa số các trường hợp (không có con trỏ và tham chiếu ) thì chỉ cần hàm sao chép
mặc định là đủ . Nhưng thực tế có rất nhiều những trường hợp có dữ liệu kiểu
 con trỏ (mảng,cấp phát động,...) ,tham chiếu thì ta không thể dùng cách này
(trường hợp này nói sau) .

Cách xây dựng hàm tạo sao chép:
Hàm tạo sao chép sử dụng đối kiểu tham chiếu để khởi gán cho đối tượng mới :

Ten_class(Ten_class & dt1){
    ...... // Những lệnh làm việc với thuộc tính dt1 để tạo đối tượng mới.
    }

Ta thấy khi không có con trỏ và tham chiếu chỉ cần hàm mặc định là đủ (không cần khai báo)
chỉ cần : TenClass dt1(dt); //dt đã được làm việc qua.

Nhưng khi thuộc tính có kiểu con trỏ , nếu ta dùng hàm mặc định khi đó 2 thuộc tính con trỏ
của 2 đối tượng trên sẽ cùng trỏ tới một ô nhớ khi đó nếu thay đổi con trỏ của đối tượng này thì con trỏ
của đối tượng kia sẽ thay đổi , thậm chí khi ta xóa (delete) vùng nhớ của con trỏ này thì cũng
đồng nghĩa con trỏ kia cũng bị xóa (NULL)

Như vậy chỉ dùng hàm tạo sao chép mặc định thôi là chưa đủ .

Ta cùng đến với một vài ví dụ :

// Hàm tao sao chép
#include<iostream>
#include<string.h>

using namespace std;

class SinhVien{
private :
int tuoi;
char* ten;
int len;// do dai ten
public :
void setname(char name[]){
strcpy(ten,name);
}
char * getname(){
return ten;
}
void settuoi(int tuoi){
this->tuoi = tuoi;
}
int gettuoi(){
return tuoi;
}
//Ham tao
  SinhVien(int len){
  this->len = len;
  ten = new char[len+1];//nen luon de dong cap phat bo nho nay
                     // trong ham khoi tao
  }
  //ham tao sao chep
  SinhVien(SinhVien & dt){
  this->len = dt.len;
  this->ten = new char[dt.len+1]; // Phai cap phat bo nho truoc
  this->tuoi = dt.tuoi;//thuoc tinh tuoi cua doi tuong dang duoc khai bao
                      // se co thuoc tinh tuoi giong thuoc tinh tuoi cua dt;
  for(int i=0 ;i<len;i++){
  (*this).ten[i] = dt.ten[i];
  }               
  }
  void show();// Xuat thong tin cua sinh vien
};
void SinhVien :: show(){
cout << "Ten la: " << ten << endl;
cout << "Tuoi la: " << tuoi << endl;
}
int main(){
//cout << "Do dai cua ho ten la: ";
//int len;
//cin >> len;
char ten[] = "Nguyen Van a";
SinhVien sv(strlen(ten));
sv.setname(ten);
sv.settuoi(17);
cout << "Thong tin cua sinh vien thu nhat: " << endl;
sv.show();
SinhVien sv2(sv); // tim den ham tao copy co doi truyen vao.
cout << "Thong tin cua sinh vien thu hai : " << endl;
sv2.show();
return 0;
}

Kết Quả :

Ở đây 2 con trỏ của 2 đôi tượng không trỏ cùng vào một vùng nhớ vì thế các bạn có thể delete[] thuộc tính tên của đối tượng bất kì và kiểm tra đối tượng còn lại
Ví dụ ở đây tôi thêm hamhuy(tôi chỉ ví dụ thôi chứ đây không phải hàm hủy thực sự.) :
void hamhuy(){
  delete[] ten;
  }
 
Thêm vào trong class SinhVien (public) .
Sau đó code lại :
  
// Hàm tao sao chép
#include<iostream>
#include<string.h>

using namespace std;

class SinhVien{
private :
int tuoi;
char* ten;
int len;// do dai ten
public :
void setname(char name[]){
strcpy(ten,name);
}
char * getname(){
return ten;
}
void settuoi(int tuoi){
this->tuoi = tuoi;
}
int gettuoi(){
return tuoi;
}
//Ham tao
  SinhVien(int len){
  this->len = len;
  ten = new char[len+1];//nen luon de dong cap phat bo nho nay
                     // trong ham khoi tao
  }
  //ham tao sao chep
  SinhVien(SinhVien & dt){
  this->len = dt.len;
  this->ten = new char[dt.len+1]; // Phai cap phat bo nho truoc
  this->tuoi = dt.tuoi;//thuoc tinh tuoi cua doi tuong dang duoc khai bao
                      // se co thuoc tinh tuoi giong thuoc tinh tuoi cua dt;
  for(int i=0 ;i<len;i++){
  (*this).ten[i] = dt.ten[i];
  }               
  }
  void show();// Xuat thong tin cua sinh vien
  void hamhuy(){
  delete[] ten;
  }
};
void SinhVien :: show(){
cout << "Ten la: " << ten << endl;
cout << "Tuoi la: " << tuoi << endl;
}
int main(){
//cout << "Do dai cua ho ten la: ";
//int len;
//cin >> len;
char ten[] = "Nguyen Van a";
SinhVien sv(strlen(ten));
sv.setname(ten);
sv.settuoi(17);
cout << "Thong tin cua sinh vien thu nhat: " << endl;
sv.show();
SinhVien sv2(sv); // tim den ham tao copy co doi truyen vao.
sv.hamhuy(); // Khong anh huong
cout << "Thong tin cua sinh vien thu hai : " << endl;
sv2.show();
return 0;
}
Ta thấy thuôc tình tên của đối tượng thứ 2 không bị mất đi trong đó thuộc tính của đối tượng một bị mất đi.


Nếu bạn muốn thấy vi dụ về việc hàm sao chép mặc định gây ra lỗi hai con trỏ vào cùng một vùng nhớ thì làm như sau :
 Bỏ cái hàm tạo sao chép do mình tạo nên tức là lúc này sử dụng hàm tạo mặc định của chương trình :
// Hàm tao sao chép
#include<iostream>
#include<string.h>

using namespace std;

class SinhVien{
private :
int tuoi;
char* ten;
int len;// do dai ten
public :
void setname(char name[]){
strcpy(ten,name);
}
char * getname(){
return ten;
}
void settuoi(int tuoi){
this->tuoi = tuoi;
}
int gettuoi(){
return tuoi;
}
//Ham tao
  SinhVien(int len){
  this->len = len;
  ten = new char[len+1];//nen luon de dong cap phat bo nho nay
                     // trong ham khoi tao
  }
  //ham tao sao chep
  /*SinhVien(SinhVien & dt){
  this->len = dt.len;
  this->ten = new char[dt.len+1]; // Phai cap phat bo nho truoc
  this->tuoi = dt.tuoi;//thuoc tinh tuoi cua doi tuong dang duoc khai bao
                      // se co thuoc tinh tuoi giong thuoc tinh tuoi cua dt;
  for(int i=0 ;i<len;i++){
  (*this).ten[i] = dt.ten[i];
  }               
  }*/
  void show();// Xuat thong tin cua sinh vien
  void hamhuy(){
  delete[] ten;
  }
};
void SinhVien :: show(){
cout << "Ten la: " << ten << endl;
cout << "Tuoi la: " << tuoi << endl;
}
int main(){
//cout << "Do dai cua ho ten la: ";
//int len;
//cin >> len;
char ten[] = "Nguyen Van a";
SinhVien sv(strlen(ten));
sv.setname(ten);
sv.settuoi(17);
cout << "Thong tin cua sinh vien thu nhat: " << endl;
sv.show();
SinhVien sv2(sv); // tim den ham tao copy co doi truyen vao.
sv.hamhuy(); // Khong anh huong
cout << "Thong tin cua sinh vien thu hai : " << endl;
sv2.show();
return 0;
}

Kết quả :

 
 chỗ bị bôi vàng là do ta đã xóa con trỏ của đối tượng một nên đối tượng hai có con trỏ NULL
 

Hàm tạo sao chép (Copy Constructor) : Định nghĩa , cách thức hoạt động cơ bản

Hàm tạo sao chép (Copy Constructor) : Định nghĩa , cách thức hoạt động cơ bản

Trong luc lập trình với lớp rất nhiều khi ta cần tạo một đối tượng mới có dữ liệu giống với những
đối tượng đã được thành lập từ trước , để giải quyết vấn đề này c++ có cách thức : Hàm tạo sao chép (Copy Constructor)

Ta đã có những cách khai báo đối tượng như sau :
Ten_class dt1,dt2;
TenClass * p = new TenClass;

Và đây là cách khai báo đối tượng theo kiểu Hàm tạo sao chép (Copy Constructor) :
TenClass dt3; // Tạo đối tượng dt3.
TenClass dt4(dt3); // Tạo dt4 theo dt3

Khi gặp dòng trên chương trình tạo ra một đối tượng dt4 ,cấp phát bộ nhớ cho nó kiểu TenClass
và sao chép toàn bộ dữ liệu(từng bit) của dt3 vào dt4 , người ta gọi đó là hàm tạo sao chép mặc định
. Ta thấy trong đa số các trường hợp (không có con trỏ và tham chiếu ) thì chỉ cần hàm sao chép
mặc định là đủ . Nhưng thực tế có rất nhiều những trường hợp có dữ liệu kiểu
 con trỏ (mảng,cấp phát động,...) ,tham chiếu thì ta không thể dùng cách này
(trường hợp này nói sau) .

Cách xây dựng hàm tạo sao chép:
Hàm tạo sao chép sử dụng đối kiểu tham chiếu để khởi gán cho đối tượng mới :

Ten_class(Ten_class & dt1){
    ...... // Những lệnh làm việc với thuộc tính dt1 để tạo đối tượng mới.
    }

Ta thấy khi không có con trỏ và tham chiếu chỉ cần hàm mặc định là đủ (không cần khai báo)
chỉ cần : TenClass dt1(dt); //dt đã được làm việc qua.

Nhưng khi thuộc tính có kiểu con trỏ , nếu ta dùng hàm mặc định khi đó 2 thuộc tính con trỏ
của 2 đối tượng trên sẽ cùng trỏ tới một ô nhớ khi đó nếu thay đổi con trỏ của đối tượng này thì con trỏ
của đối tượng kia sẽ thay đổi , thậm chí khi ta xóa (delete) vùng nhớ của con trỏ này thì cũng
đồng nghĩa con trỏ kia cũng bị xóa (NULL)

Như vậy chỉ dùng hàm tạo sao chép mặc định thôi là chưa đủ .

Còn tại sao kiểu đối truyền vào của Copy Constructor là tham chiếu thì sau này có time mình sẽ làm tut mới về vẫn đề này .

Con trỏ this , cách thức hoạt động , ...

// Những chú ý của con trỏ this trong class :
- Ta thấy với mọi phương thức thì đối đầu tiên truyền vào cho nó chinh là con trỏ this , con trỏ
 this này không tường minh .
- Con trỏ this chứa địa chỉ của một đối tượng thuộc kiểu class nào đó .
   Vd : Ten_lop doituong;
    Lúc này this == &doituong;


 Ta xét một ví dụ :

  #include<iostream>
//#include<graphics.h>
#include<math.h>

using namespace std;
/*struct Diem1{
float x;
float y;
float dodai(Diem1 d2);
float chuvitamgiac(Diem1 d2,Diem1 d3);
void nhap(){
cout << "Nhap toa do cua diem ";
cin >> x >> y;
}
};*/

class Diem{
private :
float x;
float y;
public :
float dodai(Diem d2);
float chuvitamgiac(Diem d2,Diem d3);
void nhap(){
cout << "Nhap toa do cua diem ";
cin >> x >> y;
}
};

float Diem1 ::dodai(Diem d2){
return (sqrt(pow(this->x-d2.x,2)+pow(this->y-d2.y,2)));
}
float Diem1 :: chuvitamgiac(Diem d2 , Diem d3){
return ((*this).dodai(d2)+ this->dodai(d3) + d2.dodai(d3));
}

 int main(){
Diem d1,d2,d3;
d1.nhap();
d2.nhap();
d3.nhap();
cout << "Do dai tu d1 den d2 la : " << d1.dodai(d2) << "   " << d2.dodai(d1) << endl;
cout << "Chu vi tam giac d1,d2,d3 = " << d1.chuvitamgiac(d2,d3);
return 0;
 }

Xét phương thức dodai trong class Diem :
 trong phương thức này nhân 2 đối truyền vào là
  - một đối tượng thuộc kiểu Diem (do this quản lý)
  - biến thứ hai là đối tượng thuộc kiểu Diem d2 .
  Trong trường hợp này vai trò của this không cao lắm , ta có thể bỏ this đi vẫn được
 
Sang phương thức thứ 2 :
 trong phương thức chuvitamgiac có 3 đối được truyền vào
 - Đối tượng thuộc Diem do this quản lý
 - 2 đối tượng thuộc kiểu điểm : d2,d3.

 Ta thấy là trong trường hợp này vai trò của this rất quan trọng .

 So sánh 2 cách thức sau :
 + dùng this
 float Diem1 :: chuvitamgiac(Diem d2 , Diem d3){
return ((*this).dodai(d2)+ this->dodai(d3) + d2.dodai(d3));
}
 + không dùng this
 float Diem1 :: chuvitamgiac(Diem d2 , Diem d3){
        Diem d1;
d1.x = x;
d1.y = y;
return (d1.dodai(d2)+ d1.dodai(d3) + d2.dodai(d3));
}
Nếu với những class có nhiều thuộc tính thì không thể làm theo cách này
 2 câu lệnh d1.x = x ; d1.y = y ;
 x,y chính là (*this).x (hay this->x) (*this).y(hay this->y).

 Tóm lại việc dùng con trỏ this trở nên hữu ích khi  ta sử dụng phương thức thuộc con trỏ this
 (hay sử dụng phương thức cho đối tượng sẽ được khai báo)

Ta đã có vùng nhớ cấp phát cho lớp khi ta khai báo một đối tượng thuộc lớp , vậy khi ta khai
báo một đối tượng thuộc lớp thì máy mới cấp bộ nhớ cho con trỏ this có kiểu là class vừa được
khai báo . Tức là khi cài đặt ta viết return ((*this).dodai(d2)+ this->dodai(d3) + d2.dodai(d3));
,... thì this trong trường hợp này trong trạng thái "chờ" , chờ được đến khi khai báo đối tượng
hay được trỏ đến đối tượng . Tức là khi khai báo (*this) mới có giá trị .

Để hiểu rõ hơn ta hãy tưởng tượng :
con trỏ this như một cái xe chở hàng đứng đợi khi ta khai báo class -
khi khai báo đối tượng thuộc class xong chiếc xe chở hàng sẽ chở "đối tượng " đến phương thức
lắp vào chỗ this,chỗ được cài đặt sẵn từ trước để làm việc .

Dễ thấy con trỏ this chỉ có tác dụng trong các phương thức của class , còn ngoài ra
nó không có mấy tác dụng

còn một chú ý nữa cho các bạn chưa học lập trình hướng đối tượng (OOP) trường hợp trên dùng
được cả cho struct . (kể cả con trỏ this) 

Chú ý về hàm khởi tạo, hàm mặc định trong class

// Hàm tạo (hàm thiết lập)
+Hàm tạo được coi là phương thức đặc biệt trong lớp dùng để tạo dụng đối tượng mới .
Hãy nhớ rằng sau khi khai báo đối tương : Ten_class doituong; thì chương trình sẽ cấp phát
bộ nhớ cho đối tượng rồi sau đó gọi hàm tạo dùng để làm các công việc như gán thuộc tính
hay thức hiện một công viêc (lệnh) cụ thể nào đó .
+Phân biệt hàm tạo và các phương thức bình thường :
 * Khác nhau :
 - hàm tạo bắt buộc phải có tên trùng với tên của lớp .
 - Không khai báo kiểu cho hàm tạo
 - hàm tạo không có kiểu trả về .
 Vd : Class Ten_class{
              public :
              void Ten_lop();//Sai
 Ten_lop(); //Đúng
                 };

 *Giống nhau :
  - Hàm tạo có thể được cài đặt bên trong hoặc bên ngoài class
  - Hàm tạo có thể có đối truyền vào
  - Trong một lớp có thể có nhiều hàm tạo (cùng tên nhưng khác bộ đối).
** Có một điều chú ý là hàm tạo luôn để public.
Vd:
 Class Ten_class{
       private :
   int x,y,z;
       public :
       Ten_class(); // không dối
  Ten_class(int n,int m ,int k = 5) ; // có đối mặc định k = 5.(xem lại phần giá trị ngầm định của tham số)
 };
 Ten_class :: Ten_class(){cout << "Chào các bạn đến với C++" << endl;};
 Ten_class :: Ten_class(int n ,int m,int k=5){x = n ;y=m;z=k};
 //Ten_class :: Ten_class(){this->x = n ; this->y = m ; (*this).z = k};
 Ten_class dt1,dt2(2,5),dt3(5,6,0);

 đối tượng dt1 gọi hàm khởi tạo không tham số  hay chương trình thực hiện lệnh cout << "Chào các bạn đến với C++" << endl;
 dt2 gọi hàm có tham số và thực hiện lệnh gán dt2.x = 2 ;dt2.y = 5; dt2.z =5 ;
 dt3 gọi hàm có tham số và thực hiện lệnh gán dt3.x = 2 ;dt3.y = 6; dt3.z =0 ;

 + Chú ý :
  - Khi khai báo một biến đối tượng có thể dùng tham số để khởi gán thuộc tính cho đối tượng
  - Khi khai báo mảng đối tượng không thể dùng tham số để khởi gán
  - Câu lệnh khai báo biến đối tượng gọi tới hàm tạo một lần
  - Câu lệnh khai báo mảng có n đối tượng gọi tới hàm n lần
  ** Với các hàm có đối kiểu lớp thì đối chỉ xem là tham số 'hình thức' vì vậy sẽ không tạo
  ĐỐI TƯỢNG MỚI do đó sẽ KHÔNG GỌI hàm tạo
 
 
  Trong trường hợp class không có hàm tạo thì khi khai báo chương trình cấp phát bộ nhớ cho đối
  tượng và gọi hàm tạo mặc định hàm này không có đối và không làm gì cả : hamtao(){};
 
 
  Trong trường hợp có ít nhất một hàm tạo khi khai báo đối tượng chương trình cấp phát bộ
  nhớ cho đối tượng sau đó gọi đến hàm tạo CẦN gọi nếu không thấy hàm nào thỏa mãn chương trình
  sẽ báo lỗi .
  Những lỗi phát sinh trong quá trình sử dụng hàm tạo :
   - Chúng tao không khai báo hàm tạo không đối nhưng sử dụng nó.
   - Chúng tao vừa khai báo hàm tạo không đối vừa khai báo hàm tạo có đối nhưng trong hàm
     tạo có đối những đối ta để giá trị mặc định vd :
  hamtao(){cout << "Chao cac ban. ";};
  hamtao(int x=0; int y=1){....};
 
  int main(){Ten_class dt;}
  Lúc này chương trình sẽ không biết gọi hàm tạo nào vì cả 2 hàm tạo đều thỏa mãn

Con trỏ this , cách thức hoạt động ,...

// Những chú ý của con trỏ this trong class :
- Ta thấy với mọi phương thức thì đối đầu tiên truyền vào cho nó chinh là con trỏ this , con trỏ
 this này không tường minh .
- Con trỏ this chứa địa chỉ của một đối tượng thuộc kiểu class nào đó .
   Vd : Ten_lop doituong;
    Lúc này this == &doituong;


 Ta xét một ví dụ :

  #include<iostream>
//#include<graphics.h>
#include<math.h>

using namespace std;
/*struct Diem1{
float x;
float y;
float dodai(Diem1 d2);
float chuvitamgiac(Diem1 d2,Diem1 d3);
void nhap(){
cout << "Nhap toa do cua diem ";
cin >> x >> y;
}
};*/

class Diem{
private :
float x;
float y;
public :
float dodai(Diem d2);
float chuvitamgiac(Diem d2,Diem d3);
void nhap(){
cout << "Nhap toa do cua diem ";
cin >> x >> y;
}
};

float Diem1 ::dodai(Diem d2){
return (sqrt(pow(this->x-d2.x,2)+pow(this->y-d2.y,2)));
}
float Diem1 :: chuvitamgiac(Diem d2 , Diem d3){
return ((*this).dodai(d2)+ this->dodai(d3) + d2.dodai(d3));
}

 int main(){
Diem d1,d2,d3;
d1.nhap();
d2.nhap();
d3.nhap();
cout << "Do dai tu d1 den d2 la : " << d1.dodai(d2) << "   " << d2.dodai(d1) << endl;
cout << "Chu vi tam giac d1,d2,d3 = " << d1.chuvitamgiac(d2,d3);
return 0;
 }

Xét phương thức dodai trong class Diem :
 trong phương thức này nhân 2 đối truyền vào là
  - một đối tượng thuộc kiểu Diem (do this quản lý)
  - biến thứ hai là đối tượng thuộc kiểu Diem d2 .
  Trong trường hợp này vai trò của this không cao lắm , ta có thể bỏ this đi vẫn được
 
Sang phương thức thứ 2 :
 trong phương thức chuvitamgiac có 3 đối được truyền vào
 - Đối tượng thuộc Diem do this quản lý
 - 2 đối tượng thuộc kiểu điểm : d2,d3.

 Ta thấy là trong trường hợp này vai trò của this rất quan trọng .

 So sánh 2 cách thức sau :
 + dùng this
 float Diem1 :: chuvitamgiac(Diem d2 , Diem d3){
return ((*this).dodai(d2)+ this->dodai(d3) + d2.dodai(d3));
}
 + không dùng this
 float Diem1 :: chuvitamgiac(Diem d2 , Diem d3){
        Diem d1;
d1.x = x;
d1.y = y;
return (d1.dodai(d2)+ d1.dodai(d3) + d2.dodai(d3));
}
Nếu với những class có nhiều thuộc tính thì không thể làm theo cách này
 2 câu lệnh d1.x = x ; d1.y = y ;
 x,y chính là (*this).x (hay this->x) (*this).y(hay this->y).

 Tóm lại việc dùng con trỏ this trở nên hữu ích khi  ta sử dụng phương thức thuộc con trỏ this
 (hay sử dụng phương thức cho đối tượng sẽ được khai báo)

Ta đã có vùng nhớ cấp phát cho lớp khi ta khai báo một đối tượng thuộc lớp , vậy khi ta khai
báo một đối tượng thuộc lớp thì máy mới cấp bộ nhớ cho con trỏ this có kiểu là class vừa được
khai báo . Tức là khi cài đặt ta viết return ((*this).dodai(d2)+ this->dodai(d3) + d2.dodai(d3));
,... thì this trong trường hợp này trong trạng thái "chờ" , chờ được đến khi khai báo đối tượng
hay được trỏ đến đối tượng . Tức là khi khai báo (*this) mới có giá trị .

Để hiểu rõ hơn ta hãy tưởng tượng :
con trỏ this như một cái xe chở hàng đứng đợi khi ta khai báo class -
khi khai báo đối tượng thuộc class xong chiếc xe chở hàng sẽ chở "đối tượng " đến phương thức
lắp vào chỗ this,chỗ được cài đặt sẵn từ trước để làm việc .

Dễ thấy con trỏ this chỉ có tác dụng trong các phương thức của class , còn ngoài ra
nó không có mấy tác dụng

còn một chú ý nữa cho các bạn chưa học lập trình hướng đối tượng (OOP) trường hợp trên dùng
được cả cho struct . (kể cả con trỏ this) 

Chủ Nhật, 8 tháng 6, 2014

Hàm tạo(dựng ) trong c++

copy từ http://laptrinh.vn/d/146-ham-tao-constructor.html
Trong chương trình C++, luôn có một hàm tạo mặc định. Tuy nhiên, trong nhiều trường hợp, hàm tạo mặc định này không đáp ứng được yêu cầu xây dựng đối tượng mới.

1. Công dụng
Hàm tạo là một phương thức của lớp, dùng để tạo dựng một đối tượng mới. Chương trình dịch sẽ cấp phát bộ nhớ cho đối tượng sau đó sẽ gọi đến hàm tạo. Hàm tạo sẽ khởi gán giá trị cho các thuộc tính của đối tượng và có thể thực hiện một số công việc khác nhằm chuẩn bị cho đối tượng mới.


* Dùng hàm tạo trong khai báo
+ Khi đã xây dựng các hàm tạo, ta có thể dùng chúng trong khai báo để tạo ra một đối tượng đồng thời khởi gán cho các thuộc tính của đối tượng được tạo. Dựa vào các tham số trong khai báo mà trình biên dịch sẽ gọi đến hàm tạo nào.
+ Khi khai báo một biến đối tượng có thể sử dụng các tham số để khởi gán cho các thuộc tính của biến đối tượng.
+ Khi khai báo mảng đối tượng, không cho phép dùng các tham số để khởi gán.
+ Câu lệnh khai báo một biến đối tượng sẽ gọi tới hàm tạo 1 lần.
+ Câu lệnh khai báo một mảng n đối tượng sẽ gọi tới hàm tạo n lần.

* Dùng hàm tạo trong cấp phát bộ nhớ:
+ Khi cấp phát bộ nhớ cho một đối tượng có thể dùng các tham số để khởi gán cho các thuộc tính của đối tương:
DIEM *x = new DIEM[10, 20];
+ Khi cấp phát bộ nhớ cho một dãy đối tượng, không được phép dùng tham số để khới gán.

* Dùng hàm tạo để biểu diễn các đối tượng hằng

2. Cách viết hàm tạo

Khi viết hàm tạo cần chú ý tới 3 sự khác biệt so với các phương thức:
+ Tên của hàm tạo phải trùng với tên của lớp
+ Không khai báo kiểu cho hàm tạo
+ Hàm tạo không có kết quả trả về

class A
{
      private:
              int x, y;
              ...
      public:
              A()
              {
                  x=0;
                  y=0;
                  ...
              }
              A(int x1, int y1)
              {
                  x=x1;
                  y=y1;
                  ...
              }
              ...
};

Thứ Bảy, 7 tháng 6, 2014

[B3] Class ,các quy tắc ,chú ý tiếp

Khi ta khai báo thuộc tính của class ta làm như sau : kieu_du_lieu tenThuocTinh;
Trong thực tế ta có thể khai báo tên biến toàn cục trùng với tên thuộc tính của một class nào đó khi đó trong các phương thức muốn sủ dụng tenThuocTinh thì ta sử dụng như sau tenClass :: tenThuocTinh; dấu '::' chỉ định tenThuocTinh là của tenClass chứ không phải biến toàn cục

Tương tự như thế khi ta triển khai định nghĩa hàm ta cũng có thể làm theo cách sau :
   class SinhVien{
               .....................
              public:
               layThongTin();
                };
SinhVien :: layThongTin() { .............. };

Trong thực tế khi lập trình ngoại trừ những hàm ngắn vài ba dong code ra thì các hàm còn lại nên được cài đặt bên ngoài lớp để rõ ràng sáng sủa và dễ theo dõi

Lưu ý: Khi dùng phương thức bên trong phương thức khác của lớp , mà phương thức lại bị trùng với các hàm tự do của chương trình thì ta dùng '::' để phân biệt đây là phương thức của lớp chứ không phải hàm tự do của chương trình . 

Còn một khái niệm nữa tôi muốn đề cập đó là hàm bạn (friend) trước khi nói đến vấn đề này ta cần xem lại khái niệm private , public , protected. Private qui định những gì nằm trong nó có phạm vi hoạt động chỉ trong lớp đó , nhưng hàm bạn có thể sử dụng nó
Có hai hàm là hàm bạn cơ bản trong C++ :
- Hàm tự do là hàm bạn của một lớp .
- Phương thức (Hàm) của lớp .

Một hàm bạn của một lớp được khai báo bằng từ khóa friend khi khai báo nguyên mẫu hàm trong lớp tương ứng :

Ta có một ví dụ :
      
#include<iostream>
//#include<stdio.h>

using namespace std;
class SinhVien{
private :
string hoten;
int tuoi;
public :
void set(string name){
hoten = name;
}
string get(){
return hoten;
}
friend void hamban(SinhVien sv); // khai báo hàm bạn
};

void hamban(SinhVien sv){ //cài đặt hàm bạn
cout << "Ban " << sv.hoten << " hoc rat gioi."; // sử dụng được sv.hoten                                                                                                        // (private) 
}

int main(){
SinhVien sinhVien1;
//fflush(stdin);
sinhVien1.set("Nguyen Van A");
cout << "Ten cua ban la : " << sinhVien1.get() << endl;
hamban(sinhVien1);
return 0;
}
Ta thấy định nghĩa hàm bạn như định nghĩa một hàm tự do thông thường
Và đây là kết quả :
   
Lưu ý : + Hàm bạn tuy được định nghĩa trong class bản chất không là phương thức của class . và cách sử dụng và khai báo cũng như hàm tự do thông thường.
               + Ta có thể định nghĩa hàm bạn trong class ở chỗ nào cũng được vì hàm bạn không                    chịu  ảnh hưởng của private , public , protected. 
                         +Hàm bạn có thể truy nhập vào thuộc tính của class mà nó làm bạn , việc này bặt buộc phải                            thông qua một đối tượng cụ thể . Đối tượng cụ thể ở ví dụ trên là sv và sinhVien1 .
 
Ngoài ra một phương thức func của class X cũng có thể là hàm bạn của class Y
 Sau đây là cách cài đặt (mình copy từ tài liệu của trường đại học Bưu Chính Viễn Thông)


 
Còn muốn các phương thức của class A sử dụng được private B thì cần khai bóa class A
là hàm bạn của B
Đến đây tôi xin nhắc lại các thuộc tính của class nên để dưới private vì thế khi truy suất các thuộc tính này ta dùng hàm get/set mà tôi đã giới thiệu ở B2.