Lập trình C/C++ nâng cao
Con trỏ cơ bản
CODE
int a=5,*p;
//p=3; //khong hop ve vi khong the gan gia tri kieu int cho bien kieu int*
//&p=3; //khong hop le vi dia chi cua p la co dinh
p=&a; //hop le, gan dia chi ma p tro den
*p=3; //hop le, gan gia tri tai dia chi ma p tro den
cout<<p<<endl; //cai gi do bat ki, dia chi cua a
cout<<&p<<endl; //cai gi do bat ki, dia chi cua p
cout<<*p<<endl; //3,dau * luc nay mang y nghia "gia tri tai dia chi cua"
.line<<endl; } return 0; } Thực ra, ngay cả khi đã biết các kĩ thuật phát hiện lỗi vẫn khó ngăn chặn hoàn toàn các lỗi trong một dự án game lớn vì nó được phát triển bởi một đội ngũ phát triển đông người làm việc liên tục với cường độ cao trong một thời gian dài. Sơ sót xảy ra vẫn là điều khó tránh khỏi. Lập trình viên dành 40% thời gian viết mã 60% thời gian sửa lỗi là vì thế. Do đó kĩ năng debug tùy thuộc rất nhiều vào tích lũy kinh nghiệm thực tế chứ không phải là thứ có thể tích lũy được qua một quyển sách hay bài báo kĩ thuật nào BÀI 15: AUTO_PTR, MUTABLE, VOLATILE VÀ ĐÁNH GIÁ TỐC ĐỘ CHƯƠNG TRÌNH auto_ptr Trong thư viện có định nghĩa lớp auto_ptr (nghĩa là con trỏ cấp phát và hủy bỏ vùng nhớ tự động) để giải quyết vấn đề rò rỉ bộ nhớ (tuy vậy vẫn có phiền toái, do đó lập trình viên tự cấp phát và giải phóng bộ nhớ vẫn là lựa chọn được khuyến khích hơn) Trong ví dụ dưới đây, p trỏ đến a (gọi là p sở hữu a) Bạn không cần gọi delete a Khi chương trình kết thúc, destructor của p được gọi, p sẽ bị hủy, nó sẽ tự động huỷ luôn a cho bạn. Đó là mục đích của auto_ptr, bạn không phải lo về leak memory CODE #include class MyClass{ int data; public: MyClass(int data):data(data){} friend ostream& operator<<(ostream& os,const MyClass& p) {os<<p.data<<endl;return os;} }; int main(){ MyClass* a = new MyClass(5); auto_ptr p(a); cout<<*p; return 0; } Dùng con trỏ bình thường thì có thể gây leak memory trong ví dụ sau CODE try{ Person *p = new Person;(*p).func();delete p; }catch(...) Dùng auto_ptr thì không lo việc ấy nữa CODE try{ auto_ptr p(new Person);(*p).func(); }catch(...) Quá tuyệt phải không ? Không hẳn thế, bản thân auto_ptr cũng có nhiều rắc rối khác. Cũng cái ví dụ trên, ta sửa lại một chút. Lần này p sẽ chuyển quyền sở hữu a cho p2. Lần này sẽ sinh ra lỗi, vì p2 trỏ đến vùng nhớ, chứ không phải p. Sau khi chuyển a cho p2 sở hữu, lúc này p chẳng sở hữu cái gì cả (khỉ thật, lúc này p đang trỏ đến cái gì ? ai mà biết) CODE class MyClass{ int data; public: MyClass(int data):data(data){} friend ostream& operator<<(ostream& os,const MyClass& p) {os<<p.data<<endl;return os;} }; int main(){ MyClass* a = new MyClass(5); auto_ptr p(a); cout<<*p; auto_ptr p2=p; cout<<*p; return 0; } const auto_ptr không thể chuyển quyền sở hữu được nữa, ví dụ sau là không hợp lệ CODE MyClass* a = new MyClass(5); const auto_ptr p(a); auto_ptr p2=p; Rắc rối thứ hai đó là auto_ptr không được dùng với cấu trúc bộ nhớ động, như mảng hay các bộ lưu trữ của STL như vector, list CODE int* a = new int[5]; auto_ptr p(a); lí do là vì khi destructor của p được gọi, nó sẽ gọi delete a, chứ không phải delete [] a Với các bộ lưu trữ của STL như vector, list, còn lí do là khi đưa phần tử vào, các bộ lưu trữ này chỉ sao chép giá trị của phần tử gốc và sau đó làm việc với các bản sao chép. Trong khi với auto_ptr, các bản sao chép này là KHÔNG giống nhau. Do đó tuỵệt đối không bao giờ dùng (dù chẳng thấy báo lỗi gì cả) CODE vector > v; auto_ptr có một vài hàm tiện ích hàm reset p đã trỏ đến a rồi, bây giờ ta trỏ p đến b, thì vùng nhớ do a trỏ đến sẽ bị phá hủy CODE MyClass* a = new MyClass(5); cout<<*a; auto_ptr p(a); MyClass* b = new MyClass(7); p = new auto_ptr(b); cout<<*p; cout<<*a; Ta có thể làm tương tự như vậy bằng hàm reset, vùng nhớ do a trỏ đến cũng sẽ bị phá hủy CODE MyClass* a = new MyClass(5); cout<<*a; auto_ptr p(a); MyClass* b = new MyClass(7); p.reset(b); cout<<*p; cout<<*a; hàm get Hàm get trả về vùng nhớ đã do auto_ptr sở hữu CODE Thay vì cout<<*p bạn có thể dùng cout<<*(p.get()) Bạn có thể dùng hàm get để kiểm tra xem vùng nhớ do auto_ptr trỏ đến có hợp lệ hay không Tuy vậy không thể dùng hàm get như sau CODE auto_ptr p(a); auto_ptr p2(p.get()); vì p vẫn còn quyền sở hữu a và p2 không thể chiếm lấy a được hàm release Hàm release y như hàm get thêm nữa là auto_ptr từ bỏ sẽ quyền sở hữu vùng nhớ Khi đó vấn đề ở trên với hàm get đã được giải quyết CODE auto_ptr p(a); auto_ptr p2(p.release()); vì p từ bỏ quyền sở hữu a nên p2 có thể chiếm lấy a mutable Trong một số trường hợp, chúng ta cần một biến số const có thể thay đổi giá trị. Ví dụ chúng ta cần thay đổi giá trị của a bằng hàm affect CODE class MyClass{ public: int a; MyClass(int a):a(a){} int affect() const{ return a++;//xuat ra roi moi thuc hien phep cong } }; Trong trường hợp này const_cast là một giải pháp hết sức tránh, const_cast không đảm bảo nó bỏ đi const với những object được khai báo const, do đó có thể gây ra lỗi không lường được, ví dụ CODE class MyClass{ public: int a; MyClass(int a):a(a){} int affect() const{ MyClass* mc = const_cast(this); return (*mc).a++; } }; int main(){ const MyClass m(6); cout<<m.affect()<<endl; return 0; } Trong trường hợp đó, mutable là lựa chọn thích hợp. mutable gần giống như "không thể là const" Một data member của một const object được khai báo mutable có thể thay đổi giá trị CODE class MyClass{ public: mutable int a; MyClass(int a):a(a){} int affect() const{ return a++; } }; int main(){ MyClass m(6); cout<<m.affect()<<endl; cout<<m.a<<endl; const MyClass m2(17); cout<<m2.affect()<<endl; cout<<m2.a<<endl; return 0; } volatile Khi bạn lập trình với các game chạy đa luồng, một biến được sử dụng bởi nhiều luồng khác nhau, mà mỗi luồng không thể biết được biến này sẽ được luồng khác thay đổi giá trị như thế nào. Một biến như vậy phải được khai báo là volatile, tức là những biến mà giá trị có thể bị thay đổi bất cứ lúc nào. Trong phần cứng thì thường dùng hơn chúng ta. Chúng ta không học về volatile lúc này Đánh giá tốc độ chương trình Đây là phần quan trọng để xác định thời gian chạy và đánh giá tốc độ chương trình của mình có tốt hay không. Với game thì tốc độ chạy chương trình là một trong những ưu tiên. Chẳng ai thích những game chất lượng chỉ ở mức khá nhưng chạy chậm rì so với những game chất lượng tốt hơn nhưng chạy nhanh hơn trên cùng một hệ thống. Bạn có thể tính thời gian chạy của những thuật toán xử lí đồ họa, AI, etc bạn viết trong game bằng những hàm trong thư viện Đây là thư viện làm việc liên quan đến thời gian của C/C++ Các hàm với time (thời điểm) Ví dụ sau sẽ in ra thời điểm hiện tại CODE #include int main(int argc,char** argv){ time_t curenttime; time(&curenttime); tm* timeinfo; timeinfo = localtime(&curenttime); char* time = asctime(timeinfo); cout<<time<<endl; return 0; } Giải thích: time_t: (time type) (kiểu thời điểm) là kiểu dữ liệu lưu trữ thời điểm tính theo giây bắt đầu từ 0 giờ 0 phút 0 giây ngày 1 tháng 1 năm 1970 time(): hàm trả về kiểu time_t thời điểm hiện tại (current time) tm: cấu trúc lưu thời gian, bao gồm giây, phút, giờ, ngày, tháng, năm localtime(): hàm chuyển kiểu time_t về kiểu tm asctime(): hàm chuyển kiểu tm về kiểu char* Một số hàm khác ctime(): hàm chuyển kiểu time_t về kiểu char* mktime(): hàm chuyển kiểu time_t về kiểu tm difftime(): tính sự khác biệt về thời gian theo giây giữa hai time_t, trả về double Ví dụ sau dùng difftime để tính sự khác biệt về thời gian theo giây với do something là chương trình của bạn CODE int main(int argc,char** argv){ time_t time1, time2; time(&time1); //do something time(&time2); cout<<difftime(time2,time1)<<endl; return 0; } Các hàm với clock (thời khắc) một khắc: một chút thời gian, một tí xíu thời gian (nhỏ hơn một giây) khắc là một khái niệm thời gian không rõ ràng trong ngôn ngữ nên bạn cũng không cần quan tâm đến một khắc bằng một phần mấy của giây làm gì clock_t: (clock type) (kiểu thời khắc) là kiểu dữ liệu lưu trữ thời khắc clock(): trả về số lượng thời khắc (clock tick) đã qua kể từ khi chương trình chạy Có một macro gọi là CLOCKS_PER_SEC trả về số lượng khắc trong một giây (số lượng khắc trong một giây tùy thuộc trình biên dịch và ta không cần quan tâm, một số trình biên dịch để là một ngàn, một số là một triệu) Ví dụ sau ta sẽ viết hàm wait (chờ tính theo giây) bằng cách dùng clock() CODE void wait(int seconds){ clock_t waittime; waittime=clock()+seconds*CLOCKS_PER_SEC; while(clock()<waittime); } int main(int argc,char** argv){ time_t time1, time2; time(&time1); wait(3);//chờ 3 giây time(&time2); cout<<difftime(time2,time1)<<endl; return 0; } seconds*CLOCKS_PER_SEC sẽ tính số lượng khắc cần trải qua trong đủ 3 giây và vòng lặp while của bạn chỉ cần chạy trong đủ số lượng khắc đó Ngoaì ra bạn cũng có thể tính số khắc đã trải qua (sự khác biệt về thời gian theo khắc) với do something là chương trình của bạn CODE int main(int argc,char** argv){ clock_t beginclock = clock(); //do something clock_t endclock = clock(); cout<<endclock-beginclock<<endl; return 0; } Bây giờ bạn đã có thể dùng để tính toán thời gian chương trình của bạn thực thi và so sánh thời gian thực hiện những thuật toán của bạn, xem cái nào nhanh cái nào chậm theo giây hoặc theo khắc. Còn một giải pháp khác chính xác hơn là tính toán dựa trên chính tốc độ của CPU nhưng mình sẽ không trình bày vì nó đụng đến hợp ngữ. Giải pháp này tuy không hoàn toàn chính xác vì còn có sai số vì tùy theo nhiều yếu tố khác nữa nhưng như vậy cũng đủ dùng vì sai số không đáng kể. ở mức chấp nhận được. Những phần sau đã bị cắt: smart pointer, garbage collector và inline assembly. Các bạn có thể tự tìm hiểu thêm nếu muốn. Môn lập trình C/C++ nâng cao đến đây kết thúc. This post has been edited by vietgameprogramming
File đính kèm:
- Lập trình CC++ nâng cao.pdf