C++ định nghĩa một hệ thống hoàn chỉnh để khai báo hằng chỉ-đọc; biến được const là chỉ đọc, trình biên dịch sẽ kiểm tra xung đột tại thời điểm biên dịch để tránh sửa đổi, đồng thời có thể tối ưu.
Thông thường nên dùng const cho biến và tham số để tăng độ an toàn và độ rõ ràng của mã.
Từ khóa loại const
Hằng
Biến const không thể đổi giá trị sau khi khởi tạo.
123
constinta=0;// a có kiểu const int// a = 1; // không thể sửa hằng
Tham chiếu hằng, con trỏ hằng
Tham chiếu hằng và con trỏ hằng đều hạn chế việc sửa giá trị được trỏ tới.
1 2 3 4 5 6 7 8 910111213141516
inta=0;constintb=0;int*p1=&a;*p1=1;constint*p2=&a;// *p2 = 2; // không thể sửa biến thông qua con trỏ hằng// int *p3 = &b; // không thể dùng int* trỏ tới biến const intconstint*p4=&b;int&r1=a;r1=1;constint&r2=a;// r2 = 2; // không thể sửa biến thông qua tham chiếu hằng// int &p3 = b; // không thể dùng int& tham chiếu biến const intconstint&r4=b;
Ngoài ra cần phân biệt con trỏ hằng (const t*) và hằng con trỏ (t* const), ví dụ:
12345678
int*constp1;// hằng con trỏ: sau khởi tạo không đổi địa chỉ, có thể đổi giá trị trỏ tớiconstint*p2;// con trỏ hằng: không đổi giá trị khi giải tham chiếu, có thể trỏ tới int khácconstint*constp3;// con trỏ hằng kiêm hằng con trỏ: cả giá trị lẫn địa chỉ đều không đổi// Dùng alias để tăng độ dễ đọcusingconst_int=constint;usingptr_to_const_int=const_int*;usingconst_ptr_to_const_int=constptr_to_const_int;
Dùng const trong tham số hàm giúp tránh sửa nhầm và tăng độ rõ ràng.
1234
voidsum(conststd::vector<int>&data,int&total){for(autoiter=data.begin();iter!=data.end();++iter)total+=*iter;// iter là iterator, kiểu sau giải tham chiếu là const int}
Hàm thành viên const
Hàm thành viên có const dùng để cấm sửa thành viên.
1 2 3 4 5 6 7 8 910111213141516171819202122232425
#include<iostream>structConstMember{ints=0;voidfunc(){std::cout<<"General Function"<<std::endl;}voidconstFunc1()const{std::cout<<"Const Function 1"<<std::endl;}voidconstFunc2(intss)const{// func(); // hàm const không thể gọi hàm không constconstFunc1();// s = ss; // hàm const không thể sửa biến thành viên}};intmain(){intb=1;ConstMemberc{};constConstMemberd=c;// d.func(); // đối tượng const không thể gọi hàm không constd.constFunc2(b);return0;}
Biểu thức hằng constexpr (C++11)
Biểu thức hằng là biểu thức có thể tính tại thời điểm biên dịch; constexpr yêu cầu trình biên dịch tính được giá trị của hàm hoặc biến tại thời điểm biên dịch.
Tính tại compile-time cho phép tối ưu tốt hơn, ví dụ nhúng trực tiếp kết quả vào mã máy, bỏ chi phí tính runtime. Khác với tối ưu do const, khi constexpr thỏa điều kiện biểu thức hằng, trình biên dịch bắt buộc tính ở compile-time.
Hiểu trực quan: const là “chỉ đọc”, constexpr là “bất biến”
12345678
constexprinta=10;// khai báo hằng trực tiếpconstexprintFivePlus(intx){return5+x;}voidtest(constintx){std::array<int,x>c1;// lỗi, x không biết tại compile-timestd::array<int,FivePlus(6)>c2;// được, FivePlus biết tại compile-time}
Ví dụ sau minh họa rõ sự khác biệt giữa const và constexpr, dùng đệ quy tính Fibonacci và in ra:
fib1(unsignedint):pushr14pushrbxpushraxmovebx,1cmpedi,2jb.LBB0_4movr14d,edixorebx,ebx.LBB0_2:leaedi,[r14-1]callfib1(unsignedint)addr14d,-2addebx,eaxcmpr14d,1ja.LBB0_2incebx.LBB0_4:moveax,ebxaddrsp,8poprbxpopr14retmain:pushr14pushrbxpushraxmovedi,9callfib1(unsignedint)# khởi tạo `v1` có gọi hàmmovebx,eaxmovr14,qwordptr[rip+std::__1::cout@GOTPCREL]movrdi,r14movesi,55# `v0` được thay bằng kết quả tính sẵncallstd::__1::basic_ostream<char,std::__1::char_traits<char>>::operator<<(unsignedint)@PLTmovbyteptr[rsp+7],32learsi,[rsp+7]movedx,1movrdi,r14callstd::__1::basic_ostream<char,std::__1::char_traits<char>>&std::__1::__put_character_sequence[abi:ne200000]<char,std::__1::char_traits<char>>(std::__1::basic_ostream<char,std::__1::char_traits<char>>&,charconst*,unsignedlong)movrdi,r14movesi,ebx# đọc giá trị biếncallstd::__1::basic_ostream<char,std::__1::char_traits<char>>::operator<<(unsignedint)@PLTxoreax,eaxaddrsp,8poprbxpopr14ret
Hàm fib0 được gọi với tham số hằng, nên toàn bộ chạy ở compile-time. Do không chạy ở runtime, trình biên dịch có thể bỏ phát sinh mã máy của hàm.
Đồng thời, v0 không có mã khởi tạo; khi in ra, v0 đã được thay bằng kết quả cuối cùng, cho thấy giá trị đã được tính tại compile-time.
Còn v1 vẫn là lời gọi đệ quy thông thường.
Vì vậy constexpr có thể dùng thay cho macro hằng, tránh rủi ro của macro.
Trong bài toán thuật toán, có thể dùng constexpr để lưu biến có kích thước nhỏ, giúp loại bỏ chi phí tính toán runtime. Đặc biệt thường gặp trong kỹ thuật "đánh bảng" khi dùng constexpr cho mảng/lưu đáp án.
Tính toán compile-time quá lớn sẽ gây lỗi biên dịch
Trình biên dịch giới hạn chi phí tính compile-time; nếu quá lớn sẽ không biên dịch được, khi đó nên dùng const.
1 2 3 4 5 6 7 8 91011121314
#include<iostream>usingnamespacestd;constexprunsignedlonglongfib(unsignedlonglongi){returni<=2?i:fib(i-2)+fib(i-1);}intmain(){// constexpr auto v = fib(32); evaluation exceeded maximum depthconstautov=fib(32);cout<<v;return0;}
Lỗi biên dịch của Clang khi dùng constexpr
1 2 3 4 5 6 7 8 910
<source>:10:20: error: constexpr variable 'v' must be initialized by a constant expression
10 | constexpr auto v = fib(32);
| ^ ~~~~~~~~~~~~
<source>:6:25: note: constexpr evaluation exceeded maximum depth of 512 calls
6 | return i <= 2 ? i : fib(i - 2) + fib(i - 1);
| ^
<source>:6:25: note: in call to 'fib(32)'
6 | return i <= 2 ? i : fib(i - 2) + fib(i - 1);
| ^~~~~~~~~~
<source>:6:25: note: in call to ...
Last updated on this page:, Update history Found an error? Want to help improve? Edit this page on GitHub! Contributors to this page:OI-wiki All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply