Modern C++学习笔记(一)
一些用法
语言可用性的强化
- nullptr
- nullptr 出现的目的是为了替代 NULL
- 专门用来区分空指针,0。nullptr能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不相等的比较
- constexpr
- C++ 标准中数组的长度必须是一个常量表达式。
- constexpr 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式
- constexpr 修饰的函数可以使用递归
变量及其初始化
- 消除限制,可以在if或者switch中完成变量命名
// 将临时变量放到 if 语句内 if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) { *itr = 4; }
初始化列表
- std::initializer_list 把初始化列表的概念绑定到类型上,允许构造函数或其他函数像参数一样使用初始化列表
MagicFoo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin(); it != list.end(); ++it)
vec.push_back(*it);
}
MagicFoo magicFoo = {1, 2, 3, 4, 5};
- 除了用在对象构造上,还能将其作为普通函数的形参
public:
void foo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin();
it != list.end(); ++it) vec.push_back(*it);
}
magicFoo.foo({6,7,8,9});
结构化绑定
- std::tuple容器用于构造一个元组,进而囊括多个返回值
#include <iostream>
#include <tuple>
std::tuple<int, double, std::string> f() {
return std::make_tuple(1, 2.3, "456");
}
int main() {
auto [x, y, z] = f();
std::cout << x << ", " << y << ", " << z << std::endl;
return 0;
}
类型推导
- auto 可以推导大部分类型,但是还不能用于推导数组类型
- decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 typeof 很相似:
auto x = 1;
auto y = 2;
decltype(x+y) z;//计算某个表达式的类型
或者
std::is_same<decltype(x), decltype(z)>::value //用于判断 T 和 U 这两个类型是否相等
- 尾返回类型:利用 auto 关键字将返回类型后置,从 C++14 开始是可以直接让普通函数具备返回值推导
//C++11
template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}
//C++14
template<typename T, typename U>
auto add3(T x, U y){
return x + y;
}
- decltype(auto) 主要用于对转发函数或封装的返回类型进行推导,它使我们无需显式的指定 decltype 的参数表达式
//在 C++11 中,封装实现是如下形式:
std::string look_up_a_string_1() {
return lookup1();
}
std::string& look_up_a_string_2() {
return lookup2();
}
//而有了 decltype(auto),我们可以让编译器完成这一件烦人的参数转发:
decltype(auto) look_up_a_string_1() {
return lookup1();
}
decltype(auto) look_up_a_string_2() {
return lookup2();
}
- if constexpr : C++17 将 constexpr 这个关键字引入到 if 语句中,允许在代码中声明常量表达式的判断条件
if constexpr (std::is_integral<T>::value) {
return t + 1;
}
- 区间for迭代
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3, 4};
if (auto itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) *itr = 4;
for (auto element : vec)
std::cout << element << std::endl; // read only
for (auto &element : vec) {
element += 1; // writeable
}
for (auto element : vec)
std::cout << element << std::endl; // read only
}
模板
- 外部模板:C++11 引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使我们能够显式的通知编译器何时进行模板的实例化
template class std::vector<bool>; // 强行实例化
extern template class std::vector<double>; // 不在该当前编译文件中实例化模板
- 别名类型模板
template<typename T>
using TrueDarkMagic = MagicType<std::vector<T>, std::string>;
int main() {
TrueDarkMagic<bool> you; // 使用模板别名,生成一个类型为 MagicType<std::vector<bool>, std::string> 的变量
}
- 变长参数模板
template<typename... Ts> class Magic;
如果不希望产生的模板参数个数为 0,可以手动的定义至少一个模板参数。
除了在模板参数中能使用 … 表示不定长模板参数外, 函数参数也使用同样的表示法代表不定长参数, 这也就为我们简单编写变长参数函数提供了便捷的手段。
template<typename... Args> void printf(const std::string &str, Args... args);
对参数进行解包,到目前为止还没有一种简单的方法能够处理参数包,但有两种经典的处理手法:
1)递归模板函数
2)变参模板展开
template<typename T0, typename... T>
void printf2(T0 t0, T... t) {
std::cout << t0 << std::endl;
if constexpr (sizeof...(t) > 0) printf2(t...);
}
事实上,有时候我们虽然使用了变参模板,却不一定需要对参数做逐个遍历,我们可以利用 std::bind 及完美转发等特性实现对函数和参数的绑定,从而达到成功调用的目的。
// 使用 std::bind 将函数和参数绑定在一起
auto bound_print = std::bind(print, 1, 2.5, "hello");
- 折叠表达式
#include <iostream>
template<typename ... T>
auto sum(T ... t) {
return (t + ...);
}
int main() {
std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl;
}
面向对象
- 委托构造,继承构造
#include <iostream>
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // 委托 Base() 构造函数
value2 = value;
}
};
class Subclass : public Base {
public:
using Base::Base; // 继承构造
};
int main() {
Subclass s(3);
std::cout << s.value1 << std::endl;
std::cout << s.value2 << std::endl;
}
- 显式虚函数重载
- override 当重载虚函数时,引入 override 关键字将显式的告知编译器进行重载,编译器将检查基函数是否存在这样的其函数签名一致的虚函数,否则将无法通过编译
- final 是为了防止类被继续继承以及终止虚函数继续重载引入的
struct Base {
virtual void foo(int);
};
struct SubClass: Base {
virtual void foo(int) override; // 合法
virtual void foo(float) override; // 非法, 父类没有此虚函数
};
struct Base {
virtual void foo() final;
};
struct SubClass1 final: Base {
}; // 合法
struct SubClass2 : SubClass1 {
}; // 非法, SubClass1 已 final
struct SubClass3: Base {
void foo(); // 非法, foo 已 final
};
- 显式的声明采用或拒绝编译器自带的函数,使用default,delete关键字
class Magic {
public:
Magic() = default; // 显式声明使用编译器生成的构造
Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造
Magic(int magic_number);
}
- 强类型枚举,使用enum class。这样定义的枚举实现了类型安全,首先他不能够被隐式的转换为整数,同时也不能够将其与整数数字进行比较, 更不可能对不同的枚举类型的枚举值进行比较。但相同枚举值之间如果指定的值相同,那么可以进行比较
enum class new_enum : unsigned int {
value1,
value2,
value3 = 100,
value4 = 100
};
而我们希望获得枚举值的值时,将必须显式的进行类型转换,不过我们可以通过重载 « 这个算符来进行输出
//可以收藏下面这个代码段!!!!!!
#include <iostream>
template<typename T>
std::ostream& operator<<(
typename std::enable_if<std::is_enum<T>::value,
std::ostream>::type& stream, const T& e)
{
return stream << static_cast<typename std::underlying_type<T>::type>(e);
}
这时,下面的代码将能够被编译:
std::cout << new_enum::value3 << std::endl