Cơ bản về Class trong C++

Công Nghệ
Cơ bản về Class trong C++
Bài viết được sự cho phép của tác giả Khiêm Lê Class là gì? Class hay lớp là một mô tả trừu tượng (abstract) của nhóm các đối tượng (object) có cùng bản chất, ngược lại mỗi một đối tượng là một thể hiện cụ thể (instance) cho những mô tả trừu tượng đó. Một class trong C++ sẽ có các đặc điểm sau: Một class bao gồm các thành phần dữ liệu (thuộc tính hay property) và các phương thức (hàm thành phần hay method). Class thực chất là một kiểu dữ liệu do người lập trình định nghĩa. Trong C++, từ khóa class sẽ chỉ điểm bắt đầu của một class sẽ được cài đặt. Factory Function vs. Class Các kiểu dữ liệu trong lập trình C/C++ (Data type) Ví dụ về một class đơn giản, class Car. Một chiếc xe hơi vậy thì sẽ có chung những đặc điểm là đều có vô lăng, có bánh xe nhiều hơn 3, có động cơ… Đó là một class, một cái model hay mẫu mà người ta đã quy định là nếu đúng như vậy thì nó là xe hơi. Nhưng mà xe thì có thể có nhiều hãng khác nhau, BMW, Vinfast, Toyota… Thì mỗi hãng xe lại có những model xe khác nhau nhưng chúng đều là xe hơi. Vậy thì trong lập trình cũng vậy, class là quy định ra một mẫu, một cái model mà các thể hiện của nó (instance) hay đối tượng (object) phải tuân theo. Khai báo class và sử dụng class Cú pháp khai báo một class cơ bản trong C++ như sau: class < ClassName > { < access_modifier > : < data_type > property ; < return_type > < method_name > ( arguments ) { return < something_match_return_type >...

Bài viết được sự cho phép của tác giả Khiêm Lê

Class là gì?

Class hay lớp là một mô tả trừu tượng (abstract) của nhóm các đối tượng (object) có cùng bản chất, ngược lại mỗi một đối tượng là một thể hiện cụ thể (instance) cho những mô tả trừu tượng đó. Một class trong C++ sẽ có các đặc điểm sau:

  • Một class bao gồm các thành phần dữ liệu (thuộc tính hay property) và các phương thức (hàm thành phần hay method).
  • Class thực chất là một kiểu dữ liệu do người lập trình định nghĩa.
  • Trong C++, từ khóa class sẽ chỉ điểm bắt đầu của một class sẽ được cài đặt.

Ví dụ về một class đơn giản, class Car. Một chiếc xe hơi vậy thì sẽ có chung những đặc điểm là đều có vô lăng, có bánh xe nhiều hơn 3, có động cơ… Đó là một class, một cái model hay mẫu mà người ta đã quy định là nếu đúng như vậy thì nó là xe hơi. Nhưng mà xe thì có thể có nhiều hãng khác nhau, BMW, Vinfast, Toyota… Thì mỗi hãng xe lại có những model xe khác nhau nhưng chúng đều là xe hơi. Vậy thì trong lập trình cũng vậy, class là quy định ra một mẫu, một cái model mà các thể hiện của nó (instance) hay đối tượng (object) phải tuân theo.

Khai báo class và sử dụng class

Cú pháp khai báo một class cơ bản trong C++ như sau:

class <ClassName> {
    <access_modifier>:
        <data_type> property;

        <return_type> <method_name>(arguments) {
            return <something_match_return_type>;
        }

        <_return_type> <_method_name>(_arguments);
};

<_return_type> <ClassName>::<_method_name>(_arguments) {
    return <something_match_return_type>;
}

Ví dụ một class cơ bản:

class Person {
    public:
        string firstName; // property
        string lastName;  // property
        int age;          // property

        void fullname() { // method
            cout << firstName << ' ' << lastName;
        }
};

Trở lại với ví dụ ở đầu bài viết, class ở đây là Car, vậy thì thuộc tính của nó chính là speed, HP,… còn phương thức chính là run, turn left, right, turn on the light…

Lưu ý: các thuộc tính có thể bị trùng tên với các tham số trong các phương thức, vậy nên chúng ta nên dùng this-> hoặc toán tử phân giải phạm vi (::), ví dụ:

class Person {
    public:
        string firstName;
        string lastName;
        int age;

        void fullname() {
            cout << this->firstName << ' ' << Person::lastName;
        }
};

Nói qua một chút về con trỏ this, con trỏ this đề cập đến thể hiện hay instance của class đó. Do đó, thông qua con trỏ this, ta có thể truy cập đến các thuộc tính hoặc phương thức thuộc class đó như trên ví dụ bên trên.

Đối với toán tử phạm vi :: dùng để xác định phương thức hoặc thuộc tính được gọi thuộc lớp nào. Như trong ví dụ trên là truy xuất thuộc tính lastName thuộc lớp Person. Nếu như gọi từ namespace hoặc emum thì toán tử :: được dùng để gọi thành viên của namepsace hoặc enum đó. Ngoài ra, toán tử phân giải phạm vi nếu không có tên lớp phía trước thì được dùng để gọi một biến bên ngoài scope. Ví dụ:

int x;
int main()
{
    int x = 2;
    ::x = 3; // x ở ngoài
    return 0;
}

Sự khác biệt giữa this và class khá rõ ràng, this (ám chỉ thể hiện của class đang sử dụng this) chỉ sử dụng được trong class, còn đối với toán tử :: có thể sử dụng được cả ở trong và ngoài class. Quá rõ ràng, nếu dùng this ở ngoài thì biết nó chỉ thằng nào đúng không! Bạn không thể thay ::x ở ví dụ trên thành this->x hay ->x được.

Cú pháp tạo object của một class và sử dụng các thuộc tính và phương thức:

// tạo một object
<className> <object>;
// gán giá trị cho thuộc tính của object
<object>.property = <value>;

// có thể sử dụng property như một biến thông thường
cout << <object>.property;
// có thể sử dụng method như một hàm thông thường
<object>.method();

Lưu ý: chỉ những thuộc tính và phương thức public thì mới có thể được sử dụng như cách trên. Ví dụ về cách sử dụng class:

Person person;
person.firstName = "Khiem";
person.lastName = "Le";
person.fullname(); // sẽ in ra màn hình là "Khiem Le"

Access modifiers & properties declaration

Access modifier là phạm vi truy cập của các thuộc tính và phương thức sẽ được khai báo bên dưới nó. Có 3 phạm vi truy cập trong C++ là public, private và protected.

  • Các thuộc tính và phương thức khai báo public thì có thể được truy cập trực tiếp thông qua instance của class đó. Các thuộc tính nên khai báo là public nếu bạn không có ràng buộc điều kiện trước khi gán (người dùng có thể thoải mái gán giá trị) hoặc bạn không cần xử lý trước khi trả về giá trị thuộc tính;
  • Đối với private thì chỉ có thể được truy cập gián tiếp qua các phương thức public (Getter và setter). Các thuộc tính private thường được sử dụng khi bạn không mong muốn người khác có thể tùy ý gán giá trị hoặc là bạn muốn xử lý trước khi trả về giá trị.
  • Đối với protected, các phương thức và thuộc tính chỉ có thể truy cập qua các class kế thừa nó hoặc chính nó (sẽ được nói kĩ hơn trong bài kế thừa C++).

Ví dụ của access modifier:

class MyClass
{
	public:
		int public_property;

	private:
		int _private_property;

	// protected sẽ được trình bày trong bài kế thừa và đa hình trong C++
};

Đối với quy cách đặt tên biến, bạn có thể sử dụng PascalCase, CammelCase… nhưng đối với các thuộc tính và phương thức private bạn nên đặt tên có dấu _ đầu. Ví dụ như _privateProp. Trong một số ngôn ngữ bật cao, thậm chí đã không còn từ khóa private mà thay vào đó sẽ chỉ là dấu _ trước tên biến (ví dụ như Dart).

Method declaration

Phương thức cũng giống như một hàm bình thường, bạn cũng có thể không trả về giá trị, có thể có hoặc không có tham số, có thể override hàm… Đối với các tham số truyền vào phương thức, bạn cũng có thể đặt tên trùng với thuộc tính của class, sử dụng kết hợp với toán tử :: và con trỏ this. Hoặc bạn có thể đặt tên khác với thuộc tính (thường thì sẽ thêm dấu _ trước tên tham số như là thuộc tính private vậy).

Đối với phương thức thì có hai cách định nghĩa thi hành: định nghĩa thi hành trong lúc định nghĩa class và định nghĩa thi hành bên ngoài class.

Định nghĩa thi hành bên trong class:

class Animal {
    public:
        string sound;

        void makeNoise() {
            cout << sound;
        }
};

Định nghĩa thi hành bên ngoài class:

class Animal {
    public:
        string sound;

        void makeNoise();
};

void Animal::makeNoise() {
    cout << sound;
}

Lưu ý: các phương thức không làm thay đổi giá trị thuộc tính của đối tượng thì nên có từ khóa “const” trước phần thân. Ví dụ:

class Animal {
    public:
        string sound;

        void makeNoise() const;
};

void Animal::makeNoise() const {
    cout << sound;
}

Getter & setter

Đối với thuộc tính private, ta không thể truy cập trực tiếp từ bên ngoài, vậy có cách nào để truy cập? Đây là lúc sử dụng phương thức. Các phương thức lấy giá trị của thuộc tính được gọi là getter, các phương thức gán giá trị cho thuộc tính được gọi là setter.

class MyClass {
    private:
        int _age;

    public:
        int getAge() {         // getter
            return _age;
        }

        void setAge(int age) { // setter
            _age = age;
        }
};

Việc sử dụng các thuộc tính private nhằm mục đích không cho người khác tùy ý thay đổi giá trị của thuộc tính đó, ngoài ra còn giúp bạn xử lý kết quả trước khi trả về cho người yêu cầu. Việc đó được thực hiện thông qua getter và setter. Ví dụ:

class MyClass {
    private:
        int _age;

    public:
        bool isOldEnough() {
            if (_age >= 18)
                return 1;
            return 0;
        }

        int getAge() {
            return _age;
        }

        void setAge(int age) {
            if (age < 18) {
                cout << "You are not old enoughn";
            } else {
                _age = age;
            }
        }
};

Lưu ý cách đặt tên getter và setter. Bạn nên đặt get vào trước tên getter và set vào trước tên setter như ví dụ bên trên của mình. Và cũng theo như phần lưu ý cuối mục “Method declaration” ở trên, các getter nên đặt là “const” bởi vì getter chỉ lấy giá trị chứ không thay đổi giá trị thuộc tính.

Constructor

Constructor hay hàm dựng là một hàm đặc biệt, nó sẽ được gọi ngay khi chúng ta khởi tạo một object. Vậy thì tại sao chúng ta lại cần có constructor?

Nếu bạn để ý ví dụ trên bạn sẽ thấy ta phải khởi tạo một object sau đó gán các property và sử dụng, việc này rất tốn thời gian. Constructor sẽ giúp chúng ta giải quyết việc này. Cú pháp khai báo một constructor giống với hàm nhưng không có kiểu dữ liệu trả về:

class MyClass {
  public:
    MyClass() { // constructor
      cout << "Hello World!";
    }
};

// sử dụng
MyClass object; // sẽ in ra màn hình "Hello World"

Lưu ý constructor phải được khai báo public. Công dụng chính của constructor chính là khởi gán các thuộc tính, vậy nên constructor thường được định nghĩa như sau:

class Person {
	public:
	    string firstName;
	    string lastName;
	    int age;

	    Person(string _firstName, string _lastName, int _age)
	    {
	        firstName = _firstName;
	        lastName = _lastName;
	        age = _age;
	    }

	    void fullname() {
			cout << firstName << ' ' << lastName;
	    }
};

Constructor cũng có thể định nghĩa thi hành bên ngoài class giống như phương thức vậy:

class Person {
	public:
	    string firstName;
	    string lastName;
	    int age;

	    Person(string _firstName, string _lastName, int _age);

	    void fullname() {
	        cout << firstName << ' ' << lastName;
	    }
};

Person::Person(string _firstName, string _lastName, int _age)
{
	firstName = _firstName;
	lastName = _lastName;
	age = _age;
}

Để khởi tạo một object thông qua constructor, ta làm như sau:

Person person("Khiem", "Le", 20);
person.fullname(); // Khiem Le

Như vậy chúng ta không cần phải set từng thuộc tính cho object đó mà khởi tạo trực tiếp qua constructor.

Destructor

Đối với một số ngôn ngữ lập trình khác có thể destructor không phổ biến, nhưng đối với C++, việc được quản lý bộ nhớ một cách hoàn toàn do người lập trình làm chủ thì destructor là vô cùng cần thiết. Hãy thử nghĩ xem, trong số thuộc tính của class bạn định nghĩa có một con trỏ, mảng động… và bạn không sử dụng desctructor thì sẽ như thế nào? Đương nhiên sẽ xảy ra chuyện rò rỉ bộ nhớ và điều này cực kì không tốt. Với destructor bạn có thể xóa con trỏ đi khi object được thu hồi hoặc bạn có thể gọi tường minh destructor.

Cách khai báo destructor cũng giống như đối với constructor nhưng có kí hiệu ~ phía trước:

class MyClass {
    public:
        MyClass() { // constructor
            cout << "Constructor is executedn";
        }

        ~MyClass() { // destructor
            cout << "Constructor is executedn";
        }
};

// Khởi tạo object
ClassName t; // gọi constructor không tường minh
// Gọi destructor tường minh
t.~MyClass();

Static member

Static member hay thành viên tĩnh trong class C++ cũng tương tự như với static variable (biến tĩnh) trong function. Đối với function, sau khi thực hiện xong khối lệnh và thoát thì biến tĩnh vẫn sẽ không mất đi. Đối với class, thành viên tĩnh sẽ là thuộc tính dùng chung cho tất cả các đối tượng của class đó, cho dù là không có đối tượng nào tồn tại. Tức là bạn có thể khai báo nhiều object, mỗi object các thuộc tính của nó đều khác nhau nhưng riêng static thì chỉ có một và static member tồn tại trong suốt chương trình cho dù có hay không có object nào của nó hay nói ngắn gọn là dùng chung một biến static.

Các thành viên tĩnh của class có thể được truy cập từ bất kì đối tượng nào của class đó hoặc thông qua toán tử phạm vi (::). Biến tĩnh cũng có phạm vi truy cập như một biến thông thường (public, private và protected). Để khai báo một biến tĩnh, ta thực hiện thêm từ khóa “static” vào trước kiểu dữ liệu, sau đó khởi tạo giá trị bên ngoài như sau:

class MyClass {
    public:
        static int count;
};

int MyClass::count = 0;

Bây giờ bạn có thể sử dụng biến tĩnh như cách đã trình bày bên trên:

cout << MyClass::count; // 0
MyClass::count++; // 1

Để thấy được sự “dùng chung” của static member, các bạn có thể xem ví dụ sau:

MyClass khiemle;
khiemle.count = 100;
cout << MyClass::count; // 100

Trong ví dụ trên rõ ràng là mình chỉ set thuộc tính của object khiemle thôi nhưng mà khi in biến count qua toán tử phạm vi thì vẫn được kết quả là 100. Nghĩa là tất cả các object đều dùng chung thuộc tính static đó. Bạn có thể đặt thuộc tính tĩnh là hằng như sau:

class MyClass {
    public:
        static const int count;
};

const int MyClass::count = 0;

Thuộc tính là const sẽ không được thay đổi trong suốt chương trình, do đó bạn có thể gán giá trị ngay khi khai báo như sau:

class MyClass {
    public:
        static const int count = 0;
};

//const int MyClass::count = 0; Không cần dòng này nữa

Lưu ý là đối với static member không phải là hằng bạn sẽ không gán giá trị như cách trên được:

class MyClass {
    public:
        static int count = 0; // không được phép
};

Tiếp theo là phương thức tĩnh. Phương thức tĩnh cũng giống như thuộc tính tĩnh, chúng ta có thể gọi trực tiếp qua toán tử phạm vi mà không cần một object nào của nó tồn tại cả.

class MyClass {
	public:
	    static void sayHello() {
	        cout << "Hello";
	    }
};

MyClass::sayHello(); // Hello

Lưu ý là các phương thức tĩnh sẽ chỉ truy cập được đến các biến tĩnh và phương thức tĩnh khác chứ không được truy cập thành viên khác ngoài static member.

class Person {
	public:
	    string firstName;
	    string lastName;

	    static void fullname() {
	        cout << firstName << ' ' << lastName; // không hợp lệ
	    }
};

Tổng kết

Qua bài viết này, mình đã giới thiệu cho các bạn về class, thuộc tính, phương thức và hàm dựng trong C++. Trong bài viết thì cách đặt tên hàm, biến có phần không thống nhất với nhau, bạn nên chọn một cách đặt tên phù hợp nhất với bản thân để clean code. Nếu có sai xót hoặc thắc mắc gì, các bạn có thể để lại bình luận bên dưới bài viết để giúp mình phát triển bài viết tốt hơn. Cảm ơn các bạn đã theo dõi bài viết!

Bài viết gốc được đăng tải tại khiemle.dev

Có thể bạn quan tâm:

Xem thêm Việc làm C++ hấp dẫn trên Station D

Bài viết liên quan

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bài viết được sự cho phép của tác giả Chung Nguyễn Hôm nay, nhóm Laravel đã phát hành một phiên bản chính mới của “ laravel/installer ” bao gồm hỗ trợ khởi động nhanh các dự án Jetstream. Với phiên bản mới này khi bạn chạy laravel new project-name , bạn sẽ nhận được các tùy chọn Jetstream. Ví dụ: API Authentication trong Laravel-Vue SPA sử dụng Jwt-auth Cách sử dụng Laravel với Socket.IO laravel new foo --jet --dev Sau đó, nó sẽ hỏi bạn thích stack Jetstream nào hơn: Which Jetstream stack do you prefer? [0] Livewire [1] inertia > livewire Will your application use teams? (yes/no) [no]: ... Nếu bạn đã cài bộ Laravel Installer, để nâng cấp lên phiên bản mới bạn chạy lệnh: composer global update Một số trường hợp cập nhật bị thất bại, bạn hãy thử, gỡ đi và cài đặt lại nha composer global remove laravel/installer composer global require laravel/installer Bài viết gốc được đăng tải tại chungnguyen.xyz Có thể bạn quan tâm: Cài đặt Laravel Làm thế nào để chạy Sql Server Installation Center sau khi đã cài đặt xong Sql Server? Quản lý các Laravel route gọn hơn và dễ dàng hơn Xem thêm Tuyển dụng lập trình Laravel hấp dẫn trên Station D

By stationd
Principle thiết kế của các sản phẩm nổi tiếng

Principle thiết kế của các sản phẩm nổi tiếng

Tác giả: Lưu Bình An Phù hợp cho các bạn thiết kế nào ko muốn làm code dạo, design dạo nữa, bạn muốn cái gì đó cao hơn ở tầng khái niệm Nếu lập trình chúng ta có các nguyên tắc chung khi viết code như KISS , DRY , thì trong thiết kế cũng có những nguyên tắc chính khi làm việc. Những nguyên tắc này sẽ là kim chỉ nam, nếu có tranh cãi giữa các member trong team, thì cứ đè nguyên tắc này ra mà giải quyết (nghe hơi có mùi cứng nhắc, mình thì thích tùy cơ ứng biến hơn) Tìm các vị trí tuyển dụng designer lương cao cho bạn Nguyên tắc thiết kế của GOV.UK Đây là danh sách của trang GOV.UK Bắt đầu với thứ user cần Làm ít hơn Thiết kế với dữ liệu Làm mọi thứ thật dễ dàng Lặp. Rồi lặp lại lần nữa Dành cho tất cả mọi người Hiểu ngữ cảnh hiện tại Làm dịch vụ digital, không phải làm website Nhất quán, nhưng không hòa tan (phải có chất riêng với thằng khác) Cởi mở, mọi thứ tốt hơn Bao trừu tượng luôn các bạn, trang Gov.uk này cũng có câu tổng quát rất hay Thiết kế tốt là thiết kế có thể sử dụng. Phục vụ cho nhiều đối tượng sử dụng, dễ đọc nhất nhất có thể. Nếu phải từ bỏ đẹp tinh tế – thì cứ bỏ luôn . Chúng ta tạo sản phẩm cho nhu cầu sử dụng, không phải cho người hâm mộ . Chúng ta thiết kế để cả nước sử dụng, không phải những người đã từng sử dụng web. Những người cần dịch vụ của chúng ta nhất là những người đang cảm thấy khó sử dụng dịch...

By stationd
Hiểu về trình duyệt – How browsers work

Hiểu về trình duyệt – How browsers work

Bài viết được sự cho phép của vntesters.com Khi nhìn từ bên ngoài, trình duyệt web giống như một ứng dụng hiển thị những thông tin và tài nguyên từ server lên màn hình người sử dụng, nhưng để làm được công việc hiển thị đó đòi hỏi trình duyệt phải xử lý rất nhiều thông tin và nhiều tầng phía bên dưới. Việc chúng ta (Developers, Testers) tìm hiểu càng sâu tầng bên dưới để nắm được nguyên tắc hoạt động và xử lý của trình duyệt sẽ rất hữu ích trong công việc viết code, sử dụng các tài nguyên cũng như kiểm thử ứng dụng của mình. Cách để npm packages chạy trong browser Câu hỏi phỏng vấn mẹo về React: Component hay element được render trong browser? Khi hiểu được cách thức hoạt động của trình duyệt chúng ta có thể trả lời được rất nhiều câu hỏi như: Tại sao cùng một trang web lại hiển thị khác nhau trên hai trình duyệt? Tại sao chức năng này đang chạy tốt trên trình duyệt Firefox nhưng qua trình duyệt khác lại bị lỗi? Làm sao để trang web hiển thị nội dung nhanh và tối ưu hơn một chút?… Hy vọng sau bài này sẽ giúp các bạn có một cái nhìn rõ hơn cũng như giúp ích được trong công việc hiện tại. 1. Cấu trúc của một trình duyệt Trước tiên chúng ta đi qua cấu trúc, thành phần chung và cơ bản nhất của một trình duyệt web hiện đại, nó sẽ gồm các thành phần (tầng) như sau: Thành phần nằm phía trên là những thành phần gần với tương tác của người dùng, càng phía dưới thì càng sâu và nặng về xử lý dữ liệu hơn tương tác. Nhiệm...

By stationd
Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Lĩnh vực EdTech (ứng dụng công nghệ vào các sản phẩm giáo dục) trên toàn cầu hiện nay đã tương đối phong phú với nhiều tên tuổi lớn phân phối đều trên các hạng mục như Broad Online Learning Platforms (nền tảng cung cấp khóa học online đại chúng – tiêu biểu như Coursera, Udemy, KhanAcademy,…) Learning Management Systems (hệ thống quản lý lớp học – tiêu biểu như Schoology, Edmodo, ClassDojo,…) Next-Gen Study Tools (công cụ hỗ trợ học tập – tiểu biểu như Kahoot!, Lumosity, Curriculet,…) Tech Learning (đào tạo công nghệ – tiêu biểu như Udacity, Codecademy, PluralSight,…), Enterprise Learning (đào tạo trong doanh nghiệp – tiêu biểu như Edcast, ExecOnline, Grovo,..),… Hiện nay thị trường EdTech tại Việt Nam đã đón nhận khoảng đầu tư khoảng 55 triệu đô cho lĩnh vực này nhiều đơn vị nước ngoài đang quan tâm mạnh đến thị trường này ngày càng nhiều hơn. Là một trong những xu hướng phát triển tốt, và có doanh nghiệp đã hoạt động khá lâu trong ngành nêu tại infographic như Topica, nhưng EdTech vẫn chỉ đang trong giai đoạn sơ khai tại Việt Nam. Tại Việt Nam, hệ sinh thái EdTech trong nước vẫn còn rất non trẻ và thiếu vắng nhiều tên tuổi trong các hạng mục như Enterprise Learning (mới chỉ có MANA), School Administration (hệ thống quản lý trường học) hay Search (tìm kiếm, so sánh trường và khóa học),… Với chỉ dưới 5% số dân công sở có sử dụng một trong các dịch vụ giáo dục online, EdTech cho thấy vẫn còn một thị trường rộng lớn đang chờ được khai phá. *** Vừa qua Station D đã công bố Báo cáo Vietnam IT Landscape 2019 đem đến cái nhìn toàn cảnh về các ứng dụng công...

By stationd