一、模板 (template)
1.1、模板函数
1> 语法格式:
泛型编程
1. 函数不用确定其处理的数据类型
2. 把数据类型作为参数传递
3. 泛型编程的语法规则
template 的含义是开始泛型编程
< typename T >的含义是声明数据类型叫T
template <typename 模板类型1, typename 模板类型2, ...>
返回类型 函数名(形参表){
函数体; // 函数中的类型可以使用模板类型替换
}
2> 模板函数的调用
函数名<模板实参类型表>(实参表);
顺口溜:尖找尖,圆找圆。
1.2、模板类
1> 语法格式
template <typename 模板类型1, ...>
class 类名:继承表{
成员变量;
成员函数;
};
2> 模板类的使用
类名<模板实参类型1,...> 对象名(实参表);
类名<模板实参类型1,...> 对象名 = 类名<模板实参类型1,...>(实参表);
类名<模板实参类型1,...> *对象指针 = new 类名<模板实参类型1,...>(实参表);
类名<模板实参类型1,...> &引用名 = 引用的目标;
3> 编译器编译模板函数/模板类的实现的机制?
采用是二次编译或者延时编译
当编译器第一次“看到”模板函数定义时,由于模板函数的参数尚不明确,编译器只做与类型无关的一般性检查,如果检查编译无误则生成模板的内部表现形式。
当编译器第二次“看到”模板函数被调用时,再次使用模板函数调用时传递的类型实参,结合模板函数的内部表现形式,做与类型相关的语法检查,如果无误则生成具体的二次编译的代码。
4> C 模板
#include <stdio.h>
#if 0
int add_int(int x, int y)
{
return x > y ? x : y;
}
float add_int(float x, float y)
{
return x > y ? x : y;
}
#endif
// 宏定义中不允许出现换行,如果右换行需要加续行符"\"
// ## : 拼接
// int max_int(int x, int y){}
#define MAX(T) T max_##T(T x, T y) \
{ \
return x > y ? x : y; \
}
MAX(int)
MAX(short)
MAX(double)
#define max(T) max_##T
// void min_int(int *min, int x, int y){}
#define MIN(T0, T1, T2) T0 min_##T2(T1 min, T2 x, T2 y) \
{ \
*min = x > y ? y : x; \
}
MIN(void, int *, int)
MIN(void, double *, double)
#define min(T) min_##T
int main(int argc, const char *argv[])
{
int a = 100, b = 200;
printf("max_int = %d\n", max_int(a, b));
int min;
min_int(&min, a, b);
printf("min_int = %d\n", min);
double d1 = 3.14, d2 = 5.67;
printf("max_double = %lf\n", max_double(d1,d2));
double mind;
min(double)(&mind, d1, d2);
short s1 = 20, s2 = 6;
//max_short(s1,s2)
printf("max_short = %d\n", max(short)(s1,s2));
return 0;
}
// 结果:
max_int = 200
min_int = 100
max_double = 5.670000
max_short = 20
#include <stdio.h>
// 宏定义的最后一个表达是就是宏定义的返回值
#define MAX(T, a, b) ({T tmp,retVal; if (a > b) tmp = a; else tmp = b; retVal = tmp;})
#define MIN(a, b) ({a>b ? b : a;})
int main(int argc, const char *argv[])
{
printf("max = %d\n", MAX(int, 100,200));
printf("min = %d\n", MIN(100,200));
return 0;
}
5> C++ 模板
#include <iostream>
using namespace std;
// 交换两个变量的值
template <typename A>
void Swap(A& a, A& b){
A tmp;
tmp = a;
a = b;
b = tmp;
}
int main(int argc, const char *argv[])
{
int x = 100, y = 200;
Swap<int>(x, y);
cout << "x = " << x << ", y = " << y << endl;
string str1 = "zhou";
string str2 = "kai";
Swap<string> (str1, str2);
cout << str1 << " " << str2 << endl;
return 0;
}
// 结果:
x = 200, y = 100
kai zhou
#include <iostream>
using namespace std;
template <typename A>
class Shape{
public:
Shape(A x, A y):m_x(x),m_y(y){}
~Shape(void){}
void Painter(void){
cout << "坐标" << m_x << ":" << m_y << endl;
}
private:
A m_x;
A m_y;
};
int main(int argc, const char *argv[])
{
Shape <int> S1(10,20);
S1.Painter();
Shape <float> *S2 = new Shape<float>(11.1, 12.1);
S2->Painter();
Shape <double> S3 = Shape<double>(111.11,222.22);
Shape <double> &S4 = S3;
S4.Painter();
return 0;
}
//结果:
坐标10:20
坐标11.1:12.1
坐标111.11:222.22
#include <iostream>
using namespace std;
template <typename A>
class Shape{
public:
Shape(A x, A y):m_x(x),m_y(y){}
virtual ~Shape(void){}
virtual void Painter(void){
cout << "坐标" << m_x << ":" << m_y << endl;
}
private:
A m_x;
A m_y;
};
template <typename A, typename B>
class Rect:public Shape<A>{
public:
Rect(A x, A y, B w, B h):Shape<A>(x, y),m_w(w),m_h(h){}
~Rect(void){}
void Painter(void){
Shape<A>::Painter();
cout << "宽高:" << m_w<< ":" << m_h << endl;
}
private:
B m_w;
B m_h;
};
template <typename A, typename B>
class Circle:public Shape<A>{
public:
Circle(A x, A y, B r):Shape<A>(x,y),m_r(r){}
virtual ~Circle(){}
virtual void Painter(void){
Shape<A>::Painter();
cout << "半径:" << m_r << endl;
}
private:
B m_r;
};
int main(int argc, const char *argv[])
{
Shape<int> *S1 = new Rect<int, float>(1,2,3.4,5.6);
S1->Painter();
Circle <int, double> C1(5,6,3.14);
Shape<int> &S2 = C1;
S2.Painter();
return 0;
}
//结果:
坐标1:2
宽高:3.4:5.6
坐标5:6
半径:3.14
笔试题:编译器编译模板函数/模板类的实现机制?
采用是二次编译或者延时编译
当编译器第一次“看到”模板函数定义时,由于模板函数的参数尚不明确,编译器只做与类型无关的一般性检查,如果检查编译无误则生成模板的内部表现形式。
当编译器第二次“看到”模板函数被调用时,再次使用模板函数调用时传递的类型实参,结合模板函数的内部表现形式,做与类型相关的语法检查,如果无误则生成具体的二次编译的代码。
(及具体函数的代码)。
二、基于模板C++实现的标准模板库
STL(Standed Template Library)
2.1、容器、迭代器、泛型算法
容器:模板化的数据结构
泛型算法:模块化的算法
迭代器:为不同类型的容器,提供了
相同的数据访问的接口函数。
2.2、容器 (模板类)
vector (向量)
list(列表)
2.3、迭代器
1> 根据迭代器的特性:单向迭代器和双向迭代器
2> 根据迭代器的方向:正向迭代器和反向迭代器
3> 根据迭代器的运算:顺序迭代器和随机迭代器
4> 根据对目标的访问:可写迭代器和只读迭代器
每一种容器都会以嵌套的形式提供至少四种迭代器
iterator - 正向可写迭代器
const_iterator - 正向只读迭代器
reverse_iterator - 反向可写迭代器
const_reverse_iterator - 反向只读迭代器
2.4、泛型算法
排序,查找,组合
三、vector(向量)
3.1、特点
连续的内存空间,通过下标访问
动态内存管理
3.2、实例化对象
#include <vector>
using namespace std;
A.vector<元素类型> 向量对象; // 空向量
vector<int> vi;
cout << vi.size () << endl; // 0
cout << sizeof (vi) << endl; // 12
容器大小:元素个数,size()
容器对象大小:容器本身的字节数,sizeof()
B.vector<元素类型> 向量对象 (初始大小); // 非空向量
vector<int> vi (3);
基本类型:初始化为"零"
类类型:用缺省构造函数初始化
C.vector<元素类型> 向量对象 (初始大小, 初始值);
vector<int> vi (3, 5); // 5 5 5
vector<Student> vs (3, Student ("张飞", 22));
D.vector<元素类型> 向量对象 (起始迭代器, 终止迭代器);
int ai[5] = {10, 20, 30, 40, 50};
vector<int> v1 (ai, ai + 5); // 10 20 30 40 50
vector<int> v2 (&ai[0], &ai[5]); // 10 20 30 40 50
vector<int> v3 (ai+1, ai + 4); // 20 30 40
在STL中但凡用两个迭代器表示一个元素范围时,
其中的终止迭代器一定是该范围中最后一个元素的下一个位置。
#include <iostream>
#include <vector>
using namespace std;
void print(vector<int> const& v){
cout << "对象大小:" << sizeof(v) << endl;
size_t size = v.size(); // 向量的容量
cout << "向量容量:" << size << endl;
cout << "变量向量中的元素:";
for(int i = 0; i < size; i++)
cout << v[i] << " ";
cout << endl;
}
int main(int argc, const char *argv[])
{
vector<int> v1; // 空向量
print(v1);
cout << "---------------" << endl;
vector<int> v2(3); // 定义向量对象,大小为3
print(v2);
cout << "---------------" << endl;
// 定义向量对象,大小为3,初始值为5
vector<int> v3(3,5);
print(v3);
cout << "---------------" << endl;
v3.reserve(5);
print(v3);
v3.push_back(6); // 插入元素6
print(v3);
v3.push_back(7); // 插入元素7
print(v3);
cout << "---------------" << endl;
int arr[5]={10,20,30,40,50};
vector<int> v4(arr,arr+5);
print(v4);
vector<int> v5(&arr[0], &arr[5]);
print(v5);
vector<int> v6(arr+2, arr+4);
print(v6);
return 0;
}
//结果:
对象大小:24
向量容量:0
变量向量中的元素:
---------------
对象大小:24
向量容量:3
变量向量中的元素:0 0 0
---------------
对象大小:24
向量容量:3
变量向量中的元素:5 5 5
---------------
对象大小:24
向量容量:3
变量向量中的元素:5 5 5
对象大小:24
向量容量:4
变量向量中的元素:5 5 5 6
对象大小:24
向量容量:5
变量向量中的元素:5 5 5 6 7
---------------
对象大小:24
向量容量:5
变量向量中的元素:10 20 30 40 50
对象大小:24
向量容量:5
变量向量中的元素:10 20 30 40 50
对象大小:24
向量容量:2
变量向量中的元素:30 40
四、迭代器
A.随机迭代器:
在顺序迭代器的基础上又增加了如下功能:可以和整数做加减法运算;同类型的迭代器之间可以做大小比较运算和相减运算。
B.八个特征迭代器
iterator begin (void)
返回一个迭代器,该迭代器引用向量的第一个元素
iterator end (void)
返回一个迭代器,该迭代器位于向量的最后一个元素之后
reverse_iterator rbegin (void)
返回引用向量中最后一个元素的迭代器
reverse_iterator rend (void)
返回位于向量中第一个元素之前的迭代器
const_iterator begin (void) const
返回一个迭代器,该迭代器引用向量的第一个元素
const_iterator end (void) const
返回一个const 迭代器,该迭代器位于向量的最后一个元素之后
const_reverse_iterator rbegin (void) const
返回引用向量中最后一个元素的迭代器
const_reverse_iterator rend (void) const
返回位于向量中第一个元素之前的迭代器
begin - 起始
end - 终止
不带const - 可写
带const - 只读
不带r - 正向
带r - 反向
对于正向迭代器而言,起始位置在首元素,终止位置在尾元素之后;
对于反向迭代器而言,起始位置在尾元素,终止位置在首元素之前。
任何可能导致容器结构发生变化的操作,比如数据元素的增删,都可能引发其内存布局的调整,
因此操作之前所获得的迭代器就会因为这种调整而失效,安全起见最好重新定位这个迭代器以后再继续使用。
#include <iostream>
#include <vector>
using namespace std;
void print(vector<int> const& v){
// 使用正向迭代器遍历容器中的所有元素
for(vector<int>::const_iterator it = v.begin();
it != v.end(); ++it)
cout << *it << " ";
cout << endl;
}
void rprint(vector<int> const& v){
for(vector<int>::const_reverse_iterator
it = v.rbegin(); it != v.rend();
it++){
cout << *it << " ";
}
cout << endl;
}
int main(int argc, const char *argv[])
{
// 空向量
vector<int> v1;
for(int i = 1;i<=10;i++)
v1.push_back(i*10);
print(v1);
rprint(v1);
return 0;
}
//结果:
10 20 30 40 50 60 70 80 90 100
100 90 80 70 60 50 40 30 20 10
五、算法
排序
IT sort (IT begin, IT end);
对[begin,end)区间内的元素做快速排序。
<algorithm>头文件中的全局函数sort()只能应用于带有随机迭代器的连续内存容器,
比如vector、deque。
列表的排序必须使用其成员函数sort()。
#include <iostream>
#include <algorithm> // 算法相关的头文件
#include <vector>
using namespace std;
template <typename T1>
void print(T1 begin, T1 end){
while(begin != end)
cout << *begin++ << " ";
cout << endl;
}
int main(int argc, const char *argv[])
{
vector<int> v1;
v1.push_back(33);
v1.push_back(57);
v1.push_back(78);
v1.push_back(25);
v1.push_back(123);
// 编译器可以完成隐式类型的推导
print (v1.begin(), v1.end());
// 升序排序
sort(v1.begin(), v1.end());
print (v1.begin(), v1.end());
return 0;
}
//结果:
33 57 78 25 123
25 33 57 78 123
六、列表(list)
6.1、特点
双向链表
在内存中是不连续的
6.2、实例化
list() 声明一个空列表;
list(n) 声明一个有n个元素的列表,每个元素都是由其默认构造函数T()构造出来的
list(n,val) 声明一个由n个元素的列表,每个元素都是由其复制构造函数T(val)得来的
list(n,val) 声明一个和上面一样的列表
list(first,last) 声明一个列表,其元素的初始值来源于由区间所指定的序列中的元素
6.3、唯一化
void unique (void);
仅对容器中连续的重复出现的元素做唯一化,而不是对整个容器中的元素做唯一化。
10 10 20 20 10 20 30 20 20 10 10
| unique()
V
10 20 10 20 30 20 10
6.4、排序
void sort (void); // 用元素类型的"<"运算符比较大小
<algorithm>头文件中的全局函数sort()只能应用于带有随机迭代器的连续内存容器,
比如vector、deque。列表的排序必须使用其成员函数sort()。
6.5、拆分:调用列表.splice (目标迭代器, 参数列表, ...);
从参数列表中剪切出全部或者部分元素,插入到调用列表中目标迭代器之前。
void splice (IT pos, list& lst); // 剪切全部
void splice (IT pos, list& lst, IT del); // 剪切一个
void splice (IT pos, list& lst, IT begin, IT end); //剪切区间
6.6、合并:将两个有序列表合并为一个,仍然保持有序。
void merge (list& lst); // <
参见:04list.cpp
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;
template <typename T1>
void print(T1 begin, T1 end)
{
while(begin != end)
cout << *begin++ << " " ;
cout << endl;
}
int main(int argc, const char *argv[])
{
int arr[10] = {10,10,20,20,40,50,50,30,10,10};
list<int> l1(arr, arr+10);
// 连续重复的元素唯一化
l1.unique();
print(l1.begin(), l1.end());
// 调用list中的成员函数sort进行排序
l1.sort();
print(l1.begin(), l1.end());
l1.unique();
print(l1.begin(), l1.end());
cout << "拆分" << endl;
list <int> l2;
l2.push_back(111);
l2.push_back(555);
l2.push_back(333);
l2.push_back(444);
l2.push_back(222);
list<int>::iterator pos = l1.begin();
/*
l1.splice(pos,l2); 剪切全部
*/
/*
// 剪切一个元素
list<int>::iterator del = l2.begin();
l1.splice(pos, l2, ++del);
*/
print(l1.begin(), l1.end());
cout << "合并" << endl;
l1.merge(l2);
l1.sort();
print(l1.begin(), l1.end());
return 0;
}
//结果:
10 20 40 50 30 10
10 10 20 30 40 50
10 20 30 40 50
拆分
10 20 30 40 50
合并
10 20 30 40 50 111 222 333 444 555