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"

pdf76 trang | Chuyên mục: C/C++ | Chia sẻ: dkS00TYs | Lượt xem: 2612 | Lượt tải: 5download
Tóm tắt nội dung Lập trình C/C++ nâng cao, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
.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:

  • pdfLập trình CC++ nâng cao.pdf