程序员

学堂在线郑莉《c++程序设计语言》(基础 & 进阶)课程笔记

2019-01-10  本文已影响0人  逐日的夸父

笔记呢只简要记录了老师讲课的关键知识点和结构,然后积累了一些有用的代码小片段和解释性很强的例子,之所以没有分章节是为了使用浏览器页内查找(ctrl+f/ command+f)时方便。
课程的编程作业的代码我也上传了gayhub(在这里呀https://github.com/BMR731/XueTangCplusplus).
有错误的地方希望大家指出啦,有一起继续学习C++的小伙伴也可以一起交流哦~

auto i = j+k;
vector<int> v(5);
for(auto e : v){
    cout<<e;
}
int main(int argc, char* argv[]){
//argc是参数的个数,包括程序执行本身的一个参数;argv是一个字符串的数组
}
if(Derived* dp= dynamic_cast<Derived*)(bp)){
//转换成功,dp指向Derived对象
}else{
//转换失败,bp指向Base对象,
}
//把转换语句写在条件判断中更加安全
int char2int(char c){
    return static_cast<int>(c) - 48;
}
float f = 1.234;
cout<<fixed<<setprecision(2)<<f<<endl;
unsigned i = n;//n is the number we try to reverse
int m=0;//m is the auxiliary number
while(i>0){
    m= m*10 + i%10;
    i  = i/10;
}
//m is the reversed n;
#include <cstdlib>
cin>>seed;
srand(seed);
int i = rand()%6 + 1;//模拟扔骰子

注意:
内联函数体内不能有循环语句和switch语句;
内联函数的定义必须出现在内联函数第一次被调用之前;
对内联函数不能进行异常接口声明。

inline double calArea(int r){//计算圆面积
    return PI*r*r;
}
constexpr int get_size(){ return 20;}
constexpr int foo = get_size();//此时可以确保foo是一个常量表达式
int add(int x, int y=2, int z=3);//对的,默认参数给的顺序必须从右到左
int add(int x =1, int y, int z=3);//错
Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM),  second(newS) {
  }
class 类名 {
public :
    类名(形参);//构造函数
    类名(const  类名 &对象名);//复制构造函数
    //       ...
};

类名::类( const  类名 &对象名)//复制构造函数的实现
{    函数体    }

//如果不希望被复制
//class Point {   //Point 类的定义
public:
    Point(int xx=0, int yy=0) { x = xx; y = yy; }    //构造函数,内联
    Point(const Point& p) =delete;  //指示编译器不生成默认复制构造函数
private:
    int x, y; //私有数据
};

//复制构造函数被调用的三种时机
//1  用一个对象初始化对象时
//2  形参和实参结合时
//3  return 语句返回一个无名对象时
class Point {     
public:
  Point(int xx,int yy);
  ~Point();
  //...其他函数原型
private:
  int x, y;
};
class B;  //前向引用声明
class A {
public:
  void f(B b);
};

class B {
public:
  void g(A a);
};
class foo{
  private:
    static int count;
}
int foo::count =0;//this line don't forget;在类外进行定义和初始化!
int main(){}
enum class Type: char { General, Light, Medium, Heavy};
//友元函数
class Point { //Point类声明
public: //外部接口
  Point(int x=0, int y=0) : x(x), y(y) { }
    int getX() { return x; }
    int getY() { return y; }
    friend float dist(Point &a, Point &b);
private: //私有数据成员
    int x, y;
};

float dist( Point& a, Point& b) {
  double x = a.x - b.x;
  double y = a.y - b.y;
  return static_cast<float>(sqrt(x * x + y * y));
}

//友元类
class A {
friend class B;
public:
void display() {
cout << x << endl;
}

private:
int x;
};

class B {
public:
void set(int i);
void display();
private:
A a;
};

void B::set(int i) {
a.x=i;
}

void B::display() {
a.display();
};
#include
#define
#if....#endif 条件编译
#if..#elif....#else...#endif
#ifdef.. #endif 如果标记被定义过
#ifndef....#endif 如果标记未被定义过

最常用的用法在类的声明文件中,为了避免重复编译,常这样写:
#ifndef CLIENT_H
#define CLIENT_H
...类的声明
#endif
const int* p = &i; //表明p为只读指针,但指针本身可以指向其他地方
int* const p = &i;//表明指针本身只能指向i的地址,但可对i进行读写操作

//void指针作为通用指针来使用
void* p;
int i=0;
p = &i;
int* p2 = static_cast<int*>(p);

//空指针;
int* p = nullptr; //c++11 推荐
p==0;//判断指针是否为空

int compute(int a, int b,  int(*func)(int,int)){
    return func(a,b);
}

int max(int a, int b){return ((a>b)? a:b;)}
int min(int a, int b){return ((a<b)? a:b;)}

res = compute(a,b,&max);
res = compute(a,b,&min);
//分配多维数组
int (*cp)[8][9] = new int[7][8][9];
vector<string> get_subsequences(const string str){
    long len = str.length();
    long num = 1<<str.length();//将1左移len位,求2的len次幂。
    vector<string> res;
    for (int i = 1; i <num ; ++i) {
        string ss;
        for (int j = 0; j < len; ++j) {
            if(i&(1<<j)) ss.push_back(str[j]);
        }
        res.push_back(ss);
    }
    return res;
}
vector<int> nums(5,2);//初始化
sort(nums.begin(), nums.end());//排序
class C: public B {
public:
    C();
    C(int i, int j);
    ~C();
    void print() const;
private:
    int c;
};
C::C(int i,int j): B(i), c(j){
    cout << "C's constructor called." << endl;
}

重载为类内成员函数的要求是,操作符的第一个参数必须是该类的类型。

//双目运算符的重载
//例8-1复数类加减法运算重载为成员函数
Complex Complex::operator + (const Complex &c2) const{
  //创建一个临时无名对象作为返回值 
  return Complex(real+c2.real, imag+c2.imag); 
}
//单目运算符的重载
//重载前置++
Clock & Clock::operator ++ () { 
    second++;
    if (second >= 60) {
        second -= 60;  minute++;
        if (minute >= 60) {
          minute -= 60; hour = (hour + 1) % 24;
        }
    }
    return *this;//返回值的本身引用,可以当左值被修改
}
//重载后置++
Clock Clock::operator ++ (int) {
    //注意形参表中的整型参数
    Clock old = *this;
    ++(*this);  //调用前置“++”运算符
    return old;//返回值的一个副本,只能做右值,不能做触及到本身的修改
}

重载为非成员函数的规则
函数的形参代表依自左至右次序排列的各操作数。
重载为非成员函数时,参数个数=原操作数个数(后置++、--除外)
至少应该有一个自定义类型的参数。
后置单目运算符 ++和--的重载函数,形参列表中要增加一个int,但不必写形参名。
如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。
典型例题:

 class Complex {
    public:
        Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }  
        friend Complex operator+(const Complex &c1, const Complex &c2);//声明类外的函数为友元来提高访问的效率
        friend Complex operator-(const Complex &c1, const Complex &c2);
        friend ostream & operator<<(ostream &out, const Complex &c);
    private:    
        double real;  //复数实部
        double imag;  //复数虚部
    };
    Complex operator+(const Complex &c1, const Complex &c2){//注意这里传const的引用来提高传输效率
        return Complex(c1.real+c2.real, c1.imag+c2.imag); 
    }
    Complex operator-(const Complex &c1, const Complex &c2){
        return Complex(c1.real-c2.real, c1.imag-c2.imag); 
    }

    ostream & operator<<(ostream &out, const Complex &c){
        out << "(" << c.real << ", " << c.imag << ")";
        return out;//返回为ostream的引用来继续保持cout的级联输出
    }

struct B4
{
    virtual void g(int) {}
};

struct D4 : B4
{
    virtual void g(int) override {} // OK
    virtual void g(double) override {} // Error
};
struct B2
{
    virtual void f() final {} // final 函数
};

struct D2 : B2
{
    virtual void f() {}
};
//函数模板
template <typename T>
T add(T x, T y){
    return x+y;
}
//类模板
template <class T>
class Foo{
  T element;
   Foo();
}
Foo<T>::Foo(){}//注意此时在类外标注类名时要把模板参数带上,写成Foo<T>::
//求平方的函数
double square(double x) {
    return x * x;
}
int main() {
    //从标准输入读入若干个实数,分别将它们的平方输出
    transform(istream_iterator<double>(cin), istream_iterator<double>(),
        ostream_iterator<double>(cout, "\t"), square);
    cout << endl;
    return 0;
}

//程序涉及到输入迭代器、输出迭代器、随机访问迭代器这三个迭代器概念,并且以前两个概念为基础编写了一个通用算法。
#include <algorithm>
#include <iterator>
#include <vector>
#include <iostream>
using namespace std;

//将来自输入迭代器的n个T类型的数值排序,将结果通过输出迭代器result输出
template <class T, class InputIterator, class OutputIterator>
void mySort(InputIterator first, InputIterator last, OutputIterator result) {
    //通过输入迭代器将输入数据存入向量容器s中
    vector<T> s;
    for (;first != last; ++first)
        s.push_back(*first);
    //对s进行排序,sort函数的参数必须是随机访问迭代器
    sort(s.begin(), s.end());  
    copy(s.begin(), s.end(), result);   //将s序列通过输出迭代器输出
}

int main() {
    //将s数组的内容排序后输出
    double a[5] = { 1.2, 2.4, 0.8, 3.3, 3.2 };
    mySort<double>(a, a + 5, ostream_iterator<double>(cout, " "));
    cout << endl;
    //从标准输入读入若干个整数,将排序后的结果输出
    mySort<int>(istream_iterator<int>(cin), istream_iterator<int>(), ostream_iterator<int>(cout, " "));
    cout << endl;
    return 0;
}
/*
#include <iostream>
#include <list>
#include <deque>

//输出指定的顺序容器的元素
template <class T>
void printContainer(const char* msg, const T& s) {
    cout << msg << ": ";
    copy(s.begin(), s.end(), ostream_iterator<int>(cout, " "));
    cout << endl;
}

int main() {
    //从标准输入读入10个整数,将它们分别从s的头部加入
    deque<int> s;
    for (int i = 0; i < 10; i++) {
        int x;
        cin >> x;
        s.push_front(x);
    }
    printContainer("deque at first", s);
    //用s容器的内容的逆序构造列表容器l
    list<int> l(s.rbegin(), s.rend());
    printContainer("list at first", l);

    //将列表容器l的每相邻两个元素顺序颠倒
    list<int>::iterator iter = l.begin();
    while (iter != l.end()) {
        int v = *iter;  
        iter = l.erase(iter);
        l.insert(++iter, v);
    }
    printContainer("list at last", l);
    //用列表容器l的内容给s赋值,将s输出
    s.assign(l.begin(), l.end());
    printContainer("deque at last", s);
    return 0;
}

int main() {
    istream_iterator<int> i1(cin), i2;  //建立一对输入流迭代器
    vector<int> s1(i1, i2); //通过输入流迭代器从标准输入流中输入数据
    sort(s1.begin(), s1.end()); //将输入的整数排序
    deque<int> s2;
    //以下循环遍历s1
    for (vector<int>::iterator iter = s1.begin(); iter != s1.end(); ++iter) 
    {
         if (*iter % 2 == 0)    //偶数放到s2尾部
             s2.push_back(*iter);
         else       //奇数放到s2首部
             s2.push_front(*iter);
    }
    //将s2的结果输出
    copy(s2.begin(), s2.end(), ostream_iterator<int>(cout, " "));
    cout << endl;
    return 0;
}

STL所提供的顺序容器各有所长也各有所短,我们在编写程序时应当根据我们对容器所需要执行的操作来决定选择哪一种容器。
如果需要执行大量的随机访问操作,而且当扩展容器时只需要向容器尾部加入新的元素,就应当选择向量容器vector;
如果需要少量的随机访问操作,需要在容器两端插入或删除元素,则应当选择双端队列容器deque;
如果不需要对容器进行随机访问,但是需要在中间位置插入或者删除元素,就应当选择列表容器list或forward_list;
如果需要数组,array相对于内置数组类型而言,是一种更安全、更容易使用的数组类型。
顺序容器的插入迭代器
用于向容器头部、尾部或中间指定位置插入元素的迭代器
包括前插迭代器(frontinserter)、后插迭代器(backinsrter)和任意位置插入迭代器(inserter).

输入一串实数,将重复的去掉,取最大和最小者的中值,分别输出小于等于此中值和大于等于此中值的实数

//10_9.cpp
#include <set>
#include <iterator>
#include <utility>
#include <iostream>
using namespace std;

int main() {
    set<double> s;
    while (true) {
        double v;
        cin >> v;
        if (v == 0) break;  //输入0表示结束
        //尝试将v插入
       pair<set<double>::iterator,bool> r=s.insert(v); 
        if (!r.second)  //如果v已存在,输出提示信息
           cout << v << " is duplicated" << endl;
    }
  //得到第一个元素的迭代器
    set<double>::iterator iter1=s.begin();
    //得到末尾的迭代器
    set<double>::iterator iter2=s.end();    
  //得到最小和最大元素的中值    
    double medium=(*iter1 + *(--iter2)) / 2;    
    //输出小于或等于中值的元素
    cout<< "<= medium: "
    copy(s.begin(), s.upper_bound(medium), ostream_iterator<double>(cout, " "));
    cout << endl;
    //输出大于或等于中值的元素
    cout << ">= medium: ";
    copy(s.lower_bound(medium), s.end(), ostream_iterator<double>(cout, " "));
    cout << endl;
    return 0;
}
统计一句话中每个字母出现的次数
// 10_11.cpp
#include <iostream>
#include <map>
#include <cctype>
using namespace std;
int main() {
    map<char, int> s;   //用来存储字母出现次数的映射
    char c;     //存储输入字符
    do {
      cin >> c; //输入下一个字符
      if (isalpha(c)){ //判断是否是字母
          c = tolower(c); //将字母转换为小写
          s[c]++;      //将该字母的出现频率加1
      }
    } while (c != '.'); //碰到“.”则结束输入
    //输出每个字母出现次数
    for (map<char, int>::iterator iter = s.begin(); iter != s.end(); ++iter)
        cout << iter->first << " " << iter->second << "  ";
    cout << endl;
    return 0;
}
//10_12.cpp
#include <iostream>
#include <map>
#include <utility>
#include <string>
using namespace std;
int main() {
    multimap<string, string> courses;
    typedef multimap<string, string>::iterator CourseIter;

    //将课程上课时间插入courses映射中
    courses.insert(make_pair("C++", "2-6"));
    courses.insert(make_pair("COMPILER", "3-1"));
    courses.insert(make_pair("COMPILER", "5-2"));
    courses.insert(make_pair("OS", "1-2"));
    courses.insert(make_pair("OS", "4-1"));
    courses.insert(make_pair("OS", "5-5"));
    //输入一个课程名,直到找到该课程为止,记下每周上课次数
    string name;
    int count;
    do {
        cin >> name;
        count = courses.count(name);
        if (count == 0)
          cout << "Cannot find this course!" << endl;
    } while (count == 0);
    //输出每周上课次数和上课时间
    cout << count << " lesson(s) per week: ";
    pair<CourseIter, CourseIter> range = courses.equal_range(name);
    for (CourseIter iter = range.first; iter != range.second; ++iter)
        cout << iter->second << " ";
    cout << endl;

    return 0;
}

STL提供的函数对象
用于算术运算的函数对象:
一元函数对象(一个参数) :negate
二元函数对象(两个参数) :plus、minus、multiplies、divides、modulus
用于关系运算、逻辑运算的函数对象(要求返回值为bool)
一元谓词(一个参数):logical_not
二元谓词(两个参数):equalto、notequalto、greater、less、greaterequal、lessequal、logicaland、logical_or

#include <funtional>
sort(a.begin(), a.end(), greater<int>());
cout << accumulate(a, a + N, 1, multiplies<int>());
cout << accumulate(a, a + N, 1, mult)
//数适配器实例——找到数组中第一个大于40的元素
int main() {
    int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 };
    const int N = sizeof(intArr) / sizeof(int);
    vector<int> a(intArr, intArr + N);
    vector<int>::iterator p = find_if(a.begin(), a.end(), bind2nd(greater<int>(), 40));
    if (p == a.end())
        cout << "no element greater than 40" << endl;
    else
        cout << "first element greater than 40 is: " << *p << endl;
    return 0;
}

注:
find_if算法在STL中的原型声明为:
template<class InputIterator, class UnaryPredicate>
InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred);
它的功能是查找数组[first, last)区间中第一个pred(x)为真的元素。

//ptr_fun、not1和not2产生函数适配器实例
bool g(int x, int y) {
    return x > y;
}

int main() {
    int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 };
    const int N = sizeof(intArr) / sizeof(int);
    vector<int> a(intArr, intArr + N);
    vector<int>::iterator p;
    p = find_if(a.begin(), a.end(), bind2nd(ptr_fun(g), 40));
    if (p == a.end())
        cout << "no element greater than 40" << endl;
    else
        cout << "first element greater than 40 is: " << *p << endl;
    p = find_if(a.begin(), a.end(), not1(bind2nd(greater<int>(), 15)));
    if (p == a.end())
        cout << "no element is not greater than 15" << endl;
    else
        cout << "first element that is not greater than 15 is: " << *p << endl;

    p = find_if(a.begin(), a.end(), bind2nd(not2(greater<int>()), 15));
    if (p == a.end())
        cout << "no element is not greater than 15" << endl;
    else
        cout << "first element that is not greater than 15 is: " << *p << endl;
    return 0;
}

// 成员函数适配器实例
struct Car {
    int id;
    Car(int id) { this->id = id; }
    void display() const { cout << "car " << id << endl; }
};

int main() {
    vector<Car *> pcars;
    vector<Car> cars;
    for (int i = 0; i < 5; i++)
        pcars.push_back(new Car(i));
    for (int i = 5; i < 10; i++)
        cars.push_back(Car(i));
    cout << "elements in pcars: " << endl;
    for_each(pcars.begin(), pcars.end(), std::mem_fun(&Car::display));
    cout << endl;

    cout << "elements in cars: " << endl;
    for_each(cars.begin(), cars.end(), std::mem_fun_ref(&Car::display));
    cout << endl;

    for (size_t i = 0; i < pcars.size(); ++i)
        delete pcars[i];

    return 0;
}

STL算法分类
不可变序列算法
可变序列算法
排序和搜索算法
数值算法

struct deleter{
template<class T>
void operator()(T* p){ delete p;}
};
for_each(container.begin(), container.end(), deleter());
    sort(nums.begin(), nums.end());
    auto end_unique = unique(nums.begin(), nums.end());
    nums.erase(end_unique, nums.end());
    copy(nums.begin(),nums.end(), ostream_iterator<int>(cout, "\n"));
count(ivec.begin() , ivec.end() , searchValue)
bool greater10(int value)
 {
    return value >10;
 }
result1 = count_if(v1.begin(), v1.end(), greater10);
#include <iomanip>
cout<<setw(10)<<nums[i]<<endl;//指定宽度,一次性的
cout << setiosflags(ios_base::left)<<nums[i]//左对齐
cout <<resetiosflags(ios_base::left)//取消左对齐的方式,恢复到默认右对齐,
cout<< setprecision(1) << values[i] << endl;//设置有效数字位数

cout<<setiosflags(ios_base::fixd)<< setprecision(1) << values[i] << endl;//设置有效数字

将流写入二进制文件中:当待存文件无需供人阅读时可以选用二进制的这种方式,读入读出效率都非常高

#include <fstream>
using namespace std;
struct Date { 
    int mon, day, year;  
};
int main() {
    Date dt = { 6, 10, 92 };
    ofstream file("date.dat", ios_base::binary);
    file.write(reinterpret_cast<char *>(&dt),sizeof(dt));
    file.close();
    return 0;
}

字符串输出流:典型应用是将数值转换为字符串,对于自定义类型,则必须重载相应的操作符如<<来使用

//11_6.cpp
#include <iostream>
#include <sstream>
#include <string>
using namespace std;

//函数模板toString可以将各种支持“<<“插入符的类型的对象转换为字符串。

template <class T>
inline string toString(const T &v) {
    ostringstream os;   //创建字符串输出流
    os << v;        //将变量v的值写入字符串流
    return os.str();    //返回输出流生成的字符串
}

int main() {
    string str1 = toString(5);
    cout << str1 << endl;
    string str2 = toString(1.2);
    cout << str2 << endl;
    return 0;
}

例11-7 get函数应用举例
//11_7.cpp
#include <iostream>
using namespace std;
int main() {
    char ch;
    while ((ch = cin.get()) != EOF)//注意这里的EOF在unix下应该是crtl+Z那种东西
        cout.put(ch);
    return 0;
}
例11-8为输入流指定一个终止字符:
//11_8.cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
    string line;
    cout << "Type a line terminated by 't' " << endl; 
    getline(cin, line, 't');
    cout << line << endl;
    return 0;
}
例11-9 从文件读一个二进制记录到一个结构中
//11_9.cpp
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;

struct SalaryInfo {
    unsigned id;
    double salary;
}; 
int main() {
    SalaryInfo employee1 = { 600001, 8000 };
    ofstream os("payroll", ios_base::out | ios_base::binary);
    os.write(reinterpret_cast<char *>(&employee1), sizeof(employee1));
    os.close();
    ifstream is("payroll", ios_base::in | ios_base::binary);
    if (is) {
        SalaryInfo employee2;
        is.read(reinterpret_cast<char *>(&employee2), sizeof(employee2));
        cout << employee2.id << " " << employee2.salary << endl;
    } else {
        cout << "ERROR: Cannot open file 'payroll'." << endl;
    }
    is.close();
    return 0;
}
例11-10用seekg函数设置位置指针
//11_10.cpp, 头部分省略
int main() {
    int values[] = { 3, 7, 0, 5, 4 };
    ofstream os("integers", ios_base::out | ios_base::binary);
    os.write(reinterpret_cast<char *>(values), sizeof(values));
    os.close();

    ifstream is("integers", ios_base::in | ios_base::binary);
    if (is) {
        is.seekg(3 * sizeof(int));
        int v;
        is.read(reinterpret_cast<char *>(&v), sizeof(int));
        cout << "The 4th integer in the file 'integers' is " << v << endl;
    } else {
        cout << "ERROR: Cannot open file 'integers'." << endl;
    }
    return 0;
}
例11-11 读一个文件并显示出其中0元素的位置
//11_11.cpp, 头部分省略
int main() {
    ifstream file("integers", ios_base::in | ios_base::binary);
    if (file) {
        while (file) {//读到文件尾file为0
            streampos here = file.tellg();
            int v;
            file.read(reinterpret_cast<char *>(&v), sizeof(int));
            if (file && v == 0) 
            cout << "Position " << here << " is 0" << endl;
        }
    } else {
        cout << "ERROR: Cannot open file 'integers'." << endl;
    }
    file.close();
    return 0;
}

istringstream的使用

template <class T>
inline T fromString(const string &str) {
    istringstream is(str);  //创建字符串输入流
    T v;
    is >> v;    //从字符串输入流中读取变量v
    return v;   //返回变量v
}

int main() {
    int v1 = fromString<int>("5");
    cout << v1 << endl;
    double v2 = fromString<double>("1.2");
    cout << v2 << endl;
    return 0;
}
输出结果:
5
1.2

d=floor(d*100)/100;//处理后d=12.34
//12_3.cpp
#include <iostream>
#include <cmath>
#include <stdexcept>
using namespace std;
//给出三角形三边长,计算三角形面积
double area(double a, double b, double c)  throw (invalid_argument)
{
   //判断三角形边长是否为正
    if (a <= 0 || b <= 0 || c <= 0)
        throw invalid_argument("the side length should be positive");
   //判断三边长是否满足三角不等式
    if (a + b <= c || b + c <= a || c + a <= b)
        throw invalid_argument("the side length should fit the triangle inequation");
   //由Heron公式计算三角形面积
    double s = (a + b + c) / 2; 
    return sqrt(s * (s - a) * (s - b) * (s - c));
}
int main() {
    double a, b, c; //三角形三边长
    cout << "Please input the side lengths of a triangle: ";
    cin >> a >> b >> c;
    try {
        double s = area(a, b, c);   //尝试计算三角形面积
        cout << "Area: " << s << endl;
    } catch (exception &e) {
        cout << "Error: " << e.what() << endl;
    }
    return 0;
}
上一篇 下一篇

猜你喜欢

热点阅读