RaymondHuang
RaymondHuang
发布于 2022-12-13 / 78 阅读
0
0

模板(Template)

函数的模板:

通过函数的模板,我们可以实现一些骚操作,比如通过这个函数,可以实现任意对象的交换:

template<class T> // 定义下方函数为模板
void swap(T& a, T& b) { // 使用模板T
    T t = a; // 使用T定义变量
    a = b;
    b = t;
}

int main() {
    int a = 10;
    int b = 20;

    // my_swap(a, b); // 调用函数,传入两个int
    my_swap<int>(a, b); // 当然,通过尖括号可以指定传入的参数的类型是int
    cout << a << endl; // 20
    cout << b << endl; // 10

    return 0;
}

如果没有模板,那就需要写很多的函数重载才能实现上面的功能。

类的模板:

对于类模板,可以指定的模板是类的成员属性

template<class IdType, class AgeType> // 定义IdType和AgeType为模板
class Person {
public:
    // 使用模板作为类的属性成员
    IdType id;
    AgeType age;

    Person(IdType _id, AgeType _age) : id(_id), age(_age) {}
};

int main() {
    Person<char, int> p('1', 18); // 先定义两个模板真实的类型,然后再作类的初始化
    // Person<char, int> p = Person<char, int>('1', 18); // 这样的写法更加直观
    cout << p.id << ", " << p.age << endl; // 1, 18

    return 0;
}

通过上述例子,我们发现可以给类的成员去定义这个模板。

那么如果出现了继承有模板属性的父类的情况,应该怎么办呢?

template<class StuIdType> // 子类仍然可以定义模板属性
class Student : public Person<int, int> { // 在继承的时候就已经考虑了Person的两个模板的类型为int
public:
    StuIdType stu_id;
    
    // 传入的参数,由于Person的两个模板属性已经固定了,所以必然是int,但StuIdType没固定
    // 初始化列表的时候,stu_id可以直接初始化,但是Person需要以一种特殊的方式初始化
    // 注意不可以写成stu_id(_stu_id), id(_id), age(_age),因为Student没有id和age这两个属性!
    Student(StuIdType _stu_id, int _id, int _age) : stu_id(_stu_id), Person<int, int>(_id, _age) {}
};

int main() {
    Student<int> s(0, 18, 1); // 注意这里只需要指定1个模板的类型
    cout << s.id << ", " << s.age << ", " << s.stu_id << endl; // 0, 18, 1
    return 0;
}

其实,在继承的时候也可以不先定义两个父类的模板的类型,这种就更加自由一些:

template<class StuIdType, class IdType, class AgeType> // 同时定义三个模板
class Student :public Person<IdType, AgeType> { // 父类的属性也用模板来代替
public:
    StuIdType stu_id;

    Student(StuIdType _stu_id, IdType _id, AgeType _age) : stu_id(_stu_id), Person<IdType, AgeType>(_id, _age) {}
};

int main() {
    Student<int, int, int> s(0, 18, 1); // 注意需要指定3个模板的类型
    cout << s.id << ", " << s.age << ", " << s.stu_id << endl; // 0, 18, 1
    return 0;
}

C++标准库内的vector类:

vector是C++内置的一个有用的类,和string一样是自带的。vector其实是一个动态数组类,但是vector是一个模板类,后面尖括号传的参数代表了这个动态数组里面元素的属性

要使用vector类,需要在程序开头声明:

#include <vector>

接下来看如何使用vector这个类:

// 这个函数实现了打印一个vector数组内的所有元素的功能
void print(vector<int>& v) { // 首先传入一个vector
    // v.begin()和v.end()返回的是一个迭代器,这个迭代器是vector内定义的一个静态变量
    // 所以我们通过以下方式,就能拿到每一个元素的对象,注意是对象,不是地址!
    // 因此++i其实是vector重载了++运算符
    for (vector<int>::iterator i = v.begin(); i != v.end(); ++i) {
        cout << *i << ' '; // 同理,vector重载了*运算符
    }
    cout << endl;
    
    // 实际上,有更舒服更符合只觉得访问方式:
    for (int i = 0;i < v.size();i++) cout << v[i] << endl; // vector重载了[]运算符
}

int main() {
    vector<int> v;
    for (int i = 0;i < 8;i++) {
        v.push_back(i); // 尾插法插入元素
    }
    print(v);
}
// 这个函数展现了多个vector的构造器
void construct(vector<int>& v) {
    vector<int> v1(v.begin(), v.end()); // 有参构造函数,复制一份v给v1,从begin到end
    print(v1);

    vector<int> v2(8, 6); // 有参构造函数(重载后),初始化一个vector,数组内有8个6
    print(v2);

    vector<int> v3(v2); // 拷贝构造函数,把v2复制给v3
    print(v3);
}
// 下面这个函数展示了vector的多种赋值方式
void assign(vector<int>& v) {
    vector<int> v1 = v; // 直接拷贝构造函数
    print(v1);

    vector<int> v2; // 先默认构造
    v2.assign(v.begin(), v.end()); // 从头到尾赋值
    print(v2);

    vector<int> v3; // 先默认构造
    v3.assign(8, 6); // 直接给8个6
    print(v3);
}
void length(vector<int>& v) {
    cout << v.empty() << endl; // 数组是否为空
    cout << v.size() << endl; // 数组当前的元素个数
    cout << v.capacity() << endl; // 数组当前的容量

    // 通过下面的例子,我们可以知道vector的容量的运算方式
    // 每次内存不够用的时候,会变成原来capacity的1.5倍。
    for (int i = 0; i < 32; ++i) {
        v.push_back(i);
        cout << v.capacity() << ' '; // 最终是42
    }
    cout << endl;

    v.resize(8); // 直接截取前八个元素,后面的不要,但是容量不会改变。
    print(v);
    cout << v.size() << endl; // 8
    cout << v.capacity() << endl; // 42

    v.resize(12); // 把小的扩大,默认用0填充多出来的元素
    print(v);

    v.resize(16, 1); // 把小的扩大,用1来填充多出来的元素
    print(v);
}
// 下面这个函数展示了vector可以在任意位置插入或删除元素
void insert_delete(vector<int>& v) {
    v.pop_back(); // 直接删掉最后面的元素
    print(v);
    cout << v.capacity() << endl; // 但是容量不会改变,除非需要扩容

    v.insert(v.begin(), 10); // 在开头插入一个10
    print(v);

    v.insert(v.begin(), 2, 18); // 在开头插入两个18
    print(v);

    v.erase(v.begin()); // 删除最开头的元素
    print(v);
    cout << v.capacity() << endl; // 但是容量不会改变,除非需要扩容

    v.erase(v.begin() + 1, v.end()); // 从第二个元素到最后一个元素都删除
    print(v);
    cout << v.capacity() << endl; // 但是容量不会改变,除非需要扩容

    v.clear(); // 全部清空
    print(v);
    cout << v.capacity() << endl; // 但是容量不会改变,除非需要扩容
}
// 这个函数展示了访问vector内元素的多个方式
void access(vector<int>& v) {
    cout << v.at(2) << endl;
    cout << v[2] << endl; // vector重载了[]运算符,非常符合直觉
    cout << v.front() << endl; // 头元素
    cout << v.back() << endl; // 尾元素
}
// 这个函数展示了交换两个vector的方式
void swap(vector<int>& v) {
    vector<int> v1;
    print(v1);
    cout << v.capacity() << endl;
    cout << v1.capacity() << endl;

    cout << "After swap" << endl;
    v.swap(v1); // 让v和v1进行交换
    print(v1);
    cout << v.capacity() << endl;
    cout << v1.capacity() << endl;
}

但是,我们没有改变capacity的函数,但如果我原来有一个装着8个元素的数组,我把元素都删掉之后,虽然元素没了,但是占用的空间还是在的,为了删除这部分空间,我们可以用swap来实现:

void shrink(vector<int>& v) {
    cout << v.capacity() << endl;
    vector<int>().swap(v); // 初始化了一个空的vector然后和传进来的v进行交换
    cout << v.capacity() << endl; // 变成0了
}


评论