C++并发编程学习日记 2025/4/24

C++并发编程学习日记 2025/4/24

今天主要巩固了C++模板的内容,包括函数模板、类模板、运算符重载和迭代器的应用。

模板

函数模板

编译器在代码执行到模板函数或者模板类的调用点时,会根据用户指定的类型将模板实例化成一个真正的模板函数。函数模板一般__定义__在头文件当中,在源文件中调用。

函数模板会有实参推导,即根据用户给定的实参类型来推导出模板的类型,这样就不需要明确指定模板的类型。

函数模板本身不能被初始化,可以理解为函数模板本身是不完整的,不能生成一个可以被解析和重定位的符号。完整的符号名应当是模板名<用户指定的类型>。注意C++的完整符号名是函数名及其形参列表,这是与C不同之处。

特殊的,如果某种类型的实参需要的操作与其他类型不同,就需要用户提供这种类型的特化版本。

下面用代码示例加深对函数模板的理解:

template<typename T>
bool compare(T a, T b) {
    std::cout << "编译器特化为:" <<typeid(T).name() << std::endl;
    return a > b;
}
template<>
bool compare(const char* a, const char* b) {
    std::cout << "特化版本" << typeid(a).name()<<std::endl;
    return strcmp(a, b);
}
int main()
{
    int a = 10, b = 20;
    bool flag1 = compare(a, b);
    bool flag2 = compare<int>(a, b);

    bool flag3 = compare("aaa", "bbb");//等同于compare<const char*>("aaa","bbb");
    bool flag4 = compare<const char*>("aaa", "bbb");

    std::cout << flag1 <<" " << flag2 << " " << flag3 << " " << flag4 << std::endl;

    return 0;
}
/*输出结果为:
编译器特化为:int
编译器特化为:int
特化版本char const * __ptr64
特化版本char const * __ptr64
0 0 1 1
*/
类模板

类模板的注意事项比较少,用类模板实现了一个容器vector,代码示例如下:

template<typename T>
class vector {
public:
    vector(int size = 10){
        _first = new T[size];
        _last = _first;
        _end = _first + size;
    }
    ~vector() {
        delete[] _first;
        _first = _end = _last = nullptr;
    }
    vector(const vector& other) {
        int size = other._end - other._first;
        _first = new T[size];
        int length = other._last - other._first;
        for (int i = 0;i < length;i++) {
            _first[i] = other[i];
        }
        _last = _first + length;
        _end = _first + size;
    }
    vector<T>& operator=(const vector& other) {
        if (this == &other) return *this;
        delete[] _first;
        int size = other._end - other._first;
        _first = new T[size];
        int length = other._last - other._first;
        for (int i = 0;i < length;i++) {
            _first[i] = other[i];
        }
        _last = _first + length;
        _end = _first + size;
        return *this;
    }
    void push_back(T value) {
        if (full()) return;
        std::cout << value << std::endl;
        *_last = value;
        ++_last;
    }
    void pop_back() {
        if (empty()) expand();
        std::cout << *(_last - 1) << std::endl;
        --_last;
    }
    T back()const {
        return *(_last - 1);
    }//返回容器末尾的元素,只读操作,写成const
    bool empty()const {
        return _first == _last;
    }
    bool full() const {
        return _last == _end;
    }
    int size()const {
        return _last - _first;
    }
    void show()const {
        for (int i = 0;i < size();i++) {
            std::cout << *(_first+i)<< std::endl;
        }
    }
private:
    T* _first;//数组第一个元素
    T* _last;//数组最后一个元素
    T* _end;//数组后继空间
    void expand() {
        int size = _last - _first;
        T* tmp = new T[size * 2];
        for (int i = 0;i < size;i++) {
            tmp[i] = *(_first + i);
        }
        delete[] _first;
        _first = tmp;
        _last = _first + size;
        _end = _first + size * 2;
    }
};
空间配置器

上面写的容器vector是不完善的,他没有空间配置器allocator。下面为这个容器加一个空间配置器。下面用代码示例理解一下空间配置器:

/*
* 用类模板实现一个空间配置器
*/
template<typename T>
class Allocator {
public:
    T* allocator(size_t size) {
        return (T*)malloc(sizeof(T) * size);
    }//负责内存开辟
    void deallocator(void* p) {
        free(p);
    }
    void construct(T* p.const T& value) {
        //定位new,在指定的内存空间上构造对象
        new(p) T(value);
    }
    void deconstructor(T* p) {
        p->~T();
    }
};//定义自己的空间配置器

空间配置器的作用是开辟空间/释放空间,构造对象/析构对象。空间配置器的实现用到了定位new和动态分配内存,动态分配内存是用C++的库函数malloc/free这样就只分配内存而不构造对象。

空间配置器的使用:构造一个新容器时,先开辟空间allocator,再用迭代器(比如指针)遍历构造元素constructor;销毁一个容器时,先用迭代器遍历析构 元素deconstuctor,再释放容器内存deallocator

使用空间配置器重新写一个vector容器,代码示例如下:

template<typename T,typename Allocator=Allocator<T>>
class vector {
//默认给一个空间配置器,不需要用户自行指定。
public:
    vector(int size = 10){
        //_first = new T[size];
        _first = alloc.allocator(size);//将内存开辟封装起来
        _last = _first;
        _end = _first + size;
    }
    ~vector() {
        //delete[] _first;
        for (T* p = _first;p != _last;p++) {
            alloc.deconstructor(p);//对数组中的有效元素执行析构操作
        }
        alloc.deallocator(_first);
        _first = _end = _last = nullptr;
    }
    vector(const vector& other) {
        int size = other._end - other._first;
        //_first = new T[size];
        _first = alloc.allocator(size);//将内存开辟封装起来
        int length = other._last - other._first;
        for (int i = 0;i < length;i++) {
            //_first[i] = other[i];
            alloc.constrcut(_first + i, other._first[i]);//对每一个数组元素构造操作
        }
        _last = _first + length;
        _end = _first + size;
    }
    vector<T>& operator=(const vector& other) {
        if (this == &other) return *this;
        //delete[] _first;
        for (T* p = _first;p != _last;p++) {
            alloc.deconstructor(p);//先析构
        }
        alloc.deallocator(_first);//再释放空间
        int size = other._end - other._first;
        //_first = new T[size];
        _first = alloc.allocator(size);//将内存开辟封装起来
        int length = other._last - other._first;
        for (int i = 0;i < length;i++) {
           // _first[i] = other[i];
            alloc.constrcut(_first + i, other._first[i]);//对每一个数组元素构造操作
        }
        _last = _first + length;
        _end = _first + size;
        return *this;
    }
    void push_back(T value) {
        if (full()) return;
        std::cout << value << std::endl;
        alloc.construct(_last, value);
        ++_last;
    }
    void pop_back() {
        if (empty()) expand();
        std::cout << *(_last - 1) << std::endl;
        //--_last;
        --_last;
        alloc.deconstructor(_last);
    }
    T back()const {
        return *(_last - 1);
    }//返回容器末尾的元素,只读操作,写成const
    bool empty()const {
        return _first == _last;
    }
    bool full() const {
        return _last == _end;
    }
    int size()const {
        return _last - _first;
    }
    void show()const {
        for (int i = 0;i < size();i++) {
            std::cout << *(_first+i)<< std::endl;
        }
    }
private:
    T* _first;//数组第一个元素
    T* _last;//数组最后一个元素
    T* _end;//数组后继空间
    Allocator alloc;
    void expand() {
        int size = _last - _first;
       // T* tmp = new T[size * 2];
        T* tmp = alloc.allocator(size * 2);
        for (int i = 0;i < size;i++) {
            alloc.construct(tmp + i, _first[i]);
        }
        for (T* p = _first;p != _last;p++) {
            alloc.deconstructor(p);
        }
        //delete[] _first;
        alloc.deallocator(_first);
        _first = tmp;
        _last = _first + size;
        _end = _first + size * 2;
    }
};
运算符重载

这一节通过编写一个复数类CComplex来理解运算符重载。运算符重载的目的是使得自定义的对象和编译器内置类型一样表现。

编译器在处理对象运算时,先调用重载的运算符(优先调用成员函数),如果没有成员方法就从全局作用域找重载的运算符。主要注意单目运算符的重载,单目运算符如自加自减需要区分先还是后,输入输出运算符则可以封装一个show()函数。代码示例如下:

class CComplex {
public:
    CComplex(int r = 0, int i = 0) :real(r), image(i) {
        std::cout << "构造" << std::endl;
        std::cout << real << " " << image << std::endl;
    }
    CComplex(const CComplex& other) {
        std::cout << "拷贝" << std::endl;
        real = other.real;
        image = other.image;
    }
    CComplex& operator=(const CComplex& src) {
        std::cout << "赋值" << std::endl;
        if (this == &src) return *this;
        real = src.real;
        image = src.image;
        return *this;
    }
    
    void show() const{
        std::cout <<real<< " + " << image << "i" << std::endl;
    }
    //重载单目运算符自加,自加需要区分先加还是后加
    CComplex operator++(int) {
        std::cout << "后加" << std::endl;
        return CComplex(++this->real, ++this->image);
    }
    CComplex& operator++() {
        std::cout << "先加" << std::endl;
        real += 1;
        image += 1;
        return *this;
    }
    void operator+=(const CComplex& src) {
        real = real + src.real;
        image = image + src.image;
    }

    friend CComplex operator+(const CComplex& pre, const CComplex& src);
    friend std::ostream& operator<<(std::ostream& o, const CComplex& src);
    friend std::istream& operator>>(std::istream& i, CComplex& src);
private:
    int real;
    int image;
};
CComplex operator+(const CComplex& pre, const CComplex& src) {
    std::cout << "加法" << std::endl;
    return CComplex(pre.real + src.real, pre.image + src.image);
}//最好写这种方式
std::ostream& operator<<(std::ostream& o, const CComplex& src) {
    o << src.real << " + " << src.image << "i" << std::endl;
    return o;
}
std::istream& operator>>(std::istream& i, CComplex& src) {
    i >> src.real >> src.image;
    return i;
}
int main()
{
    CComplex c1(1, 2);
    c1.show();
    CComplex c2(2, 3);
    c2.show();
    CComplex c3 = c1 + c2;
    CComplex c4 = 30 + c3;
    
    c3.show();
    c4.show();
    std::cout << "===============" << std::endl;
    CComplex c5 = ++c3;
    c5.show();
    std::cout << "===============" << std::endl;
    CComplex c6 = c3++;
    c6.show();
    std::cout << "===============" << std::endl;
    CComplex c7(0, 0);
    c7 += c3;
    c7.show();
    std::cout << "===============" << std::endl;
    std::cout << c7;


    return 0;

/*
构造
1 2
1 + 2i
构造
2 3
2 + 3i
加法
构造
3 5
构造
30 0
加法
构造
33 5
3 + 5i
33 + 5i
===============
先加
拷贝
4 + 6i
===============
后加
构造
5 7
5 + 7i
===============
构造
0 0
5 + 7i
===============
5 + 7i
*/
}

迭代器

迭代器支持透明的访问容器内部元素,而不需要了解容器底层成员的数据结构。写法一般是在容器里定义一个内部类,还需要在容器类中添加begin()end()方法。编写一个Cstring类来实现以下迭代器对容器元素的遍历,代码示例如下:

class Cstring {
public:
    Cstring(const char* p=nullptr) {
        std::cout << "construct" << std::endl;
        if (p != nullptr) {
            str = new char[strlen(p)+1];
            strcpy(str, p);
        }
        else {
            str = new char[1];
            str[0] = '\0';
        }
    }
    ~Cstring() {
        std::cout << "delete" << std::endl;
        delete[] str;
        str = nullptr;
    }
    Cstring(const Cstring& src) {
        std::cout << "copy" << std::endl;
        str = new char[strlen(src.str) + 1];
        strcpy(str, src.str);
    }
    Cstring& operator=(const Cstring& src) {
        std::cout << "=" << std::endl;
        if (this == &src) return *this;
        delete[] str;//防止浅拷贝
        this->str = new char[strlen(src.str) + 1];
        strcpy(str, src.str);
        return *this;
    }
    bool operator<(const Cstring& rhs) {
        return !strcmp(this->str, rhs.str);
    }
    bool operator>(const Cstring& rhs) {
        return strcmp(this->str, rhs.str);
    }
    int length()const {
        return strlen(str);
    }
    char& operator[](int index) {
        return str[index];
    }//可修改
    void show() {
        std::cout << str << std::endl;
    }
    
    friend std::ostream& operator<<(std::ostream& o, const Cstring& src);
    friend Cstring operator+(const Cstring& lhs, const Cstring& rhs);
    class iterator {
    public:
        iterator(char* ptr = nullptr) :p(ptr) {}
        bool operator!=(const iterator& src) {
            return p != src.p;
        }
        void operator++() {
            p++;
        }
        char& operator*() {
            return *p;
        }
        void operator++(int) {
            p++;
        }

    private:
        char* p;
    };
    iterator begin() { return iterator(str); }
    iterator end() { return iterator(str + length()); }
private:
    char* str;
};
std::ostream& operator<<(std::ostream& o, const Cstring& src) {
    o << src.str;
    return o;
}
Cstring operator+(const Cstring& lhs, const Cstring& rhs) {
    Cstring tmp;
    tmp.str = new char[strlen(lhs.str) + strlen(rhs.str) + 1];
    strcpy(tmp.str, lhs.str);
    strcat(tmp.str, rhs.str);
    return tmp;
}

foreach的底层实现是通过容器的迭代器来实现容器的遍历。对于连续内存的容器来说,还可以通过索引来随机访问数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值