Hướng dẫn check cấp phát con trỏ

Trước tiên,tôi xin cung cấp link sau.Đây là bài viết rất rõ ràng của tác giả langman trên congdongcviet.Tôi nghĩ xem hết loạt kiến thức đó là quá đủ để hiểu về con trỏ rồi.Tuy nhiên,ở bài viết này.Tôi cũng muốn trình bày lại 1 ít kiếnthức cơ bản về con trỏ để các bạn dễ nhìn nhận vấn đề.và hơn nữa,nó cũng mạch lạc với loạt bài tôi viết.

link bài viết của langman -congdongcviet.com

http://diendan.congdongcviet.com/showthread.php?t=42977

Các vấn đề được đưa ra

1/ con trỏ là gì

2/ tại sao lại phải dùng con trỏ

3/ con trỏ hàm,lớp …

4/ cấp phát và giải phóng bộ nhớ

5/ toán tử new và delete

Phần 1 ,2,3:

Khái niệm về con trỏ:

Con trỏ thực chất chỉ là 1 biến nguyên,nó mang giá trị của ô nhớ mà nó trỏ đến.

Hướng dẫn check cấp phát con trỏ

Như vậy.Dù được sử dụng như thế nào đi nữa,thì nó cũng chỉ là 1 biến nguyên.Trên các hệ thống 32bit nó có độ dài là 4byte.Vì sao lại là 4byte.Vì khi 1 vùng nhớ ảo cho mỡi tiến trình (đã trình bày ở chương 1).Thì chỉ cần 4byte là đủ đánh địa chỉ cho tất cả các ô nhớ ảo rồi.

Công dụng của nó thì cứ làm đi,sử dụng nó đi rồi biết.

khai báo :

Code:

int *p;

char * q;

float * h;

Gán ô nhớ cho con trỏ trỏ đến.

Ta sẽ xét trường hợp trên,thử printf ra xem sao :

Code:

printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);

Ta build ở chế độ debug,không cần optimize,chỉ để hiểu thôi

Kết quả :

20

0032F9E8

0032F9E8

20

0032F9DC

Ta có thể thấy rằng

20 là giá trị của biến a,tại ô nhớ của biến a(ô nhớ này được đánh số 0032F9E8 tức là 0032F9DC)

ô nhớ chứa con trỏ p đánh giá trị 0032:F9DC và có giá trị là 0032F9DC

ta chỉ cần dùng *p là có thể lấy được giá trị của a;

Con trỏ đa cấp

Là loại con trỏ .. trỏ đến con trỏ.Thật dễ hiểu phải không !!

Code:

int a=2;

int **q=&p;

int * p = &a;

printf(“%d – %d -%p -%d -%d -%p \n”,p,*p,&p,max,*max,&max);

Con trỏ void

Là con trỏ đặc biệt,thích trỏ đi đâu trỏ.Có thể trỏ đến 1 vùng nhớ mang kiểu int,char …

Gán địa chỉ cho con trỏ :

Con trỏ hàm :

vâng, hàm vẫn cứ làm theo nguyên tắc 1 và 1 bản sao của con trỏ được tạo ra, và hàm làm việc với bản sao hàm, và trước khi gọi hàm con trỏ trỏ vào đâu thì nó vẫn được trỏ vào đấy chứng minh :

Code:

`

include `

include

int ham(int *a)

{

*a=2;

a++;

}

void main()

{

int *a;

printf(“Truoc : %x”,a); //trước và sau khi gọi hàm

ham(a); //con trỏ a trỏ vào đâu

printf(“Sau %x”,a); // thì nó vẫn trỏ vào đó

getch();

}

hoạt động của 1 hàm :

1 hàm khi được biên dịch sẽ được compoler chừa ra 1 local_size nằm trên 1 vùng nhớ xác định trong không gian virtual memory của mõi tiến trình

Tùy vào kiểu calling convention mà localsize là bao nhiêu.Tôi sẽ trình bày phần này ở chương về PE file và các load của windows loader.Phần này không đi vào trình bày sâu.Bạn chỉ cần biết rằng.1 hàm khi biên dịch đều có 1 local size và đầu 1 hàm là 1 địa chỉ nhất định.

Ví dụ sau sẽ cho bạn thấy :

PHP Code:

int test(int foo){ return foo; } int main(int argc,char* argv[]) { int (*adr)(int); int (*p)(int); p = test; adr = test; `int c = ( int) adr; p = (int(*)(int))c; // c là địa chỉ của hàm test //thực thi : // printf("%d",p(2));`return

0; }

Qua ví dụ trên ta thấy,ta chỉ cần trỏ con trỏ p vào địa chỉ chứa hàm là có thể gọi thực thi từ địa chỉ cụ thể trên memory là c.Đó chính là cách hoạt động của con trỏ hàm.

Con trỏ trỏ đến class :

thật đơn giản.Con trỏ trỏ đến đầu địa chỉ của class.

Ví dụ :

PHP Code:

class test{ public : int val; void func(){ cout << "hello"; } }; int main(int argc, char* argv[]) { test t; t.val =2; test *p = &t; cout << p->val; p->func(); return 0; }

Con trỏ struct :

Tương tự class,không khác lắm

Code:

struct test{

int val;

};

int main(int argc, char* argv[])

{

test t;

t.val =2;

test *p = &t;

cout << p->val;

return 0;

}

Các phép toán trên con trỏ :

PHP Code:

`int *p;`0

`int *p;`1

`int *p;`2

`int *p;`3

`int *p;`4

Mảng và con trỏ.

Nếu ta khai báo :

Code:

`int *p;`5

foo[0]=2;

foo[1]=3;

int *f = foo;

printf(“%d \n%d”,*(f++),*f);

Kết quả

2

3

Vậy là con trỏ f trỏ đến vị trí đầu tiên của mảng(foo[0])

Như vậy,nếu muốn xuất 1 mảng,chỉ cần viết :

Code:

`int *p;`6

printf(“%d”,*(f+i));

}

//——————–

Lấy byte thứ i trong 1 biến n byte :

Code:

`int *p;`7

char *p = ( char * ) &a;

int i = 3;

printf(“%p”,p[i]);

Kiểm tra hệ thống là little endian hay big endian

Code:

`int *p;`8

if(*(char *)&num) = 78){

printf(“Little Endian”);

}

else printf(“Big Endian”);

con trỏ hằng là 1 optional ability trong lập trình, tác dụng của nó tựa như là phương thức hằng trong C++;

ý nghĩa là 1 con trỏ, trỏ đến 1 ô nhớ, nhưng ko được quyền thay đổi giá trị của ô nhớ đó

Code:

`int *p;`9

const int *p;

p=&a; <<<<<<<<<<<<< bản thân p thì có thể thay đổi, cho p gán vào chỗ khác được nhưng

(*p)++;// loi tai day

Phần 2 :Cấp phát và giải phóng bộ nhớ :

Bộ nhớ cho mỗi tiến trình được nạp.

Vậy khi cấp phát bộ nhớ cho chương trình,trình biên dịch sẽ tính toán và resize 1 khoảng cho chương trình như thế nào.

Ta cần tìm hiểu khái niệm và epilog và prolog của hàm.Nó sẽ được mô tả ở các chương sau.

Vùng LOCAL_SIZE sẽ là vùng nhớ được chừa ra cho chương trình khi chạy.Vậy nếu ta cần cấp phát động cho chương trình thì ta phải làm như thế nào.

Ta sẽ nói đến 1 số khái niệm trong quá trình cấp phát động.

Đầu tiên là hàm malloc:

malloc

Nhìn vào VD sau

PHP Code:

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`0

int main(){ char *vungnhocapphat; int mb \= MB; vungnhocapphat \= (char *)malloc(mb);

if(vungnhocapphat != NULL){ sprintf(vungnhocapphat,“cap phat malloc cua lttq \n”); printf(“%s”,vungnhocapphat); free(vungnhocapphat); } else printf(“loi trong qua trinh cap phat”); }

hàm malloc sẽ yêu cầu hệ điều hành cấp phát và trả về con trỏ ở 1 vùng nhớ dài 1MB.Hàm malloc trả về kiểu *void,nên ta chỉ cần ép qua char để access đến vùng nhớ theo kiểu chuỗi ký tự.

Ta kiểm tra con trỏ chỉ là để giúp xem xem việc cấp phát có thành công hay không.

Hàm malloc đơn thuần chỉ xin cấp phát 1 chuỗi byte liên tiếp trên bộ nhớ,vì thế kết quả trả về có thể ép qua kiểu gì cũng được.

Cần nói thêm là trên Windows đánh địa chỉ ảo đến 4GB,để việc access vào memory dễ chịu hơn.

Khi giải phóng bộ nhớ bằng hàm free thì con trỏ được trỏ đến vùng nhớ được giải phóng.Lúc này con trỏ đó không thể đọc ghi được nữa.Nếu cố tình đọc ghi thì sẽ sinh lỗi

Có 1 điều khá thú vị là bạn thử cấp phát 1 vùng nhớ lớn tầm 500 MB thì có thể sẽ được.Rồi thử cấp phát tiếp 100 vùng 5MB. thì chắc chắn là hệ điều hành có thể k cho phép nữa.Vì việc cấp phát lúc này là rất rời rạc trên bộ nhớ,dẫn đến hết bộ nhớ để cấp phát.Nói như thế để thấy việc cấp phát bộ nhớ lớn chưa chắc là không được.Nhưng cấp phát quá nhiều cho các yêu cầu nhỏ hơn thì sẽ không được.

Hàm calloc :

calloc hoạt động rất giống với malloc,sự khác nhau chủ yếu là prototype của nó:

void* calloc (size_t nelements, size_t size);

nó sửdụng hai tham số thay vì một. Hai tham số nàyđược nhân với nhau để có được kích thước tổng cộng của khối nhớ cần cấp phát. Thông thường tham số đầu tiên (n elements) là số phần tử và tham số thức hai (size) là kích thước của mỗi phần tử. Ví dụ,chúng ta có thể định nghĩa bobby với calloc như sau:

ví dụ :

Code:

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`1

bobby = (int *) calloc (5, sizeof(int));

Mộtđiểm khác nhau nữa giữa malloc và calloc là calloc khởi tạo tất cả các phần tử của nóvề 0.

Hàm realloc.

Nó thay đổi kích thước của khối nhớ đã được cấp phát cho một con trỏ.

void* realloc (void * pointer,size_t size);

tham số pointer nhận vào một con trỏ đã được cấp phát bộ nhớ hay một con trỏ null, và size chỉ định kích thước của khối nhớ mới. Hàm này sẽ cấp phát size byte bộ nhớ cho con trỏ. Nó có thể phải thay đổi vị trí của khối nhớ để có thể để đủ chỗ cho kích thước mới của khối nhớ, trong trường hợp này nội dung hiện thời của khối nhớ được copy tới vị trí mới để đảm bảo dữ liệu không bị mất. Con trỏ mới trỏ tới khối nhớ được hàm trả về.Nếu không thể thay đổi kích thước củakhối nhớ thì hàm sẽ trả về một con trỏ null nhưng tham số pointer và nội dung của nó sẽ không bị thay đổi.

ví dụ :

Code:

`

include `

include

int main ()

{

int input,n;

int count=0;

int * numbers = NULL;

int * more_numbers;

do {

printf (“Enter an integer value (0 to end): “);

scanf (“%d”, &input);

count++;

more_numbers = (int*) realloc (numbers, count * sizeof(int));

if (more_numbers!=NULL) {

numbers=more_numbers;

numbers[count-1]=input;

}

else {

free (numbers);

puts (“Error (re)allocating memory”);

exit (1);

}

} while (input!=0);

printf (“Numbers entered: “);

for (n=0;n

free (numbers);

return 0;

}

free()

Hàm này giải phóng một khối nhớ động đãđược cấp phát bởi malloc,calloc hoặc realloc.Prototype của nó

void free (void * pointer);

Ví dụ :

Code:

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`3

free (buffer1);

Hàm này chỉđược dùng để giải phóng bộ nhớđược cấp phát bởi các hàm malloc,calloc and realloc.

toán tử new và delete

Toán tử new

Để yêu cầu bộ nhớ động, chúng ta sử dụng toán tử new, theo sau nó là kiểu dữ liệu. Nếu nó là mảng của nhiều phần tử, thì số phần tử sẽ được ấn định bên trong dấu [] ngay sau kiểu dữ liệu. Khi đó, nó sẽ trả về con trỏ trỏ vào khối ô nhớ đầu tiên.

Ví dụ :

Bộ nhớ động được yêu cầu bởi chương trình, và hệ điều hành sẽ cung cấp cho nó từ bộ nhớ heap. Tuy nhiên, bộ nhớ máy tính cũng hữu hạn, và nó có thể bị cạn kiệt. Chính vì lẽ đó, chúng ta cần đến một kĩ thuật để kiểm tra tình trạng nàycủa bộ nhớ. C++ (và thậm chí C) cung cấp cho ta hai phương thức chuẩn để kiểm tra: vượt qua ngoại lệ (throw bad_alloc) và không vượt qua ngoại lệ (nothrow bad_alloc).

Ta nên viết lại như sau :

PHP Code:

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`4

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`5

overload toán tử new và delete => ở post 2

Khác nhau giữa new và malloc :

+malloc cấp phát không được sẽ trả về null

+new cấp phát không được sẽ trả về 1 exception(bad_alloc)

+new dùng trong C++,đi kèm với contructor

+malloc dùng trong C

Khác nhau giữa delete và free :

+free dùng trong C,chỉ để giải phóng từ việc cấp phát bởi malloc,calloc và realloc

+delete trong C++,đi cùng với destructor

————

1 số vấn đề cần lưu ý

+ không được hủy 2 lần

+ không được chưa cấp phát mà hủy

+ có thể delete mà không cần kiểm ra null => vì sao ?

CallBack là gì?

định nghĩ call back trên wiki :

gọi lại là một tham chiếu đến một đoạn mã thực thi, được đưa vào hàm các thông số là địa chỉ các mã thực thi . Điều này cho phép phần mềm lớp cấp thấp hơn để gọi một chương trình con được định nghĩa trong một lớp cấp cao hơn.

Qua định nghĩ này ta thấy rằng,giả sử chương trình chính của ta bắt buộc muốn chạy một hàm nào đó, hoặc thư viện của của ta bắt buộc phải chạy hàm nào đó.Nhưng ta muốn một lập trình viên khác có cơ hội thay đổi hành vi của hàm ta đã viết, đó chính là lúc dùng đền call back

Xét ví dụ sau :

PHP Code:

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`6

Nhập xuất trên C :

Xem bài viết sau :

http://diendan.congdongcviet.com/showpos…ostcount=7

Nhập xuất trên C++

Xin trích lại bài viết từ nguồn sau

Source : http://vncommunity.wordpress.com/2011/03…n-trong-c/

1. Xuất dữ liệu ra màn hình

Để in dữ liệu của một biểu thức nào đó ra màn hình (standard output device) ta dùng cú pháp sau:

Code:

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`7

Hoặc cho một dãy các biểu thức:

Code:

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`8

Ta cũng có thể viết câu lệnh trên trên nhiều dòng ví dụ như:

Code:

`printf("%d /n %p /n %p /n %d /n %p /n ",a,&a,p,*p,&p);`9

<< exp_2

<< exp_3

<< exp_n;

Kết quả thu được là hoàn toàn tương tự. cout cùng với toán tử << có thể xuất được nhiều kiểu dữ liệu khác nhau mà không cần sự can thiệp của lập trình viên. C++ tự động nhận biết và xuất ra dưới định dạng phù hợp. Xét đoạn chương trình sau:

Code:

`int a=2;`0

include

using namespace std;

int main(){

int n=3;

float x=12.08;

string str=”Be aware ! I have a gun”;

cout << n << endl;

cout << x << endl;

cout << str << endl;

return 0;

}

Kết quả thu được trên màn hình sẽ như sau:

3

12.08

Be aware ! I have a gun

2. Nhập dữ liệu vào từ bàn phím

Bàn phím là thiết bị nhập chuẩn (standard input device). Để vào dữ liệu từ bàn phím cho các biến ta có thể dùng cin vùng toán tử >>. Cú pháp sẽ như sau:

Code:

`int a=2;`1

Hoặc cho nhiều biến:

Code:

`int a=2;`2

Khi gặp những câu lệnh như thế này chương trình sẽ “pause” lại để chờ chúng ta nhập dữ liệu vào từ bàn phím. Câu lệnh cin >> var_1 >> var_2 >> … >> var_n; coi các ký tự trắng là ký tự phân cách các lần nhập dữ liệu. Các ký tự trắng (white space characters) bao gồm: dấu cách, dấu tab, và ký tự xuống dòng (new line). Ví dụ a, b là hai biến kiểu int, thì câu lệnh:

cin >> a >> b;

sẽ đợi người dùng nhập dữ liệu hai lần, cách nhau bởi ít nhất một ký tự trắng. Ví dụ ta nhập vào bàn phím như sau : 1989 2011 ↵ thì biến a sẽ nhận giá trị 1989, còn biến b sẽ nhận giá trị 2011. Chúng ta cũng không cần quan tâm đến kiểu của các biến, C++ tự động nhận biết điều này. Xem xét đoạn chương trình sau:

Nếu ta nhập vào giá trị 12.08 thì biến num chỉ nhận được giá trị 12. Còn phần thập phân sẽ không được ghi nhận, do num là một biến kiểu int nên C++ tự động nhận biết và “chặt cụt” đi phần này.

3. Những lưu ý trong nhập/ xuất xâu ký tự.

Trong tất cả các kiểu dữ liệu, theo mình thì nhập xuất với xâu (string) là thể loại củ chuối và rắc rối nhất Trước hết mình nói qua một chút về thư viện chuẩn của C++. Nếu bạn nào đã học qua ngôn ngữ C rồi thì đều biết xâu ký tự là một mảng chứa các ký tự (kiểu char) và phần tử cuối cùng của mảng là NULL (hay ” ) để đánh dấu sự kết thúc xâu. Tuy nhiên, sử dụng mảng để lưu trữ xâu có phần phức tạp vì mảng là một cấu trúc tĩnh phải biết rõ kích thước ngay khi khai báo. Điều này làm cho chương trình “cứng nhắc” và không “kinh tế”. Ví dụ nếu khai báo ít quá thì khi muốn chứa thêm nhiều ký tự hơn sẽ không được, mà nếu khai báo nhiều quá, không dùng hết sẽ lãng phí bộ nhớ. Để khắc phục người ta dùng biện pháp quản lý mảng bằng con trỏ và cấp bộ nhớ phát động cho mảng (dynamic memory allocation). Tuy nhiên nếu việc này diễn ra thường xuyên thì chương trình sẽ rất rối rắm, gây mệt mỏi cho lập trình viên và dễ dẫn đến lỗi. C++ giải quyết tốt vấn đề này bằng cách xây dựng lớp string. Một đối tượng của lớp string có thể lưu trữ các ký tự “tốt hơn” mảng trong C rất nhiều. Muốn sử dụng nó ta phải include header file . Trong các bài viết, nếu không có lý do gì đặc biệt, thì mình sẽ dùng string để lưu trữ xâu.

Bây giờ trở lại vấn đề của chúng ta, đó là nhập/ xuất xâu ký tự. Xuất thì ok, không có vấn đề gì phải bàn luận nhiều, nhưng nhập thì lại có nhiều điều để nói.

  1. cin không cho phép nhập dấu trắng

Xét chương trình sau đây:

Code:

`int a=2;`0

include

using namespace std;

int main(){

string str;

cin>> str;

cout << str << endl;

return 0;

}

Nếu ta nhập vào đoạn văn bản sau: “Osama Binladen” thì xâu str chỉ ghi nhận đoạn đầu “Osama” vì dấu cách là một khoảng trắng mà cin coi các dấu trắng là ký tự báo hiệu kết thúc việc nhập dữ liệu. Để khắc phục điều này C++ cung cấp một số cách thức để nhập toàn bộ xâu ký tự. Nhưng trước hết ta phải xem xét một vấn đề về bộ đệm.

ii. Bộ đệm (buffer)

Khi ta nhập dữ liệu vào bàn phím thì dữ liệu không được đọc ngay vào biến mà được đẩy lên trên bộ đệm (buffer). Dữ liệu sẽ tồn tại trong bộ đệm cho tới khi một lệnh nào đó gửi yêu cầu đến bộ đệm “xin phép” được load dữ liệu về. Ví dụ khi chương trình gặp câu lệnh:

cin >> x; // với x là một biến kiểu int

thì nó sẽ mò lên buffer để load dữ liệu về, nếu như trên bộ đệm có số 100 thì nó sẽ đọc 100 vào biến x mà không đợi ta nhập gì cả, còn nếu bộ đệm trống hoặc có những dữ liệu không phải số nguyên (ví dụ mã phím Enter của lần nhập trước) thì nó mới dừng lại đợi ta nhập dữ liệu vào. Như vậy ta hoàn toàn không cần để ý nhiều việc sử dụng cin >> để nhập các dữ liệu số (nguyên hoặc dấu chấm động – floating point), nhưng để nhập dữ liệu ký tự thì lại hoàn phải hết sức chú ý. Trong đoạn chương trình test nhập xâu ký tự bên trên ta nhận thấy biến str chỉ lưu trữ phần “Osama”, phần còn lại thì vẫn nằm trên buffer để cho lần nhập sau. C++ cung cấp getline cho phép nhập toàn bộ xâu ký tự kể cả khoảng trắng. Cú pháp sử dụng hàm getline như sau.

Code:

`int a=2;`4

Câu lệnh trên sẽ thực hiện đọc toàn bộ xâu nhập từ bàn phím vào biến str, cho tới khi bắt gặp ký tự kết thúc (delimiter) hoặc EOF (end-of-file). Nếu không viết delimiter thì mặc định là ký tự xuống dòng – ‘\n’. Xét chương trình sau:

Code:

`int a=2;`0

include

using namespace std;

int main(){

string str;

getline(cin,str);

cout << str << endl;

return 0;

}

Nếu ta nhập vào bàn phím xâu “Osama Binladen” rồi nhấn Enter thì kết quả thu được trên màn hình sẽ là trọn vẹn xâu “Osama Binladen”. Điều gì xảy ra với ký tự Enter, nó có nằm lại trên bộ đệm không? Câu trả lời là getline đã đọc mã của ký tự Enter nhưng không gắn nó vào trong xâu str và cũng không để lại nó trên bộ đệm mà hủy nó đi.

iii. Hiện tượng trôi lệnh

Xét chương trình sau:

Code:

`int a=2;`0

include

using namespace std;

int main(){

int num;

string str;

cout << “Input an integer a= “;

cin >> num;

cout << num << endl;

cout << “Input a string str= “;

getline(cin,str);

cout << str << endl;

cout << “End program” << endl;

return 0;

}

Bạn chạy thử chương trình trên sẽ thấy ngay. Sau khi nhập dữ liệu cho biến num, chương trình không dừng lại cho ta nhập dữ liệu cho str. Mà in ngay ra thông báo “End program”. Nguyên nhân là do sau khi nhập dữ liệu cho biến num ta gõ phím Enter. Mã của Enter được lưu trong bộ đệm (chính là ký tự xuống dòng ‘\n’) và do đó khi chương trình gặp câu lệnh:

getline(cin,str);

nó sẽ đọc ngay ký tự này và nhận thấy đây là ký tự kết thúc nên nó sẽ loại bỏ ký tự này ra khỏi bộ đệm mà không đọc vào xâu str. Sau đó nó sẽ chạy thẳng đến câu lệnh tiếp theo là:

cout << “End program” << endl;

Người ta thường gọi đó là hiện tượng “trôi lệnh”. Để khắc phục hiện tượng trôi lệnh này thì trước mỗi lệnh getline ta nên đặt câu lệnh:

fflush(stdin);

Câu lệnh này có tác dụng xóa bộ đệm và do đó ta có thể yên tâm là sẽ không bị trôi lệnh nữa.

Trên đây mình đã giới thiệu hai thao tác cơ bản và thông dụng nhất là nhập và xuất dữ liệu ra các thiết bị chuẩn. Đồng thời cũng đưa ra một số chú ý về nhập xuất với dữ liệu ký tự và xâu ký tự. Hy vọng có thể giúp các bạn tránh gặp phải những lỗi không mong muốn khi nhập xuất dữ liệu trong C++.

Smart Pointer :

unique_ptr : 1 pointer trỏ tới 1 vùng nhớ duy nhất, không cho ai trỏ vào, khi nó ko dùng nữa, nó tự hủy.