C++
类和对象
c++面向对象三大特性:封装、继承、多态
1.封装
意义:将属性(成员变量)和行为(成员函数)写在一起,表现事物
访问权限:public;protected;private
struct和class区别:默认访问权限不同
struct默认权限public,class默认访问权限private
成员属性设置为私有的优点:
1.可以控制读写权限;
2.对于写,可以检测数据有效性
简单实例
class Student
{
public:
/*设置姓名*/
void setname(string name)
{
s_name = name;
}
/*获得姓名*/
string getname()
{
return s_name;
}
private:
string s_name;
};
int main()
{
Student.s1;
s1.setname("abc");
cout<<s1.getname()<<endl;
return 0
}
2.对象的初始化和清理
2.1 构造函数和析构函数
构造函数
1.没有返回值;
2.函数名和类名相同;
3.可以有参数,可以重载
4.创建对象时,构造函数自动调用
析构函数
1.没有返回值;
2.函数名和类名相同;
3.不可以有参数,不可以发生重载
4.对象销毁前,自动调用析构函数
2.2 构造函数分类及调用
1.按参数分类:有参构造和无参(默认)构造函数;
2.按类型分类:普通构造和拷贝构造函数
创建:
拷贝构造函数:Person(const Person &p)
调用:
括号法 | 显示法 | 隐式转换法 | |
---|---|---|---|
无参 | Person p1; | Person p1; | Person p1; |
有参 | Person p2(10); | Person p2 = Person(10); | Person p2 = 10; |
拷贝 | Person p3(p2); | Person p3 = Person(p2); | Person p3 = p2; |
注:
1.无参构造函数不要加(),会被认为是函数声明
2.Person(10) :匿名对象,当前行执行完成后,系统回收
3.不要利用拷贝构造函数初始化匿名对象 Person(p3) 等价于 Person p3,重定义
4…隐式转换法:Person p4 = 10 等价于 Person p4 = Person(10)
调用时机
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数参数传值
3.值方式返回局部对象
调用规则
1.创建一个类,编译器添加默认构造、拷贝构造、析构函数
2.如果写了有参构造函数,编译器不提供默认构造函数;
如果写了拷贝构造函数,编译器不提供其他构造函数.
Person(const Person&p)>Person(int age)>Person()
2.3 深拷贝和浅拷贝
1.浅拷贝:编译器提供,简单赋值拷贝工作
带来的问题:堆区内存重复释放
2.深拷贝:自己实现,在堆区重新申请空间,进行拷贝工作
简单实例
class Person
{
public:
Person()
{
cout<<"默认构造函数"<<endl;
}
Person(int age,int height)
{
m_age = age;
m_height = new int(height);
cout<<"有参构造函数"<<endl;
}
Person(const Person &p)
{
cout<<"拷贝构造函数"<<endl;
m_age = p.m_age;
//深拷贝操作
m_height = new int(*p.m_height);
}
~Person()
{
//使用浅拷贝,p2会释放掉堆区内存,导致p1重复释放
if(m_height != NULL)
{
delete m_height;
m_height = NULL;
}
cout<<"析构函数"
}
int m_age; //年龄
int *m_height;//身高
};
int main()
{
Person p1(18);
cout<<p1.m_age<<p1.m_height<<endl;
Person p2(p1);
cout<<p2.m_age<<p2.m_height<<endl;
return 0;
}
注:如果有堆区开辟,一定要提供深拷贝
2.4 初始化列表
Person(int a,int b,int c):m_A(a),m_B(b),m_C(c)
2.5 类对象作为类成员
构造函数:先调用类成员的构造,在调用自身构造
析构函数:先调用自身析构,在调用类成员的析构(与构造顺序相反)
2.6 静态成员(static)
特点:
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量
class Person
{
public:
static void func()
{
m_A = 100;
//m_B = 100;//错误,不可以访问非静态成员变量
}
static int m_A;
int m_B;
};
/*静态成员变量,类内声明,类外初始化*/
int Person::m_A = 10;
3.C++对象模型和this指针
3.1 成员变量和成员函数分开存储
1.int m_A;
非静态成员变量,属于类的对象上
2.static int m_B;
静态成员变量,不属于类的对象上
3.void func();
非静态成员函数,不属于类的对象上
4.staticvoid func2();
静态成员函数,不属于类的对象上
空类大小:1个字节
3.1 this指针
指向 被调用的成员函数 所属的对象
作用:
1.解决名称冲突
2.在类的非静态成员函数中返回对象本身
class Person
{
public:
Person& Add(Person p)//Person Add(Person) 拷贝构造函数,会创建一个新的对象
{
this->age += p.age;
return *this;//返回本体
}
int age;
};
int main()
{
Person p1(10);
Person p2(10);
p2.Add(p1).add(p1);
cout<<p2.age<<endl;
return 0;
}
本质:指针常量,不可以修改指针的指向
3.3 常函数和常对象
常函数
成员函数后面加const称为常函数
void func() const
等价于const Person *const this
常函数内不可以修改成员属性
加上关键字mutable,在常函数和常对象中可以修改值
常对象
(const Person p)只能调用常函数(void func() const)
4.友元
4.1 全局函数做友元
类里添加函数:friend void func()
可以访问私有内容
class Person
{
friend void func(Person &p);
public:
Person()
{
p_Name = "asd";
}
private:
string p_Name;
}
void func(Person *p)
{
cout<<p->p_Name<<endl;
}
int main()
{
Person p;
fun(&p);
return 0;
}
4.2 类做友元
类B里添加 friend class A
类A可以访问类B私有内容
4.3 成员函数做友元
类B里添加friend void A::func()
类A中func()可以访问类B私有内容
5. 运算符重载
5.1 加号+
成员函数
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A+p.m_A;
temp.m_B = this->m_B+p.m_B
}
Person p3 = p1+p2
全局函数
Person operator+(Person &p1,Person &p2)
5.2 左移<<
只能利用全局函数重载左移运算符
ostream& operator<<(ostream &cout,Person &p) //ostream输出流类型
友元访问私有成员
5.3 自加++
前置:
Person &operator++()
{
m_Num++;//先运算
return *this;//再返回
}
返回引用,可以对同一个数多次操作(链式操作)
后置:
Person operator++(int)//int代表占位参数,区分前置和后置
{
Person temp = *this;//先记录当前结果
m_Num++;//再递增
return temp;//最后返回记录结果
}
返回 值,在函数内使用一个局部变量记录初值并返回,局部变量使用完被释放,如果返回引
用,属于非法操作
Person &operator++(int)//error
5.4 赋值 =
先判断是否有属性在堆区,如果有先释放,然后深拷贝,最后返回自身
class Person
{
public:
Person(int age)
{
m_Age = new int (age);
}
//重载赋值运算符
Person& operator=(Person &p)
{
if(m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深拷贝
m_Age = new int (*p.m_Age);
//返回自身
return *this;
}
~Person()
{
if(m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
int *m_Age;
}
5.5 关系 ==
bool operator==(Person &p)
匿名对象:类型() /Person()
6.继承
作用:可以减少重复代码
语法:class 子类:继承方式 父类
子类也称为派生类,父类也称为基类
6.1 继承方式
公共继承、保护继承、私有继承
1.父类中私有内容无法继承
2.公共继承:父类中公共、保护成员到子类中依然是公共(类外可访问)、保护权限
3.保护继承:父类中公共、保护成员到子类中变为保护权限
4.私有继承:父类中公共、保护成员到子类中变为私有权限
父类中所有非静态成员属性都会被子类继承
顺序:先调用父类构造函数,在调用子类构造函数;析构顺序与构造相反
6.2 同名继承
1.子类对象可以直接访问子类中同名成员函数
2.子类对象加作用域可以访问到父类同名成员
3.当子类与父类拥有同名成员函数,子类会隐藏掉父类中同名函数,加作用域可以访问到父类中同名函数
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout<<"Base-func()"<<endl;
}
void func(int a)
{
cout<<"Base-func(int a)"<<endl;
}
public:
int m_A;
};
class Son : public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout<<"Son-func()"<<endl;
}
public:
int m_A;
};
void test()
{
Son s;
cout<<s.m_A<<endl;//Son中的m_A
cout<<s.Base::m_A<<endl;//Bass中的m_A
s.func();
s.Base::func();
s.Base::func(1);
}
int main()
{
test();
return 0;
}
6.3 访问静态变量
class Son : public Base
Son s;
静态成员对象 | 静态成员函数 | |
---|---|---|
通过对象访问 | s.m_A – s.Base::m_A | s.func() – s.Base::func() |
通过类名访问 | Son::m_A – Son::Base::m_A | Son::func() – Son::Base::func() |
6.4 多继承
语法:
class 子类:继承方式 父类1,继承方式 父类2。。。
class Son : public Person , public Person2…
当出现二义性问题时,解决方法:添加作用域
6.5 菱形继承
A->B,A->C,B->D,C->D,数据重复,资源浪费
利用虚继承,解决菱形继承问题
class A {}//虚基类
class B : virtual public A {}
class C : virtual public A {}
class D : public B,public C {}
继承虚基类(最大类A)时加上关键字virtual
vbptr:虚基类指针 指向虚基类表,只有唯一数据
7 多态
7.1 多态基本概念
静态多态:函数重载,运算符重载,复用函数名 函数地址早绑定
动态多态:派生类和虚函数实现运行时多态 函数地址晚绑定
动态多态满足条件:
1.有继承关系
2.子类重写父类虚函数(重写:函数返回值、声明、参数列表完全相同)
动态多态使用条件:父类的指针或引用,指向子类对象
优点:
1.代码组织结构清晰
2.可读性强
3.利于前期和后期的扩展以及维护
class Animal
{
public:
virtual void speak()//+virtual 虚函数
{
cout<<"animal: speak()"<<endl;
}
};
class Cat : public Animal
{
public:
void speak()//重写虚函数
{
cout<<"cat: speak()"<<endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
void test()
{
Cat cat;
doSpeak(cat);
}
int main()
{
test();
return 0;
}
原理:父类中虚函数会创建vfptr(虚函数指针),将函数入口地址存入虚函数表中,子类继承父类,当子类重写虚函数时,会把继承来的虚函数替换掉,替换为自身的函数。当用父类引用指向子类对象时,会从子类虚函数表中找子类函数入口地址。
7.2 纯虚函数和抽象类
纯虚函数:virtual 返回值类型 函数名 (参数列表) = 0
当类中有了纯虚函数,这个类被称为抽象类
特点:
无法实例化对象;
子类必须重写纯虚函数,否则也属于抽象类
class Base
{
public:
virtual void func1() = 0;//纯虚函数,无法实例化对象
virtual void func2() = 0;
void allfunc()
{
func1();
func2();
}
};
class Son :public Base
{
public:
//必须重写纯虚函数,否则无法实例化对象
virtual void func1()
{
cout<<"func1()"<<endl;
}
void func2()//virtual在派生类中可加可不加
{
cout<<"func2()"<<endl;
}
};
void dofunc(Base *b)//Base *b = new Son
{
b->allfunc();
delete b;//释放
}
void test()
{
dofunc(new Son);
}
int main()
{
test();
return 0;
}
7.3 虚析构和纯虚析构
共性:
1.解决父类指针释放子类对象
父类指针在析构时,不会调用子类析构函数,导致如果子类有堆区属性,会导致内存泄漏
2.都需要具体的函数实现
虚析构:可以实例化对象
virtual ~类名(){}
纯虚析构:属于抽象类,无法实例化对象
virtual ~类名() = 0;
类名::~类名(){}
class Base
{
public:
Base()
{
cout<<"base()"<<endl;
}
//父类指针在析构时,不会调用子类析构函数,导致堆区数据没有释放干净,内存泄漏
//~Base()
//解决方法:虚析构
virtual ~Base()
{
cout<<"~base()"<<endl;
}
//纯虚析构:
//virtual ~Base() = 0;
//使用纯虚析构,需要函数的具体实现
virtual void speak() = 0;
};
/*
Base::~Base()
{
cout<<"~base()"<<endl;
}
*/
class Son :public Base
{
public:
Son(string name)
{
cout<<"son()"<<endl;
m_Name = new string(name);
}
~Son()
{
cout<<"~son()"<<endl;
if(m_Name != NULL)
{
delete m_Name;
m_Name = NULL;
}
}
void speak()
{
cout<<*m_Name<<"speak()"<<endl;
}
string *m_Name;
};
void test()
{
Base *b = new Son("AA");
b->speak();
delete b;
}
如果子类中没有堆区数据,可以不写虚析构和纯虚析构
模板
泛型编程
函数模板
template <tempname T>
void swap(T &a ,T &b)
{
T temp = a;
a = b;
b = temp;
}
1.自动类型推到
swap(a,b);
2.显示指定类型
swap<int>(a,b);
类模板
temp <class T>
void func();
func<int>();
普通函数可以发生自动类型转换
函数模板不能发生自动类型转换
调用规则
1.优先调用普通函数
2.可以通过空模板参数列表 强制调用函数模板
3.函数模板可以发生函数重载
4.如果函数模板可以产生更好的匹配,优先调用模板
template<class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void show()
{
cout<<endl;
}
private:
string m_Name;
int m_Age;
}
//常用 指定传入的类型
void print(Person<string ,int>&p)
{
p.show();
}
void test()
{
Person<string ,int>p("a",1);
print(p);
}
继承
template <class T>
class Base
{
T m;
};
class Son :public Base<int>//必须指定数据类型
{
Son s;
};
template <class T1,class T2>
class Son1:public Base<T2>
{
T1 obj;
};
void test()
{
Son1 <int ,char> child;
}
类外实现
template<class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age);
void show()
{
cout<<endl;
}
private:
string m_Name;
int m_Age;
}
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void Person<T1,T2>::show()
{
cout<<endl;
}
分文件编写
问题:类模板函数创建时机在调用阶段,导致分文件编写时链接不到
解决:
1.直接包含.cpp文件
2.将声明和实现写到同一个文件中,更改后缀名为.hpp
友元
//类外实现 需要提前声明 编译器可以提前识别
template<class T1,class T2>
class Person;
template<class T1,class T2>
void print(Person<T1,T2> p)
{
}
template<class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age);
friend void print<>(Person<T1,T2>);
private:
string m_Name;
int m_Age;
}
STL(标准模板库)
面向对象和泛型编程 ,目的是提高复用性
划分:容器、算法、迭代器
容器和算法之间通过迭代器进行无缝连接。
1.六大组件
1.容器:各种数据结构,存放数据
序列式容器:强调值的排序,每个元素均有固定的位置
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
2.算法:各种常用算法
质变算法:运算过程中会改变区间内的元素内容(拷贝、替换)
非质变算法:运算过程中不会改变区间内的元素内容(查找、遍历)
3.迭代器:扮演容器和算法之间胶合剂
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据只读访问 | ++、==、!= |
输出迭代器 | 对数据只写访问 | ++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | ++、==、!= |
双向迭代器 | 读写操作,并能向前和向后操作 | ++、– |
随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据 |
常用的迭代器为双向迭代器和随机访问迭代器
4.仿函数:行为类似函数,可作为算法的某种策略
5.适配器:修饰容器或仿函数、迭代器接口的东西
6.空间配置器:负责空间配置与管理
接口:
push()
pop()
front
back
2.vector
//包含头文件
#include <vector>
//标准算法头文件
#include <algorithm>
void test()
{
//创建一个vector容器,类似数组
vector<int> v;
//向容器中插入数据
v.push_back(10);
v.push_back(10);
//通过迭代器访问容器中的数据
//起始迭代器,指向容器中第一个元素
vector<int>::iterator itBegin = v.begin();
//结束迭代器,指向容器中最后一个元素的下一个位置
vector<int>::iterator itEnd = v.end();
//访问
//第一种遍历
while (itBegin != itEnd)
{
cout<<*itBegin<<endl;
itBegin++;
}
//第二种 包含创建
for(vector<int>::iterator it = v.begin();it!=v.end();it++)
{
cout<<*it<<endl;
}
//第三种 利用STL提供的遍历算法
for_each(v.begin(),v.end(),print);
}
void print(int val)
{
cout<<val<<endl;
}
自定义数据类型
class Person
{
public:
Person(string name,int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
string m_Age;
};
void test()
{
vector<Person>v;
Person p1("a",10);
Person p2("b",20);
//向容器中添加数据
v.push_back(p1);
v.push_back(p1);
//遍历容器中的数据
for(vector<Person>::iterator it = v.begin();it!=v.end();it++)
{
//(*it) 是一个<Person>数据类型
//(*it).m_Name 等价于 it->m_Name
cout<<"name:"<<(*it).m_Name<<"age:"<<(*it).m_Age<<endl;
}
}
void test2()
{
vector<Person*>v;
Person p1("a",10);
Person p2("b",20);
//向容器中添加数据
v.push_back(&p1);
v.push_back(&p1);
//遍历容器中的数据
for(vector<Person*>::iterator it = v.begin();it!=v.end();it++)
{
//(*it) 是一个<Person*>数据类型
cout<<"name:"<<(*it)->m_Name<<"age:"<<(*it)->m_Age<<endl;
}
}
嵌套容器
void test()
{
vector <vector<int>>v;
//创建小容器
vector<int>v1;
vector<int>v2;
//添加数据
v1.push_back(1);
v2.push_back(2);
//将小容器加入大容器中
v.push_back(v1);
v.push_back(v2);
//通过大容器,把所有数据遍历一遍
for (vector<vector<int>>::iterator it = v.begin();it!=v.end();it++)
{
//(*it) vector<int>类型
for (vector<int>::iterator vit = (*it).begin();it!=(*it).end();vit++)
{
cout<<*vit<<" ";
}
cout<<endl;
}
}
3.string
本质是一个类
赋值:=、assign
void test()
{
string str1;
str1 = "123"; //123
string str2;
str2 = str1; //123
string str3;
str3 = 'a'; //a
//assign
string str4;
str4.assign("a"); //a
//将前3个字符赋值
string str5;
str5.assign("hello",3); //hel
string str6;
str6.assign(str5); //hel
string str7;
str7.assign(5,'a'); //aaaaa
}
字符串拼接:=、append
void test()
{
string str1 = "aa";
str1 += "123"; //aa123
str1 += '+'; //aa123+
string str2 = "bb";
str1 += str2; //aa123+bb
string str3 = "cc";
str3.append("dd"); //ccdd
str3.append("123456",3);//ccdd123
//str3.append(str2); //ccdd123aa123+bb
//从第0个位置开始,截取3个字符追加
str3.append(str2,0,3); //ccdd123aa1
}
查找和替换 find rfind
void test()
{
string str1 = "abcdefg";
int pos = str1.find("df");
if(pos == -1)
{
cout<<"no"<<endl;
}
else
{
cout<<pos<<endl; //3 从第0个数开始找 从左往右查询
}
pos = rfind("de");
cout<<pos<<endl; //3 从右向左查询
}
void test1()
{
string str1 = "abcdefg";
istr1.replace(1,3,"1111"); //从1号位置起,3个字符,替换为1111
cout<<str1<<endl; //a1111efg
}
比较
void test()
{
string str1 = "abcdefg";
string str2 = "abcdefg";
if(str1.compare(str2) == 0)
{
cout<<"相等"<<endl;
}
//大小不常用,多用于比较是否相等
if(str1.compare(str2) > 0)
{
cout<<"1 > 2"<<endl;
}
}
存取
void test()
{
string str1 = "abcdefg";
//读
for(int i = 0;i < str1.size();i++)
{
cout<<str1[i]<<endl;
//str.at(i)
}
//写
str[0] = 'x'; //xbcdefg
str.at(1) = 'y'; //ybcdefg
}
插入与删除
void test()
{
string str = "abcdefg";
str.insert(1,"111");
cout<<str<<endl; //a111bcdefg
str.erase(1,3);
cout<<str<<endl; //abcdefg
}
子串
void test()
{
string str = "abcdefg";
string subStr = str.substr(1,3);
cout<<subStr; //bcd
//邮箱姓名截取
string email = "zhang@163.com";
int pos = email.find("@"); //5
string usrName = email.substr(0,pos);//zhang
}
4.vector (单端数组)
与普通数组的区别:数组是静态空间,vector可以动态扩展
动态扩展:找到更大的内存空间,将数据拷贝新空间,释放原有空间
#include <vector>
void test()
{
vector<int>v1;//默认构造 无参构造
for(int i = 0;i<10;i++)
{
v1.push_back(i);
}
print(v1);
//通过区间方式进行构造
vector<int>(v1.begin(),v1.end());
print(v2);
//n个elem方式构造
vector<int>v3(10,100);//10个100
print(v3);
//拷贝构造
vector<int>v4(v3);
print(v4);
}
void print(vector<int> &v)
{
for (vector<int>::iterator it = v.begin();it!=v.end();it++)
{
cout<<*it<<" ";
}
}
赋值
void test()
{
vector<int> v1;
for(int i = 0;i<10;i++)
{
v1.push_back(i);
}
//赋值 operator=
vector<int> v2;
v2 = v1;
//assign
vector<int> v3;
v3.assign(v1.begin(),v1.end()); //[begin,end)
//n个elem
vector<int> v4;
v4.assign(10,100);
}
void print(vector<int>&v)
{
for(vector<int>::iterator it = v.begin();it!=v.end();it++)
{
cout<<*it<<" ";
}
}
注:
//构造
vector<int>v1(10,100);//10个100
//赋值
vector<int> v2;
v4.assign(10,100);
容量和大小
void test()
{
vector<int> v1;
for(int i = 0;i<10;i++)
{
v1.push_back(i);
}
if(v1.empty()) //为真,代表容器为空
{
cout<<"empty"<<endl;
}
else
{
cout<<v1.capacity()<<endl; //容量
cout<<v1.size()<<endl; //大小
}
//重新指定大小
v1.resize(15);
//如果重新指定的比原来长,默认用0填充新的位置
print(v1); //0123...8900000
v1.resize(15,100);
print(v1); //0123... 100 100 100 100 100
//如果重新指定的比原来短,超出的部分被删除
v1.resize(5);
print(v1); //01234
}
void print()
{
for(vector<int>::iterator it = v.begin();it!=v.end();it++)
cout<<*it<<endl;
}
总结:
判断是否为空:empty
返回元素个数:size
返回容量容器:capacity
重新指定大小:resize
插入和删除
void test()
{
vector<int> v1;
v1.push_back(10);
v1.push_back(10);
//尾删
v1.push_pop(); //删除最后一个
//插入
//insert第一个数据是迭代器
v1.insert(v1.begin(),100); //100 10
v1.insert(v1.begin(),2,1000); //1000 1000 100 10
//删除
//insert第一个参数是迭代器
v1.erase(v1.begin()); //1000 100 100 10
v1.erase(v1.begin(),v1.end()); //清空 等价于v1.clear()
}
void print(vector<int> &v)
{
for(vector<int>::iterator it = v.begin();it!=v.end();it++)
{
cout<<*it;
}
}
数据存取
void test()
{
vector<int> v1;
for(int i = 0;i<10;i++)
{
v1.push_back(i);
}
//获取第一个元素
cout<<v1.front()<<endl;
//获取最后一个元素
cout<<v1.back()<<endl;
}
容器互换
void test()
{
vector<int> v1;
for(int i = 0;i<10;i++)
{
v1.push_back(i);
}
vector<int> v2;
for(int i = 10;i>0;i--)
{
v1.push_back(i);
}
//交换
v1.swap(v2);
vector<int> v;
for(int i = 0;i<1000;i++)
{
v.push_back(i);
}
v.resize(3);
//容量1000+,大小3 资源浪费
//vector<int>(v) 匿名对象,当前行执行完系统回收
//按照v当前所用的个数来初始化匿名对象容量
//v与一个容量3的匿名对象互换
vector<int> (v).swap(v);
}
void print(vector<int> &v)
{
for(vector<int>::iterator it = v.begin();it != v.end();it++)
{
cout<<*it;
}
}
用途:swap可以收缩内存空间
预留空间 reserve
void test()
{
vector<int> v;
int *p = NULL;
int num = 0;
for(int t = 0;i<1000;i++)
{
v.push_back(i);
//判断p是否指向首地址,即开辟次数
if(p!= &v[0])
{
p = &v[0];
num++;
}
}
//如果提前知道数据量较大
//利用reserve预留空间
v.reserve(10000);
}
5.deque (双端数组)
双端数组,可以对头端进行插入删除操作
deque和vector区别
vector对于头部的插入和删除效率低,数据量越大,效率越低
deque对于头部插入速度比vector快
deque访问速度低于vector
deque通过中控器维护每段缓冲区内容
//const 只读 防止误操作
void print(const deque<int>d)
{
for(deque<int>::const_iterator it = d.begin();it!=d.end();it++)
{
cout<<*it;
}
}
排序 sort
#include <algorithm>
#include <deque>
void test()
{
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_back(30);
d.push_front(100);//头插
print(d); //100 10 20 30
//sort 默认升序
sort(d.begin(),d.end());
print(d); //10 20 30 100
}
void print(const deque<int>&d)
{
for(deque<int>::const_iterator it =d.begin();it!=d.end();it++)
cout<<*it;
}
6.stack(栈)
先进后出
不允许有遍历行为:只有栈顶元素可以被外界访问到
void test()
{
stack<int>s;
s.push(10);
while(!s.empty())
{
//top()查看栈顶元素
cout<<"栈顶:"<<s.top();
//pop()出栈
s.pop();
}
s.size(); //0 栈的大小
}
7.queue (队列)
先进先出
入队:push
出队:pop
第一个元素:队头front
最后一个元素:队尾back
不允许有遍历行为:只有队头和队尾可以被外界访问到
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Score = age;
}
string m_Name;
int m_Age;
};
void test()
{
queue<Person>q;
Person p1("A",1);
Person p2("B",2);
Person p3("C",3);
q.push(p1);
q.push(p2);
q.push(p3);
//判断队列不为空,查看队头队尾
while(!q.empty())
{
cout<<"头:"<<q.front().m_Name<<q.front().m_Age;
cout<<"尾:"<<q.back().m_Name<<q.back().m_Age;
//出队
q.pop();
}
q.size();//0
}
8.*list (链表)
数据域:存储的数据
指针域:下一节点地址
链表存储方式不是连续内存空间,因此list迭代器只支持前移和后移,是双向迭代器
优点:动态存储分配,不会造成内存浪费和溢出;可以对任意位置进行快速插入或删除元素
缺点:对于容器遍历速度没有数组快;占用空间比数组大
void()
{
list<int> L;
L.push_back(10);
L.push_back(10);
list<int> L2(L.begin(),L.end());
}
void print(const list<int> &L)
{
for(list<int>::const_iterator it = L.begin();it!=L.end();it++)
{
cout<<*it;
}
}
赋值和交换
void test()
{
list<int>L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
list<int>L2;
L2=L1;
//交换
L1.swap(L2);
//删除所有的 10
L.remove(10);
}
list不支持[]和at()访问
list本质是链表,不是连续线型空间存储数据,迭代器不支持随机访问
list<int>::iterator it = L.begin()
it++ 支持单向
it-- 支持双向
it=it+1; 不支持随机访问
翻转和排序
void test()
{
list<int>L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
//翻转
L1.reverse();
//排序
//不支持随机访问迭代器的容器,内部提供对应算法
L1.sort();
}
9.set(二叉树)
关联式容器
所有元素插入时自动排序
#include<set>
void test()
{
set<int>s1;
//插入数据只有insert
s1.insert(10);
s2.insert(20);
}
特点:自动排序;不允许插入重复的值
10.map
map中所有元素都是pair
pair中第一个元素是key(索引),第二个元素是value(实值)
所有元素都会安照key排序
#include <map>
void test()
{
map<int ,int>m;
m.insert(pair<int,int>(1,10));
m.insert(pair<int,int>(2,20));
}
void print(map<int,int> &m)
{
for(map<int,int>::iterator it = m.begin();it!=m.end();it++)
{
cout<<(*it).first<<it->second;
}
}