C++11 是第二个真正意义上的 C++ 标准,也是 C++ 的一次重大升级。C++11增加了很多现代编程语言的特性,比如自动类型推导、智能指针、lambda 表达式等,这使得 C++ 看起来又酷又潮,一点也不输 Java 和C#。
GitHub地址:https://github.com/0voice/cpp_new_features#cpp_11
2021-2000道大厂面经/面试题整理(腾讯、字节、阿里、百度、京东等)
c++11新特性
- 关键字
- STL容器
- 新语法
-
- 预处理
- C++宏(cplusplus macro)
- 基于范围的for语句
- 对齐支持(alignment support)
- 显式转换操作符(explicit conversion operators)
- 静态断言(static assert)
- 数字限制(numeric limits)
- 原始字符串(raw string)
- 追踪返回类型语法(trailing return type syntax)
- 扩展的friend语法(extended friend syntax)
- 扩展的整型(extended integer types)
- 非受限联合体(unrestricted union)
- 内联名字空间(lnline namespace)
- 用户定义的字面量(user-defined literals)
- 强类型枚举(scoped and strongly typed enums)
- 随机装置(random device)
- std::ref和std::cref
- 常量表达式(constexpr)
- lamda表达式
- 指针空值(nullptr)
- 防止类型收窄(Preventing narrowing)
- 初始化列表(initializer lists)
- 统一的初始化语法和语义(Uniform initialization syntax and semantics)
- POD(plain old data)
- long long整型
- 移动语义(move semantics)
- 右值引用(rvalue reference)
- c99特性(c99)
- 一般化的SFINAE规则(generalized SFINAE rules)
关键字
新增关键字
thread_local
thread_local是C++11增加的存储类指定符
C++中有4种存储周期:
- automatic
- static
- dynamic
- thread
有且只有thread_local关键字修饰的变量具有线程周期(thread duration),这些变量(或者说对象)在线程开始的时候被生成(allocated),在线程结束的时候被销毁(deallocated)。并且每 一个线程都拥有一个独立的变量实例(Each thread has its own instance of the object)。thread_local 可以和static 与 extern关键字联合使用,这将影响变量的链接属性(to adjust linkage)。
那么,哪些变量可以被声明为thread_local?可以是以下3类:
- 命名空间下的全局变量
- 类的static成员变量
- 本地变量
thread_local案例
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
thread_local unsigned int rage = 1;
std::mutex cout_mutex;
void increase_rage(const std::string& thread_name) {
++rage; // 在锁外修改 OK ;这是线程局域变量
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Rage counter for " << thread_name << ": " << rage << '\n';
}
void test() {
thread_local int i = 0;
printf("id=%d, n=%d\n", std::this_thread::get_id(), i);
i++;
}
void test2() {
test();
test();
}
int main() {
std::thread a(increase_rage, "a"), b(increase_rage, "b");
{
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Rage counter for main: " << rage << '\n';
}
a.join();
b.join();
std::thread t1(test);
std::thread t2(test);
t1.join();
t2.join();
std::thread t3(test2);
t3.join();
system("pause");
return 0;
}
static_assert
static_assert
struct MyClass
{
char m_value;
};
struct MyEmptyClass
{
void func();
};
// 确保MyEmptyClass是一个空类(没有任何非静态成员变量,也没有虚函数)
static_assert(std::is_empty<MyEmptyClass>::value, "empty class needed");
//确保MyClass是一个非空类
static_assert(!std::is_empty<MyClass>::value, "non-empty class needed");
template <typename T, typename U, typename V>
class MyTemplate
{
// 确保模板参数T是一个非空类
static_assert(
!std::is_empty<T>::value,
"T should be n non-empty class"
);
// 确保模板参数U是一个空类
static_assert(
std::is_empty<U>::value,
"U should be an empty class"
);
// 确保模板参数V是从std::allocator<T>直接或间接派生而来,
// 或者V就是std::allocator<T>
static_assert(
std::is_base_of<std::allocator<T>, V>::value,
"V should inherit from std::allocator<T>"
);
};
// 仅当模板实例化时,MyTemplate里面的那三个static_assert才会真正被演算,
// 藉此检查模板参数是否符合期望
template class MyTemplate<MyClass, MyEmptyClass, std::allocator<MyClass>>;
nullptr
nullptr
nullptr关键字用于标识空指针,是std::nullptr_t类型的(constexpr)变量。它可以转换成任何指针类型和bool布尔类型(主要是为了兼容普通指针可以作为条件判断语句的写法),但是不能被转换为整数。
char *p1 = nullptr; // 正确
int *p2 = nullptr; // 正确
bool b = nullptr; // 正确. if(b)判断为false
int a = nullptr; // error
noexcept
noexcept
noexcept有两类作用:noexcept指定符和noexcept运算符
- noexcept 指定符
void f() noexcept; // 函数 f() 不抛出
void (*fp)() noexcept(false); // fp 指向可能抛出的函数
void g(void pfa() noexcept); // g 接收指向不抛出的函数的指针
// typedef int (*pf)() noexcept; // 错误
- noexcept运算符
#include <iostream>
#include <utility>
#include <vector>
// noexcept 运算符
void may_throw() {};
void no_throw() noexcept {};
auto lmay_throw = [] {};
auto lno_throw = []() noexcept {};
class T {
};
class T1 {
public:
~T1() {}
};
class T2 {
public:
~T2() {}
int v;
};
class T3 {
public:
~T3() {}
std::vector<int> v;
};
class T4 {
public:
std::vector<int> v;
};
int main()
{
T t;
T1 t1;
T2 t2;
T3 t3;
T4 t4;
std::vector<int> vc;
std::cout << std::boolalpha
<< "Is may_throw() noexcept? " << noexcept(may_throw()) << '\n'
<< "Is no_throw() noexcept? " << noexcept(no_throw()) << '\n'
<< "Is lmay_throw() noexcept? " << noexcept(lmay_throw()) << '\n'
<< "Is lno_throw() noexcept? " << noexcept(lno_throw()) << '\n'
<< "Is ~T1() noexcept? " << noexcept(std::declval<T1>().~T1()) << '\n'
<< '\n'
<< '\n'
<< "Is T(rvalue T) noexcept? " << noexcept(T(std::declval<T>())) << '\n'
<< "Is T(lvalue T) noexcept? " << noexcept(T(t)) << '\n'
<< '\n'
<< "Is T1(rvalue T1) noexcept? " << noexcept(T1(std::declval<T1>())) << '\n'
<< "Is T1(lvalue T1) noexcept? " << noexcept(T1(t1)) << '\n'
<< '\n'
<< "Is T2(rvalue T2) noexcept? " << noexcept(T2(std::declval<T2>())) << '\n'
<< "Is T2(lvalue T2) noexcept? " << noexcept(T2(t2)) << '\n'
<< '\n'
<< "Is T3(rvalue T3) noexcept? " << noexcept(T3(std::declval<T3>())) << '\n'
<< "Is T3(lvalue T3) noexcept? " << noexcept(T3(t3)) << '\n'
<< '\n'
<< "Is T4(rvalue T4) noexcept? " << noexcept(T4(std::declval<T4>())) << '\n'
<< "Is T4(lvalue T4) noexcept? " << noexcept(T4(t4)) << '\n'
<< '\n'
<< "Is std::vector<int>(rvalue std::vector<int>) noexcept? " << noexcept(std::vector<int>(std::declval<std::vector<int>>())) << '\n'
<< "Is std::vector<int>(lvalue std::vector<int>) noexcept? " << noexcept(std::vector<int>(vc)) << '\n';
system("pause");
return 0;
}
decltype
decltype
decltype类型说明符,它的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。 decltype用法
- 基本用法
int getSize();
int main(void)
{
int tempA = 2;
/*1.dclTempA为int*/
decltype(tempA) dclTempA;
/*2.dclTempB为int,对于getSize根本没有定义,但是程序依旧正常,因为decltype只做分析,并不调用getSize,*/
decltype(getSize()) dclTempB;
return 0;
}
- 与const结合
double tempA = 3.0;
const double ctempA = 5.0;
const double ctempB = 6.0;
const double *const cptrTempA = &ctempA;
/*1.dclTempA推断为const double(保留顶层const,此处与auto不同)*/
decltype(ctempA) dclTempA = 4.1;
/*2.dclTempA为const double,不能对其赋值,编译不过*/
dclTempA = 5;
/*3.dclTempB推断为const double * const*/
decltype(cptrTempA) dclTempB = &ctempA;
/*4.输出为4(32位计算机)和5*/
cout<<sizeof(dclTempB)<<" "<<*dclTempB<<endl;
/*5.保留顶层const,不能修改指针指向的对象,编译不过*/
dclTempB = &ctempB;
/*6.保留底层const,不能修改指针指向的对象的值,编译不过*/
*dclTempB = 7.0;
```C
* 与引用结合
```C
int tempA = 0, &refTempA = tempA;
/*1.dclTempA为引用,绑定到tempA*/
decltype(refTempA) dclTempA = tempA;
/*2.dclTempB为引用,必须绑定到变量,编译不过*/
decltype(refTempA) dclTempB = 0;
/*3.dclTempC为引用,必须初始化,编译不过*/
decltype(refTempA) dclTempC;
/*4.双层括号表示引用,dclTempD为引用,绑定到tempA*/
decltype((tempA)) dclTempD = tempA;
const int ctempA = 1, &crefTempA = ctempA;
/*5.dclTempE为常量引用,可以绑定到普通变量tempA*/
decltype(crefTempA) dclTempE = tempA;
/*6.dclTempF为常量引用,可以绑定到常量ctempA*/
decltype(crefTempA) dclTempF = ctempA;
/*7.dclTempG为常量引用,绑定到一个临时变量*/
decltype(crefTempA) dclTempG = 0;
/*8.dclTempH为常量引用,必须初始化,编译不过*/
decltype(crefTempA) dclTempH;
/*9.双层括号表示引用,dclTempI为常量引用,可以绑定到普通变量tempA*/
decltype((ctempA)) dclTempI = ctempA;
- 与指针结合
int tempA = 2;
int *ptrTempA = &tempA;
/*1.常规使用dclTempA为一个int *的指针*/
decltype(ptrTempA) dclTempA;
/*2.需要特别注意,表达式内容为解引用操作,dclTempB为一个引用,引用必须初始化,故编译不过*/
decltype(*ptrTempA) dclTempB;
decltype总结 decltype和auto都可以用来推断类型,但是二者有几处明显的差异: 1.auto忽略顶层const,decltype保留顶层const; 2.对引用操作,auto推断出原有类型,decltype推断出引用; 3.对解引用操作,auto推断出原有类型,decltype推断出引用; 4.auto推断时会实际执行,decltype不会执行,只做分析。 总之在使用中过程中和const、引用和指针结合时需要特别小心。
constexpr
constexpr
constexpr意义 将变量声明为constexpr类型以便由编译器来验证变量是否是一个常量表达式(不会改变,在编译过程中就能得到计算结果的表达式)。是一种比const更强的约束,这样可以得到更好的效率和安全性。
constexpr用法
- 修饰函数
/*1.如果size在编译时能确定,那么返回值就可以是constexpr,编译通过*/
constexpr int getSizeA(int size)
{
return 4*size;
}
/*2.编译通过,有告警:在constexpr中定义变量*/
constexpr int getSizeB(int size)
{
int index = 0;
return 4;
}
/*3.编译通过,有告警:在constexpr中定义变量(这个有点迷糊)*/
constexpr int getSizeC(int size)
{
constexpr int index = 0;
return 4;
}
/*4.编译通过,有告警:使用了if语句(使用switch也会告警)*/
constexpr int getSizeD(int size)
{
if(0)
{}
return 4;
}
/*5.定义变量并且没有初始化,编译不过*/
constexpr int getSizeE(int size)
{
int index;
return 4;
}
/*6.rand()为运行期函数,不能在编译期确定,编译不过*/
constexpr int getSizeF(int size)
{
return 4*rand();
}
/*7.使用了for,编译不过*/
constexpr int getSizeG(int size)
{
for(;0;)
{}
return 4*rand();
}
- 修改类型
int tempA;
cin>>tempA;
const int ctempA = 4;
const int ctempB = tempA;
/*1.可以再编译器确定,编译通过*/
constexpr int conexprA = 4;
constexpr int conexprB = conexprA + 1;
constexpr int conexprC = getSizeA(conexprA);
constexpr int conexprD = ctempA;
/*2.不能在编译期决定,编译不过*/
constexpr int conexprE = tempA;
constexpr int conexprF = ctempB;
- 修饰指针
int g_tempA = 4;
const int g_conTempA = 4;
constexpr int g_conexprTempA = 4;
int main(void)
{
int tempA = 4;
const int conTempA = 4;
constexpr int conexprTempA = 4;
/*1.正常运行,编译通过*/
const int *conptrA = &tempA;
const int *conptrB = &conTempA;
const int *conptrC = &conexprTempA;
/*2.局部变量的地址要运行时才能确认,故不能在编译期决定,编译不过*/
constexpr int *conexprPtrA = &tempA;
constexpr int *conexprPtrB = &conTempA
constexpr int *conexprPtrC = &conexprTempA;
/*3.第一个通过,后面两个不过,因为constexpr int *所限定的是指针是常量,故不能将常量的地址赋给顶层const*/
constexpr int *conexprPtrD = &g_tempA;
constexpr int *conexprPtrE = &g_conTempA
constexpr int *conexprPtrF = &g_conexprTempA;
/*4.局部变量的地址要运行时才能确认,故不能在编译期决定,编译不过*/
constexpr const int *conexprConPtrA = &tempA;
constexpr const int *conexprConPtrB = &conTempA;
constexpr const int *conexprConPtrC = &conexprTempA;
/*5.正常运行,编译通过*/
constexpr const int *conexprConPtrD = &g_tempA;
constexpr const int *conexprConPtrE = &g_conTempA;
constexpr const int *conexprConPtrF = &g_conexprTempA;
return 0;
}
- 修饰引用
int g_tempA = 4;
const int g_conTempA = 4;
constexpr int g_conexprTempA = 4;
int main(void)
{
int tempA = 4;
const int conTempA = 4;
constexpr int conexprTempA = 4;
/*1.正常运行,编译通过*/
const int &conptrA = tempA;
const int &conptrB = conTempA;
const int &conptrC = conexprTempA;
/*2.有两个问题:一是引用到局部变量,不能再编译器确定;二是conexprPtrB和conexprPtrC应该为constexpr const类型,编译不过*/
constexpr int &conexprPtrA = tempA;
constexpr int &conexprPtrB = conTempA
constexpr int &conexprPtrC