Những tính năng quan trọng trong C++11

Thảo luận trong 'Kiến thức, kinh nghiệm lập trình' bắt đầu bởi nhatlectv, 8/2/18.

  1. nhatlectv

    nhatlectv Stanford - Nâng tầm tri thức Thành viên BQT

    C++11 là một phiên bản cải tiến và nâng cấp từ C++98 (hay các bạn vẫn gọi là C++), với những tính năng mới tối ưu hơn, dễ sử dụng hơn, dễ quản lý bộ nhớ hơn và khắc phục được các nhược điểm của phiên bản C++98. Những cải tiến quan trọng đó bao gồm các tính năng thú vị sau đây.

    1. Khai báo biến với từ khoá auto

    Bình thường, trong C++98, khi khai báo biến buộc phải chỉ rõ kiểu dữ liệu của biến, chẳng hạn:


    inti;
    longlongl;
    foo*p;
    Trong C++11, ta có thể yêu cầu chương trình dịch tự xác định kiểu dữ liệu của biến thông qua giá trị ban đầu của biến bằng cách khai báo biến với từ khoá auto, đoạn mã trên có thể viết lại như sau:

    autoi=42; // i là biến kiểu int
    autol=42LL; // l là biến thuộc kiểu long long
    autop=newfoo();// p là biến con trỏ foo*



    Khai báo biến với từ khoá auto giúp tiết kiệm được kha khá mã nguồn nhưng vẫn dễ đọc:


    std::map<std::string,std::vector<int>>map;
    for(auto it=begin(map);it!=end(map);++it){
    //it là biến thuộc kiểu std::map<std::string, std::vector<int>>::interator
    }



    Lưu ý là C++11 hiểu được cả >>

    Khi sử dụng auto cho kết quả trả lại của hàm, phải chỉ rõ kiểu kết quả trả lại của hàm:

    template<typename T1,typename T2>
    auto compose(T1 t1,T2 t2)->decltype(t1+t2){//decltype(t1 + t2) = kiểu dữ liệu của biểu thức t1 + t2
    returnt1+t2;
    }
    // Sử dụng hàm compose:
    autov=compose(2,3.14);// Kiểu của biến v là double
    autou=compose(2,3);// Kiểu của biến u là int



    2. Con trỏ std::nullptr

    Trong C++98 giá trị vô nghĩa của biến con trỏ là NULL, NULL là số nguyên: 0. Sự đồng nhất NULL và 0 có điểm bất tiện. Trong ví dụ dưới đây, khi gọi foo(NULL) thì hàm func nào sẽ được gọi?


    //Định nghĩa chồng tên hai hàm foo
    voidfoo(int*x){}
    voidfoo(intx){}

    //Sử dụng:
    foo(NULL);



    Câu trả lời là foo(int) chứ không phải là foo(int*) vì NULL là một số nguyên.

    C++11 định nghĩa rõ ràng giá trị vô nghĩa của con trỏ là nullptr. Trong ví dụ trên, nếu gọi foo(nullptr) thì foo(int*) sẽ được gọi.
    nullptr được sử dụng tương tự như NULL, ngoại trừ một điểm: nullptr thuộc kiểu con trỏ. Giá trị NULL trong C++11 vẫn được sử dụng để tương thích ngược với C++98 các phiên bản trước. Ví dụ sau minh hoạ cách sử dụng nullptr:


    int*p1=NULL;
    int*p2=nullptr;
    if(p1==p2){// Phép so sánh NULL == nullptr có giá trị true }

    boolf=nullptr;// nullptr có thể tự động chuyển kiểu bool, f = false
    inti=nullptr; // Lỗi: biến int i không thể nhận giá trị con trỏ



    3. Cấu trúc lặp for duyệt qua các phần tử của một tập hợp (Ranged-base for loop)

    C++11 bổ sung cấu trúc lặp for dùng để duyệt qua các phần tử của một tập hợp, tương tự như thuật toán for_each trong thư viện algorithms của C++98 nhưng thuận tiện hơn nhiều:


    inta[]={1,2,3,4,5};

    for(auto&x:a)x--;// Duyệt qua các phần tử của a, mỗi phần tử giảm đi 1 đơn vị
    for(autox:a)cout<<x<<' ';// duyệt qua các phần tử của a, ghi giá trị ra stdout



    Tương tự:

    std::map<std::string,std::vector<int>>map;
    std::vector<int>v={1,2,3};
    map["one"]=v;

    for(constauto&kvp:map){
    std::cout<<kvp.first<<std::endl;

    for(autov:kvp.second){
    std::cout<<v<<std::endl;
    }
    }
    4. Sử dụng chỉ định override và final cho các hàm ảo

    Trong chương trình sau, ở định nghĩa lớp B, người lập trình muốn định nghĩa lại (override) hàm ảo Show() của lớp cha: A:


    #include <iostream>
    using namespacestd;
    classA{
    public:
    virtual voidShow(){
    }
    };

    classB:publicA{
    public:
    virtual voidShow()const{
    }
    };

    intmain(){
    Bb;
    A *p=&b;

    p->Show();
    return0;
    }
    Nếu chương trình đúng thì p->Show() phải gọi đến B::Show(), nhưng chương trình trên gọi đến A::Show(). Nguyên nhân do hàm Show trong lớp B khai báo khác với nguyên mẫu hàm Show trong lớp A (có/không có const). Theo đó, Show trong lớp B chỉ được coi là định nghĩa chồng tên (overload) và là hàm khác với Show của lớp A. Đây là một lỗi trong lập trình. Để đạt mục đích định nghĩa lại, phải sửa: bỏ const đi.
    Nhằm tránh những lỗi như trên và để rõ ràng hơn trong mã nguồn, C++11 bổ sung chỉ định override để chỉ rõ hàm ảo là hàm định nghĩa lại.

    Trong ví dụ sau, nếu có chỉ định override cho hàm Show, chương trình dịch có thể bắt lỗi được ngay:


    classA{
    public:
    virtual voidShow(){
    }
    };

    classB:publicA{
    public:
    voidShow()constoverride{// Lỗi: Không có hàm Show (có const) nào để định nghĩa lại
    }
    };
    C++11 cũng bổ sung thêm chỉ định final cho hàm ảo, khi đó hàm ảo này ở các lớp con sẽ không được phép định nghĩa lại. Ví dụ:


    classA{
    public:
    virtual voidShow()final{
    }
    };

    classB:publicA{
    public:
    voidShow(){//Lỗi: Show không được phép định nghĩa lại từ lớp A, do Show trong lớp A có chỉ thị final
    }
    };
    5. Kiểu dữ liệu liệt kê enum class

    Trong C++98, cùng một phạm vi mã nguồn (scope), các giá trị trong 2 kiểu dữ liệu liệt kê (enum) phải khác nhau, chẳng hạn:

    enumE1{a,b,c};
    enumE2{c,d,f};// Lỗi: Trùng giá trị c với E1.
    C++11 khắc phục hạn chế nói trên bằng kiểu liệt kê enum class. Enum class hỗ trợ định kiểu mạnh: chỉ rõ giá trị liệt kê thuộc kiểu liệt kê nào, do đó tránh được xung đột tên:

    enumclassE1{a,b,c};
    enumclassE2{c,d,f};
    //Sử dụng:
    E1 e1=E1::c;// Giá trị c của E1
    E2 e2=E2::c;// Giá trị c của E2
    Hy vọng bài chia sẻ này sẽ giúp ích cho các bạn!
     

Chia sẻ trang này