1.类型
long long
char
char16_t
char32_t
wchar(存在不同平台字节不同的问题)
字符和字面量前缀 u8 u U
u16string = basic_string<char16_t>
u32string
wstring
char8_t = unsigned char
u8string
2. 内联和嵌套
内联命名空间:
namespace A { inline namespace B { void foo() { std::cout << "foo" << std::endl; } } }
父空间可直接使用子空间内容如: A::foo();
简化嵌套空间写法
namespace A::B::C {} == namespace A{ namespace B { namespace C{} } }
简化内联命名空间写法 namespace A::B::inline C {}和namespace A::inline B::C{}一个内联C一个内联B
namespace A::B::inline C {
int foo() { return 5; }
}
// 或者
namespace A::inline B::C {
int foo() { return 5; }
}
======等于下面
namespace A::B {
inline namespace C {
int foo() { return 5; }
}
}
namespace A {
inline namespace B {
namespace C {
int foo() { return 5; }
}
}
}
3.auto
auto就含义: auto生命周期
auto新的含义:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
注意:
1.当用一个auto关键字声明多个变量的时候,编译器遵从由左往右的推导规则,以最左边的表达式推断auto的具体类型。
2.当使用条件表达式初始化auto声明的变量时,编译器总是使用表达能力更强的类型。
3.虽然C++11标准已经支持在声明成员变量时初始化,但是auto却无法在这种情况下声明非静态成员变量。
struct sometype {
auto i = 5; // 错误,无法编译通过
};
在C++11中静态成员变量是可以用auto声明并且初始化的,不过前提是auto必须使用const限定符。在C++17标准中,对于静态成员变量,auto可以在没有const的情况下使用。(static inline可使静态变量在类内初始化)
4.按照C++20之前的标准,无法在函数形参列表中使用auto声明形参(注意,在C++14中,auto可以为lambda表达式声明形参);
另外,auto也可以和new关键字结合。当然,我们通常不会这么
用,例如:
auto i = new auto(5);
auto* j = new auto(5);
不建议使用,破环代码可读性。
5. auto推导规则
1.如果auto声明的变量是按值初始化,则推导出的类型会忽略cv限定符。
const int i = 5;
auto j = i; // auto推导类型为int,而非const int
auto &m = i; // auto推导类型为const int,m推导类型为const int&
auto *k = i; // auto推导类型为const int,k推导类型为const int*
const auto n = j; // auto推导类型为int,n的类型为const int
2.使用auto声明变量初始化时,目标对象如果是引用,则引用属性会被忽略。
int i = 5;
int &j = i;
auto m = j; // auto推导类型为int,而非int&
3.使用auto和万能引用声明变量时(见第6章),对于左值会
将auto推导为引用类型
int i = 5;
auto&& m = i; // auto推导类型为int& (这里涉及引用折叠的概念)
auto&& j = 5; // auto推导类型为int
4.使用auto声明变量,如果目标对象是一个数组或者函数,
则auto会被推导为对应的指针类型
5.当auto关键字与列表初始化组合时,这里的规则有新老两个版
本,这里只介绍新规则(C++17标准)。
(1)直接使用列表初始化,列表中必须为单元素,否则无法编
译,auto类型被推导为单元素的类型。
(2)用等号加列表初始化,列表中可以包含单个或者多个元
素,auto类型被推导为std::initializer_list<T>,其中T是元素类
型。请注意,在列表中包含多个元素的时候,元素的类型必须相同,否
则编译器会报错。
#include <iostream> #include <limits> #include <cstdio> class A{ public : virtual void foo() { std::cout << "a" << std::endl; } }; class B : public A { public : virtual void foo() override { std::cout << "b" << std::endl; } }; int main() { A* a = new B(); a->foo(); auto &c = *a; c.foo(); auto b = a; b->foo(); return 0; }
// 输出
b
b
b
6.auto返回类型推导
1.lambda表达式中使用auto类型推导
auto l = [](int &i)->auto& { return i; };
auto x1 = 5;
auto &x2 = l(x1);
assert(&x1 == &x2); // 有相同的内存地址
起初在后置返回类型中使用auto是不允许的,但是后来人们发现,
这是唯一让lambda表达式通过推导返回引用类型的方法了。
2.非类型模板形参占位符
C++17标准对auto关键字又一次进行了扩展,使它可以作为非类型
模板形参的占位符
4. decltype
typeof
<typeinfo> typeid
1.typeid的返回值是一个左值,且其生命周期一直被扩展到程序
生命周期结束。
2.typeid返回的std::type_info删除了复制构造函数,若想保
存std::type_info,只能获取其引用或者指针.
3.typeid的返回值总是忽略类型的 cv 限定符,也就
是typeid(const T)== typeid(T))。
template<class T1, class T2>
auto sum(T1 a1, T2 a2)->decltype(a1 + a2)
{
return a1 + a2;
}
auto x4 = sum(5, 10.5);
上述用法只推荐在C++11标准的编译环境中使用,因为C++14标准
已经支持对auto声明的返回类型进行推导了
4. 推导规则
decltype(e)(其中e的类型为T)的推导规则有5条。
1.如果e是一个未加括号的标识符表达式(结构化绑定除外)或者
未加括号的类成员访问,则decltype(e)推断出的类型是e的类型T。如
果并不存在这样的类型,或者e是一组重载函数,则无法进行推导。
2.如果e是一个函数调用或者仿函数调用,那么decltype(e)推断
出的类型是其返回值的类型。
3.如果e是一个类型为T的左值,则decltype(e)是T&。
4.如果e是一个类型为T的将亡值,则decltype(e)是T&&。
5.除去以上情况,则decltype(e)是T。
const int&& foo();
int i;
struct A {
double x;
};
const A* a = new A();
decltype(foo()); // decltype(foo())推导类型为const int&&
decltype(i); // decltype(i)推导类型为int
decltype(a->x); // decltype(a->x)推导类型为double
decltype((a->x)); // decltype((a->x))推导类型为const double&
由于decltype((a->x))推导的
是一个带括号的表达式(a->x),因此规则1不再适用,但很明显a->x是
一个左值,又因为a带有const限定符,所以其类型被推导为const
double&。
6.decltype(auto)遵循decltype推导规则。
7.decltype(auto)作为非类型模板形参占位符
5.返回类型后置
auto fun() -> int { return 0;}
1.推导函数模板返回类型
template<class T1, class T2>
auto sum1(T1 t1, T2 t2)->decltype(t1 + t2)
{
return t1 + t2;
}
int main() {
auto x1 = sum1(4, 2);
}
c++ 14后可写成
template<class T1, class T2>
auto sum1(T1 t1, T2 t2)
{
return t1 + t2;
}
int main() {
auto x1 = sum1(4, 2);
}
C++类成员函数的外部定义中,返回类型的前置和后置写法的区别,主要在于,后置返回类型会默认处在该类的作用域中。
6.右值引用
在C++中所谓的左值一般是指一个指向特定内存的具有名称的值
(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命
周期。而右值则是不指向稳定内存地址的匿名值(不具名对象),它的
生命周期很短,通常是暂时性的。
基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值。
x++ ++x
其中x++是右值,因为在后置++操作中编译器首先会生成一份x值的临时复制,然后才对x递增,最后返回临时复制内容。而++x则不同,它是直接对x递增后马上返回其自身,所以++x是一个左值.
在函数返回的时候编译器并不会返回变量本身,而是返回变量的临时复制,所以值传递的函数返回值是右值.
通常字面量都是一个右值,除字符串字面量以外,字符串字面量是一个左值,const char (&)[];原因仔细想来也很简单,
编译器会将字符串字面量存储到程序的数据段中,程序加载的时候也会
为其开辟内存空间,所以我们可以使用取地址符&来获取字符串字面量
的内存地址。
2. 左值引用
非常量左值的引用对象很单纯,它们必须是一个左值。对于这一点,常量左值引用的特性显得更加有趣,它除了能引用左值,还能够引用右值
3.右值引用是一种引用右值且只能引用右值的方法。在语法方面右值引用可以对比左值引用,在左值引用声明中,需要在类型后添加&,而右值引用则是在类型后添加&&。
右值引用的特点之一是可以延长右值的生命周期。
RVO 函数返回值优化
4.右值的性能优化空间
C++11标准中引入了移动语义,它可以帮助我们将临时对象的内存移动到my_pool对象中,以避免内存数据的复制
移动构造函数
class A
{
public:
A(A&& b)
{
a = b.a;
}
A(constexpr A& a)
{
a = b.a;
}
private:
int a;
}
操作指针和字符串是才能体现移动语义的性能优越。
注意:
1.同复制构造函数一样,编译器在一些条件下会生成一份移动构
造函数,这些条件包括:没有任何的复制函数,包括复制构造函数和复
制赋值函数;没有任何的移动函数,包括移动构造函数和移动赋值函
数;也没有析构函数。虽然这些条件严苛得让人有些不太愉快,但是我
们也不必对生成的移动构造函数有太多期待,因为编译器生成的移动构
造函数和复制构造函数并没有什么区别。
2.虽然使用移动语义在性能上有很大收益,但是却也有一些风
险,这些风险来自异常。试想一下,在一个移动构造函数中,如果当一
个对象的资源移动到另一个对象时发生了异常,也就是说对象的一部分
发生了转移而另一部分没有,这就会造成源对象和目标对象都不完整的
情况发生,这种情况的后果是无法预测的。所以在编写移动语义的函数
时建议确保函数不会抛出异常,与此同时,如果无法保证移动构造函数
不会抛出异常,可以使用noexcept说明符限制该函数。这样当函数抛
出异常的时候,程序不会再继续执行而是调用std::terminate中止执
行以免造成其他不良影响。
3.值类别
值类别是C++11标准中新引入的概念,具体来说它是表达式的一种属性,该属性将表达式分为3个类别,它们分别是左值(lvalue)、纯右值(prvalue)和将亡值(xvalue)。
1.所谓泛左值是指一个通过评估能够确定对象、位域或函数的标
识的表达式。简单来说,它确定了对象或者函数的标识(具名对象)。
2.而纯右值是指一个通过评估能够用于初始化对象和位域,或者
能够计算运算符操作数的值的表达式。
3.将亡值属于泛左值的一种,它表示资源可以被重用的对象和位
域,通常这是因为它们接近其生命周期的末尾,另外也可能是经过右值
引用的转换产生的。
值类别都是表达式的属性,所以我们常说的左值和右值实际上指的是表达式,不过为了描述
方便我们常常会忽略它。
从本质上说产生将亡值的途径有两种,第一种是使用类型转换将泛左值转换为该类型的右值引用。比如:
static_cast<BigMemoryPool&&>(my_pool)
第二种在C++17标准中引入,我们称它为临时量实质化,指的是纯右值转换到临时对象的过程。每当纯右值出现在一个需要泛左值的地方时,临时量实质化都会发生,也就是说都会创建一个临时对象并且使用纯右值对其进行初始化,这也符合纯右值的概念,而这里的临时对象就是一个将亡值。
static_cast<T &&>() 可以将左值转为右值
std::move() 是封装好的函数模板 。
4. 万能引用
常量左值引用既可以引用左值又可以引用右值,是一
个几乎万能的引用,但可惜的是由于其常量性,导致它的使用范围受到
一些限制。
void foo(int &&i) {} // i为右值引用
template<class T>
void bar(T &&t) {} // t为万能引用
int get_val() { return 5; }
int &&x = get_val(); // x为右值引用
auto &&y = get_val(); // y为万能引用
所谓的万能引用是因为
发生了类型推导,在T&&和auto&&的初始化过程中都会发生类型的推
导,如果已经有一个确定的类型,比如int &&,则是右值引用。在这
个推导过程中,初始化的源对象如果是一个左值,则目标对象会推导出
左值引用;反之如果源对象是一个右值,则会推导出右值引用,不过无
论如何都会是一个引用类型。
万能引用最典型的用途被称为完美转发
注意:形参是左值。为了让转发将左右值的属性也带到目标函数中,需要将形参进行类型转换
在C++11的标准库中提供了一个std::forward
函数模板,在函数内部也是使用static_cast进行类型转换,只不过使
用std::forward转发语义会表达得更加清晰,std::forward函数模板
的使用方法也很简单:
template<class T>
void perfect_forwarding(T &&t)
{
show_type(std::forward<T>(t));
}
请注意std::move和std::forward的区别,其中std::move一定
会将实参转换为一个右值引用,并且使用std::move不需要指定模板实
参,模板实参是由函数调用推导出来的。而std::forward会根据左值
和右值的实际情况进行转发,在使用的时候需要指定模板实参。