C和C++知识精粹汇总.docx
《C和C++知识精粹汇总.docx》由会员分享,可在线阅读,更多相关《C和C++知识精粹汇总.docx(33页珍藏版)》请在冰豆网上搜索。
C和C++知识精粹汇总
一个由c/C++编译的程序占用的内存分为以下几个部分:
1.栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。
其操作方式类似于数据结构中的栈。
2.堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3.全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
-程序结束后由系统释放。
4.文字常量区—常量字符串就是放在这里的。
程序结束后由系统释放。
5.程序代码区—存放函数体的二进制代码。
C中的内存管理:
1、内存分配方式 内存分配方式有三种:
(1)从静态存储区域分配。
内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。
例如全局变量,static变量。
(2)在栈上创建。
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配,亦称动态内存分配。
程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。
动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
常用解决办法是,在使用内存之前检查指针是否为NULL。
如果指针p是函数的参数,那么在函数的入口处用assert(p!
=NULL)进行检查。
如果是用malloc或new来申请内存,应该用if(p==NULL)或if(p!
=NULL)进行防错处理。
注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
动态内存的申请与释放必须配对,防止内存泄漏。
用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?
这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
C++语言代码检查工具PC-Lint简介
概述
PC-Lint是一个历史悠久,功能异常强劲的静态代码检测工具。
它的使用历史可以追溯到计算机编程的远古时代(30多年以前)。
经过这么多年的发展,它不但能够监测出许多语法逻辑上的隐患,而且也能够有效地帮你提出许多程序在空间利用、运行效率上的改进点,在很多专业级的软件公司,比如Microsoft,PC-Lint检查无错误无警告是代码首先要过的第一关,我个人觉得,对于小公司和个人开发而言,PC-Lint也非常重要,因为基于开发成本考虑,小公司和个人往往不能拿出很多很全面的测试,这时候,PC-Lint的强劲功能可以很好地提高软件的质量。
功能
1)PC-Lint是一种静态代码检测工具,可以说,PC-LINT是一种更加严格的编译器,不仅可以象普通编译器那样检查出一般的语法错误,还可以检查出那些虽然完全合乎语法要求,但很可能是潜在的、不易发现的错误。
2)PC-lint不但可以检测单个文件,也可以从整个项目的角度来检测问题,因为C语言编译器固有的单个编译,这些问题在编译器环境下很难被检测,而PC-Lint在检查当前文件的同时还会检查所有与之相关的文件,可想而知,它会对我们有很大的帮助。
3)PC-lint支持几乎所有流行的编辑环境和编译器,比如BorlandC++从1.x到5.x各个版本、BorlandC++Build、GCC、VC,VC.net、watcomC/C++、Sourceinsight、intelC/C++等等,也支持16/32/64的平台环境。
4)支持ScottMeyes的名著(EffectiveC++/MoreEffectiveC++)中说描述的各种提高效率和防止错误的方法。
结构体及成员数据对齐:
结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
C中提供了#pragmapack(n)来设定变量以n字节对齐方式。
n字节对齐就是说变量存放的起始地址的偏移量有两种情况:
第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
结构的总大小也有个约束条件,分下面两种情况:
如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。
下面举例说明其用法。
#pragmapack(push)//保存对齐状态
#pragmapack(4)//设定为4字节对齐
structtest
{
charm1;
doublem4;
intm3;
};
#pragmapack(pop)//恢复对齐状态
以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。
接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。
接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。
这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。
如果把上面的#pragmapack(4)改为#pragmapack(16),那么我们可以得到结构的大小为24。
(请读者自己分析)
二、#pragmapack(n)对齐用法详解
什么是对齐,以及为什么要对齐:
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:
各个硬件平台对存储空间的处理上有很大的不同。
一些平台对某些特定类型的数据只能从某些特定地址开始存取。
其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。
比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。
显然在读取效率上下降很多。
这也是空间和时间的博弈。
对齐的实现通常,我们写程序的时候,不需要考虑对齐问题。
编译器会替我们选择时候目标平台的对齐策略。
当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。
但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。
最常见的就是struct数据结构的sizeof结果,出乎意料。
为此,我们需要对对齐算法所了解。
作用:
指定结构体、联合以及类成员的packingalignment;
语法:
#pragmapack([show]|[push|pop][,identifier],n)
说明:
1,pack提供数据声明级别的控制,对定义不起作用;2,调用pack时不指定参数,n将被设成默认值;3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降;
语法具体分析:
1,show:
可选参数;显示当前packingaligment的字节数,以warningmessage的形式被显示;
2,push:
可选参数;将当前指定的packingalignment数值进行压栈操作,这里的栈是theinternalcompilerstack,同时设置当前的packingalignment为n;如果n没有指定,则将当前的packingalignment数值压栈;
3,pop:
可选参数;从internalcompilerstack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packingalignment数值;如果指定了n,则n将成为新的packingaligment数值;如果指定了identifier,则internalcompilerstack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packingalignment数值为当前栈顶的record;如果指定的identifier并不存在于internalcompilerstack,则pop操作被忽略;
4,identifier:
可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internalcompilerstack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;5,n:
可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。
重要规则:
1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;
2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐;
3,结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragmapack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragmapack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;
4,复杂类型(如结构)整体的对齐<注意是“整体”>是按照结构体中长度最大的数据成员和#pragmapack指定值之间较小的那个值进行;这样在成员是复杂类型时,可以最小化长度;
5,结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;
对齐的算法:
由于各个平台和编译器的不同,现以本人使用的环境为例(默认对齐参数为4),来讨论编译器对struct数据结构中的各成员如何进行对齐的。
在相同的对齐方式下,结构体内部数据定义的顺序不同,结构体整体占据内存空间也不同,如下:
设结构体如下定义:
structA{
inta;
charb;
shortc;
};
结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。
所以A用到的空间应该是7字节。
但是因为编译器要对数据成员在空间上进行对齐。
所以使用sizeof(strcutA)值为8。
其结构为:
11111x11
abc
现在把该结构体调整成员变量的顺序。
structB{
charb;
inta;
shortc;
};
这时候同样是总共7个字节的变量,但是sizeof(structB)的值却是12。
其结构为1xxx111111xx
bac
下面我们使用预编译指令#progmapack(value)来告诉编译器,使用我们指定的对齐值来取代缺省的。
#progmapack
(2)/*指定按2字节对齐,等价于#pragmapack(push,2)*/
structC{charb;inta;shortc;};
#progmapack()/*取消指定对齐,恢复缺省对齐,等价于#pragmapack(pop)*/
sizeof(structC)值是8。
1x111111
bac
修改对齐值为1:
#progmapack
(1)/*指定按1字节对齐*/
structD{charb;inta;shortc;};
#progmapack()/*取消指定对齐,恢复缺省对齐*/
sizeof(structD)值为7。
111111
bac
对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,其自身对齐值为4,对于double类型,其自身对齐值为8,单位字节。
这里面有四个概念值:
1.数据类型自身的对齐值:
就是上面交代的基本数据类型的自身对齐值。
2.指定对齐值:
#progmapack(value)时的指定对齐值value。
3.结构体或者类的自身对齐值:
其数据成员中自身对齐值最大的那个值。
4.数据成员、结构体和类的有效对齐值:
自身对齐值和指定对齐值中小的那个值。
有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。
有效对齐值N是最终用来决定数据存放地址方式的值,最重要。
有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。
第一个数据变量的起始地址就是数据结构的起始地址。
结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。
这样就不能理解上面的几个例子的值了。
例子分析:
分析例子B;
structB{charb;
inta;
shortc;
};假设B从地址空间0x0000开始排放。
该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。
第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.
第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,符合0x0004%4=0,且紧靠第一个变量。
第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。
所以从0x0000到0x0009存放的都是B内容。
再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。
根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。
所以0x0000A到0x000B也为结构体B所占用。
故B从0x0000到0x000B共有12个字节,sizeof(structB)=12;
同理,分析上面例子C:
#progmapack
(2)/*指定按2字节对齐*/
structC{charb;
inta;
shortc;};
#progmapack()/*取消指定对齐,恢复缺省对齐*/
第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;
第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。
第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x0006、0x0007中,符合0x0006%2=0。
所以从0x0000到0x00007共八字节存放的是C的变量。
又C的自身对齐值为4,所以C的有效对齐值为2。
又8%2=0,C只占用0x0000到0x0007的八个字节。
所以sizeof(structC)=8.
C++中栈和队列实现:
栈
1、栈的相关概念
栈是限定只能在表的一端进行操作的线性表。
在表中允许插入和删除的一端叫做栈顶(top),而不允许插入和删除的另一端叫做栈底,向栈顶插入一个元素的操作叫做入栈(push)操作,从栈顶取出一个元素的操作叫做出栈(pop)操作。
栈一经确定,则栈底便固定不变,而栈顶随入栈、出栈操作的执行不断地变化。
2、栈的特点
先进后出或者后进先出
3、顺序栈的定义和实现
SeqStack.h
templateclassSeqStack
{
public:
SeqStack(intsz):
m_ntop(-1),m_nMaxSize(sz)
{
m_pelements=newType[sz];
if(m_pelements==NULL)
{
cout<<"ApplicationError!
"<exit
(1);
}
}
~SeqStack()
{
delete[]m_pelements;
}
public:
voidPush(constTypeitem);//pushdata
TypePop();//popdata
TypeGetTop()const;//getdata
voidPrint();//printthestack
voidMakeEmpty()
{//makethestackempty
m_ntop=-1;
}
boolIsEmpty()const
{
returnm_ntop==-1;
}
boolIsFull()const
{
returnm_ntop==m_nMaxSize-1;
}
private:
intm_ntop;
Type*m_pelements;
intm_nMaxSize;
};
templatevoidSeqStack:
:
Push(constTypeitem)
{
if(IsFull())
{
cout<<"Thestackisfull!
"<return;
}
m_pelements[++m_ntop]=item;
}
templateTypeSeqStack:
:
Pop()
{
if(IsEmpty())
{
cout<<"Thereisnoelement!
"<exit
(1);
}
returnm_pelements[m_ntop--];
}
templateTypeSeqStack:
:
GetTop()const
{
if(IsEmpty())
{
cout<<"Thereisnoelement!
"<exit
(1);
}
returnm_pelements[m_ntop];
}
templatevoidSeqStack:
:
Print()
{
cout<<"bottom";
for(inti=0;i<=m_ntop;i++)
{
cout<<"--->"<}
cout<<"--->top"<}
Test.cpp
#include
usingnamespacestd;
#include"SeqStack.h"
intmain(){
SeqStackstack(10);
intinit[10]={1,2,6,9,0,3,8,7,5,4};
for(inti=0;i<10;i++)
{
stack.Push(init[i]);
}
stack.Print();
stack.Push(88);
cout<stack.Print();
stack.MakeEmpty();
stack.Print();
stack.Pop();
return0;
}
4、链式栈的定义和实现
LinkStack.h
#include"StackNode.h"
templateclassLinkStack
{
public:
LinkStack():
m_ptop(NULL){}
~LinkStack()
{
MakeEmpty();
}
public:
voidMakeEmpty();//makethestackempty
voidPush(constTypeitem);//pushthedata
TypePop();//popthedata
TypeGetTop()const;//getthedata
voidPrint();//printthestack
boolIsEmpty()const
{
returnm_ptop==NULL;
}
private:
StackNode*m_ptop;
};
templatevoidLinkStack:
:
MakeEmpty()
{
StackNode*pmove;
while(m_ptop!
=NULL)
{
pmove=m_ptop;
m_ptop=m_ptop->m_pnext;
deletepmove;
}
}
templatevoidLinkStack:
:
Push(constTypeitem)
{
m_ptop=newStackNode(item,m_ptop);
}
templateTypeLinkStack:
:
GetTop()const
{
if(IsEmpty())
{
cout<<"Thereisnoelements!
"<exit
(1);
}
returnm_ptop->m_data;
}
templateTypeLinkStack:
:
Pop()
{
if(IsEmpty())
{
cout<<"Thereisnoelements!
"<exit
(1);
}
StackNode*pdel=m_ptop;
m_ptop=m_ptop->m_pn