cen's blog cen's blog
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • C++学习
    • C++数据结构
    • MySQL
    • Linux
    • 网络编程
算法
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

cen

十年饮冰,难凉热血
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • C++学习
    • C++数据结构
    • MySQL
    • Linux
    • 网络编程
算法
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • 类和对象
  • 内存管理
  • 泛型模板
  • string
  • vector
  • list
  • stack和queue
  • priority_queue
  • 继承
  • 多态
  • set和map
  • bitset
  • C++11
    • 变量类型推导
      • auto 类型推导
      • decltype 类型推导
    • 默认成员函数控制
      • 显式缺省函数
      • 删除默认函数
    • 可变参数模板
    • lambda 表达式
    • 类型转换
      • static_cast
      • reinterpret_cat
      • const_cast
      • dynamic_cast
  • 异常
  • 智能指针
  • 特殊类设计
  • function包装器
  • 右值引用和移动语义
  • C++学习笔记
cen
2025-02-01
目录

C++11

# 变量类型推导

# auto 类型推导

在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂,使用auto来类型推导会很方便。

# decltype 类型推导

auto 使用的前提是:必须要对 auto 声明的类型进行初始化,否则编译器无法推导出 auto 的实际类型。但有时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时 auto 也就无能为力。所以需要 decltype

    int a = 100;
    int b = 200;
    decltype(a + b) c = 300;

    cout << typeid(c).name() << endl;   // i
1
2
3
4
5

# 默认成员函数控制

在 C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构函数和&和 const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是 C++11 让程序员可以控制是否需要编译器生成。

# 显式缺省函数

在 C++11 中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。

# 删除默认函数

如果能想要限制某些默认函数的生成,在 C++98 中,是该函数设置成 private,并且不给定义,这样只要其他人想要调用就会报错。在 C++11 中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

# 可变参数模板

语法:

template<class ...Args>
返回类型 函数名(Args... args) {
  //函数体
}
1
2
3
4

例子:

template<class ...Args>
void ShowList(Args... args) {}
1
2
template<typename T>
void print(T item) { // 基础情况
    cout << item << " ";
}

template<typename T, typename... Args>
void print(T first, Args... args) { // 递归情况
    print(first);
    print(args...);
}

int main() {
    print(1, 2.5, "three", 'a', true);  // 1 2.5 three a 1
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# lambda 表达式

class Goods {
public:
    string name;
    double price;
    Goods(const string _name, const double _price) : name(_name), price(_price) {}
};

class priceSortLess {
public:
    bool operator()(const Goods& g1, const Goods& g2) const {
        return g1.price > g2.price;
    }
};

int main() {
    vector<Goods> goods = {
            Goods("apple", 12.3),
            Goods("pear", 10.7),
            Goods("banana", 10.1)
    };
    sort(goods.begin(), goods.end(), priceSortLess());
    for(auto i : goods) {
        cout << i.name << " + " << i.price << endl;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

随着 C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法, 都要重新去 写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了 极大的不便。因此,在 C++11 语法中出现了 Lambda 表达式。

lambda 表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

  • [capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来 的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用。
  1. [var]:表示值传递方式捕捉变量 var。
  2. [=]:表示值传递方式捕获所有父作用域中的变量(成员函数包括 this 指针)。
  3. [&var]:表示引用传递捕捉变量 var。
  4. [&]:表示引用传递捕捉所有父作用域中的变量(成员函数包括 this 指针)。
  5. [this]:表示值传递方式捕捉当前的 this 指针。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  • mutable:默认情况下,lambda 函数总是一个 const 函数,mutable 可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

    注意: 在 lambda 函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。

在上面的例子中,可以简化书写为下面的代码:

    sort(goods.begin(), goods.end(),
         [](const Goods &g1, const Goods &g2) -> bool {
        return g1.price > g2.price;
    });
1
2
3
4

实际编译器在底层对于 lambda 表达式的处理方式,完全就是按照函数对象的方式处理的。

# 类型转换

C 语言支持隐式转换和显式转换

  • 隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
  • 显式类型转换:需要用户自己处理,以(指定类型)变量的方式进行类型转换。
int main() {
	//隐式类型转换
	int i = 1;
	double d = i;
	cout << i << endl;
	cout << d << endl;

	//显式类型转换
	int* p = &i;
	int address = (int)p;
	cout << p << endl;
	cout << address << endl;
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符,分别是static_cast、reinterpret_cast、const_cast和dynamic_cast。

# static_cast

static_cast 用于相近类型之间的转换,编译器隐式执行的任何类型转换都可用 static_cast,但它不能用于两个不相关类型之间转换。

    int i = 100;
    auto d = static_cast<double>(i);
1
2

# reinterpret_cat

reinterpret_cast 用于两个不相关类型之间的转换。

    int i = 100;
    int* p = nullptr;
    p = reinterpret_cast<int*>(i);
1
2
3

# const_cast

const_cast 用于删除变量的 const 属性,转换后就可以对 const 变量的值进行修改。

    const int i = 100;
    auto ptr = const_cast<int*>(&i);
    *ptr = 1000;
    cout << *ptr << endl;   // 1000
    cout << i << endl;      // 100
1
2
3
4
5
  • 由于编译器认为 const 修饰的变量是不会被修改的,因此会将 const 修饰的变量存放到寄存器当中,当需要读取 const 变量时就会直接从寄存器中进行读取,而我们修改的实际上是内存中的 a 的值,因此最终打印出 a 的值是未修改之前的值。
  • 如果不想让编译器将 const 变量优化到寄存器当中,可以用volatile关键字对 const 变量进行修饰,这时当要读取这个 const 变量时编译器就会从内存中进行读取,即保持了该变量在内存中的可见性。

# dynamic_cast

dynamic_cast 用于将父类的指针(或引用)转换成子类的指针(或引用)。

  1. **向上转型:**子类的指针(或引用)→ 父类的指针(或引用)
  2. **向下转型:**父类的指针(或引用)→ 子类的指针(或引用)

向下转型分为两种情况:

  • 如果父类的指针(或引用)指向的是一个父类对象,那么将其转换为子类的指针(或引用)是不安全的,因为转换后可能会访问到子类的资源,而这个资源是父类对象所没有的。
  • 如果父类的指针(或引用)指向的是一个子类对象,那么将其转换为子类的指针(或引用)则是安全的
class A {
public:
	virtual void f() {}
};
class B : public A
{};
void func(A* pa) {
	B* pb1 = (B*)pa;               //不安全
	B* pb2 = dynamic_cast<B*>(pa); //安全

	cout << "pb1: " << pb1 << endl;
	cout << "pb2: " << pb2 << endl;
}
int main() {
	A a;
	B b;
	func(&a);
	func(&b);
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

注意:

dynamic_cast 只能用于含有虚函数的类,因为运行时类型检查需要运行时的类型信息,而这个信息是存储在虚函数表中的,只有定义了虚函数的类才有虚函数表。

上次更新: 2025/09/03, 18:26:17
bitset
异常

← bitset 异常→

最近更新
01
网络协议
09-03
02
套接字和TCP
08-26
03
常用数据结构
08-23
更多文章>
Theme by Vdoing | Copyright © 2024-2025 京ICP备2020044002号-3 京公网安备11010502056119号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式