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的底层实现是通过容器的迭代器来实现容器的遍历。对于连续内存的容器来说,还可以通过索引来随机访问数组。