C++学习笔记之函数模板、类模板、STL初识

文章目录

  • 前言
  • 一、模板
    • 1. 模板的概念
    • 2. 函数模板
      • 2.1 函数模板语法
      • 2.2 函数模板注意事项
      • 2.3 函数模板案例
      • 2.4 普通函数与函数模板的区别
      • 2.5 普通函数与函数模板的调用规则
      • 2.6 模板的局限性
    • 3. 类模板
      • 3.1 类模板语法
      • 3.2 类模板与函数模板的区别
      • 3.3 类模板中成员函数创建时机
      • 3.4 类模板对象做函数参数
      • 3.5 类模板与继承
      • 3.6 类模板成员函数类外实现
      • 3.7 类模板分文件编写
      • 3.8 类模板与友元
      • 3.9 类模板案例
      • 3.10 自定义类型是类模板的参数,构造函数哪个先
  • 二、STL初识
    • 1. STL概念
    • 2. STL六大组件
    • 3. STL中容器、算法、迭代器
      • 容器:存储数据的数据结构
      • 算法:Algorithms
      • 迭代器:容器和算法之间粘合剂
    • 4. 容器算法迭代器初识
      • 4.1 Vector存放内置数据类型
      • 4.2 Vector存放自定义数据类型
      • 4.3 Vector容器嵌套容器

前言

C++提高编程:主要针对C++泛型编程STL技术做详细讲解

一、模板

1. 模板的概念

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。

2. 函数模板

  • C++另一种编程思想称为泛型编程,主要利用的技术就是模板
  • C++提供两种模板机制:函数模板类模板

2.1 函数模板语法

函数模板作用:
建立一个通用函数,其函数返回值类型形参类型可以不具体制定,用一个虚拟的类型来代表

语法template 函数声明或定义
解释
template(模板) — 声明创建模板
typename — 表明其后面的符合是一种数据类型,可以用class代替
T — 通用的数据类型,名称可以代替,通常为大写字母

代码如下(示例):

//交换两个整型函数
void swapInt(int& a, int& b)//用引用交换的是本体,相当于地址传递
{
	int temp = a;
	a = b;
	b = temp;
}
//交换两个浮点型函数
void swapDouble(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}

//函数模板
template<typename T>	//声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void myswap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test01()
{
	int a = 10;
	int b = 20;
	swapInt(a,b);
	cout<<"a="<<a<<endl;//20
	cout<<"b="<<b<<endl;//10
	//利用函数模板交换
	//两种方式使用函数模板
	//1.自动类型推导
	//myswap(a, b);	//因为a是int型,b也是int型。使用编译器推导出T是int型
	//2.显示指定类型
	myswap<int>(a, b);
	cout << "a= " << a << endl;
	cout << "b= " << b << endl;
}

总结:

  • 函数模板利用关键字template
  • 使用函数模板有两种方式:自动类型推导、显示指定类型
  • 模板的目的是为了提高复用性,将类型参数化

2.2 函数模板注意事项

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T 才可以使用
  • 模板必须要确定出T的数据类型,才可以使用

代码如下(示例):

//利用模板提供通用的交换函数
template<class T>	//typename 可以替换成class
void myswap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
//1.自动类型推导,必须推导出一致的数据类型T才可以使用
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';

	//myswap(a, b);	//正确!
	//myswap(a, c);	//错误!推导不出一致的T类型

	cout << "a= " << a << endl;
	cout << "b= " << b << endl;
}

//2.模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{
	cout << "func函数的调用" << endl;
}

void test02()
{
	//func();	报错,因为这个是模板函数,必须确定T的数据类型
	func<int>();
}

2.3 函数模板案例

案例概述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则由大到小,排序算法为选择排序
  • 分别利用char数组int数组进行测试

代码如下(示例):

template<typename T>
void mysort(T arr[], int len)	//选择排序
{
	for (int i = 0; i < len; i++)
	{
		int min = i;
		for (int j = i + 1; j < len; j++)
		{
			if (arr[j] < arr[min]) {
				min = j;
			}
		}
		if (min != i)
		{
			T temp = arr[i];
			arr[i] = arr[min];
			arr[min] = temp;
		}
	}
}

template<class T>
void myprintf(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " " ;
	}
}
void test01()
{
	//测试char数组
	char arr[] = "baderc";
	int len = strlen(arr);	//用sizeof()会把字符数组中的'/0'符合读取进去
	mysort<char>(arr, len);
	myprintf<char>(arr ,len);
	//测试int整型数组
	int arr1[] = { 5,8,6,9,1,2 };
	int len1 = sizeof(arr1) / sizeof(arr1[0]);
	mysort<int>(arr1, len1);
	myprintf<int>(arr1, len1);
}

2.4 普通函数与函数模板的区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

代码如下(示例):

//普通函数
int myadd01(int a, int b)
{
	return a + b;
}

//函数模板
template<typename T>
T myadd02(T a, T b)
{
	return a + b;
}

void test01()
{
	int a = 10;
	int b = 20;
	char c = 'a'; // a --- 97
	cout << myadd01(a, b) << endl;		//30
	cout << myadd01(a, c) << endl;		//107 编译器将char型数据转换为int型
	//cout << myadd02(a, c) << endl;		//报错,自动类型推导不会发生隐式类型转换,不会把char型转换为int型
	cout << myadd02<char>(a, c) << endl;	// k
	cout << myadd02<int>(a, c) << endl;		//107
}	

2.5 普通函数与函数模板的调用规则

调用规则如下:

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数
  2. 可以通过空模板参数列表来强制调用函数模板
  3. 函数模板也可以发生重载
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板

代码如下(示例):

//普通函数
void myprintf(int a, int b)
{
	cout << "调用的普通函数" << endl;
}

//函数模板
template<typename T>
void myprintf(T a, T b)
{
	cout << "调用的模板" << endl;
}
//函数模板可以重载
template<typename T>
void myprintf(T a, T b, T c)
{
	cout << "调用重载的模板" << endl;
}

void test01()
{
	int a = 10;
	int b = 20;
	myprintf(a, b);//允许普通函数和函数模板同时存在,但优先调用普通函数
	//通过空模板参数列表,强制调用函数模板
	myprintf<>(a, b);
	//myprintf(a,b,100);
	//4.如果函数模板可以产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myprintf(c1, c2);	//调用的模板,因为如果调用函数,还需要隐式类型转换
}	

总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性

2.6 模板的局限性

局限性:

  • 模板的通用性并不是万能的
    例如:
template<class T>
void function(T a,T b)
{
	a = b;
}

在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现
再例如:

template<class T>
void f(T a, T b)
{
	if(a > b){...}
}

如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行

可以进行运算符重载,另一种方法是提供具体化的模板

C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板
代码如下(示例):

//创建一个person类
class person
{
public:
	person(string name, int age)
	{
		this->m_age = age;
		this->m_name = name;
	}
	string m_name;	//姓名
	int m_age;		//年龄
};

//对比两个数据是否相等函数
template<class T>
bool mycompare(T &a, T &b)
{
	return a == b;
}
//模板重载,具体化
//这样就能对比自定义类型的数据了
template<> bool mycompare(person& p1, person& p2)
{
	return p1.m_name == p2.m_name && p1.m_age == p2.m_age;
}
void test01()
{
	int a = 10;
	int b = 20;
	bool ret = mycompare(a, b);
	if (ret)
		cout << "a == b " << endl;
	else
		cout << " a != b" << endl;
}
void test02()
{
	person p1("张三", 10);
	person p2("李四", 20);
	bool ret = mycompare(p1, p2);
	if (ret)		cout << "p1 == p2" << endl;
	else			cout << "p1 != p2" << endl;
}

利用具体化的模板,可以解决自定义类型的通用化
学习模板不是为了写模板,而是在STL能够运用系统提供的模板

3. 类模板

3.1 类模板语法

类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表
语法:

template<typename T>

代码如下(示例):

template<typename nametype,typename agetype>
class person
{
public:
	person(nametype name, agetype age)
	{
		this->m_age = age;
		this->m_name = name;
	}
	nametype m_name;
	agetype m_age;
};
void test01()
{
	person<string, int> p1("张三",10);
	cout << "name = " << p1.m_name << " " << "age = " << p1.m_age << endl;
}

类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板

3.2 类模板与函数模板的区别

区别有两点:

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数

代码如下(示例):

#include
using namespace std;
#include

//类模板与函数模板的区别
template<class  nametype, class agetype = int>    //模板中typename可以替换为class
class person
{
    public:
        person(nametype name, agetype age)
            {
                this->m_age = age;
                this->m_name = name;
            }
            void showPerson()
            {
                cout << "name:" << this->m_name << endl
                     << "age:" << this->m_age << endl;
            }
  
            nametype m_name;
            agetype m_age;
};

//1.类模板没有自动类型推导使用方式
void test01()
    {
        //person p1("张三", 100);     //会报错,无法使用自动类型推导
        person<string, int> p1("张三", 100);     //正确,只能用显示指定类型
        p1.showPerson();
    }
    //2.类模板在模板参数列表中可以有默认参数

void test02()
{
    person<string>p2("猪八戒", 999);
    p2.showPerson();
}
int main()
{
    test01();
    test02();
    return 0;
}

总结:

  • 类模板使用只能用显示指定类型方式
  • 类模板中的模板参数列表可以有默认参数

3.3 类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建

代码如下(示例):

#include
using namespace std;

//类模板中成员函数在调用时才创建

class person1
{
    public:
        void showperson1()
        {
            cout << "person1" << endl;
        }
};

class person2
{
    public:
        void showperson2()
        {
            cout << "person2" << endl;
        }
};

template<class T>
class Myclass
{
    public:
        T obj;

        //类模板中的成员函数
        void func1()
        {
            obj.showperson1();
        }
         void func2()
        {
            obj.showperson2();
        }
};

void test01()
{
    Myclass<person1> m;
    m.func1();
    //m.func2(); //编译会出错,说明 函数调用才会去创建成员函数
}
int main()
{
    test01();
    return 0;
}

总结

  • 类模板中的成员函数不是一开始就创建的,只是在调用时才去创建

3.4 类模板对象做函数参数

学习目标:类模板实例化出的对象,向函数传参的方式

有三种传入方式:
1.指定传入类型 — 直接显示对象的数据类型
2. 参数模板化 — 将对象中的参数变为模板进行传递
3. 整个类模板化 — 将这个对象类型 模板化进行传递

代码如下(示例):

#include
using namespace std;
#include

//类模板对象做函数参数

//写个类模板
template<class T1, class T2>
class person
{
    public:
        person(T1 name, T2 age)
        {
            this->m_age = age;
            this->m_name = name;
        }

        void showperson()
        {
            cout << "姓名:" << this->m_name << endl
                 << "年龄:" << this->m_age << endl;
        }
        T1 m_name;
        T2 m_age;
};
//1.指定传入类型
void printfperson1(person<string,int> &p)   //直接指定
{
    p.showperson();
}
void test01()
{
    person<string, int> p1("孙悟空", 100);
    printfperson1(p1);
}
//2.参数模板化
template<class T1, class T2>
void printfperson2(person<T1,T2> &p)
{
    p.showperson();
    cout << " T1的类型为:" << typeid(T1).name() << endl;
    cout << "T2的类型为:" <<  typeid(T2).name() << endl;
}
void test02()
{
     person<string, int> p2("猪八戒", 90);
    printfperson2(p2);
}

//3.整个类模板化
template<class T3>
void printperson3(T3 &p)
{
    p.showperson();
}
void test03()
{
    person<string, int> p3("唐僧", 80);
    printperson3(p3);
}
int main()
{
    test01();
    test02();
    test03();
    return 0;
}

总结:

  • 通过类模板创建的对象,可以有三种方式向函数中进行传参
  • 使用广泛是第一种:指定传入的类型

3.5 类模板与继承

当类模板碰到继承时,需要注意以下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模板
#include
using namespace std;

//类模板与继承
//基类
template<class T>
class base
{
    T m;
};

//class son : public base     //错误,必须要知道父类中的T类型,才能继承给子类
//c++  编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class son : public base<int>
{
    
};
void test01()
{
    son s1;
}

//如果想灵活指定父类中T类型,子类也需要变为类模板
template<class T1, class T2>
class son1 :public base<T2>
{
    public:
    T1 obj;
};
void test02()
{
    son1<int, char> S2;
}

int main()
{
    test01();
    test02();
    return 0;
}

总结: 如果父类是类模板,子类需要指定出父类中T的数据类型

3.6 类模板成员函数类外实现

代码如下(示例):

#include
using namespace std;

// 类模板成员函数类外实现
template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void showPerson();
	
	T1 m_Name;
	T2 m_Age;
};

// 构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Age = age;
	this->m_Name = name;
	
}
// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout<<"姓名: "<< this->m_Name <<" " << "年龄: " << this->m_Age << endl;
}

void test01()
{
	Person<string, int> p1("张三", 100);
	p1.showPerson();
}
int main()
{
	test01();
}

3.7 类模板分文件编写

问题:

  • 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

  • 解决方式1:直接包含.cpp源文件
  • 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,.hpp是约定的名称,并不是强制

代码如下(示例):

// 方式1
// person.h文件
#pragma once
#include
using namespace std;

template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void showPerson();
	
	T1 m_Name;
	T2 m_Age;
};
// person.cpp 文件
#include "person.h"

// 构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Age = age;
	this->m_Name = name;
	
}
// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout<<"姓名: "<< this->m_Name <<" " << "年龄: " << this->m_Age << endl;
}
// 运用方式1,主函数文件要包含.cpp源文件
// 如果用方式2,将声明和实现同一个文件中,并改后缀名为.hpp,然后主函数文件引入
#include
#include
using namespace std;
#include "person.cpp"

void test01()
{
	Person<string, int> p1("张三", 100);
	p1.showPerson();
}
int main()
{
	test01();
}

3.8 类模板与友元

实现:类模板配合友元函数的类内和类外实现

全局函数类内实现 - 直接在类内声明友元即可
全局函数类外实现 - 需要提前让编译器知道全局函数的存在

总结:用类内实现就好,用法简单

//类内实现
#include
#include 
using namespace std;

// 类模板成员函数类外实现
template<class T1, class T2>
class Person
{
    //全局函数 类内实现
    friend void printPerson(Person<T1, T2> p)
    {
        cout << "姓名:" << p.m_Name << "年龄: "<<  p.m_Age << endl;
    }

public:
	Person(T1 name, T2 age)
    {
        this->m_Age = age;
        this->m_Name = name;
    }

private:

	T1 m_Name;
	T2 m_Age;
};

void test01()
{
	Person<string, int> p1("张三", 100);
	printPerson(p1);
}
int main()
{
	test01();
}
#include
#include 
using namespace std;

// 提前让编译器知道person类
template<class T1,class T2>
class Person;

// 要让编译器知道有这个全局函数
template<class T1,class T2>
void printPerson(Person<T1, T2> p)
{
    cout << "姓名:" << p.m_Name << "年龄: "<<  p.m_Age << endl;
}

// 类模板成员函数类外实现
template<class T1, class T2>
class Person
{
    //全局函数 类外实现
    // 加空模板参数列表
    friend void printPerson<>(Person<T1, T2> p);
public:
	Person(T1 name, T2 age)
    {
        this->m_Age = age;
        this->m_Name = name;
    }

private:

	T1 m_Name;
	T2 m_Age;
};


void test01()
{
	Person<string, int> p1("张三", 100);
	printPerson(p1);
}
int main()
{
	test01();
}

3.9 类模板案例

案例描述:实现一个通用的数组类,要求如下:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量
#include
using namespace std;

template<class T>
class MyArray
{
public:
    // 构造函数 传入容量参数
    MyArray(int capacity)
    {
        cout << "有参构造函数调用" << endl;
        this->m_capacity = capacity;
        this->m_size = 0;
        this->pAddress = new T[this->m_capacity];
    }
    // 拷贝构造函数 
    MyArray(const MyArray& arr)
    {
        cout << "拷贝构造函数调用" << endl;
        this->m_capacity = arr.m_capacity;
        this->m_size = arr.m_size;

        //深拷贝
        this->pAddress = new T[arr.m_capacity];
        // 将arr中的数据都拷贝过来
        for(int i=0; i< arr.m_capacity; i++)
        {
            this->pAddress[i] = arr.pAddress[i];
        }
    }

    // operator= 防止浅拷贝问题
    MyArray& operator=(const MyArray& arr)
    {
        cout << "运算符=重载函数调用" << endl;
        //先判断原来堆区是否有数据,如果有释放
        if(this->pAddress != NULL)
        {
            delete[] this->pAddress;
            this->pAddress = NULL;
            this->m_capacity = 0;
            this->m_size = 0;
        }
        // 深拷贝
        this->m_capacity = arr.m_capacity;
        this->m_size = arr.m_size;
        
        this->pAddress = new T[arr.m_capacity];
        // 将arr中的数据都拷贝过来
        for(int i=0; i< arr.m_capacity; i++)
        {
            this->pAddress[i] = arr.pAddress[i];
        }
        return *this;
    }

    // 尾插法
    void Push_Back(const T& val)
    {
        // 判断容量是否等于大小
        if(this->m_capacity == this->m_size)
        {
            return;
        }
        this->pAddress[this->m_size] = val; //在数组末尾插入数据
        this->m_size++;
    }

    // 尾删法
    void Pop_Back()
    {
        if(this->m_size == 0)
        {
            return;
        }
        // 让用户访问不到最后一个元素,即为尾删,逻辑删除
        this->m_size--;
    }
    // 通过下标访问数据
    T operator[] (int index)
    {
        return this->pAddress[index];
    }

    // 返回数据容量
    int getCapacity()
    {
        return this->m_capacity;
    }
    // 返回数组大小
    int getSize()
    {
        return this->m_size;
    }
    // 析构函数
    ~MyArray()
    {
        cout << "析构函数调用" << endl;
        if(this->pAddress != NULL)
        {
            delete[] this->pAddress;
            this->pAddress = NULL;
        }
    }


private:
    T * pAddress;  // 指针指向堆区开辟的真实数组
    int m_capacity;  // 数组容量
    int m_size;     // 数组大小
};

void test01()
{
    MyArray <int> arr1(5);      // 有参构造函数调用
    //arr1[0];          // 访问不到,需要重载
    MyArray<int> arr2(arr1);    //  拷贝构造函数调用
    MyArray<int> arr3(100);     //  有参构造函数调用
    arr3 = arr1;                //  运算符=重载函数调用

    
}
int main()
{
    test01();
    return 0;
}

3.10 自定义类型是类模板的参数,构造函数哪个先

#include
using namespace std;

class base
{
public:
    base()
    {
        cout << "自定义类型构造函数" << endl;
    }
    ~base()
    {
        cout << "自定义类型析构函数" << endl;
    }
};

template<class T>
class person1
{
public:
    person1()
    {
        cout << "模板类构造函数" << endl;
    }
    ~person1()
    {
        cout << "模板类析构函数" << endl;
    }
private:
    T a;
};

int main()
{
    person1<base> p1;
    return 0;
}

在这里插入图片描述

二、STL初识

1. STL概念

  • C++的面向对象和泛型编程思想,目的就是复用性的提升
  • STL(standard template library),标准模板库
  • STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)
  • 容器算法之间通过迭代器进行无缝连接
  • STL 几乎所有的代码都采用了模板类和模板函数

2. STL六大组件

STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
  2. 算法:各种常用的算法,如sort、find、copy、for_each等
  3. 迭代器:扮演了容器与算法之间的胶合剂
  4. 仿函数:行为类似函数,可作为算法的某种策略
  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
  6. 空间配置器:负责空间的配置与管理

3. STL中容器、算法、迭代器

容器:存储数据的数据结构

STL容器就是将应用最广泛的一些数据结构实现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表 等
这些容器分为序列式容器关联式容器两种:

  • 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置
  • 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

算法:Algorithms

算法分为:质变算法非质变算法

  • 质变算法:是指运算过程中会更改区间中的元素的内容,例如拷贝,替换,删除等等
  • 非质变算法:是指运算过程中不会更改区间中的元素内容,例如查找、计数、遍历、寻找极值等等

迭代器:容器和算法之间粘合剂

提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
某个容器都有自己专属的迭代器
迭代器使用非常类似于指针

迭代器种类:

种类 功能 支持运算
输入迭代器 对数据的只读访问 只读,支持++、==、!=
输出迭代器 对数据的只写访问 只写,支持++
前向迭代器 读写操作,并能向前推进迭代器 读写,支持++、==、!=
双向迭代器 读写操作,并能向前和向后操作 读写,支持++、–
随机访问迭代器 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 读写,支持++、–、【n】、-n、<、<=、>、>=

常用的容器中迭代器种类为双向迭代器,和随机访问迭代器

4. 容器算法迭代器初识

STL中最常用的容器为Vector,可以理解为数组,下面将学习如何向这个容器中插入数据、并遍历这个容器

4.1 Vector存放内置数据类型

容器:vector
算法:for_each
迭代器:vector::iterator

#include 
#include 
#include 
using namespace std;

void MyPrint( int val)
{
    cout << val << endl;
}

void test01()
{
    // 创建vector容器对象,并且通过模板参数指定容器中存放的数据的类型
    vector<int> v;
    // 向容器中放数据
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);

    // 每个容器都有自己的迭代器,迭代器是用来遍历容器中的元素
    // v.begin() 返回迭代器,这个迭代器指向容器中第一个数据
    // v.end() 返回迭代器, 这个迭代器指向容器元素的最后一个元素的下一个位置
    // vector::iterator 拿到vector这种容器的迭代器类型

    vector<int>::iterator pBegin = v.begin();  // 指向第一个数据
    vector<int>::iterator pEnd = v.end();   // 指向最后一个数据的下一个位置

    // 第一种遍历方式
    while(pBegin != pEnd)
    {
        cout<< *pBegin << endl;
        pBegin++;
    }

    // 第二种遍历方式
    for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
    {
        cout << *it << endl;
    }

    // 第三种遍历方式
    // 使用STL提供标准遍历算法 需要头文件 algorithm
    for_each(v.begin(), v.end(), MyPrint);

}
int main()
{
    test01();
    return 0;
}

4.2 Vector存放自定义数据类型

学习目标:vector中存放自定义数据类型,并打印输出

#include
#include
#include
using namespace std;

// 自定义数据类型
class Person
{
public:
    Person(string name, int age)
    {
        this->mAge = age;
        this->mName = name;
    }

public:
    string mName;
    int mAge;
};

// 存放对象
void test01()
{
    // 创建存放person类型的数据容器
    vector<Person> v;

    // 创建数据
    Person p1("aaa", 111);
    Person p2("bbb", 222);
    Person p3("ccc", 333);
    Person p4("ddd", 444);
    
    v.push_back(p1);
    v.push_back(p2);
    v.push_back(p3);
    v.push_back(p4);

    for(vector<Person>::iterator it = v.begin(); it != v.end(); it++)
    {
        cout<<"Name: " << (*it).mName << " Age: " << (*it).mAge << endl;
    }
}
// 存放对象指针
void test02()
{
    vector<Person*> v1;
    // 创建数据
    Person p1("aaa", 111);
    Person p2("bbb", 222);
    Person p3("ccc", 333);
    Person p4("ddd", 444);
    
    v1.push_back(&p1);
    v1.push_back(&p2);
    v1.push_back(&p3);
    v1.push_back(&p4);
    
    for(vector<Person*>::iterator it = v1.begin(); it != v1.end(); it++)
    {
        cout<<"Name: " << (*it)->mName << " Age: " << (*it)->mAge << endl;
    }
}
int main()
{
    test01();
    test02();
    return 0;
}

4.3 Vector容器嵌套容器

学习目标:容器中嵌套容器,我们将所以数据进行遍历输出

#include
#include
#include
using namespace std;


// 容器嵌套容器
void test01()
{
    
    vector< vector<int> > v;

    vector<int> v1;
    vector<int> v2;
    vector<int> v3;
    vector<int> v4;

    for(int i=0; i<4; i++)
    {
        v1.push_back(i+1);
        v2.push_back(i+2);
        v3.push_back(i+3);
        v4.push_back(i+4);
    }

    // 将容器元素插入到vector v 中
    v.push_back(v1);
    v.push_back(v2);
    v.push_back(v3);
    v.push_back(v4);

    for(vector< vector<int> >::iterator it = v.begin(); it != v.end(); it++)
    {
        // 此时(*it) --------容器vector
        for(vector<int>::iterator it1 = (*it).begin(); it1 != (*it).end(); it1++)
        {
            cout<< *it1 <<" ";
        }
    }
}

int main()
{
    test01();

    return 0;
}

你可能感兴趣的:(C++,c++)