ویژگی های اضافه شده در C++11 ,C++14 :
-
auto
-
Range Based Loops
-
User-defined Literals
-
Regular expressions
-
Thread support
-
Lambda Functions/Expressions
-
static_assert
-
Enum class
-
decltype
-
nullptr
-
char32_t / char16_t
-
Override
-
Final
-
Default
-
Delete
-
initializer_list
-
constexpr
-
Move Semantics
-
Hash table
-
Variadic templates
-
Non static data member initialize
-
Generic lambas
-
Return type deduction
-
Binary literals
-
Initialize lambda captures
-
Single qoution mark as seperator
auto
قبلا برای تعریف متغییر ها حتما باید نوع اون رو مشخص میکردید تا متغییر مورد نظر نسبت به نوع مورد نظر ساخته بشه . ولی گذاشتن کلمه ی auto به جای نوع متغییر میشه باعث میشه که نوع متغییر نسبت به مقداری نوع مقداری که به اون داده بشه بصورت خودکار مشخص بشه و کامپایلر خودش نوع مناسب رو برای متغییر در نظر میگیره .
مثلا :
list<int> numbers;
list<int>::iterator i = numbers.begin();
auto j = numbers.begin();
auto k = 1;
auto m = some_func();
در کد بالا متغییر i رو از نوع list<int>::iterator تعریف کردیم برای اینکه باید از نوع برگشتی () numbers.begin باشه و کاملا هم درسته .
خوب بجای این نوشتن همچین نوعی برای متغییر میتونیم به جاش از auto استفاده کنیم تا خود کامپایلر نوع رو تشخیص بده و دیگه نیازی به این نباشه که ببینیم () numbers.begin چه نوعی رو برمیگردونه نداریم و نوع متغییر خودمون رو نسبت به اون تعریف کنیم .
که برای متغییر j همچین کاری رو کردیم .نوع متغییر i رو خودمون مشخص کردیم و نوع متغییر j رو کامپایلر مشخص کرده . درواقع متغییر j دقیقا هم نوع متغییر i هست .
متغییر k هم بدلیل اینکه مقدار 1 رو بهش دادیم کامپایلر اون رو به int تبدیل میکنه .
و متغییر m هم همون نوعی میشه که تابع () some_func برمیگردونه .
چندتا نکته در مورد auto :
متغییری که بصورت auto تعریف میشه و برای مقدار دهی اولیه مقداری بهش داده میشه . ولی هیچ ويژگي مانند Const یا Reference رو از مقدار داده شده رو نمیگیره .تنها از نوع اون متغییر یا مقدار-لفظی الگو میگیره و نوع خودش رو دوباره تعریف میکنه .
نمونه :
const int i = 3;
const int& t = i;
auto r = t;
r++;
در اینجا r تغییرپذیر است چون متغییر r تنها int رو از متغییر t الگو گرفته . نه const و reference رو .این به این معنی است که متغییر r تنها یک int معمولی است .
نکته دیگه اینه که اگه شما auto رو بصورتی یک ارجاع تعریف کنید همه ی ویژگی های متغییر سمت راست رو میگیره .یعنی خود این auto از نوع ارجاع میشه .
int t = 0;
auto& r = t;
r++;
اگر هم متغییر سمت راست اشاره گر باشه . auto همه ی ویژگی های سمت راست رو میگیره.
const int a = 0;
const int* b = &a;
auto& r = b;
*r = 10;
اگر r رو تغییر بدید با خطای تغییر دادن const رو برو میشید .
Range Based Loops
یکی از ویژگی های بدرد بخور C++ 11 همین range-based loops هست .
این حلقه همون حلقه for هست با این تفاوت که دامنه ای محدود داره و برای شمردن های خاص بکار مبیره .
برای نمونه این تکه کدی هست که با for معمولی نوشته شده :
int list[]={1,2,3,4,5,6,7,7,7,7,7,8,9,0,0,0,1,1};
for(int i = 0;i < (sizeof(list) / sizeof(int));i++)
{
cout<<list[i];
}
حالا این همون کد با range-based for نوشته شده :
int list[]={1,2,3,4,5,6,7,7,7,7,7,8,9,0,0,0,1,1};
for(int i:list)
cout<<i;
این هم یه روش دیگه :
for(auto i:{1,2,3,4,5,6,7,7,7,7,7,8,9,0,0,0,1,1})
cout<<i;
i یک نوع هست . میتونه int باشه میتونه float باشه . میتونه هر نوع دیگه ای باشه .
تعداد تکرار این حلقه به اندازه ی تعداد اعضا (Member) های این آرایه است .در کد بالا 18 بار این حلقه میچرخه.چونکه تعداد اعضای آرایه ی list میشه 18 تا .
و در هر گام for مقدار بعدی ریخته میشه درون متغییر i .
یعنی در اولین گام حلقه for مقدار i میشه 1. و کد درون for انجام میشه .
و در دوازدهمین گام حلقه for مقدار i میشه 8 . چون دوازدهمین عنصر این آرایه مقدار 8 هست.
برای نمونه اگه i از نوع float باشه . در اینجا یه تغییر کوچیک انجام میگیره و مقدار 8 در متغییر i که از نوع float هست ریخته میشه .
مناسب ترین روش برای این حلقه ها بکارگرفتن از auto هست .
اگه i رو از نوع auto تعریف کنیم Compiler نوع i رو از نوع عنصر داده شده تعریف میکنه. که اینجوری خیلی کار آسونتر و بهتر میشه .
برای مثال اگه میخواهید یه vector رو مقدار دهی کنید ، میتوانید بجای این روش :
vector<int> vec;
vec.push_back(10);
vec.push_back(3);
vec.push_back(5);
vec.push_back(17);
vec.push_back(1);
vec.push_back(6);
vec.push_back(2);
vec.push_back(2);
از این روش میشه استفاده کرد :
for(auto i:{10,3,5,17,1,6,2,2})
vec.push_back(i);
User-defined Literals
توسط BlueBlade
توانایی تعریف literal های جدید مشایه literal های پش فرض :
123 // int
1.2 // double
1.2F // float
'a' // char
1ULL // unsigned long long
0xD0 // hexadecimal unsigned
"as" // string
مثال :
#include <iostream>>
#include <vector>
#include <cmath>
#include <stdlib.h>
using namespace std;
vector <int > operator "" _vect(unsigned long long num)
{
vector <int > v;
while(num>0)
{
v.push_back(num%10);
num /=10;
}
return v ;
}
int operator "" _pow (const char *str,size_t n)
{
return pow(atoi(str),2);
}
string operator "" _cpp(const char ch)
{
return "C++11 is fun:D";
}
int main()
{
vector <int > vect=12345678_vect;
for(auto i:vect)
cout<<i<<endl;
cout<<"12"_pow<<endl;
cout<<'m'_cpp<<endl;
}
Lambda Functions/Expressions
یکی از ویژگی های جدید و برجسته ی C++ 11 توابع ها و عبارت های لامبدا (Lambda) هست .
Lambda ها توابعی هستند بدون نام ! که میتونن درون و بیرون توابع معمولی تعریف و پیاده سازی بشند.
توابع Lambda از این بخش ها تشکیل میشن :
-
Capture
-
Arguments
-
Mutability
-
Return Type
-
Body
-
Parameters
برای لامبدا ها چگونگی تعریف کردن یکسانی نیست . بسته به نیاز تعریف کردنشون هم متفاوت هست .
ولی رویه هم رفته میشه لامبدا ها رو به این شکل نشون داد :
[] () mutable -> {} ()
[] : این برای اعلان کردن یک عبارت لامبدا بکار میره .
() : در این پرانتزها آرگومان های لامبدا گذاشته میشه
mutable : بسته به شرایط است. اگر mutable نوشته شود ، لامبدا میتونه مقدارهایی رو که بصورت pass-by-value رو از حوزه ی (Scope) بیرون Capture کرده رو تغییر بده . و یا مثلا اگر یک شی از یک کلاس رو Capture کرده که و میخواد یکی از متد های اون شی رو صدا بزنه و اون متد const نیست و باعث تغییر عضو های کلاس میشه باید لامبدا رو mutable تعریف کنیم .
<- : پس از این عملگر نوع برگشتی لامبدا نوشته میشه .
{} : بدنه ی لامبدا درون این تعریف میشه .
() : این پرانتز در پایان لامبدا نشوته میشه که برای وارد کردن پارامتر هست .
نمونه هایی از لامبدا :
auto a = []{return 5;}();
auto b = []{return 'a';}();
int a = 2,b = 7;
auto r = [](int a,int b){if(a > b)return a;if(b > a)return b; return 0;}(a,b);
auto jam = [](int n1, int n2, int n3){ return n1+n2+n3; } (10,20,70);
در نمونه های بالا نوع برگشتی تعریف نشده . ولی بصورت خودکار بسته به مقداری که return برمیگردونه مشخص میشه .
در این نمونه نوع برگشتی رو double درنظر میگیریم و برای اینکار از عملگر <- بکارگیری میکنیم :
auto aa = [](double f)->double { float ret ; ret = f * 10;return ret;}(44.0f);
یه نکته اینه که برای صدا زدن یک عبارت لامبدا باید پرانتز های پایانی رو استفاده کنید. اگر لامبدا پارامتر میخواست پارامتر رو میدیم ، اگرهم نه بصورت خالی () باید نوشته بشه .
دخیره کردن Lambda برای استفاده دوباره .
برای ذخیره کردن و صداکردن یک لامبدا به این شکل میشه این کار رو کرد :
auto aa = [](void) {cout<<"Hi"<<endl;return;};
aa();
یک نکته هم در مورد این هست . شما میتونید همین تعریف بالایی رو بیرون از توابع هم بکنید . و در توابع دیگه میتونید aa رو صدا بزنید .
یه مثال کاربردی برای مرتب سازی اعداد :
std::vector<int> vec_(10);
for (int i=0;i < 10;i++)
vec_[i]= rand()%10;
sort(vec_.begin(),vec_.end(), [](const int & a, const int & b) -> bool
{
return a < b;
});
for (auto a:vec_)
cout <<a << endl;
بخش Capture عبارت های Lambda :
بخش Capture مشخص میکنه که چه جور متغییر هایی که از حوزه های بالاتر هستند گرفته ( Capture ) بشن .
یعنی اگر شما بخواهید از متغییرهایی که بیرون از عبارت لامبدا تعریف شدن استفاده کنید ، اول باید اونها رو Capture کنید تا بتونید درون لامبدا ازشون استفاده کنید .
درصورتی که لامبدایی رو که تعریف میکنید به این شکل [] باشه ، هیچ متغییری رو از بیرون Capture نمیکنه .
اگر شما نام متغییری رو بیارید که میخواهید اون رو Capture کنید ٰ، یه کپی از اون متغییر درست با همون نام درون لامبدا ایجاد میشه . که نمیتونید تغییرش بدید و فقط خواندنی هست .
درصورتی میتونید متغییر Capture شده ای که کپی آن در لامبدا ایجاد شده رو تغییر بدید که لامبدا رو با ویژگی mutable بشناسونید.
الگو های Capture (ربودن) :
-
[] : هیچ چیز رو Capture نمیکنه .
-
[=] : همه چی رو بصورت مقدار Capture میکنه . یعنی کپی میکنه درون خود لامبدا با همون نام ها .
-
[&] : همه چیر رو بصورت ارجاع Capture میکنه . یعنی اگر شما تغییرش بدید ، متغییری که به اون ارجاع شده هم تغییر میکنه.
-
[var] var نام متغییری است که میخواهید Capture بشه بصورت مقدار .
-
[var&] var نام متغییری است که میخواهید Capture بشه بصورت ارجاع .
نمونه :
int a = 5,b = 10;
[a] (void)
{
a++;
b++;
}();
متغییر a حالا Capture شده . ولی تغییرپذیر نیست . چون که ما Lambda رو بصورت Mutable تعریف نکردیم تا بتونه متغییر رو تغییر بده .
متغییر b هرگز Capture نشده . درکل نمیشه ازش استفاده کرد .
اگر تو همین مثال ما Lambda رو از نوع Mutable تعریف میکردیم ، میتونستیم a رو تغییر بدیم . ولی متغییر a اصلی که بیرون Lambda هست تغییر نمیکنه. چون متغییر aی که درون Lambda هست بصورت مقدار Capture شده.
و باز از b نمیتونیم استفاده کنیم چون هرگز Capture نشده .
یه نمونه دیگه :
int a = 5,b = 10,c = 15;
auto my = [=] (void) -> float
{
return a + b + c;
}();
علامت = در داخل بخش Capture به این معنی است که همه ی متغییر ها رو بصورت مقدار Capture کن .
ولی اگر شما بخواهید تغییری درونشون بدید باید Labmda رو با ویژگی Mutable تعریف کنید . در اینجا عبارت Lambda تنها متغییر ها رو جمع میکنه و مقدار رو برمیگردونه و هیچ تغییری نمیده.
نمونه دیگه :
int a = 5,b = 10,c = 15;
auto pishfarz = [&](void) -> void
{
a = 1;
b = 2;
c = 3;
};
تو این نمونه علامت & داخل بخش Capture میگه که همه چیز رو بصورت یک ارجاع Capture کن .
و حالا ما این Lambda رو صدا نمیزنیم . ولی اون رو میزاریم درون متغییر Pishfarz .هر زمان خواستیم میتونیم متغییر Pishfarz رو با 2 پرانتز () صدا بزنیم تا Lambda ی که درون اون هست انجام بشه .
و حالا شما اگه هرچیز روکه بصورت یک ارجاع Capture شده رو تغییر بدید ، متغییر اصلیش هم تغییر میکنه .
و برای تغییر دادن متغییرهای ارجاع نیازی نیست که Lambda رو با ویژگی Mutable بشناسونیم.
Mutable تنها برای تغییر متغییر های Capture شده بصورت مقدار هست .
نکته ی دیگه ای که در مورد Capture ها هست اینه که شما میتونید اونها رو باهم ترکیب کنید .
نمونه :
[a,b]() {return a+b;}();
متغییر a و b رو بصورت مقدار Capture میکنه .
int a = 0;
int b = 5;
int jam = 0;
[=, &jam](){ jam = a+b;}();
همه ی متغییر ها رو بصورت مقدار Capture میکنه ،ولی متغییر jam رو بصورت ارجاع Capture میکنه.
در Capture های ترکیبی & و = باید اول نوشته شوند وگرنه با خطا روبرو میشید .
[&jam,=]{} // nadorost . = baiad samte chap bashe.
[a1,a2,&]{} // nadorost . & baiad samte chap bashe.
در لیست Capture کردن نمیشه این عبارت رو نوشت :
[=, &]{} // nadorost