c++2.0
C++ 2.0
nullptr 空指针
void f(int);
void f(void*);
f(0); // 调用 1
f(NULL); // 模糊不定,报错
f(nullptr); // 调用 2
auto
自动推导类型
推导变量类型
auto i = vec.begin(); // 编译器推导出迭代器类型,免去用户手写
推导函数返回值
auto fun() -> decltype(var) {
//...
}
Unifrom Initialization
旧版的赋值
Rect r1 = { 3, 4, 65 };
Rect r1(3, 4, 65);
int arr[4] = { 11, 23, 65, 32};
新版的列表初始化
int arr[] {1,2,3,4};
vector<int> v {1,2,3,4,5};
vector<string> cities {"New York", "London", "Cairo"};
complex<double> c {4.0, 3.0}; // 等于 c(4.0, 3.0)
编译器一旦检测到 {1, 2, 3, 4}
类似的事物,则立刻做出一个 initializer_list<T>
它关联到一个 array<T, n>
。调用函数时(如 ctor)该array内的元素可被编译器分解逐个传给函数。在构建模板对象时调用接受 initializer_list 类型的 ctor。
Initializer_list
接受数量可变的
int i; // undefined
int j{}; // 0
int* p; // undefined
int* q{}; // nullptr
int x(5.3); // 数据窄化为 5
int x = 5.3; // 窄化为 5
int x{5.3}; // ERROR: narrowing
int x = {5.3}; // ERROR: narrowing
char x{7}; // OK
char x{9999}; // error
vector<int> v {1,2,3,4,5}; // OK
vector<int> v {1.1, 2.2, 3.3}; // error
作为函数参数
void print(initializer_list<int> vals) {
for (auto p = vals.begin(); p != vals.end(); ++p) {
cout << p;
}
}
print({12, 3, 5, 3});
实现
template <class _E>
class initializer_list {
public:
typedef _E value_type;
typedef const _E& reference;
typedef const _E& const_reference;
typedef size_t size_type;
typedef const _E* iterator;
typedef const _E* const_iterator;
private:
iterator _M_array;
size_type _M_len;
// 编译器在遇到初始化列表时可以调用此构造方法
constexpr initializer_list(const_iterator __a, size_type __l)
: _M_array(__a), _M_len(__l) {}
public:
constexpr initializer_list() noexpect
: _M_array(__a), _M_len(__l) {}
constexpr size_type
size() const noexcept { return _M_len; }
constexpr const_iterator
begin() const noexcept { return _M_array; }
constexpr const_iterator
end() const no except { return begin() + size(); }
//...
};
explicit
用于构造函数前,保证不会调用被explicit修饰的构造函数进行隐式类型转换。C++11前只能用于一个实参的ctor,之后可以用于不止一个实参的ctor
struct Complex {
int real, imag;
Complex(int re, int im=0) : real(re), image(im) {}
Complex operator+(const Complex& x) {
return Complex((real + x.real), (imag + x.imag));
}
};
Complex c1(12, 5);
Complex c2 = c1 + 5; // 此处会调用隐式类型转换,将5转为Complex类型。使用explicit修饰后则不会隐式转换
=default =delete
big five:(当对象内部含有指针指向其他对象时,需要重写big five)
- 默认构造函数(不带参数的构造函数)
- 析构函数
- 赋值函数(operator=)(按字节拷贝)
- 拷贝构造函数(按字节拷贝)
- 移动构造函数
- 移动赋值操作
如果自定义ctor,那么编译器不再提供default ctor。当强制使用=default后重新获得default ctor。
class Zoo {
public:
Zoo(int i1, int i2) : d1(i1), d2(i2) {}
Zoo(const Zoo&) = delete;
Zoo(Zoo&&) = default;
Zoo& operator=(const Zoo&) = default;
Zoo& operator=(const Zoo&&) = delete;
virtual ~Zoo() {}
private:
int d1, d2;
};
=default
只能用于 big five 等编译器提供默认实现的方法上
=delete
可以用于任何方法
=0
只能用于 virtual 修饰的方法
几种特殊结构
struct NoCopy {
NoCopy() = default;
NoCopy(const NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
~NoCopy() = default;
};
struct NoDtor {
NoDtor() = default;
~NoDtor() = delete;
};
NoDtor nd; // ERROR 没有dtor 局部变量无法销毁
NoDtor *p = new NoDtor(); // 无法销毁
delete p; // ERROR
// 只允许 friend 或自己成员进行copy
class PrivateCopy {
private:
PrivateCopy(const PrivateCopy&);
PrivateCopy& operator=(const PrivateCopy&);
public:
PrivateCopy() = default;
~PrivateCopy();
};
Alias Template
template<typename T>
using Vec = std::vector<T, MyAlloc<T>>;
// 使用
Vec<int> coll; // 等价于 vector<int, MyAlloc<int>> coll;
template<typename Container>
void test_moveable(Container c) {
// 通过萃取机能够在使用模板作为模板参数时获得参数的参数
// 如当 Container 为 Vector<string> 时,获得类型 string
typedef typename iterator_traits<typename Container::iterator>::value_type Valtype;
Valtype val;
}
alias type
using func = void(*)(int, int); // 等价于 typedef
Template template parameter
template <typename T,
template<class>
class Container>
class MyClass{
Container<T> c;
};
// 这种模板命名必须声明在 funcation body 之外
template<typename T>
using Vec=vector<T, allocator<T>>;
template<typename T>
using Lst = list<T, allocator<T>>;
MyClass<MyString, Vec> c1;
MyClass<MyString, Lst> c2;
noexcept
就是在指定条件下保证不会抛出异常的意思
void foo() noexcept;
// 等于
void foo() noexcept(true);
// 保证在 x.swap(y) 不出错的条件下没有异常
void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))) {
x.swap(y);
}
存于vector容器中的对象,必须在构造函数、move构造函数上声明noexcept。否则vector无法使用这种对象。在容器成长时需要将对象从旧数组赋值到新数组。
overried
struct Base {
virtual void vfunc(float) {}
};
struct Derived1 : Base {
virtual void vfunc(int) override {} // 此处没有正确override所以会报错
};
struct Derived2 : Base {
virtual void vfunc(float) override {} // 此处正确override所以不会报错
};
final
类服务被继承,方法无法被重写
struct Base1 final{};
struct Base2 {
virtual void f() final;
};
decltype
获得一个变量的类型表达式
map<string, float> coll;
decltype(coll)::value_type elem; // map<string, float>::value_type elem;
经常用于函数返回类型
template<typename T1, typename T2>
decltype(x+y) add(T1 x, T2 y); // 此处decltype推测类型时无法得知x y的类型
template<typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x+y); // 返回值类型是 T1 和 T2 两种类型相加的结果的类型
lambdas
完整形式
[...](...) mutable throwSpec -> retType {...}
[] 使用外部非静态变量
() 参数
auto fun = [] {
cout << "hello!" << endl;
};
fun();
int id = 0;
auto f = [id] () mutable {//id的值在编译时复制到lambdas对象中,与外面的id是不同变量。没有mutable不能改变id
cout << "id:" << id << endl;
++id;
};
id = 42;
f(); // id:0
f(); // id:1
cout << id; // 42
int id = 0;
auto f = [&id] () { // id 的引用在编译时复制到lambdas对象中,内部变化会引起外界的变化
cout << "id:" << id << endl;
++id;
};
id = 42;
f(); // id:42
f(); // id:43
cout << id; // 44
Variadic Templates
利用参数个数和参数类型逐个递减的特性实现递归调用。
void fun() { // 这个空函数必须要有,因为递归到最后一定是调用空参数函数
cout << endl;
}
template <typename T, typename... Types>
void fun(const T& firstArg, const Types&... args) {
cout << firstArg << " ";
fun(args...); // 递归调用模板函数
}
获得可变模板参数的个数使用
sizeof...(args);
递归调用
万能hash函数的例子,用于 unordered 容器
class CustomerHash {
public:
// 自定义求hash的类函数
size_t operator()(const Customer& c) const {
return hash_val(c.xxx, c.yyy, c.zzz);//没有传入 seed,调用下方第一个函数
}
};
/************ 万能的哈希函数 ***********/
template<typename... Type>
inline size_t hash_val(const Types&... args) {
size_t seed = 0;
hash_val(seed, args...);
return seed; // 返回值为 seed
}
template<typename T, typename... Type>//逐一取出 value,将n个变为 1 + (n-1) 个 variadic templates
inline size_t hash_val(size_t& seed, const Types&... args) {
hash_combine(seed, val);
hash_val(seed, args...);
}
template<typename T>//处理只有一个 value 的情况
inline void hash_val(size_t& seed, const T& val) {
hash_combine(seed, val);
}
template<typename T>
inline void hash_combine(size_t& seed, const T& val) {
seed ^= std::hash<T>()(val) + 0x9e377b9 + (seed<<6) + (seed>>2);
}class CustomerHash {
public:
size_t op
};
递归继承 tuple 实现
在类中也有相似的用法
template<typename... Values> class tuple;
template<> class tuple<> {};
template<typename Head, typename... Tail>
class tuple<Head, Tail> : private tuple<Tail...> {
typedef tuple<Tail...> inherited;
protected:
Head m_head; // 在初始化时赋值头元素
public:
tuple() {}
tuple(Head v, Tail... vtail)
: m_head(v), inherited(vtail...) {}//此处 inherited(vtail...) 是调用父类构造函数
Head head() { return m_head; }
inherited& tail() { return *this; } // 通过强转去除头元素
};
tuple<int, float, string> -继承-> tuple<float, string> -继承-> tuple<string> -继承-> tuple<>
Rvalue references
用于解决不必要的复制。赋值时如果右边的值为Rvalue,左边的对象可以从右侧偷出资源而不是进行一次分配。
Lvalue:可以出现在 operator= 左侧,一般就是变量
Rvalue:只能出现在 operator= 右侧,临时对象
int a = 1;
int b = 2;
a = b; // ok
a = a + b; // ok
// a b 都是左值
a + b = 3; // ERROR a+b是右值,不能出现在左边
/********************* 临时对象是右值,但下面是例外 *********************/
string s1("hello");
string s2("world");
s1 + s2 = s2; // ok 且 s1 s2 的值不变
string() = "world"; // ok 可以给临时对象赋值
complex<int> c1(3, 8), c2(1, 0);
c1 + c2 = complex<int>(4, 9); // ok 且 c1 c2 的值不变
complex<int>() = complex<int>(4, 9); // ok
class MyClass {
// copy assignment
MyClass(const MyClass& str) {
// 使用 malloc 分配新内存
return *this;
}
// move assignment
MyClass(const MyClass&& str) noexcept { // move ctor
// 直接将指针指向同一个资源,旧对象接触对资源的引用
return *this;
}
};
vector<MyClass> vec();
vec.insert(MyClass()); // 此处向 vector 中插入一个临时对象,默认使用右值引用。
MyClass c1;
MyClass c2(std::move(c1)); // 使用 std::move 获得左值c1的右值引用,在这之后c1释放资源且**不能再被使用**
转交中的问题
void process(int& i) {} // 1
void process(int&& i) {} // 2
void forward(int&& i) { process(i); } // 3
int a = 0;
process(a); // 调用 1
process(1); // 调用 2
process(std::move(a)); // std::move将左值变为右值,调用 2
forward(2); // 调用 3 再调用 1,因为在forward内部变量变为了一个 named object
forward(std::move(a)); // 调用 3 再调用 1
forward(a); // ERROR
const int& b = 1;
process(b); // ERROR no matching function fo call to 'process(cosnt int&)'
process(move(b)); // ERROR
int& x(5);
一个 move aware class。如果当它存于vector中,vector扩容时使用浅拷贝而非重新申请空间。
class MyString {
private:
char* _data;
size_t _len;
// 复制内容
void _init_data(const char* s) {
_data = new char[_len+1];
memcpy(_data, s, _len);
_data[_len] = '\0';
}
private:
// default ctor
MyString() : _data(NULL), _len(0) {}
// ctor
MyString(const char* p) : _len(strlen(p)) {
_init_data(p);
}
// copy ctor
MyString(const MyString& str) : _len(str._len) {
_init_data(str._data); // COPY
}
// move ctor
MyString(MyString&& str) noexpect
: _data(str._data), _len(str._len) {
str._len = 0;
str._data = NULL; // 重要 原对象解除对资源的引用
}
// copy assignment
MyString& operator=(const string& str) {
if (this != &str) {
_len = str._len;
_init_data(str._data);
} else {}
return *this;
}
// move assignment
MyString& operator=(MyString&& str) noexcept {
if (this != &str) {
if (_data)
delete _data;
_len = str._len;
_data = str._data;
str._len = 0;
str._data = NULL; // 重要 原对象解除对资源的引用
}
return *this;
}
// dtor
virtual ~MyString() {
if (_data) {
delete _data;
}
}
bool operator<(const MyString& rhs) const {
return string(this->data) < string(rhs._data);
}
bool operator==(const MyString& rhs) const {
return string(this->data) == string(rhs._data);
}
char* get() const { return _data; }
};