Skip to content

Latest commit

 

History

History
477 lines (414 loc) · 26 KB

File metadata and controls

477 lines (414 loc) · 26 KB

变量的声明和定义有什么区别

为变量分配地址和存储空间的称为定义, 不分配地址的称为声明; 一个变量可以在多个地方声明, 但是只在一个地方定义;
加入extern修饰的是变量的声明, 说明此变量将在文件以外或在文件后面部分定义; 说明: 很多时候一个变量, 只是声明不分配内存空间, 直到具体使用时才初始化, 分配内存空间,如外部变量;

写出bool, int, float, 指针变量与"零值"比较的if语句

bool型数据: if( flag ) { A; } else { B; }   
int型数据: if( 0 != flag ) { A; } else { B; }
指针型数: if( NULL == flag ) { A; } else { B; }   
float型数据: if(x>0.000001&&x<-0.000001) { A;}

注意: 应特别注意在int, 指针型变量和"零值"比较的时候,把"零值"放在左边, 这样当把“=="误写成“="时,编译器可以报错, 否则这种逻辑错误不容易发现, 并且可能导致很严重的后果;

写一个“标准"宏Min

#define Min(a, b) ((a)<=(b)?(a):(b))

a和&a有什么区别

请写出以下代码的打印结果, 主要目的是考察a和&a的区别

#include<stdio.h>	 
void main() 	
{	
	int a[5]={1,2,3,4,5};	
	int *ptr=(int *)(&a+1); 
	printf("%d,%d",*(a+1),*(ptr-1));	
}

输出结果: 2, 5, 注意: 数组名a可以作数组的首地址, 而&a是数组的指针

简述C, C++程序编译的内存分配情况

C, C++中内存分配方式可以分为三种:

  1. 从静态存储区域分配: 内存在程序编译时就已经分配好, 这块内存在程序的整个运行期间都存在; 速度快, 不容易出错, 因为有系统会善后; 例如全局变量, static变量等;
  2. 在栈上分配: 在执行函数时, 函数内局部变量的存储单元都在栈上创建, 函数执行结束时这些存储单元自动被释放;栈内存分配运算内置于处理器的指令集中, 效率很高, 但是分配的内存容量有限;
  3. 从堆上分配: 即动态内存分配; 程序在运行的时候用malloc或new申请任意大小的内存, 程序员自己负责在何时用free或delete释放内存; 动态内存的生存期由程序员决定, 使用非常灵活; 如果在堆上分配了空间, 就有责任回收它, 否则运行的程序会出现内存泄漏, 另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块;

C, C++程序编译时内存分为5大存储区: 堆区, 栈区, 全局区, 文字常量区, 程序代码区

设置地址为0x67a9的整型变量的值为0xaa66

int *ptr;		ptr = (int *)0x67a9;		*ptr = 0xaa66;

说明: 这道题就是强制类型转换的典型例子, 无论在什么平台地址长度和整型数据的长度是一样的, 即一个整型数据可以强制转换成地址指针类型, 只要有意义即可;
那么要是想让程序跳转到绝对地址是0x100000去执行, 应该怎么做?
*((void (*)( ))0x100000 ) ( );
首先要将0x100000强制转换成函数指针, 即:
(void (*)())0x100000
然后再调用它: *((void (*)())0x100000)();
用typedef可以看得更直观些:
typedef void(*)() voidFuncPtr;
*((voidFuncPtr)0x100000)();

面向对象的三大特征

  • 封装性: 将客观事物抽象成类, 每个类对自身的数据和方法实行protection(private, protected,public);
  • 继承性: 它可以使用现有类的所有功能, 并在无需重新编写原来的类的情况下对这些功能进行扩展; 广义的继承有三种实现形式: 实现继承(使用基类的属性和方法而无需额外编码的能力), 可视继承(子窗体使用父窗体的外观和实现代码), 接口继承(仅使用属性和方法,实现滞后到子类实现);
  • 多态性: 是将父类对象设置成为和一个或更多它的子对象相等的技术; 用子类对象给父类对象赋值之后, 父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作;

简单地说就是同样的消息被不同的类型的对象接收时导致不同的行为; 基本分为两类, 编译时的多态性, 通过函数重载实现; 运行时的多态,由虚函数来实现;

C++的空类有哪些成员函数

Empty();                               // 缺省构造函数
Empty( const Empty& );                // 拷贝构造函数
~Empty();                             // 析构函数
Empty& operator=( const Empty& );   // 赋值运算符
Empty* operator&();                  // 取址运算符
const Empty* operator&() const;      // 取址运算符 const

常量指针与指针常量的区别

  • 常量指针: 指针指向的对象是常量, 那么这个对象不能被更改, const int *p;
  • 指针常量: 指针常量的值是指针, 这个值因为是常量, 所以不能被赋值, 在声明的时候一定要给它赋初始值; 一旦赋值, 以后这个常量再也不能指向别的地址; 虽然指针常量的值不能变, 可是它指向的对象是可变的, 因为我们并没有限制它指向的对象是常量, int *const b = &a; //const放在指针声明操作符的右侧指针常量强调的是指针的不可改变性, 而常量指针强调的是指针对其所指对象的不可改变性

谈谈你对拷贝构造函数和赋值运算符的认识

拷贝构造函数和赋值运算符重载有以下两个不同之处:

  1. 拷贝构造函数生成新的类对象,而赋值运算符不能;
  2. 由于拷贝构造函数是直接构造一个新的类对象, 所以在初始化这个对象之前不用检验源对象是否和新建对象相同;而赋值运算符则需要这个操作, 另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉

用C++设计一个不能被继承的类

template <typename T>
class A 
{ 
	friend T;
	private: 
		A() {} 
		~A() {} 
}; 
class B : virtual public A<B> 
{ 
	public: 
		B() {} 
		~B() {} 
}; 
class C : virtual public B   //不能访问A类的构造函数,因此不能实现继承
{ 
	public: 
		C() {} 
		~C() {} 
}; 
void main( void ) 
{ 
	B b; 
	//C c; return; 
}
注意: 构造函数是继承实现的关键, 每次子类对象构造时, 首先调用的是父类的构造函数, 然后才是自己的; 

访问基类的私有虚函数

#include <iostream.h> 
class A
{ 
	virtual void g() { cout << "A::g" << endl; } 
	private: 
	virtual void f() { cout << "A::f" << endl; 
}; 
class B : public A 
{ 
	void g() { cout << "B::g" << endl; } 
	virtual void h() { cout << "B::h" << endl; } 
}; 
typedef void( *Fun )( void ); 
void main() 
{ 
	B b; 
	Fun pFun; 
	for(int i = 0 ; i < 3; i++) 
	{ 
		pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i ); 
		pFun();
	}
}

简述类成员函数的重写、重载和隐藏的区别

(1)重写和重载主要有以下几点不同:

  • 范围的区别: 被重写和重写的函数在两个类中, 而重载和被重载的函数在同一个类
  • 参数的区别: 被重写函数和重写函数的参数列表一定相同, 而被重载函数和重载函数的参数列表一定不同;
  • virtual的区别: 重写的基类中被重写的函数必须要有virtual修饰, 而重载函数和被重载函数可以被virtual修饰, 也可以没有;

(2)隐藏和重写, 重载有以下几点不同;

  • 与重载的范围不同: 和重写一样, 隐藏函数和被隐藏函数不在同一个类中;
  • 参数的区别: 隐藏函数和被隐藏的函数的参数列表可以相同, 也可不同, 但是函数名肯定要相同; 当参数不相同时, 无论基类中的参数是否被virtual修饰, 基类的函数都是被隐藏, 而不是被重写;

简述多态实现的原理

编译器发现一个类中有虚函数, 便会立即为此类生成虚函数表 vtable;
虚函数表的各表项为指向对应虚函数的指针; 编译器还会在此类中隐含插入一个指针vptr(对vc编译器来说, 它插在类的第一个位置上)指向虚函数表; 调用此类的构造函数时, 在类的构造函数中, 编译器会隐含执行vptr与vtable的关联代码, 将vptr指向对应的vtable, 将类与此类的vtable联系了起来;
另外在调用类的构造函数时, 指向基础类的指针此时已经变成指向具体的类的this指针, 这样依靠此this指针即可得到正确的vtable; 如此才能真正与函数体进行连接, 这就是动态联编,实现多态的基本原理; 注意: 一定要区分虚函数,纯虚函数, 虚拟继承的关系和区别;牢记虚函数实现原理, 因为多态C++面试的重要考点之一 , 而虚函数是实现多态的基础;

链表和数组有什么区别

数组和链表有以下几点不同:

  1. 存储形式:数组是一块连续的空间,声明时就要确定长度;链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针;
  2. 数据查找:数组的线性查找速度快,查找操作直接使用偏移地址;链表需要按顺序检索结点,效率低;
  3. 数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动;
  4. 越界问题:链表不存在越界问题,数组有越界问题;

说明: 在选择数组或链表数据结构时, 一定要根据实际需要进行选择; 数组便于查询, 链表便于插入删除; 数组节省空间但是长度固定, 链表虽然变长但是占了更多的存储空间;

typedef和define有什么区别

  1. 用法不同: typedef用来定义一种数据类型的别名, 增强程序的可读性; define主要用来定义常量, 以及书写复杂使用频繁的宏;
  2. 执行时间不同: typedef是编译过程的一部分, 有类型检查的功能; define是宏定义, 是预编译的部分, 其发生在编译之前, 只是简单的进行字符串的替换, 不进行类型的检查;
  3. 作用域不同: typedef有作用域限定; define不受作用域约束, 只要是在define声明后的引用都是正确的;
  4. 对指针的操作不同: typedef和define定义的指针时有很大的区别;

注意: typedef定义是语句,因为句尾要加上分号; 而define不是语句, 千万不能在句尾加分号;

关键字const是什么

const用来定义一个只读的变量或对象; 主要优点:便于类型检查, 同宏定义一样可以方便地进行参数的修改和调整, 节省空间, 避免不必要的内存分配, 可为函数重载提供参考;

说明: const修饰函数参数, 是一种编程规范的要求, 便于阅读, 一看即知这个参数不能被改变, 实现时不易出错;

static有什么作用

static在C中主要用于定义全局静态变量, 定义局部静态变量, 定义静态函数; 在C++中新增了两种作用: 定义静态数据成员, 静态函数成员;

  1. 在函数体, 一个被声明为静态的变量在这一函数被调用过程中维持其值不变;
  2. 隐藏, 当我们同时编译多个文件时, 所有未加static前缀的全局变量和函数都具有全局可见性, 如果加了static, 就会对其它源文件隐藏;
  3. static的第三个作用是默认初始化为0; 其实全局变量也具备这一属性, 因为全局变量也存储在静态数据区; 在静态数据区, 内存中所有的字节默认值都是0x00, 某些时候这一特点可以减少程序员的工作量;

最后对static的三条作用做一句话总结: 首先static的最主要功能是隐藏, 其次因为static变量存放在静态存储区, 所以它具备持久性和默认值0;

extern有什么作用

extern标识的变量或者函数声明其定义在别的文件中, 提示编译器遇到此变量和函数时在其它模块中寻找其定义;

如何避免“野指针"

"野指针"产生原因及解决办法如下:

  1. 指针变量声明时没有被初始化; 解决办法: 指针声明时初始化, 可以是具体的地址值, 也可让它指向NULL;
  2. 指针 p 被 free 或者 delete 之后, 没有置为 NULL; 解决办法: 指针指向的内存空间被释放后指针应该指向NULL;
  3. 指针操作超越了变量的作用范围; 解决办法: 在变量的作用域结束前释放掉变量的地址空间并且让指针指向NULL;

常引用有什么作用

常引用的引入主要是为了避免使用变量的引用时, 在不知情的情况下改变变量的值; 常引用主要用于定义一个普通变量的只读属性的别名, 作为函数的传入形参, 避免实参在调用函数中被意外的改变;

说明: 很多情况下, 需要用常引用做形参, 被引用对象等效于常对象, 不能在函数中改变实参的值, 这样的好处是有较高的易读性和较小的出错率;

简述队列和栈的异同

队列和栈都是线性存储结构, 但是两者的插入和删除数据的操作不同, 队列是"先进先出", 栈是“后进先出";

谈谈你对编程规范的理解或认识

编程规范可总结为: 程序的可行性, 可读性, 可移植性以及可测试性;

说明: 这是编程规范的总纲目, 面试者不一定要去背诵上面给出的那几个例子, 应该去理解这几个例子说明的问题, 想一想, 自己如何解决可行性, 可读性, 可移植性以及可测试性这几个问题, 结合以上几个例子和自己平时的编程习惯来回答这个问题;

short i = 0; i = i + 1L;这两句有错吗

代码一是错的, 代码二是正确的;

说明: 在数据安全的情况下大类型的数据向小类型的数据转换一定要显示的强制类型转换;

&&和&、||和|有什么区别

  1. & 和 | 对操作数进行求值运算, && 和 || 只是判断逻辑关系;
  2. && 和 || 在在判断左侧操作数就能确定结果的情况下就不再对右侧操作数求值;

注意: 在编程的时候有些时候将 && 或 || 替换成 & 或 | 没有出错, 但是其逻辑是错误的, 可能会导致不可预想的后果(比如当两个操作数一个是1另一个是2时);

指针和引用主要区别

  1. 指针是一个实体, 而引用仅是个别名;
  2. 引用使用时无需解引用(*), 指针需要解引用;
  3. 引用初始化以后不能被改变, 指针可以改变所指的对象;
  4. 引用必须被初始化, 指针不声明时初始化;
  5. 引用不能为空, 指针可以为空;
  6. sizeof(引用), 得到的是所指向的变量(对象)的大小, 而sizeof(指针)得到的是指针本身(所指向的变量或对象的地址)的大小;
  7. 指针和引用的自增(++)运算意义不一样;
  8. 从内存分配上看: 程序为指针变量分配内存区域, 而引用不需要分配内存区域;
  9. 引用初始化以后不能被改变, 指针可以改变所指的对象;

简述strcpy、sprintf与memcpy的区别

  1. 操作对象不同, strcpy的两个操作对象均为字符串, sprintf的操作源对象可以是多种数据类型, 目的操作对象是字符串, memcpy 的两个对象就是两个任意可操作的内存地址, 并不限于何种数据类型;
  2. 执行效率不同, memcpy最高, strcpy次之, sprintf的效率最低;
  3. 实现功能不同, strcpy主要实现字符串变量间的拷贝, sprintf主要实现其他数据类型格式到字符串的转化, memcpy主要是内存块间的拷贝;

构造函数能否为虚函数

构造函数不能是虚函数; 而且不能在构造函数中调用虚函数, 因为那样实际执行的是父类的对应函数, 因为自己还没有构造好; 析构函数可以是虚函数, 而且在一个复杂类结构中, 这往往是必须的; 析构函数也可以是纯虚函数, 但纯虚析构函数必须有定义体, 因为析构函数的调用是在子类中隐含的;

说明: 虚函数的动态绑定特性是实现重载的关键技术, 动态绑定根据实际的调用情况查询相应类的虚函数表, 调用相应的虚函数;

编码实现某一变量某位清0或置1

给定一个整型变量a, 写两段代码, 第一个设置a的bit 3, 第二个清a的bit 3, 在以上两个操作中, 要保持其他位不变;

#define BIT3 (0x1 << 3 ) 
Satic int a;
void set_bit3( void ) 
{
	a |= BIT3;           //将a第3位置1
}
void set_bit3( void ) 
{ 
	a &= ~BIT3;        //将a第3位清零 
}

变量详解

局部变量, 也称内部变量, 在函数内部声明定义的, 作用域仅限于函数内部, 离开该作用域使用是非法;
全局变量, 也称为外部变量, 在函数外部定义的变量, 不属于那一个函数, 作用域是整个源程序; 变量的存储方式: 动态存储方式与静态存储方式; 用户存储空间的分配分为 程序区, 静态存储区, 动态存储区;
动态存储区放的数据有: 函数形式参数, 自动变量(未加static声明的局部变量), 函数调用时的现场保护和返回地址; 变量类型有: auto变量(没有static修饰), 用static声明的局部变量(静态局部变量,函数调用后不消失而保留原值), register变量(局部变量的值放在CPU的寄存器中,叫做寄存器变量), 用extern声明的外部变量;

在头文件中使用#ifndef,#define,#endif

可以防止双重定义的错误
如你在头文件aaa.h中定义了一个类aaa如下:

class   aaa   
{   
};   

如果两次#include "aaa.h"(不见得是直接, 也有可能两个不同的头文件中都包含了这个头文件)就会出错, 因为相同的类不能定义两次, 把aaa.h稍做修改:

#ifndef   _aaa_   
#define   _aaa_   
class   aaa   
{   
};   
#endif   

就可以避免这样的问题; 因为当你已经包含过这个文件, _aaa_就会有了定义, 那么#ifndef的条件为假, 就不会再执行后面的类定义了;

什么是预编译 何时需要预编译

预编译又称为预处理, 是做些代码文本的替换工作; 处理#开头的指令, 比如拷贝 #include 包含的文件代码, #define 宏定义的替换, 条件编译等, 就是为编译做的预备工作的阶段, 主要处理#开始的预编译指令, 预编译指令指示了在程序正式编译前就由编译器进行的操作, 可以放在程序中的任何位置;

  1. 总是使用不经常改动的大型代码体;
  2. 程序由多个模块组成, 所有模块都使用一组标准的包含文件和相同的编译选项; 在这种情况下, 可以将所有包含文件预编译为一个预编译头;

提供的预处理功能主要有以下三种: 宏定义, 文件包含, 条件编译;

关键字volatile有什么含意

提示编译器对象的值可能在编译器未监测到的情况下改变; volatile是一个类型修饰符, 它是被设计用来修饰被不同线程访问和修改的变量, 如果没有volatile, 基本上会导致这样的结果: 要么无法编写多线程程序, 要么编译器失去大量优化的机会, 一个定义为volatile的变量是说这变量可能会被意想不到地改变, 这样编译器就不会去假设这个变量的值了;

交换两个变量的值,不使用第三个变量

即a=3, b=5, 交换之后a=5, b=3;
有两种解法, 一种用算术算法, 一种用^(异或):

a = a + b;
b = a - b;
a = a - b; 
//or
a = a^b; // 只能对int,char..
b = a^b;
a = a^b;
//or
a ^= b ^= a;

c和c++中的struct, class

c和c++中struct的主要区别是c中的struct不可以含有成员函数, 而c++中的struct可以;
c++中struct和class的主要区别在于默认的存取权限不同, struct默认为public, 而class默认为private;

下面语句产生什么结果, 为什么

char szstr[10];
strcpy(szstr,"0123456789");

长度不一样, 会造成非法的操作, 破坏其他内存区的数据;

求数据结构占内存的大小

struct A 
{
       char t:4;
       char k:4;
       unsigned short i:8;
       unsigned long m;
};
sizeof(A) = 8

struct B
{
   char str;
   short x;
   int num;
};
sizeof(B)=8

struct C
{
   char str;
   int num;
   short x;
};
sizeof(C)=12

统计9999的二进制数值中有多少个1

输入x=9999; 
int func ( x )
{   int countx = 0; 
    while ( x ) 
    { 
        countx ++; 
        x = x&(x-1); 
    } 
    return countx; 
} 

9999=9×1024+512+256+15,9×1024中含有1的个数为2, 512中含有1的个数为1, 256中含有1的个数为1, 15中含有1的个数为4, 故共有1的个数为8, 结果为8;
1000 - 1 = 0111, 正好是原数取反, 这就是原理, 用这种方法来求1的个数是很效率很高的; 不必去一个一个地移位,循环次数最少;

int a, b, c 请写函数实现c=a+b, 不可以改变数据类型, 如将c改为long int

关键是如何处理溢出问题;

bool add (int a, int b,int *c)
{	
	*c=a+b;
	return (a>0 && b>0 &&(*c<a || *c<b) || (a<0 && b<0 &&(*c>a || *c>b)));	
} 

strcpy把strSrc复制到strDest, 为什么还要char * 类型的返回值

为了实现链式表达式
例如: int length = strlen(strcpy(strDest,"hello world"));

sizeof()用法汇总

sizeof()功能:计算数据空间的字节数
1.常规
char str1[] = “Hello” ;
char str2[5] = {'H','e','l','l','o'};
char str3[6] = {'H','e','l','l','o','\0'};
char *p1 = "Hello";
char *p2[]={"hello","world"}; 
int n = 10;
int *q = &n;
sizeof (str1 ) = 6 (自动加了'\0') 
strlen (str1 ) = 5 (字符串的长度) 
sizeof (str2 ) = 5 (字符数组的大小)
strlen (str2) = 未知 (该字符串缺少结束符'\0')
sizeof (str3) = 6 (字符数组的大小)
strlen (str3) = 5 (该字符串的长度为5)
sizeof ( p1 ) = 4 (p1是一个指针,大小为4)
sizeof ( p2 ) = 8 (p2是长度为2的字符串数组)
sizeof ( n ) = 4 (整型大小为4)
sizeof ( q ) = 4 (q是一个指针,大小为4)
2.动态分配内存
int *p = (int *)malloc( 100 );
sizeof ( p ) = 4 (p是一个指针,大小为4)
3.函数参数
void Function1( char p[],int num ){
sizeof ( p ) = 4 (数组在做为函数参数时均化为指针)
}
void Function2( int p[],int num ){
sizeof ( p ) = 4 (数组在做为函数参数时均化为指针)
}
4.多重继承
class A{};
class B{};
class C:public A,public B{};
class D:virtual public A{};
class E:virtual public A,virtual public B{};
sizeof ( A ) = 1 (空类大小为1,编译器安插一个char给空类,用来标记它的每一个对象)
sizeof ( B ) = 1 (空类大小为1,编译器安插一个char给空类,用来标记它的每一个对象)
sizeof ( C ) = 1 (继承或多重继承后空类大小还是1)
sizeof ( D ) = 4 (虚继承时编译器为该类安插一个指向父类的指针,指针大小为4)
sizeof ( E ) = 8 (指向父类A的指针与父类B的指针,加起来大小为8)
5.数据对齐
类(或结构)的大小必需为类中最大数据类型的整数倍.CPU访问对齐的数据的效率是最高的,因此通常编译浪费一些空间来使得我们的数据是对齐的
class A{
public:
int a;
};
class B{
public:
int a ;
char b;
};
class C{
public:
int a ;
char b;
char c;
};
sizeof(A) = 4 (内含一个int ,所以大小为4)
sizeof(B) = 8 (int为4,char为1,和为5,考虑到对齐,总大小为int的整数倍即8) 
sizeof(C) = 8 (同上)
6.函数与虚函数
编译器为每个有虚函数的类都建立一个虚函数表(其大小不计算在类中),并为这个类安插一个指向虚函数表的指针,即每个有虚函数的类其大小至少为一个指针的大小4
class A{
public:
int a;
void Function();
};
class B{
public:
int a;
virtual void Function();
};
class C:public B{
public:
char b;
};
class D:public B{
public:
virtual void Function2();
};
class E{
public:
static void Function();
};
sizeof (A) = 4 (内含一个int,普通函数不占大小)
sizeof (B) = 8 (一个int ,一个虚函数表指针)
sizeof (C) =12 (一个int ,一个虚函数表指针,一个char ,再加上数据对齐)
sizeof (D) = 8 (一个int ,一个虚函数表指针,多个虚函数是放在一个表里的,所以虚函数表指针只要一个就行了)
sizeof (E) = 1 (static 函数不占大小,空类大小为1)
7.父类的私有数据
虽然在子类中不可用,但是是可见的,因此私有的数据还是要占子类的大小
class A{
private:
int a;
};
class B:public A{};
sizof(B) = 4; (内含一个不可用的父类的int)
8.大概就这么多了吧,想到再加吧。虚函数,多重继承,空类是比较复杂的,大家大概记住知道就行了
weiloujushi补充:
class static_D
{
int static intVar;
static void fun(){}
};
sizeof(static_D) ==1 //静态数据成员不计入类内
经典问题: 
double* (*a)[3][6]; 
cout<<sizeof(a)<<endl; // 4 a为指针
cout<<sizeof(*a)<<endl; // 72 *a为一个有3*6个指针元素的数组
cout<<sizeof(**a)<<endl; // 24 **a为数组一维的6个指针
cout<<sizeof(***a)<<endl; // 4 ***a为一维的第一个指针
cout<<sizeof(****a)<<endl; // 8 ****a为一个double变量

问题解析: a是一个很奇怪的定义, 他表示一个指向double*[3][6]类型数组的指针;   
既然是指针, 所以sizeof(a)就是4; 既然a是执行double*[3][6]类型的指针,
*a就表示一个double*[3][6]的多维数组类型, 因此sizeof(*a)=3*6*sizeof(double*)=72;   
同样的, **a表示一个double*[6]类型的数组, 所以sizeof(**a)=6*sizeof  (double*)=24;
***a就表示其中的一个元素, 也就是double*了, 所以sizeof(***a)=4; 
至于****a, 就是一个double了, 所以sizeof(****a)=sizeof(double)=8; 

new malloc delete free 区别

  1. delete 用于释放 new 分配的空间; free 用来释放 malloc 分配的空间;
  2. delete[] 用于释放 new[] 分配的空间;
  3. delete 释放空间的时候会调用相应的析构函数, 同时new时调用构造函数, 而malloc不会, 它只是分配内存;
  4. 调用free 之前需要检查 需要释放的指针是否为空,使用delete 释放内存则不需要检查指针是否为NULL
  5. malloc与free是C++/C语言的标准库函数, new/delete是C++的运算符, 它们都可用于申请动态内存和释放内存;

对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。