前言
随着C++的学习,讲了C++的发展过程、流插入、流提取、函数缺省值、类与构造等等。接下来学习C++很方便的 玩意,函数模版。函数模版就像是模具一样,C++会自动用模版编译出合适的函数供程序员使用。以前不同类型相同操作的函数都能通过函数模版,只写一个来解决。这么说来,怪不得祖师爷会写出函数重载这样的优化。因为如此,函数模版大有作用。
在此之前,上一节中提到的新增操作符new和delete。这两个操作符也是在堆上申请空间操作符,这一节会回顾一下,并进行补充。
一、new和delete
1、简介
new和delete是C++中新增的关键字,new用来申请堆上的空间,delete用来释放堆上的空间。在C语言中我们学过了3个函数,分别是“malloc”“ralloc”“free”,前两个用来申请空间,free用来释放空间。
既然C语言中已经有了申请空间的函数,为什么C++中又单独开发出来一套系统来申请空间呢?接下来就来聊聊他们之间的区别吧。
2、malloc/free与new/delete的区别:
(1)malloc/free是函数,new/delete是操作符。
(2)malloc申请空间就只是申请空间不会初始化,new会申请空间并初始化空间。
(3)malloc需要手动计算申请空间的大小,new能够通过类型自动计算需要申请大小。
(4)malloc申请空间失败会返回NULL,new申请空间失败不会返回NULL但是需要捕获异常(这里需要用到try/catch)。
(5)malloc只是开空间,free只会释放空间、new会调用构造函数初始化空间,delete会调用析构函数释放空间。
3、实际举例
如果正常的使用new和delete,也就是说配套使用,方法如下:
#include <iostream>using namespace std;class A
{
public:A(int a = 0):_a(a){cout << "A(int a = 0)->" << _a <<endl;}~A(){cout << "~A()->" << _a << endl;}
private:int _a;
};int main()
{A* exp = new A;delete exp;exp = new A[4]{1,2,3,4};delete[] exp;exp = nullptr;return 0;
}
如举例所示,在new后面接上“类型”就能够直接开辟一个类的空间,释放空间接delete和它的指针。如果需要开辟多个空间就像数组一样在new后面接上“类型[]”,释放空间就需要接“delete[]”和它的指针。同时如果想修改初始化中的成员可以增加“{}”,里面写变量。那么调用构造的时候会直接使用。
但是为什么不直接用“delete”而是需要增加“delete[]”?这是因为用new申请多个空间的时候会在所开空间前额外开4个字节的空间用来记录调用构造和析构函数的次数,方便处理。这个时候使用delete会出错,程序会直接结束。因为多开了4字节。到了现在C++的进步,如果不写析构函数,那么优化做的比较多的编译器就不会多生成这4个字节“delete”也能够使用。
如果将类“A”的析构函数去掉,那么使用“delete”释放空间也是可行的。例如:
class A
{
public:A(int a = 0):_a(a){cout << "A(int a = 0)->" << _a <<endl;}private:int _a;
};
另外,malloc/free也可以和new/delete混用,但是不会调用构造和析构函数,如果类里面有申请空间,就不能使用例如:
class B
{
public:B(){_b = new int(0);}~B(){delete _b;}
private:int* _b;
};
这是因为malloc/free不会调用析构和构造去释放申请的空间,造成内存泄漏,所以最好将new和delete配套使用。
4、练习题举例
求1+2+3+...+n_牛客题霸_牛客网求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、swit。题目来自【牛客题霸】https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
描述
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
数据范围: 0<𝑛≤2000<n≤200
进阶: 空间复杂度 𝑂(1)O(1) ,时间复杂度 𝑂(𝑛)O(n)
这道题就能用到我们类的算法,建立全局变量然后通过类的创建来修改它,只需要用new创建n个类的空间,然后自动调用构造函数进行累加,这样就能计算出和:
class Sum
{
public:Sum(){i++;ret += i;}static void Reset(){i = 0;ret = 0;}static int i;static int ret;
};int Sum::i = 0;
int Sum::ret = 0;class Solution {
public:int Sum_Solution(int n) {Sum::Reset();Sum* ps = new Sum[n];delete[] ps;return Sum::ret;}
};
二、模版
1、函数模版
1.1、简介
函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。
它的出现虽然对于编译器来说增加了开销,但是却节省了程序员的开销,让程序员写一个功能就能一劳永逸。
1.2、举例使用
在讲到C语言函数的时候,我们学到的有加法函数,就是将两个数加起来,最后变成一个数返回。那么这里我们也用这个函数举例。
template<class T>
T Add(T& n1, T& n2)
{return n1 + n2;
}
这里的“tempelate”是C++中新增的关键字,这个关键字能够用来定义模版变量的类型。在上述函数中,“n1”和“n2”是同一种类型,他们都属于类型“T”。这里的“T”可以根据需要取名字,就和类一样。这个类型“class”也可以使用“typename”,所以这个函数用以下写法也是一样的:
template<typename T1>
T1 Add(T1& n1, T1& n2)
{return n1 + n2;
}
在函数的使用方面分为两种方法,第一种是推导实例化、另一种是显示实例化。接下来会在例子中主要说明两种实例化的不同操作方式。模版函数就采用上面写的“Add”函数:
#include <iostream>
using namespace std;template<class T>
T Add(T n1, T n2)
{return n1 + n2;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;// 推导实例化cout << Add(a1, a2) << endl;cout << Add(a1, (int)d1) << endl;cout << Add((double)a1, d1) << endl << endl;// 显示实例化cout << Add<int>(a1, d1) << endl;cout << Add<double>(a1, d1) << endl;return 0;
}
推导实例化就是模版的参数程序员不给出,由传入函数的参数以及编译器的推导决定。相反显示实例化就是程序员固定了模版的类型,计算机直接将这个类型交给模版就行了。注意返回值无法影响编译器的推导,只有输入的形参才行。上述代码的结果如下:
请注意,如果使用推导实例化就不能让编译器搞不清楚变量实例化成什么类型。如下传法都是错误的:
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;// 推导实例化cout << Add(a2, d2) << endl;cout << Add(a1, d1) << endl;cout << Add(a1, d2) << endl << endl;return 0;
}
编译器会觉得,到底是“int”还是“double”呢?就直接报错退出了。所以这里建议使用显示实例化,编译器也不含糊,不容易出错。
2、类模版
除了函数能够使用模版之外,类也能使用模版。他们的作用都是相同的,减少程序员的代码量。使用方法也类似。所以这里就直接举例了。
2.1、使用举例
比如说我们需要建立一个可以装不同数据的栈,那么我们也可以用到函数模版:
namespace lcs
{template<typename T>class Stack{public:Stack(int n = 4):_array(new T[n]),_capacity(n),_size(0){}~Stack(){delete[] _array;_array = nullptr;_size = _capacity = 0;}void push(const T& x){if(_size == _capacity){T* tmp = new T[_capacity * 2];memcpy(tmp, _array, sizeof(T) * _size);delete[] _array;_array = tmp;_capacity *= 2;}_array[_size++] = x;}void pop(){if(_size != 0){--_size;}}T top(){if(_size != 0){return _array[_size - 1];}else{return T(0);}}bool empety(){return !_size;}private:T* _array;size_t _capacity;size_t _size;};
}int main()
{lcs::Stack<int> st_int;lcs::Stack<double> st_double;st_int.push(1);st_int.push(2);st_int.push(3);st_int.push(4);cout << st_int.top() << endl;st_int.pop();st_int.pop();st_int.pop();cout << st_int.top() << endl;st_double.push(1.123);cout << st_double.top() << endl;return 0;
}
这样我们的栈也能够使用了,和函数不同的是类模板都需要显示实例化,请注意。
这里我也用到了命名空间,这样就不会和库里的函数重叠。运行后无误:
创建了两种栈“int”和“double”的,同时输入输出一些栈内元素,输出上和代码预期相同。首先入4个数到“int”栈,然后取栈顶元素输出。然后从“int”栈中删除3个再次输出栈顶元素。随后输入一个元素到“double”栈,然后输出它。
在模版之中的类型也可以是自定义类型,但相应的就需要写更多的代码来确保运行。
作者结语
到这里C++中的模版也就告一段落了,学习本节我觉得最大的作用就是为接下来学习stl标准库做准备,当然标准库的介绍分一节还讲不完。接下来可能会分多次博客进行细致讲解,或者偷个懒,全部写完总结之后写到一篇里一次发出来。又是一项大工程了,吐血。
要是博客也有个模版就好了,我也一套然后计算机帮我整理,哈。我愿称为计算机万能。接下来其实还挺简单的,虽然我还没学到但是我预习了。和这里模版类中自己写的类差不多的写法就行。之前也学过很多操作符重载,就是这些。然后就是通用的一些函数,包装一下就能直接用了。
除了string还要重新学习C++的顺序表和链表和二叉树和哈希表。我故意这么写的,主要是想起来还很多。干啦兄弟们!