• 13539426682
  • liuyixi2009@163.com

目录:C++

备战黑框框:(一)

小球的反复横跳:

打飞机:

 

来自新世界:输入,输出和文件

金字塔输出hello

设置空格(只对下一个有效)

设置填充字符

显示全部数字

 

来自新世界:标准模板库

什么是STL?

STL可分为容器(containers)、迭代器(iterators)、空间配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函数(functors)六个部分

 

引入:模板类vector

这里建立了一个类型为int,名字是rating的vector类,这个rating里面有两个int元素(其实就是数组)

但是这个vector类对外有接口,比如begin()方法返一个指向容器中第一个元素的迭代器,迭代器是什么?它可以是指针,可对其执行类似指针的操作(解除引用,递增),它存在的意义就是让STL能够为各种各样不同的容器类提供统一的接口。迭代器的类型是一个名字为iterator的typedef,作用域为整个类。

还有其他用法:

下面就来讲讲喜闻乐见的sort()函数,以前特别讨厌去写排序,一直想着去用sort去解决问题,那时都是用sort去对一个int/double数组进行排序,然鹅怎么对对象进行排序呢?

就是重载<,或者重新定义一个函数然后把函数当成参数传进sort()里面去

泛型编程

1.迭代器:连接容器和算法的一种重要桥梁,迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。

迭代器可以分为一下5种:

  • 输入迭代器:仅仅读。支持++、==、!=
  • 输出迭代器:仅仅写,支持++
  • 前向迭代器:读写,支持++、==、!=
  • 双向迭代器:读写,支持++、–
  • 随机訪问迭代器:读写,支持++、–、[n]、-n、<、<=、>、>=

 

前文提到像begin()方法,find()方法它也会返回一个迭代器,我们来看一下它的函数原型:

 

 

这里声明了find方法要传进一个输入迭代器,

我们可以看出,find是一种算法,vector是一种容器里面可以塞任何东西,当需要把算法应用于容器时,我们为了控制,才使用迭代器这个东西,可以看出不同的迭代器有着不同的权限,这也要求我们要根据算法的需求来选择迭代器。比如查找算法只需要定义++运算符和读取数据(假如查找算法可以修改数据将会变得很蛋疼)

 

sort算法使用双向迭代器对vector里面的数据进行修改与排序

 

 

事实上,迭代器是广义上的指针,指向int的常规指针是一个随机访问迭代器模型。

再来讲讲其他有用的迭代器

1.反向类迭代器,vector里面有一个rbegin和rend方法,返回的值分别和end与begin方法返回的值相同,但是前两者返回的迭代器的类型是reverse_iterator而不是iterator

2.插入迭代器(预定义的),在通过copy来把数据插到容器中时,很有可能会覆盖掉前面的数据,那么这时就要把特殊的迭代器传进容器中了

 

顺带一提copy可以:

  1. 将信息从一个容器复制到另外一个容器中
  2. 把数据从容器中复制到输出流
  3. 把信息从一个容器中插入到另外一个容器中

我们再来回顾一下前面的那一张图,我们可以发现,copy作为一种独立的算法,通过传给它不同的迭代器,可以实现对不同种类的容器的不同操作(输出,复制,插入)。

 

2.容器

list vector 什么的,还有关联容器set map pair,看的脑子疼就不计下来了

那个map和pair可以搭配使用

我感觉有点用的核心语句:

 

函数对象

函数对象是可以以函数方式与()结合使用的任意对象,包括:

1)函数名;

2)指向函数的指针;

3)重载了()操作符的类对象(即定义了函数operator()的类)

那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求——函数指针无法和STL其他组件(如配接器adapter)搭配,产生更灵活的变化。同时,函数指针无法保存信息,而仿函数可以。

 

。。。

 

看不懂,可能是直接用函数对象比较方便吧,比如:



 

这里可以发现,要用函数指针,先要这样子写一大串东西,然后才能传进去,但是用类的话会方便一点,直接实例化A<int>/<double>/<string>就完事了,然后还可以保存信息什么的,扩展性很高,但是占用内存更大和效率会比函数指针要慢得多。

 

其他库

1.vector,valarray,array

2.模板initializer_list:就是可以使用初始化列表语法来初始化STL容器,因为容器类现在包含将initializer_list<T>作为参数的构造函数。例如vector<double>包含一个将initializer_list<double>

作为参数的构造函数。

数据可以进行有必要的转化但不能隐式的窄化转化

 

总结

STL是一个容器类模板,迭代器模板,函数对象模板和算法模板的集合,他们的设计都是基于泛编程原则的。

  • 诸如vector和set等容器类是容器的概念(容器,序列,关联容器)的模型
  • 有一些算法被表示为容器的类方法,但是大量算法都被表示为通用的,非成员函数的,这是通过将迭代器作为容器和算法之间的接口得以实现的(把迭代器(这个类)给传进去,比如要把数据复制到输出流就传一个ostream_iterator,这样就把数据复制到输出流中。比如只是单纯的复制的话就传一个输入迭代器就成)。还有一个优点就是,我们只需要一个诸如copy(),for_each()这样的函数就可以了而不需要为每一个容器提供不同的版本。另外一个优点就是STL算法可以用于非STL容器,如常规数组,string对象。
  • 上面可以看到,算法对容器是有要求的,容器必须支持算法所需的迭代器,不然就要提供一个专门的方法。
  • STL还提供了函数对象,函数对象是重载了()运算符的类,可以用函数的表示方法来调用这种类的对象,同时可以携带更多的信息。

来自新世界:智能指针

不会发生内存泄漏的东西

智能指针的三个模板:auto_ptr  unique_ptr shared_ptr

当智能指针过期时,其析构函数会自动调用delete来释放内存。

注意所有指针都有一个explicit的构造函数,因此不能这样做:

事实上,我们可以发现,在编译文件时,vs code会发出警告,那是因为C++11已经要抛弃这个auto_ptr了。

为啥呢?

这个是因为有一个东西叫所有权,当一个智能指针赋值给了另外一个时,原来的就会放弃它的所有权,因此再次用原来的智能指针访问数据时就会出错。(我们称为“悬挂指针”)

怎么办?

可以把auto_ptr换成shared_ptr或者unique_ptr,前者的机制是当有多个指针指向同一对象时,引用计数增加,在析构时(先析构)那个最后声明的指针,其析构函数把计数器数减一,直到计数器降低到0时才会释放分配的空间。后者的机制是在使用等于号那一步编译器就报错了。但其实也不是不能通过等于号来给unique_ptr指针赋值,如果给它赋值的是临时右值,就不会报错,因为反正这个右值它很快就会被销毁了。

事实上也可以单纯的用等号,右值也不用是临时变量,在要转移的指针上加个move方法就行

怎么选择智能指针

如果有多个指针指向同一个元素,比如有很多个对象,但是每个对象里面都有两个指针,指向某个最大值和最小值,这时就要用shared_ptr

来自新世纪:打开文件

 

来自新世界:RTTI和类型转换运算符

RTTI元素

  • dynamic_cast 运算符将使用一个指向基类的指针来生成一个指向派生类的指针。否则返回一个空指针。
  • typeid 运算符返回一个指出对象的类型的值
  • type_info 结构储存了有关特定类型的信息

上代码:

结果是:

 

dynamic_cast在将父类cast到子类时,父类必须要有虚函数!!

具体有什么用呢?

其实就是基类指针可以指向它的派生类,但是有时候通过基类指针会调用到基类没用的方法,这时我们就可以用这个转换去通过使用一个基类指针来生成一个派生类指针再赋给其他指针,从而进行灵活的运用而不是通过typeid 和 if else 来判断这个基类指针指向的到底是什么类,再把操作写进去

类型转换运算符

上面说的dynamic_cast就是一种,而其他的还有像const_cast可以通过指针改变被const指针指向的值:

但是如果对象本来就是const,那终究是修改不了的。

来自新世界:关于子类父类的同名函数

之前我一直以为若子类的公有函数要和父类同名只能加个virtual,事实上不是的:

 

 

这时我们可以发现,即使基类指针指向派生类,调用的依旧是父类的函数,而非像加了virtual那样子的调用子类版本的show(),但是当指针被强制转换为子类指针时才可以调用子类的函数。想要用基类指针调用不同的版本的函数,就要在父类的show()前加一个virtual。

然鹅我突然想到,为什么这两个show函数不会冲突呢?其实这两个函数不在同一个作用域里面,C++会先去子类中搜索要用的函数,找不到再去父类里面找,因此如果要在子类对象里面调用父类函数,可以在子类对象中使用解析符 :: 去调用基类方法。

最后来个汇总:

结果:

 

 

(第二个由于没有默认构造一个B类,也就是没用构造好B类的部分,因此B类的私有成员值会变成一串无意义的数字)

来自新世界:友元,异常及其他

一个蛋疼的问题

那就是在写友元函数时会出现循环嵌套的情况,这时,就要在那个死循环的前面加个声明:Class x,就好了

 

异常

abort()函数遇到错误情况会直接退出,而不返回main函数

 

try catch

简单来说就是在一个被调用的函数里面写一个条件加throw,然后在try模块中调用,如果出错了就会直接进入相匹配的catch模块。注意这里引用一个栈解退的概念,它和return不一样,它会直接回退到最近的try模块,即中间函数生成的对象会被自动调用析构函数(自动变量元素则被自动释放)。可以利用这个特性,把控制权交给不同的函数。

 

其他异常特性

可以把error变成一个对象,然后直接throw 一个problem对象,注意这里throw的对象是对象的副本。然后catch是这个problem的引用。

为什么说是错的?当我输入1或者2时,系统抛出的错误来源都是来自bad_1而不是bad_2或者bad_3,原因是因为bad_2/3都是bad_1的派生类,因此catch的第一个是适用于他们的,所以才进入了块一。

正确的是:

除此之外,还可以像switch一样设置一个默认值:

 

exception 类

1.stdexcept 类:你可以自己写一个异常类,继承std中的exception类,然后重定义what()方法

通过继承exception类又诞生出几种错误类型:

logic_error 类

  • domain_error 定义域/值域错误
  • length_error 没用足够空间来执行操作
  • out_of_bounds 通常用于指示索引错误
  • invalid_argument  传递了一个以外的值

runtime_error 类

  • range _error
  • overflow_error
  • underflow_error

通常runtime_error表明存在无法避免的问题

 

2.bad_alloc 和 new

当new申请的空间过大时会抛出这个bad_alloc类,这个类里面的what()方法返回“std::bad_alloc”字符串

重定义退出的方向

未捕获异常:简单来说就是出错后,可以自行定义怎么退出,可以自己定义一个没用参数,没用返回值的函数,然后把函数名传给set_terminate( myQuit )

意外异常:这里说一下怎么捕获所有的异常

 

内存泄漏

就是在throw exception 后控制权会返回到try后面的catch模块那里,在函数进行过程中创造的类会自动调用析构函数,但是假如在前面使用了new去申请内存,那么这时就会把指针给删掉,指针指向的内存就会没用办法再去用,这就造成了内存泄漏。

 

来自新世界:类模板

先回顾一下鸭!

很好,接下来就是类模板了:

可以看出类模板的每个方法都是模板函数,而且方法前变成了 Stack<Type>::

而且注意的是必须显示地提供所需的类型:

那么这个类模板有什么卵用呢?

书上提到了一个远古的数据结构,就是栈(不是

想一想栈这个东西确实用的多,把它弄成什么东西都能装的栈就不用去一个一个地去写了,这里给出一个比较棘手的关于字符串的栈:

而且转念一想,要有“适应多种数据类型”的特性的模块,不就是基类吗?因此基类通常可以设置为类模板

你比如,我想存两个类型未定的值,这时就可以用模板了:

类模板除了和模板函数有什么隐式具体化,显示/隐式实例化之外,还有部分具体化,说白了就是在限制了模板的通用性,详情请看 C++ Primer Plus 583页。现在感觉暂时用不到….

 

成员模板

然鹅更牛皮的是,还有个东西叫成员模板,就是在模板类里面加载个模板类

 其中不同颜色代表不同的数据类型

 

 

还没完,这个模板还可以当成参数传进去:

很明显的看到了,这种模板之间的组合极大的增加了灵活性与代码的重用

 

 

模板类的友元

 

  • 非模板的友元
  • 约束友元模板,即友元的类型取决于类被实例化时的类型
  • 非约束的模板友元,即友元的所有具体化都是类的每一个具体化的友元

 

1.非模板的友元(非模板函数):

并且在友元函数的实现中也要给定参数:

看上去就是友元函数的重载不是吗?

 

2.约束模板友元函数(模板函数):

先声明模板函数,再在模板类里面声明为友元:

完整:

 

3.非约束模板的友元函数

我们可以看出,非约束的模板友元函数可以随着传进函数的参数的类型而生成相应的模板,“模板函数”这个特点更加明显,不再依赖于模板类的具体类型。这让这个函数可以处理各种各样的数据。

反观上面的约束模板友元函数,它生成的具体模板仅于模板类有关,因此说是 “被约束的”,作用我想是增加类模板的灵活性。

 

构造一个类和声明函数的混淆

哈哈哈,高浩裕看出来的

当代码是这样子时,过不了

但是当代码改一下变成:

当只有这一句时过了,然鹅并不能调用成员函数,这就很蛋疼

但是为什么可以过呢?

这一句其实不是声明一个A类,而是声明一个函数,它没用参数,返回一个A类的对象!

正确代码: