Skip to content

C++中级

刚学 C++的时候的基本笔记

auto

auto 也能推导一个类型,但是它会摘除掉引用和 const

所以你在 for 循环里使用 auto,如果是数字,就还好

如果是 CComPtr 就一定会复制

decltype

decltype 是 C++11 引入的关键字

用于在编译器确定表达式的静态类型(包括引用、const 等)

也就是说 decltype 这个表达式,占据的是 int 这种类型的位置

cpp
int a = 0;
const int& b = a;
decltype(b) c = a;  // c 的类型是 const int&,而不是 int 或 int&
// 比如说我忘记了 b 的类型了,我这里需要推导一下
// 如果是 auto,这里 c 就变成了 int

模板推到返回值的类型

cpp
template <typename T, typename U>
auto add(T x, U y) -> decltype(x + y) { // 尾置返回类型,C++11
    return x + y;
}

lambda 表达式推到

cpp
auto lambda = [](auto x) {
    decltype(x) y = x; // y 的类型与 x 完全一致
    return y;
};

Using

也是 C++11 引入的

1、

using 似乎可以完全替代 typedef

cpp
using IntVector = std::vector<int>;

2、

using 支持模板

cpp
template <typename T>
using MyMap = std::map<T, std::string>; // 模板别名,无法用 typedef 实现
MyMap<int> m;                           // 等价于 std::map<int, std::string>

3、

在模板类中通过 using 定义依赖类型,简化代码

cpp
template <typename T>
class Container {
public:
    using value_type = T; // 标准做法,允许外部通过 Container<T>::value_type 访问类型
    // ...
};

// 用法:
Container<int>::value_type x = 10; // x 的类型是 int

泛型编程

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

cpp
template <typename T>  int compare (T t1, T t2);
template <typename T> class compare;

1、

template<>都是在最前面,而且是同一行(可以换行,可以不换行,但是没有;)

注意到 C++有关键词 template,而 java 使用<>则直接泛型

2、

同时,C++可以给你的类型起一个名字 T

typename 和 class 在 template 后面完全等价,即使是算两个 int 相加的函数,也可以完全用 class

cpp
class A
{
public:
    int x;
    int add(A* pA) {
        return 0;
    }
};

template <class T>  int compare(T t1, T t2) {
    t1.add(&t2); // 我这里调了 add,但是这是安全的,只要这个类有 add
    return 0;
}

int main() {
    A x{};
    A y{};
    compare(x, y);
    return 0;
}

3、

在 C++中 ,泛型方法的函数体,必须实现在头文件里,否则不能过编译,因为要生成代码

4、

类型默认参数

这个是一个没啥用的例子

cpp
class A
{
public:
    int x;
    int add(A* pA) {
        return 0;
    }
    int operator+(A& other) {
        return 0;
    }
};

template <class T = int>  int compare(T t1, T t2) {
    return t1 + t2;
}

int main() {
    A x{};
    A y{};
    compare(x, y);
    compare(1, 3);
    return 0;
}

5、模板继承

cpp
template <class T>
class A :  T
{
    int x;
};

class B
{
    int y;
};

int main() {
    A<B> x{};
    return 0;
}

A 是有一些公共方法的类

B 是一些自定义类

以前我们总是让 A 去做父类,B 做子类

但是这样可以实现 A 继承 B,永远构造 A?

传统继承要求B必须派生自A,这会强制BA形成强耦合

如果B已经继承自其他类,传统的B : A会导致多层继承,而A<B>只需作为B的派生类,避免冲突

A<T>可以改变其基类的行为。例如,若T提供某种算法策略,A<T>可复用该策略并添加额外状态(如x

以及实现 策略模式

cpp

// 策略接口(隐式约定,非显式接口)
class B {
public:
    // 策略方法:计算平方
    int calculate(int value) const {
        return value * value;
    }
};

class C {
public:
    // 策略方法:计算立方
    int calculate(int value) const {
        return value * value * value;
    }
};

// 模板类 A 继承自策略 T
template <typename T>
class A : public T {
private:
    int x;
    typedef A<T> _MyBase; // 等价
    using _MyBase = T; 
public:
    A(int x) : x(x) {}

    // 复用策略的算法,并扩展功能
    void execute() {
        int result = this->calculate(x); // 调用策略的算法
        int result = _MyBase::calculate(x); // 避免虚函数调用开销,但可能破坏多态
        std::cout << "Strategy result: " << result << std::endl;
    }
};

返回值后置

C++11 开始

1、

lambda

2、

模板无法推导出参数

cpp
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

返回值后置,则必定要求函数的返回值是 auto,然后 auto 用 -> 后的类型

cpp
auto func(int x, int y) -> int
{
    return x + y;
}
auto func(int x, int y) //ok
{
    return x + y;
}

变量用 auto 必须赋值

函数的话,返回值是 auto 有时候可以不后置,推不出来就后置

std::ref

cpp
#include <iostream>
#include <functional>
#include <vector>

void increment(int& n) {
    ++n;
}

int main() {
    int x = 10;
    // 直接使用引用,不能存入容器中
    // int& ref = x;  // ref 只能作为别名使用

    // 使用 std::ref 将 x 包装成引用包装器
    std::vector<std::reference_wrapper<int>> vec;
    vec.push_back(std::ref(x));  // 这样可以存入容器

    // 通过引用包装器修改 x 的值
    vec[0].get() = 20;
    std::cout << "x = " << x << std::endl;  // 输出:x = 20

    // 同样可以传递给要求值传递的接口
    increment(std::ref(x).get());
    std::cout << "x = " << x << std::endl;  // 输出:x = 21

    return 0;
}

std::shared_ptr

实现:

在大多数实现中,std::shared_ptr 的引用计数并不是存储在每个 shared_ptr 对象内部的成员变量,而是放在一个称为控制块(control block)的单独内存区域中,这个控制块通常是在堆上分配的

constexpr

自:C++11

可用于修饰普通变量、函数(包括模板、构造)

获得在编译阶段计算出结果的能力,并不代表 constexpr 修饰的表达式一定会在程序编译阶段被执行

1、变量

实际上是设计用来取代 const 的。

因为函数的参数 const,是表示只读,但是明显是个变量

而语句的 const,是表示这是个常量

为了区分,只读叫做 const,常量叫做 constexpr

cpp
const int a = 5 + 4;
constexpr int a = 5 + 4; //完全等价,都可以在程序的编译阶段计算出结果

2、返回值是 constexpr

约束 (1)函数的函数体里,除了 return,不能有其它语句(using、assert 等无意义语句是可以的)

(2)return 的表达式,必须是常量表达式,不可以求别的值,不可以赋值

(3)像模板函数一样,使用前必须有完整的定义,而不是声明

cpp
constexpr int display(int x){
    return 3 + x;
}
int main()
{
    //调用常量表达式函数
    int a[display(3)] = { 1,2,3,4 };
    return 0;
}

explicit

阻止隐式转换,只能显式通过构造函数构造

初始化参数列表

位置:放在 cpp 的实现里

类名(形参 1,形参 2,形参 3):基类类名(形参 1),成员变量(形参 2)

C
class MyDialog : public QDialog
{
    Q_OBJECT

public:
    explicit MyDialog(QDialog *parent = nullptr);
    ~MyDialog();

private:
    int someValue; // 假设这是我们需要初始化的成员变量
};
C
MyDialog::MyDialog(QDialog *parent)
    : QDialog(parent),  // 初始化基类 QDialog
      someValue(42)     // 初始化成员变量 someValue
{
    setWindowTitle("My Non-Modal Dialog");
    resize(300, 200);
}

1、初始化列表的第一个参数,可以调用基类的构造函数

2、剩下的,括号外是成员变量

3、括号内一直是形参

如果你要在子类初始化之前,自定义的调用父类的初始化函数,必须这样写在初始化列表里

不能写在子类构造函数里

在初始化列表中指定基类构造函数和成员变量初始化是唯一的方法,同时能避免赋值提高效率

顺序:

调用基类构造函数。

按声明顺序初始化成员变量。

执行派生类的构造函数体。