基础
请说出C++语言的优点,缺点,和主要用途?
- 优点
- 强大的抽象封装能力:这让C++语言具备了强大的开发工程能力,封装的同时让C++最大程度的保留了高性能;
- 高性能:运行快,快并且占用资源少一直是C++语言的追求;
- 低功耗:特别适合在各种微型的嵌入式设备中运行高效的程序;
- 缺点
- 语法相对复杂,细节比较多,学习曲线比较陡;
- 需要一些好的规范和范式,否则代码很难维护;
- 主要用途
- 大型桌面应用程序(如Google Chrome和Microsoft Office)
- 大型网站的后台,如Google的搜索引擎
- 游戏和游戏引擎(如Unity)
- 视觉库和AI引擎 (如Opencv和Tensorflow)
- 数据库 (Miscrosoft SQL Server和Mongodb)
- 其他(如自动驾驶系统,嵌入式设备等)
标识符、关键字、常量
下面标识符是合法的有哪些( B E F)
A.float //关键字不能定义为标识符
B.ipad
C.1button //不能以数字开头
D. A#BC //不能包含特殊字符
E.my_button
F. button_1_ok
请给一个退出按钮命一个好变量名( C ) //不能以数字开头,定义出的标识符最好有含义,不太建议使用汉语拼音
A. 1button
B. button1
C.buttonQuit
D.button_tuichu
下面整数常量合法的是( D )
A.078 //以0开头代表是8进制,不能有超过7的数字出现
B.03UU //后缀表示U不能重复
C.0x9AHX //十六进制不能有超过F的字符出现
D.0xFFAA00
下面程序输出结果是___
#define MA(x) x*(x-1)
void main()
{
int a=1, b=2;
cout << MA(1+a+b) << endl; //8
}
运算符与表达式
下面每行程序输出结果是分别是什么 在vs2015中x86
cout << sizeof(char) << endl; //1
cout << sizeof(short) << endl; //2
cout << sizeof(int) << endl; //4
cout << sizeof(float) << endl; //4
cout << sizeof(double) << endl; //8
cout << sizeof(long double) << endl; //8
cout << sizeof(long long) << endl; //8
下面程序输出结果是分别是什么
int x, y, z;
int a = (x = 10, y = 20, z = x + y);
cout << a << endl; //30
typedef struct {
short Sunday = 0;
short Monday = 1;
short Tuesday = 2;
short Wednesday = 3;
short Thursday = 4;
short Friday = 5;
short Saturday = 6;
} Week;
Week w;
cout << sizeof(w.Sunday) << endl; //2
cout << sizeof(w) << endl; //14
使用#define 编写一段代码,来实现“标准”宏MIN?
#define MIN(x, y) (x)>(y)?(y):(x)
分别写出bool 、int、 float、与“零值”比较,表达式返回值等于1的代码片段
/*********bool和零比值********/
bool bool_and_zero(bool a)
{
if(a)
{
return true;
}
else
{
return false;
}
}
/********int和零比值*************/
int int_and_zero(int a)
{
if(0 == a)
{
return 0;
}
else if(0 < a)
{
return 1;
}
else
{
return -1;
}
}
/***********float和零比值******************/
float float_and_zero(float a)
{
float zero = 1e-6;
float yes = 0.1;
float no = 0.2;
if(fabs(a) <= zero)
{
return yes;
}
else
{
return no;
}
}
/************指针和零比值**************/
char p_and_zero(int *a)
{
void *p= (void *)(a);
printf("%p\n",p);
if(NULL == p)
{
return 'y';
}
if(NULL != p)
{
return 'n';
}
}
基础容器
- 数组
- 动态数组vector
- 字符串,字符数组,string
指针与引用
指针的数组(array of pointers)与数组的指针(a pointer to an array)
指针的数组 T* t[]
数组的指针 T(t)[]
eg:
int a[4]; //指针的数组
int(*b)[4]; //数组的指针,注意[]优先级比较高
int c[4] = {0x80000000, 0xFFFFFFFF, 0x000000000, 0x7FFFFFFF};
int *a[4];
int(*b)[4];
b = &c;
for(unsigned int i = 0; i < 4; ++i){
a[i] = &c[i];
}
const 与指针的结合
const pointer与pointer to const
char strHelloworld[] = {"helloworld"};
char const *pStr1 = "helloworld";
char* const pStr2 = "helloworld";
char const * const pStr3 = "helloworld";
pStr1 = strHelloworld;
//pStr2 = strHelloworld; //pStr2不可改
//pStr3 = strHelloworld; //pStr3不可改
其中:
1. char const * 和 const char * 修饰的变量表示:所指向的值不可改,变量本身可以修改
2. char* const 修饰的变量表示:所指向的值可以修改,变量本身不可修改
3. char const * const 修饰的变量:所指向的值不可以修改,变量本身不可修改
const 修饰部分的规则:
- 看左侧最近的部分
- 如果左侧没有,则看右侧
*操作符
*操作符具有从右向左的结合性
int a = 123;
int* b = &a;
int** c = &b;
*这个表达式相当于(*c),必须从里向外逐层求值;
c得到的是c指向的位置,即b;
**c相当于b,得到变量a的值;
野指针
- 指针变量未初始化
- 已经释放不用的指针没有置NULL,如delete和free之后的指针;
- 指针操作超越了变量的作用范围
关于+++ ,—等运算符
编译器程序分解符号的方法是:一个字符一个字符的读入,如果该字符可能组成一个符号,那么读入下一个字符,一直到读入的字符不再能组成一个有意义的符号,这个处理过程称为“贪心法”
int a = 1, b = 2,c;
c = a+++b; //相当于a++ + b
int d = a++++b; //相当于a++ ++b ,error
每行代码在内存单元中的地址位置
int a = 0; //(GVAR)全局初始化区
int* p1; //(bss)全局未初始化区
int main() //(text)代码区
{
int b; //(stack)栈区变量
char s[]="abc"; //(stack)栈区变量
int* p2 = NULL; //(stack)栈区变量
char* p3 = "123456"; //"123456"在常量区,p3在(stack)栈区
static int c = 0; //(GVAR)全局静态初始化区
p1 = new int(10); //(heap)堆区变量
p2 = new int(20); //(heap)堆区变量
char *p4 = new char[7]; //(heap)堆区变量
strcpy_s(p4, 7, "123456"); //(text)代码区
return 0; //(text)代码区
}
资源管理方式–RAII
RAII(Resource Acquisition Is Initialization)
智能指针
- auto_ptr
由new expression获得对象,在auto_ptr对象销毁时,它所管理的对象也会自动被delete掉。
所有权转移,不小心把它传递给另外的智能指针,原来的指针就不再拥有这个对象了。
在拷贝/赋值过程中,会直接剥夺指针对原对象内存的控制权,转交给新对象,然后再将原对象指针置为nullptr. - unique_ptr:
unique_ptr是专属所有权,所以unique_ptr管理的内存,只能被一个对象持有,不支持复制和赋值。
移动语义:unique_ptr禁止了拷贝协议,但有时我们也需要能够转移所有权,于是提供了移动语义,即可以使用std::move()进行控制所有权的转移。 - shared_ptr
shared_ptr通过一个引用计数共享一个对象
shared_ptr是为了解决auto_ptr在对象所有权上的局限性,在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销。
当引用计数为0时,该对象没有被使用,可以进行析构。
shared_ptr,使用的引用计数技术会存在循环引用的问题,循环引用会导致堆里的内存无法正常回收,造成内存泄漏。
void testShared_ptr() {
{
//shared_ptr代表的是共享所有权,即多个shared_ptr可以共享一块内存
auto wA = shared_ptr<int>(new int(20));
{
auto wA2 = wA;
cout << ((wA2.get() != nullptr) ? (*wA2.get()) : -1) << endl; //20
cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl; //20
cout << wA2.use_count() << endl; //2
cout << wA.use_count() << endl; //2
}
//cout << wA2.use_count() << endl; //报错,wA2生命周期已过
cout << wA.use_count() << endl; //1
//shared_ptr内部是利用引用计数来实现内存的自动管理,每当复制一个shared_ptr,
//引用计数会+1,当一个shared_ptr离开作用域时,引用计数会-1.
//当引用计数为0的时候,则delete内存
}
}
void testmakeshared() {
auto wAA = std::make_shared<int>(30);
auto wAA2 = std::move(wAA); //此时wAA等于nullptr, wAA2.use_count()等于1
cout << ((wAA.get() != nullptr) ? (*wAA.get()) : -1) << endl; //-1
cout << ((wAA2.get() != nullptr) ? (*wAA2.get()) : -1) << endl; //30
cout << wAA.use_count() << endl; //0
cout << wAA2.use_count() << endl; //1
//将wAA对象move给wAA2,意味着wAA放弃了对内存的所有权和管理,此时wAA对象等于nullptr,
//而wAA2获得了对象的所有权,但因为此时wAA已不再次有对象,因此wAA2的引用计数为1
}
void testmakeshared() {
auto wAA = std::make_shared<int>(30);
auto wAA1 = wAA;
auto wAA2 = std::move(wAA); //此时wAA等于nullptr, wAA2.use_count()等于2
cout << ((wAA.get() != nullptr) ? (*wAA.get()) : -1) << endl; //-1
cout << ((wAA2.get() != nullptr) ? (*wAA2.get()) : -1) << endl; //30
cout << wAA.use_count() << endl; //0
cout << wAA2.use_count() << endl; //2
//将wAA对象move给wAA2,意味着wAA放弃了对内存的所有权和管理,此时wAA对象等于nullptr,
//而wAA2获得了对象的所有权,但因为此时wAA已不再次有对象,因此wAA2的引用计数为2
}
struct B;
struct A {
shared_ptr<B> pb;
~A() {
cout << "~A" << endl;
}
};
struct B {
shared_ptr<A> pa;
~B() {
cout << "~B" << endl;
}
};
struct WB;
struct WA {
shared_ptr<WB> pb;
~WA() {
cout << "~WA" << endl;
}
};
struct WB {
weak_ptr<WA> pa;
~WB() {
cout << "~WB" << endl;
}
};
void Test() {
cout << "Test shared_ptr and shared_ptr :" << endl;
shared_ptr<A> tA(new A());
shared_ptr<B> tB(new B());
cout << tA.use_count() << endl; //1
cout << tB.use_count() << endl; //1
tA->pb = tB;
tB->pa = tA;
cout << tA.use_count() << endl; //2
cout << tB.use_count() << endl; //2
}
void Test2() {
cout << "Test weak_ptr and shared_ptr :" << endl;
shared_ptr<WA> tA(new WA());
shared_ptr<WB> tB(new WB());
cout << tA.use_count() << endl; //1
cout << tB.use_count() << endl; //1
tA->pb = tB;
tB->pa = tA;
cout << tA.use_count() << endl; //1
cout << tB.use_count() << endl; //2
}
- weak_ptr
weak_ptr被设计为与shared_ptr共同工作,用一种观察着模式工作。
作用是协助shared_ptr工作,可获得资源的观测权,像旁观者那样观测资源的使用情况,观察者意味着weak_ptr只对shared_ptr进行引用,而不改变其引用计数,当被观察的shared_ptr失效后,相应的weak_ptr也相应失效
c++引用
引用是一种特殊的指针,不允许修改的指针。
-
使用指针有哪些坑:
- 空指针
- 野指针
- 不知不觉改变了指针的值,却继续使用
-
使用引用则可以:
- 不存在空引用
- 必须初始化
- 一个引用永远指向它初始化的那个对象;
-
关于函数传递参数类型的说明:
- 对于内置基础类型(如int, double等)而言,在函数中传递是用pass by value更高效;
- 对OO面向对象中自定义类型而言,在函数中传递时pass by reference to const 更高效
- 有了指针为什么还需要引用?
bjarne stroustrup的解释:为了支持函数运算符重载; - 有了引用为什么还需要指针?
bjarne stroustrup的解释:为了兼容c语言;
枚举
c++ 基础句法
指向函数的指针与返回指针的函数
每一个函数都占用一段内存单元,他们有一个起始地址,指向函数入口地址的指针称为函数指针;
一般形式:数据类型 (*指针变量名)(参数表)
eg: int (*p)(int);
注意与返回指针的函数之间的区别
eg:
int (p)(int); //是指针,指向一个函数入口地址
int p(int); //是函数,返回的值是一个指针
switch分支与if分支的比较
- 使用场景
- switch只支持常量值固定相等的分支判断;
- if还可以判断区间范围
- 用switch能做的,用if都能做,但反过来则不行
- 性能比较
- 分支少时,差别不是很大;分支多时,switch性能较高
- if开始处几个分支效果高,之后效率递减
- switch所有case的速度几乎一样
结构体内存布局
递归
递归的四个基本法则:
- 基准情形:无须递归就能解出
- 不断推进:每一次递归调用都必须使求解状况朝接近基准情形的方向推进;
- 设计法则:假设所有递归调用都能运行
- 合成效益法则(compound interest rule): 求解一个问题的同一个实例时,切勿在不同的递归调用中做重复性的工作。
递归的优化:
- 尾递归:所有递归形式的调用都出现在函数的末尾;
- 使用循环替代
- 使用动态规划,空间换时间
//斐波那契数列的尾递归实现
int fib3(int n, int ret0, int ret1){
if(n == 0){
return ret0;
} else if( n == 1 ){
return ret1;
}
return fib3(n-1, ret1, ret0+ret1);
}
//斐波那契数列的循环实现
int fib2(int n){
if(n < 2){
return n;
}
int n0 = 0, n1 = 1;
int temp;
for(int i = 0; i <= n; i++){
temp = n0;
n0 = n1;
n1 = temp + n1;
}
return n1;
}
//斐波那契数列的动态规划实现
int g_a[1000];//全局的数组,记录斐波那契数列数列的前1000个的值
int fib4(int n){
g_a[0] = 0;
g_a[1] = 1;
for(int i= 2; i<= n; i++){
if(g_a[i] == 0){
g_a[i] = g_a[a-i] + g_a[i-2];
}
}
return g_a[n];
}
c++高级语法
- c++使用struct和class来定义一个类:struct的默认成员权限是public,class的默认成员权限是private,除此之外,两者基本无差别
- 面向对象的误区
误区1. 对象是对现实世界中具体物体的反映,继承是对物体分类的反映
知识点
c++ 11 类在未实现下列类时会在编译时自动生成:
Test(); //默认构造函数
~Test(); //析构函数
Test(const Test &); //拷贝构造函数
Test & operator =(const Test &); //赋值函数
Test(Test &&); //移动构造函数
Test & operator =(const Test &&); //移动赋值函数
1. 假如我们自己定义了有参构造函数,编译器则不会自动生成默认构造函数,需要自己手动定义默认构造函数(无参数);
2. 假如我们自己定义了拷贝构造函数,编译器则不会自动生成拷贝构造函数,反之这会自动生成拷贝构造函数;
3. 假如我们未重载=运算符,编译器会自动实现=运算符,反之不会自动实现=运算符
Complex的实现:
- 默认构造函数
- 有参构造函数
- 拷贝构造函数
Complex(const Complex& x);
- Get和Set方法
- 运算符重载 + =
Complex operator+(const Complex& x) const;
Complex& operator=(const Complex& x);
Complex& operator+=(const Complex& x);
bool operator==(const Complex& x);
bool operator!=(const Complex& x);
- 前置和后置++、–重载
Complex operator++(int); //后置++
Complex& operator++(); //前置++
- 运算符<<重载
friend ostream& operator<<(ostream& os, const Complex& x); //并不是类的成员
friend istream& operator<<(istream& is, Complex& x);
String的实现:
IO缓冲区
清空缓冲区脏数据的操作:
cin.ignore(numeric_limits<std::streamsize>::max(), '\n'); //因为标准IO是按行缓存的
文件操作
c++编程思想
面向对象的三大特性
- 封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问,封装可以使得代码模块化;
- 继承性:让某种类型对象获得另一个类型对象的属性和方法,继承可以扩展已存在的代码;
- 多态性:同一事物表现出不同事物的能力,即向不同对象会产生不同的行为,多态的目的则是为了接口重用;
一个模式描述了一个不断发生的问题以及这个问题的解决方案;模式是前人的设计经验总结出来的对于一些普通存在的问题提供的通用的解决方案;
单例模式Singleton
- 整个程序中有且只有一个实例
- 常见的引用场景:系统日志、windows资源管理窗口、数据库分配主键操作等
- 实现思路:
- Singleton拥有一个私有构造函数,确保用户无法直接通过new直接实例它;
- 包含一个静态私有成员变量instance与静态公有方法instance()
观察者模式
- 在观察者模式中,观察者需要直接订阅目标时间;在目标发出内容改变的事件后,直接接收事件并作出响应,对象常是一对多的关系;
- 常见场景:各种MVC的框架,Model的变化通知各种类型的View时几乎都存在这种模式
- 实现思路:
- 首先要有个观察者,观察者中有事件通知方法
- 然后又被观察者,被观察者中有个观察者列表属性,也有绑定观察者、解绑观察者,事件发生时调用列表中所有观察者的通知方法
适配器(Adapter)模式
- 适配器将类接口转换为客户端期望的另一个接口;
- 使用适配器可以防止类由于接口不兼容而一起工作
- 适配模式的动机是,如果可以更改接口,则可以重用现有软件
class LegacyRectangle{
public:
LegacyRectangle(double x1, double y1, double x2, double y2){
_x1 = x1;
_y1 = y1;
_x2 = x2;
_y2 = y2;
}
void LegacyDraw(){
cout << "LegacyRectangle::LegacyDraw()" << _x1 << " " << _y1 << " " << _x2 << " " << _y2 << endl;
}
private:
double _x1, _y1, _x2, _y2;
}
class Rectangle{
public:
virtual void Draw(string str) = 0;
}
// 第一种适配的方式: 使用多重继承
class RectangleAdapter: public Rectangle, public LegacyRectangle{
public:
RectangleAdapter(double x, double y, double w, double h):
LegacyRectangle(x, y, x+w, y+h)
{
cout << "RectangleAdapter(int x, int y, int w, int h)" << endl;
}
virtual void Draw(string str){
cout << "RectangleAdapter::Draw()" << endl;
LegacyDraw();
}
}
//组合方式的Adapter
class RectangleAdapter: public Rectangle{
public:
RectangleAdapter(double x, double y, double w, double h):
_lRect(x, y, x+w, y+h)
{
cout << "RectangleAdapter(int x, int y, int w, int h)" << endl;
}
virtual void Draw(string str){
cout << "RectangleAdapter::Draw()" << endl;
_lRect.LegacyDraw();
}
private:
LegacyRectangle _lRect;
}
int main(){
double x = 20.0, y = 50.0, w = 300.0, h = 200.0;
RectangleAdapter ra(x, y, w, h);
Rectangle* pR = &ra;
pR->Draw("Testing Adapter");
cout << endl;
RectangleAdapter2 ra2(x, y, w, h);
Rectangle* pR2 = &ra2;
pR2->Draw("Testing Adapter2");
}
void *, NULL 和nullptr
c的类型转换
- 隐式类型转换:
double f = 1.0 / 2;
- 显示类型转换
格式:(类型说明符)(表达式)
double f = double(1) / double(2);
- c类型转换的问题:
- 任意类型之间都可以转换,编译器无法判断其正确性;
- 难于定位:在源码中无法快速定位;
c++类型转换
- const_cast:用于转换指针或引用,去掉类型的const属性;
const int a = 10;
int* pA = const_cast<int*>(&a);
*pA = 100;
cout << "a:" << a << endl; //a: 100
cout << "*pA:" << *pA << endl; //*pA: 100
a = 20;//error C3892: 'a': you cannot assign to a variable that is const
- reinterpret_cast:(很危险)重新解释类型,既不检查指向的内容,也不检查指针类型本身;但要求转换前后的类型所占用内存大小一致,否则将引发编译时错误;
int Test(){
return 0;
}
typedef void(*FuncPtr)();
FuncPtr funcPtr;
//funcPtr = &Test; //error
funcPtr = reinterpret_cast<FuncPtr>(&Test);
- static_cast: 用于基本类型转换,有继承关系类对象和类指针之间转换,由程序员来确保转换是安全的,它不会产生动态转换的类型安全检查的开销
int i = 5;
double d = static_cast<double>(i);
double d2 = 5.6;
int i2 = static_cast<int>(d2);
- dynamic_cast:只能用于含有虚函数的类,必须用在多态体系中,用于类层次间的向上和向下转化;向下转化时,如果是非法的对于指针返回NULL;
class Base{
public:
Base():_i(0){;}
virtual void T(){ cout << "Base:T" << _i << endl;}
private:
int _i;
}
class Derived : public Base{
public:
Derived():_j(1){;}
virtual void T(){ cout << "Derived:T" << _j << endl;}
private:
int _j;
}
Base cb;
Derived cd;
Base* pcb;
Derived* pcd;
//子类->父类
pcb = static_cast<Base*>(&cd);
pcb = dynamic_cast<Base*>(&cd);
if(pcb == NULL){
cout << "unsafe dynamic_cast from Derived to Base" << endl;
}
//父类->子类
pcd = static_cast<Derived*>(&cb);
pcd = dynamic_cast<Derived*>(&cb);
if(pcd == NULL){
cout << "unsafe dynamic_cast from Base to Derived" << endl; //此处会输出
}
泛型编程的思想
- 如果说面向对象是一种通过间接层来调用函数,以换取一种抽象,那么泛型编程则是更直接的抽象,它不会因为间接层而损失效率
- 不同于面向对象的动态期多态,泛型编程是一种静态期多态,通过编译器生成最直接的代码;
- 泛型编程可以将算法与特定类型,结构剥离,尽可能复用代码;
template<class T>
T max(T a, T b){
return a > b ? a:b;
}
//特化
template<>
char* max(char* a, char* b){
return (strcmp(a, b) > 0?(a):(b));
}
template<class T1, class T2>
int max(T1 a, T2 b){
return static_cast<int>(a > b ? a:b);
}
泛型编程的递推过程及总结
STL标准库
容器
容器用于存放数据;STL的容器分为两大类:
- 序列式容器(Sequence Containers): 其中的元素都是可排序的(ordered),STL提供了vector,list,deque等序列式容器,而stack,queue,priority_queue则是容器适配器;
- stack堆栈:一种“先进后出”的容器,底层数据结构是使用deque;
- queue队列:一种“先进先出”的容器,底层数据结构是使用deque;
- priority_queue优先队列:一种特殊的队列,它能够在队列中进行排序(堆排序),底层实现结构是vector或者deque;
#include <iostream>
#include <vector>
#include <list>
#include <deque>
#include <algorithm>
struct Display {
void operator()(int i) {
cout << i << " ";
}
};
void testSTL() {
int iArr[] = { 1, 2, 3, 4, 5 };
vector<int> iVector(iArr, iArr + 4);
list<int> iList(iArr, iArr + 4);
deque<int> iDeque(iArr, iArr + 4);
for_each(iVector.begin(), iVector.end(), Display()); // 1 2 3 4
cout << endl;
for_each(iList.begin(), iList.end(), Display()); // 1 2 3 4
cout << endl;
for_each(iDeque.begin(), iDeque.end(), Display()); // 1 2 3 4
cout << endl;
}
queue<int> iQueue(iDeque); //队列先进先出
stack<int> iStack(iDeque); //栈 先进后出
priority_queue<int> iPQueue(iArr, iArr + 4); //优先队列,按优先权
while (!iQueue.empty())
{
cout << iQueue.front() << " ";
iQueue.pop();
}
cout << endl; //1 2 3 4
while (!iStack.empty())
{
cout << iStack.top() << " ";
iStack.pop();
}
cout << endl; //4 3 2 1
while (!iPQueue.empty())
{
cout << iPQueue.top() << " ";
iPQueue.pop();
}
cout << endl; //4 3 2 1
- 关联式容器(Associative Containers): 每个数据元素都是由一个键(key)和值(value)组成,当元素被插入到容器时,按其键以某种特定规则放入适当位置;常见的关联容器如:set. multiset, map, multimap;
#include <map>
struct Display1 {
void operator()(pair<string, double> info) {
cout << info.first.c_str() << ":" << info.second << endl;;
}
};
void testAssociativeContainers() {
map<string, double> studentScores;
studentScores["LiMing"] = 95.0;
studentScores.insert(pair<string, double>("zhangsan", 100.0));
studentScores.insert(map<string, double>::value_type("wangwu", 94.5));
for_each(studentScores.begin(), studentScores.end(), Display1());
cout << endl;
map<string, double>::iterator iter = studentScores.find("wangwu");
if (iter != studentScores.end()) {
cout << "Found the score is " << iter->second << endl;
}
//使用迭代器完成遍历查找的过程
iter = studentScores.begin();
while (iter != studentScores.end()) {
if (iter->second < 95.0) {
studentScores.erase(iter++); //注意: 迭代器的失效问题,使用++操作来解决,
}
else {
iter++;
}
}
for (iter = studentScores.begin(); iter != studentScores.end(); iter++) {
if (iter->second <= 98.5) {
iter = studentScores.erase(iter); //注意:迭代器失效的问题,erase操作会返回下一个有效的迭代器
}
}
//find得到并删除元素
iter = studentScores.find("LiHong");
if (iter != studentScores.end()) {
studentScores.erase(iter);
}
for_each(studentScores.begin(), studentScores.end(), Display1());
int n = studentScores.erase("LiHong"); //n为0,表示未找到元素
//清空所有元素
studentScores.erase(studentScores.begin(), studentScores.end());
}
仿函数
- 仿函数一般不会单独使用,主要是为了搭配STL算法使用
- 函数指针不能满足STL对抽象性的要求,不能满足软件积木的要求,无法和STL其他组件搭配
- 本质就是类重载了一个operator(),创建一个行为类似函数的对象
//测试仿函数
bool MySort(int a, int b) {
return a < b;
}
void Display2(int a) {
cout << a << " ";
}
template<class T>
inline bool MySort1(T const& a, T const& b) {
return a < b;
}
template<class T>
void Display3(T const& a) {
cout << a << " ";
}
struct SortF
{
bool operator()(int a, int b) {
return a < b;
}
};
struct DisplayF
{
void operator()(int a) {
cout << a << " ";
}
};
//C++仿函数模板
template<class T>
struct SortTF
{
inline bool operator()(T const& a, T const& b) {
return a < b;
}
};
template<class T>
struct DisplayTF
{
void operator()(T const& a) {
cout << a << " ";
}
};
void testFunctor() {
//c++ 方式
int arr[] = { 4, 3, 2, 1, 7 };
sort(arr, arr + 5, MySort); //MySort函数返回为假,则进行交换,即假如为<,则为升序,否则为降序
for_each(arr, arr + 5, Display2);
cout << endl;
//c++泛型
int arr2[] = { 4, 3, 2, 1, 7 };
sort(arr2, arr2 + 5, MySort1<int>); //MySort函数返回为假,则进行交换,即假如为<,则为升序,否则为降序
for_each(arr2, arr2 + 5, Display3<int>);
cout << endl;
//c++仿函数
int arr3[] = { 4, 3, 2, 1, 7 };
sort(arr3, arr3 + 5, SortF()); //MySort函数返回为假,则进行交换,即假如为<,则为升序,否则为降序
for_each(arr3, arr3 + 5, DisplayF());
cout << endl;
//C++仿函数模板
int arr4[] = { 4, 3, 2, 1, 7 };
sort(arr4, arr4 + 5, SortTF<int>()); //MySort函数返回为假,则进行交换,即假如为<,则为升序,否则为降序
for_each(arr4, arr4 + 5, DisplayTF<int>());
cout << endl;
}
算法(algorithm)
STL中算法大致分为四类:包含于, ,
- 非可变序列算法:指不直接修改其所操作的容器内容的算法
- 可变序列算法: 指可以修改它们锁操作的容器内容的算法
- 排序算法: 包括对序列进行排序和合并的算法/搜索算法以及有序序列上的集合操作
- 数值算法: 对容器内容进行数值计算
#include <functional>
#include <algorithm>
void testAlgorithm() {
int ones[] = { 1,2,3,4,5 };
int twos[] = { 10, 20, 30, 40, 50 };
int results[5];
transform(ones, ones + 5, twos, results, std::plus<int>()); //数组元素依次相加
for_each(results, results + 5,
[](int a)->void {
cout << a << " "; }); //lambda表达式,匿名函数
cout << endl;
//find
int arr[] = { 0, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7, 7, 8 };
int len = sizeof(arr) / sizeof(arr[0]);
cout << count(arr, arr + len, 6) << endl; //统计6的个数
cout << count(arr, arr + len, 7) << endl; //统计7的个数
cout << count_if(arr, arr + len, bind2nd(less<int>(), 7)) << endl; //统计<7的个数, bind2nd表示 ? < 7
cout << count_if(arr, arr + len, bind1st(less<int>(), 7)) << endl; //统计>7的个数, bind1st表示 7 < ?
cout << binary_search(arr, arr + len, 8) << endl; //二分查找
vector<int> iA(arr + 2, arr + 6);
cout << *search(arr, arr + len, iA.begin(), iA.end()) << endl; //查找子序列
cout << endl;
}
面试题
// 输入一个不存在重复字符的字符串,打印出字符串中字符的全排列。
//比如:
//输入: "123" 3*2*1 = 3!
//输出: 123
// 132
// 213
// 231
// 321
// 312
//f(123) = 1+f(23), f(23) = 2+f(3), f(3) = 3 递归
void swap(char* a, char* b){
char temp = *a;
*a = *b;
*b = temp;
}
void Permutation(char* pStr, char* pPostion){
// 基准点
if (*pPostion == '\0'){
cout << pStr << endl;
}
else{
for (char* pChar = pPostion; *pChar != '\0'; pChar++){
// 依次和后面的字符交换
swap(*pChar, *pPostion);
Permutation(pStr, pPostion + 1);
// 换回来
swap(*pChar, *pPostion);
}
}
}
int main(){
char test[] = "132";
Permutation(test, test);
cout << endl;
// 用STL输出全排列
// 注意:必须要保证数组顺序,否则会有问题
do{
cout << test[0] << test[1] << test[2] << endl;
} while (next_permutation(test, test + 3));
cout << endl;
char test2[] = "321";
// 注意:必须要保证数组顺序,否则会有问题
do
{
cout << test2[0] << test2[1] << test2[2] << endl;
} while (prev_permutation(test2, test2 + 3));
return 0;
}
迭代器
- 是一种smart pointer,用于访问顺序容器和关联容器中的元素,相当于容器和操作容器的算法之间的中介
- 迭代器安装定义方式分成以下四种:
- 正向迭代器 iterator
- 常量正向迭代器 const_iterator
- 反向迭代器 reverse_iterator
- 常量反向迭代器 const_reverse_iterator
空间配置器