C++ Primer Plus 第18章 C++新标准

1.初始化

1.可以使用大括号括起来的列表表示,可以添加等号也可不添加

int x = {5};
int *arr = new int[4]{2,4,6,7};

2.动态分配内存也可以直接列表初始化,而不必循环赋值
3.初始化类对象的时候也可以用大括号代替圆括号来调用构造函数
但是,如果类有将模板std::initializer_list作为参数的构造函数,则只有该构造函数可以使用列表初始化形式,其他构造函数不能使用。
4.列表初始化不允许缩窄,即只能往更宽的类型转换不允许将值存储到比它窄的变量中。

2.auto关键字

auto关键字是用来实现自动类型推断的,比如:

auto maton = 112;	//112是int类型,auto将maton自动设置为int类型
auto pt = &maton;	//同理,将pt设置为int *即指向int类型的指针
double fm(double , int);	//一个函数声明
auto pf = fm;	//fm是函数的地址,因此pf就是一个指向函数的指针,它的类型就是double(* ) (double,int)

3.decltype关键字

关键字decltype将变量的类型声明为表达式指定的类型,这在模型类定义的时候非常有用,比如在一个模板类中定义一个函数,该函数有两个形参,但是这两个形参的类型不同,现在我需要这两个形参的乘积,代码可以如下:

template<class T1,class T2>
void ClassName<T1,T2>ef(T1 U,T2,V)
{
	decltype (T * V) tu;
}

因为还不知道T1和T2的类型,因此tu的类型只有在模板实例化的时候才知道,但是模板类方法编写的时候需要事先知道tu的类型,这个时候就可以使用decltype。
另外如果返回类型是不确定的,也可以通过decltype后置来定义函数:

template <typename T,typename U>
auto eff(T t,U u) -> decltype(T * u)	//将返回类型设置为decltype(T*U)类型
{...}

4. 关键字noexcept

1.从C++11开始,我们能看到很多代码当中都有关键字noexcept。比如下面就是std::initializer_list的默认构造函数,其中使用了noexcept。

 constexpr initializer_list() noexcept
  : _M_array(0), _M_len(0) { }

该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。
如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。

2.C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。
在实践中,一般两种异常抛出方式是常用的:

一个操作或者函数可能会抛出一个异常;
一个操作或者函数不可能抛出任何异常。
后面这一种方式中在以往的C++版本中常用throw()表示,在C++ 11中已经被noexcept代替。

void swap(Type& x, Type& y) throw()   //C++11之前
{
    x.swap(y);
}
void swap(Type& x, Type& y) noexcept  //C++11
{
    x.swap(y);
}

3 有条件的noexcecpt
单独使用noexcept,表示其所限定的swap函数绝对不发生异常。然而,使用方式可以更加灵活,表明在一定条件下不发生异常。

void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y)))    //C++11
{
    x.swap(y);
}

它表示,如果操作x.swap(y)不发生异常,那么函数swap(Type& x, Type& y)一定不发生异常。

一个更好的示例是std::pair中的移动分配函数(move assignment),它表明,如果类型T1和T2的移动分配(move assign)过程中不发生异常,那么该移动构造函数就不会发生异常。

pair& operator=(pair&& __p)
noexcept(__and_<is_nothrow_move_assignable<_T1>,
                is_nothrow_move_assignable<_T2>>::value)
{
    first = std::forward<first_type>(__p.first);
    second = std::forward<second_type>(__p.second);
    return *this;
}

4 什么时候该使用noexcept?
使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。然而,并不是加上noexcept就能提高效率,步子迈大了也容易扯着蛋。
以下情形鼓励使用noexcept:

移动构造函数(move constructor)
移动分配函数(move assignment)
析构函数(destructor)。这里提一句,在新版本的编译器中,析构函数是默认加上关键字noexcept的。下面代码可以检测编译器是否给析构函数加上关键字noexcept。

struct X
{
    ~X() { };
};

int main()
{
    X x;

    // This will not fire even in GCC 4.7.2 if the destructor is
    // explicitly marked as noexcept(true)
    static_assert(noexcept(x.~X()), "Ouch!");
}

5.类内成员初始化

C++11允许在类定义中初始化成员,如果类构造函数列表初始化的时候提供了相应成员变量的值,那么这些值就会覆盖类内成员初始化时候的值。
需要注意的是不能用圆括号初始化,只能使用等号或者大括号版本的初始化。

6.cbegin、cend、begin和end

cbegin、cend和begin、end一样,都是返回一个迭代器,分别指向容器的第一个元素和最后一个元素的后面,唯一的区别只是cbegin、cend是将迭代器设置为const类型。

7. 右值引用

右值就是一些表达式以及函数等形式,这个时候不能通过=直接将这些表达式的值赋给变量,而是需要右值引用(&&),变量的值不会随着表达式的值变更而变更,实例代码如下:

inline double f(double tf)
{
	return 5.0 * (tf - 32.0) / 9.0;
};
int main()
{
	using namespace std;
	double tc = 21.5;
	double &tc1 = tc;
	double && rd1 = 7.07;
	double && rd2 = 1.8 * tc + 32.0;
	double && rd3 = f(rd2);
	cout << tc << &tc << endl;
	cout << rd1 << &rd1 << endl;
	cout << rd2 << &rd2 << endl;
	cout << rd3 << &rd3 << endl;
	cout << tc1 << endl;
	tc = 20.0;	//将tc的值修改为20.0 这个时候rd2的值会不会改变呢?

	cout << rd2 << &rd2 << endl;
	cout << tc1 << endl;
	cin.get();
	return 0;
}

代码结果如下:

21.5,0059FCC8
7.07,0059FCA0
70.7,0059FC84
21.5,0059FC68
21.5
70.7,0059FC84
20

说明右值引用指向的值所在的地址不会随着表达式的改变而改变,而左值会随着指向的变量内容改变而改变。

8. 移动语义

实际上移动不是真的移动了原始数据,而是修改了记录。一般我们的复制构造函数有动态分配内存的情况下都是新建一个指针,然后通过一系列变换将传入复制构造函数的实参深复制为我们需要的对象,但是这样做会导致复制构造函数新建一个副本,然后将这个副本传递给我们对象,然后再删除这个副本。
移动构造函数和移动赋值函数则是直接将指针指向原始数据,并不移动原始数据,并且移动构造函数与表达式、有返回值的函数匹配。
左值引用不能指向右值,
如果需要强制移动,可以使用utility类中的std::move()方法,但是std::move()方法并不是一定会调用移动赋值函数,如果类中定义了移动赋值函数,那么就调用它,如果没有定义,则调用复制赋值函数,如果复制赋值函数也没有定义,那么在这里使用std::move()方法是非法的。

9.自动提供的成员函数

如果提供了复制构造函数、复制赋值函数,编译器将不会自动提供移动构造函数和移动赋值函数,如果提供了移动构造函数、移动赋值函数,编译器将不会自动提供复制构造函数和复制赋值函数。
例如我现在提供了一个移动构造函数,现在我想重新提供一个没有参数的构造函数,这个时候必须要自己添加,编译器不会再提供默认构造函数,复制构造函数。

10.default和delete关键字

default关键字只能用于6个构造函数,delete可用于任何成员函数。
defalut关键字的作用是显式声明构造函数,比如:

class Someclass
{
private:
...
public:
	someclass(someclass &&)	;//移动构造函数
	someclass() = default;	//显式声明默认构造函数
};

另外delete关键字的用法是禁止使用相关成员函数,比如我们要禁用复制构造函数和复制赋值函数,可以这么写:

class Someclass
{
private:
...
public:
	someclass(someclass &&)	;//移动构造函数
	someclass() = default;	//显式声明默认构造函数
	someclass(const someclass &) = delete;	//禁用掉复制构造函数
	someclass & operator=(const someclass &) = delete;	//禁用复制赋值函数
};

delete关键字还可以禁止参数类型转换,比如:

class someclass
{
public:
	...
	void redo(double);
	void redo(int) = delete;
};

someclass sc;
sc.redo(5)

这种时候5与int类型匹配,但是编译器检测到int类型作为形参的成员函数被禁用掉了,所以会判定编译失败。

11.委托构造函数

其实在一般情况下,根本不需要多个构造函数,这些构造函数有很大一部分内容是相同的,因此可能大量重复编写相同的代码。C++11允许我们在一个构造函数的定义中使用另一个构造函数。这被称为委托。
该技术和列表初始化成员变量有相似之处,实例代码如下:

class someclass
{
private:
int k ;
public:
	someclass(int);
	someclass();
};
someclass::someclass(int kk):k(kk){...}
someclass::someclass():someclass(0){...}

12.继承构造函数

在原有继承中,派生类构造函数需要列表初始化基类,使基类构造函数构建在派生类构造函数之前,这种可以用以下表示:

class T1
{
private:
	...
public:
	T1();
	T1(int );
	T1(int,double);
	T1(const T1 &);
}; 

class T2
{
private:
	...
public:
	using T1::T1;	//T2可以继承T1的所有构造函数,但不会使用与派生类构造函数的特征标匹配的构造函数
	T2(double x):T1(2*x){}
}

那么当我们新建对象是这样的时候:

T2 t2(10,2.0);

T2没有提供这种参数的构造函数,但是T1有,所以这时候T2就会使用继承而来的T1(int,double)。

13.override和final标识符

C++11中,使用override指出覆盖一个虚函数,并且将其放在参数列表后面。如果声明与基类方法声明不匹配,编译器将视为错误。

struct Base 
{
    virtual void Turing() = 0;
    virtual void Dijkstra() = 0;
    virtual void VNeumann(int g) = 0;
    virtual void DKnuth() const;
    void Print();
};
struct DerivedMid: public Base 
{
    // void VNeumann(double g);
    //接口被隔离了,曾想多一个版本的VNeumann函数
};
struct DerivedTop : public DerivedMid 
{
    void Turing() override;
    void Dikjstra() override; //无法通过编译,拼写错误,并非重载
    void VNeumann(double g) override; //无法通过编译,参数不一致,并非重载    
void DKnuth() override; //无法通过编译,常量性不一致,并非重载
void Print() override; //无法通过编译,非虚函数重载
};

(之前听过,但是没看过结构体重载 ==)

如果没有使用override关键字,上面的写法编译可以通过但是运行的效果与真实意愿(希望重载)不符
举例子说明

class testoverride
{
public:
    testoverride(void);
    ~testoverride(void);
    virtual void show() const = 0;
    virtual int infor() = 0;
    virtual void test() = 0;
    virtual int spell() = 0;
};

class B: public testoverride
{
public:
    virtual void show();     //1
    virtual void infor();    //2
    virtual void vmendd();   //3
virtual void test(int x);//4
virtual void splle();    //5 
};

上面的1-5个重载函数编译过程中,除了返回值不同的infor会报错以外,其他函数都不会有问题,但是在类实例化的时候会提示是抽象类,因为他们都没有真正实现重载

class C: public testoverride
{
public:
    virtual void show() override;
    virtual void infor() override;   
    virtual void vmendd() override;
    virtual void test(int x) override;
    virtual void splle() override;
};
class C: public testoverride
{
public:
    virtual void show() override;
    virtual void infor() override;   
    virtual void vmendd() override;
    virtual void test(int x) override;
    virtual void splle() override;
};

添加了override以后,会在编译器override修饰符则可以保证编译器辅助地做一些检查,上面的情况无法通过编译

结论
如果派生类里面是像重载虚函数 就加上关键字override 这样编译器可以辅助检查是不是正确重载,如果没加这个关键字 也没什么严重的error 只是少了编译器检查的安全性

标识符final解决了另外一个问题,有时候在进行model编写测试的时候,可能并不想派生类重写基类方法,但是不小心手残用派生类虚方法将基类的虚方法覆盖了,这个时候非常想禁止派生类覆盖某些特定的虚方法,为此可以在基类的相关函数参数列表后面加上final,这样将禁止派生类重新定义基类方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值