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

    • markdown使用
  • 学习笔记

    • 《JavaScript教程》
    • C++学习
    • C++数据结构
    • MySQL
    • Linux
  • 高中时代
  • 工作日常
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

cen

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

    • markdown使用
  • 学习笔记

    • 《JavaScript教程》
    • 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
  • 异常
  • 智能指针
  • 特殊类设计
  • 线程库
  • 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

# 右值引用和移动语义

左值引用主要引用左值,左值通常可以修改,比如变量 右值引用主要引用右值,右值通常不可以修改,比如常量、表达式、函数的返回值及临时变量等,C++11还将右值区分为纯右值和将亡值。

  • 纯右值:比如:a+b, 100
  • 将亡值:比如:表达式的中间结果、函数按照值的方式进行返回

左值引用不能直接引用右值,但是const + 左值引用可以引用右值 右值引用不能直接引用左值,但是可以引用move后的左值

    // 左值引用
    int a = 100;
    int& b = a;
    // 右值引用
    int&& c = 100;

    const int& d = 100;
    int&& e = move(a);
    return 0;
1
2
3
4
5
6
7
8
9

在operator+中:strRet在按照值返回时,必须创建一个临时对象,临时对象创建好之后,strRet就被销毁 了,最后使用返回的临时对象构造s3,s3构造好之后,临时对象就被销毁了。仔细观察会发现:strRet、临 时对象、s3每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完 全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大,那能否对该 种情况进行优化呢?

//...

# 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算法, 都要重新去 写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了 极大的不便。因此,在C11语法中出现了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

# 类型转换

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/02/07, 18:31:39
bitset
异常

← bitset 异常→

最近更新
01
线程安全
05-21
02
cmake教程
05-08
03
项目
05-07
更多文章>
Theme by Vdoing | Copyright © 2024-2025 京ICP备2020044002号-3 京公网安备11010502056119号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式