1.函数的引用型返回值可以作为左值
2.引用作为函数的返回值时不能返回函数中局部变量
如:
int& xxx(...)
{
int a;//局部变量 不能作为引用返回 在栈区
static int b;//变量成了全局变量 在全局区
//return a;//可以接收到一次值 再次接收将会乱码
return b;//正确
}
3.关于const和指针
const int* ptr;//指向一个整型常量 指向的整型不能变
int* const ptr;//指向一个整型变量 指针不能指向别的变量 指向的
//值可修改
const int* const ptr;//不能指向其他常量 指向的值也不能修改
4.关于常数指针
//错误
int& a = 10;//给引用初始化必须得是栈或者堆上的数据,常量区不行
//正确 加上 const
const int& a = 10;//编译器自动转为
//int temp = 10; int& a = temp;
5.函数的声明和实现不能同时存在默认参数,否则编译器会发生二义性错误(见下面例子),且默认参数一定是从左到右连贯出现的(若有很多默认参数的话)
6.关于占位参数,在调用有占位参数的函数时,必须在相应位置写上相同类型数据
7.关于函数重载
1.得在同一个作用域下
2.函数名称需要一致
3.函数参数类型,个数或者顺序不同
8.函数重载需要注意的两个情况
1.引用作为函数重载条件时
int test(int& a);
int test(const int& a);
//这可以区分重载,变量走上面的函数,常数走下面的函数
2.默认参数造成的二义性问题
int test(int a, int b = 10);
int test(int a);
//当调用test(10);时 会报错 编译器不知道调用上面的函数还是
//下面的函数 test(10,20);这样调用是可以的 所以函数重载最好
//不要用默认参数
9.C++面向对象编程三大特点:封装,继承,多态
10.class默认权限是私有 struct默认权限是公有 shift+tab整体缩进
构造与析构
11.没有返回值 不写void 且是编译器自动的调用这两个函数 对象被创建就会调用构造 对象使用完毕被销毁时才会调用析构 构造可以重载
12.构造函数分为:有参构造(默认构造)和无参构造 或者 普通构造和拷贝构造
拷贝构造函数
Person(const Person& p)
{
//可以理解为将p完全复制 所以这个类不予许被修改(const)
//比如拷贝p的年龄
age = p.age;
}
13.三种调用构造函数
1.括号法
Person p;//默认构造函数被调用
Person p1(10);//有参构造函数被调用
Person p2(p1);//拷贝构造函数
Person p3();//不会调用无参构造 编译器会认为是函数声明
2.显示法
Person p1;
Person p2 = Person(10);//有参
Person p3 = Person(p2);//拷贝
Person(10);//匿名对象 特点:当执行结束,系统会立刻将其回收
//不要用拷贝构造函数初始化匿名对象
Person(p3);//编译器会认这行代码为 Person(p3) == Person p3;
//编译报错 重复定义
3.隐式转换法
Person p1 = 10;//有参构造
Person p2 = p1;//拷贝构造
14.拷贝构造函数使用的地方
1.使用一个已经创建完毕的对象来初始化新对象
void test01()
{
Person p1(10);
Person p2(p1);//调用拷贝构造函数
}
2.以值传递的方式给函数参数传值
void work(Person p)
{
}
void test02()
{
Person p;
work(p);//调用拷贝构造函数 拷贝出一个新的副本作为值来传递
}
3.值方式返回局部对象
Person work()
{
Person p1;
return p1;//调用拷贝
}
void test03()
{
Person p1 = work();
}
15.构造函数调用规则
1.创建一个类 编译器至少提供三个默认函数
构造函数(空实现)
析构函数(空实现)
拷贝构造(值拷贝)
2.写了有参构造函数编译器就不提供默认构造函数 写了拷贝构造函
数编译器将不提供其它的构造函数
16.深拷贝和浅拷贝(重点)
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝
//主要会出现的问题是当构造函数中申请了堆区内存 浅拷贝将原封不动
//复制给新的对象 如果需要用到析构函数去释放堆区内存那么将会
//造成析构两次同一块内存引起程序奔溃 所以这个时候需要自己写一个
//构造函数运用new来重新申请一块新的堆区来存放数据。
17.初始化列表
构造函数():属性1(),属性2()…
Person(int a, int b):m_a(a),m_b(b){}
这样可以更加灵活赋值
18.当其他类作为本类成员的时候,构造是先构造其他类再构造自身 析构刚好和构造相反
19.静态成员变量属于类,所有由这个类创建出来的对象都共用这个变量,任何一个对象中修改了这个变量那么其他所有的对象中的这个变量会跟着改变。静态成员变量初始化必须在类声明的外部进行,它的内存分配只在外部初始化时分配其他比如对象的创建,类的声明都不进行内存分配
初始化方法如下
type classname::name = value;
int Person::m_age = 18;
20.静态成员函数通过对象和类名都可以进行访问,可以访问静态成员变量不能访问非静态成员变量因为无法区分这个成员到底是谁的变量
21.空对象空间大小是一个字节用来区分空对象占内存的位置,每个空对象都有一个独一无二的内存地址 如果类中有非静态成员变量那么就是那个成员的大小不会加1 只有非静态成员函数属于类的对象上
22.this指针是指向对象的 可以解决名称冲突的问题比如参数和变量重名 this也可以返回对象本身但是得用*this 函数返回类型是Person&不返回这个类型可能会达不到需要的目的 this指针本质是一个指针常量
23.空指针访问成员函数,可以访问,但是如果成员函数中包含了成员变量那么程序就会崩溃,因为空指针没有实体,成员函数中的隐式指针this无处可指直接奔溃
24.const修饰成员函数称为常函数,常函数中的成员不可被修改,成员属性声明时加上mutable后在常函数中依然可以被修改。
void test() const
{
this->num = 100;//将会报错 this变成了既不能改变指向也
//不能修改指向值的一个指针 因为那个const
}
//可以给num加上mutale 那么num值也可以正常修改 如:
mutable int num;
常对象也类似不能修改成员变量 除非加了mutable 常对象只能调用常函数 因为在普通函数中可以对属性进行修改这违背了常对象不能修改属性的原则所以会报错
25.友元
当全局函数做友元的时候 需要注意的是在类中声明不在各种权限区域内声明 且声明的时候函数的返回值类型一定不能遗漏 然后在最前方加上friend即可访问到私有属性中的变量。
类做友元的时候和上一样 加个friend
成员函数做友元 需要在那个想访问私有成员的类中声明 加上域作用符告诉编译器是哪个类中的函数想友元 不要忘记加这个函数的类型
26.运算符重载
1.加法重载 不能修改原有的加法准则 比如整型的加法 浮点的加法等等 可以重载出两个类相加
1 #include<iostream>
2
3 using namespace std;
4 class add
5 {
6 public:
7 add(int s1, int s2):sum1(s1),sum2(s2){}
8 //成员函数实现
9 /* add operator+(add& a1)
10 {
11 add temp(0,0);
12 temp.sum1 = this->sum1 + a1.sum1;
13 temp.sum2 = this->sum2 + a1.sum2;
14 return temp;
15 }*/
16
17 int sum1;
18 int sum2;
19 };
20 //外部函数实现
21 add operator+(add& a1, add& a2)
22 {
23 add temp(0,0);
24 temp.sum1 = a1.sum1 + a2.sum1;
25 temp.sum2 = a1.sum2 + a2.sum2;
26 return temp;
27
28 }
29 void see(add a)
30 {
31 cout << "sum1:" << a.sum1 << endl;
32 cout << "sum2:" << a.sum2 << endl;
33 }
34 int main()
35 {
36 add a1(0,0);
37 add a2(7,8);
38 add a3(10,11);
39 /*a1 = a2 + a3;
40 see(a1);*/
41 a1 = operator+(a2,a3);//本质用法 可以直接 a1 = a2 + a3;
42 see(a1);
43 return 0;
44 }
2.左移运算符重载
一般不用成员函数去实现 所以这里用全局函数重载
cout属于ostream类 所以将cout看成一个类 参数就按照普通类来看就好
需要运用到之前遇到过的链式编程思想,这样就可以无限的往后追加想要输出的东西
1 #include<iostream>
2
3 using namespace std;
4
5 class Left//这种名字很容易与库中的某些定义重复 引起一堆报错。。。所以改大写
6 {
7 friend ostream& operator<<(ostream& out, Left& l1);
8 public:
9 Left(int a, int b)
10 {
11 a1 = a;
12 a2 = b;
13 }
14 private:
15
16 int a1;
17 int a2;
18
19 };
20
21 ostream& operator<<(ostream& out, Left& l1)//注意只有一条输出流必须用ostream&
22 {
23 out << "first:" << l1.a1 << "second:" << l1.a2;
24 return out;
25 }
26 int main()
27 {
28 Left l1(10,9);
29 cout << l1 << endl;
30 }
3.递增运算符重载
1 #include<iostream>
2 #include<string>
3 #include<cstdio>
4
5 using namespace std;
6 class myint
7 {
8 friend ostream& operator<<(ostream& out, myint mint);
9 public:
10 myint():sum(1){}
11 myint& operator++();
12 myint operator++(int);
13 private:
14 int sum;
15 };
16 myint& myint::operator++()//虽然在外面做了域区说明 但还是需要声明
17 {
18 sum++;
19 return *this;
20 }
21 myint myint::operator++(int)//占位符上线了!!它有用了!!
22 {
23 myint tempint = *this;
24
25 sum++;
26
27 return tempint;
28 }
29
30
31 ostream& operator<<(ostream& out, myint mint)
32 {
33 out << mint.sum;
34 return out;
35 }
36 int main()
37 {
38 myint mint;
39 cout << (++mint)++ << endl;
40 cout << mint <<endl;
41 return 0;
42 }
做一个自减的 基本差不多
4.赋值运算符重载
这个重载必须在类中声明!!!vim不声明报错
1 #include<iostream>
2
3 using namespace std;
4
5 class deng
6 {
7 public:
8 deng(int n_p)
9 {
10 p = new int(n_p);
11 }
12 ~deng()
13 {
14 if(p != NULL)
15 {
16 delete p;
17 p = NULL;
18 }
19 }
20 deng(const deng& pp)
21 {
22 p = new int(*pp.p);
23 }
24 deng& operator=(deng& pp);
25 void see();
26 private:
27 int *p;
28 };
29 void deng::see()
30 {
31 cout << *p << endl;
32 }
33 deng& deng::operator=(deng& pp)
//注意这个我用的vim 等号重载必须在类中声明一下 否则报错
//https://blog.csdn.net/cook2eat/article/details/52420200大佬详解
34 {
35 if(p != NULL)
36 {
37 delete p;
38 p = NULL;
39 }
40
41 p = new int(*pp.p);
42
43 return *this;
44 }
45 int main()
46 {
47 deng p1(10);
48 deng p2(20);
49
50 p1 = p2;
51 p1.see();
52 }
5.关系运算符重载
这个也要在类中声明 不然报一页的错 直接爆炸
1 #include<iostream>
2 #include<string>
3 using namespace std;
4 class guan
5 {
6 public:
7 guan(string n, int age)
8 {
9 name = n;
10 this->age = age;
11 }
12 bool operator==(guan& g);//声明一下
13 string name;
14 int age;
15 };
16
17 bool guan::operator==(guan& g)//!!!
18 {
19 if(this->name == g.name && this->age == g.age)
20 {
21 return true;
22 }
23 else
24 return false;
25 }
26 int main()
27 {
28 guan g1("小米",18);
29 guan g2("小米",18);
30 if(g1 == g2)
31 cout << "两个人一样" << endl;
32
33 return 0;
34 }
6.函数调用运算符重载
又称为仿函数
没有固定写法 非常灵活
1 #include<iostream>
2
3 using namespace std;
4
5 class myprintf
6 {
7 public:
8 int operator()(int a, int b)
9 {
10 return a+b;
11 }
12 };
13 int main()
14 {
15 myprintf mp;//也可以不用创建新对象 可以匿名使用 但是vim好像不太行必须需要定义对象
16 cout << mp(10,10) << endl;
//cout << myprintf(10,10) <<endl;
17 }
27.继承!!!
37.文件操作
ofstream:写文件
ifstream:读文件
fstream:读写文件
这三个都是类
1.需要包含一个头文件#include
2.包含流对象
3.打开文件
对象.open(“文件路径”,打开方式);
4.写数据
对象 << “写入数据”
5.关闭文件
对象.close();
打开文件方式有:
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
1 #include<iostream>
2 #include<fstream>
3
4 using namespace std;
5
6 void test()
7 {
8 fstream ofs;
9 ofs.open("./text.txt",ios::out);
10
11 ofs << "姓名:xxx" << endl;
12 ofs << "年龄:xx" << endl;
13 ofs << "性别:x" << endl;
14
15 ofs.close();
16 }
17 int main()
18 {
19 test();
20
21 return 0;
22 }
~
38.读文件
1.包含头文件
2.创建流对象
3.打开文件并且判断是否打开成功
对象.open(“文件路径”,打开方式);
判断函数
对象.is_open()//这个返回的是一个布尔值
4.读数据
四种方式
1、
ifstream ifs;
char buf[1024] = {0};
while(ifs >> buf)
{
cout << buf << endl;
}
2、
char buf[1024] = {0};
while(ifs.getline(buf,sizeof(buf)))//一行一行的读
{
//输出
}
3、
string buf;//包含头文件
while(getline(ifs,buf))
{
//输出
}
4、
//不太推荐的方法
char c;
while((c = ifs.get()) != EOF)
{
cout << c;
}
5.关闭文件
39.二进制文件
打开方式加一个ios::binary
可以将自定义的类型写入文件中保存,不仅仅是文本等数据
创建流对象直接初始化可以简易一些
ofstream ofs("路径",打开方式);
1 #include<iostream>
2 #include<fstream>
3 #include<string>
4 using namespace std;
5
6 class Person//避免使用string 会造成读二进制文件时会读出乱码的情况
7 {
8 public:
9 int m_age;
10 char m_name[4];
11 char m_sex[4];
12 };
13 int main()
14 {
15 Person p = {18,"lkj","man"};
16 ofstream ofs("./test02.txt",ios::out | ios::binary);
17 ofs.write((const char*)&p,sizeof(Person));
18 ofs.close();
19
20 return 0;
21 }
40.二进制文件读
1 #include<iostream>
2 #include<fstream>
3
4 using namespace std;
5
6 class Person//注意这个结构体必须和写入文件的结构体相同
7 {
8 public:
9 int m_age;
10 char m_name[4];
11 char m_sex[4];
12 };
13 int main()
14 {
15 ifstream ifs("test02.txt", ios::in | ios::binary);
16 if(!ifs.is_open())
17 {
18 cout << "文件打开失败" << endl;
19 return 0;
20 }
21 Person p;
22 ifs.read((char*)&p,sizeof(Person));
23 cout << "年龄:" << p.m_age << " 姓名:" << p.m_name << " 性别:" << p.m_sex << endl;
24 ifs.close();
25 }
41.职工管理系统
…//类似通讯录
42.模板
函数模板的作用:
建立一个通用的函数,其函数返回值类型和形参类型可以不确定,用一个虚拟类型表示,如下:
template<typename T>//告诉编译器我在写一个模板,T是一个通用的数据类型
模板的使用方法:
1.自动类型推导
myswap(a,b);
2.显示数据类型
myswap<int>(a,b);
代码实现:
1 #include<iostream>
2
3 using namespace std;
4
5 template<typename T>
6 void myswap(T& a, T& b)
7 {
8 T temp = a;
9 a = b;
10 b = temp;
11 }
12 void test()
13 {
14 int a = 1;
15 int b = 2;
16 swap<int>(a,b);
17 cout << "a:" << a << "b:" << b << endl;
18 }
19 int main()
20 {
21 test();
22 return 0;
23 }
43.模板的注意事项
typename 可以替换成class
1.自动类型推导,必须要推导出一致的数据类型T
2.模板必须要确定出T的数据类型,才可以使用
template<class T>
void fite()
{
cout << "函数调用" << endl;
}
void test()
{
fite();//错误!
fite<int>();//这里不加数据类型是无法调用的,必须告诉编译器数据类型
}
淡云一笔安洛三福 单目>算数运算符>移位>比较>按位>逻辑>三目>赋值
44.函数模板的案例
。。。
45.模板函数是不能想普通函数那样进行隐式类型转化的,这也算一个局限,普通函数可以将字符型转成整型然后和整型相加,模板可以使用显示指定类型这样不会报错。
46.函数模板可以重载
但是会优先调用普通函数
通过空模板的参数列表可以强制调用函数模板
myswap<>(a,b);
还有一种情况会自动调用模板,就是有更好的匹配,调用普通函数会发生隐式类型转化那么编译器会调用函数模板
有了模板就最好不要普通函数了!!
47.模板的局限性
模板不是万能,有些特定的数据类型需要用到特殊的实现
可以在函数前面加一个template<>
函数中的T就可以换成具体的类型了
48.类模板
就是在template下面紧跟着类就是类模板了
template<class nametype, class agetype>
class Person
{
public:
nametype name;
agetype age;
};
//如何用?
Person<string, int>p1("xxx",99);
49.类模板与函数模板的区别
类模板是没有自动类型推导的使用方式,只能用指定显示类型。
类模板在模板的参数列表中有默认参数比如
template<class nametype, class agetype = int>
50.类模板中的成员函数在调用的时候才会创建,因为不调用不知道类型,调用了编译器才知道成员是什么类型才能判断是否可以调用函数
51.类模板对象做函数参数
1.指定传入类型就是加个<>里面标明参数类型
2.参数模板化,在调用函数参数上加一个模板template<class T1,class T2>
3.整个类模板化
52.类模板与继承
因为编译器无法得知继承下来的模板是什么类型,所以继承基类时需要加一个类型说明
template<class T>
class base
{
T obj;
};
class son:public base<int>//告诉编译器我继承了这个类型的类
如果要想灵活运用基类类型需要再加一个模板
template<class T>
class base
{
T obj;
};
template<class T1, class T2>
class son:public base<T1>
{
T2 obj;
};
//然后子类创建对象需要这样
son2<int,char>s1;//说明父类的那个成员是int型继承,子类的成员是char型
53.类模板成员函数的类外实现
template<class T1,class T2>
class Person
{
public:
Person();
T1 name;
T2 age;
};
template<class T1,class T2>
Person<T1,T2>::Person(T1 name, T2 age)//和普通的类外实现多了个尖括号
{
this->name = name;
this->age = age;
}
54.类模板的分文件编写
无法解析外部命令
直接包含源文件,因为可能出现模板类中成员函数没有创建实例导致错误的情况,解决方法有两种
一种是直接包含原码.cpp文件
一种是用.hpp,一般是类模板用的
55.类模板与友元
最好是类内实现直接写就ok,类外实现非常麻烦,需要事先让编译器知道很多东西
56.类模板的案例
//myarry.hpp文件
1 #ifndef __MYARRY_HPP_
2 #define __MYARRY_HPP_
3 #include<iostream>
4 using namespace std;
5 template<class T>
6 class myarry
7 {
8 public:
9 myarry(int m_rong)
10 {
11 this->m_rong = m_rong;
12 this->m_size = 0;
13 this->paddress = new T[this->m_rong];
14 }
15
16 ~myarry()
17 {
18 if(this->paddress != NULL)
19 {
20 delete[] paddress;
21 this->paddress = NULL;
22 }
23 }
24
25 myarry(const myarry& m)
26 {
27 int i;
28 this->m_rong = m.m_rong;
29 this->m.m_size = m.m_size;
30 this->paddress = new T[m.m_rong];
31 for(i = 0; i < this->m_size; i++)
32 {
33 this->paddress[i] = m.paddress[i];
34 }
}
36
37 myarry& operator=(const myarry& m)
38 {
39 if(this->paddress != NULL)
40 {
41 delete[] paddress;
42 paddress = NULL;
43 this->m_size = 0;
44 this->m_rong = 0;
45 }
46 int i;
47 this->m_rong = m.m_rong;
48 this->m_size = m.m_size;
49 this->paddress = new T[m.m_rong];
50 for(i = 0; i < this->m_size; i++)
51 {
52 this->paddress[i] = m.paddress[i];
53 }
54 return *this;
55
56 }
57 void push_back(const T& val)
58 {
59 if(this->m_rong == this->m_size)
60 {
cout << "容量已满" << endl;
62 return;
63 }
64 this->paddress[this->m_size] = val;
65 this->m_size++;
66 }
67 void pop_back()
68 {
69 if(this->m_size == 0)
70 {
71 cout << "已经没有任何元素了" << endl;
72 return;
73 }
74 this->m_size--;
75 }
76
77 T& operator[](int index)
78 {
79 return this->paddress[index];
80 }
81 int getrong()
82 {
83 return this->m_rong;
84 }
85 int getsize()
86 {
87 return this->m_size;
88 }
89 private:
90 T* paddress;//指针指向堆区开辟的地址
91 int m_rong;
92 int m_size;
93 };
94
95 #endif
//main.cpp文件
1 #include<iostream>
2 #include<string>
3 using namespace std;
4
5 #include"myarry.hpp"
6
7 class Person
8 {
9 public:
10 Person()
11 {
12 cout << "无参构造函数调用" << endl;
13 };
14 Person(int age, string name)
15 {
16 cout << "有参构造函数调用" << endl;
17 m_age = age;
18 m_name = name;
19 }
20 int m_age;
21 string m_name;
22 };
23 void printfmyarry(myarry<int>& m)
24 {
25 cout << "容量为:" << m.getrong() << endl;
26 cout << "目前大小为:" << m.getsize() << endl;
27 for(int i = 0; i < m.getsize(); i++)
28 {
29 cout << " " << m[i] ;
30 }
31 cout << endl;
32 }
33 void printfmyarrym(myarry<Person>& p)
34 {
35 for(int i = 0; i < p.getsize(); i++)
36 {
37 cout << "年龄:" << p[i].m_age << endl;
38 cout << "姓名:" << p[i].m_name << endl;
39 cout << "--------------------" << endl;
40 }
41 }
42 void test()
43 {
44 myarry<int> m(10);
45 for(int i = 0; i < 5; i++)
46 {
47 m.push_back(i);
48 }
49 cout << "插入好的打印输出为:" << endl;
50 printfmyarry(m);
51 myarry<int> m1(100);
52 m = m1;
53 printfmyarry(m1);
54 Person p1(18,"xxfvx");
55 Person p2(12,"xxxfw");
56 Person p3(13,"xxxwqd");
57 Person p4(18,"xxsx");
58 myarry<Person> mp(5);
59 mp.push_back(p1);
60 mp.push_back(p2);
61 mp.push_back(p3);
62 mp.push_back(p4);
63 printfmyarrym(mp);
64
65 }
66 int main()
67 {
68 test();
69 return 0;
70 }
//结果
插入好的打印输出为:
容量为:10
目前大小为:5
0 1 2 3 4
容量为:100
目前大小为:0
有参构造函数调用
有参构造函数调用
有参构造函数调用
有参构造函数调用
无参构造函数调用
无参构造函数调用
无参构造函数调用
无参构造函数调用
无参构造函数调用
年龄:18
姓名:xxfvx
--------------------
年龄:12
姓名:xxxfw
--------------------
年龄:13
姓名:xxxwqd
--------------------
年龄:18
姓名:xxsx
--------------------
57.STL
六大组件:容器,算法,迭代器,仿函数,适配器,空间配置器
容器是放东西的
容器分为序列式容器和关联式容器
序列式强调值的排序容器中的每个元素都有自己的固定位置
关联式为二叉树结构,各个元素之间没有严格上的顺序关系
算法解决问题的Algorithm
质变算法会改变数据改变内容
非质变算法比如查看不会改动
迭代器
算法需要用到迭代器才能访问容器中的数据
58.vector存放自定义数据类型
//迭代器iterator
1 #include<iostream>
2 #include<string>
3 #include<algorithm>
4 #include<vector>
5
6 using namespace std;
7
8 class Person
9 {
10 public:
11 Person(int age, string name)
12 {
13 this->age = age;
14 this->name = name;
15 }
16 string name;
17 int age;
18 };
19 int main()
20 {
21 vector<Person> v;
22 Person p1(10,"aaa");
23 Person p2(123,"bbb");
24 Person p3(19,"ccc");
25 Person p4(18,"ddd");
26 Person p5(12,"eee");
27 v.push_back(p1);
28 v.push_back(p2);
29 v.push_back(p3);
30 v.push_back(p4);
31 v.push_back(p5);
32 for(vector<Person>::iterator it = v.begin(); it != v.end(); it++)
33 {
34 cout << "名字:" << it->name << "年龄:" << it->age << endl;
35 }
36 return 0;
37 }
//结果
名字:aaa年龄:10
名字:bbb年龄:123
名字:ccc年龄:19
名字:ddd年龄:18
名字:eee年龄:12
59.容器嵌套容器
1 #include<iostream>
2 #include<vector>
3 #include<algorithm>
4 using namespace std;
5
6
7 int main()
8 {
9 vector<vector<int> > v;
10 vector<int> v1;
11 vector<int> v2;
12 vector<int> v3;
13 vector<int> v4;
14 for(int i = 0; i < 5; i++)
15 {
16 v1.push_back(i + 1);
17 v2.push_back(i + 2);
18 v3.push_back(i + 3);
19 v4.push_back(i + 4);
20 }
21
22 v.push_back(v1);
23 v.push_back(v2);
24 v.push_back(v3);
25 v.push_back(v4);
26
27 for(vector< vector<int> >::iterator it = v.begin(); it != v.end(); it++)
28 {
29 for(vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
30 {
31 cout << *vit << " " ;
32 }
33 cout << endl;
34 }
35 return 0;
36 }
//结果
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
60.string容器
string本质是一个类,类内维护一个char*指针
string的构造函数
void test()
{
string s1;//默认构造
const char *str = "hello world";//有参构造
string s2(str);
string s3(s2);//拷贝构造
string(10,'a');//结果10个a
}
还有一些函数比如assign append 还有一些重载
+=号可以直接把字符串追加到字符串后
61.string查找和替换
str1.find(“de”)//查找de这几个字符第一次出现的位置 没有找到会返回-1,从左往右查找
rfind从右往左查找用法一样
str1.replace(1,3,“1111”);//将1到3的字符换成1111,四个全部进去了
插入删除 这些函数基本看了就会 用起来也非常方便
str1.insert(1,“111”);//为1位置插入111
str1.erase(1,3);//在第1个位置删除三个字符
62.string子串
string str1 = "21312313";
string subSTR = str1.substr(1,3);
//结果131
61.vector
vector数据结构和数组非常相似,也称为单端数组
和数组不同的地方在于数组是静态的空间,而vector是动态的空间,如果要插入数据不是在原有的空间后插入数据,而是会将原油数据存在新的一块空间然后这个新空间会比原来的大,多出的这一块空间就可以存放新插入的数据,原空间将被释放。
vector前端是封闭的,拥有的迭代器begin() end()是可以随机访问的迭代器很强悍
vector的构造函数
4个
vector<T> v;
//将这个区间的元素拷贝给本身,前闭后开
vector<T>v1(v.begin(),v.end());
//n个elem方式构造
vector<T>v2(10,100)//10个100
//拷贝构造
vector<T>v3(v2);
具体实现如下
1 #include<iostream>
2 #include<vector>
3
4 using namespace std;
5
6 void printfvector(vector<int>& v)
7 {
8 for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
9 {
10 cout << *it << " ";
11 }
12 cout << endl;
13 }
14 void test()
15 {
16 vector<int> v1;
17 for(int i = 0; i < 10; i++)
18 {
19 v1.push_back(i);
20 }
21 printfvector(v1);
22 vector<int> v2(v1.begin(), v1.end());
23 printfvector(v2);
24 vector<int> v3(10,5);
25 printfvector(v3);
26 vector<int> v4(v3);
27 printfvector(v4);
28 }
29 int main()
30 {
31 test();
32 }
//结果
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
5 5 5 5 5 5 5 5 5 5
5 5 5 5 5 5 5 5 5 5
赋值操作
直接等号赋值 等号是被重载的
比如v1 = v2;
还有就是用到v3.assign(v1.begin(),v1,end());
还有就是n个elem
v4.assign(10,5);
vector容量和大小
empty();//判断是否为空 返回bool
capacity();//容器容量
size();//容器大小
resize(int a, int b);//重新指定大小 a过长会用b填充 短了就被删除
插入
push_back(T);//。。。尾插用的多
pop_back(T);//尾删
v.insert(v.begin(),100);//头部插100
v.erase(v.begin())//删除
v.clear();//清空
数据存取操作
v1[i]也可以输出用for
v1.at(i)用for
v1.front();返回第一个元素
v1.back()返回最后一个元素
容器的互换
v1.swap(v2)//v1和v2就互换了
实际的作用就是当容器存入了大量的数据容量被阔的很大,然后来了一个resize将大小变的很小但是容量却还是和以前一样大,这样就造成了空间的浪费,这个时候就用swap将容器容量进行缩减避免空间的浪费,具体实现如下
//这个v本来容量巨大但大小很小
vector<int>(v).swap(v)
这段代码的意思是,前半部分用拷贝构造函数创建了一个匿名对象,这个匿名对象容量小大小为v,这个时候再和v进行交换就起到了v容量减小的操作了,虽然匿名对象指向了v原来的大空间但是匿名对象的特性就是执行完就会被系统释放,这样就刚好回收了v原来的那段大空间。妙啊。
预留空间
reserve(int len);
预留了一段空间 但是没有初始化是不允许访问的
可以用下面这段代码得知容器扩展了几次空间
int num = 0;
int *p = NULL;
for(int i = 0; i<10000; i++)
{
v.push_back(i);
if(p != &v[0])
{
p = &v[0];
num++;//因为vector每次扩展都会开辟新空间 所以记录元素首地址 妙啊
}
}
所以预留一波,就不需要不停的找新空间造成的时间浪费了
v.reserve(10000);几个数据写多少
vector的优点在于访问一个元素的速度快 但是如果希望头插一个元素那么vector因为前封闭的特性效率就会低,且数据量越大效率越低
但是deque容器就可以高效的完成头插
deque容器
双端数组
相比vector多了头部的操作
头部插入push_front()
头部删除pop_front()
deque容器有一个中控器去维护着一段段空间,中控器会记录每一段缓冲空间的地址,这使得deque使用起来像一片连续的地址,因为需要通过地址去访问元素所以deque的访问速度比vector来的低
deque的迭代器也可以随机访问
deque的构造函数
和vector的构造方法一致
有区间构造,拷贝构造,n个元素构造,有参构造。还有assign赋值
之前想通过const修饰传入容器的实参,结果直接报了一段很长的错误,问题就出在iterator迭代器身上,需要将其改为const_iterator
如下:
6 void printfvector(const vector<int>& v)
7 {
8 for(vector<int>::const_iterator it = v.begin(); it != v.end(); it++)
9 {
10 cout << *it << " ";
11 }
12 cout << endl;
13 }
deque是没有容量这个概念的,所以调用capacity()会报错不要奇怪
deque容器的插入,insert可以区间插入比如
d3.insert(d3.begin(),d2.begin(),d2.end());
在头部插入d2
对于数据存取
d2[i]或者d2.at(i)都可以获取到相应位置的数据,d2.front(),d2.back()访问第一个和最后一个元素
deque的排序
可以自己用冒泡什么什么的排序排
但有官方的排序 白嫖就完事了
使用sort(d1.begin(),d1.end());就好了加一个algorithm头文件
支持随机访问的的容器就可以使用sort
stack容器
stack是一种先进后出的数据结构,不予许有遍历的行为
可以默认构造和拷贝构造 也可以直接=号赋值
有top pop push size等一些接口
queue容器
是一个先进先出的容器也就是队列,push入队 pop出队
队头 队尾
构造有两个默认和拷贝
接口back front 分别返回最后一个和第一个元素
list容器 链表
优点是可以任意的快速插入删除元素
容器遍历速度没有数组快 占用的空间比数组大
STL中的提供的链表是一个双向链表,由于链表的储存空间不是连续的所以它的迭代器只能进行前移和后移,叫双向迭代器。
动态分配不会造成内存浪费和溢出,不需要大量的移动元素
但是会耗费更多的空间和时间
构造方式
默认构造 拷贝构造 区间构造 n个元素构造
插入insert(位置,数据)
删除erase(位置)
数据存储接口
front() back()返回第一个和最后一个元素
reverse()反转
sort();不支持随机访问的容器不能使用这个算法,只能使用容器中自带的一些算法。
所以这个sort需要这样用,L.sort();
当是自定义数据类型需要告诉sort排序规则,要用到
bool comparemm(Person &p1, Person &p2)
{
if(p1.age == p2.age)
{
return p1.height > p2.height;//降序
}
else
{
return p1.age < p2.age;//升序
}
}
set容器
特点:所有元素会自动在插入时就排好序,set容器不予许插入重复的值
这个容器只有insert可以插入时数据,没有push_back什么什么的方法
multiset和set的区别就在于multiset可以插入重复的数据
pair对组
make_pair(val,val);比较简单