变参宏定义
#define LOG(...) {\
fprintf(stderr, "%s: Line %d: \t", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
}
int main() {
int x = 3;
LOG("x = %d", x);
return 0;
}
C++11中,非常量的静态成员变量需要到文件以外定义。这样做可以保证在编译时,类静态成员的定义最后只存在于一个目标文件中。
外部模板声明不能用于一个静态函数,因为静态函数没有外部链接属性,不可能在本编译单元之外出现,但是可以用于类静态成员函数。
在代码清单2-30中,我们定义了一个模板类X和一个模板函数TempFun,然后分别用普通的全局结构体、匿名的全局结构体,以及局部的结构体作为参数传给模板。可以看到,
使用了局部的结构体C及变量c,以及匿名的结构体B及变量b的模板类和模板函数,在C++98标准下都无法通过编译。而除了匿名的结构体之外,匿名的联合体以及枚举类型,在C++98标准下也都是无法做模板的实参的。如今看来这都是不必要的限制。所以在C++11中标准允许了以上类型做模板参数的做法﹐故而用支持C++11标准的编译器编译以上代码,代码清单2-30所示代码可以通过编译。
委派构造函数只能在函数体中为成员变量赋值,不能使用初始化列表。
struct Rule1 {
int i;
Rule1(int a) : i(a) {}
Rule1() : Rule1(40), i(i) {} //这里委派了Rule1(40),但是其使用了初始化列表,不能通过编译
};
但是如果私有化委派构造函数,可以通过编译,改进方法如下:
class Info {
public:
Info() : Info(1, 'a') {}
Info(int i) : Info(i, 'a') {}
Info(char e) : Info(1, e) {}
private:
Info(int i, char e) : type(i), name(e) {/*其他初始化*/}
int type;
char name;
//....
};
使用构造模板函数
#include <list>
#include <vector>
#include <queue>
#include <iostream>
using namespace std;
class TDConstructed {
private:
template <class T> TDConstructed(T first, T last)
: li(first, last) {}
list<int> li;
public:
TDConstructed(vector<short> &v) :
TDConstructed(v.begin(), v.end()) {}
TDConstructed(deque<int> &d) :
TDConstructed(d.begin(), d.end()) {}
};
目标函数产生的异常,如果委派函数使用了try语句,则可以捕获异常。
class DCExcept {
public:
DCExcept(double d)
try : DCExcept(1, d) {
cout << "Run the body." << endl;
//其他初始化
} catch (...) {
cout << "caught exception." << endl;
}
private:
DCExcept(int i, double d) {
cout << "going to throw!" << endl;
throw 0;
}
int type;
double data;
};
在代码清单3-15中,我们在目标构造函数DCExcept(int,double)抛出了一个异常,并在委派构造函数DCExcept(int)中进行捕捉。编译运行该程序,我们在实验机上获得以下输出:
右值引用:移动语义和完美转发
可以取地址的是左值,不可以取地址的是右值。
右值分为将亡值,纯右值,字面值是纯右值,类型转换函数的返回值,lambda表达式都是右值。
而将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的
对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值(稍后解释),
或者转换为T&&的类型转换函数的返回值(稍后解释)。而剩余的,可以标识函数、对象的
值都属于左值。在C++11的程序中,所有的值必属于左值、将亡值、纯右值三者之一。
T&& a = ReturnRvalue();
T b = ReturnRvalue();
上面的代码少一次对象的析构和构造,因为右值引用直接绑定了ReturnRvalue()返回的临时量,而b是由临时值构造而成的。
const 左值引用可以绑定右值上。
C++判断引用类型
标准库<type_traits>提供了三个模板类,is_rvalue_reference, is_lvalue_reference, is_reference
比如cout << is_rvalue_reference<string&&>::value;
判断类型是否可以移动
is_move_constructible, is_trivially_move_constructible, is_nothrow_move_constructible,使用方法时其成员value
cout << is_move_constructible<UnknownType>::value;
编译器优化RVO/NRVO,(Return Value Optimization返回值优化,NRVO,Named Return Value optimization),使用-fno-elide-constructors选项在g++/clang++关闭优化。
完美转发:指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
C++引用折叠:
template <typename T>
void IamForwording(T && t) {
IrunCodeActually(static_cast<T &&>(t));
}
完美转发的一个作用就是做包装函数。
initializer_list<int> 的使用
#include <iostream>
#include <vector>
using namespace std;
class Mydata {
public:
Mydata & operator [] (initializer_list<int> l) {
//用idx记录下标索引
for(auto i = l.begin(); i != l.end(); i++) {
idx.push_back(*i);
}
return *this;
}
Mydata& operator = (int v) {
if(idx.empty() != true) {
for(auto i = idx.begin(); i != idx.end(); i++) {
d.resize((*i > d.size()) ? *i : d.size());
//将指定下标的地方全部替换为v
d[*i - 1] = v;
}
idx.clear();
}
return *this;
}
void Print() {
for(auto i = d.begin(); i != d.end(); i++) {
cout << *i << " ";
cout << endl;
}
private:
vector<int> idx;
vector<int> d;
}
int main() {
Mydata d;
d[{2, 3, 5}] = 7;
d[{1, 4, 5, 8}] = 4;
d.Print(); // 4 7 7 4 4 0 0 4
}
在中括号可以使用列表,指定需要修改的下标。
初始化列表可以用来防止类型收窄。
判断是否是标准布局:is_standard_layout<T>::value
判断是否为POD类型,std::is_pod
#include <string>
using namespace std;
union T {
string s; //string 有非平凡构造函数
int n;
};
int main() {
T t; //构造失败,因为T的构造函数被删除了
}
在代码清单3-41中,联合体T拥有一个非POD的成员s。而string有非平凡的构造函
数,因此T的构造函数被删除,其类型的变量t也就无法声明成功。解决这个问题的办法是,
由程序员自己为非受限联合体定义构造函数。通常情况下,placement new会发挥很好的作用,
如代码清单3-42所示。
#include <string>
using namespace std;
union T {
string s;
int n;
public:
//自定义构造函数和析构函数
T () {new (&s) string; } //这句话的意思是指定地址,进行new操作,new 一个空string
~T() { s.~string();}
};
int main() {
T t; //构造成功
}
采用了placement new 将s构造在其地址&s处。在这个例子中,析构的时候也必须是一个string对象,否则可能会出现问题。
#include <cstring>
using namespace std;
struct Student {
Student(bool g, int a) :gender(g), age(a){}
bool gender;
int age;
}
class Singer {
public:
enum Type {STUDENT, NATIVE, FOREIGNER};
Singer(bool g, int a) :s(g, a) {t=STUDENT;}
Singer(int i): id(i){ t = NATIVE; }
Singer(const char* n, int s) {
int size = (s > 9) ? 9 : s;
memcpy(name, n, size);
name[s] = '\0';
t = FOREIGNER;
}
~Singer() {}
private:
Type t;
union { //匿名的非受限联合体
Student s;
int id;
char name[10];
};
};
int main() {
Singer(true, 13);
Singer(310217);
Singer("J Michael", 9);
}
字符串后缀操作符
#include <cstdlib>
#include <iostream>
using namespace std;
typedef unsigned char uint8;
struct RGBA {
uint8 r;
uint8 g;
uint8 b;
uint8 a;
RGBA(uint8 R, uint8 G, uint8 B, uint8 A = 0)
:r(R), g(G),b(B),a(A) {}
};
RGBA operator "" _C (const char* col, size_t n) {
const char* p = col
const char* end = col + n;
const char* r, *g, *b, *a;
r = g = b = a = nullptr;
for(; p != end; ++p) {
if(*p == 'r') r = p;
else if (*p == 'g') g = p;
else if (*p == 'b') b = p;
else if (*p == 'a') a = p;
}
if((r == nullptr ) || (g == nullptr ) || (b == nullptr))
throw;
else if ( a == nullptr)
return RGBA(atoi(r+1), atoi(g+1), atoi(b+1));
else
return RGBA(atoi(r+1), atoi(g+1), atoi(b+1), atoi(a+1));
}
std::ostream& operator << (std::ostream * out, RGBA & col) {
return cout << "r: " << (int)col.r
<< ", g: " << (int)col.g
<< ", b: " << (int)col.b
<< ", a: " << (int)col.a << endl;
}
void blend(RGBA && col1, RGBA && col2) {
cout << "blend " << endl << col1 << endl << col2 <<endl;
}
int main() {
blend("r255 g240 b155"_C, "r15 g255 b10 a7"_C);
}
量操作符( literal operator )函数: RGBA operator "" _C(const char* col, size_ t n)函数。这个函数会解析以C为后缀的字符串,并返回-一个RGBA的临时变量。
struct Watt{ unsigned int v; };
Watt operator "" _w(unsigned long long v) {
return {(unsigned int)v};
}
int main() {
Watt capacity = 1024_w;
}
使用_w表示瓦特,自定义字面量还可以用于浮点整数,但应该满足如下规则:
如果字面量位整型数,那么字面量的操作符函数只可以接受unsigned long long 或者const char* 为其参数,当unsigned long long 无法容纳字面量的时候,编译器会自动将该字面量转化为以'\0'结束的字符串,并调用以const char* 为参数的版本进行处理。
如果字面量为浮点整数,则字面量操作符函数只可接受longdouble或者constchar*为参数。const char*版本的调用规则同整型的一样(过长则使用const char*版本)。
如果字面量为字符串,则字面量操作符函数函数只可接受const char*, size_ t为参数(已知长度的字符串)。
如果字面量为字符,则字面量操作符函数只可接受一个char为参数。
)在字面量操作符函数的声明中,operator "与用户自定义后缀之间必须有空格。
后缀建议以下划线开始。不宜使用非下划线后缀的用户自定义字符串常量,否则会被编
译器警告。当然,这也很好理解,因为如果重用形如201203L这样的字面量,后缀“L”
无疑会引起一些混乱的状况。为了避免混乱,程序员最好只使用下划线开始的后缀名。
is_same判断类型是否一致。
using可以使用与模板别名
template <typename T> using MapString = std::map<T, char*>;
MapString<int> numberedString;
auto不能使用的地方
(1)函数中,不能用于形参类型。
(2)结构体中,非静态成员变量的类型不能是auto的。
(3)不能声明auto数组。
(4)实例化模板的时候,不能使用auto作为模板参数。
查看类的名称:typeid(a).name()
查看类的hash值:typeid(a).hash_code() == typeid(b).hash_code()
1)如果e是一个没有带括号的标记符表达式( id-expression )或者类成员访问表达式,
那么decltype(e)就是e所命名的实体的类型。此外,如果e是-一个被重载的函数,则会导致
编译时错误。
2)否则,假设e的类型是T,如果e是一个将亡值(xvalue), 那么decltype(e)为T&&。
3)否则,假设e的类型是T,如果e是一个左值,则decltype(e)为T&。
4)否则,假设e的类型是T,则decltype(e) 为T。
int arr[5] = {0};
int *ptr = arr ;
struct S { double d; } s;
void Overloaded (int} ;
void Overloaded (char) ; //重载的函数
int && RvalRef {};
const bool Func (int) ;
// 规则l:单个标记符表达式以及 访问类成员,推导为本类型
decltype (arr) varl ; // int[5], 标记符表达式
decltype (ptr) var2; // int*, 标记符表达式
decltype(s.d} var4 ; // double, 成员访问表达式
decltype (Overloaded) var5 ; //无法通过编译,是个重载的函数
//规则2:将亡值,推导为类型的右值引用
decltype (RvalRef ()) var6 = 1; // int&&
//规则3:左值,推导为类型的引用
decltype (true ? i : i) var7 = i; // int&, 三元运算符,这里返回一个i的左值
decltype{(i}) var8 = i; // int&, 带圆括号的左值
decltype(++i) var9 = i; // int&, ++i返回i的左值
decltype(arr [3] ) var10 = i; // int& [] 操作返回左值
decltype(*ptr) var11 = i; // int& *操作返回左值
decltype ("lval") var12 = "lval"; // const char(&) [9],字符串字面常量为左值
//规则4:以上都不是,推导为本类型
decltype(1) var13; // int, 除字符串外字面常量为右值
decltype(i++} var14; // int, i++返回右值
decltype ( (Func(1})) var15; // const bool, 圆括号可以忽略
is_rvalue_reference<decltype(RvalRef())>::value
is_lvalue_reference<decltype((i))>::value
is_const<decltype(ic)>::value
is_volatitle<decltype(b)>::value