1、什么是指针
(1)在C语言中,访问变量的方式有两种,通过变量访问,通过地址访问。指针存放的就是地址。
(2)野指针和悬空指针:野指针,就是没有被初始化的指针,悬空指针,最初指向的内存已经被释放了的指针。无论是野指针还是悬空指针,都是指向无效内存区域(这里的无效指的是"不安全不可控")的指针。 访问"不安全可控"(invalid)的内存区域将导致"Undefined Behavior"。
(3)指针和引用的区别:指针和引用都是地址的概念,指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。本质,引用是别名,指针是地址。使用指针的优点和必要性:指针能够有效的表示数据结构;能动态分配内存,实现内存的自由管理;能较方便的使用字符串;便捷高效地使用数组指针直接与数据的储存地址有关:
★条款一:指针与引用的区别
指针与引用看上去完全不同(指针用操作符’*’和’->’,引用使用操作符’.’),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?
首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?
char*pc =0;// 设置指针为空值
char& rc = *pc;// 让引用指向空值
这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生),应该躲开写出这样代码的人除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。因为引用肯定会指向一个对象,在C里,引用应被初始化。
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。
string& rs;// 错误,引用必须被初始化
strings("xyzzy");
string&rs = s;// 正确,rs指向s指针没有这样的限制。
string*ps;// 未初始化的指针// 合法但危险
voidprintDouble(constdouble& rd)
{
cout<< rd;// 不需要测试rd,它
}// 肯定指向一个double值
相反,指针则应该总是被测试,防止其为空:
voidprintDouble(constdouble*pd)
{
if(pd)
{// 检查是否为NULL
cout<< *pd;
}
}
指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。
strings1("Nancy");
strings2("Clancy");
string& rs = s1;// rs 引用 s1
string*ps= &s1;// ps 指向 s1
rs = s2;// rs 仍旧引用s1
// 但是s1的值现在是"Clancy"
ps = &s2;// ps 现在指向 s2;// s1 没有改变
总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款30)当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。
2、strut和union的区别,什么是内存对齐
共用体和结构体都是由多个不同的数据类型成员组成, 但在任何同一时刻, 共用体只存放一个被选中的成员, 而结构体则存放所有的成员变量。对于共用体的不同成员赋值,将会对其他成员重写, 原来成员的值就不存在了, 而对于结构体的不同成员赋值是互不影响的。内存分配不同。
为什么要内存对齐:(转) 内存对齐 | Light.Moon,
内存读取粒度:程序员通常认为,内存就像一个字节数组,然而处理器(CPU)不是按照字节来存储的,一般会以双字节,四字节,八字节,16字节,甚至是32字节为单位来存取内存。所以为了增加速度,就有了内存对齐。
高层(语言)程序员认为的内存形态和处理器对内存的实际处理方式之间的差异产生了许多有趣的问题.如果你不理解内存对齐,你编写的程序将有可能产生下面的问题,按严重程度递增:程序运行速度变慢,应用程序产生死锁,操作系统崩溃,你的程序会毫无征兆的出错,产生错误的结果。
内存对齐的例子:在实际的编程中(以 C++ 语言为例),编译器都会为你自动对齐的。在结构中,编译器为结构的每个成员按其自身的自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,**第一个成员的地址和整个结构的地址相同。
规则:1、第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。2、在数据成员完成各自对齐之后,类(结构或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。明显#pragma pack(n)作为一个预编译指令用来设置多少个字节对齐的。值得注意的是,n的缺省数值是按照编译器自身设置,一般为8,合法的数值分别是1、2、4、8、16。即编译器只会按照1、2、4、8、16的方式分割内存。若n为其他值,是无效的。
class test
{
private:
char c = '1';//1byte
int i;//4byte
short s=2;//2byte
};
class test2
{
int i;
char c;
short s;
};
(1)对于类test的内存空间是这样的:
内存分配过程:
1、char和编译器默认的内存缺省分割大小比较,char比较小,分配一个字节给它。
2、int和编译器默认的内存缺省分割大小比较,int比较小,占4字节。只能空3个字节,重新分配4个字节。
3、short和编译器默认的内存缺省分割大小比较,short比较小,占2个字节,分配2个字节给它。
4、对齐结束类本身也要对齐,所以最后空余的2个字节也被test占用。
(2)对于类test2的内存空间是这样的:
1)、int和编译器默认的内存缺省分割大小比较,int比较小,占4字节。分配4个字节给int。
2)、char和编译器默认的内存缺省分割大小比较,char比较小,分配一个字节给它。
3)、short和编译器默认的内存缺省分割大小比较,short比较小,此时前面的char分配完毕还余下3个字节,足够short的2个字节存储,所以short紧挨着。分配2个字节给short。
4)、对齐结束类本身也要对齐,所以最后空余的1个字节也被test占用。
3、什么是多态,单继承和多继承的虚函数表
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
4、关键字violatile
violatile 的作用,是否具有原子性,对编译器有什么影响
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
volatile关键字是防止在共享的空间发生读取的错误。只保证其可见性,不保证原子性;使用volatile指每次从内存中读取数据,而不是从编译器优化后的缓存中读取数据,简单来讲就是防止编译器优化。
在单任务环境中,如果在两次读取变量之间不改变变量的值,编译器就会发生优化,会将RAM中的值赋值到寄存器中;由于访问寄存器的效率要高于RAM,所以在需要读取变量时,直接寄存器中获取变量的值,而不是从RAM中。
在多任务环境中,虽然在两次读取变量之间不改变变量的值,在一些情况下变量的值还是会发生改变,比如在发生中断程序或者有其他的线程。这时候如果编译器优化,依旧从寄存器中获取变量的值,修改的值就得不到及时的响应(在RAM还未将新的值赋值给寄存器,就已经获取到寄存器的值)。
要想防止编译器优化,就需要在声明变量时加volatile关键字,加关键字后,就在RAM中读取变量的值,而不是直接在寄存器中取值。
什么场景一定要使用 violatile
一般说来,volatile用在如下的几个地方:
- 中断服务程序中修改的供其它程序检测的变量需要加volatile;
- 多任务环境下各任务间共享的标志应该加volatile;
- 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
多线程下的volatile
有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下:
volatile BOOL bStop = FALSE;
(1) 在一个线程中:
while( !bStop ) { ... }
bStop = FALSE;
return;
(2) 在另外一个线程中,要终止上面的线程循环:
bStop = TRUE;
while( bStop );
等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。
这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中:
int nMyCounter = 0;
for(; nMyCounter<100;nMyCounter++)
{
}
在此段代码中,nMyCounter的拷贝可能存放到某个寄存器中(循环中,对nMyCounter的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter -= 1;这个操作中,对nMyCounter的改变是对内存中的nMyCounter进行操作,于是出现了这样一个现象:nMyCounter的改变不同步。
violatile 能否和 const 一起用
文章基础:
(1) “编译器一般不为const变量分配内存,而是将它保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作。”
(2) volatile的作用是“告诉编译器,i是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值”。 《c语言深度解剖》
const, volatile含义
(1)const含义是“请做为常量使用”,而并非“放心吧,那肯定是个常量”。
(2)volatile的含义是“请不要做自以为是的优化,这个值可能变掉的”,而并非“你可以修改这个值”。
const, volatile的作用以及起作用的阶段
(1)const只在编译期有用,在运行期无用
const在编译期保证在C的“源代码”里面,没有对其修饰的变量进行修改的地方(如有则报错,编译不通过),而运行期该变量的值是否被改变则不受const的限制。
(2) volatile在编译期和运行期都有用
在编译期告诉编译器:请不要做自以为是的优化,这个变量的值可能会变掉;在运行期:每次用到该变量的值,都从内存中取该变量的值。
补充:编译期 -- C编译器将源代码转化为汇编,再转化为机器码的过程;运行期 -- 机器码在CPU中执行的过程。
const, volatile同时修饰一个变量
(1)合法性
“volatile”的含义并非是“non-const”,volatile 和 const 不构成反义词,所以可以放一起修饰一个变量。
(2)同时修饰一个变量的含义
表示一个变量在程序编译期不能被修改且不能被优化;在程序运行期,变量值可修改,但每次用到该变量的值都要从内存中读取,以防止意外错误。
5、new和malloc
new 和 malloc 的区别:查看另一篇文章
6、inline 实现原理
基本定义
inline是C++语言中的一个关键字,可以用于程序中定义内联函数,inline的引进使内联函数的定义更加简单。说到内联函数,这里给出比较常见的定义,内联函数是C++中的一种特殊函数,它可以像普通函数一样被调用,但是在调用时并不通过函数调用的机制而是通过将函数体直接插入调用处来实现的,这样可以大大减少由函数调用带来的开销,从而提高程序的运行效率。一般来说inline用于定义类的成员函数。
inline的基本使用
inline 返回值类型 函数名(函数参数){
//此处定义函数体
}
一般来说,inline适用的函数有两种,一种是在类内定义的成员函数,另一种是在类内声明,类外定义的成员函数,对于这两种情况inline的使用有一些不同:
(1)类内定义成员函数
这种情况下,我们可以不用在函数头部加inline关键字,因为编译器会自动将类内定义的函数声明为内联函数,代码如下:
class temp{
public:
int amount;
//构造函数
temp(int amount){
this->amount = amount;
}
//普通成员函数,在类内定义时前面可以不加inline
void print_amount(){
cout << this-> amount;
}
}
从上面的代码可以看出,在类内定义函数时,可以不加inline关键字,编译器会自动将类内定义的函数(构造函数、析构函数、普通成员函数等)设置为内联,具有内联函数调用的性质。
(2) 类内声明函数,在类外定义函数
根据C++编译器的规则,这种情况下如果想将该函数设置为内联函数,则可以在类内声明时不加inline关键字,而在类外定义函数时加上inline关键字,代码如下所示:
class temp{
public:
int amount;
//构造函数
temp(int amount){
this->amount = amount;
}
//普通成员函数,在类内声明时前面可以不加inline
void print_amount()
}
//在类外定义函数体,必须在前面加上inline关键字
inline void temp:: print_amount(){
cout << amount << endl;
}
从上面代码我们可以看出,类内声明可以不用加上inline关键字,但是类外定义函数体时必须要加上,这样才能保证编译器能够识别其为内联函数。另外,我们可以在声明函数和定义函数的同时写inline,也可以只在函数声明时加inline,而定义函数时不加inline。只要在调用该函数之前把inline的信息告知编译系统,编译系统就会在处理函数调用时按内联函数处理。也就是说,上面说的几种方法都可以实现一个内联函数的定义,根据自己的需要来写即可。
内联函数的优缺点
优点:
1.inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
2.很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。(宏替换不会检查参数类型,安全隐患较大)
3.inline函数可以作为一个类的成员函数,与类的普通成员函数作用相同,可以访问一个类的私有成员和保护成员。内联函数可以用于替代一般的宏定义,最重要的应用在于类的存取函数的定义上面。
缺点:
1.内联函数具有一定的局限性,内联函数的函数体一般来说不能太大,如果内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。(换句话说就是,你使用内联函数,只不过是向编译器提出一个申请,编译器可以拒绝你的申请)这样,内联函数就和普通函数执行效率一样了。
2.nline说明对编译器来说只是一种建议,编译器可以选择忽略这个建议。比如,你将一个长达1000多行的函数指定为inline,编译器就会忽略这个inline,将这个函数还原成普通函数,因此并不是说把一个函数定义为inline函数就一定会被编译器识别为内联函数,具体取决于编译器的实现和函数体的大小。
内联函数和宏定义的区别
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。内联函数与带参数的宏定义进行下比较,它们的代码效率是一样,但是内联欢函数要优于宏定义,因为内联函数遵循的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关联上内联扩展,将与一般函数一样进行调用,比较方便。
另外,宏定义在使用时只是简单的文本替换,并没有做严格的参数检查,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。
C++的inline的提出就是为了完全取代宏定义,因为inline函数取消了宏定义的缺点,又很好地继承了宏定义的优点,《Effective C++》中就提到了尽量使用Inline替代宏定义的条款,足以说明inline的作用之大。
使用注意事项
1.内联函数不能包括复杂的控制语句,如循环语句和switch语句;
2.内联函数不能包括复杂的控制语句,如循环语句和switch语句;
3.只将规模很小(一般5个语句一下)而使用频繁的函数声明为内联函数。在函数规模很小的情况下,函数调用的时间开销可能相当于甚至超过执行函数本身的时间,把它定义为内联函数,可大大减少程序运行时间。
C++中不但可以用define定义常量还可以用const定义常量,它们的区别如下:
用#define MAX 255定义的常量是没有类型的,所给出的是一个立即数,编译器只是把所定义的常量值与所定义的常量的名字联系起来,define所定义的宏变量在预处理的时候进行替换,在程序中使用到该常量的地方都要进行拷贝替换;
用const float MAX = 255; 定义的常量有类型名字,存放在内存的静态区域中,在程序运行过程中const变量只有一个拷贝,而#define 所定义的宏变量却有多个拷贝,所以宏定义在程序运行过程中所消耗的内存要比const变量的大得多;
用define定义的常量是不可以用指针变量去指向的,用const定义的常量是可以用指针去指向该常量的地址的;
用define可以定义一些简单的函数,const是不可以定义函数的.
具体来说,有以下几方面的区别:
1).编译器处理方式
define – 在预处理阶段进行替换
const – 在编译时确定其值
2).类型检查
define – 无类型,不进行类型安全检查,可能会产生意想不到的错误
const – 有数据类型,编译时会进行类型检查
3).内存空间
define – 不分配内存,给出的是立即数,有多少次使用就进行多少次替换,在内存中会有多个拷贝,消耗内存大
const – 在静态存储区中分配空间,在程序运行过程中内存中只有一个拷贝
4).其他
在编译时, 编译器通常不为const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
宏替换只作替换,不做计算,不做表达式求解。
宏定义的作用范围仅限于当前文件。
默认状态下,const对象只在文件内有效,当多个文件中出现了同名的const变量时,等同于在不同文件中分别定义了独立的变量。
如果想在多个文件之间共享const对象,必须在变量定义之前添加extern关键字(在声明和定义时都要加)。