Class trong C++

Class trong C++ là khái niệm được người dùng định nghĩa như một kiểu dữ liệu đơn giản hay một cấu trúc dữ liệu, được khai báo bằng từ khóa class, nó chứa các biến (còn gọi là thuộc tính) hay các hàm (còn gọi là phương thức). Các phần tử của class được quản lý bởi ba thuộc tính truy cập: "private", "protected" hoặc "public" (thuộc tính mặc định khi truy cập vào một phần tử trong class là "private"). Các phần tử private không thể được truy cập bên ngoài class mà chỉ có thể được truy cập thông qua các phương thức của class chứa chúng. Ngược lại, các phần tử "public" có thể được truy cập ở bất kỳ class nào.

Thể hiện của một kiểu dữ liệu lớp được gọi là các đối tượng và có thể chứa các biến, hằng, hàm thành viên và nạp chồng toán tử được xác định bởi lập trình viên.

== Sự khác nhau giữa kiểu cấu trúc (struct) và lớp (class) trong C++ ==
Trong C++, một lớp được định nghĩa với từ khóa "class và theo mặc định, nó có các phần tử" private". Một "cấu trúc" được định nghĩa với từ khóa struct", theo mặc định, các thành viên của struct có thuộc tính truy cập kiểu "public".

== Lớp tổng hợp ==
Một lớp tổng hợp là một lớp không có khai báo bộ khởi tạo, không có thuộc tính truy cập riêng tư hay bảo vệ của các thành viên dữ liệu không tĩnh, không có các lớp cơ sở, không có phương thức ảo. Như vậy một lớp có thể được khởi tạo bằng cách gán thể hiện của lớp bằng một danh sách lần lượt các giá trị thuộc tính ngăn cách nhau bởi dấu phẩy, danh sách này được bao quanh bởi cặp dấu ngoặc nhọn. Đoạn code dưới đây có cùng ý  nghĩa trong C và C++.struct C {

struct D {

// khởi tạo một đối tượng kiểu C với một danh sách khởi tạo
C c = {1, 2.0};

/* D có một tập hợp con của loại C. Trong những trường 
hợp như vậy, các mệnh đề khởi tạo có thể được lồng vào nhau */
D d = {10, 20.0, {1, 2.0}};

=== Cấu trúc POD ===
Cấu trúc POD (Cấu trúc dữ liệu cũ thuần túy) là một lớp tổng hợp không có thành viên dữ liệu không tĩnh thuộc kiểu non-POD-struct, non-POD-union (hoặc mảng thuộc các kiểu như vậy) hoặc tham chiếu và không có người dùng - toán tử gán được xác định và không có trình hủy do người dùng xác định. Một cấu trúc POD có thể được cho là tương đương với C ++ của cấu trúc C. Trong hầu hết các trường hợp, cấu trúc POD sẽ có cùng bố cục bộ nhớ với cấu trúc tương ứng được khai báo trong C. Vì lý do này, cấu trúc POD đôi khi được gọi một cách thông tục là "cấu trúc kiểu C"

Thuộc tính được chia sẻ giữa cấu trúc trong C và cấu trúc POD trong C++

- Các thành viên dữ liệu được cấp phát để các thành viên sau này có địa chỉ cao hơn trong một đối tượng, ngoại trừ trường hợp được phân tách bởi một chỉ định truy cập.
- Hai kiểu cấu trúc POD tương thích với bố cục nếu chúng có cùng số lượng thành viên dữ liệu không tĩnh và các thành viên dữ liệu không tĩnh tương ứng (theo thứ tự) có kiểu tương thích với bố cục.
- Cấu trúc POD có thể chứa phần đệm không tên.
- Một con trỏ đến đối tượng cấu trúc POD, được chuyển đổi phù hợp bằng cách sử dụng diễn giải lại, trỏ đến thành viên ban đầu của nó và ngược lại, ngụ ý rằng không có phần đệm ở đầu cấu trúc POD.
- Một cấu trúc POD có thể được sử dụng với macro offset.

== Khai báo và sử dụng ==
Các lớp C++ có các thành viên riêng của chúng. Các thành viên này bao gồm các biến (bao gồm các cấu trúc và lớp khác), các hàm (các định danh cụ thể hoặc các toán tử nạp chồng) được gọi là các phương thức, các hàm tạo và các hàm hủy. Các thành viên được tuyên bố là có thể truy cập công khai hoặc riêng tư bằng cách sử dụng công cụ xác định quyền truy cập codice_1 và codice_2 tương ứng. Bất kỳ thành viên nào gặp sau một mã xác định sẽ có quyền truy cập liên quan cho đến khi gặp một bộ chỉ định khác. Ngoài ra còn có sự kế thừa giữa các lớp có thể sử dụng codice_3

=== Lớp toàn cục và lớp cục bộ ===
Một lớp được định nghĩa bên ngoài tất cả các phương thức là một lớp toàn cục vì các đối tượng của nó có thể được tạo từ bất kỳ đâu trong chương trình. Nếu nó được định nghĩa trong một thân hàm thì đó là một lớp cục bộ vì các đối tượng của lớp như vậy là cục bộ đối với phạm vi hàm.

=== Khai báo cơ bản và các biến thành viên ===
Các lớp được khai báo với từ khóa codice_4 hoặc codice_5. Tuyên bố của các thành viên được đặt trong tuyên bố này.
struct Person {

class Person {
Các định nghĩa trên là tương đương về mặt chức năng. Một trong hai mã sẽ xác định các đối tượng kiểu codice_6 là có hai thành viên dữ liệu công khai, codice_7 và codice_8. Dấu chấm phẩy sau dấu ngoặc nhọn là bắt buộc.

Sau một trong các khai báo này (nhưng không phải cả hai), codice_6 có thể được sử dụng như sau để tạo các biến mới được xác định của kiểu dữ liệu codice_6:
1. include <iostream>
2. include <string>

struct Person {

int main() {
Khi chạy chương trình trên sẽ in ra màn hình:

=== Chức năng thành viên ===
Một tính năng quan trọng của lớp và cấu trúc C ++ là các hàm thành viên. Mỗi kiểu dữ liệu có thể có các hàm tích hợp riêng của nó (được gọi là các phương thức) có quyền truy cập vào tất cả các thành viên (public và private) của kiểu dữ liệu. Trong phần thân của các hàm thành viên không tĩnh này, từ khóa codice_11 có thể được sử dụng để tham chiếu đến đối tượng mà hàm được gọi. Điều này thường được thực hiện bằng cách chuyển địa chỉ của đối tượng như một đối số ngầm định đầu tiên cho hàm. Lấy kiểu codice_6 ở trên làm ví dụ một lần nữa:
1. include <iostream>

class Person {

void Person::Print() const {

Trong ví dụ trên, hàm codice_13 được khai báo trong phần thân của lớp và được định nghĩa bằng cách định nghĩa nó với tên của lớp theo sau là codice_14. Cả codice_15 và codice_16 đều là private (mặc định cho lớp) và codice_13 được khai báo là public, cần thiết nếu nó được sử dụng từ bên ngoài lớp.

Với chức năng thành viên codice_13, việc in ấn có thể được đơn giản hóa thành:
a.Print();
b.Print();
Trong đó codice_19 và codice_20 ở trên được gọi là người gửi và mỗi người trong số họ sẽ tham chiếu đến các biến thành viên của riêng chúng khi hàm codice_21 được thực thi.

Thực tế phổ biến là tách khai báo lớp hoặc cấu trúc (được gọi là giao diện của nó) và định nghĩa (được gọi là hiện thực của nó) thành các đơn vị riêng biệt. Giao diện, được người dùng cần, được giữ trong tiêu đề và việc triển khai được giữ riêng ở dạng nguồn hoặc dạng biên dịch.

=== Sự thừa kế ===
Việc bố trí các lớp không phải POD trong bộ nhớ không được tiêu chuẩn C ++ chỉ định. Ví dụ, nhiều trình biên dịch C ++ phổ biến thực hiện kế thừa đơn bằng cách nối các trường lớp cha với các trường lớp con, nhưng tiêu chuẩn này không yêu cầu. Lựa chọn bố cục này làm cho việc tham chiếu đến một lớp dẫn xuất thông qua một con trỏ đến kiểu lớp cha là một hoạt động tầm thường.

Ví dụ, hãy xem xét
struct P {

struct C : P {
Một ví dụ của codice_22 với codice_23 trỏ tới nó có thể trông như thế này trong bộ nhớ:
↑
p
Một ví dụ của codice_24 với codice_23 trỏ đến nó có thể trông như thế này:
↑
p
Do đó, bất kỳ mã nào thao tác với các trường của một đối tượng codice_22 đều có thể thao tác các trường codice_22 bên trong đối tượng codice_24 mà không cần phải xem xét bất kỳ điều gì về định nghĩa các trường của codice_24. Một chương trình C++ được viết đúng cách không nên đưa ra bất kỳ giả định nào về bố cục của các trường kế thừa, trong mọi trường hợp. Sử dụng toán tử chuyển đổi kiểu static_cast hoặc dynamic_cast sẽ đảm bảo rằng các con trỏ được chuyển đổi đúng cách từ kiểu này sang kiểu khác.

Đa kế thừa không phải là đơn giản. Nếu một lớp codice_30 kế thừa codice_22 và codice_24, thì các trường của cả hai trường cha mẹ cần được lưu trữ theo một số thứ tự, nhưng (tối đa) chỉ một trong các lớp cha có thể được đặt ở phía trước của lớp dẫn xuất. Bất cứ khi nào trình biên dịch cần chuyển đổi con trỏ từ kiểu codice_30 sang codice_22 hoặc codice_24, trình biên dịch sẽ cung cấp chuyển đổi tự động từ địa chỉ của lớp dẫn xuất sang địa chỉ của các trường lớp cơ sở (thông thường, đây là một phép tính bù đơn giản).

Để biết thêm về đa kế thừa, hãy xem kế thừa ảo.

=== Nạp chồng toán tử ===
Trong C++, các toán tử, chẳng hạn như codice_36, có thể được nạp chồng để phù hợp với nhu cầu của người lập trình. Các toán tử này được gọi là toán tử có thể nạp chồng.

Theo quy ước, các toán tử được nạp chồng sẽ hoạt động gần giống như chúng hoạt động trong các kiểu dữ liệu dựng sẵn (codice_37, codice_38, v.v.), nhưng điều này là không bắt buộc. Người ta có thể khai báo một cấu trúc gọi là codice_39, trong đó biến thực sự lưu trữ một số nguyên, nhưng bằng cách gọi codice_40, tổng, thay vì tích, của các số nguyên có thể được trả về:
struct Integer {

Đoạn mã trên đã sử dụng một hàm tạo để "tạo" giá trị trả về. Để trình bày rõ ràng hơn (mặc dù điều này có thể làm giảm hiệu quả của chương trình nếu trình biên dịch không thể tối ưu hóa câu lệnh thành câu lệnh tương đương ở trên), đoạn mã trên có thể được viết lại thành:
Integer operator*(const Integer& k) const {
Lập trình viên cũng có thể đặt một nguyên mẫu của toán tử trong khai báo struct và xác định chức năng của toán tử trong phạm vi toàn cục:
struct Integer {

Integer Integer::operator*(const Integer& k) const {
codice_41 ở trên đại diện cho biến thành viên của chính người gửi, trong khi codice_42 đại diện cho biến thành viên từ biến đối số codice_43.

Từ khóa codice_44 xuất hiện hai lần trong đoạn mã trên. Lần xuất hiện đầu tiên, đối số codice_45, chỉ ra rằng biến đối số sẽ không bị thay đổi bởi hàm. Tỷ lệ thứ hai ở cuối khai báo hứa với trình biên dịch rằng người gửi sẽ không bị thay đổi bởi hàm chạy.

Trong codice_45, dấu và (&) có nghĩa là "chuyển qua tham chiếu". Khi hàm được gọi, một con trỏ tới biến sẽ được chuyển tới hàm, thay vì giá trị của biến. 

Các thuộc tính nạp chồng tương tự ở trên cũng áp dụng cho các lớp.

Lưu ý rằng không thể thay đổi độ hiếm, tính liên kết và thứ tự ưu tiên của các toán tử.

==== Nạp chồng toán tử nhị phân ====
Toán tử nhị phân (toán tử có hai đối số) được nạp chồng bằng cách khai báo một hàm với một toán tử "định danh" (một cái gì đó) gọi một đối số duy nhất. Biến ở bên trái của toán tử là người gửi trong khi ở bên phải là đối số.
Integer i = 1; 
/* chúng ta có thể khởi tạo một biến cấu trúc theo cách 
Integer j = 3;
/* tên biến độc lập với tên của
Integer k = i * j;
std::cout « k.i « std::endl;
Sẽ in ra màn hình là
3
Sau đây là danh sách các toán tử có thể nạp chồng nhị phân:

Toán tử '=' (gán) giữa hai biến có cùng kiểu cấu trúc được nạp chồng theo mặc định để sao chép toàn bộ nội dung của biến từ biến này sang biến khác. Nó có thể được ghi đè bằng thứ khác, nếu cần.

Các toán tử phải được nạp chồng từng cái một, nói cách khác, không có quá tải nào được liên kết với nhau. Ví dụ, codice_47không nhất thiết phải ngược lại vớicodice_48.

==== Toán tử có thể nạp chồng một lần ====
Trong khi một số toán tử, như được chỉ định ở trên, nhận hai điều khoản, người gửi ở bên trái và đối số ở bên phải, một số toán tử chỉ có một đối số - người gửi và chúng được cho là "một ngôi". Ví dụ là dấu phủ định (khi không có gì được đặt ở bên trái của nó) và "logic NOT" (dấu chấm than,codice_49).

Người gửi của toán tử một ngôi có thể ở bên trái hoặc bên phải của nhà khai thác. Sau đây là danh sách các toán tử có thể nạp chồng một lần:

Cú pháp nạp chồng toán tử một ngôi, trong đó người gửi ở bên phải, như sau:

codice_50

Khi người gửi ở bên trái, khai báo là:

codice_51

codice_52 trên là viết tắt của toán tử được quá tải. Thay thế codice_53 bằng kiểu dữ liệu của giá trị trả về (codice_37, codice_55, cấu trúc, v.v.)

Tham số codice_37 về cơ bản không có nghĩa gì ngoài một quy ước để cho thấy rằng người gửi ở bên trái của toán tử.

Đối số codice_44 có thể được thêm vào cuối khai báo nếu có.

==== Mảng có thể nạp chồng ====
Dấu ngoặc vuông codice_58 và dấu ngoặc tròn codice_59 có thể được nạp chồng trong cấu trúc C++. Dấu ngoặc vuông phải chứa chính xác một đối số, trong khi dấu ngoặc tròn có thể chứa bất kỳ số lượng đối số cụ thể nào hoặc không có đối số nào.

Khai báo mảng có thể nạp chồng

codice_60 

Nội dung bên trong dấu ngoặc được chỉ định trong codice_61.

Dấu ngoặc tròn được nạp chồng theo cách tương tự.

codice_62 

Nội dung của dấu ngoặc trong lệnh gọi nhà điều hành được chỉ định trong dấu ngoặc thứ hai.

Ngoài các toán tử được chỉ định ở trên, toán tử mũi tên (codice_63), mũi tên có dấu sao (codice_64), từ khóa codice_65 và từ khóa codice_66 cũng có thể được nạp chồng. Các toán tử liên quan đến bộ nhớ hoặc con trỏ này phải xử lý các chức năng cấp phát bộ nhớ sau khi nạp chồng. Giống như toán tử gán (codice_67), chúng cũng được nạp chồng theo mặc định nếu không có khai báo cụ thể.

=== Hàm tạo ===
Đôi khi các lập trình viên có thể muốn các biến của họ nhận một giá trị mặc định hoặc một giá trị cụ thể khi khai báo. Điều này có thể được thực hiện bằng cách khai báo các hàm tạo.
Person::Person(string name, int age) {
Các biến thành viên có thể được khởi tạo trong danh sách trình khởi tạo, với việc sử dụng dấu hai chấm, như trong ví dụ dưới đây. Điều này khác với ở trên ở chỗ nó khởi tạo (sử dụng hàm tạo), thay vì sử dụng toán tử gán. Điều này hiệu quả hơn đối với các loại lớp, vì nó chỉ cần được xây dựng trực tiếp; trong khi với phép gán, chúng phải được khởi tạo đầu tiên bằng cách sử dụng hàm tạo mặc định, sau đó được gán một giá trị khác. Ngoài ra, một số kiểu (như tham chiếu và kiểu const) không thể được gán cho và do đó phải được khởi tạo trong danh sách trình khởi tạo.
Lưu ý rằng không thể bỏ qua dấu ngoặc nhọn, ngay cả khi trống.

Giá trị mặc định có thể được cấp cho các đối số cuối cùng để giúp khởi tạo các giá trị mặc định.
Khi không có đối số nào được cung cấp cho hàm tạo trong ví dụ trên, nó tương đương với việc gọi hàm tạo sau không có đối số (một hàm tạo mặc định):
Khai báo của một phương thức khởi tạo trông giống như một hàm có cùng tên với kiểu dữ liệu. Trên thực tế, một lời gọi đến một phương thức khởi tạo có thể ở dạng một lời gọi hàm. Trong trường hợp đó, một biến kiểu codice_6 được khởi tạo có thể được coi là giá trị trả về:
int main() {
Một cú pháp thay thế thực hiện tương tự như ví dụ trên là
int main() {
Các hành động chương trình cụ thể, có thể có hoặc không liên quan đến biến, có thể được thêm vào như một phần của hàm tạo.
Person() {
Với hàm tạo ở trên, "Xin chào!" sẽ được in khi phương thức khởi tạo codice_6 mặc định được gọi.

==== Hàm tạo mặc định ====
Các hàm tạo mặc định được gọi khi các hàm tạo không được định nghĩa cho các lớp.
struct A {
// Đối tượng được tạo bằng cách sử dụng dấu ngoặc đơn.
A* a = new A(); // Gọi hàm tạo mặc định và b sẽ được khởi tạo bằng '0'.
// Đối tượng được tạo không sử dụng dấu ngoặc đơn.
A* a = new A; // Phân bổ bộ nhớ, sau đó gọi hàm tạo mặc định và b sẽ có giá trị '0'.
// Tạo đối tượng mà không cần new.
A a; // Dành không gian cho a trên ngăn xếp và b sẽ có giá trị rác không xác định.
Tuy nhiên, nếu một hàm tạo do người dùng xác định được định nghĩa cho lớp, thì cả hai khai báo trên sẽ gọi hàm tạo do người dùng xác định này, mà mã được xác định sẽ được thực thi, nhưng không có giá trị mặc định nào được gán cho biến b.

=== Hàm hủy ===
Một hàm hủy là nghịch đảo của một hàm tạo. Nó được gọi khi một thể hiện của lớp bị hủy, ví dụ: khi một đối tượng của một lớp được tạo trong một khối (tập hợp các dấu ngoặc nhọn "{}") bị xóa sau dấu ngoặc nhọn đóng, thì hàm hủy được gọi tự động. Nó sẽ được gọi khi làm trống vị trí bộ nhớ lưu các biến. Bộ hủy có thể được sử dụng để giải phóng tài nguyên, chẳng hạn như bộ nhớ được cấp phát theo đống và các tệp đã mở khi một thể hiện của lớp đó bị hủy.

Cú pháp để khai báo một hàm hủy tương tự như cú pháp của một hàm tạo. Không có giá trị trả về và tên của phương thức giống với tên của lớp có dấu ngã (~) ở phía trước.
~Person() {

== Điểm giống nhau giữa hàm tạo và hàm hủy ==
- Cả hai đều có cùng tên với lớp mà chúng được khai báo.
- Nếu không được khai báo bởi người dùng, cả hai đều có sẵn trong một lớp theo mặc định nhưng bây giờ họ chỉ có thể cấp phát và phân bổ bộ nhớ từ các đối tượng của một lớp khi một đối tượng được khai báo hoặc xóa.
- Đối với một lớp dẫn xuất: Trong thời gian chạy của hàm tạo lớp cơ sở, hàm tạo của lớp dẫn xuất vẫn chưa được gọi; trong thời gian chạy của hàm hủy lớp cơ sở, hàm hủy lớp dẫn xuất đã được gọi. Trong cả hai trường hợp, các biến thành viên của lớp dẫn xuất ở trạng thái không hợp lệ.

== Class template ==
Trong C++, khai báo lớp có thể được tạo từ các class template Các class template như vậy đại diện cho một họ các lớp. Khai báo lớp thực sự có được bằng cách khởi tạo mẫu với một hoặc nhiều đối số mẫu. Một khuôn mẫu được khởi tạo với một tập hợp các đối số cụ thể được gọi là chuyên môn hóa khuôn mẫu.

= Tính chất =
Cú pháp của C ++ cố gắng làm cho mọi khía cạnh của cấu trúc trông giống như các kiểu dữ liệu cơ bản. Do đó, các toán tử nạp chồng cho phép các cấu trúc được thao tác giống như số nguyên và số dấu phẩy động, mảng cấu trúc có thể được khai báo bằng cú pháp ngoặc vuông (codice_70) và con trỏ đến cấu trúc có thể được tham chiếu theo cách tương tự như con trỏ tới các kiểu dữ liệu tích hợp sẵn.

== Tiêu thụ bộ nhớ ==
Mức tiêu thụ bộ nhớ của một cấu trúc ít nhất là tổng kích thước bộ nhớ của các biến cấu thành. Lấy cấu trúc codice_71 dưới đây làm ví dụ.
struct TwoNums {
Cấu trúc bao gồm hai số nguyên. Trong nhiều trình biên dịch C ++ hiện tại, các số nguyên là số nguyên 32 bit theo mặc định, vì vậy mỗi biến thành viên sử dụng bốn byte bộ nhớ. Do đó, toàn bộ cấu trúc tiêu tốn ít nhất (hoặc chính xác) tám byte bộ nhớ, như sau.
Tuy nhiên, trình biên dịch có thể thêm phần đệm giữa các biến hoặc vào cuối cấu trúc để đảm bảo căn chỉnh dữ liệu thích hợp cho một kiến trúc máy tính nhất định, các biến đệm thường được căn chỉnh 32 bit. Ví dụ, cấu trúc
struct BytesAndSuch { 

có thể trông giống như
Trong bộ nhớ, trong đó X đại diện cho các byte đệm dựa trên sự liên kết 4 byte.

Vì cấu trúc có thể sử dụng con trỏ và mảng để khai báo và khởi tạo các biến thành viên của nó, nên mức tiêu thụ bộ nhớ của cấu trúc không nhất thiết là hằng số. Một ví dụ khác về kích thước bộ nhớ không đổi là cấu trúc khuôn mẫu.

=== Trường bit ===
Các trường bit được sử dụng để xác định các thành viên lớp có thể chiếm ít dung lượng hơn kiểu tích phân. Trường này chỉ áp dụng cho các kiểu tích phân (int, char, short, long, v.v.) và loại trừ float hoặc doub
struct A { 
Cấu trúc bộ nhớ

Các trường bit không được phép trong liên hợp. Nó chỉ áp dụng cho các lớp được xác định bằng từ khóa struct hoặc class.

== Truyền theo tham chiếu ==
Nhiều lập trình viên thích sử dụng dấu và (&) để khai báo các đối số của một hàm liên quan đến cấu trúc. Điều này là do bằng cách sử dụng ký hiệu hội nghị và chỉ một từ (thường là 4 byte trên máy 32 bit, 8 byte trên máy 64 bit) được yêu cầu chuyển vào hàm, cụ thể là vị trí bộ nhớ cho biến. Ngược lại, nếu sử dụng giá trị truyền, đối số cần được sao chép mỗi khi hàm được gọi, điều này rất tốn kém với các cấu trúc lớn.

Vì tính năng chuyển qua tham chiếu cho thấy cấu trúc ban đầu được hàm sửa đổi, nên từ khóa codice_44 nên được sử dụng để đảm bảo rằng hàm không sửa đổi tham số (xem const-đúng), khi điều này không được dự định.

== Từ khóa "this" ==
Để hỗ trợ khả năng tự tham chiếu của cấu trúc, C ++ triển khai từ khóa codice_11 cho tất cả các hàm thành viên. Từ khóa codice_11 hoạt động như một con trỏ trỏ đến đối tượng hiện tại. Kiểu của nó là của một con trỏ đến đối tượng hiện tại.

Từ khóa codice_11 đặc biệt quan trọng đối với các hàm thành viên với chính cấu trúc là giá trị trả về:
Complex& operator+=(const Complex& c) {
Như đã nói ở trên, đây là một con trỏ, vì vậy việc sử dụng dấu hoa thị (*) là cần thiết để chuyển nó thành một tham chiếu được trả về.