直接输出用法
cpp
#include<iostream>
using namespace std;
int main()
{
//这个用法括号里面的东西就可以完全的输出出来
cout << R"(
111
111
)";
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值
- 整形强制转换时,低等级的需要转换为高等级,有符号的需要转换为无符号的
类成员的快速初始化
即有些可在类内进行初始化了
cpp
#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在修饰函数时只能修饰虚函数,这样就能阻止子类重写父类函数了
cpp
#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关键字修饰过的类是不允许被继承的,也就是说这个类不能有派生类
cpp
#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 描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。
cpp
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;
}
};
模板的优化
增加了对函数模板默认参数的支持
cpp
#include<iostream>
using namespace std;
template<class T = int>
void f(T a)
{
}
int main()
{
return 0;
}
数值类型和字符串之间的转换
to_string 转为字符串类型
stoi 将字符串转为数值类型
断言
Assert 断言是一种在程序运行时进行状态检查的方法,它是一个宏,而不是函数,通常用于调试阶段来确保程序中的某些条件一定为真。如果条件为假,则程序会打印错误信息并终止执行。这种机制有助于开发者在开发过程中快速发现并修复潜在的错误。
cpp
#include <assert.h>
void assert(int expression);
如果expression为0,程序将报告错误并终止执行。如果expression不为0,则程序继续执行后面的语句。
静态断言:
cpp
静态断言
① 在编译时能够进行检查的断言
② 使用时不需要引用头文件
③ 可以自定义违反断言时的错误提示信息
④ 使用起来非常简单,它接收两个参数
- 参数1:断言表达式,这个表达式通常需要返回一个 bool值
- 参数2:警告信息,它通常就是一段字符串,在违反断言(表达式为false)时提示该信息
例如:
cpp
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可以接受一个表达式,并在编译时计算该表达式是否可能抛出异常,这依赖于编译器能够在编译时期确定表达式的可能异常。
cpp
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,所以返回true
std::cout << noexcept(f2(1000000)) << std::endl; // f2未声明noexcept,可能抛出异常,所以返回false
return 0;
}
自动类型推导
auto关键字
限制:
1:不能作为函数参数使用。因为只有在函数调用的时候才会给函数参数传递实参,auto要求必须要给修饰的变量赋值,因此二者矛盾。
2:不能用于非静态成员变量的初始化
3:不能用auto定义数组
4:无法使用auto推导出模板参数
decltype:
在C++中,decltype 是一个用于在编译时推导表达式类型的关键字。它与C++11中引入的 auto 关键字类似,但在某些情况下更加适用。decltype 不仅可以用于变量,还可以用于表达式和函数名,使得在复杂的类型声明中非常有用。
cpp
int x = 0;
decltype(x) y = 1; // y的类型被推导为int
在这个例子中,即使 y 没有被初始化,编译器也能够通过 decltype 推导出它的类型
当 decltype 用于表达式时,它会返回表达式结果的类型。如果表达式是一个左值,decltype 将返回类型的引用;如果是右值,将返回类型本身。例如:
cpp
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // r + 0是右值,b的类型为int
decltype(*p) c = i; // *p是左值,c的类型为int&
decltype 也可以用于函数名,以推导函数的返回类型。这在定义函数指针时特别有用,因为它允许我们避免复杂的类型声明。例如:
cpp
int add(int a, int b) { return a + b; }
decltype(add) *func_ptr = add; // func_ptr是指向add函数的指针
ecltype 在泛型编程中尤其有用,因为它可以用来追踪函数的返回值类型,或者在模板中推导成员类型。例如,如果我们有一个模板类,我们可以使用 decltype 来推导容器的迭代器类型:
cpp
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 表达式中,会随外部变量变化而变化。
隐式捕获:使用 = 或 & 表示按值或按引用捕获所有外部变量。
cpp
int x = 10;
auto f = [x](int y) -> int { return x + y; }; // 值捕获 x
x = 20;
std::cout << f(5) << std::endl; // 输出 15,不受外部 x 的影响
参数列表用于表示 Lambda 表达式的参数,可以为空,也可以指定参数类型和名称。返回类型可以省略,由编译器推导,也可以显式指定。
cpp
auto add = [](int a, int b) -> int { return a + b; };
std::cout << add(3, 4) << std::endl; // 输出 7
Lambda 表达式的优点
简洁:省略函数名和类名,代码更加简洁清晰。
灵活:可以捕获外部变量,作为函数参数或返回值。
安全:控制外部变量的访问方式,避免全局变量和悬空指针。
定义简单的匿名函数:
cpp
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
捕获外部变量
cpp
int x = 10, y = 20;
auto add = [x, y]() -> int { return x + y; };
std::cout << add() << std::endl; // 输出 30
作为函数参数
cpp
#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; });
作为函数返回值
cpp
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计算阶乘的函数:
cpp
// 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引入的一种功能,允许一个构造函数调用同类中的另一个构造函数来完成对象的初始化。这种机制可以有效地减少代码重复,提高代码的可维护性和可读性。
委托构造函数在其成员初始化列表中调用另一个构造函数
例子:
cpp
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;
};
使用委托构造函数的主要优势是避免了在多个构造函数中重复相同的初始化代码
cpp
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; // 输出 100
std::cout << data.num2 << std::endl; // 输出 99
}
C++11引入了继承构造函数,允许派生类通过简单的声明来继承基类的构造函数,从而减少代码重复,提高代码清晰度。通过using声明,派生类可以继承基类的所有构造函数
cpp
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引入的一种新特性,用于优化资源管理和提高程序性能。它通过&&符号表示,主要用于绑定右值(如临时对象)并实现资源的高效转移。
右值引用的核心思想是移动语义和完美转发,它允许程序避免不必要的拷贝操作,从而提升效率
左值与右值的区别:左值是指具有明确存储地址的对象,可以通过&获取地址,通常是变量或对象。右值则是临时的、没有明确存储地址的值,如字面量、表达式的计算结果或函数的非引用返回值。
cpp
int a = 10; // a是左值
int b = a + 5; // a + 5是右值
定义与特性:右值引用通过&&定义,必须初始化且只能绑定右值。它允许修改右值的内容,从而实现资源的转移。
cpp
int &&r = 10; // 右值引用绑定字面量
r = 20; // 修改右值内容
右值引用的主要特性包括:
只能绑定右值(如临时对象)。
允许修改绑定的右值内容。
支持资源的高效转移,避免不必要的拷贝。
移动语义的实现:移动语义通过右值引用实现资源的转移,而非复制。它在处理大规模数据结构(如std::vector、std::string)时尤为重要。
cpp
#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实现,它能够将参数的原始类型(左值或右值)完美地传递给另一个函数。
cpp
#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引入的重要特性,旨在提高资源管理和参数传递的效率。
移动语义
移动语义允许将资源的所有权从一个对象转移到另一个对象,而不是进行深拷贝。这在处理临时对象(右值)时尤其重要,因为深拷贝会带来不必要的性能损耗。移动语义的实现依赖于右值引用,即通过&&符号来引用右值。以下是移动语义的基本概念:
右值和左值:左值是有名称的对象,可以取地址;右值是临时对象或字面量,不能取地址。
移动构造函数:通过移动构造函数,可以将一个对象的资源直接转移到另一个对象,而不需要复制数据。例如:
cpp
MyString(MyString&& other) noexcept : data(other.data), length(other.length) {
other.data = nullptr; // 释放原对象的资源所有权
other.length = 0;
}
完美转发
完美转发允许函数模板将参数的值类别(左值或右值)保持不变地转发给另一个函数。这是通过使用std::forward实现的。完美转发的关键在于万能引用(T&&)和引用折叠规则。以下是完美转发的基本概念:
std::forward:根据模板参数的类型信息,std::forward可以将左值和右值正确地转发。例如:
cpp
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 保持原始值类别
}
应用场景:完美转发在需要将参数传递给其他函数时非常有用,尤其是在模板编程中,可以避免不必要的拷贝,提高性能。
总结:移动语义和完美转发是现代C编程中不可或缺的特性,它们通过优化资源管理和参数传递,显著提高了程序的性能和效率。理解这两个概念对于编写高效的C代码至关重要。对于想深入学习的开发者,建议参考相关文献和示例代码,以加深理解和应用。
列表初始化
列表初始化是 C++11 引入的一种特性,通过使用大括号 {} 来初始化变量。这种方式提供了统一且类型安全的初始化方法,适用于内置类型、数组、类对象、结构体以及标准容器等。
基本语法:
cpp
T var = {value}; // 等号形式
T var{value}; // 直接形式
其中 T 是类型,value 是初始化的值。
使用实例:
cpp
// 初始化内置类型
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来判断类型是否平凡。例如:
cpp
#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; // 输出1
std::cout << std::is_trivial<TrivialC>::value << std::endl; // 输出0
return 0;
}
标准布局(Standard Layout)
标准布局类型需要满足以下条件:
所有非静态成员具有相同的访问权限。
派生类和基类不能同时包含非静态成员。
第一个非静态成员的类型不能与基类相同。
不包含虚函数或虚基类。
所有非静态成员和基类都必须是标准布局类型。
可以使用std::is_standard_layout::value来判断类型是否符合标准布局。例如:
cpp
#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; // 输出1
std::cout << std::is_standard_layout<StdLayoutC>::value << std::endl; // 输出0
return 0;
}
POD类型的优势:
POD类型具有以下优点:
内存操作安全:可以使用memcpy和memset进行内存操作。
与C语言兼容:POD类型的数据布局与C语言一致,便于跨语言交互。
静态初始化:POD类型支持安全的静态初始化。
注意事项:
在C++20之后,POD类型的概念逐渐被更细化的类型要求(如平凡类型)所取代,但了解POD类型对于底层编程、内存管理和与C语言交互仍然非常重要。
默认函数控制 =default 与 =delete
C++11标准引入了新特性:类默认函数的控制:=default (默认函数)和 =delete (已删除函数)
默认函数和已删除函数使得可以显式控制是否自动生成特殊成员函数。 已删除的函数还可以提供简单语言,以防止所有类型的函数(特殊成员函数和普通成员函数以及非成员函数)的自变量中出现有问题的类型提升,这会导致意外的函数调用。
可以默认设置任何特殊成员函数------以显式声明特殊成员函数使用默认实现 ,定义非public的特殊成员函数、恢复其他情况下被阻止自动生成的特殊成员函数。(在声明特殊成员函数时,末尾添加"=default")
cpp
#include<iostream>
using namespace std;
class Person
{
public:
Person() = default;
};
int main()
{
return 0;
}
可以删除特殊成员函数以及普通成员函数和非成员函数,以阻止定义或调用它们。 删除特殊成员函数提供了一种使编译器无法生成不需要的特殊成员函数的更清晰的方式。 必须在声明函数时将其删除;不能在这之后通过声明一个函数然后不再使用的方式来将其删除。
cpp
#include<iostream>
using namespace std;
class Person
{
public:
Person() = default;
Person(int a) = delete;
};
int main()
{
return 0;
}
扩展的friend语法
做友元时可以省略class了,也有了其他的一些用法了
cpp
#include<iostream>
using namespace std;
class S
{
public:
int a;
};
//定义别名
using B = S;
class Person
{
//friend S;
friend B;//两种都可以
public:
Person() = default;
Person(int a) = delete;
};
//也可以为类模板声明友元
int main()
{
return 0;
}
强类型枚举
强类型枚举(enum class)是C++11引入的一种增强特性,用于解决传统枚举的局限性。它通过提供更好的类型安全性和作用域限制,使代码更加健壮和可维护
定义与基本用法:
强类型枚举使用 enum class 语法定义,其枚举值被封闭在枚举类中,避免了与其他作用域的冲突。例如:
cpp
enum class Color {
Red,
Green,
Blue
};
Color myColor = Color::Red; // 必须通过作用域访问
与传统枚举不同,强类型枚举的值不会隐式转换为整数类型。如果需要转换,必须显式使用 static_cast:
cpp
int colorCode = static_cast<int>(Color::Green); // 转换为整数
优势
强类型枚举具有以下显著优势:
类型安全:禁止隐式转换为整数,避免类型不匹配问题。
作用域限定:枚举值必须通过枚举类名访问,减少命名冲突。
底层类型支持:可以指定底层数据类型(如 uint8_t),优化内存使用并提高跨平台兼容性。
底层类型指定:
通过指定底层类型,可以控制枚举值的内存占用。例如:
cpp
enum class Status : uint8_t {
Ready,
Running,
Finished
};
Status statusArray[1000]; // 仅占用1000字节
常见应用场景:
强类型枚举在以下场景中非常实用:
状态机:如网络连接状态、任务状态等。
配置选项:定义日志级别、压缩模式等。
错误码管理:为不同错误类型分配唯一标识。
标志位操作:通过位运算管理权限或标志。
示例代码展示状态机的实现:
cpp
enum class ConnectionState {
Disconnected,
Connecting,
Connected,
Reconnecting,
Failed
};
class NetworkConnection {
private:
ConnectionState state = ConnectionState::Disconnected;
public:
bool connect() {
if (state == ConnectionState::Disconnected) {
state = ConnectionState::Connecting;
state = ConnectionState::Connected;
return true;
}
return false;
}
void disconnect() {
if (state == ConnectionState::Connected) {
state = ConnectionState::Disconnected;
}
}
ConnectionState getState() const {
return state;
}
};
注意事项
在使用强类型枚举时,需注意以下几点:
始终优先使用 enum class 而非传统枚举。
为枚举值提供有意义的名称,提升代码可读性。
在需要整数转换时,使用显式类型转换(如 static_cast)。
在类中使用时,可将枚举作为类的成员类型,增强封装性。
非受限联合体
在C中,联合体(Union)是一种特殊的数据结构,允许在同一内存位置存储不同类型的数据。传统的联合体在C98标准中对成员类型有严格限制,只能包含POD(Plain Old Data)类型。而C++11引入的非受限联合体取消了这些限制,允许任何非引用类型作为联合体的成员,包括具有自定义构造函数和析构函数的类型。
特性:
允许非POD类型:C++11允许联合体的成员是非POD类型,这意味着可以使用包含自定义构造函数、析构函数和拷贝控制的类作为成员。
静态成员:非受限联合体可以包含静态成员,这在C++98中是被禁止的。
内存共享:所有成员共享同一块内存,因此在任何时刻,联合体只能存储一个成员的值。
cpp
#include <iostream>
#include <string>
union MyUnion {
std::string strValue; // 非POD类型
int intValue;
MyUnion() {} // 默认构造函数
~MyUnion() {} // 默认析构函数
};
int main() {
MyUnion u;
new (&u.strValue) std::string("Hello, World!"); // 使用placement new构造
std::cout << u.strValue << std::endl; // 输出字符串
u.strValue.~string(); // 显式调用析构函数
return 0;
}
在这个示例中,MyUnion联合体包含一个std::string和一个int。使用placement new来构造std::string,并在使用后显式调用析构函数。
应用场景
非受限联合体在需要管理资源(如动态内存、文件句柄等)的情况下非常有用。它们提供了更高的内存效率和灵活性,适用于类型安全枚举、数据解析等场景。
通过这些特性,非受限联合体为C++开发者提供了更强大的工具,使得在同一内存位置存储不同类型的数据变得更加灵活和高效。
智能指针
智能指针是C++中用于自动管理内存的对象,它们确保在对象不再使用时自动释放分配的内存,从而避免内存泄漏。C++11标准引入了几种智能指针,主要包括std::unique_ptr、std::shared_ptr和std::weak_ptr。
std::unique_ptr:
std::unique_ptr是一种独占式智能指针,它保证同一时间只有一个智能指针可以指向一个特定的对象。当std::unique_ptr对象被销毁时,它指向的对象也会被自动删除。std::unique_ptr不支持复制操作,但支持移动操作,这意味着可以将所有权从一个std::unique_ptr转移到另一个。
cpp
#include <memory>
// 创建一个unique_ptr指向一个整数
std::unique_ptr<int> ptr(new int(10));
// 使用std::move转移所有权
std::unique_ptr<int> ptr2 = std::move(ptr);
std::shared_ptr:
std::shared_ptr是一种共享式智能指针,允许多个std::shared_ptr实例共享同一个对象的所有权。当最后一个引用该对象的std::shared_ptr被销毁时,对象才会被删除。std::shared_ptr使用引用计数机制来跟踪有多少个智能指针共享同一个对象。
cpp
#include <memory>
// 创建一个shared_ptr指向一个整数
std::shared_ptr<int> sharedPtr(new int(20));
// 复制shared_ptr,增加引用计数
std::shared_ptr<int> sharedPtr2 = sharedPtr;
std::weak_ptr
std::weak_ptr是一种特殊的智能指针,它不拥有对象的所有权,但可以观察和访问std::shared_ptr管理的对象。std::weak_ptr用于解决std::shared_ptr相互引用可能导致的循环引用问题。
cpp
#include <memory>
// 创建一个shared_ptr
std::shared_ptr<int> sharedPtr(new int(30));
// 创建一个weak_ptr观察shared_ptr
std::weak_ptr<int> weakPtr(sharedPtr);
智能指针的使用注意事项
不要将原始指针赋给多个智能指针,这可能导致多次删除同一内存。
避免使用std::unique_ptr的get()方法返回的原始指针来初始化另一个智能指针。
使用std::make_shared和std::make_unique来创建智能指针,这样可以更安全且效率更高。
注意std::shared_ptr的循环引用问题,必要时使用std::weak_ptr来打破循环。