主要是模板、自动类型推断
// C++的特殊技巧:第一弹 // g++ --std=c++17 -Wall main.cpp #include <algorithm> #include <iostream> #include <initializer_list> // 1: 在初始化变量的时候,顺便做一些其他的操作 namespace init_operation { // 主要用到的是「逗号表达式」,(a, b) == b inline void test() { // Warning: 声明了一个变量但是没有使用 // 因为有警告所以一般不会这么写 // int _t((std::cout << "Hi" << std::endl, 0)); // 演示,其实和上面的写法本质上是一样的,都是声明了一个新的变量 // 逗号表达式的值是最后一部分的值 int _t = (std::cout << "Hi" << std::endl, 0); std::cout << "_t = " << _t << std::endl; // 真正常用的写法 // 前面的(void)避免编译器发出警告,像是「执行一个int的构造函数,但是把构造结果丢弃」 (void)int((std::cout << "Hi Again" << std::endl, 0)); // 1 arg,初始化一个int // n args,常用于可变参数的情况 (void)std::initializer_list<int>{ (std::cout << "Hi, init" << std::endl, 0) }; } } // namespace init_operation // 2: 特殊的运算符重载 namespace special_operator { class F1 { public: // 重载了一个有点奇怪的运算符:->* // 这个运算符是做什么的呢?看下面F2的演示 // 再重复一遍:可重载的运算符是固定的(包括「->*」),自己是不能随机增加的 template<typename Lambda> // 参数使用右值引用,是一个可调用的对象 // 右值是为了支持「完美转发」 std::forward // 也可以同时高效接收左值和右值参数,不会多余地拷贝或移动 void operator->*(Lambda&& l) const { l(1, 1); } // 在C++ 20中,operator重载也可以使用auto自动推断参数类型 // C++ 17 // template <typename Lambda> // void operator->*(Lambda&& lambda) const { ... } // C++ 20 // void operator->*(auto lambda) const { ... } }; class F2 { public: // 一个普通的函数 int func(float x) { return (int)(x + 0.5f); } }; inline void test() { F1 f1; // 特殊写法,直接传递一个lambda表达式给操作符函数 // (正常应该是函数调用的形式) f1->*[](int a, int b) { std::cout << a + b << std::endl; }; F2 f2; // 类似普通的函数指针,成员函数也可以有指针 int (F2::*fp)(float) = &F2::func; ((&f2)->*fp)(0.1f); }; } // namespace special_operator // 3: lambda表达式的自动参数类型推断(通用 lambda),C++ 14开始支持 namespace generic_lambda { inline void test() { auto func = [](auto data) { // 使用auto自动推断参数类型,就像模板函数一样 std::cout << data << std::endl; }; func(1); func("hello"); } // auto f = [](auto x) { return x + 1; }; // 底层会被编译为这样的结构 // struct Lambda { // template <typename T> // auto operator()(T x) const { return x + 1; } // }; } // namespace generic_lambda // 4: 参数类型、数量动态推导 namespace auto_args { // 这是一个接受任意类型数量参数的模板 template<typename... Types> struct MyStruct { void printTypes() { // 统计类型数量 static constexpr size_t count = sizeof...(Types); (void)count; // 用initializer_list展开类型,打印每个类型的名字 (void)std::initializer_list<int>{ (std::cout << typeid(Types).name() << std::endl, 0)... }; } }; void print() { std::cout << "end" << std::endl; } // 递归打印每一个参数 template<typename T, typename... Args> void print(T first, Args... rest) { // 多个未知类型、数量的参数 std::cout << first << std::endl; print(rest...); // 直接传递 } // 初始化列表技巧 // 可以参考上面的init_operation template<typename... Args> void printAll(Args... args) { int dummy[] = { (std::cout << args << "\n", 0)... }; // 用逗号表达式保证依次执行每个 std::cout << args (void)dummy; // 避免编译器警告 } inline void test() { // 可以这样用 MyStruct<int, float, double> a; MyStruct<> b; // 甚至可以一个参数也不传 a.printTypes(); // i f d // 关警告的技巧 (void)b; // 用法 print(1, 2.5, "hello", 'A'); // 会打印每个参数 printAll(1, 2.5, "hello", 'A'); // 会打印每个参数 } } // namespace auto_args // 5: 自动推断返回值类型 namespace auto_return { // 自动推断返回值类型,C++ 11就引入了,C++ 14增加了功能 // 写在非模板函数中其实没什么意义 // 当有多处return并且返回值类型不同的时候,编译器报错:无法自动推断唯一返回类型 auto func1() { return 42; } // auto + 尾返回类型 // 这不是更麻烦了吗? auto func2(int x, int y) -> int { return x + y; } // 用在这里就合理了 // 使用decltype描述返回值的类型 template<typename T, typename U> auto add(T a, U b) -> decltype(a + b) { return a + b; } inline void test() { auto a = func1(); auto b = func2(1, 1); auto c = add(1.0, -1); // double (void)(a + b + c); } } // namespace auto_return int main() { init_operation::test(); special_operator::test(); generic_lambda::test(); auto_args::test(); auto_return::test(); return 0; }