跳过正文
  1. 文章/

cpp学习笔记(4)

·1493 字·3 分钟
目录

函数默认参数
#

  1. 声明的时候带默认参数,定义的时候可以不带。但是不能定义的时候有默认参数而声明的时候没有。
  2. 如果用默认参数,那么必须从右到左都有(左边可以没有,不能中断)。
  3. 传入参数的时候,只能从左往右传入,未传入的自动使用默认的参数。

模板
#

定义:

template<typename T> T foo(T a) {
    //your function
}

如果还有一个同名的非模板函数:

int foo(int a) {
    // function
}

调用的时候:

  1. 如果使用foo(),那么会直接调用非模板的函数。
  2. 如果用foo<>(),那么会自动推断,并从模板实例化函数。如果指定类型,则直接从模板按指定的类型实例化函数。

在两个不同的模板参数时,可以定义模板返回用auto关键字,让编译器自动推导返回类型。这是C++17后的CTAD(class template auto deduction)。

注意: CTAD仅用于推断实参(argument,调用时) 不能推断形参(parameter,函数声明)

在C++20后,函数参数传入auto会自动变成模板。


非类型模板:定义constexpr或者consteval(C++20),不写 typename

template <int N>
auto foo() {
    std::cout<<N;
}

foo<5>(); // print 5

由于模板展开需要看到定义,所以模板函数应该放在头中。模板不违反单一定义原则。

左值和右值
#

  • 左值表达式:在表达式结束之后,还能用标识对象访问的表达式。
  • 右值表达式:表达式结束之后立即销毁的临时量。

特别:++x是左值,x++是右值。

引用
#

左值引用
#

左值引用只能绑定到可修改的左值。

常量左值引用可以绑定到可修改/不可修改的左值,右值。

一个常量引用可以引用非常量的对应对象,这使得此引用由编译器保证不修改其引用的对象,而且是函数的只读参数的推荐做法。

如果类型不匹配,引用会尝试将其进行隐式类型转换,得到一个临时对象。需要注意:

  1. 只允许数值提升和非窄化转换。
  2. 此时转化后的临时对象会被引用持有,生命周期与引用本身相同。
  3. 修改原始对象不会对因引用产生的临时对象造成影响。

小心!引用无法延长函数返回值的声明周期,以下例子为UB:

const int& a = getTempVar();

引用和指针
#

左值引用可以绑定到右值,但是必须是常量的引用才可以。同时,对于右值的常量引用会使得直接绑定的临时对象生命周期被延长。

而右值引用(&&)则是用于移动语义。

与auto相结合
#

  • 顶级const: 指的是修饰此别名/对象的const
  • 低级const: 指的是被引用绑定/被指针指向的const

指针可以同时拥有两种const,但是引用不行,因为引用不是对象。

当用auto关键字以及引用来初始化对象时,遵守:

  • 引用会被删除。
  • 顶层的常量会被删除。

当然可以用auto&来重新添加引用,不过这样会同时保留顶级的const。我们一般推荐要手动加上const,这样会让语义更加清晰。

constexpr不是表达式的一部分,所以auto不会自动推断出它,如果需要则要在auto前面手动加上,就像这样:

constexpr const auto& ref{getConstRef()};

但是推断指针的时候,auto会完整的保留指针的类型。

有一点比较特殊,使用const autoauto const是同义的,都会使得新对象成为一个常量的指针。但是如果你想const auto const,那就会报错。

如果想要这么做,最好明确一点,就像这样:

  • const auto*
  • auto* const
  • const auto* const

这样就很明显的看出,在*左边的意义是指向常量,在右边的意义是常量的指针,事实上既然都auto了,也不差这几个关键字吧?我其实比较担心auto不够智能这个问题。

为什么呢?

因为指针是一个对象,而引用可以理解成一个别名,所以当我们对引用进行推断的时候,实际上是推断原始的对象究竟是什么类型。

in/out 参数
#

为了避免拷贝,可以预先初始化一个对象传入其引用,这样可以避免拷贝,不过使得参数列表很乱。如果可行,还是通过返回值优化或者移动语义。