1. Định nghĩa lớp:

2. Tạo đối tượng của một lớp và cách truy cập đến phương thức và thuộc tính của lớp

3. Các từ khóa phạm về vi truy cập các trường trong một lớp

4. Hàm copy contructor mặc định và hàm copy contructor:
  Mục đích: copy contructor là hàm tạo đặc biệt của C++, được sử dụng với mục đích tạo ra một bản sao của một đối tượng đã có, được truyền vào thông qua tham chiếu hằng của địa chỉ đến đối tượng đó.
    Hàm sao chép mặc định (copy contructor default)
    - Về hàm sao chép mặc định và cách hoạt động của chúng
 
    Hàm sao chép (copy contructor)
  - Khi có ít nhất một trường trong đối tượng là kiểu con trỏ (hoặc tham chiếu) thì ta phải viết lại hàm sao chép.
  - Các dạng của hàm sao chép như sau
X (const X& copy_from_me)
X (X* copy_from_me)
X (X& copy_from_me)
X (const X&copy_from_me, int = 10, float = 1.0)

Ví dụ về hàm sao chép: copy constructor
Nếu các trường không là con trỏ: qua ví dụ dưới đây vùng nhớ giữa các các biến progame.age và manager.age là khác nhau.
#include <iostream>

using namespace std;

class Employee{
public:
      int age;
      Employee(int x) {//Hàm constructor có tham số
            age = x;
      }
      int GetAge() {
            return age;
      }
      void SetAge(int x){
            age = x;
      }
};

void main()
{
      Employee programmer(25);
      cout << programmer.GetAge() << endl;
      Employee manager = programmer);//sẽ gọi hàm sao chép mặc định
      cout << manager.GetAge() << endl;
      manager.SetAge(30);//Vùng nhớ khác nhau nên khi thay đổi nội dung biến manager.age thì giá trị ở progammer.age vẫn không thay đổi như ví dụ dưới đây   
      getchar();
}

Nếu thay 1 số trường là con trỏ:
#include <iostream>

using namespace std;

class Employee{
public:
      int *age;
      Employee(int x){
            age = new int;
            *age = x;
      }
      int GetAge(){
            return *age;
      }
      void SetAge(int x){
            *age = x;
      }
      ~Employee(){
            delete age;
            age = NULL;

      }
};
void main()
{
      Employee programmer(25);
      cout << "programmer: " << programmer.GetAge() << endl;
      Employee manager = programmer;//Sẽ gọi hàm SAO CHÉP MẶC ĐỊNH nên vùng nhớ                       //của programmer.age và manager.age cùng trỏ vào một vùng 
     
      cout << "manager:    " << manager.GetAge() << endl;
      manager.SetAge(30);//Thay đổi vùng nhớ age của manager thì vùng nhớ                                  programmer.age thay đổi theo
                         //Hướng giải quyết --> Viết lại hàm copy contructor                                thay hàm copy constructor default
      cout << "programmer: " << programmer.GetAge() << endl;//kết quả thay đổi 
      cout << "manager:    " << manager.GetAge() << endl;
      getchar();
}

-->Hướng giải quyết trường hợp này: không sử dụng hàm sao chép mặc định bằng cách viết thêm hàm copy constructor xem code bên dưới
#include <iostream>

using namespace std;

class Employee{
public:
      int *age;
      Employee(int x){
            age = new int;
            *age = x;
      }
      Employee(Employee &people){//Viết thêm hàm copy constructor này
            age = new int;
            *age = *people.age;
      }
      int GetAge(){
            return *age;
      }
      void SetAge(int x){
            *age = x;
      }
      ~Employee(){
            delete age;
            age = NULL;

      }
};
void main()
{
      Employee programmer(25);
      cout << "programmer: " << programmer.GetAge() << endl;
      Employee manager = programmer;//sẽ gọi hàm copy constructor viết thêm ở trên
                                   //programmer.age và manager.age sẽ trỏ vào 2 vùng nhớ khác nhau
      cout << "manager:    " << manager.GetAge() << endl;
      manager.SetAge(30);
      cout << "programmer: " << programmer.GetAge() << endl;
      cout << "manager:    " << manager.GetAge() << endl;   //chỉ thay đổi manager.age
      getchar();
}


Hỏi: 2 hàm sau
      Employee(Employee &people, int x){//Hàm này là hàm constructor không ?
            //*age = x;//*people.age;
      }


      Employee(Employee &people, int x = 10){//Viết thêm hàm copy constructor này
            age = new int;
            *age = x;//*people.age;
      }

   Ví dụ về hàm sao chép:


#include <iostream>

using namespace std;

class Employee{
private:
      int _id;
      char *_name;
public:
      Employee(char *name, int id);
      //Employee(Employee people);//copy constructor default hàm sao chép mặc định
      //Employee(Employee &human);//copy constructor hàm sao chép
      ~Employee();
      char *GetName(){
            return _name;
      }
      int GetId(){
            return _id;
      }
};

Employee::Employee(char *name, int id)
{
      _id = id;
      _name = new char[strlen(name) + 1];
      strcpy(_name, name);
}

//Employee::Employee(Employee &humman)//copy constructor
//{
//    _id = humman.GetId();
//    _name = new char[strlen(humman._name) + 1];
//    strcpy(_name, humman._name);
//}

Employee::~Employee()
{
      delete []_name;
}

void main()
{
      Employee programmer("ABC", 31);//Khởi tạo
      cout << programmer.GetName() << endl;
      Employee manager(programmer);//Nếu sử dụng copy contructor mặc định địa chỉ ô nhớ của progame._name và manager._name giống nhau
                                   //dẫn đến nếu thay đổi nội dung của một người thì người kia cũng đổi theo
      cout << manager.GetName() << endl;
      //strcpy(manager._name, "HIE");//Nếu set lại name thì người kia cũng đổi theo
      getchar();
}


Ví dụ về hàm : 


#include <iostream>

using namespace std;

class Student{
private:
      char *_name;
public:
      Student(){
            _name = NULL;
      }

      Student(const Student &student){
            _name = new char[strlen(student._name) + 1];
            strcpy(_name, student._name);
      }

      void SetName(const char *str){
            //Việc kiểm tra biến _name ở (1) để xem biến _name có trỏ tớ vùng nhớ nào không ?
            //Nếu có trỏ tới vùng nhớ nào đó thì ta phải xóa vùng nhớ đó trước khi cấp phát
            //vùng nhớ mới (2) cho biến _name để tránh trường hợp vùng nhớ cũ không được quản lý
            //bởi con trỏ nào --> dẫn đến lead memory.
            if(_name != NULL) {//(1)
                  delete []_name;//Nếu không xóa vùng nhớ cũ trước khi cấp phát vùng nhớ mới thì sẽ bị lead memory
            }
            _name = new char[strlen(str) + 1];//(2)
            strcpy(_name, str);
      }

      void Print(){
            cout << _name << endl;
      }
      ~Student(){
            if (_name != NULL)
            {
                  delete []_name;
            }
      }
};

void main()
{
      Student a;
      a.SetName("Nguyen Van A");
      a.Print();
      Student b = a;
      b.SetName("Nguyen Van B");
      b.Print();
      getchar();
}

5. Hàm hủy


6.Con trỏ this

7. Kế thừa

8. Đa hình (Polymorphism)
1. Tính trừu tượng trong C++, cài đặt với từ khóa virtual. Từ khóa virtual không được đặt ra ngoài lớp
2. Không thể tạo ra một đối tượng từ lớp trừu tượng. Lớp trừu tượng lớp chứa các phương thức ảo thuần túy như sau:
Virutual void tên_phương_thức() = 0;//gán bằng 0 thay cho việc cài đặt phương thức này
3. Bất kỳ lớp dẫn xuất từ một lớp sở trừu tượng phải định nghĩa lại tất cả các phương thức thuần túy ảo thừa hưởng:
        - Bằng các phương thức ảo thuần túy

        - Hoặc những định nghĩa thực sự

Quy tắc về cách gọi các phương thức ảo:
Lời gọi phương thức tĩnh
Lời gọi phương thức ảo
1. Nếu lời gọi xuất phát từ một đối tượng của lớp nào thì phương thức của lớp đó sẽ được gọi

2. Nếu lời gọi xuất phát từ một con trỏ kiểu lớp nào, thì phương thức của lớp đó sẽ được gọi bất kể con trỏ chứa địa chỉ của đối tượng nào.
2. Nếu lời gọi tới phương thức ảo từ một con trỏ thì phụ thuộc vào đối tượng cụ thể mà con trỏ đó trỏ tới: con trỏ đang trỏ tới đối tượng của lớp nào thì phương thức của lớp đó sẽ được gọi.

Ví dụ: Tạo lớp Cat kế thừa từ lớp Animal
            Trong lớp Animal có hàm:
                     public:
                     virtual void Eat() {std::cout << "Animal: Eat\n";};
                            void Run() {std::cout << "Animal: Run\n";};
            Trong lớp Cat overiding lại 2 hàm ở lớp cha Animal:
                     virtual void Eat() {std::cout << "Cat: Eat\n";};

                            void Run() {std::cout << "Cat: Run\n";};
            Trong hàm main.cpp:
                            Animal *obj;//obj là con trỏ có KIỂU Animal và trỏ tới ĐỐI TƯỢNG Cat
                      obj = new Cat();

                      obj->Eat();//--> Cat:Eat
                    obj->Run();//--> Aniaml:Run


//Animal.h
#pragma once

class Animal
{
public:
     virtual void Eat();
     void Run();
};


//Animail.cpp
#include "Animal.h"
#include <iostream>

void Animal::Eat()
{
     std::cout << "Animal: Eat\n";
}

void Animal::Run()
{
     std::cout << "Animal: Run\n";
}


//Cat.h
#pragma once
#include "animal.h"

class Cat :   public Animal
{
public:
     void Eat();
     void Run();
};



//Cat.cpp
#include "Cat.h"
#include <iostream>

void Cat::Eat()//overiding lai ham Eat cua lop cha Animal
{
     std::cout << "Cat: Eat\n";
}

void Cat::Run()//overiding lai ham Eat cua lop cha Animal
{
     std::cout << "Cat: Run\n";
}


//main.cpp
#include <iostream>
#include "Animal.h"
#include "Cat.h"

void main()
{
     printf("1. Doi tuong kieu con tro------\n");
     Animal *obj;
     obj = new Cat();

     obj->Eat();//--> Cat:Eat
     obj->Run();//--> Aniaml:Run
    
     printf("2. Doi tuong obj1------\n");
     Animal obj1;
     obj1.Eat();//Animal:Eat
     obj1.Run();//Animal:Run
    
     printf("3. Doi tuong obj2------\n");
     Cat obj2;
     obj2.Eat(); //--> Cat:Eat
     obj2.Run(); //--> Cat:Eat

     system("pause");
}

Kết quả:


Ví dụ:
Tạo lớp A có hàm virtual hienthi() và hàm Todo()
       lớp B kế thừa từ lớp A: cài đặt lại hàm hiển thị() và hàm Todo()
       lớp C kế thừa từ lớp B: cài đặt lại hàm hiển thị() và hàm Todo()
       lớp D kế thừa từ lớp A: cài đặt lại hàm hiển thị() và hàm Todo()
#include <iostream>

using namespace std;

class A{
public:
      virtual void HienThi(){
            cout << "A:hienthi\n" << endl;
      };
      void Todo(){
            cout << "A:Todo\n" << endl;
      }
};
class B: public A{
public:
      void Hienthi(){
            cout << "B:hienthi\n" << endl;
      };
      void Todo(){
            cout << "B:Todo\n" << endl;
      }
};

class C: public B{
public:
      void Hienthi(){
            cout << "C:hienthi\n" << endl;
      };
      void Todo(){
            cout << "C:Todo\n" << endl;
      }
};

class D: public A{
public:
      void hienthi(){
            cout << "D:hienthi\n" << endl;
      };
      void Todo(){
            cout << "D:Todo\n" << endl;
      }
};
void main()
{
      A *p;//Khởi tạo p là con trỏ có kiểu A
      A a; //a là biến đối tượng kiểu A
      B b; //b là biến đối tượng kiểu B
      C c; //c là biến đối tượng kiểu C
      D d; //d là biến đối tượng kiểu D
      //1
      p = &a;     //p trỏ tới đối tượng a của lớp A
      p->HienThi();//gọi tới hàm A:hienthi()
      p->Todo();   //gọi tới hàm A:Todo()

      //2
      p = &b;     //p trỏ tới đối tượng b của lớp B
      p->HienThi();//gọi tới hàm B:hienthi()
      p->Todo();   //gọi tới hàm A:Todo()

      //3
      p = &c;     //p trỏ tới đối tượng c của lớp C
      p->HienThi();//gọi tới hàm C:hienthi()
      p->Todo();   //gọi tới hàm A:Todo()

      //4
      p = &d;     //p trỏ tới đối tượng d của lớp D
      p->HienThi();//gọi tới hàm D:hienthi()
      p->Todo();   //gọi tới hàm A:Todo()

      getchar();
}


9. Overriding vs. Overloading

10. Operator overloading

11.Class’ static member