1. 列表初始化的使用

在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:

struct Point
{int _x;int _y;
};
int main()
{int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0;
}

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型用户自定义类型使用初始化列表时,可添加等号(=),也可不添加

struct Point
{int _x;int _y;
};
int main()
{int x1 = 1;int x2{ 2 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };// C++11中列表初始化也可以适用于new表达式中int* pa = new int[4]{ 0 };return 0;
}

创建对象时也可以使用列表初始化方式调用构造函数初始化。

class Date
{
public:Date(int year, int month, int day):_year(year),_month(month),_day(day){cout << "Date(int year, int month, int day)" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2022, 1, 1); // old style// C++11支持的列表初始化,这里会调用构造函数初始化Date d2{ 2022, 1, 2 };Date d3 = { 2022, 1, 3 };return 0;
}

注意:初始化列表语法可防止缩窄,即禁止将数值赋值给无法存储它的数值变量,即将值存储到比它窄的变量中。

2.C++11新声明

1. auto

C++11中将auto用于实现自动类型推断。要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。通过auto自动类型推断获得的变量可以使用typeid( )函数获得实际类型

int main()
{int i = 10;auto p = &i;float m = 10.01;auto pf = m;cout << typeid(p).name() << endl;//int *cout << typeid(pf).name() << endl;//floatmap<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();return 0;
}

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

 auto a = 1, b = 2;auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同

auto不能作为函数的参数不能直接用来声明数组

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a){}
// 此处代码编译失败,auto不能推导数组
auto b[] = {4,5,6};

2. decltype

关键字decltype将变量的类型声明为表达式指定的类型

int main()
{const int x = 1;double y = 2.2;int z = 0;decltype(x * y) ret; // ret的类型是doubledecltype(&x) p;  // p的类型是int*decltype(z) tmp;// z的类型是intcout << typeid(ret).name() << endl;//doublecout << typeid(p).name() << endl;//int *cout << typeid(tmp).name() << endl;//intreturn 0;
}

在定义模板的时候特别好用,因为只有等到模板被实例化时才能确定类型。

template<class T, class U>
void func(T t, U u)
{...decltype(T*U) tu;...
}

3.返回类型后置

C++新增的一种函数声明语法:在函数名和参数列表后面(而不是前面)指定返回类型。让我们能够使用 decltype 来指定模板函数的返回类型。

template<class T, class U> -> decltype(T*U)
auto func(T t, U u)
{...decltype(T*U) tu;...
}

这里解决的问题是,在编译器遇到func的参数列表前, T和U还不在作用域内,因此必须在参数列表后使用decltype。这种新语法使得能够这样做。

4.模板别名:using =

新语法可用于模板部分具体化,但是typedef不能

typedef array<double, 12> arrd;
typedef array<int, 12> arri;
typedef array<std::string, 12> arrstr;arrd gallons;// gallons is type array<double, 12>
arri days;   // days    is type array<int, 12>
arrst months;// months  is type array<std::string, 12>/*新语法*/
template<class T>
using arrtype = array<T, 12>;//template to create multiple aliasesarrtype<double> gallons;     //gallons is type array<double, 12>
arrtype<int> days;           //days    is type array<int, 12>
arrtype<std::string> months; //months  is type array<std::string, 12>

5. nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。nullptr 可以被隐式转换成任意的指针类型。

为向后兼容,C++11仍允许使用0来表示空指针,因此表达式nullptr==0为true

6. explicit

C++11引入了关键字explicit,以禁止单参数构造函数导致的自动转换:

class Plebe
{...Plebe (int);explicit Plebe(double);...
};intm main()
{Plebe a, b;a= 5;          //allowedb = 0.5;       //unallowedb = Plebe (0.5);//显式转换,allowed
}

7.类成员初始化

在类声明中,可使用等号或大括号版本的初始化,但不能使用圆括号版本的初始化。

8. 管理虚方法 override 和 final

class A
{int a;
public:Action(int i = 0) : a(i) {}int val() const {return a;}virtual void show(char ch) const { cout << val() << ch << endl;}
};class B: public A
{
public:    B(int i = 0) : A (i) {}virtual void show(char * ch) const {cout << val() << ch << "!" << endl; }
};

由于类B定义的是show(char * ch)而不是show(char ch),将对B对象隐藏show(char ch),因此下面代码报错:

B b(10);
b.show('b');//faild

在C++11中,可使用虚说明符override指出您要覆盖一个虚函数:将其放在参数列表后面。如果声明与基类方法不匹配,编译器将视为错误。下面的函数重写将生成一条编译错误:

virtual void show(char * ch) const override {cout << val() << ch << "!" << endl; }

如果想禁止派生类覆盖特定的虚方法,为此可在参数列表后面加上final。例如,下面的代码禁止A的派生类重新定义函数show(char ch):

virtual void show(char ch) const final{ cout << val() << ch << endl;}

3.基于范围的for循环

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围

void TestFor()
{int array[] = { 1, 2, 3, 4, 5 };for(auto& e : array)//e为引用类型,用于修改数组元素e *= 2;for(auto e : array)cout << e << " ";//2,4,6,8,10return 0;
}

注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。for循环迭代的范围必须是确定的。

4.右值引用

1、概念理解

  • 左值:表示可寻址。是保存在内存中,具有确切地址,并能取地址,进行访问,修改等操作的表达式(变量)。
  • 右值:表示可读不可寻址。是保存在内存中,或者寄存器中,不知道也无法获得其确切地址,在得到计算表达式结果后就销毁的临时表达式。包括字面常量、诸如x+y表达式以及函数返回值。
  • 右值引用:表示即将过期但还没过期的值。

右值是能够赋值给左值,但是左值不能赋值给右值。

右值引用主要作用是解决:(1)大对象在作为函数返回值返回时的深度拷贝问题,(2)智能指针时将其他unique_ptr通过move()移动后可以赋值给unique_ptr,(3)大对象之间的快速复制

int &a = 1;//错误,1是右值,a是左值引用,无法直接初始化,需要用左值初始化;
    int a = 1; //正确,a是左值不是左值引用,可以被赋值;
    int const &a = 1;//正确,a是常量左值引用,右值可以绑定到常左值引用
    int &&a = 1;//正确,这是C++11中的右值引用,a为左值;

2、函数的返回值一般是右值,也可能是左值:

  • 当返回值为函数内部定义的局部变量时,函数返回值为右值,因为函数内部该变量已被销毁。
  • 当返回值为指针类型或引用类型的参数时,或者全局变量时,函数返回值为左值
    //返回右值的常规函数int fun(){//函数中a是局部变量,会销毁,所以返回值是右值,只能临时使用。int a = 1;return a;}//返回左值的函数int &fun(int &a){//函数将输入的引用返回,从始至终都是这个aa++;return a;}//以上函数可以这么用fun(a) = 4;//因为fun返回左值,所以fun可以被赋值//错误的返回左值案例int &fun(){//不要将局部变量的引用返回,因为返回后局部变量被销毁int a = 3;return a;}

3、移动语义std::move()

  • move作用是可以将一个左值转换成右值引用,从而可以调用C++11的拷贝构造函数。由于在C++11中的移动构造函数的入参是右值引用,因此当传入左值时,无法调用该移动构造函数,需要借助move将左值转换成右值引用。
  • 使用move后,原来的指针unique_ptr指针转让所有权变成空指针,让你能够将一个unique_ptr赋给另一个unique_ptr

4、完美转发std::forward()

完美转发是指在函数模板中,完全依照模板的参数类型,将参数传递给当前函数模板中的另外一个函数。
因此,为了实现完美转发,除了使用万能引用之外,我们还要用到std::forward(C++11),它在传参的过程中保留对象的原生类型属性。这样右值引用在传递过程中就能够保持右值的属性。

void Func(int& x) { cout << "左值引用" << endl; }
void Func(const int& x) { cout << "const左值引用" << endl; }
void Func(int&& x) { cout << "右值引用" << endl; }
void Func(const int&& x) { cout << "const右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)  // 万能引用
{Func(std::forward<T>(t));  // 根据参数t的类型去匹配合适的重载函数
}
int main()
{int a = 4;  // 左值PerfectForward(a);const int b = 8;  // const左值PerfectForward(b);PerfectForward(10); // 10是右值const int c = 13;PerfectForward(std::move(c));  // const左值被move后变成const右值return 0;
}

运行结果如下:

5.新增容器


用橘色圈起来是C++11中的几个新容器,但是实际最有用的是unordered_map和unordered_set。

6.类的新功能

1、类默认生成函数

任何一个类,在什么都不写的时候,都会默认生成6个成员函数
默认构造函数析构函数拷贝构造函数赋值重载函数普通对象取地址const对象取地址

C++11中类新增了2个默认成员函数,使其变为了8个默认成员函数。新增的这两个成员函数就是移动构造函数移动赋值函数

如果我们没有自己实现没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器才自动生成默认移动赋值移动赋值函数。如果我们自己实现了移动构造函数或移动赋值运算符,编译器将不会自动提供复制构造函数复制赋值运算符。

class Person {
public:Person(const char* name="", int age = 0){ ... };Person (const Person& p) ;//复制构造函数Person(Person&& p);//移动构造函数
private:char* _name;int _age;
}//复制构造函数
Person::Person(const Person& p)
{_name = new char[20];strcpy(_name, p._name);_age = p._age;
}//移动构造函数
//移动构造函数/移动赋值运算符的参数不能是const引用,因为这个方法修改了源对象
Person::Person(Person&& p)
{_name = p._name;p._name = nullptr;//修改了原对象_age = p._age;
}

通过提供一个使用左值引用的构造函数和一个使用右值引用的构造函数,将初始化分成了两组。使用左值对象初始化对象时,将使用复制构造函数,而使用右值对象初始化对象时,将使用移动构造函数。

2、defult  / delete

  • delete关键字除了释放被new开辟的空间外,delete关键字还可以修饰默认生成的成员函数,让其禁止默认生成
  • default关键字,假设要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,在类成员函数的后面提供defult,会让该成员函数强制默认生成
class Person {
public:Person(const char* name="", int age = 0){ ... };Person (const Person& p) = delete;//禁止生产默认复制构造函数Person(Person&& p) =default;//强制默认生成移动构造函数Person& operator =(Person&&/p) = default;//强制默认生成移动赋值重载函数
private:lb::string _nane;int _age;
}
void test()
{Person s1;Person s2 = s1;//调用拷贝(此处被禁止了)Person s3 = std::move (s1);//调用移动构造Person s4;s4 = std:: move(s2);//调用移动赋值
}

7.Lambda表达式

Lambda 表达式是一个匿名函数,即没有函数名的函数。

1、Lambda表达式的语法规则

[capture-list] (parameters) mutable -> return-type { statement}

  • [capture-list] : 捕捉列表,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针
  • (parameters):参数列表,如果不需要参数传递,则可以连同()一起省略。
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型,声明函数的返回值类型,没有返回值时此部分可省略。也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体,在函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

【注意】在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[] {};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=] {return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c) {b = a + c; };fun1(10);cout << a << " " << b << endl;// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int {return b += a + c; };cout << fun2(10) << endl;// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0;
}

lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量

2、为什么引入Lambda表达式?

如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。

int main()
{int array[] = { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较,排出来结果是升序std::sort(array, array + sizeof(array) / sizeof(array[0]));// 如果需要降序,需要改变元素的比较规则std::sort(array, array + sizeof(array) / sizeof(array[0]), std::greater<int>());return 0;
}

但是如果待排序的元素为自定义类型,则需要用户自定义排序时的比较规则

struct Goods
{string _name;  // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
struct ComparePriceLess//按照水果价格降序排序
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};
struct ComparePriceGreater//按照水果价格生序排序
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,    3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());//降序排序sort(v.begin(), v.end(), ComparePriceGreater());//升序排序
}

引入Lambda表达式后,上述排序代码可以写成如下形式:

vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,    3 }, { "菠萝", 1.5, 4 } };
//降序排序
sort(v.begin(), v.end(), [](Goods gl, Goods gr)->bool {return gl._price<gr._price});
//升序排序
sort(v.begin(), v.end(), [](Goods gl, Goods gr)->bool {return gl._price>gr._price});

3、Lambda表达式原理

函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象

class Rate
{
public:Rate(double rate):_rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};
int main()
{// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lambdaauto r2 = [=](double monty, int year)->double {return monty * rate * year;};r2(10000, 2);return 0;
}

从使用方式上来看,函数对象与lambda表达式完全一样。函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

8.包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。那我们为什么需要包装器呢?先看例子:ret = func(x) ;

上面的代码中func可以是仿函数对象,可以是函数指针,也可以是lambda表达式. . .这些对象都可以调用,在实现模板时就会对一个模板进行多次的实例化,导致了模板的效率底下。

template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)//普通函数
{return i / 2;
}
struct Functor//仿函数
{double operator()(double d){return d / 3;}
};
void test20()
{// 函数名cout << useF(f, 11.11) << endl;// 仿函数cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
}

由代码的执行结果我们可以看到,count有三份,说明我们实现了3个不同的useF函数模板,主要是由于程序中定义的几种模式的函数,都满足函数模板中调用的函数参数与变量参数之间的使用关系,因此在实际运行模板函数时,分别调用几种不同的函数

但是如果我们使用包装器将三个函数进行包装,对应的模板只会实现一份。包装格式如下:

function<返回类型(参数类型)> 包装器名称=包装函数

#include<iostream>
#include<functional>
using namespace std;template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
int f(int a) {//普通函数return a + 0;
}
struct Functor {//仿函数
public:int operator() (int a) {return a + 1;}
};
class Plus//类成员函数
{
public:static int plusi(int a) {return a + 2;}double plusd(double a) {return a + 3;}
};
void test21()
{// 函数名(函数指针)std::function<int(int)> func1 = f;cout << useF(func1, 11) << endl;// 函数对象std::function<int(int)> func2 = Functor();cout << useF(func2, 11) << endl;// lamber表达式std::function<int(int)> func3 = [](const int a) {return a + 5; };cout << useF(func3, 11) << endl;
}

在使用了包装器后,模板参数就不再出现实例化多份的情况了。

9.智能指针

C++11——智能指针_oywLearning的博客-CSDN博客

10.原子操作

C++并发编程 | 原子操作std::atomic_oywLearning的博客-CSDN博客_std::atomic

11.线程库std::thread

C++多线程编程之thread类详解_oywLearning的博客-CSDN博客_c++ thread

12.可变参数模板

可变参数模板 使得编程者能够创建这样的 模板函数模板类 ,即可 接受可变数量的参数
例如要编写一个函数,它可接受任意数量的参数,参数的类型只需是 cout 能显示的即可,并将
参数显示为用逗号分隔的列表,其函数模板如下:
//为多个参数的情况定义的函数
template<typename T,typename... Arg>
void show_list(T value,Arg... arg)
{cout<<value<<",";show_list(arg...);//递归调用多个变量的show_list版本,最后调用单个变量的show_list版本
}
//为一个参数的情况定义的
template<typename T>
void show_list(T value)
{cout<<value<<endl;
}
//为没有参数的情况定义的
void show_list(){}
要创建可变参数模板,需要理解几个要点:
  • 模板参数包
  • 函数参数包
  • 展开参数包
  • 递归

C++11新特性——总结相关推荐

  1. IntelliJ IDEA 使用 Java 11新特性以及Java 8 以来的Java 11新特性介绍

    文章目录 Java 11 安装 IDEA 设置 特性1:lambda表达式中允许使用var 特性2: String新增REPEAT 方法,方便拷贝字符串 特性3: 文件读写更方便:readString ...

  2. Java 11 新特性

    2019独角兽企业重金招聘Python工程师标准>>> Java 11 新特性 转载于:https://my.oschina.net/u/3764794/blog/2993127

  3. C++11新特性中的匿名函数Lambda表达式的汇编实现分析(二)

    2019独角兽企业重金招聘Python工程师标准>>> C++11新特性中的匿名函数Lambda表达式的汇编实现分析(一) 首先,让我们来看看以&方式进行变量捕获,同样没有参 ...

  4. C++11 新特性之std::thread

    C++11 新特性之std::thread 原文:https://blog.csdn.net/oyoung_2012/article/details/78958274 从C++11开始,C++标准库已 ...

  5. C++11新特性之新类型与初始化

    C++11新特性之新类型与初始化 snoone | 2016-06-23 11:57    浏览量(148)    评论(0)   推荐(0) 数据 这是C++11新特性介绍的第一部分,比较简单易懂, ...

  6. Java 11新特性解读

    概述 美国当地时间9月25日,Oracle 官方宣布 Java 11 (18.9 LTS) 正式发布,可在生产环境中使用!这是自 Java 8 后的首个长期支持版本,将支持到2026年,可以使用下面的 ...

  7. C++11新特性decltype

    该博文为原创文章,未经博主同意不得转载,如同意转载请注明博文出处 本文章博客地址:https://cplusplus.blog.csdn.net/article/details/105042574 C ...

  8. 深入浅出之C++11新特性

    1. auto类型赋予新含义 1.1 auto类型定义 在之前的 C++ 版本中,auto 关键字用来指明变量的存储类型,它和 static 关键字是相对的.auto 表示变量是自动存储的,这也是编译 ...

  9. 《深入理解C++11:C++ 11新特性解析与应用》——导读

    前 言 为什么要写这本书 相比其他语言的频繁更新,C++语言标准已经有十多年没有真正更新过了.而上一次标准制定,正是面向对象概念开始盛行的时候.较之基于过程的编程语言,基于面向对象.泛型编程等概念的C ...

  10. C++11 新特性简介

    1.auto auto是旧关键字,在C++11之前,auto用来声明自动变量,表明变量存储在栈,很少使用.在C++11中被赋予了新的含义和作用,用于类型推断. auto关键字主要有两种用途:一是在变量 ...

最新文章

  1. unity3d做简单小游戏可以吗?
  2. MIPS 汇编指令学习
  3. 看看C# 6.0中那些语法糖都干了些什么(上篇)
  4. 适合Web服务器的iptables规则
  5. 解决ubuntu未安装无线网卡驱动的问题
  6. matlab曲线图导出,从Matlab的Figure中导出数据的办法
  7. 调用微博API获取微博内容
  8. dell服务器读不到无线网卡,求助:Dell venue 11 pro 7130 ms 无线网卡无法驱动。找不到无线网卡。...
  9. powerdesign165破解以及使用教程
  10. 电机与拖动 - 1 绪论
  11. Unity体积光实现浅析
  12. 软件工程总结笔记——软件结构设计(四)
  13. 打不死的又如何能毁灭呢?尴尬的 Windows XP 是升级还是保留?
  14. EXTRACT() 提取函数
  15. 抖音跳转微信小程序、公众号、个人微信、微信群技术路线
  16. 机器学习:向量空间中的投影
  17. 电大计算机专业毕业自我鉴定,电大计算机毕业生自我鉴定.doc
  18. 遥感数据下载——FIRM:VIIRS火点数据、MODIS火点数据简介及下载
  19. 【SQL】【读书笔记】《MySQL必知必会》
  20. 南京邮电大学图书管理系统

热门文章

  1. java web课程设计之图书管理系统
  2. CyberArk被评为Gartner特权访问管理魔力象限的领导者
  3. GM7150,振芯科技,视频解码器,CVBS转BT656/601,QFN32,替换TVP5150/CJC5150
  4. 自然语言处理--Keras 实现LSTM循环神经网络分类 IMDB 电影评论数据集
  5. 如何搭建合理的BOM
  6. 【YBT2023寒假Day11 B】催眠大师(费用流)
  7. 需求提炼:产品需求、技术需求
  8. UnrealEngine蓝图进阶学习笔记(六):按波次生成敌人
  9. 机动战士高达观影顺序
  10. 我的程序员转行过程,聊聊程序员的职业出路在哪里?