1.介绍new与delete
1.malloc和free是函数,new和delete是操作符
2.malloc申请的空间不会初始化,new可以初始化
3.malloc申请空间失败时,返回的是NULL,因此必须判空,new不需要,但是new需要捕获异常
4.申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的释放和清理
5.malloc申请空间时,需要手动计算空间大小并传递,new只需要在其后跟空间类型就可以,如果申请是多个对象,[]中指定对象个数
2.模板介绍
cpp
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
如果是实现交换函数的话,写这么多函数重载,但其实这些函数的区别只有函数参数的类型不一样,代码代码复用率比较低,要是有新类型出现就需要用户再去写,所以可以写一个模板。
泛型编程:编写与类型无关的通用代码,是代码复写的手段。模板是泛型编程的基础
函数模板格式:
template<typename T1,typename T2,······,typename Tn>
注意:typename是用来定义模板参数关键字,也可以使用class
函数模板是一个蓝图,它本身不是函数,是编译器使用方式产生特定类型函数的模具,所以其实模版就是将本来我们应该做的事情交给编译器
在编译器阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对于类型的函数以供调用,比如:但double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后生成一份专门处理double类型的代码。
实例1:
cpp
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1;
int b = 2;
cout << a << " " << b << endl;
Swap(a, b);
cout << a << " " << b << endl;
return 0;
}
实例2:
cpp
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1;
int b = 2;
double c = 3.3;
double d = 4.4;
Swap(a, c);
return 0;
}
这样子会报错是因为,在编译期间,当编译器看到该实例化时,需要推演实参类型,通过实参a将T推演为int,通过实参c将T推演为double类型,当模板参数列表只有一个T,所以编译器不确定是哪一个。
处理1:通过强制转换
Swap(a,(int)c)
处理2:使用显示实例化
Swap<int>(a,c)
显式实例化就是告诉编译器T是int,不用编译器去推演T的类型
3.模板参数的匹配原则
1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
cpp
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
void Swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1;
int b = 2;
double c = 3.3;
double d = 4.4;
Swap(a, b);
return 0;
}
如果推演的与非模板一样则就会使用非模板函数
如果使用显示实例化就会用模板函数
对于非模板函数和同名函数模板,如果其它条件相同,在调用时会优先调用非模板函数而不会从该模板产生一个实例,如果模板可以产生一个匹配更好的函数才会调用模板
cpp
int Add(int a, int b)
{
return a + b;
}
template<typename T1,typename T2>
T1 Add(T1& a, T2& b)
{
return a + b;
}
int main()
{
int a = 1;
int b = 2;
double c = 3.3;
//Swap<int>(a, b);
Add(1, 2);
Add(a, c);
return 0;
}
非模板是俩个int相加,模板则是俩个可推演的类型,当参数不同为int时就会使用模板的而不是非模板。
模板参数兼容的类型有:
1.基本数据类型:如 int、double、char、bool 等。
2.自定义类和结构体:用户定义的类型,如 struct 或 class。
3.指针类型:如 int*、double* 等。
4.引用类型:如 int&、const double& 等。
5.标准库类型:如 std::vector<T>、std::string 等。
6.枚举类型:自定义的 enum 类型。
只要在调用模板时传入的类型与 T 兼容,编译器就能成功推导出模板参数。
类模板
类模板定义格式
template<class T1,class T2,······,class Tn>
class A
{
//内容
};
cpp
template<class T>
class zym
{
public:
zym(size_t capacity = 10)
:_Date(new T[capacity])
,_size(0)
,_capacity(capacity)
{}
~zym();
private:
T* _Date;
size_t _size;
size_t _capacity;
};
template<class T>
zym<T>::~zym()
{
//
}
int main()
{
zym<int> a;
return 0;
}
如果在类外定义析构还需要再写一次模板,告诉编译器这个函数是这个模板的析构函数
在 C++ 中,类的成员函数(包括析构函数)的定义需要在类外部进行时,必须明确指定模板参数。这是因为编译器需要知道该成员函数与哪个模板实例相关联。
原因
1.模板实例化:
当你在类内部声明一个模板成员函数时,编译器不会自动生成它的定义。你需要在外部明确告诉编译器该成员函数是为哪个特定类型的实例化而定义的。
2.编译器的工作方式:
编译器在处理模板时,会在需要时生成特定类型的代码。如果你在类外部定义析构函数而不指明模板参数,编译器无法知道该函数属于哪个具体的模板实例。
示例
template <class T>
class zym {
public:
zym(size_t capacity = 10);
~zym();
private:
T* _Date;
size_t _size;
size_t _capacity;
};
// 外部定义析构函数,必须指定模板参数
template <class T>
zym<T>::~zym() {
delete[] _Date; // 释放内存
}
总结
在外部重新写一次模板参数是为了清晰地告诉编译器这个成员函数属于哪个模板实例。这是 C++ 模板机制的一个基本要求。
4.模板实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后面跟<>,然后将实例化的类型放在<>中,类模板名字不是真正的类,实例化的结果才是真正的类
zym<int> s1;
//zym是类名,zym<int> 才是类
5.栈的实现
cpp
template<typename T>
class Stack
{
public:
Stack(int n = 4)
:_array(new T[n])
,_size(0)
,_capacity(n)
{}
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
void Push(const T& x);
private:
T* _array;
size_t _capacity;
size_t _size;
};
template<class T>
void Stack<T>::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;
}
int main()
{
// 类模板都是显示实例化
Stack<int> st1; // int
st1.Push(1);
st1.Push(2);
st1.Push(3);
Stack<double> st2; // double
st2.Push(1.1);
st2.Push(1.1);
st2.Push(1.1);
Stack<double>* pst = new Stack<double>;
//...
delete pst;
return 0;
}
首先pst指向的是堆区上的一个地址,内容是私有成员变量,私有成员变量_array有指向堆区上的一片空间,当delete pst时,会先调用Stack的析构函数释放_array指向的空间,然后再释放私有成员变量的空间。
6.string类的简单介绍
cpp
int main()
{
string s1;
string s2("hello world");
string s3(s2);
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
//cin >> s1;
//cout << s1 << endl;
string s4(s2, 6, 2);
cout <<"s4:"<< s4 << endl;
string s5(s2, 6);
cout << "s5:"<<s5 << endl;
string s6("hello world", 5);
cout << "s6:"<<s6 << endl;
string s7(10, 'X');
cout << "s7:"<<s7 << endl;
//s6[10];
s6[0] = 'x';
cout << s6 << endl;
return 0;
}
s1没有内容为空
s2参数为"hello world"
s3是拷贝了s2
s4是需要一个string类的实例,6是从第六个开始,2是取俩个字符停下(若超过能取的最长也只能取到最后一个停止)
s5是从第六个开始到最后一个取完
s6是取"hello world"的前五个
s7是把'X'重复十次来实例
s6有用了operator[]把索引为0的地方重新赋值为'x'