C++11新特性基础知识点汇总
直接输出用法
#include<iostream>using namespace std;int main()
{//这个用法括号里面的东西就可以完全的输出出来cout << R"(111111)";return 0;
}
long long整形
- long long 后缀可以用LL或ll
- ussigned long long 后缀可以用ULL或ull 属于无符号类型
- LLONG_MIN : 最小的long long值
- LLONG_MAX : 最大的long long值
- ULLONG_MAX : 最大的unsigned long long值
- 整形强制转换时,低等级的需要转换为高等级,有符号的需要转换为无符号的
类成员的快速初始化
即有些可在类内进行初始化了
#include<iostream>using namespace std;//静态变量的初始化必须在类外
//非静态成员变量可以在类内进行初始化
class Person
{
public:Person(int a){m_a = a;}int m_a = 1;static int b;
};
int Person :: b = 1;int main()
{//这样进行调用的时候m_a就等于10了Person(10);}
final和override关键字
final用来限制某个类不能被继承,或者某个虚函数不能被重写
final在修饰函数时只能修饰虚函数,这样就能阻止子类重写父类函数了
#include<iostream>using namespace std;class Person
{
public:virtual void test(){cout << ' ' << endl;}
};class Son : public Person
{
public:void test()final//不能在继续继承这个函数了{cout << " " << endl;}
};
int main()
{}
final关键字修饰过的类是不允许被继承的,也就是说这个类不能有派生类
#include<iostream>using namespace std;class Person
{
public:virtual void test(){cout << ' ' << endl;}
};//不能在继续继承这个类了
class Son final : public Person
{
public:void test(){cout << " " << endl;}
};
int main()
{}
override 关键字明确表示一个函数是对基类中一个虚函数的重载。如果派生类在虚函数声明时使用了 override 描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。
class Base {
public:virtual void show() const = 0;virtual int info() = 0;
};
class Derived : public Base {
public:void show() const override {// 实现 show 方法}int info() override {// 实现 info 方法return 0;}
};
模板的优化
增加了对函数模板默认参数的支持
#include<iostream>using namespace std;template<class T = int>
void f(T a)
{}int main()
{return 0;
}
数值类型和字符串之间的转换
to_string 转为字符串类型
stoi 将字符串转为数值类型
断言
Assert 断言是一种在程序运行时进行状态检查的方法,它是一个宏,而不是函数,通常用于调试阶段来确保程序中的某些条件一定为真。如果条件为假,则程序会打印错误信息并终止执行。这种机制有助于开发者在开发过程中快速发现并修复潜在的错误。
#include <assert.h>
void assert(int expression);
如果expression为0,程序将报告错误并终止执行。如果expression不为0,则程序继续执行后面的语句。
静态断言:
静态断言① 在编译时能够进行检查的断言② 使用时不需要引用头文件③ 可以自定义违反断言时的错误提示信息④ 使用起来非常简单,它接收两个参数- 参数1:断言表达式,这个表达式通常需要返回一个 bool值- 参数2:警告信息,它通常就是一段字符串,在违反断言(表达式为false)时提示该信息
例如:
static_assert(condition, message);static_assert(sizeof(int) < sizeof(unsigned int), "int is not smaller than unsigned int");
noexcept
在C++11及之后的版本中,noexcept关键字是用来指明一个函数是否会抛出异常。noexcept既可以作为异常说明符,也可以作为运算符使用。作为异常说明符时,noexcept告诉编译器和调用者该函数不会抛出异常,这允许编译器进行一些优化。如果函数声明为noexcept却抛出了异常,程序将直接调用terminate()函数结束进程。作为运算符时,noexcept可以接受一个表达式,并在编译时计算该表达式是否可能抛出异常,这依赖于编译器能够在编译时期确定表达式的可能异常。
class X {
public:void fx() noexcept {} // 声明为不会抛出异常int GetValue() const noexcept { return v; } // 同样声明为不会抛出异常
private:int v = 100;
};
void f1() noexcept {} // 声明为不会抛出异常
int* f2(int size) { return new int[size]; } // 可能抛出异常,因为使用了new
int main() {std::cout << std::boolalpha;std::cout << noexcept(f1()) << std::endl; // 因为f1声明了noexcept,所以返回truestd::cout << noexcept(f2(1000000)) << std::endl; // f2未声明noexcept,可能抛出异常,所以返回falsereturn 0;
}
自动类型推导
auto关键字
限制:
1:不能作为函数参数使用。因为只有在函数调用的时候才会给函数参数传递实参,auto要求必须要给修饰的变量赋值,因此二者矛盾。
2:不能用于非静态成员变量的初始化
3:不能用auto定义数组
4:无法使用auto推导出模板参数
decltype:
在C++中,decltype 是一个用于在编译时推导表达式类型的关键字。它与C++11中引入的 auto 关键字类似,但在某些情况下更加适用。decltype 不仅可以用于变量,还可以用于表达式和函数名,使得在复杂的类型声明中非常有用。
int x = 0;
decltype(x) y = 1; // y的类型被推导为int
在这个例子中,即使 y 没有被初始化,编译器也能够通过 decltype 推导出它的类型
当 decltype 用于表达式时,它会返回表达式结果的类型。如果表达式是一个左值,decltype 将返回类型的引用;如果是右值,将返回类型本身。例如:
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // r + 0是右值,b的类型为int
decltype(*p) c = i; // *p是左值,c的类型为int&
decltype 也可以用于函数名,以推导函数的返回类型。这在定义函数指针时特别有用,因为它允许我们避免复杂的类型声明。例如:
int add(int a, int b) { return a + b; }
decltype(add) *func_ptr = add; // func_ptr是指向add函数的指针
ecltype 在泛型编程中尤其有用,因为它可以用来追踪函数的返回值类型,或者在模板中推导成员类型。例如,如果我们有一个模板类,我们可以使用 decltype 来推导容器的迭代器类型:
template<typename Container>
class MyClass {decltype(Container().begin()) iterator; // 使用decltype推导迭代器类型//
};
基于范围的for循环
通过对基于范围的for循环语法的介绍可以得知,在for循环内部声明一个变量的引用就可以修改遍历的表达式中的元素的值,但是这并不适用于所有的情况,对应set容器来说,内部元素都是只读的,这是由容器的特性决定的,因此在for循环中auto&会被视为const auto & 。
容器遍历用auto更加方便
指针空值类型- nullptr
在C++中,nullptr和NULL的主要区别在于:
nullptr是C++11引入的关键字,专门用于表示空指针,消除了二义性,确保在所有情况下都能正确表示空指针。
NULL*通常被定义为0或((void)0),在某些情况下可能会导致类型不明确的问题。
使用nullptr**可以避免函数重载时的歧义,因为它是一个类型安全的空指针常量。
nullptr的类型是nullptr_t,而NULL的类型则不明确,可能会导致潜在的错误。
因此,推荐在C++中使用nullptr来表示空指针,以提高代码的可读性和安全性。
Lambda表达式
Lambda 表达式是 C++11 引入的一种语法糖,用于定义匿名函数对象(闭包)。它们通常用于封装传递给算法或异步方法的少量代码行。Lambda 表达式的基本语法如下:[capture list] (parameter list) -> return type { function body }
捕获列表
捕获列表用于指定 Lambda 表达式可以访问的外部变量。捕获方式有三种:
值捕获:将变量的值拷贝到 Lambda 表达式中,不会随外部变量变化而变化。
引用捕获:将变量的引用传递到 Lambda 表达式中,会随外部变量变化而变化。
隐式捕获:使用 = 或 & 表示按值或按引用捕获所有外部变量。
int x = 10;
auto f = [x](int y) -> int { return x + y; }; // 值捕获 x
x = 20;
std::cout << f(5) << std::endl; // 输出 15,不受外部 x 的影响
参数列表用于表示 Lambda 表达式的参数,可以为空,也可以指定参数类型和名称。返回类型可以省略,由编译器推导,也可以显式指定。
auto add = [](int a, int b) -> int { return a + b; };
std::cout << add(3, 4) << std::endl; // 输出 7
Lambda 表达式的优点
简洁:省略函数名和类名,代码更加简洁清晰。
灵活:可以捕获外部变量,作为函数参数或返回值。
安全:控制外部变量的访问方式,避免全局变量和悬空指针。
定义简单的匿名函数:
auto is_odd = [](int n) { return n % 2 == 1; };
std::cout << is_odd(5) << std::endl; // 输出 1
std::cout << is_odd(6) << std::endl; // 输出 0
捕获外部变量
int x = 10, y = 20;
auto add = [x, y]() -> int { return x + y; };
std::cout << add() << std::endl; // 输出 30
作为函数参数
#include <algorithm>
#include <vector>
std::vector<int> vec = {1, 2, 3, 4, 5};
std::sort(vec.begin(), vec.end(), [](int a, int b) { return a > b; });
作为函数返回值
auto make_adder = [](int x) {return [x](int y) -> int { return x + y; };
};
auto add5 = make_adder(5);
std::cout << add5(10) << std::endl; // 输出 15
constexpr
constexpr是C++11引入的一个关键字,它用于声明可以在编译时求值的变量或函数。这意味着使用constexpr声明的变量或函数的值可以用在需要编译时常量的场合,例如数组的大小、整数模板参数等。
constexpr变量
要成为constexpr变量,必须满足以下条件:
1,类型必须是字面量类型(LiteralType)。
2,必须立即初始化。
3,初始化的完整表达式,包括所有隐式转换、构造函数调用等,必须是常量表达式。
4,必须具有常量析构,即它不是类类型或数组类型,或者是具有constexpr析构函数的类类型或数组类型。
constexpr函数
1,不能是虚函数。
2,不能是函数try块。
3 ,对于构造函数和析构函数,类不能有虚基类。
4.返回值和每个参数必须是字面量类型。
5,必须至少存在一组参数值,使得函数的调用可以是核心常量表达式的一部分。
constexpr构造函数和析构函数
constexpr构造函数的函数体必须为空,所有成员变量的初始化都放在初始化列表中。constexpr析构函数必须满足额外的要求,即用于销毁非静态数据成员和基类的每个析构函数都必须是constexpr析构函数。
constexpr和指针
在constexpr声明中,如果定义了一个指针,constexpr仅对指针有效,与指针所指对象无关。这意味着,即使指针被声明为constexpr,也可以修改指针所指向的数据。
constexpr和引用
constexpr所引用的对象必须在编译期就确定地址。如果要确保引用是常量引用,需要使用constexpr const来修饰。
constexpr是C++11中引入的强大特性,它允许在编译时进行更多的计算,从而提高程序的性能。使用constexpr可以让编译器对代码进行更大的优化,例如将使用到的constexpr表达式直接替换成结果,与宏相比没有额外的开销。
例如,以下是使用constexpr计算阶乘的函数:
// C++11 constexpr函数使用递归而不是迭代
constexpr int factorial(int n) {return n <= 1 ? 1 : (n * factorial(n - 1));
}
// C++14 constexpr函数可以使用局部变量和循环
#if __cplusplus >= 201402L
constexpr int factorial(int n) {int result = 1;while (n > 1) result *= n--;return result;
}
#endif // C++14
在C++中,constexpr的使用提供了编译时计算的能力,这对于性能敏感的应用程序来说是非常有用的。随着C++标准的发展,constexpr的功能也在不断增强,使得可以在编译时执行更复杂的计算。
委托构造函数和继承构造函数
委托构造函数是C++11引入的一种功能,允许一个构造函数调用同类中的另一个构造函数来完成对象的初始化。这种机制可以有效地减少代码重复,提高代码的可维护性和可读性。
委托构造函数在其成员初始化列表中调用另一个构造函数
例子:
class Person {
public:// 非委托构造函数Person(std::string _name, int _age, double _income) : name(_name), age(_age), income(_income) {}// 委托构造函数Person() : Person("", 0, 0) {}Person(std::string _name) : Person(_name, 0, 0) {}Person(std::string _name, int _age) : Person(_name, _age, 0) {}
private:std::string name;int age;double income;
};
使用委托构造函数的主要优势是避免了在多个构造函数中重复相同的初始化代码
class Data {
public:int num1;int num2;// 目标构造函数Data() {num1 = 100;}// 委托构造函数Data(int num) : Data() {num2 = num;}
};
void function() {Data data(99);std::cout << data.num1 << std::endl; // 输出 100std::cout << data.num2 << std::endl; // 输出 99
}
C++11引入了继承构造函数,允许派生类通过简单的声明来继承基类的构造函数,从而减少代码重复,提高代码清晰度。通过using声明,派生类可以继承基类的所有构造函数
struct A {A(int i) {}A(double d, int i) {}A(float f, int i, const char* c) {}
};
struct B : A {using A::A;
};
注意事项
无法初始化派生类数据成员:继承构造函数只能初始化基类部分,无法初始化派生类的数据成员。可以通过就地初始化或新增构造函数来解决这个问题。
默认参数问题:继承构造函数无法继承基类构造函数的默认参数,因此在使用有默认参数的基类构造函数时需要小心。
多继承冲突:在多继承情况下,多个基类的构造函数可能会导致派生类中的继承构造函数冲突。可以通过显式定义来解决这个问题。
私有构造函数和虚继承:如果基类的构造函数被声明为私有成员函数,或者派生类是从虚基类继承而来,那么就不能在派生类中声明继承构造函数。
通过这些注意事项,可以更好地理解和使用C++11中的继承构造函数,从而提高代码的可读性和可维护性。
右值引用
右值引用是C++11引入的一种新特性,用于优化资源管理和提高程序性能。它通过&&符号表示,主要用于绑定右值(如临时对象)并实现资源的高效转移。
右值引用的核心思想是移动语义和完美转发,它允许程序避免不必要的拷贝操作,从而提升效率
左值与右值的区别:左值是指具有明确存储地址的对象,可以通过&获取地址,通常是变量或对象。右值则是临时的、没有明确存储地址的值,如字面量、表达式的计算结果或函数的非引用返回值。
int a = 10; // a是左值
int b = a + 5; // a + 5是右值
定义与特性:右值引用通过&&定义,必须初始化且只能绑定右值。它允许修改右值的内容,从而实现资源的转移。
int &&r = 10; // 右值引用绑定字面量
r = 20; // 修改右值内容
右值引用的主要特性包括:
只能绑定右值(如临时对象)。
允许修改绑定的右值内容。
支持资源的高效转移,避免不必要的拷贝。
移动语义的实现:移动语义通过右值引用实现资源的转移,而非复制。它在处理大规模数据结构(如std::vector、std::string)时尤为重要。
#include <iostream>
#include <vector>
#include <utility> // std::move
std::vector<int> createVector() {std::vector<int> v = {1, 2, 3};return v; // 返回右值
}
int main() {std::vector<int> v1 = createVector(); // 调用移动构造函数std::vector<int> v2 = std::move(v1); // 转移资源return 0;
}
在上述代码中,std::move将左值强制转换为右值引用,从而触发移动构造函数,避免了深拷贝的开销。
完美转发的实现:完美转发通过std::forward实现,它能够将参数的原始类型(左值或右值)完美地传递给另一个函数。
#include <iostream>
#include <utility>
void process(int &x) {std::cout << "左值引用: " << x << std::endl;
}
void process(int &&x) {std::cout << "右值引用: " << x << std::endl;
}
template <typename T>
void forwardToProcess(T &&arg) {process(std::forward<T>(arg)); // 完美转发
}
int main() {int a = 10;forwardToProcess(a); // 调用左值引用版本forwardToProcess(20); // 调用右值引用版本return 0;
}
通过std::forward,可以确保参数的引用类型在转发时保持不变,从而实现高效的参数传递。
注意事项
避免滥用std::move:一旦资源被转移,原对象可能变为不可用状态。
右值引用的主要用途:实现移动语义和完美转发,而非替代左值引用。
右值引用是C++11中极为重要的特性,它通过优化资源管理和减少拷贝操作,为开发高效的现代C++程序提供了强大的工具。
转移和完美转发
转移(移动语义)和完美转发是C++11引入的重要特性,旨在提高资源管理和参数传递的效率。
移动语义
移动语义允许将资源的所有权从一个对象转移到另一个对象,而不是进行深拷贝。这在处理临时对象(右值)时尤其重要,因为深拷贝会带来不必要的性能损耗。移动语义的实现依赖于右值引用,即通过&&符号来引用右值。以下是移动语义的基本概念:
右值和左值:左值是有名称的对象,可以取地址;右值是临时对象或字面量,不能取地址。
移动构造函数:通过移动构造函数,可以将一个对象的资源直接转移到另一个对象,而不需要复制数据。例如:
MyString(MyString&& other) noexcept : data(other.data), length(other.length) {
other.data = nullptr; // 释放原对象的资源所有权
other.length = 0;
}
完美转发
完美转发允许函数模板将参数的值类别(左值或右值)保持不变地转发给另一个函数。这是通过使用std::forward实现的。完美转发的关键在于万能引用(T&&)和引用折叠规则。以下是完美转发的基本概念:
std::forward:根据模板参数的类型信息,std::forward可以将左值和右值正确地转发。例如:
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 保持原始值类别
}
应用场景:完美转发在需要将参数传递给其他函数时非常有用,尤其是在模板编程中,可以避免不必要的拷贝,提高性能。
总结:移动语义和完美转发是现代C编程中不可或缺的特性,它们通过优化资源管理和参数传递,显著提高了程序的性能和效率。理解这两个概念对于编写高效的C代码至关重要。对于想深入学习的开发者,建议参考相关文献和示例代码,以加深理解和应用。
列表初始化
列表初始化是 C++11 引入的一种特性,通过使用大括号 {} 来初始化变量。这种方式提供了统一且类型安全的初始化方法,适用于内置类型、数组、类对象、结构体以及标准容器等。
基本语法:
T var = {value}; // 等号形式
T var{value}; // 直接形式
其中 T 是类型,value 是初始化的值。
使用实例:
// 初始化内置类型
int a = {10};
int b{20};
// 初始化数组
int arr[3] = {1, 2, 3};
int arr2[] = {4, 5, 6};
// 初始化类对象
class Person {
public:std::string name;int age;Person(const std::string& n, int a) : name(n), age(a) {}
};
Person p1{"John", 30};
// 初始化标准容器
std::vector<int> vec = {1, 2, 3, 4};
std::vector<int> vec2{5, 6, 7};
// 初始化结构体
struct Point {int x, y;
};
Point p1 = {10, 20};
Point p2{30, 40};
通过列表初始化,C++ 提供了一种更安全、更简洁的变量初始化方式,适用于多种场景
using 的使用
using MyInt = int; // 将int类型定义为MyInt的别名
"using"在不同编程语言中有不同的用法,但主要用于简化代码、管理资源和避免命名冲突。了解这些用法可以帮助开发者更有效地编写和维护代码。
可调用对象包装器、绑定器
可调用对象是指在 C++ 中能够像函数一样被调用的实体。它包括了多种类型的对象,使得它们能够像函数一样被调用,可以是函数、函数指针、函数对象、Lambda 表达式等
std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。
绑定器: std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。通俗来讲,它主要有两大作用:
将可调用对象与其参数一起绑定成一个仿函数。
将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数。
POD类型
POD(Plain Old Data)是C++中的一种类型概念,表示可以通过简单的内存复制进行传输和操作的数据类型。POD类型兼容C语言的数据结构,能够直接与C库交互。它由两部分组成:平凡性(Trivial)[&和标准布局(Standard Layout)&],必须同时满足这两个条件才能被认为是POD类型。
平凡性(Trivial):
默认构造函数和析构函数是平凡的,且不能自定义。
拷贝构造函数和移动构造函数是平凡的。
赋值运算符是平凡的。
不包含虚函数或虚基类。
可以使用std::is_trivial::value来判断类型是否平凡。例如:
#include <iostream>
#include <type_traits>
class TrivialA {};
class TrivialB { int a; };
class TrivialC { TrivialC() {} }; // 非平凡
int main() {std::cout << std::is_trivial<TrivialA>::value << std::endl; // 输出1std::cout << std::is_trivial<TrivialC>::value << std::endl; // 输出0return 0;
}
标准布局(Standard Layout)
标准布局类型需要满足以下条件:
所有非静态成员具有相同的访问权限。
派生类和基类不能同时包含非静态成员。
第一个非静态成员的类型不能与基类相同。
不包含虚函数或虚基类。
所有非静态成员和基类都必须是标准布局类型。
可以使用std::is_standard_layout::value来判断类型是否符合标准布局。例如:
#include <iostream>
#include <type_traits>
class StdLayoutA {};
class StdLayoutB { int a; int b; };
class StdLayoutC : public StdLayoutA { int a; }; // 非标准布局
int main() {std::cout << std::is_standard_layout<StdLayoutA>::value << std::endl; // 输出1std::cout << std::is_standard_layout<StdLayoutC>::value << std::endl; // 输出0return 0;
}
POD类型的优势:
POD类型具有以下优点:
内存操作安全:可以使用memcpy和memset进行内存操作。
与C语言兼容:POD类型的数据布局与C语言一致,便于跨语言交互。
静态初始化:POD类型支持安全的静态初始化。
注意事项:
在C++20之后,POD类型的概念逐渐被更细化的类型要求(如平凡类型)所取代,但了解POD类型对于底层编程、内存管理和与C语言交互仍然非常重要。
