C++学习笔记(1)
《21天学通C++(第8版)》Siddhartha Rao著
auto
-
auto自动推断类型(C++11新增)
//使用auto必须进行初始化 auto largeNumber = 25000000000000; //编译器根据初始值来确定合适的值,这里应该为long long
-
auto迭代
int a[] = {1,2,3,4,5}; for(auto temp : a) { cout<<temp<<endl; } //auto& 可以修改数组a的值 for(auto& temp : a) { temp++; cout<<temp<<endl; }
const; constexpr
-
如果变量的值不应该被改变,就应该将其声明为常量。
const double PI = 3.1415926; constexpr double GetPi(){ return 22.0/7;} //使用constexpr声明常量表达式 enum CardinalDirections { North, South, East, West }; //不要使用#define定义常量,该方法已被摒弃
不要使用#define定义常量,该方法已被摒弃。
动态数组
std::vector
C++字符串
- 避免C风格字符串:以’\0’作为终止空字符。
- 使用std:string,包含
#include<string>
内联函数
- 当函数非常简单,且需要降低开销的时候,可以声明为内联函数。
lambda函数(C++11新增)
指针
-
指针是存储内存地址的变量。
int *p = NULL;//初始化,未初始化的指针包含的值是垃圾值(随机的),可能导致问题(非法访问)。
-
使用引用运算符
&
获取变量的地址。 -
解除引用运算符
*
访问指向地址所存储的值。 -
const修饰指针
-
修饰 指针包含的地址为常量,无法修改,但可修改指针指向的数据。
int daysInMonth = 30; int* const pdaysMonth = &daysInMonth; *pDaysMonth = 31;//OK! int daysInLUnarMonth = 28; pDaysMonth = &daysInLUnarMonth;//Not OK!
-
修饰 指针指向的数据为常量,不能修改,但可以修改指针包含的地址。
int daysInMonth = 30; const int* pdaysMonth = &daysInMonth; *pDaysMonth = 31;//Not OK! int daysInLUnarMonth = 28; pDaysMonth = &daysInLUnarMonth;//OK! int* newMonth = pDaysMonth;//Not OK!
-
修饰 指针包含的地址以及它指向的值都是常量,不能修改。
int daysInMonth = 30; const int* const pdaysMonth = &daysInMonth; *pDaysMonth = 31;//Not OK! int daysInLUnarMonth = 28; pDaysMonth = &daysInLUnarMonth;//Not OK!
-
-
数组可赋给指针。
-
使用指针常见错误:
- 内存泄漏
- 指针指向无用的内存单元:未初始化
new(std::nothrow)
- new(std::nothrow) 顾名思义,即不抛出异常,当new一个对象失败时,默认设置该对象为NULL,这样可以方便的通过if(p == NULL) 来判断new操作是否成功。
- 普通的new操作,如果分配内存失败则会抛出异常,虽然后面一般也会写上if(p == NULL) 但是实际上是自欺欺人,因为如果分配成功,p肯定不为NULL;而如果分配失败,则程序会抛出异常,if语句根本执行不到。
- 因此,建议在c++代码中,凡是涉及到new操作,都采用new(std::nothrow),然后if(p==NULL)的方式进行判断
#include <iostream>
using namespace std;
int main()
{
int* points = new(nothrow) int[10];
if(points)
{
cout<<"Memory allocation successed. Ending program"<<endl;
delete[] points;
}
else{
cout<<"Memory allocation failed. Ending program"<<endl;
}
return 0;
}
引用
-
引用是相应变量的别名。
int flag = 30; int& temp = flag;
-
const修饰引用:禁止通过引用修改它所指向变量的值。
-
引用向函数传递参数的优点之一:可避免将形参赋值给形参,从而极大提高性能。
#include <iostream> using namespace std; //如果函数接收非常大的对象,则按值传递的开销将非常大,通过使用引用,可极大地提高函数调用的效率。别忘了将const用于引用参数,除非函数需要将结果储存于参数中。 void GetSquare(const int& number, int& result) { result = number*number; } int main() { int number = 0; cin >> number; int square = 0; GetSquare(number,square); cout<<number<<"^2 = "<<square<<endl; return 0; }
类和对象
拷贝构造函数
什么是浅拷贝?只有普通变量初始化的拷贝构造函数就浅拷贝。咋算普通变量?如int,char, string…不涉及指针变量。可以直接使用默认拷贝构造函数。
什么是深拷贝?不用默认拷贝构造函数,自己显式定义一个拷贝构造函数,并且在其内部再次分配动态内存,这就是深拷贝。总的来说,就是类中涉及到指针变量,需要在拷贝构造函数内部申请一遍。
使用浅拷贝还是深拷贝,最直接判断方式是看一下类中有没有指针变量。
-
浅拷贝:如果类中含有指针变量,导致多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏。
#include <iostream>
using namespace std;
// 深浅拷贝操作
class Person
{
public:
// 无参构造函数
Person()
{
cout << "Person的构造函数调用" << endl;
}
// 有参构造函数
Person(int a, int h)
{
m_Age = a;
m_Height = new int(h);
cout << "Person的有参构造函数调用" << endl;
}
// 析构造函数
~Person()
{
// 将堆区开辟的空间释放掉
if(m_Height != NULL)
{
delete m_Height;
m_Height = NULL; // 防止野指针出现
}
cout << "Person的析构造函数调用" << endl;
}
// 自己实现拷贝构造函数,解决浅拷贝带来的问题
//务必将接受源对象的参数声明为const引用
Person(const Person &p)
{
cout << "Person拷贝造函数调用" << endl;
m_Age = p.m_Age;
// m_Height = p.m_Height; // 编译器默认实现就是这行代码(浅拷贝)
// 深拷贝操作
m_Height = new int(*p.m_Height);
}
int m_Age; // 年龄
int *m_Height; // 身高
};
void test01()
{
Person p1(18, 160);
cout << "Person的年龄:" << p1.m_Age << "身高为:" << *p1.m_Height << endl;
Person p2(p1); // 执行来浅拷贝操作(调用默认的拷贝函数)
cout << "P2的年龄:" << p1.m_Age << "身高为:" << *p2.m_Height << endl;
}
int main(int argc, char const *argv[])
{
/* code */
test01();
return 0;
}
单例类
28.C+± 单例类模板(详解) - 诺谦 - 博客园 (cnblogs.com)
explicit
-
explicit关键字的作用就是防止类构造函数的隐式自动转换.
-
explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了.
-
explicit关键字只需用于类内的单参数构造函数前面。由于无参数的构造函数和多参数的构造函数总是显示调用,这种情况在构造函数前加explicit无意义。
-
google的c++规范中提到explicit的优点是可以避免不合时宜的类型变换,缺点无。所以google约定所有单参数的构造函数都必须是显示的,只有极少数情况下拷贝构造函数可以不声明称explicit。例如作为其他类的透明包装器的类。
-
effective c++中说:被声明为explicit的构造函数通常比其non-explicit兄弟更受欢迎。因为它们禁止编译器执行非预期(往往也不被期望)的类型转换。除非我有一个好理由允许构造函数被用于隐式类型转换,否则我会把它声明为explicit,鼓励大家遵循相同的政策。
继承(inheritance)
-
is-a:公有继承
子类、子类的对象可以访问基类的public成员。
-
has-a:
-
保护继承
子类可以访问基类的public和protect方法,但子类的对象不能访问基类的public成员。
-
私有继承
子类、子类的对象不能访问基类的public成员。
-
-
使用final禁止继承(C++11)
多态(polymorphism)
使用虚函数实现多态
-
对于将被派生类覆盖的基类方法,务必将其声明为虚函数。
-
对基类析构函数,务必提供一个虚析构函数。
纯虚函数
- 纯虚函数导致类变成抽象基类,且在派生类中必须提供虚函数的实现。
- 抽象基类无法被实例化。
//抽象基类(ABC)
class AbstractBase
{
public:
virtual void DoSomeThing() = 0; //纯虚函数
};
//上述声明告诉编译器,AbstractBase的派生类必须实现方法DoSomeThing();
class Derived: public AbstractBase
{
public:
void DoSomeThing()
{
cout<<"Implemented virtual function"<<endl;
}
};
虚继承
-
如果派生类可能被用作基类,派生它 最好使用关键字virtual:
class Derived1 : public virtual Base { }; class Derived2 : public virtual Base { }; //并且使用关键字final禁止将SubDerived作为基类 class SubDerived final: public Derived1, public Derived2 { };
在继承层次结构中,继承多个从同一个类派生而来的基类时,如果这些基类没有采用虚继承,将导致二义性。这种二义性被称为菱形问题(Diamond Problem)。
override(C++11)
在派生类中声明要覆盖基类函数的函数时,务必使用override。
override提供了一种强大的途径,让程序员明确地表达对基类的虚函数进行覆盖的意图,进而让编译器做如下检查:
- 基类函数是否是虚函数?
- 基类中相应虚函数的特征标识是否与派生类中被声明为override的函数完全相同?
class Base {
virtual void f();
};
class Derived : public Base {
void f() override; // 表示派生类重写基类虚函数f
void F() override;//错误:函数F没有重写基类任何虚函数
};
- overide作用:在派生类中提醒自己要重写这个同参数函数,不写则报错。
运算符
- 理论上来说,前缀运算符优于后缀运算符。
++value优于value++
。 - 对于有原始指针的类,务必实现拷贝构造函数和重载赋值运算符。
运算符重载
return_type operator operator_symbol(...parameter list...)
#include<iostream>
using namespace std;
class Box
{
double length; //长度
double width; //宽度
double height; //高度
public:
Box(){}
Box(double length,double width, double height):length(length),width(width),height(height){}
~Box(){}
void print(void)
{
cout<<length<<","<<width<<","<<height<<endl;
}
//重载 + 运算符, 实现Box相加
Box operator + (const Box& b)
{
Box box;
box.length = this->length + b.length;
box.width = this->width + b.width;
box.height = this->height + b.height;
return box;
}
//重载前缀运算符 ++
Box operator ++ ()
{
++this->length;
++this->width;
++this->height;
return *this;
}
//重载后缀运算符 --
//注意,int 在 括号内是为了向编译器说明这是一个后缀形式,而不是表示整数。只是为了区分前置后置
Box operator -- (int) //加上int
{
//保存原始值
Box temp = *this; //隐式调用转换函数
//Box temp(*this); //调用默认拷贝构造函数
//Box temp(length,width,height);
--this->length;
--this->width;
--this->height;
return temp; //返回原始值
}
/*在这里,有一点很重要,我们需要把运算符重载函数声明为类的友元函数,这样我们就能不用创建对象而直接调用函数。习惯上人们是使用 cin>> 和 cout<< 的,得使用友元函数来重载运算符,如果使用成员函数来重载会出现 d1<<cout; 这种不自然的代码。
*/
//相当于在整个全局声明了一个重载运算符。当重载的运算符函数是全局函数时,需要在类中将该函数声明为友元
//重载 >>
friend istream& operator >> (istream& input, Box& b)
{
input >> b.length >> b.width >> b.height;
return input;
}
//重载 <<
friend ostream& operator << (ostream& output,const Box& b)
{
output<<b.length<<","<<b.width<<","<<b.height;
return output;
}
};
int main()
{
Box box1(5.0,6.0,7.0);
Box box2(8.0,9.0,10.0);
Box box3 = box1 + box2;
box3.print(); //13,15,17
Box ox4 = ++box3;
box4.print(); //14,16,18
Box box5 = box3--;
box5.print(); //14,16,18
box3.print(); //13,15,17
Box box6;
cin >> box6;
cout <<box6<<endl;
return 0;
}
C++类型转换运算符
destination_type result = cast_operator<destination_type> (object_to_cast)
static_cast
- 用于在相关类型的指针之间转换。
Derived objDerived1 = new Derived();
Base* objBase = &objDerived1; //OK!向上转换,无需显式转换
Derived* objDerived2 = objBase;//Error! 向下转换,需要显式转换
//OK!
Derived* objDerived2 = static_cast<Derived*>objBase;
//static_cast只验证指针类型是否相关,而不执行任何运行阶段的检查。
Base* objBase = new Base();
Derived* objDer = static_cast<Derived*>objBase;//Not Error!但有可能导致意外结果。
- 将原本隐式的标准数据类型的类型转换 转换为 显式。
dynamic_cast
-
与static_cast相反,dynamic_cast在运行阶段执行转换,可检查dynamic_cast操作的结果,以判断类型转换是否成功。
Base* objBase = new Derived(); Derived* objDer = dynamic_cast<Derived*>(objBase); //务必检查dynamic_cast的返回值,看它是否有效。如果返回值为NULL,说明转换失败。 if(objDer) { objDer->CallDerivedFunction(); }
-
这种在运行阶段识别对象类型的机制称为 运行阶段类型识别(runtime type identification, RTTI)
reinterpret_cast
- 强制转换。
- 应尽量避免使用reinterpret_cast。
const_cast
-
让程序员能够关闭对象的访问限制符const。
//DisplayMembers()本应该为const的,但没有这样定义。并且类SomeClass属于第三方库,无法对其进行修改。 void DisplayAllData(const SomClass& object) //显然 这里使用const是应该的 { object.DisplayMembers();//编译错误 //原因:使用const引用调用non-const的成员。 } void DisplayAllData(const SomClass& object) { SomeClass& refData = const_cast<SomeClass&>(object); object.DisplayMembers();//OK! }
-
非必要不使用const_cast来调用非const函数。这样导致的结果不可预料!
除dynamic_cast外的类型转换都是可以避免的;应尽量避免使用类型转换。
宏
使用#define定义常量
- 预处理只是进行死板的文本替换,而不检查替换是否正确。
#define PI 3.1415926 //再预处理器看来,无法确定其数据类型
//定义常量时,更好的方法是使用const
const double PI = 3.1415926;
typedef double MY_DOUBLE; //#define MY_DOUBLE double
使用宏避免多次包含
-
预处理器编译指令:
#ifndef
#endif
#ifndef _HEAD_H_ #define _HRAD_H_ //head.h的内容 #endif //end of head.h
使用#define编写宏函数
- 注意使用括号保证优先级
使用assert宏验证表达式
- 需包含
#include<assert.h>
。 - assert用来调试,对输入的参数进行验证。
- assert()在发布模式下不可用。
- 推荐在代码中大量使用assert(),虽然在发行版本被禁用,但对提高代码质量很有帮助。
尽量不要自己编写宏函数;使用const常量而不是宏常量。宏不是类型安全的。
模板
- 模板无疑是C++语言中最强大的特性之一。模板是类型安全的。
- 在编译器看来,仅当模板被使用时,其代码才存在。
- 对模板来说,实例化是指使用一个或多个模板参数来创建特定的类型。
模板声明 template
template<typename objType>
模板函数
#include<iostream>
using namespace std;
//模板函数
template<typename objType>
const objType& GetMax(const objType& value1, const objType& value2)
{
return value1 > value2 ? value1 : value2;
}
int main()
{
int num1 = 25, num2 = 40;
//int maxVal = GetMax<int>(num1,num2); //模板函数可以不显式指定类型,但模板类必须显式指定。
int maxVal = GetMax(num1,num2);
cout<<maxVal<<endl;
return 0;
}
模板类
#include<iostream>
using namespace std;
//模板类
template<typename T1, typename T2 = T1> //T2的类型默认为T1
//template<typename T1 = int, typename T2 = string> //声明默认参数的模板
class MyTemplate
{
T1 member1;
T2 member2;
public:
MyTemplate(const T1& t1, const T2& t2):member1(t1),member2(t2){}
const T1 getObj1() const{return member1;} //返回值为const ; this指针为const
T2 getObj2(){return member2;}
//...
};
//使用
int main()
{
MyTemplate<int ,double> temp1(10,19.5);
MyTemplate<int ,string> temp2(10,"hello world!");
cout<<temp1.getObj1()<<endl;
cout<<temp2.getObj2()<<endl;
return 0;
}
参数数量可变的模板(C++11)
泛化之美–C++11可变模版参数的妙用 - qicosmos(江南) - 博客园 (cnblogs.com)
//这里class关键字表明T是一个类型,后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字,它的作用同class一样表明后面的符号为一个类型
//template <class... T>
template<typename... T>
void f(T... args);
sizeof...(args) //sizeof...()打印可变参的个数
上面的可变模版参数的定义当中,省略号的作用有两个:
- 声明一个参数包T… args,这个参数包中可以包含0到任意个模板参数;
- 在模板定义的右边,可以将参数包展开成一个一个独立的参数。
我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数。
展开可变模版参数函数的方法一般有两种:一种是通过递归函数来展开参数包,另外一种是通过逗号表达式来展开参数包。
-
递归函数方式展开参数包
#include <iostream> using namespace std; //通过递归函数展开参数包,需要提供一个参数包展开的函数和一个重载的递归终止函数,递归终止函数正是用来终止递归的。 //递归终止函数 template <typename T> void print(T head) { cout << "parameter " << head << endl; } //展开函数 template <typename T, typename ...Args> void print(T head, Args... rest) { cout << "parameter " << head << endl; print(rest...); } int main(void) { print(1,2,3,4); return 0; }
-
逗号表达式展开参数包
#include <iostream> using namespace std; //这种方式需要借助逗号表达式和初始化列表 template <typename T> void printarg(T t) { cout << t << endl; } template <typename ...Args> void expand(Args... args) { int arr[] = {(printarg(args), 0)...}; } int main(void) { expand(1,2,3,4); return 0; }
static_assert(C++11)
-
执行编译阶段断言:禁止不希望的模板实例化。
//禁止针对int实例化模板 static_assert(sizeof(T) != sizeof(int),"No in please!"); //不满足条件禁止编译
标准模板库
STL容器
容器是用于存储数据的STL类。
顺序容器
顾名思义,顺序容器按顺序存储数据,如数组和列表。插入速度快,查找满。
std::vector
:常规动态数组。std::deque
:动态数组,但允许在开头插入或删除。std::list
:双向链表。std::forward_list
:单向链表。
关联容器
按指定的顺序存储数据。插入慢,查找快。
std::set
:集合std::unordered_set
:std::map
:键-值对。std::unordered_map
:std::multiset
:类似set,不要求值唯一的,可存储多个值相同的项。std::unordered_multiset
:std::multimap
:类似map,不要求键是唯一的。std::unordered_multimap
:
容器适配器(Container Adapter)
std::stack
:栈std::queue
:队列std::priority_queue
:
STL迭代器
STL算法
STL字符串类
std::string
:简单字符串std::wstring
:宽字符串
STL string类
- #include
- std::string 具体化的是std::basic_string模板类
实例化string
#include <string>
#include <iostream>
using namespace std;
void print(const string& str,const string& s)
{
cout<<s<<": "<<str<<endl;
}
int main()
{
string str0; //生成空字符串str
string str1 = "Hello String!";
const char* Cstr = "Hello Char*";
string str2(str1,6); //从"索引为5"的位置开始拷贝
string str3(Cstr,5); //将C字符串前5个字符作为字符串s的初值。
string str4("Hello String!",5);
string str5(10,'a'); //生成一个字符串,包含n个c字符
string str6(str1,0,5);//将字符串str内“始于index且长度顶多n”的部分作为字符串的初值
string str7(str5.begin(),str5.begin()+5);
print(str0,"str0");
print(str1,"str1");
print(str2,"str2");
print(str3,"str3");
print(str4,"str4");
print(str5,"str5");
print(str6,"str6");
print(str7,"str7");
/*输出
str0:
str1: Hello String!
str2: String!
str3: Hello
str4: Hello
str5: aaaaaaaaaa
str6: Hello
str7: aaaaa
*/
return 0;
}
string增删改查
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
//遍历获取元素
void display()
{
//size(),length() //返回字符数量
string str("hello world!");
//size_t 作为unsigned int 和unsigned long 的别名,编译器根据不同的系统类型替换标准类型,旨在增强移植性
for(size_t i = 0; i < str.length(); ++i)
{
cout<<"str["<<i<<"]: "<<str[i]<<endl;
}
cout<<endl;
//逆序迭代器方式
for(auto t = str.rbegin(); t != str.rend(); ++t)
{
cout<<*t<<",";
}
cout<<endl;
cout<<str.c_str()<<endl;//将内容以C_string返回
}
//拼接字符串
void append()
{
//+=,append()
//push_back() //在尾部添加字符
string str1("Hello World!");
string str2("Hello Programer!");
str1 += str2; //+=
cout<<" str1 += str2, str1: "<<str1<<endl;
str1.append(str2); //append()
cout<<" str1.append(str2)2, str1: "<<str1<<endl;
str2.push_back('?');
cout<<" str2.push_back('?'), str2: "<<str2<<endl; //push_back()在末尾添加字符
}
//查找字符(串)
void find()
{
string s("dog bird chicken bird cat");
cout << s.find("bird") << endl; //查找子串s,返回找到的位置索引,
cout << (int)s.find("pig") << endl; //-1表示查找不到子串
cout << s.rfind("bird") << endl; //反向查找子串s,返回找到的位置索引,
cout << s.rfind('i') << endl;
cout << s.find_first_of("13r98") << endl; //查找第个属于某子串的字符
cout << s.find_first_not_of("dog bird 2006") << endl;//查找第一个不属于某字符串的字符
}
//删除
void erase()
{
string s("123456789abcd!");
//iterator erase(iterator p);//删除字符串中p所指的字符
s.erase(s.begin());
cout << s << endl; //out: 23456789abcd!
//iterator erase(iterator first, iterator last);//删除字符串中迭代器区间[first,last)上所有字符
s.erase(s.begin()+2,s.begin()+3);
cout << s << endl; //out: 2356789abcd!
//string& erase(size_t pos = 0, size_t len = npos);//删除字符串中从索引位置pos开始的len个字符
s.erase(0,2);
cout << s << endl; //out: 56789abcd!
//void clear();//删除字符串中所有字符
s.clear();
cout << s << endl;
}
//比较
void compare()
{
string s1("abcdef"), s2("abc");
//相等返回0,大于返回 正数,小于返回 负数
cout << s1.compare("abcdef") << endl; //相等,打印0
cout << s1.compare(s2) << endl; //s1 > s2,打印 >0
cout << s1.compare("abyz") << endl; //s1 < "abyz",打印 <0
cout << s1.compare(0,3,s2) << endl; //s1的前3个字符==s2,打印0
}
//反转算法
//需要 头文件是#include <algorithm>
void reverse()
{
string str("hello string!");
cout<<"str: "<<str<<endl;
reverse(str.begin(),str.end());
cout<<"reverse str: "<<str<<endl;
}
int main()
{
//display();
//append();
//erase();
find();
compare();
reverse();
return 0;
}
STL vector类
- 需包含头文件
#include <vector>
实例化vector
#include <vector>
using namespace std;
int main()
{
vector<int> intArray; //默认构造
vector<float> floatArray{20.4, 15.9}; //初始化两个元素的值
vector<int> intArray2(10); //初始化包含10个元素
vector<int> intArray3(10, 1);//初始化包含10个元素,且值都为1
vector<int> intArray4(intArray3); //使用一个实例初始化
return 0;
}
vector增删改查
#include <vector>
#include <iostream>
using namespace std;
//遍历
void traverse()
{
vector<int> temp{10,12,14,16};
for(auto t : temp)
{
cout<<t<<" ";
}
cout<<endl;
cout<<"temp[3]: "<<temp[3]<<endl; //注意 索引不能越界
cout<<"temp[2]: "<<temp.at(2)<<endl; //at()在运行阶段执行检查 更安全
}
void traverse(const vector<int>& temp)
{
cout<<"size = "<<temp.size()<<": ";
for(auto t : temp)
{
cout<<t<<" ";
}
cout<<endl;
}
//插入、删除
void insert()
{
vector<int> integers;
//push_back()在末尾添加元素,首选
integers.push_back(1);
integers.push_back(2);
traverse(integers);
//insert()在指定位置插入元素,效率低
integers.insert(integers.begin()+1, 25); //在第一个元素后面插入25
integers.insert(integers.end(),2,45); //在数组末尾插入2个元素,值都为45
traverse(integers);
vector<int> temp = {15,16};
integers.insert(integers.begin(), temp.begin(), temp.end());//插入temp的值到数组开头
traverse(integers);
//pop_back()从末尾删除元素
integers.pop_back();
traverse(integers);
//清空元素
integers.clear();
traverse(integers);
//判空
if(integers.empty())
{
cout<<"The container is now empty!"<<endl;
}
}
int main()
{
traverse();
insert();
return 0;
}
- size():实际存储的元素数;capacity():总的容量。 数组的大小 < = <= <= 容量。(如果数组进行了插入,vector需要重新分配内部缓冲区,重新分配的逻辑实现是智能的:提前分配更多的容量)。
- 重新分配内部缓冲区时,需要复制容器中包含的对象,这可能降低性能。
STL deque类
- 需包含
#include <deque>
- 具有vector的所有性质。
- 使用
push_front
和pop_front
在开头插入/删除元素。
STL list类
- 需包含
#include<list>
。 - 双向链表。
- 迭代器可以
++
或者--
。 - 虽然STL提供了sort()和remove()两种算法,但是list也提供了这两种算法。这些算法的成员函数版本**确保元素的相对位置发生变化后指向元素的迭代器仍然有效。**
实例化list
#include<list>
#include<iostream>
using namespace std;
int main()
{
list<int> linkInts0; //初始化空链表
list<int> linkInts1(10); //初始化包含10个元素的list
list<int> linkInts2(10,99);//包含10个元素,均为99
list<int> linkInts3(linkInts2);//复制构造
vector<int> vecInts(10,200);
list<int> linkInts4(vecInts.begin(),vecInts.end());//从vector对象中复制
return 0;
}
list增删改查
#include<list>
#include<iostream>
using namespace std;
//遍历
template<typename T>
void display(const T& container)
{
for(auto element = container.cbegin(); element != container.cend(); ++element)
{
cout<<*element<<" ";
}
cout<<endl;
}
//插入
void insert()
{
list<int> listInts;
listInts.push_back(10);
listInts.push_back(20);
listInts.push_front(0);
display(listInts);
//iterator insert(iterator pos, const T& x)
/*
既然是链表,其迭代器就无法进行加减运算,但可以自增自减
*/
//listInts.insert(listInts.begin()+1, 5);// error: no match for ‘operator+’ (operand types are ‘std::_Fwd_list_iterator<int>’ and ‘int’)
listInts.insert(++listInts.begin(), 5); //(插入位置,值)
display(listInts);
//void insert(iterator pos, sizer_type n, const T& x)
listInts.insert(listInts.end(), 2, 0); //(插入位置,插入个数,值)
display(listInts);
/*template<class InputIterator>
void insert(iterator pos, InputIterator f, InputIterator l);
*/
list<int> listInts1;
listInts1.insert(listInts1.begin(), listInts.begin(), listInts.end());
display(listInts1);
}
//删除
void erase()
{
list<int> listInts{0,10,20,30,40};
listInts.erase(listInts.begin()); //删除
display(listInts);
listInts.clear(); //清空
}
//反转、排序
bool Sort_Desc(const int& t1, const int& t2) //二元谓词,告诉sort如何解释小于。
{
return (t1 > t2);
}
void sort()
{
list<int> listInts{-20,20,10,100,40};
//反转
listInts.reverse();
display(listInts);
//排序
//默认排序
listInts.sort();
display(listInts);
//高级排序
listInts.sort(Sort_Desc); //降序
display(listInts);
}
int main()
{
sort();
return 0;
}
- 对包含对象的list进行排序
- 在list包含的对象所属类中,实现运算符<;
- 提供一个排序二元谓词。
STL forward_list(C++11)
- 单向链表
- 与list类似
- 插入元素不能使用
push_back
;而只能使用push_front
。 - 对迭代器只能使用
++
,不能使用--
。 - 相比list,占用的内存稍少,因为只需指向下一个元素,而无需指向上一个元素。