面试->面试题

1.请说出static 和 const关键字尽可能多的作用

static关键字至少有下列n个作用:   
1. 函数体内static变量的作用范围为该函数体(变量作用域),链接属性为空链接,生命周期为随程序(静态局部变量),不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值; 
2. 在文件内的static全局变量可以被文件内所有函数访问(作用域),但不能被文件外其它函数访问,链接属性为内链接,生命周期为随程序(静态全局变量);
3. static关键字修饰局部变量时改变变量的生命周期为随程序,不改变变量的作用域,也不改变变量的链接属性(空链接)。 static关键字修饰全局变量时,改变变量的链接属性(由外链接变为内链接),改变作用域为本文件内有效,不改变变量的生命周期;
4. 在文件内的static函数只可被这一文件内的其它函数调用,这个函数的使用范围被限制在声明它的文件内;   
5. 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;   
6. 在类中的static成员函数属于整个类所拥有,并不属于某个特定的 类实例化出的对象,所以这个函数不接收this指针,因而只能访问类的static成员变量。


const关键字至少有下列n个作用:   
1. 欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;   
2. 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;   
3. 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;   
4. 对于类的成员函数,若指定其为const类型,则表明其是一个常函数(const 修饰*this),不能修改类的成员变量;   
5. 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
例如:   
const classA operator*( const classA& a1, const classA& a2 );   
operator*的返回结果是一个临时对象,它必须是一个const对象。如果不是,这样的变态代码也不会编译出错:   
classA a, b, c;   
(a * b) = c; // 对a*b的结果赋值   
操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。



2.const 与 #define 相比有什么不同?

C++语言可以用const定义常量,也可以用#define定义常量,但是前者比后者有更多的优点:
1.const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且字符替换中可能会产生意料不到的错误(边际效应)。
2.有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。


C++ 语言可以用const 来定义常量,也可以用#define 来定义常量。但是前者比后者有更多的优点:
(1编译器处理方式不同
  define宏是在预处理阶段展开。
  const常量是编译运行阶段使用。
(2类型和安全检查不同
  define宏没有类型,不做任何类型检查,仅仅是展开。
  const常量有具体的类型,在编译阶段会执行类型检查。
(3存储方式不同
  define宏仅仅是展开,有多少地方使用,就展开多少次。(宏定义不分配内存但宏定义增加代码段长度)
  const常量会分配内存(可以是堆中也可以是栈中)。
(4const 可以节省空间,避免不必要的内存分配。 例如:  
        #define PI 3.14159 //常量宏  
        const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......  
        double i=Pi; //此时为Pi分配内存,以后不再分配!  
        double I=PI; //编译期间进行宏替换,分配内存  
        double j=Pi; //没有内存分配  
        double J=PI; //再进行宏替换,又一次分配内存!  
        const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以const定义的常量在程序运行过程中只有一份拷贝(因为是全局的只读变量,存在静态区),而#define定义的常量在内存中有若干个拷贝。 
(5提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
(6宏替换只作替换,不做计算,不做表达式求解;

宏预编译时就替换了,程序运行时,并不分配内存。


实现机制
宏是预处理命令,即在预编译阶段进行字节替换。const常量是变量,在执行时const定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态存储区的只读数据区。根据c/c++语法,当你声明该量为常量,即告诉程序和编译器,你不希望此量被修改。 程序的实现,为了保护常量,特将常量都放在受保护的静态存储区内。凡是试图修改这个区域内的值,都将被视为非法,并报错。 这不能理解为凡是字符串都是放在静态存储区域的。这个跟数据类型没有关系,而是这个量是变量还是常量的问题。例如,一个字符串变量就是可以被修改的。 这种静态存储区域的保护机制是由编译器实现的,而非存储该值的内存的属性。换言之,实质上内存永远都可以被用户随意修改,只是编译器给用户的代码注入了一些自己的保护代码,通过软件手段将这段内存软保护起来。这种保护在汇编级别可以轻松突破,其保护也就无效了。)。


用法区别
define宏定义和const常变量区别:
1.define是宏定义,程序在预处理阶段将用define定义的内容进行了替换。因此程序运行时,常量表中并没有用define定义的常量,系统不为它分配内存。const定义的常量,在程序运行时在常量表中,系统为它分配内存。
2.define定义的常量,预处理时只是直接进行了替换。所以编译时不能进行数据类型检验。const定义的常量,在编译时进行严格的类型检验,可以避免出错。
3.define定义表达式时要注意“边缘效应”,例如如下定义:
#define N 2+3 //我们预想的N值是5,我们这样使用N,int a = N/2; //我们预想的a的值是2,可实际上a的值是3。原因在于在预处理阶段,编译器将 a = N/2处理成了 a = 2+3/2;这就是宏定义的字符串替换的“边缘效应”因此要如下定义:#define N (2+3)。const定义的表达式则没有上述问题。const定义的常量叫做常变量原因有二:const定义常量像变量一样检查类型;const可以在任何地方定义常量,编译器对它的处理过程与变量相似,只是分配内存的地方不同。



3.  内存对齐

(1.概念
对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。
(2.为什么要字节对齐
需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。
(3.正确处理字节对齐
 对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
联合 :按其包含的长度最大的数据类型对齐。 
结构体: 结构体中每个数据类型都要对齐。
(4.什么时候需要设置对齐
在结构体中,编译器为结构的每个成员按其自然边界分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
使用伪指令#pragma pack (),取消自定义字节对齐方式。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要两个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。

规则:

对齐原因:
1、平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的。某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2、性能原因:数据结构(尤其是栈)应该尽可能地在⾃然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问


结构体内存对其规则:
1.第一个成员在与结构体变量偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
//对齐数 = 编译器默认的一个对齐数 与 该成员对齐数大小的较小值
VS中默认的值为8
linux中的默认值为4
3.结构体总大小为各成员最大对齐数(每个成员变量都有1个对齐数)的整数倍
4.如果嵌套了结构体,被嵌套的结构体对齐到自身的最大对齐数的
整数倍处,结构体的整体大小就是所有成员中(含嵌套结构体)最大对齐数的整数倍



4.说明sizeof和strlen之间的区别

1.sizeof操作符的返回类型是size_t,它是在头文件中 typedef的unsigned int类型。该类型保证其大小足以存储内存中对象的大小
2.sizeof是运算符,strlen是函数
3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以'\0'结尾的。sizeof还可以用函数返回值做参数-->sizeof(F( ))(对函数使用sizeof,在编译阶段会被函数返回值的类型取代)
4.数组做sizeof参数不退化,数组传递给strlen退化为指针
5.大部分编译程序在编译的时候就把sizeof计算过了,是类型或是变量的长度。这就是sizeof(x)可以用来定义数组维数的原因
6.strlen的结果是在运行时计算的,用来计算字符串的长度,而不是占内存的大小
7.sizeof后面是类型要加括号,变量名可以不加括号。因为sizeof是操作符而不是函数
8.当使用结构类型或变量时,sizeof返回实际的大小。当使用静态的数组时,sizeof返回全部数组的大小。sizeof操作符不能返回动态分配的数组(指针)或外部的数组(参数传进来)的大小
9.sizeof操作符不能用于函数类型、不完全类型或位字段。不完全类型指具有未知存储大小数据的数据类型,如未知存储大小的数组类型,未知内容的结构或联合类型、void类型等

数组作为参数传递给函数退化为指针(数组首地址)。在C++里传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小。如果想在函数内知道数组的大小,需要将数组长度由另一个形参传进去



5.说明sizeof的使用场合

 (1.sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如:
void *malloc(size_t size),  
size_t fread(void * ptr, size_t size, size_t nmemb, FILE * stream) 
(2.用它可以看某种类型的对象在内存中所占的单元字节。例如:
void * memset(void * s, int c, sizeof(s)) 
(3.在动态分配一对象时,可以让系统知道要分配多少内存
(4.由于操作数的字节数在实现时可能出现变化,在涉及操作数字节大小时最好用sizeof代替常量计算
(5.如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小



6.空类所占大小为1,单继承的空类大小也是1,多继承的空类大小还是1.

注意不要说类的大小,是类的对象的大小。 
首先,类的大小是什么?确切的说,类只是一个类型定义,它是没有大小可言的。 用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小。 

一个对象的大小大于等于所有非静态成员大小的总和。 
为什么是大于等于而不是正好相等呢?超出的部分主要有以下两方面: 
1) C++对象模型本身 对于具有虚函数的类型来说,需要有一个方法为它的实体提供类型信息(RTTI)和虚函数入口,常见的方法是建立一个虚函数入口表,这个表可为相同类型的对象共享,因此对象中需要有一个指向虚函数表的指针,此外,为了支持RTTI,许多编译器都把该类型信息放在虚函数表中。但是,是否必须采用这种实现方法,C++标准没有规定,但是这几户是主流编译器均采用的一种方案。 
2) 编译器优化 因为对于大多数CPU来说,CPU字长的整数倍操作起来更快,因此对于这些成员加起来如果不够这个整数倍,有可能编译器会插入多余的内容凑足这个整数倍,此外,有时候相邻的成员之间也有可能因为这个目的被插入空白,这个叫做“补齐”(padding)。所以,C++标准仅仅规定成员的排列按照类定义的顺序,但是不要求在存储器中是紧密排列的。 
基于上述两点,可以说用sizeof对类名操作,得到的结果是该类的对象在存储器中所占据的字节大小,由于静态成员变量不在对象中存储,因此这个结果等于各非静态数据成员(不包括成员函数)的总和加上编译器额外增加的字节。后者依赖于不同的编译器实现,C++标准对此不做任何保证。 
C++标准规定类的大小不为0,空类的大小为1,当类不包含虚函数和非静态数据成员时,其对象大小也为1。 如果在类中声明了虚函数(不管是1个还是多个),那么在实例化对象时,编译器会自动在对象里安插一个指针指向虚函数表VTable,在32位机器上,一个对象会增加4个字节来存储此指针,它是实现面向对象中多态的关键。而虚函数本身和其他成员函数一样,是不占用对象的空间的。 
 一个类中,虚函数、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间的。 
对象大小= vptr(可能不止一个,这个很难确定,不过试过,类中定义了一个virtual函数,仍然为占用4个字节) + 所有非静态数据成员大小 + Aligin字节大小(依赖于不同的编译器)

c++空类实例大小不是0原因?  
首先:我们要知道什么是类的实例化,所谓类的实例化就是在内存中分配一块地址. 
类a,b明明是空类,它的大小应该为为0,为什么编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以a,b的大小为1. 
而类c是由类a派生而来,它里面有一个纯虚函数,由于有虚函数的原因,有一个指向虚函数的指针(vptr),在32位的系统分配给指针的大小为4个字节,所以最后得到c类的大小为4. 
类d的大小更让初学者疑惑吧,类d是由类b,c派生迩来的,它的大小应该为二者之和5,为什么却是8呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.
当然在不同的编译器上得到的结果可能不同,但是这个实验告诉我们初学者,不管类是否为空类,均可被实例化(空类也可被实例化),每个被实例都有一个独一无二的地址. 
为什么类b多了一个数据成员,却大小和类a的大小相同呢?因为:类b的静态数据成员被编译器放在程序的一个global data members中,它是类的一个数据成员.但是它不影响类的大小,不管这个类实际产生了多少实例,还是派生了多少新的类,静态成员数据在类中永远只有一个实体存在,而类的非静态数据成员只有被实例化的时候,它们才存在.但是类的静态数据成员一旦被声明,无论类是否被实例化,它都已存在.可以这么说,类的静态数据成员是一种特殊的全局变量. 

出类的大小与它当中的构造函数,析构函数,以及其他的成员函数无关,只与它当中的成员数据有关. 
1.为类的非静态成员数据的类型大小之和. 
2.由编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针). 
3.为了优化存取效率,进行的边缘调整(字节对齐). 
4 与类中的构造函数,析构函数以及其他的成员函数无关.

C++的空类是指这个类不带任何数据,即类中没有非静态(non-static)数据成员变量,没有虚函数(virtual function),也没有虚基类(virtual base class)。 
直观地看,空类对象不使用任何空间,因为没有任何隶属对象的数据需要存储。然而,C++标准规定,凡是一个独立的(非附属)对象都必须具有非零大小。换句话说,
C++空类的大小不为0
class NoMembers
{};
NoMembers n;
sizeof(n) == 1
C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址。这是由于:
new需要分配不同的内存地址,不能分配内存大小为0的空间
避免除以 sizeof(T)时得到除以0错误
故使用一个字节来区分空类。
值得注意的是,这并不代表一个空的基类也需要加一个字节到子类中去。这种情况下,空类并不是独立的,它附属于子类。子类继承空类后,子类如果有自己的数据成员,而空基类的一个字节并不会加到子类中去。例如,
class Empty {};
struct D : public Empty {int a;};
sizeof(D)为4。

再来看另一种情况,一个类包含一个空类对象数据成员。class Empty {};

class HoldsAnInt {
    int x;
    Empty e;
};
在大多数编译器中,你会发现 sizeof(HoldsAnInt) 输出为8。这是由于,Empty类的大小虽然为1,然而为了内存对齐,编译器会为HoldsAnInt额外加上一些字节,使得HoldsAnInt被放大到足够又可以存放一个int。



7.内联函数和宏的差别是什么?

内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。
内联函数要做参数类型检查,这是内联函数跟宏相比的优势。
   inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到哪里去,
   对于短小的代码来说inline增加了空间消耗换来的是效率提高,这方面和宏是一模一样的,但是在inline在和宏相比没有付出任何额外代价的情况下更安全。至于需不需要inline,就要根据实际情况来取舍了。
   inline一般只用于如下情况:
a:一个函数不断被重复调用
b:函数只有简单的几行,且函数内不包含for、while、switch语句
一般来说,我们写小程序没有必要定义inline,但是如果要完成一个工程项目,当一个简单的函数被调用多次时,应该考虑用inline。
宏在C语言里极其重要,而在C++里用得就少多了。
关于宏的第一规则是绝不应该去使用它,除非你不得不这样做。几乎每个宏都表明了程序设计语言里、程序里或者程序员的一个缺陷。因为它将在编译器看到程序正文之前重新摆布这些正文。宏也是许多程序设计主要麻烦。所以,如果你使用了宏,就应该准备只能从各种工具中得到较少的服务。
宏是在代码出不加任何验证的简单替换。而内联函数是将代码直接插入调用处,而减少了普通函数调用时的资源消耗。
宏不是函数,只是在编译前(编译预处理阶段)将程序有关字符串替换成宏
关键字inlin必须与函数定义体放在一起才能使函数成为内联函数,仅将inlin放在函数声明前面不起任何作用。
如下面代码:函数FOO不能成为内联函数:
inline void Foo(int x, int y);//inline 仅在函数声明放在一起
void Foo(int x, int y)
而如下风格函数Foo则成为内联函数:
void Foo(int x, int y);
inline void Foo(int x, int y)//inline与函数定义放在一起。
inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。 内联能提高函数的执行效率,至于为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。 如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。 另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
1 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
2 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。 类的构造函数和析构函数容易让人误解成使用内联更有效。 要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。 所以不要随便地将构造函数和析构函数的定义体放在类声明中。 一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中)。


宏定义和内联函数的区别

1. 宏定义不是函数,但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提高了效率。
    内联函数本质上是一个函数,内联函数一般用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身。如果内联函数的函数体过大,编译器会自动的把这个内联函数变成普通函数。
2. 宏定义是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换
    内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的开销,提高效率
3. 宏定义是没有类型检查的,无论对还是错都是直接替换
    内联函数在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表等
4. 宏定义和内联函数使用的时候都是进行代码展开。不同的是宏定义是在预编译的时候把所有的宏名替换,内联函数则是在编译阶段在所有调用内联函数的地方把内联函数插入。这样可以省去函数压栈退栈,提高了效率
内联函数和普通函数的区别
1. 内联函数和普通函数的参数传递机制相同,但是编译器会在每处调用内联函数的地方将内联函数内容展开,这样既避免了函数调用的开销又没有宏机制的缺陷
2. 普通函数在被调用的时候,系统首先要到函数的入口地址去执行函数体,执行完成之后再回到函数调用的地方继续执行,函数始终只有一个复制。
    内联函数不需要寻址,当执行到内联函数的时候,将此函数展开,如果程序中有N次调用了内联函数则会有N次展开函数代码
3. 内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句。如果内联函数函数体过于复杂,编译器将自动把内联函数当成普通函数来执行



8.指针和引用的差别

(1 非空区别。在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这是你就可以把变量声明为引用。不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针要高。
(2 合法性区别。在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
(3 可修改区别。指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。
(4 应用区别。总的来说,在以下情况下你应该使用指针:一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。


指针:指针是一个变量,这个变量存储的是一个地址,指向内存的一个存储单元间接对对象进行读写;而引用跟原来的变量实质上是同一个东西,是原变量的一个别名。

引用和指针的区别和联系:
1.引用只能在定义时初始化一次,并且引用被创建的同时必须被初始化,之后不能改变指向其它变量(从一而终,不能改变引用关系);指针则可以在任何时候被初始化,可以随时改变所指的对象。
2.引用必须指向有效的变量,不能有 NULL 引用,引用必须与合法的存储单元关联,指针可以为空
3.sizeof 指针对象 和 引用对象的意义不一样。  sizeof引用得到的是所指向的变量的大小,而sizeof指针是指针的大小。
4.指针和引用自增(++)和自减(--)意义不一样。
5.相对而言,引用比指针更安全。
总结一下:指针比引用更灵活,但是也更危险。  使用指针时一定要注意检查指针是否为空。指针所指的地址释放以后指针最好置NULL,否则可能存在野指针的问题。



8.指针函数和函数指针?

http://blog.csdn.net/ameyume/article/details/8220832



9.指针数组和数组指针?

1.什么是指针数组和数组指针?
指针数组:指针数组可以说成是”指针的数组”,首先这个变量是一个数组,其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。
数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针,其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的地址,或者说这个指针指向一个数组。 
根据上面的解释,可以了解到指针数组和数组指针的区别,因为二者根本就是两种类型的变量。
2.指针数组和数组指针到底是什么?
2.1指针数组
首先先定义一个指针数组,既然是数组,名字就叫arr
char* arr[4] = {"hello", "world", "shannxi", "xian"};
// arr就是我定义的一个指针数组,它有四个元素,每个元素是一个char* 类型的指针,这些指针存放着其对应字符串的首地址。
// 定义时如果记不清优先级可以通过加括号解决: 比如定义一个指针数组,可以写成char* (arr[4])。不过在定义之前一定要清楚自己定义的变量,如果目的是一个数组,那就把arr[4]括起来,如果是一个指针,就把*arr括起来。
// 如果是看到一段这样的代码,可以从它的初始化来分辨它是数组还是指针,很明显,我这定义的是一个数组,如果是指针,会用NULL来初始化。

内存映像图                                           内容权限
栈区 函数中的普通变量 可读可写
堆区 动态申请的内存 可读可写
静态变量区 static修饰的变量              可读可写
数据区 用于初始化变量的常量           只读
代码区 代码指令 只读
一般情况下,从栈区到代码区,是从高地址到低地址。栈向下增长,堆向上增长。

arr[4]是一个在主函数定义的数组。把它对应到对应到内存中,arr是一个在栈区,有四个元素的数组,而每一个数组又是一个指针.
arr的{“hello”, “world”, “shannxi”, “xian”}也存在内存中,只是跟arr这个变量不在同一段空间,它们被分配在只读数据区,数组arr[4]的四个指针元素,分别存放着这四个字符串的首地址,想象一下,从栈区有四只无形的手指向数据区的空间。arr+1会跳过四个字节。也就是一个指针的大小 
这就相当于定义char *p1 = “hello”,char *p1 = “world”,char *p3 = “shannxi”, char *p4 = “xian”,这是四个指针,每个指针存放一个字符串首地址,然后用arr[4]这个数组分别存放这四个指针,就形成了指针数组。

2.2数组指针
首先来定义一个数组指针,既然是指针,名字就叫pa
char (*pa)[4];
如果指针数组和数组指针这俩个变量名称一样就会是这样:char *pa[4]和char (*pa)[4],原来指针数组和数组指针的形成的根本原因就是运算符的优先级问题,所以定义变量是一定要注意这个问题,否则定义变量会有根本性差别!
pa是一个指针指向一个char[4]的数组,每个数组元素是一个char类型的变量,所以我们不妨可以写成:char[4] (*pa);这样就可以直观的看出pa的指向的类型,不过在编辑器中不要这么写,因为编译器根本不认识,这样写只是帮助我们理解。

既然pa是一个指针,存放一个数组的地址,那么在我们定义一个数组时,数组名称就是这个数组的首地址,那么这二者有什么区别和联系呢?
char a[4];,
a是一个长度为4的字符数组,a是这个数组的首元素地址。既然a是地址,pa是指向数组的指针,那么能将a赋值给pa吗?答案是不行的!因为a是数组首元素地址,pa存放的却是数组首地址,a是char 类型,a+1,a的值会实实在在的加1,而pa是char[4]类型的,pa+1,pa则会加4,虽然数组的地址和首元素地址的值相同,但是两者操作不同,所以类型不匹配不能直接赋值,但是可以这样:pa = &a,pa相当与二维数组的行指针,现在它指向a[4]的地址。
数组名代表数组首元素地址

3.指针数组和数组指针的使用
3.1指针数组在参数传递时的使用
指针数组常用在主函数传参,在写主函数时,参数有两个,一个确定参数个数,一个指针数组用来接收每个参数(字符串)的地址
int main(int argc, char *argv[])
此时可以想象内存映像图,主函数的栈区有一个叫argv的数组,这个数组的元素是你输入的参数的地址,指向着只读数据区。
如果是向子函数传参,这和传递一个普通数组的思想一样,不能传递整个数组过去,如果数组很大,这样内存利用率很低,所以应该传递数组首元素的地址,用一个指针接收这个地址。因此,指针数组对应着二级指针
void fun(char** pp);//子函数中的形参
fun(p/*p是一个指针数组的数组名*/);//主函数中的实参

数组指针传参时的使用
数组指针既然是一个指针,那么就是用来接收地址,在传参时就接收数组的地址,所以数组指针对应的是二维数组。
void fun(int (*P)[4]);//子函数中的形参,数组指针 

a[3][4] = {0};//主函数中定义的二维数组
fun(a);//主函数调用子函数的实参,是二维数组的首元素地址(一维数组的地址)(所以形参为数组指针,指向数组的指针)

//指针数组 数组名做实参,数组名代表首元素地址,即指针的地址,所以形参为二级指针
//二维数组 数组名做实参,数组名代表首元素地址,即一维数组的地址,所以用数组指针,指向数组的指针



10.空指针与野指针的区别是什么?

当delete一个指针的时候,实际上仅是让编译器释放内存,但指针本身依然存在。这时它就是一个野指针。
当使用以下语句时,可以把野指针改为空指针:
myPtr = NULL;
通常,如果在删除一个指针后又把它删除一次,程序就会变得非常不稳定,任何情况都有可能发生。但是如果你只是删除了一个空指针,则什么事都不会发生,这样做非常安全。
使用野指针或空指针(如果myPtr=0)是非法的,而且有可能造成程序崩溃。如果指针是空指针,尽管同样是崩溃,但它同野指针的崩溃相比是一种可预料的崩溃。这样调试起来会方便得多。


野指针指一个指针变量指向了不可使用的内存空间。

野指针不是 NULL 指针,是指向“垃圾”内存的指针。人们一般不会错用 NULL指针,因为用 if 语句很容易判断。但是“野指针”是很危险的, 因为if 语句对它不起作用。

产生野指针三个原因:
(1 指针变量创建时候没有被初始化:任何指针变量在创建的时候不会自动成为NULL指针,它的默认值是随机的,它会乱指一气。所以指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存。否则该指针就会成为一个野指针,可能指向一块不可使用的内存空间。
例如char *p;  这样创建一个指针p,指向一个随机的内存地址空间
所以指针在创建的时候要被初始化,可以将其初始化为NULL,或指向合法的内存空间
比如 char *p = NULL ; 或 char *p = new char; //这个时候p就不会是一个野指针
(2 delete或free指针之后没有把指针设置为NULL:delete和free只是把指针所指的内存空间释放掉,而没有对指针本身进行释放,并没有清理掉指针。这时候的指针依然指向原来的位置,只不过这个位置的内存数据已经被毁尸灭迹,此时的这个指针指向的内存就是一个垃圾内存。但是此时的指针由于并不是一个NULL指针(在没有置为NULL的前提下),指针没有置为 NULL,让人误以为是个合法的指针。在做如下指针校验的时候
if(p != NULL)  
会逃过校验,此时的p不是一个NULL指针,也不指向一个合法的内存块,造成程序中指针访问失败。
比如char *p = new char[4] ; delete[] p; //这时候指针p所指的内存空间被释放,但是指针p本身不为空,指针p所指向的内存空间已经不能使用,造成了野指针。正确的做法是及时的把指针p赋值为NULL
(3 指针操作超过了指向内存空间的作用范围(指针操作超越了变量的作用范围):当指针越界之后也会变成一个野指针。指针指向一个临时变量的引用,当该变量被释放时,此时的指针就变成了一个野指针
由于C/C++中指针有++操作,因而在执行该操作的时候,稍有不慎,就容易指针访问越界,访问了一个不该访问的内存,结果程序崩溃
如:
class A{
public:
void Func( ){ cout << “Func of class A” << endl; }
};

void Test( ){
A* p;
{
A a;
p = &a; // 注意 a 的生命期
}
p->Func(); // p 是“野指针”
}
函数 Test 在执行语句 p->Func()时,对象 a 已经消失,而 p 是指向 a 的,所以 p 就成了“野指针”。

无类型指针
无类型指针指的是void* 这种指针,表示可以指向任何数据类型。
比如
int n = 3;
int *p = NULL;  //说明指针p此时空闲,没有指向任何有意义的内存空间
void *gp = &n;  //无类型指针gp指向整型变量n
p = (int *)gp;   //把无类型指针转换为整型指针
printf("%d\n", *p);
结果输出3,说明无类型指针可以转换成任何数据类型的指针。

空指针常量
一个表示0值的整数常量,叫做空指针常量。例如:0、0L、1-1(它们都是值为0的整数常量表达式)以及(void*)0、void* NULL 都是空指针常量,空指针常量可以赋值给任何指针类型,因为它是变体类型(void*)。但是我们更倾向于使用NULL表示这个空指针常量。对于其它方式(比如0)来表示空指针常量虽然不会产生任何问题,但是在根本意义上并不符合空指针常量的定义。因为空指针常量的存在意义还在强调它并不指向任何对象。

空指针
空指针不指向任何实际的对象或者函数。反过来说,任何对象或者函数的地址都不可能是空指针。
空指针是一个特殊的指针,表示当前这个指针变量处于空闲状态,没有指向任何有意义的内存空间,因为这个指针不指向任何地方。这意味任何一个有效的指针如果和空指针进行相等的比较运算时,结果都是false。
在程序中,得到一个空指针最直接的方法就是运用预定义的NULL,这个值在多个头文件中都有定义。
如果要初始化一个空指针,我们可以这样, 
int *ip = NULL;  
校验一个指针是否为一个有效指针时,我们更倾向于使用这种方式
if(ip != NULL)  
而不是
if(ip)  
为什么有人会用if(ip)这种方式校验一个指针非空,而且在C++中不会出现错误呢?而且现在很多人都会这样写。
原因是这样的,
// Define   NULL   pointer   value   
#ifndef   NULL   
#ifdef   __cplusplus   
#define   NULL      0   
#else   
#define   NULL      ((void   *)0)   
#endif   
#endif //   NULL   
在现在的C/C++中定义的NULL即为0,而C++中的true为≠0,所以此时的if(ip)和if(ip != NULL)是等效的。

NULL指针
NULL是一个标准规定的宏定义,用来表示空指针常量。在C++里面NULL被直接定义成了整数0,而在没有__cplusplus定义的前提下,就被定义成一个值是0的 void* 类型的指针常量

零指针
零值指针,是值为0的指针,可以是任何一种类型的指针,可以是通用变体类型 void*,也可以是 char*, int* 等等。
在C++里面,任何一个概念都以一种语言内存公认的形式表现出来,例如std::vector会提供一个empty()子函数来返回容器是否为空,然而对于一个基本数值类型(或者说只是一个类似整数类型的类型)我们不可能将其抽象成一个类(当然除了auto_ptr等智能指针)来提供其详细的状态说明,所以我们需要一个特殊值来做为这种状态的表现。
C++标准规定,当一个指针类型的数值是0时,认为这个指针是空的。(我们在其它的标准下或许可以使用其它的特殊值来定义我们需要的NULL实现,可以是1,可以是2,是随实现要求而定的,但是在标准C++下面我们用0来实现NULL指针)

空指针指向内存的什么地方
标准并没有对空指针指向内存中的什么地方这一问题作出规定,也就是说用哪个具体地址值表示空指针取决于系统实现。我们常见的空指针一般指向0地址,即空指针的内部用全0来表示(zero null pointer,零空指针);也有一些系统用一些特殊的地址值或者特殊的方式表示空指针(nonzero null pointer,非零空指针)
在编程中不需要了解在我们的系统上空指针到底是一个zero null pointer还是 nonzero null pointer,我们只需要了解一个指针是否是空指针就可以了——编译器会自动实现其中的转换,为我们屏蔽其中的实现细节。注意:不要把空指针的内部实现表示等同于整数0的对象表示——如上所述,有时它们是不同的。

对空指针实现的保护政策
逻辑地址和物理地址
既然我们选择了0作为空的概念。在非法访问空的时候我们需要保护以及报错。因此,编译器和系统提供了很好的政策。
我们程序中的指针其实是windows内存段偏移后的地址,而不是实际的物理地址,所以不同的地址中的零值指针指向的同一个0地址,其实在内存中都不是物理内存的开端的0,而是分段内存的开端,这里我们需要简单介绍一下windows下的内存分配和管理制度:
windows下,执行文件(PE文件)在被调用后,系统会分配给它一个额定大小的内存段用于映射这个程序的所有内容(就是磁盘上的内容)并且为这个段进行新的偏移计算,也就是说我们的程序中访问的所有near指针都是在我们“自家”的段里面的,当我们需要访问far指针的时候,我们其实是跳出了“自家的院子”到了他人的地方,我们需要一个段偏移资质来完成新的偏移(人家家里的偏移)所以我们的指针可能是OE02:0045就是告诉我们要访问0E02个内存段的0045号偏移,然后windows会自动给我们找到0E02段的开始偏移,然后为我们计算真实的物理地址。
所以程序A中的零值指针和程序B中的零值指针指向的地方可能是完全不同的。
空指针赋值分区
这一分区是进程的地址空间中从0x00000000 到 0x0000FFFF 的闭区间(64K 的内存大小),这 64K 的内存是一块保留内存,不能被程序动态内存分配器分配,不能访问,也不能使用,保留该分区的目的是为了帮助程序员捕获对空指针的赋值。如果进程中的线程试图读取或者写入位于这一分区内的内存地址,就会引发访问违规。
为什么空指针访问会出现异常
归根结底,程序中所使用的数据都需要从物理设备上获取,即程序中的数据需要从一个真实的物理地址中读取或者写入。所以当一个指针的逻辑地址可以通过计算能够准确无误的映射到一个正确的物理地址上时,这时候数据的访问就是正确的,程序的执行也没有任何问题。如果一个指针为空指针,那么该指针所指向的逻辑地址空间位于空指针赋值分区的区间上。空指针赋值分区上的逻辑地址没有物理存储器与之对应,因而访问时就会产生违规访问的异常。



11.C++中有了malloc/free,为什么还需要new/delete?

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理和释放内存工作的运算符delete。new/delete不是库函数,而是运算符。
malloc 是C语言的标准库函数,它实现了在堆内存管理中进行按需分配的机制,但是它不提供C++中对像构造的支持,而new 则是一个 在C++中同时完成堆内存按需分配支持和对像构造功能的运算符,malloc只能分配动态内存,而new除了分配动态内存还能构造对象,free只能释放内存,而delete除了释放内存还能执行析构函数
我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。
既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。
 


12.this指针

关于this指针,有这样一段描述:当你进入一个房子后,你可以看见桌子、椅子、地板等,但房子的全貌你是看不到的。 
对于一个实例来说,你可以看到它的成员函数、成员变量,但是实例本身呢?this指针就是这样一个指针,它时时刻刻指向这个实例本身。
this指针易混的几个问题如下:
(1. this 指针本质是一个函数参数,只是编译器隐藏起来,形式的、语法层面上的参数。this只能在成员函数中使用,全局函数,静态函数都不能使用this。实际上,成员函数默认第一个参数为T* const this。如:
class A
{
     public:
         int func(int p) {}
};
其中,func的原型在编译器看来应该是:
int func(A* const this, int p);

(2. this在成员函数的开始前构造,在成员的结束后清除。这个生命周期同任何一个函数的参数是一样的,没有任何区别。当调用一个类成员函数时,编译器将类的指针作为函数的this参数传递进去。如:
A a;
a.func(10);
此处,编译器将会编译成:
A::func(&a, 10);
看起来和静态函数没差别,不过,区别还是有的。编译器通常会对this指针做一些优化,因此,this指针的传递效率比较高,如VC通常是通过ecx寄存器传递this参数的。

(3. this指针并不占用对象的空间。 this相当于非静态成员函数的一个隐含的参数,不占对象空间。它跟对象之间没有包含关系,只是当前调用函数的对象被它指向而已。所有成员函数的参数,不管是不是隐含的,都不会占用对象的空间,只会占用参数传递时的栈空间,或者直接占用一个寄存器。

(4. this 指针是什么时候创建的?
     this 在成员函数的开始执行前构造,在成员的执行结束后清除。 但是如果class或者struct里面没有方法的话,它们是没有构造函数的,只能当作C的struct使用。采用TYPE xx的方式定义的话,在栈里分配内存,这时候this指针的值就是这块内存的地址。采用new方式创建对象的话,在堆里分配内存,new操作符通过eax返回分配地址,然后设置给指针变量。之后去调用构造函数(如果有构造函数的话),这时将这个内存块的地址传给ecx。

(5. this指针存放在何处?堆、栈、还是其他?
this指针会因编译器不同而有不同的放置位置。可能是堆、栈,也可能是寄存器。C++是一种静态的语言,那么对C++的分析应该从语法层面和实现层面两个方面进行。
语法上,this是个指向对象的“常指针”,因此无法改变。它是一个指向相应对象的指针。所有对象共用的成员函数利用这个指针区别不同变量,也就是说,this是“不同对象共享相同成员函数”的保证。
而在实际应用的时候,this应该是个寄存器参数。这个不是语言规定的,而是“调用约定”,C++默认调用约定是_cdecl,也就是c风格的调用约定。该约定规定参数自右向左入栈,由调用方负责平衡堆栈。对于成员函数,将对象的指针(即this指针)存入ecx中。因为这只是一个调用约定,不是语言的组成部分,不同编译器自然可以自由发挥。但是现在的主流编译器都是这么做的。

(6. this指针是如何传递给类中的函数的?绑定?还是在函数参数的首参数就是this指针?那么,this指针又是如何找到“类实例后函数”的?
大多数编译器通过ecx寄存器传递this指针。事实上,这也是一个潜规则。一般来说,不同编译器都会遵循一致的传参规则,否则不同编译器产生的obj就无法匹配了。

(7. 我们只有获得一个对象后,才能通过对象使用this指针。如果我们知道一个对象this指针的位置,可以直接使用吗?
this指针只有在成员函数中才有定义。因此,你获得一个对象后,也不能通过对象使用this指针。所以,我们无法知道一个对象this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,你是可以知道this指针的位置的(可以通过&this获得),也可以直接使用它。


a.this 指针是C++中的一个关键字,也是一个const指针,它指向当前对象,通过它可以访问当前对象的所有成员。

void Student :: setNam(char*name){
this ->name = name;
 }
this虽然用在类的内部,但是只有在对象被创建以后才会给this赋值,并且这个赋值的过程是编译器自动完成的,不需要用户干预,用户也不能显示的给this赋值。

b.this是const指针,他的值是不能被修改的,一切企图修改该指针的操作,如赋值,递增,递减等都是不允许的

c.this只能在成员函数内部使用,用在其他地方没有意义,也是非法的。

d.只有对象被创建后this才有意义,因此不能再static成员函数中使用

e.this实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this。不过this这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中

f.this作为隐式形参,本质上是成员函数的局部变量所以只能在成员函数内部使用,并且只有在通过对象调用成员函数时才给this赋值。



13.头文件中的 ifndef/define/endif是干什么用的?

头文件中的 ifndef/define/endif是条件编译的一种,除了防止头文件被重复引用(整体),还可以防止重复定义(变量、宏或结构)



14.评价一下C与C++的各自特点。

C是一种结构化的语言,重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入进行运算处理得到输出,而对于C++首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程控制.



15.C++中的空默认产生哪些类成员函数?

默认的构造函数,析构函数,默认的复制构造函数,默认的赋值函数



16.struct是否可以拥有成员函数?如果可以,那么struct和class还有区别么?

C语言中的struct结构体不可以有函数。C++中的struct可以有各种成员函数。 class和struct的区别是,class默认的访问控制权限为private,默认私有继承。struct默认的访问控制权限为public,默认为公有继承。因为C++编译器得兼容以前用C语言struct结构体开发的项目



17.哪一种成员变量可以在同一个类的实例之间共享?

必须使用静态成员变量在一个类的所有实例间共享数据。如果想限制对静态成员变量的访问,则必须把它们声明为私有型或保护型。不允许用静态成员变量去存放某一个对象的数据。静态成员数据是在这个类的对象间共享的。

如果把静态成员数据设为私有,可以通过公有的静态成员函数访问。



18.为什么虚拟的析构函数是必要的?

我们可以先构造一个类如下:
class CBase  {  
    public:  
        ~CBase(){......}   
};  
class CChild : public CBase  {  
    public:  
        ~CChild(){......}  
};  

int main()  {  
    CChild c;  
    return 0;  
}  
上面的代码在运行时,由于在生成CChild对象c时,实际上在调用CChild类的构造函数之前必须首先调用其基类CBase的构造函数,所以当撤销c时,也会在调用CChild类析构函数之后,调用CBase类的析构函数(析构函数调用顺序与构造函数相反)。也就是说,无论析构函数是不是虚函数,派生类对象被撤销时,肯定会依次上调其基类的析构函数。
那么为什么CObject类要搞一个虚的析构函数呢?
因为多态的存在。
仍以上面的代码为例,如果main()中有如下代码:
CBase *pBase;  
CChild c;  
pBase = &c;  
那么在pBase指针被撤销时,调用的是CBase的析构函数还是CChild的呢?显然是CBase的(静态联编)析构函数。但如果把CBase类的析构函数改成virtual型,当pBase指针被撤销时,就会先调用CChild类的析构函数,再调用CBase类的析构函数。
答案:在这个例子中,所有对象都存在于栈框中,当离开其所处的作用域时,该对象会被自动撤销,似乎看不出什么大问题。但是试想,如果CChild类的构造函数在堆中分配了内存,而其析构函数又不是virtual型的,那么撤销pBase时,将不会调用CChild::~CChild(),从而不会释放CChild::CChild()占据的内存,造成内存泄漏。
将基类的析构函数设为virtual型,则所有基类的派生类的析构函数都将自动变为virtual型,这保证了在任何情况下,不会出现由于析构函数未被调用而导致的内存泄漏。



19.析构函数可以virtual型,构造函数则不能。那么为什么构造函数不能为虚呢?

构造函数不能申明为virtual也是很明显的,virtual关键字是为了实现多态性,它与构造函数根本就不在一个时段里出现。构造函数,只是在创建对象的时候会被调用,而多态性,需要的使用情景是,父类指针指向子类对象。然后通过该指针调用函数的时候出现了多态调用。给构造函数定义virtual根本没有意义。多态最直观的意思是,某个函数调用只有在实行的时候才能知道它要执行那个函数体。
虚函数采用虚调用的办法。虚调用是一种可以在只有部分信息的情况下工作的机制,特别允许我们调用一个只知道接口而不知道其准确对象类型的函数。但是如果创造一个对象,你势必要知道对象的准确类型,因此构造函数不能为virtual型。



20.如果虚函数是非常有效的,我们是否可以把每个函数都声明为虚函数?

 不行,因为虚函数是有代价的:由于每个虚函数的对象都必须维护一个虚函数表,因此在使用虚函数的时候会产生一个系统开销。如果仅仅是很小的类,且不想派生其他的类,根本没有必要使用虚函数。



21.什么是多态?

多态性可以简单地概括为“一个接口,多种方法”,在程序运行的过程中才决定调用的函数。同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法(虚函数)。多态性是面向对象编程领域的核心概念。
C++中的多态性具体体现在运行和编译两个方面。运行时多态是动态多态,其具体引用的对象在运行时才能确定。编译时多态是静态多态,在编译时就可以确定对象使用的形式。
C++中,实现多态有以下方法:虚函数,抽象类,覆盖,模板(重载和多态无关)。


虚函数就是允许被其子类重新定义的成员函数。而子类重新定义父类虚函数的做法,称为“覆盖”(override)
其实,重载的概念并不属于“面向对象编程”,重载的实现是编译器根据函数不同的参数列表,对同名函数的名称做修饰,然后这些同名函数就具有了不同的函数名(至少对于编译器来说是这样的)。如,有两个同名函数func(int p)和func(string p),那么经过编译器的修饰后,这两个函数名可能是int_func和str_func。对于这两个函数的调用,在编译期间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期间就绑定了(早绑定)。因此,重载和多态无关,它只是一种语法规则。
真正与多态相关的是覆盖,父类指针是可以指向子类对象的,当子类重新定义了父类的虚函数后,父类指针根据赋给它的子类对象的不同,动态(注意:是动态)地调用属于子类的函数,这样的调用在编译期间是无法确定的(调用的具体的子类的虚函数地址无法给出),因此,这样的函数地址是在运行时绑定的(晚绑定)。
结论就是,重载只是一种语言特性,与多态无关,也与面向对象无关。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态!”
那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类)。它们的目的都是为了代码重用,而多态则是为了实现另一个目的——接口重用。而且现实往往是,要有效重用代码很难,而真正最具有价值的重用是接口重用,因为“接口是公司最有价值的资源。设计接口比用一堆类来实现这个接口更费时间,而且接口需要耗费更昂贵的人力和时间。”其实,继承为重用代码而存在的理由已经越来越薄弱,因为“组合”可以很好地取代继承的扩展现有代码的功能,而且“组合”的表现更好(至少可以防止“类爆炸”)。因此笔者个人认为,继承的存在很大程度上是作为“多态”的基础而非扩展现有代码的方式。



22.重载和覆盖有什么不同?

覆盖是指派生类重写基类的虚函数。重写的函数必须有一致的参数表和返回值(C++标准允许返回值不同的情况,但是很少有编译器支持这个特性)。
“重载”,是指编写一个与已有函数同名但是参数表不同的函数,两个函数必须处于同一个作用域。重载不是一种面向对象的编程,而只是一种语法规则,重载与多态没有什么直接关系。



23.什么是虚继承?它与一般的继承有什么不同?它有什么用?写出一段虚继承的C++代码。

虚拟继承是多重继承中特有的概念。虚基类是为了解决多重继承而出现的,请看下图:

QQ截图20111106094148

 

在图 1中,类D接触自类B和类C,而类B和类C都继承自类A,因此出现了图 2所示的情况。

在图 2中,类D中会出现两次A。为了节省内存空间,可以将B、C对A的继承定义为虚拟继承,而A成了虚拟基类。最后形成了图 3。

代码如下:

1
2
3
4
class  A;
class  B : public  virtual  A;
class  C : public  virtual  A;
class  D : public  B, public  C;
虚函数 继承和虚继承是完全不同的两个概念。

    虚函数:类将维护一个虚函数表,来记录对应的函数入口地址

    虚继承:派生类将维护一个虚类指针,来指向其父类。



24.为什么虚函数效率低?

因为虚函数需要一次间接的寻址,而一般的函数可以在编译时定位到函数的地址,虚函数(动态类型调用)是要根据某个指针定位到函数的地址。针对类的虚函数机制,如果有虚函数的话,编译器会为类增加一个虚函数表,当在动态执行程序时,会到该虚函数表中寻找函数。多增加了一个过程, 效率肯定会低一些,但带来了运行时的多态。



25.虚函数的入口地址和普通函数的区别是什么?

每个虚函数在vtable中占了一个表项,该表项保存着虚函数的入口地址。当创建一个包含虚函数的对象时,该对象在头部附加一个指针,指向vtable中相应的位置。调用虚函数的时候,不管你是用什么指针调用的,它先跟据vtable找到入口地址再执行,从而实现了动态联编。而不像普通函数那样简单的跳转到一个固定的地址。



26.多重继承的优点和缺陷

优点:对象可以调用多个基类中的接口。
缺点:易产生二义性和钻石型(菱形)继承问题。



27.多继承时,类D同时继承自A和B,A和B都有一个函数foo( ),如何在子类对象中明确指出调用哪个父类的foo( )?

D d;

d.A::foo( );

d.B::foo( );



28.什么是虚指针?

虚指针或虚函数指针是一个虚函数的实现细节。带有虚函数的类中的每一个对象都有一个虚指针指向该类的虚函数表



29.C++中如何阻止一个类被实例化?构造函数被声明成private会阻止编译器生成默认的copy constructor吗?什么时候编译器会生成默认的copy constructor呢?如果此时你已经写了一个构造函数,编译器还会生成copy constructor吗?

1.使用抽象类,或构造函数声明成private

2.构造函数设为private,并不能阻止编译器生成默认的copy constructor,这是毫不相干的两件事情。如果我们没有定义复制构造函数,编译器就会为我们合成一个。与和成默认构造函数不同,即使我们定义了其他构造函数,也会合成复制构造函数

3.只要自己没写,而程序中需要,都会生成

4.会生成



30.

区分虚表,虚表指针。虚继承,偏移指针


C++ 虚函数表解析//

https://coolshell.cn/articles/12165.html

C++ 对象的内存布局//

https://coolshell.cn/articles/12176.html

虚函数、虚指针和虚表//

http://eriol.iteye.com/blog/1167737

解析虚函数表和虚继承//

http://blog.csdn.net/xy913741894/article/details/52981011

菱形继承与菱形虚拟继承(上)//
http://blog.csdn.net/qq_34992845/article/details/58300556
多态+菱形虚拟继承(下)//
http://blog.csdn.net/qq_34992845/article/details/59213498




多态性从字面上理解就是多种形态,多种形式。具体到C++这种面向对象(OOP)的语言中,其实就是“一种接口,多种实现(方法)”。 
多态可分为静态多态动态多态。

静态多态动态多态的区别其实只是在什么时候将函数实现和函数调用关联起来,是在编译时期还是运行时期,即函数地址是早绑定还是晚绑定的? 
静态多态是指在编译期间就可以确定函数的调用地址,并生成代码,这就是静态的,也就是说地址是早早绑定的,静态多态也往往被叫做静态联编。 
动态多态则是指函数调用的地址不能在编译期间确定,必须需要在运行时才确定,这就属于晚绑定,动态多态也往往被叫做动态联编。 
静态多态往往通过模版(泛型编程)来实现

template <typename T>
T Add( T a, T b )
{
    return a + b;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

为什么要有多态?

我们知道C++有封装,继承和多态三大特性,封装可以使得代码模块化,继承可以在原有的代码基础上扩展目的是为了代码重用。而多态则是为了接口重用。也就是说,不论传递过来的究竟是哪个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。 

class Base {
public:
    void fun()
    {
        cout << "Base::fun()" << endl;
    }
};
class Derived : public Base {
public:
    //void fun()
    //{
    //  cout << "Derived::fun()" << endl;
    //}

};

void FunTest()
{
    Base b;
    Derived d;
    b.fun();//调用基类的fun()打印Base::fun()
    d.fun();//子类继承了基类的fun()且子类本身没有fun(),因此此处还是调用基类的fun()打印Base::fun()
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
class Base {
public:
    void fun()
    {
        cout << "Base::fun()" << endl;
    }
};

class Derived : public Base {
public:
    void fun()
    {
        cout << "Derived::fun()" << endl;
    }

};

void FunTest()
{
    Base b;
    Derived d;
    b.fun();//调用基类的fun()打印Base::fun()
    d.fun();//子类虽然继承了基类的fun()但是子类定义了fun(),此时基类中的fun()被隐藏,因此调用的是子类的fun()打印Derived::fun()
    d.Base::fun();//若想调用基类的fun()需要加类的作用域限定符,打印Base::fun()
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 指针调用:
void FunTest()
{
    Base b;
    Derived d;
    Base* pb = &b;
    Derived* pd = &d;
    pb->fun();//pb指向基类,打印Base::fun()
    pd->fun();//pd指向子类,打印Derived::fun()

    pb = &d; pb->fun();//pb指向子类,打印Base::fun()
    //同理引用也是
    Base& rb = b;
    Derived& rd = d;
    rb.fun();//rb引用基类,打印Base::fun()
    rd.fun();//rd引用子类,打印Derived::fun()

    Base& rd2 = d;//rd2引用子类,打印Base::fun()
    rd2.fun();
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

C++继承中有赋值兼容,即基类指针可以指向子类,那么为什么还会出现基类指针指向子类或者基类对象引用子类对象,却调用基类自己的fun函数打印Base::fun()呢?这就是我们上面讲的静态联编,在编译时期就将函数实现和函数调用关联起来,不管是引用还是指针在编译时期都是Base类的自然调用Base类的fun()。为了避免这种情况,我们引入了动态多态。 
所谓的动态多态是通过继承+虚函数来实现的,只有在程序运行期间(非编译期)才能判断所引用对象的实际类型,根据其实际类型调用相应的方法。具体格式就是使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,并且派生类需要重新实现该成员函数,编译器将实现动态绑定。 
在上面的代码如果我们在基类的fun函数前加virtual即可实现动态绑定:

class Base {
public:
    virtual void fun()
    {
        cout << "Base::fun()" << endl;
    }
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其他不变,在FunTest函数中就达到我们想要的效果:

void FunTest()
{
    Derived d;

    Base* pb = &d;
    pb->fun();//基类指针pb指向子类,可以打印Derived::fun()
    Base& rb = d;
    rb.fun();//基类对象pb引用子类,可以打印Derived::fun()
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

动态绑定条件:
1、必须是虚函数 
2、通过基类类型的引用或者指针调用虚函数 

重载重写(覆盖),重定义(隐藏)。 
所谓重载是指在同一作用域中允许有多个同名函数,而这些函数的参数列表不同,包括参数个数不同,类型不同,次序不同,需要注意的是返回值相同与否并不影响是否重载。比如int fun()和void fun()不构成重载,连编译都过不去,给出的提示是无法重载仅按返回类型区分的函数。 
重写(覆盖)重定义(隐藏)则有点像,区别就是在写重写的函数是否是虚函数,只有重写了虚函数的才能算作是体现了C++多态性,否则即为重定义,在之前的代码中,我们看到子类继承了基类的fun()函数,若是子类没有fun函数,依旧会调用基类的fun函数,若是子类已重定义,则调用自己的fun函数,这就叫做同名隐藏,当然此时如果还想调用基类的fun函数,只需在调用fun函数前加基类和作用域限定符即可。综上他们的关系和区别如下:

重载:

1.同一作用域

2.函数名相同

3.参数不同,返回值可以不同

4.virtual关键字可有可无

覆盖/重写:  派生类函数覆盖基类函数

1.不同作用域(派生类和基类)

2.函数名相同,参数相同,返回值相同(但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针,这被称为返回类型协变,允许返回类型随类类型的编号而变化)

3.必须有 virtual关键字

4.访问修饰符可以不同

注意:如果基类函数声明有重载版本,则应在派生类中重写所有的基类版本,如果只重写一个版本,另外两个版本将被隐藏,派生类对象将无法使用它们。 如果不需要修改,则重写版本可以只调用基类版本

隐藏/重定义:

1.派生类和基类,函数名相同,参数不同。此时无论有无virtual关键字,基类的函数将被隐藏

2.派生类和基类,函数名相同,参数相同,但基类没有virtual关键字,此时基类函数被隐藏

3.不同作用域

包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数必须在派生类中重新定义以后,派生类才能实例化出对象。

class Person
{
public:
    virtual void Display() = 0; // 纯虚函数
private:
    string _name; // 姓名
};
class Student : public Person
{
    void Display()
    {
        cout << "Student" << endl;
    }
};

int main()
{
    //Person p;不能用抽象类实例化
    Student stu;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

抽象类往往用于这样的情况,它可以方便我们使用多态特性,且在很多情况下,基类本身生成对象是不合情理的,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就要使用抽象类,就像一个水果类可以派生出橘子香蕉苹果等等,但是水果类本身定义对象并不合理也没有必要。

要点

1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外) 
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性(可以加virtual 让人清楚看到它是一个虚函数, 虚函数和纯虚函数特性都可以继承,但纯虚函数可以重写为虚函数(就可以实例化出对象,不重写,派生类照样不能实例化))。 
3、只有类的非静态成员函数才能定义为虚函数,静态成员函数和友元函数不能定义为虚函数。 
4、如果在类外定义虚函数,在声明函数时加virtual关键字,定义时不用加。 
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容 
易混淆。 
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会 
出现未定义的行为。 
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构 
函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)。 
8、虚表是所有类对象实例共用的。

class A
{
public:
    virtual void Display()
    {}
};
class B
{
public:
    void Display()
    {}
};

int main()
{
    cout << sizeof(A) << endl;//4
    cout << sizeof(B) << endl;//1
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

我们知道一个空类占一个字节,那为什么加了virtual关键字就变成了4? 
这是因为每个有虚函数的类或者虚继承的子类,编译器都会为它生成一个虚拟函数表(简称:虚表),表中的每一个元素都指向一个虚函数的地址。(注意:虚表是从属于类的) 
此外,编译器会为包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针(常被称为vptr),每一个由此类别派生出来的类,都有这么一个vptr。虚表指针是从属于对象的。也就是说,如果一个类含有虚表,则该类的所有对象都会含有一个虚表指针,并且该虚表指针指向同一个虚表。因此这里的4是指针的大小。


31.智能指针

http://www.cnblogs.com/Lynn-Zhang/p/5699983.html

比特公众号,智能指针那些事

http://blog.csdn.net/ssopp24/article/details/71437987

Boost智能指针——weak_ptr

http://www.cnblogs.com/TianFang/archive/2008/09/20/1294590.html

智能指针 weak_ptr

http://blog.csdn.net/mmzsyx/article/details/8090849



32.C语言中结构体和联合体的区别?

结构体是一种自定义数据类型,包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。
结构体的定义形式为:
struct 结构体名{
    结构体所包含的变量或数组
};
struct stu stu1, stu2; 注意关键字struct不能少。
还可以在定义结构体的同时定义结构体变量:
struct stu{
    char *name;
    int num;
    int age;
    char group;
    float score;
} stu1, stu2;
如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名
struct{
    char *name;
    int num;
    int age;
    char group;
    float score;
} stu1, stu2;
这样的写法很简单,但是因为没有结构体名,后面就没法用该结构体定义新的变量了。
理论上讲结构体的各个成员在内存中是连续存储的,和数组非常类似。但是在编译器的具体实现中,各个成员之间可能会存在空隙,即结构体内存对齐
结构体和数组类似,也是一组数据的集合,结构体使用点号.获取单个成员。
除了这种方式赋值外,还可以在定义的时候赋值:
struct{
    char *name;
    int num;
    int age;
    char group;
    float score;
} stu1, stu2 = { "haozi", 12, 18, 'A', 136.5 };
如果在定义结构体变量时并未对其赋初始值,那么在程序中要对它赋值的话,就只能一个一个地对其成员逐一赋值,或者用已赋值的同类型的结构体变量对它赋值。

联合体(共用体)
共用体(Union),它的定义格式为:
union 共用体名{
    成员列表
};
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙,进行内存对齐),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
共用体也是一种自定义类型,可以通过它来创建变量,例如:
union data{
    int n;
    char ch;
    double f;
};
union data a, b, c;
上面是先定义共用体,再创建变量,也可以在定义共用体的同时创建变量:
union data{
    int n;
    char ch;
    double f;
} a, b, c;
共用体 data 中,成员 f 占用的内存最多,为 8 个字节,所以 data 类型的变量(也就是 a、b、c)也占用 8 个字节的内存

联合体
用途:使几个不同类型的变量共占一段内存(相互覆盖)
结构体是一种构造数据类型
用途:把不同类型的数据组合成一个整体-------自定义数据类型
联合体变量所占内存长度是各最长的成员占的内存长度,联合体每次只能存放一种变量
联合体变量中起作用的成员是最后一次存放的成员,在存入新的成员后原有的成员失去了作用

Struct与Union主要有以下区别:
1. struct和union都是由多个不同的数据类型成员组成, 但在任何同一时刻, union中只存放了一个被选中的成员, 而struct的所有成员都存在。在struct中,各成员都占有自己的内存空间,它们是同时存在的。一个struct变量的总长度大于等于所有成员长度之和。在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在。Union变量的长度等于最长的成员的长度。
2. 对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于struct的不同成员赋值是互不影响的。

在C/C++程序的编写中,当多个基本数据类型或复合数据结构要占用同一片内存时,我们要使用联合体;当多种类型,多个对象,多个事物只取其一时,我们也可以使用联合体来发挥其长处。
联合体变量在内存中的排列为声明的顺序从低到高 --> 判断大小端



33.位段

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
位域定义与结构定义相仿,其形式为:
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度
struct bs
{
    int a:8;
    int b:2;
    int c:6;
};
位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:
struct bs
{
    int a:8;
    int b:2;
    int c:6;
}data;
说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:
1. 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
struct bs
{
    unsigned a:4;
    unsigned :0;
    unsigned b:4;
    unsigned c:4;
}
在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
2. 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。
3. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k
{
    int a:1;
    int :2;
    int b:3;
    int c:2;
};
从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。
位域的使用
位域的使用和结构成员的使用相同,其一般形式为:位域变量名·位域名
位域允许用各种格式输出。
main(){
    struct bs
        {
             unsigned a:1;
             unsigned b:3;
             unsigned c:4;
        } bit,*pbit;
    bit.a=1;
    bit.b=7;
    bit.c=15;
    printf("%d,%d,%d\n",bit.a,bit.b,bit.c);
    pbit=&bit;
    pbit->a=0;
    pbit->b&=3;
    pbit->c|=1;
    printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);
}
应注意赋值不能超过该位域的允许范围 

http://www.cnblogs.com/pure/archive/2013/04/22/3034818.html



34.虚表

https://baike.baidu.com/item/%E8%99%9A%E8%A1%A8/5332652?fr=aladdin

http://blog.jobbole.com/103102/



35.STL中都有什么容器?以及他们的运用?

http://www.cnblogs.com/wuchanming/p/4057439.html

http://blog.csdn.net/conanswp/article/details/23297441

http://blog.csdn.net/raito__/article/details/51592149

http://blog.sina.com.cn/s/blog_5413483701016413.html

http://www.cnblogs.com/dong008259/archive/2012/03/06/2380724.html



36.vector和list的区别以及其增删查改的时间复杂度

得把 vector 和 list 的模拟实现 好好看下

http://blog.163.com/lhl_soft/blog/static/20175000420120161422375/

http://blog.csdn.net/zhangbinsijifeng/article/details/52083749



37.C++中我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数(写在函数的最后表示不能修改成员变量即修饰this指针,不是修饰返回值和形参)。请问:能不能同时用static和const修饰类的成员函数?
不可以。const修饰类的成员函数是为了类的实例不被该成员函数修改,实现原理是当我们使用了const修饰一个成员函数时,比如下面的一个成员函数
void method(int parament) const;
编译器编译后是这样的
void method(const *this,int parament)
我们知道在类的方法中(非静态方法)所有访问到类的成员时,都会在编译时加上一个隐藏的this指针,而this指针指向的是具体的那个实例本身。这也是实例化同一个类的具体实例只通过同一个函数却能够正确的访问到属于自己的成员的原因。
这里的const修饰this指针,对this指针加上const修饰后我们就不能在这个方法中改变其成员变量了。
对于static修饰的成员函数,能够改变类的静态变量。既然能够改变也就不能够有const修饰了,因为这是冲突的。static修饰的成员函数只能访问静态变量,而静态变量不属于具体的实例,它是所有产生于同一个类的具体实例都能够访问的。
通俗点说就是const修饰的是不变的this指针,static静态成员函数根本没有this指针。两者一起用是有矛盾的。



38.比较好的博客

http://blog.csdn.net/bcypxl/article/category/1667711

http://blog.csdn.net/mbuger/article/details/52356961

http://blog.csdn.net/u013309870/article/details/77430017

http://blog.csdn.net/gebushuaidanhenhuai/article/details/77164748

http://blog.csdn.net/han_xiaoyang

http://blog.csdn.net/lixungogogo/article/details/52201202



39.OSI,TCP/IP,五层协议的体系结构,以及各层协议

https://www.2cto.com/net/201310/252965.html

http://blog.csdn.net/qq_29817411/article/details/51802147

http://blog.chinaunix.net/uid-23622436-id-2394060.html

http://blog.csdn.net/superjunjin/article/details/7841099

OSI分层 (7层):物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
TCP/IP分层(4层):网络接口层、 网际层、运输层、 应用层。
五层协议 (5层):物理层、数据链路层、网络层、运输层、 应用层。
每一层的协议如下:
物理层:RJ45、CLOCK、IEEE802.3 (中继器,集线器)
数据链路:PPP、FR、HDLC、VLAN、MAC (网桥,交换机)
网络层:IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP、 (路由器)
传输层:TCP、UDP、SPX
会话层:NFS、SQL、NETBIOS、RPC
表示层:JPEG、MPEG、ASII
应用层:FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS
每一层的作用如下:
物理层:通过媒介传输比特,确定机械及电气规范(比特Bit)
数据链路层:将比特组装成帧和点到点的传递(帧Frame)
网络层:负责数据包从源到宿的传递和网际互连(包PackeT)
传输层:提供端到端的可靠报文传递和错误恢复(段Segment)
会话层:建立、管理和终止会话(会话协议数据单元SPDU)
表示层:对数据进行翻译、加密和压缩(表示协议数据单元PPDU)
应用层:允许访问OSI环境的手段(应用协议数据单元APDU)



40.TCP和UDP的区别?

http://blog.csdn.net/li_ning_/article/details/52117463

http://www.cnblogs.com/xiaomayizoe/p/5258754.html

http://blog.csdn.net/quiet_girl/article/details/50599777

http://www.cnblogs.com/LUO77/p/5801977.html

http://blog.csdn.net/u014682691/article/details/52061646

TCP提供面向连接的、可靠的数据流传输,而UDP提供的是非面向连接的、不可靠的数据流传输。
TCP传输单位称为TCP报文段,UDP传输单位称为用户数据报。
TCP注重数据安全性,UDP数据传输快,因为不需要连接等待,少了许多操作,但是其安全性却一般。
TCP对应的协议和UDP对应的协议
TCP对应的协议:
(1) FTP:定义了文件传输协议,使用21端口。
(2) Telnet:一种用于远程登陆的端口,使用23端口,用户可以以自己的身份远程连接到计算机上,可提供基于DOS模式下的通信服务。
(3) SMTP:邮件传送协议,用于发送邮件。服务器开放的是25号端口。
(4) POP3:它是和SMTP对应,POP3用于接收邮件。POP3协议所用的是110端口。
(5)HTTP:是从Web服务器传输超文本到本地浏览器的传送协议。
UDP对应的协议:
(1) DNS:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。
(2) SNMP:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。
(3) TFTP(Trival File Transfer Protocal),简单文件传输协议,该协议在熟知端口69上使用UDP服务。



41.虚函数和纯虚函数的区别

定义一个函数为虚函数,不代表函数为不被实现的函数。
定义为虚函数是为了允许用基类的指针或引用来调用派生类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
1、简介
假设我们有下面的类层次:
class A    
{    
public:    
    virtual void foo()    
    {    
        cout<<"A::foo() is called"<<endl;    
    }    
};    
class B:public A    
{    
public:    
    void foo()    
    {    
        cout<<"B::foo() is called"<<endl;    
    }    
};    
int main(void)    
{    
    A *a = new B();    
    a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!    
    return 0;    

这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。
虚函数只能借助于指针或者引用来达到多态的效果。
C++纯虚函数
一、定义
 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
 virtual void funtion1()=0
二、引入原因
  1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
  2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
  为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。
纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。
纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。


抽象类的介绍
抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。
(1)抽象类的定义:  称带有纯虚函数的类为抽象类。
(2)抽象类的作用:
抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
(3)使用抽象类时注意:
•   抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
•   抽象类是不能定义对象的。

总结:
1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
2、虚函数声明如下:virtual ReturnType FunctionName(Parameter);虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
4、实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
5、虚函数是C++中用于实现多态的机制。核心理念就是通过基类访问派生类定义的函数。
6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用父类的析构函数。

有纯虚函数的类是抽象类,不能生成对象,只能派生。如果它派生的类的纯虚函数没有被改写,那么它的派生类还是个抽象类。
定义纯虚函数就是为了让基类不可实例化化
因为实例化这样的抽象数据结构本身并没有意义,或者给出实现也没有意义
实际上我个人认为纯虚函数的引入,是出于两个目的
1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。
2、为了效率,不是程序执行的效率,而是为了编码的效率。



42.排序

博客:

http://blog.csdn.net/whuslei/article/details/6442755/

http://www.cnblogs.com/wxisme/p/5243631.html

http://www.cnblogs.com/end/archive/2011/10/22/2220995.html

http://blog.csdn.net/mbuger/article/details/67643185

http://blog.csdn.net/li563868273/article/details/51200876

http://blog.csdn.net/p10010/article/details/49557763

快()些(希尔)选()一堆()美女  不稳定

http://blog.csdn.net/u010817474/article/details/48435365

http://blog.csdn.net/tengjian6107/article/details/53898899


http://blog.csdn.net/han_xiaoyang/article/details/12163251

对于冒泡、快排(交换)。选择、堆排(选择)插入、希尔(插入)归并(归并)这些比较排序。给出一串数字,要会 从第一个要排序数字到最后一次要排序数字排序过程(思想和代码,时间复杂度,空间复杂度不用说,必须掌握)。

对于计数排序,基数排序 非比较排序掌握思想

http://www.cnblogs.com/kkun/archive/2011/11/23/2260299.html

评价排序算法好坏的标准:

1.时间/空间复杂度

2.算法本身复杂度

空间复杂度:若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间是O(1).否则一般空间复杂度为O(N)

时间复杂度:大多数排序算法的时间开销主要是关键字之间的比较和记录的移动。



43.IP地址的分类

A类地址:以0开头,   第一个字节范围:1~127(1.0.0.0 - 127.255.255.255);
B类地址:以10开头,  第一个字节范围:128~191(128.0.0.0 - 191.255.255.255);
C类地址:以110开头, 第一个字节范围:192~223(192.0.0.0 - 223.255.255.255);
D类地址:以1110开头,第一个字节范围:224~239(224.0.0.0 - 239.255.255.255);(作为多播使用)
E类地址:保留
其中A、B、C是基本类,D、E类作为多播和保留使用。
以下是留用的内部私有地址:
A类 10.0.0.0--10.255.255.255
B类 172.16.0.0--172.31.255.255
C类 192.168.0.0--192.168.255.255
IP地址与子网掩码相与得到网络号:
ip       : 192.168.2.110
&
Submask : 255.255.255.0
----------------------------
网络号   :192.168.2  .0
注:
主机号,全为0的是网络号(例如:192.168.2.0),主机号全为1的为广播地址(255.255.255.255)



44.网络

ARP是地址解析协议,简单语言解释一下工作原理。
1:首先,每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。
2:当源主机要发送数据时,首先检查ARP列表中是否有对应IP地址的目的主机的MAC地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机 IP地址,源主机MAC地址,目的主机的IP 地址。
3:当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。
4:源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
广播发送ARP请求,单播发送ARP响应。


各种协议的介绍
ICMP协议: 因特网控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。
TFTP协议: 是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。
HTTP协议: 超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。
NAT协议:网络地址转换属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术,
DHCP协议:动态主机配置协议,是一种让系统得以连接到网络上,并获取所需要的配置参数手段,使用UDP协议工作。具体用途:给内部网络或网络服务供应商自动分配IP地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。


描述RARP协议
RARP是逆地址解析协议,作用是完成硬件地址到IP地址的映射,主要用于无盘工作站,因为给无盘工作站配置的IP地址不能保存。工作流程:在网络中配置一台RARP服务器,里面保存着IP地址和MAC地址的映射关系,当无盘工作站启动后,就封装一个RARP数据包,里面有其MAC地址,然后广播到网络上去,当服务器收到请求包后,就查找对应的MAC地址的IP地址装入响应报文中发回给请求者。因为需要广播请求报文,因此RARP只能用于具有广播能力的网络。


TCP三次握手和四次挥手的全过程
三次握手:
第一次握手:客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
四次挥手
与建立连接的“三次握手”类似,断开一个TCP连接则需要“四次握手”。
第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接受数据。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
第三次挥手:(第二次挥手与第三次挥手有时间间隔,因为被动方还需要接受数据)被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
http://www.yunsec.net/a/school/wlcs/agreement/2012/0317/10262.html
三次握手和四次挥手的各种状态

http://blog.csdn.net/a987073381/article/details/52206215

在浏览器中输入www.baidu.com后执行的全部过程
客户端浏览器通过DNS解析到www.baidu.com 的IP地址220.181.27.48,通过这个IP地址找到客户端到服务器的路径。客户端浏览器发起一个HTTP会话到220.181.27.48,然后通过TCP进行封装数据包,输入到网络层。
2、在客户端的传输层,把HTTP会话请求分成报文段,添加源和目的端口,如服务器使用80端口监听客户端的请求,客户端由系统随机选择一个端口如5000,与服务器进行交换,服务器把相应的请求返回给客户端的5000端口。然后使用IP层的IP地址查找目的端。
3、客户端的网络层不用关心应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,我不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。
4、客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定IP地址的MAC地址,然后发送ARP请求查找目的地址,如果得到回应后就可以使用ARP的请求应答交换的IP数据包现在就可以传输了,然后发送IP数据包到达服务器的地址。


DNS域名系统,简单描述其工作原理。
当DNS客户机需要在程序中使用名称时,它会查询DNS服务器来解析该名称。客户机发送的每条查询信息包括三条信息:包括:指定的DNS域名,指定的查询类型,DNS域名的指定类别。基于UDP服务,端口53. 该应用一般不直接为用户使用,而是为其他应用服务,如HTTP,SMTP等在其中需要完成主机名到IP地址的转换。


TCP的三次握手过程?为什么会采用三次握手,若采用二次握手可以吗?
建立连接的过程是利用客户服务器模式,假设主机A为客户端,主机B为服务器端。
(1)TCP的三次握手过程:主机A向B发送连接请求;主机B对收到的主机A的报文段进行确认;主机A再次对主机B的确认进行确认。
(2)采用三次握手是为了防止失效的连接请求报文段突然又传送到主机B,因而产生错误。失效的连接请求报文段是指:主机A发出的连接请求没有收到主机B的确认,于是经过一段时间后,主机A又重新向主机B发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机A第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机B,主机B以为是主机A又发起的新连接,于是主机B同意连接,并向主机A发回确认,但是此时主机A根本不会理会,主机B就一直在等待主机A发送数据,导致主机B的资源浪费。
(3)采用两次握手不行,原因就是上面说的实效的连接请求的特殊情况。
http://blog.csdn.net/mbuger/article/details/74098552
http://blog.csdn.net/u013309870/article/details/77430017


了解交换机、路由器、网关的概念,并知道各自的用途
:1)交换机
在计算机网络系统中,交换机是针对共享工作模式的弱点而推出的。交换机拥有一条高带宽的背部总线和内部交换矩阵。交换机的所有的端口都挂接在这条背 部总线上,当控制电路收到数据包以后,处理端口会查找内存中的地址对照表以确定目的MAC(网卡的硬件地址)的NIC(网卡)挂接在哪个端口上,通过内部 交换矩阵迅速将数据包传送到目的端口。目的MAC若不存在,交换机才广播到所有的端口,接收端口回应后交换机会“学习”新的地址,并把它添加入内部地址表 中。
交换机工作于OSI参考模型的第二层,即数据链路层。交换机内部的CPU会在每个端口成功连接时,通过ARP协议学习它的MAC地址,保存成一张 ARP表。在今后的通讯中,发往该MAC地址的数据包将仅送往其对应的端口,而不是所有的端口。因此,交换机可用于划分数据链路层广播,即冲突域;但它不 能划分网络层广播,即广播域。
交换机被广泛应用于二层网络交换,俗称“二层交换机”。
交换机的种类有:二层交换机、三层交换机、四层交换机、七层交换机分别工作在OSI七层模型中的第二层、第三层、第四层盒第七层,并因此而得名。
2)路由器
路由器(Router)是一种计算机网络设备,提供了路由与转送两种重要机制,可以决定数据包从来源端到目的端所经过 的路由路径(host到host之间的传输路径),这个过程称为路由;将路由器输入端的数据包移送至适当的路由器输出端(在路由器内部进行),这称为转 送。路由工作在OSI模型的第三层——即网络层,例如网际协议。
路由器的一个作用是连通不同的网络,另一个作用是选择信息传送的线路。 路由器与交换器的差别,路由器是属于OSI第三层的产品,交换器是OSI第二层的产品(这里特指二层交换机)。
3)网关
网关(Gateway),网关顾名思义就是连接两个网络的设备,区别于路由器(由于历史的原因,许多有关TCP/IP 的文献曾经把网络层使用的路由器(Router)称为网关,在今天很多局域网采用都是路由来接入网络,因此现在通常指的网关就是路由器的IP),经常在家 庭中或者小型企业网络中使用,用于连接局域网和Internet。 网关也经常指把一种协议转成另一种协议的设备,比如语音网关。
在传统TCP/IP术语中,网络设备只分成两种,一种为网关(gateway),另一种为主机(host)。网关能在网络间转递数据包,但主机不能 转送数据包。在主机(又称终端系统,end system)中,数据包需经过TCP/IP四层协议处理,但是在网关(又称中介系 统,intermediate system)只需要到达网际层(Internet layer),决定路径之后就可以转送。在当时,网关 (gateway)与路由器(router)还没有区别。
在现代网络术语中,网关(gateway)与路由器(router)的定义不同。网关(gateway)能在不同协议间移动数据,而路由器(router)是在不同网络间移动数据,相当于传统所说的IP网关(IP gateway)。
网关是连接两个网络的设备,对于语音网关来说,他可以连接PSTN网络和以太网,这就相当于VOIP,把不同电话中的模拟信号通过网关而转换成数字信号,而且加入协议再去传输。在到了接收端的时候再通过网关还原成模拟的电话信号,最后才能在电话机上听到。
对于以太网中的网关只能转发三层以上数据包,这一点和路由是一样的。而不同的是网关中并没有路由表,他只能按照预先设定的不同网段来进行转发。网关最重要的一点就是端口映射,子网内用户在外网看来只是外网的IP地址对应着不同的端口,这样看来就会保护子网内的用户。


45.C++中使用哪些技术可以替代宏?为什么建议使用使用这些技术去替代宏?
使用const/enum/inline function可以替代宏。Const和enum替代宏常量,inline function 替代宏函数。
(1)     宏不可以调试查看,const/enum/inline function可以调试。
(2)     写宏函数容易出错,代码的可读性和可维护性差。
(3)     宏常量和宏函数缺少类型安全的检查,使用const/enum/inline function时编译器会进行类型安全的检查。



46.malloc/free和new/delete的区别和联系?
1、new/delete是C++的关键字,而malloc/free是C中的函数。
2、new做两件事,一是分配内存,二是调用类的构造函数(调用一个或多个构造函数(new[]));同样,delete会调用类的析构函数(一个或多个)和释放内存。而malloc和free只是分配和释放内存,它们是函数不在编译器的控制范围之内,不能执行构造或析构函数。
3、new返回对象类型指针,new失败抛异常。而malloc返回void*,如果分配空间失败,函数返回NULL。
4、new/delete是关键字,不需要头文件支持;malloc/free需要头文件库函数支持。
5、如果用free释放new的内存,程序会因为无法调用析构函数而报错。如果用delete释放之前malloc申请的内存,理论上不会出错,但是程序的可读性较差,new/delete和malloc/free一定要配对使用
6、有了new/delete,为什么还需要malloc/free呢?
因为C++经常会用到C程序,而C中只能用malloc/free管理动态内存。
7、它们都是动态管理内存的入口,申请的内存存储在堆上,不会因为函数的结束而自动销毁,这和存储在栈中的变量不一样。
8、malloc/free需要手动计算空间大小,new/delete可自动计算类型的大小。
9、new运算符实际上底层是调用malloc( )实现,相同的情况,delete运算符也总是调用标准的free( )完成;
extern void   
operator delete( void* ptr ){  
if (NULL != ptr)
free( (char*)ptr );    
}

申请一个对象的内存:
widget   *pw1   =   new   widget;// 默认构造函数
申请对象的内存并进行初始化:
widget   *pw1   =   new   widget(8);
申请多个对象的内存:
widget   *pw1   =   new   widget[5];// 默认构造函数5次

void UseMallocFree( ){
    Obj* a = (Obj*)malloc(sizeof(obj));
    a->Intialize( );
    // ...
    a->Destroy( );
    free( a );
}

void UseNewDelete()
{
    Obj *a = new Obj;
    //...
    delete a;
}
函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数UseNewDelete则简单得多。



47.什么是单例模式,说说你对单例模式的理解,设计实现一个单例类class Singleton

http://www.cnblogs.com/kubixuesheng/p/4355055.html

http://blog.csdn.net/yqynsmile/article/details/52304824



48.面向对象的三大特性是什么?分别描述一下你对它们的理解。
封装、继承、多态。
封装:把方法和数据封装到一个类中,通过访问控制符public/protected/private隐藏部分成员,暴露部分成员。提高程序的安全性和内聚度,设计出低耦合高内聚的系统。
继承:多个子类去继承父类的成员,达到代码复用。通过继承方式和访问控制符,控制继承哪些成员。
多态:多态就是一个接口多种形态,C++中分为静态多态和动态多态,提高程序的灵活性。


49.什么多态?动态多态在C++语言层是怎么实现的?
多态分为静态多态和动态多态。静态多态就是模板函数;动态多态是子类去继承父类,子类重写父类的虚方法。
动态多态是通过虚函数表实现的。


50.请实现一个简单的多态继承关系的例子?并讲解一下C++的多态是怎么实现的(面试时建议画对象存储模型解释)?
class A
{
public :
     virtual void fun1()
    {}
     void fun2 ()
    {}
private :
     int a ;
};


class B : public  A
{
public :
     virtual void fun1()
    {}
     virtual void fun3()
    {}
     void fun4 ()
    {}
private :
     int b ;
};
A的大小是多少?B的大小是多少?
8                12
画图板画对象模型图
http://www.cnblogs.com/cxq0017/p/6074247.html 这篇博客结合对象模型那的博客回答
写一个程序打印虚表
见之前博客操作



51.C++中包含哪几种强制类型转换?他们有什么区别和联系

http://blog.csdn.net/zhouwei1221q/article/details/47280919

http://blog.csdn.net/cmm0401/article/details/66040168



52.http://blog.csdn.net/lixungogogo/article/details/52201202

http://blog.csdn.net/zhongqi0808/article/details/47302801

http://btdcw.com/btd_03cyi1qemd97tl37kuug5o77k30e8m00qp2_1.html

http://www.lxway.com/549440264.htm

http://3y.uu456.com/bp_03cyi1qemd97tl37kuug5o77k30e8m00qp2_1.html



53.源代码->可执行程序

http://www.cnblogs.com/shihaochangeworld/p/5657224.html



54.面向过程和面向对象

https://www.zhihu.com/question/27468564

http://blog.csdn.net/u014602483/article/details/47815519

http://www.cnblogs.com/BeiGuo-FengGuang/p/5935763.html



55.Linux操作系统常用命令

http://www.eepw.com.cn/article/268125.htm

http://blog.csdn.net/u011974987/article/details/52695647



56.进程间通信,线程同步

http://www.cnblogs.com/mydomain/archive/2010/09/23/1833369.html

http://www.cnblogs.com/CheeseZH/p/5264465.html

http://blog.csdn.net/zsf8701/article/details/7844316



57.迭代器失效

http://blog.csdn.net/lujiandong1/article/details/49872763

http://www.cnblogs.com/blueoverflow/p/4923523.html

http://blog.csdn.net/hackbuteer1/article/details/7734382



58.解释作业,进程,线程,管程定义

作业:用户在一次解题或一个事务处理过程中要求计算机系统所做工作的集合。它包括用户程序、所需要的数据及控制命令等。作业是由一系列有序的步骤组成的。 
进程:一个程序在一个数据集上的一次运行过程。所以一个程序在不同数据集合上运行,乃至一个程序在同样数据集合上多次运行都是不同的进程。 
线程:线程是进程中的一个实体,是被系统独立调度和执行的基本单位。 
管程:管程实际上是定义了一个数据结构和在该数据结构上的能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。



59.进程与线程的区别与联系

http://blog.csdn.net/yaosiming2011/article/details/44280797/

http://www.cnblogs.com/wangzhenghua/p/4447570.html



60.进程间的通信如何实现? 
现在最常用的进程间通信的方式有信号、信号量、消息队列、共享内存。 
所谓进程通信就是不同进程之间进行一些“接触”。这种接触有简单,也有复杂。机制不同,复杂度也不一样。通信是一个广义上的意义,不仅仅指传递一些message。它们的使用方法也是基本相同的,所以只要掌握了一种使用方法,然后记住其他的使用方法就可以了。信号和信号量是不同的,它们虽然都可以用来实现同步和互斥,但前者是使用信号处理器来进行的,后者是使用P、V操作来实现的。消息队列是比较高级的一种进程间通信方法,因为它真的可以在进程间传送message,连传送一个”I see you”都可以。 
一个消息队列可以被多个进程所共享;如果一个进程的消息太多,一个消息队列放不下,也可以用多于一个的消息队列(不过可能管理会比较复杂)。共享消息队列的进程所发送的消息中除了message本身外还有一个标志,这个标志可以指明该消息将由哪个进程或者哪类进程接受。每一个共享消息队列的进程对这个队列也有自己的标志,可以用来声明自己的身份。



61.同一进程中线程的共享资源以及独有资源
线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。     进程拥有这许多共性的同时,还拥有自己的个性。有了这些个性,线程才能实现并发性。

共享的资源:
a. 堆  由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)
b. 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的
c. 静态变量 虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的
d. 文件等公用资源  这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。
独享的资源有

独有的资源:
 1.线程ID
      每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标识线程。
 
    2.寄存器组的值
       由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
 
    3.线程的堆栈
       堆栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程 必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响。

    4.错误返回码
       由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用 后设置了errno值,而在该线程还没有处理这个错误,另外一个线程就在此时被调度器投入运行,这样错误值就有可能被修改。    所以,不同的线程应该拥有自己的错误返回码变量。

    5.线程的信号屏蔽码
       由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器。

    6.线程的优先级
       由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参数,这个参数就是线程的优先级。
 
A. stack              : 每线程一个,这个无疑问
B. data section    :只有只读数据段可共享,读写的一旦写一次后,就不共享了 
C. register set     : 每个线程都得保存自己的上下文, 线程切入时恢复至硬件的寄存器,线程切出时需保存
D. file fd             :文件等公用资源 
E.threadID        : 每线程一个,这个无疑问



62.

1.单目运算符除后置++(a++)是从左向右结合,别的都是从右向左结合 !a, ~b

2..逗号运算符 从左向右,整个表达式的值是最后一个表达式的值

3.互斥锁是用于线程间互斥的

4.STL remove方法 --> 收藏夹

5.空指针 解引用 --> 报错

6.重载 收藏夹

7.含纯虚函数派生出的基类,派生类实例化时会调用基类构造函数

8.a[0] = OX12,a[1] = OX34  -->  大端1234,小端3412


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值