C++基础(一) —— 面向对象(3)


一些问题

C++模板是什么,底层怎么实现的?
(1) 编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

(2) 这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误。

一、函数模板

函数模板的作用 使函数适用于不同的输入类型

template <typename T>
T mymax(T a,T b)
{
	return a>b ? a:b;
}

void bidaxiao()
{
	int a , b;
	cout << "比大小,输入两个整数:" << endl;
	cin >> a >> b;
	cout << mymax(a,b) << endl;  

	float c , d;
	cout << "比大小,输入两个浮点数:" << endl;
	cin >> c >> d;
	cout << mymax(c,d) << endl;  //mymax函数又可以输入浮点数,又可以输入整数,达到了目的
}

函数模板特例化而生成的函数就是模板函数。

二、类模板

顺序栈

template<typename T=int>
class SeqStack // 模板名称+类型参数列表 = 类名称
{
public:
	// 构造和析构函数名不用加<T>,其它出现模板的地方都加上类型参数列表
	SeqStack(int size = 10)
		: _pstack(new T[size])
		, _top(0)
		, _size(size)
	{}
	~SeqStack()
	{
		delete[]_pstack;
		_pstack = nullptr;
	}
	SeqStack(const SeqStack<T> &stack)
		:_top(stack._top)
		,_size(stack._size)
	{
		_pstack = new T[_size];
		// 不要用memcopy进行拷贝
		for (int i = 0; i < _top; ++i)
		{
			_pstack[i] = stack._pstack[i];
		}
	}
	SeqStack<T>& operator=(const SeqStack<T> &stack)
	{
		if (this == &stack)
			return *this;

		delete[]_pstack;

		_top = stack._top;
		_size = stack._size;
		_pstack = new T[_size];
		// 不要用memcopy进行拷贝
		for (int i = 0; i < _top; ++i)
		{
			_pstack[i] = stack._pstack[i];
		}
		return *this;
	}

	void push(const T &val); // 入栈操作

	void pop() // 出栈操作
	{
		if (empty())
			return;
		--_top;
	}
	T top()const // 返回栈顶元素
	{
		if (empty())
			throw "stack is empty!"; // 抛异常也代表函数逻辑结束
		return _pstack[_top - 1];
	}
	bool full()const { return _top == _size; } // 栈满
	bool empty()const { return _top == 0; } // 栈空
private:
	T *_pstack;
	int _top;
	int _size;

	// 顺序栈底层数组按2倍的方式扩容
	void expand()
	{
		T *ptmp = new T[_size * 2];
		for (int i = 0; i < _top; ++i)
		{
			ptmp[i] = _pstack[i];
		}
		delete []_pstack;
		_pstack = ptmp;
		_size *= 2;
	}
};
template<typename T>
void SeqStack<T>::push(const T &val) // 入栈操作
{
	if (full())
		expand();
	_pstack[_top++] = val;
}
int main()
{
	// 类模板的选择性实例化
	// 模板类 class SeqStack<int>{};
	SeqStack<int> s1;
	s1.push(20);
	s1.push(78);
	s1.push(32);
	s1.push(15);
	s1.pop();
	cout << s1.top() << endl;

	SeqStack<> s2;

	return 0;
}

类模板强调模板,由此类模板生成的具体类,称之为模板类。

三、仿函数

3.1 仿函数简单的例子

不适用模板的仿函数

bool LessThan5Function(const string& str) {  //寻找长度小于5的字符串
	return str.length() < 5;
}

// 仿函数
class LessThanlenFunctor // functor 
{
public:
	bool operator ()(const string& str) const // 寻找长度小于 len 的字符串,重载了()运算符
	{  
		return str.length() < len;
	}
	LessThanlenFunctor(int l):len(l) {}    // 构造函数赋初值

private:
	int len; 
};

int main()
{ 
	vector<string> sVec{ "sadaw","srfafas","srfafas","12","srfafas" };
	int res  = count_if(sVec.begin(), sVec.end(), LessThan5Function);
	int resf = count_if(sVec.begin(), sVec.end(), LessThanlenFunctor(8));  
	// 	count_if 对sVec里面的所有字符串都输入函数中
	// 仿函数的目的,重载运算符(),使得其可以作为函数参数输入
	bool flag = LessThanlenFunctor(8)("dasd");   // 正常的调用方式
	cout << flag << endl;
	cout << res << endl;
	cout << resf << endl; //小于8个字符的字符串有5个
}

3.2 使用ceres库模拟高斯牛顿法拟合直线

/home/qqstar/桌面/slambook2-master/ch6/ceresCurveFitting.cpp

//代价函数的计算模型 残差 误差
struct qq 
{
  qq(double x, double y) : _x(x), _y(y) {}

  // 残差的计算                                 
  template<typename T>   //固定模板,T是一个类型,适用于各种类型参数的输入
  bool operator()( const T *const qwe, T *residualsssss) const  // abc模型参数,有3维 residual为残差和
  //这个里面随便取名字
  //const放前面表示这个函数的返回值是不可修改的,放后面表示这个函数不修改当前对象的成员。
  {
    residualsssss[0] = T(_y) - ceres::exp(qwe[0] * T(_x) * T(_x) + qwe[1] * T(_x) + qwe[2]); // y-exp(ax^2+bx+c)
    // e 误差项
    return true;
  }

  const double _x, _y;    // x,y数据
};

int main(){
  ..............
  // 构建最小二乘问题
  ceres::Problem problem;
  for (int i = 0; i < N; i++) //添加残差块需要将所有数据都用for添加
  {
    ceres::CostFunction *cost_function = new ceres::AutoDiffCostFunction<qq, 1, 3>( new qq(x_data[i], y_data[i]) );  
                                                      //  <结构体, 残差个数, 优化变量>(new qq(x量测, y量测));
    // AutoDIffCostFunction->使用自动求导,模板参数:误差类型,输出维度,输入维度,维数要与前面struct中一致
    problem.AddResidualBlock( cost_function, nullptr, abc );  //这个abc必须和 qwe 的类型一致 在这里使用了重载的()运算符
	/* 换一种写法 */
    // ceres::CostFunction *cost_function = 
    //   new ceres::AutoDiffCostFunction<qq, 1, 1, 1, 1>( new qq(x_data[i], y_data[i]) );  // <结构体, 残差个数, 优化变量>
    // problem.AddResidualBlock   // 向问题中添加误差项
    // (
    //   cost_function,
    //   nullptr,            // 核函数,这里不使用,为空
    //   &ae,
    //   &be,
    //   &ce                 // 参数块,待优化变量  输入为指针
    // );
  }
  ceres::Solve(options, &problem, &summary);  // 开始优化
}

四、手写vector

在C++标准库中,std::allocator 是容器类(如std::vector、std::list等)的默认内存分配器

std::allocator 是一个模板类,位于 头文件中。它用于在堆上分配和释放内存,用于动态地分配和构造对象。

// 定义容器的空间配置器,和C++标准库的 allocator 实现一样
template<typename T>
struct Allocator
{
    T* allocate(size_t size) // 负责内存开辟
    {
        return (T*)malloc(sizeof(T) * size);
    }
    void deallocate(void *p) // 负责内存释放
    {
        free(p);
    }
    void construct(T *p, const T &val) // 负责对象构造
    {
        new (p) T(val); // 定位new
    }
    void destroy(T *p) // 负责对象析构
    {
        p->~T(); // ~T()代表了T类型的析构函数
    }
};

/*
容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间配置器来实现
*/
template<typename T, typename Alloc = Allocator<T>>
class Myvector
{
public:

    Myvector(int size = 10)
    {
        // 需要把内存开辟和对象构造分开处理
        //_first = new T[size];
        _first = _allocator.allocate(size);
        _last = _first;
        _end = _first + size;
    }

    ~Myvector()
    {
        // 析构容器有效的元素,然后释放_first指针指向的堆内存
        // delete[]_first;
        for (T *p = _first; p != _last; ++p)
        {
            _allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作
        }
        _allocator.deallocate(_first); // 释放堆上的数组内存
        _first = _last = _end = nullptr;
    }

    Myvector(const Myvector<T> &rhs)
    {
        int size = rhs._end - rhs._first;
        //_first = new T[size];
        _first = _allocator.allocate(size);
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; ++i)
        {
            //_first[i] = rhs._first[i];
            _allocator.construct(_first+i, rhs._first[i]);
        }
        _last = _first + len;
        _end = _first + size;
    }

    Myvector<T>& operator=(const Myvector<T> &rhs)
    {
        if (this == &rhs)
            return *this;

        //delete[]_first;
        for (T *p = _first; p != _last; ++p)
        {
            _allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作
        }
        _allocator.deallocate(_first);

        int size = rhs._end - rhs._first;
        //_first = new T[size];
        _first = _allocator.allocate(size);
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; ++i)
        {
            //_first[i] = rhs._first[i];
            _allocator.construct(_first + i, rhs._first[i]);
        }
        _last = _first + len;
        _end = _first + size;
        return *this;
    }

    void push_back(const T &val) // 向容器末尾添加元素
    {
        if (full())
            expand();
        //*_last++ = val;   _last指针指向的内存构造一个值为val的对象
        _allocator.construct(_last, val);
        _last++;
    }

    void pop_back() // 从容器末尾删除元素
    {
        if (empty())
            return;
        //--_last; // 不仅要把_last指针--,还需要析构删除的元素
        --_last;
        _allocator.destroy(_last);
    }

    T back()const // 返回容器末尾的元素的值
    {
        return *(_last - 1);
    }

    bool full()const { return _last == _end; }
    bool empty()const { return _first == _last; }
    int size()const { return _last - _first; }

private:
    T *_first; // 指向数组起始的位置
    T *_last;  // 指向数组中有效元素的后继位置
    T *_end;   // 指向数组空间的后继位置
    Alloc _allocator; // 定义容器的空间配置器对象

    void expand() // 容器的二倍扩容
    {
        int size = _end - _first;
        //T *ptmp = new T[2 * size];
        T *ptmp = _allocator.allocate(2*size);
        for (int i = 0; i < size; ++i)
        {
            //ptmp[i] = _first[i];
            _allocator.construct(ptmp+i, _first[i]);
        }
        //delete[]_first;
        for (T *p = _first; p != _last; ++p)
        {
            _allocator.destroy(p);
        }
        _allocator.deallocate(_first);
        _first = ptmp;
        _last = _first + size;
        _end = _first + 2 * size;
    }

};

class Test
{
public:
    Test() { cout << "Test()" << endl; }
    Test(const Test&) { cout << "Test(const Test&)" << endl; }

    ~Test() { cout << "~Test()" << endl; }
};

int main()
{
    Test t1, t2, t3;
    cout << "-------------------" << endl;

    Myvector<Test> vec;
    vec.push_back(t1);
    vec.push_back(t2);
    vec.push_back(t3);
    cout << "-------------------" << endl;

    vec.pop_back(); // 只需要析构对象。 要把对象的析构和内存释放分离开  delete
    cout << "-------------------" << endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋雨qy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值