跳过正文
  1. 文章/

cpp学习笔记(7)

·2357 字·5 分钟
目录

命令行参数
#

早就知道在 c 中是可以在 main 中获取从命令行传入的参数的。但是对于解析是一窍不通,这里简单说一下如何获取整形。

不过,这些方法都仅仅适用于简单的字符串处理,而一个简单的带参 cli 程序,基本都要求参数能够无视顺序,可选参数,这些统统都不能实现…

std::stringstream
#

这个函数用于把字符串转换成流,然后再把流输出到整形。

std::stringstream convert{arcv[1]};
int num{};
if(convert>>num){
    std::cout<<num;
}

std::stoi
#

这个就是现代C++的方法了。

直接转换就行:

    int num{std::stoi(argv[1])};
    std::cout << num;

lambda表达式
#

lambda表达式看起来像这样子:

[<capture>](<parameter list>) -> <return type> {<body>};

参数列表可以省略,返回值也可以省略,会自动推断。

所以说,最简的 lambda 表达式:[]{}

捕获
#

对于以下变量来说,不需要捕获也可使用:

  • 全局变量,静态局部变量,线程局部变量
  • 常量表达式(包括被编译器视为常量表达式的变量)

捕获语法是 lambda 表达式中一个不可缺少的部分,词法分析就是通过 [] 来发现这是一个表达式的。

lambda 表达式不和 {} 一样,不能访问上级大括号中的局部变量。它只能访问静态和全局的变量,以及 constexpr

而捕获其实就是把原来应该能让 lambda 能够使用的局部变量让它使用。

捕获的时候可以声明新变量的,在 [] 内,可以使用当前块内能访问的变量来参与运算。

捕获有两种类型:默认捕获和单个捕获。

按对象捕获
#

假设有变量 a b:

  • 传值:[a,b]。默认传入变量的常量值,如果需要修改,需要在大括号前加上 mutable
  • 传引用:[&a,&b]

默认捕获
#

默认捕获会捕获 lambda 中提到的所有变量。

  • [=]:传入值
  • [&]:传入引用

不允许同时使用默认捕获和按对象的捕获。

创建闭包
#

提到闭包这个词,就不得不提函数式编程了。简单来说,函数是“一等公民”,就像我们传统的命令式编程的变量(对象)一样,可以作为参数传递,可以被返回,可以运行时创建等等。

那么闭包就是保存了当前上下文中某些状态的函数。

在C++中,想实现一个闭包,还是得定义一个普通函数(虽然说跟一般的匿名函数印象不太一样):

std::function<int(int)> multiply(int factor) {
    return [factor](int base) { return base * factor; };
}
int main() {
    auto multiply_3 = multiply(3);
    auto multiply_4 = multiply(4);
    std::cout << multiply_3(3) << '\n';
    std::cout << multiply_4(4);
}

multiply_3multiply_4 就是一个闭包。

实现一个简单的函数
#

lambda 表达式可以作为 lambda 类型的变量存储。
#

    auto square{[](int base) { return base * base; }};
    std::cout << square(3);

也可以声明的时候就调用:
#

[]() { std::cout << "Hello world!\n"; }();
// 前一个括号是形参列表,后一个括号表示调用函数

值得提的一点是,这其实不是真正的函数,实际上只是重载了 () 实现像函数一样的调用。

在算法库的应用
#

前面在 <algorithm> 中,最后一个参数是比较函数的地址,其实我们可以直接在这里写 lambda 函数。

lambda推断
#

对于实参推断(给出 (auto a, auto b) 的情况,只要函数体里面的能匹配,那么没有问题,因为编译器会自动生成重载的 lambda 表达式。

对于返回值,要求所有返回的地方一致才能推断,否则,请使用 -> 手动指定返回值类型。

带捕获的 lambda 拷贝
#

如果对带捕获的 lambda 表达式操作的时候,目标的类型是 std::function,那么会发生隐式类型转换,将 lambda 闭包进行深拷贝,使得多次使用 std::function 构造的多个对象出现独立的拥有原始闭包状态的副本。

lambda 构造的时候使用了 mutable 的值捕获,那么每次捕获的变量都是原始的捕获值。

解决方法是使用 std::ref 来包装 lambda 表达式。对于传入参数不一致的情况,它会返回一个 std::reference_wrapper 对象,保证内部存储的是对原始 lambda 对象的引用,从而保证每次调用的是同一个 lambda 表达式对象。

运算符重载
#

有三种办法重载:

友元函数:如果想要重载操作数是类对象的运算符,为了获取类内成员的访问权限,可以把此重载函数以友元的形式声明在类内。

普通函数:同时可以使用 gettersetter 来避免直接访问内部的成员变量。

类成员函数:仅适用于左侧操作数是类成员的情况,此时右操作数就作为参数。

一些选择建议:

  • 重载赋值、下标([])、函数调用、成员选择(->) -> 类成员函数
  • 重载一元运算符 -> 类成员函数
  • 不修改左操作数 -> 普通/友元函数
  • 左操作数是内置类型无法修改 -> 普通/友元函数
  • 左操作数可以修改 -> 类成员函数

重载函数一般返回左侧的对象的引用(以便可以连续用 . 调用)或者返回一个同类型的对象(用于连续使用重载运算符比如 +=)。

重载一元+/-
#

由于一元的 +/- 是类成员函数,而且没有参数,所以不会和二元的混淆。

重载自增/减
#

前自增/减参数列表没有参数,而后自增/减有一个 int 用于占位,从而将两者区分。

由于后自增/减要返回原来的值,所以需要一个临时变量用于返回,相比前自增/减多了开销,更倾向使用前自增/减。

下标访问重载
#

下标访问的重载返回值一般使用 auto&,从而作为左值被修改。

在C++23之前,如果定义类的时候使用 const,那么就需要添加一个成员重载函数:

const auto& operator[](T t) const;

这个函数被常量对象优先使用,被非常量对象次优先使用。

在C++23,用右值引用解决此问题。

飞船运算符
#

外来语言的符号,用于比较两个数大小关系。

返回下面类型:

strong_ordering: 有equivalentequalgreaterless

weak_ordering: 比 strong 少了相等,因为是弱,只有等效。

partial_ordering: 相比 weak 多了 unordered,用于表示无法比较的情况。

从上往下可以隐式转换。

类型转换重载
#

类型转换重载也是 operator 操作符,不过重载的目标其实有点像C的强制转换:

class MyClass{
    //...
  public:
    operator int() { 
        // conversion
        return <intType_obj>;
    }
};

无须返回值,也无须参数,因为显而易见的返回目标类型,传入的是这个类对象,这也必须是一个类成员函数。

建议加上 explicit 关键字,因为我们不希望隐式转换带来的不确定性。

如何平衡类型转换重载和转换构造函数?

假设要把A转换成B:

A可改,B不可改。那么就定义一个类型转换的重载。

A不可改,B可改。那么就定义一个参数只含A的转换构造函数,让B去接收A。

A、B都不可改。那么定义一个普通函数实现转换吧。

复制运算符重载
#

在动态分配内存中,有时为了避免自己赋值给自己,用一个 if 来设置保护防止出现空指针问题。

在三/五/零原则中,如果不带指针,那么应该不应该定义这个复制运算符重载。(其他:析构、复制构造,移动运算符,移动构造函数)