C++小记三

纯虚函数,在虚函数后面加上=0,就变成了存续函数。C++ 里面,基类也有虚函数的实现,特殊情况下才能用,纯虚函数不需要实现(但仍可以实现,然后通过类限定名调用)。
有纯虚函数的叫做抽象类,抽象类不能实例化,只能通过子类对象赋值。
如果子类没有实现纯虚函数,它仍然是带有纯虚函数的抽象类。

运行时类型信息

把有虚函数的类成为多态类,反之称为非多态类,通常来说,非多态类执行和空间效率高一点。
C++在运行时会专门为多态类保留类型信息,非多态类不会保存。
判断类型信息:
type_info头文件中的type_id(xxx)函数,可以传入对象和类名,返回type_info常引用,这个对象可以使用==判断是否相同,可以通过.name()得到类名。
dynamic_cast 专门用于多态类基类向子类转换,如果不能转换为指定子类,对指针结果为空,对引用则抛出异常。

虚函数动态绑定的实现原理

C++标准没有规定动态绑定的实现方法,不同的编译器有所不同,主要方法如下。
对每个有虚函数的类建立一个虚表,虚表里面包含了指向这个类的说有虚函数的指针。最后将虚表地址指针保存到对象中。对象调用函数的时候,通过指针调用,这里指针进行一定的转换即实现了动态绑定。
另外,前面的运行时类型信息就存在虚表中,非多态类是没有虚表的,自然也就没有运行时类型信息。
注意名称说法:动态绑定也叫运行时绑定,动态联编,晚期联编,晚期绑定,静态的对应称呼

泛型

函数模板

声明方式
template <class或者typename T> 函数定义
class和typename就是一个类参数的标志,然后使用即可,类似Java。
原理:传入不同的类型是,编译器生成不同的函数实例,然后加以调用。然后函数模板并不会生成具体的实例,也就是不占用内存,只有调用时传入了具体的类型,才会生成函数。 类模板、函数模板整个定义要放到头文件中,相当于一个声明。不放到里面会出错
调用函数模板时,只要编译器可以推断出类参数,就不用写明。

类模板

类模板的声明和原理一样。
注意的一个点是定义类模板的函数的时候,要在类限定名前面加上template 类名 :: 这一坨,(定标准的,写这么多不累?)
对类模板实例化时,首先成员的声明会实例化,然后它里面使用了类参数变量,方法并不会立即实例化,只有使用到了才会实例化。(书上说的,那发生动态绑定呢?)
**如果在类模板的定义中有一个静态数据成员,则在程序运行中会产生多少个相应的静态变量?
解:这个类模板的每一个实例类都会产生一个相应的静态变量。**注意这个和Java不一样,C++是通过编译器真的生成了一个类定义,Java没有,通过擦除机制什么的,类定义还是只有一份,占用内存也只有一份,类变量也是。

C++泛型的特殊用法

模板的显示实例化,template 实例化目标的声明,不太常用
类模板的特化和偏特化
类模板的特化,是指针对某种类型的类参数,重写一遍类定义,当使用时传递这个参数时,就使用这个定义成。类模板的特化其定义会实例化。写法是 template<> class 类名<类参数>{};这种情形下定义它的函数的时候,不用带上template 这一坨了,因为已经明确了
类模板的偏特化,就是指定部分类参数,重写类定义。将要保留的类参数写到template<>的<> 之中即可.偏特化之后仍然是模板,函数定义前面那一坨还要写。

写法

函数模板的重载:
函数模板的重载就是前面有个类参数,然后后面函数的使用中,被类参数当成一般的类,一样的重载规则即可重载,即类参数形成的参数或者其他类型的参数,个数或类型不同即是重载。

模板元编程:

一个比较难一些的技巧,利用C++模板代码需要在编译时期生成的特性,再利用模板的特化等特性,通过模板参数将一些**运行时才能执行的递归分支等操作,用模板代码写出来,在编译时期执行,**从而极致的提升C++代码性能。比如:

假如我们用float代表着一个多路的一维信号,那么假如我们要对两个这样的多路信号进行拷贝,
用C++,一般我们这样写:
for( size_t ch=0 ; ch<channelNum ; ++ch ) { //float
in; float** out;
for( size_t i=0; i<length ; ++i ) {
out[ch][i]=in[ch][i];
}
}
这么写挺标准,没什么问题,但是,作为一个性能狂魔,我表示不爽.
首先,如果在编译期我能确定channelNum是个确定的值,比如说是4,那么上面的代码可以优化成: 
for(size_t i=0;i<length;++i) {
out[0][i]=in[0][i]; out[1][i]=in[1][i]; out[2][i]=in[2][i]; out[3][i]=in[3][i];
}
这样做的好处是至少有两个,1.优化了分支, 2.优化了数据缓存刷新 .带来了性能提升,但代码被写死了,channelNum必须是4,万一我需要3,或者5怎么办? 这时模板元就派上用场了:
template
class Copy{
public:
static inline go(float* const out, float* const, int i) {
Copy::go(out,in,i);
out[count][i-1]=in[count][i];
}
};

template<>
class Copy<0>{
public:
static inline go(float* const,float* const,int) {}
};
这样,无论cout参数为多大,每当运行"go"函数时,go必然要再次运行Copy::go.
这是一个递归结构,我就用它构建了一个能够自动生成代码的模板类"Copy",然后我这样写:
template
void palll_copy(float** out,float** in,size_t length){
for(size_t i=0;i<length;++i) {
Copy::go(out,in,i);
}
}

C++类型参数嵌入传递的时候,比如模板里面使用vector :: iterator it; 这是编译器会出现无法辨别是否出现模板特化的情况,需要在前面加上template表示不用特化,就是单纯的模板。

STL

C++对STL还是比较精巧的,它有四种基本对象。
容器:向量 双端队列、列表、集合、多重集合、映射、多重影射 可分为两类 顺序和关联
迭代器:类似Java里面的接口,满足iteraterable的,可用作迭代器。不同的迭代器功能有所不同。
函数对象:任何普通函数和重载了() 括号运算的类的对象都可以当成函数对象使用
算法
利用以上四种基本对象的组合,可以完成十分不错的功能。比如transform(s.begin(), s.end(), ostream_iterateo(cout, " "), negate());输出列表每个元素的相反数

迭代器

在C++里面,迭代器的重要性很高,使用也很灵活。有时它就相当于一个列表在用,在不少地方也代替了列表的使用。

输入流迭代器

IO流一个接一个数据进行IO,这在某种程度上就是一个序列,然后就可以进一步抽象为迭代器。进一步的,很多类似序列,具有序列性质的东西,那 我们都可以把它看成序列和迭代器。
类定义 template istream_iterator
实际上有四个类参数,后面三个都有默认值,类型T需要满足能用>> 输入,且有默认构造函数
构造函数
istream_iterator<istream& in) ;
输入流迭代器支持
*访问刚刚读取的元素
->访问成员
++读取下一个成员等几种运算

输出流迭代器

如上面用的,还可以传入风格父参数
它的赋值运算相当于输出,即
*iter=x;

随机访问迭代器

C++里面的迭代器有随机访问迭代器,可以同过加减n移动。指针也是随机访问迭代器。

容器相关函数

distance(first, end) 计算距离
advance(first,n) 前进n步
iter.rbegin();iter.rend();用于得到反向的位置
静态函数
对应类型名::reverse_iterator(iter)获取反向迭代器

容器

通常使用容器的.empty() 或者.swap()等,比自己判断更高效。
STL提供的都至少是可逆容器。
capacity()可以的到容器的容量
reserve(n) 给容器分配不小于n这么大的空间
注意数组型容器,扩容时是分配一块新的空间,不是在当前空间后面加,那里一般都没空间了,故需要拷贝复制的过程,还是很耗时的,所以预分配有一定的必要。
数组型容器增删之后会使迭代器失效,指向元素的指针也可能失效,非数组的一般不会。

向量容器
双端队列

通常采用分段数组实现,这样可以满足前插的如果没有空间,分配一个新的数组段,请放在改段末尾即可,提高前插效率,这样的操作对于vector是不行的。

列表

STL里面的列表是双向列表

栈和队列

stack 栈
queue 队列
priority_queue优先级队列 ,默认大元素优先

容器的插入迭代器

通常我们多次插入要多次调用容器的插入函数,利用迭代器的思想,把多次插入看做一个序列,那个就可以放入一个迭代器进行多次插入,这就是容器的插入迭代器。插入迭代器可以使用利用构造函数创建,也可以用系统提供的公共函数构建,他们叫做辅助函数。利用函数构建,可以不止名类参数,依靠编译器推断即可。而构造函数则不行。
函数 front_iterator(容器) back_iterator(容器) inserter(容器)

set

构造函数,s(it1,it2),将it1到it2之间的元素放入,如果是多重set,则重复元素也放入。
s.insert(it, t); 用it做提示位置,加快速度,不对,则重新找到位置
s.lower_bound(k)第一个键值不小于k的元素的迭代器
s.upper_bound(k)第一个键值大于k的元素的迭代器
s.equal_range(k) 相等范围,返回pair,pair里面包含了头指针和尾指针

map

map放的是pair,不能直接put(key,value);, 同样的pair用构造函数要写类参数,使用make_pair(key,value)则不用
map还可以使用[ ]来操作元素,即m[ke]=xx;等 多重影射不支持[ ] 运算

函数对象的使用

就是前面讲到的算法里面传入的函数对象。有个简单的分类,0个参数的函数对象叫生成器,然后是一元函数,二元函数。

STL里面定义了很多常用的函数的类,在头文件,<functional>下,如:
plus<T>  将两个参数相加的函数对象
还有相减minus,相乘multiplies ,相处divide,取余 modulus,取相反数nagate等。这些类对应的函数都是内联函数
如acumulate(a, a+n, mutilies<in>()) 对数组进行累乘操作。	

还有一类称作谓词的函数,即进行逻辑运算的函数。其中几个如下,其余的类似

equal_to<T>
greater<T>
logical_and<T>
函数适配器

就适用于对STL中或者自己定义好的函数类进行改造的类。同样的,他们使用函数来构造,可分为4大类:
绑定适配器,将函数中某个参数设置成具体的值,而不再是变量。bind2nd(greater<int>(), 40) 将比较函数的第二各参数设置为40.还有bind1st等。
组合适配器,将逻辑运算的函数对象的返回值取反。unary_negate() ,binary_nagate().
指针适配器, 将函数指针变成目标类型。一般的函数适配器要知道函数对象的类型信息,指针函数形式的函数对象,通常无法知道,所以要先用函数指针适配器来处理,在传给一般的函数适配器。如bind2nd(ptr_fun(g), 40); 其中g是自定义的函数名。
成员函数适配器:用于需要使用容器中对象本身的成员函数做函数对象的情况,这时候,需要把对象自身当成第一个参数传进去,其中几个如下
mem_fun(成员函数) 传入本对象的指针
const_mem_fun(成员函数) 传入本对象的引用

算法

STL的算法,可分为4类, 不改变序列算法,改变序列算法,排序和搜索算法,数值算法。
注意这里的算法是通用的算法,一般对搜索序列使用。有一个情况是通常容器如果自带了同样的算法,则自带的算法通常会比这个通用的算法高效,优先用之。
**不改变序列算法:**各种查找,计数,finf_if count_if search_n还有for_each等等
**改变序列算法:**复制,生成,删除等,以及其它各种变化:移动,交换,分割,去重,填充,洗牌等等
其中交换算法,对某些特定数据,可能只需要交换部分数据,对swap算法具体化,可提高效率。
排序和搜索算法:
排序中还有部分排序,让第n个数前面的比它小且有序,后面的比它大且保持原来顺序,
使第n个元素左边小右边大等。以上两个都是快排的衍生品。
稳定排序,等值元素原来顺序不变,
字典序操作:字典序比较,生成字典序上一个或下一个排列
集合的生成并集,差集,交集等
还有堆操作的算法,创建堆,取堆顶,加入元素,堆排序等。
数字算法
累积运算 accumulate,可以自定义累积方式,累加,累乘等
部分和,partial_sum,意思是计算1-i的和,放到另一个序列中
每个相邻元素结合运算,结果保存到另一个序列中,adjacent-different
内积运算 inner_product 计算两列元素的内积,即对应相乘然后相加,当然可以自定义乘和加。

C++的boost库

提供了比STL更多的扩展库,相当于STL的一个超前版,有望成为下一代标准库的内容。有的已经加入C++11中。具有免费,开源,可移植性。
主要内容有:
Array 直接包装静态数组,提供诸如size,end等方法
bind 比stl中方法适配器灵活的多的方法适配器,支持更多参数,能对更多参数进行绑定。
正则表达式处理库Regex
智能指针和共享指针 Smart Ptr shareed_ptr 提供自动内存回收
bimap双向映射
unordered_set 和unordered_map就是hash的,已经加入C++ 11
GIL图像库等等

流类库

流就是数据从一个对象流动到另一个对象的抽象。
C++流类库的结构
默认情况下 cout cerr clog都是输出到屏幕,但是可以分别重定向。
各种设备都被当做文件进行IO。

文件输出流ofstream

文件输出流创建或者打开时,可以指定参数说明操作方式。
write(char *p, n), 函数,写入一段内存,比如一个对象,首先要将对象指针转化为char*
seekp、tellp设置写指针位置
错误检测函数。while(cout)等,就是利用运算符重载,调用了错误检测函数,错误检测函数返回0,条件失败。

字符串输出流

字符串输出流和文件输出流继承同一个父类,大部分方法相同。ostringstream有一个.str方法,返回生成的字符串。

输入流

只有少数几个输入流能用
输入流里面也有get和getline函数
read函数,读一块指定长度的存储区域,和wriet类似,参数里面传入char * 指针,指向读取后放到的内存位置
seekg和tellg设置和获取读指针位置
字符串输入流,和字符串输出流类似的

C++中的字符编码处理

C++中的char一个字节,只能处理ASCII的,如果要处理多国的语言,C++提供了宽字符wchar_t,每个字符占用两个字节,宽字符串wstring,在字符串字面量前面加上L就会使用宽字符方式处理。宽字符对gbk,unicode都能处理。C++的流是基于字符的,开始的流也都是基于单字节的字符的流,不能IO宽字符,需要使用宽流。宽流 只需要简单的在对应的IO类前面加上w即可。如wcin。

设置编码方案:
首先创建本地化对象Local local,设置本地化方案。通过.数字的字符串表示编码方案。如.936表示gbk。
在对宽流调用imbue方法,传如lacal即可。

C++的串行化(序列化)

直接的C++对象进行序列化存在比较大的难度,因为度与指针、应用类的对象,序列化之后是不能恢复的,要特殊处理。自己单独针对每个对象编写处理方法难度太大。在boost库中提供了序列化的看。 Serialization。使用该库可以处理序列化中的各种问题,包括指针等。它全面支持STL容器类的序列化,同一个指针指向的对象不会多次序列化等都支持。

C++的异常处理机制

基本内容:

C++的异常处理机制和Java相比,没有finally,其它的在大的地方基本一样,
catch里面使用…捕捉所有异常。
函数头异常声明的写法 xxx throw(a,b,c,d) {}
如果写了throw,则只能抛出括号里面声明的异常,没有写throw,则可以抛出任何异常。什么狗反常语法。
如果抛出了异常声明不允许的异常会调用unexcepted函数,该函数可以自定义,默认是调用terminal结束程序。
异常处理过程中的内存管理:抛出异常后,try到抛出点异常之间的栈上的数据都会自动清理,析构。
catch后面的参数和函数类似,值参数传值,引用则传递应用。
在catch语句块内用throw再次抛出异常对象,抛出的就是最初的对象,不是副本。
对于数组类的容器,如果构造节点出现异常,不保证容器的不变性。

异常安全

由于异常处理流程的特殊性,很可能异常过程中会导致一些不安全的状态,比如容器里面添加元素,先增加了size,但赋值的时候抛出异常了,则会出问题。所以编写程序的过程中,要避免这种问题。
编写异常安全的代码基本原则
1、知道哪些操作不会抛出异常,基本数据类型的操作几乎不会抛出异常,特殊的只有除0等。STL的函数基本不会抛出异常。
2、析构函数不要抛出异常,前面讲的try到异常抛出点,在异常抛出后会清理,清理过程中调用析构函数,如果此时抛出异常,会自己terminal。 诸如delete语句用错了,会抛出异常。等等要尤其注意。
3、异常抛出,可能导致堆上的对象无法析构,用智能指针自动清理、

题目

6-4 已知有一个数组名叫 oneArray,用一条语句求出其元素的个数。
解:
源程序:
nArrayLength = sizeof(oneArray) / sizeof(oneArray[0]);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值