C++11 新特性

变量和基本类型

1.1 long long 类型
扩展精度浮点数,10位有效数字

1.2 列表初始化
初始化的几种不同形式,其中用花括号来初始化变量称为列表初始化;

比如:

int i = 0;
int i = {0};
int i{0};
int i(0);

需要注意的是,当用于内置类型的变量时,这种初始化形式有一个重要的特点:如果我们使用初始化且初始值存在丢失信息的风险,则编译器报错;

例如:

long double ld = 3.1414141414;
int a{ld}, b = {ld}; //报错
int c(ld), d = ld; //正确
cout << "a:" << a << "b:" << b << "c:" << c << "d" << d << endl;

运行的时候,a,b则会提示报错信息:error: type ‘long double’ cannot be narrowed to ‘int’ in initializer list [-Wc++11-narrowing],这是因为使用long double的值初始化int变量时可能会丢失数据,所以拒绝a和b的初始化请求;虽然c,d虽然没有报错,但是确实丢失了数据;

[这是为什么?]

1.3 nullptr 常量
有几种生成空指针的方法:

int *p1 = nullptr; // 等价于int *p1 = 0;
int *p2 = 0;
int *p3 = NULL; // 等价于int *p3 = 0;

在新标准下,建议尽量使用新标准nullptr,nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型,虽然其它的方式也是可以的;

1.4 constexpr 变量
将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式;

声明为constexpr的变量一定是一个常量,而且必须用常量表达式来初始化,比如说下面的情况则是不正确的:

int t = 10;
constexpr int q = t + 20;
cout << "q" << q << endl;

需要将t声明为 const 才是正确的;

一般来说,如果你认定变量是一个常量表达式,那就把它声明为constexpr类型;

1.5 类型别名声明
使用类型别名可以使复杂的类型名字变得更简单明了,易于理解和使用;

现在有两种方法可以用来定义类型别名,一种是 typedef ,另一种则是新标准中的 using;

#include <iostream>
using namespace std;

int add(int val) {
    return 10 + val;
}

int main() {
    typedef double dnum;
    // 字符指针
    typedef char *pstring;

    // 函数
    // 返回值类型为int,参数类型为int的函数
    typedef int func(int);
    // 函数指针,指向返回值类型为int,参数类型为int的函数
    typedef int (*pfunc)(int);
    // 函数引用,指向返回值类型为int,参数类型为int的函数
    typedef int (&tfunc)(int);

    pfunc pfunc_add = nullptr;
    pfunc_add = add;
    cout << "函数指针,result is " << pfunc_add(10) << endl;
    tfunc tfunc_add = add;
    cout << "函数引用,result is " << tfunc_add(10) << endl;
    func &func_add = add; //这里使用指针或者引用都可以
    cout << "函数,result is " << func_add(10) << endl;

    // 数组
    // 元素类型为int,个数为10的数组
    typedef int arr[10];
    // 数组指针,指向元素类型为int,个数为10的数组
    typedef int (*parr)[10];
    // 数组引用,绑定到元素类型为int,个数为10的数组
    typedef int (&tparr)[10];



    using dnum2 = double;
    using pstring2 = char*;

    using func2 = int(int);
    using pfunc2 = int(*)(int);

    using arr2 = int[10];
    using parr2 = int(*)[10];
    using tparr2 = int(&)[10];

    std::cout << "Hello, World!" << std::endl;
    return 0;
}

但是需要注意的是,如果某个类型别名指代的是复合类型或者常量,那么就会产生意想不到的后果;

比如说

typedef char *pstring;
const pstring cstr = 0;

按照我们正常的理解就是,将char*替换掉pstring,得到 const char* cstr;然而事实是pstring是一个字符指针,其基本数据类型是个指针,此时用此字符指针去声明cstr,得到的是一个常量的字符指针,但是按照本意是指向char的常量指针,其基本类型是char,也就是说两者修饰的东西是不一样的!

但是如果是引用变量则没有关系。

1.6 auto 类型指示符
auto让编译器通过初始值来推算变量的类型,所以,其定义的变量必须要有初始值;

使用auto也能在一条语句中声明多个变量;因为一条声明语句只能有一个基本数据类型,所以该语句中所有的变量的初始基本数据类型都必须是一样的;

顶层const:指针本身是一个常量;
底层const:指针指向的对象是一个常量;

int main() {

    int i = 0;
    const int ci = i;

    auto b = &i; // b是一个整形指针(整数的地址就是指向整数的指针)
    auto c = &ci; // c是一个指向整数常量的指针(对常量对象取地址是一种底层const)

    return 0;
}

1.7 decltype 类型指示符
作用:选择并返回操作数的数据类型;

1.7.1 decltype 和 auto 的区别
处理顶层const和引用的方式

const int ci = 0, &cj = ci;
decltype(ci) x = 0;
decltype(cj) y = x;
decltype(cj) z; //报错,因为cj是一个引用,因此作为引用的 z 必须要进行初始化

引用从来都是作为其所指对象的同义词出现,也就是根据其所指的对象决定,只有在decltype处是个例外;

decltype的结果类型与表达式形式密切相关

int i = 0;
decltype((i)) a; //报错,因为a类型为 int&,必须进行初始化
decltype(i) b; //正确

需要注意的是,decltype((variable))的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用,其实就是根据它的类型决定;

1.8 类内初始化
建议初始化每一个内置类型的变量,为了保证初始化后程序安全

字符串、向量和数组

2.1 使用 auto 或 decltype 缩写类型
注意的是,如果表达式中已经有了size函数就不要再使用int了,因为size函数返回的是unsigned,如果其和有符号数进行比较的时候,有符号数会自动的转换成一个比较大的无符号数。

2.2 范围 for 语句

string str("hello world");
for (auto c : str) {
    cout << c;
}

2.3 定义vector对象的vector(向量的向量)
编译器根据模版vector生成了三种不同的类型,分别是:

vector<int>,vector<vector<int>>, vector<classname>

新的版本已经不需要再添加一个空格

vector<vector<int> >

2.4 vector对象的列表初始化
放在花括号里

2.5 容器的cbegin 和 cend 函数
2.6 标准库begin 和 end 函数
2.7 使用auto和decltype简化声明
3 表达式
3.1 除法的舍入规则
新标准中,一律向0取整(直接切除小数部分)

double a = 12/5;
cout << a << endl;
输出结果为2,删掉了小数部分;

3.2 用大括号包围的值列表赋值
3.3 将sizeof用于类成员
使用作用域运算符来获取类成员的大小是许可的;

对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中
所有的元素各执行一次sizeof运算并将所得结果求和;
对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用多少空间;
4 语句
4.1 范围for语句
5 函数
5.1 标准库 initializer_list类
可以处理不同数量实参的函数,前提是实参类型要相同;

其提供的操作包括:

默认初始化
列表初始化
拷贝对象
size
begin
end
5.2 列表初始化返回值
函数可以返回花括号包围的值的列表,如果列表为空,临时量执行初始化,否则,返回的值由函数的返回类型决定;

vector getAddress(int num) {
if (num == 0) {
return {};
}
return {“address1”, “address2”, “address3”};
}
5.3 定义尾置返回类型
任何函数的定义都能够使用尾置来返回,最适用于返回类型比较复杂的情况;

比如:

// 返回类型为指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10] {
int arr[10] = {0};
return &arr;
}
5.4 使用decltype简化返回类型定义
int odd[] = {1, 3, 5, 7, 9};
int even[] = {2, 4, 6, 8, 10};

// 返回一个指针,指向数组
decltype(odd) *getArr(int i) {
return (i % 2) ? &odd : &even;
}
decltype并不会因为获取的是数组,则返回的是指针,还是需要在函数名前面加上 *;

5.5 constexpr函数
是指能用于常量表达式的函数;

需要遵从几项约定:

函数的返回类型以及所有形参的类型都是字面值类型(只能用它的值来称呼它);
函数体中必须有且只有一条return语句(C++14不再做要求);
必须非virtual
constexpr int func2() {
return 10;
}

int main() {
int arr[func2() + 10] = {0};

return 0;

}
6 类
6.1 使用=default生成默认构造函数
如果实现了默认的构造函数,编译器则不会自动生成默认版本;可以通过使用关键字 default 来控制默认构造函数的生成,显示的指示编译器生成该函数的默认版本;

class MyClass{
public:
MyClass()=default; //同时提供默认版本和带参版本
MyClass(int i):data(i){}
private:
int data;
};
比如说如果想要禁止使用拷贝构造函数,则使用关键字 delete;

class MyClass
{
public:
MyClass()=default;
MyClass(const MyClass& )=delete;
};

int main() {
MyClass my;
MyClass my2(my); //报错

return 0;

}
但需要注意的是,析构函数是不允许使用delete的,否则无法删除;

6.2 类对象成员的类内初始化
当提供一个类内初始值时,必须以符号=或者花括号表示

6.3 委托构造函数
一个委托构造函数使用它所属类的其它构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)指责委托给了其它构造函数;

class MyClass
{
public:
MyClass() : MyClass(10) {}
MyClass(int d) : data(d){}

inline int getData() {return data;}

private:
int data;
};

int main() {
MyClass my;
cout << my.getData() << endl;
return 0;
}
6.4 constexpr构造函数
如果想要使得函数拥有编译时计算的能力,则使用关键字 constexpr

同样的编译时使用对象:

class Square {
public:
constexpr Square(int e) : edge(e){};
constexpr int getArea() {return edge * edge;}
private:
int edge;
};

int main() {
Square s(10);
cout << s.getArea() << endl;
return 0;
}
如果成员函数标记为 constexpr,则默认其是内联函数,如果变量声明为
constexpr,则默认其是 const;

参考文章

constexpr指定符(C++11 起)

7 IO库
7.1 用string对象处理文件名
新版本中增加了使用库类型string对象作为文件名,之前只能使用C风格的字符数组;

ifstream infile(“/hello.sh”);
8 顺序容器
补充:

顺序容器的类型:

容器 描述
vector 可变大小数组。支持快速随机访问。在尾部之外的位置插入或者删除元素时可能很慢
deque 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快
list 双向链表。只支持双向双向顺序访问。在list中任何位置进行插入/删除操作
forward_list 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作都很快
array 固定大小数组。支持快速随即访问。不能添加或删除元素
string 与vector相似的容器,但专门用于保存字符(字符数组,封装了char而已)。随机访问速度快,在尾部插入、删除速度快
总而言之,数组随机访问速度快,链表插入删除速度快;

8.1 array和forward_list容器
array

include

include

include

include

include

int main()
{
// 使用聚合初始化来构造
std::array

include

include

using namespace std;
using namespace std::placeholders;
class MyClass
{
public:
void fun1(void)
{
cout << “void fun1(void)” << endl;
}
int fun2(int i)
{
cout << “int fun2(int i)” << ” i = ” << i << endl;
return i;
}
};
int main()
{
MyClass my;
//使用类对象绑定
auto fun1 = bind(&MyClass::fun1, my);
fun1();
MyClass *p;
//使用类指针绑定,-1为占位符
auto fun2 = bind(&MyClass::fun2, p, _1);
int i = fun2(1);
cout << “i = ” << i << endl;
cin.get();
return 0;
}
10. 关联容器
关联器类型

map 关联数组,保存关键字-值对
set 关键字即值,即只保存关键字的容器
multimap 关键字可重复出现的map
multiset 关键字可重复出现的set
unordered_map 用哈希函数组织的map
unordered_set 用哈希函数组织的set
unordered_multimap 用哈希函数组织的map,关键字可重复
unordered_multiset 用哈希函数组织的set,关键字可重复
10.1 关联容器的列表初始化
和顺序容器差不多

10.2 列表初始化pair的返回类型
10.3 pair的列表初始化
10.4 无序容器
包括4个无序关联容器(使用哈希函数和关键字类型的 == 运算符)

unordered_map
unordered_set
unordered_multiset
unordered_multimap
11 动态内存
已总结,略

11.1 智能指针
11.2 shared_ptr类
11.3 动态分配对象的列表初始化
11.4 auto和动态分配
11.5 unique_ptr类
11.6 weak_ptr类
11.7 范围for语句不能应用于动态分配数组
因为分配的内存并不是一个数组类型

11.8 动态分配数组的列表初始化
可以对数组中的元素进行值初始化,方法是在大小之后跟一对括号;

int *pia = new int[10]; //10个未初始化的int
int *pia2 = new int10; //10个初始化为0的int
int *pia3 = new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

delete [] pia; //释放
delete [] pia2;
delete [] pia3;
11.9 auto不能用于分配数组
虽然我们用空括号对数组中元素进行值初始化,但不能在括号中给出初始化器,这意味着不能用auto分配数组;

11.10 allocator::construct可使用任意构造函数
allocator分配的内存是未构造的,我们按需要在此内存中构造对象。在新标准库中,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素。

int main() {
allocator alloc;
string* str = alloc.allocate(4); //分配10个未初始化的string

auto itr = str;
alloc.construct(itr, "chengdu"); //chengdu
cout << str[0] << endl;
itr++;
alloc.construct(itr, "beijing"); //beijing
cout << str[1] << endl;
itr++;
alloc.construct(itr, 10, 'c'); //10个c组成的字符串
cout << str[2] << endl;
itr++;
alloc.construct(itr); //空字符串
cout << str[3] << endl;

for (int i = 0; i < 4; i++) {
    cout << str[i] << endl;
}

// 销毁
while (itr != str) {
    alloc.destroy(itr--);
}

return 0;

}
为了使用allocate返回的内存,我们必须用construct构造对象,使用未构造的内存,其行为是未定义的;我们只有对真正构造了的元素进行destroy操作;

12 拷贝控制
12.1 将=default用于拷贝控制类对对象
可以类内或者类外修饰成员函数,如果是类内,合成的函数将隐式地声明未内联的,如果不希望这样,则在类外声明;

我们只能对具有合成版本的成员函数使用=default(即,默认构造函数或拷贝控制成员)

12.2 使用=delete用于拷贝控制成员
对于析构函数已删除的类型,不能定义该类型的变量或释放指向该类型动态分配的指针;

12.3 用移动类对象代替拷贝类对象
为了解决之前存在的性能问题,避免不必要的内存拷贝,新标准中有两种方法来替代拷贝函数:移动函数和 move

移动构造函数

A(A && h) : a(h.a){
h.a = nullptr;
}
12.4 右值引用
右值引用就是必须绑定到右值的引用,通过 && 来获得右值引用;

12.4.1 区别左值和右值
左值:在赋值号左边,可以被赋值的值,可以取地址;
右值:在赋值号右边,取出值赋给其他变量的值;

左值引用:type & 引用名 = 左值表达式
右值引用:type && 引用名 = 右值表达式

int a = 0; //2
int b = a; //1

一个对象被用作右值时,使用的是它的内容(值),比如1中的 a ,被当作左值时,使用的是它的地址,比如2中的 a,常规左值;

int main() {
int i = 1; //i为常规左值
int &r = i; //正确:r绑定到i上,r是一个引用
int &&rr = i; //错误:不能将一个右值引用绑定到左值i上
int &r2 = i * 1; //错误:等号右边是一个右值,但左值引用只能绑定到左值上
int &&rr2 = i * 1; //正确:右值引用绑定到右值上
const int &r3 = i * 1; //正确:可以将一个const的左值引用绑定到右值上
return 0;
}
返回左值引用的函数、赋值、下标、解引用和前置递增/递减运算符,都是返回左值的表达式的例子,我们可以将一个左值引用绑定到这类表达式的结果上;

返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值;我们不能将左值引用绑定到这类表达式上,但是我们可以将一个const的左值引用或者一个右值引用绑定到这类表达式之上;

12.5 标准库move函数
我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值

// 移动构造函数,参数 “arg.member” 是左值
A(A&& arg) : member(std::move(arg.member))
{
}

// 移动赋值函数
A& operator=(A&& other) {
member = std::move(other.member);
return *this;
}
12.6 移动构造函数和移动赋值
12.7 移动构造函数通常是noexcept
如果需要通知标准库这是一个不会抛出异常的移动操作,可以在函数后面指明 noexcept,如果在一个构造函数中,noexcept 出现在参数列表和初始化列表开始的冒号之间;

不抛出异常的移动构造函数和移动赋值运算符必须标记为noexcept

12.8 移动迭代器
新标准中可以通过调用标准库的 make_move_iterator 函数将一个普通迭代器转换为一个移动迭代器,这样可以避免拷贝操作带来的性能问题;

include // std::cout

include // std::make_move_iterator

include // std::vector

include // std::string

include // std::copy

int main() {
std::vector foo(3);
std::vector bar{ “one”,”two”,”three” };

std::copy(make_move_iterator(bar.begin()),
    make_move_iterator(bar.end()),
    foo.begin());

// bar now contains unspecified values; clear it:
bar.clear();

std::cout << "foo:";
for (std::string& x : foo) std::cout << ' ' << x;
std::cout << '\n';

return 0;

}
12.9 引用限定成员函数
引用限定符可以是 & 或者 &&,分别指出 this 可以指向一个左值或者右值,引用限定符只能用于成员函数,而且必须同时出现在函数的声明和定义中;

可以在参数列表之后使用引用限定符来指定this对象的左值与右值属性;

若引用限定符为&,则表明this对象指向着左值对象;
若引用限定符为&&,则表明this对象指向着右值对象;
参考文章

C++-引用限定符

13 重载运算与类型转换
13.1 function类模版
function模版提供一种通用、多态的函数封装。其实例可以对任何可调用的目标进行存储、赋值和调用操作,这些目标包括函数、lambda表达式、绑定表达式以及其它函数对象等;

include

include

struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << ‘\n’; }
int num_;
};

void print_num(int i)
{
std::cout << i << ‘\n’;
}

struct PrintNum {
void operator()(int i) const
{
std::cout << i << ‘\n’;
}
};

int main()
{
// 保存自由函数
std::function

template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t );

template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t );

例子:

#include <iostream>
#include <utility>
#include <memory>
class A {
public:
    A(int && n) { std::cout << "rvalue constructor -> n=" << n << std::endl;}
    A(int& n) { std::cout << "lvalue constructor -> n=" << n << std::endl;}
};
template<class T, class U>
std::unique_ptr<T> make_unique1(U&& u) {
    return std::unique_ptr<T>(new T(std::forward<U>(u)));
    //return std::unique_ptr<T>(new T(u));
}
int main() {
    int i = 24;
    auto p1 = make_unique1<A>(666); // rvalue forwarding
    auto p2 = make_unique1<A>(i); // lvalue forwarding
    auto p3 = make_unique1<A>(std::move(i)); // rvalue forwarding
    return 0;
}

但是如果我们没有使用 forward 函数,结果则全部都调用的是 lvalue constructor;

15.9 可变参数模版与转发
右值引用+完美转发+可变参数模版实现下面这个函数;

// args为右值引用,decltype用于返回值
template<class Function, class... Args>
auto FuncWrapper(Function && f, Args && ... args) -> decltype(f(std::forward<Args>(args)...))
{
    return f(std::forward<Args>(args)...);
}

void test0()
{
    cout <<"void"<< endl;
}

int test1()
{
    return 1;
}

int test2(int x)
{
    return x;
}

string test3(string s1, string s2)
{
    return s1 + s2;
}

int main() {
    int num = 10;
    int && nnum = num + 10;
    int & nnum2 = num;

    FuncWrapper(test0);      // 没有返回值,打印1
    cout << FuncWrapper(test1) << endl;      // 没有参数,有返回值,返回1
    cout << FuncWrapper(test2, 1) << endl;   // 有参数,有返回值,返回1
    cout << FuncWrapper(test2, std::move(num)) << endl;   // 有参数,有返回值,返回左值10
    cout << FuncWrapper(test2, std::move(nnum2)) << endl;   // 有参数,有返回值,返回左值引用10
    cout << FuncWrapper(test2, nnum) << endl;   // 有参数,有返回值,返回右值引用20
    cout << FuncWrapper(test3, "aa", "bb") << endl; // 有参数,有返回值,返回"aabb"
    return 0;
}

返回值分别为:

void
1
1
10
10
20
aabb

http://www.cnblogs.com/George1994/p/6684989.html

猜你喜欢

转载自blog.csdn.net/ch853199769/article/details/80340585