C++面试总结(六)杂记

1.malloc、free与new、delete的区别? 
1)malloc是函数,而new是操作符 
2)malloc申请内存时,需要我们指定申请的空间大小,且返回的类型为void*,需要将其强制转换为所需类型指针;new申请内存时,会根据所申请的类型自动计算申请空间的大小,且可直接返回指定类型的指针 
3)malloc释放内存时,用free函数,而new删除对象时,用的是delete操作符 
4)malloc/free申请释放内存时,不需要需要调用析构函数,而new/delete申请释放内存时需要调用析构函数

2.sizeof的使用

对引用类型执行sizeof运算得到被引用对象所占空间的大小;对数组执行sizeof得到整个数组所占空间的大小。sizeof运算不会把数组转换成指针处理;对string对象或者vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用多少空间。
(1)sizeof运算符在编译阶段处理的特性?

sizeof操作符在其作用范围内,内容不会被编译,而只是简单替换成其类型。

(2)sizeof(字符串序列)和sizeof(字符串数组)的区别?

sizeof(字符串序列)=字符串长度+1;(算上最后面的’\0’) 
sizeof(字符串数组)=字符串长度;(用{}表示时)

(3)sizeof(动态数组)?

动态数组实质上是一个指针,其占用空间为4/8(32位/64位系统)。

int *d = new int[10];
cout<<sizeof(d)<<endl; // 4

void main()
{
    vector<int> vec(10,0);
    cout<<sizeof(vec)<<endl;//输出16
}
//vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用多少空间

(4)用strlen得到字符串的长度? 

    char p[]="abcdefg";
    char a[]={'a','b','c','\0'};
    cout<<strlen(p)<<endl;//输出7
    cout<<strlen(a)<<endl;//输出3

(5)sizeof(联合)需要注意的占用空间最大的成员及对齐方式问题? 

联合与结构的区别:结构中各成员有各自的内存空间,结构变量所占用内存空间是各成员所占用内存空间之和;联合中,各成员共享一段内存空间,一个联合变量所占用内存空间等于各成员中所占用内存空间最大的变量所占用内存空间(需考虑内存对齐问题),此时共享意味着每次只能赋一种值,赋入新值则冲去旧值。

union u //8对齐
{
    double a;
    int b;
};

union u2 //4对齐
{
   char a[13];
   int b;
};

union u3 //1对齐
{
   char a[13];
   char b;
};
cout<<sizeof(u)<<endl;  // 8
cout<<sizeof(u2)<<endl;  // 16
cout<<sizeof(u3)<<endl;  // 13

知道union的大小取决于它所有的成员中,占用空间最大的一个成员的大小。所以对于u来说,大小就是最大的double类型成员a了,所以sizeof(u)=sizeof(double)=8。但是对于u2和u3,最大的空间都是char[13]类型的数组,为什么u3的大小是13,而u2是16呢?关键在于u2中的成员int b。由于int类型成员的存在,使u2的对齐方式变成4(4字节对齐),也就是说,u2的大小必须在4的对界上,所以占用的空间变成了16(最接近13的对界)。 
结论:复合数据类型,如union,struct,class的对齐方式为成员中对齐方式最大的成员的对齐方式。

(6)sizeof(class)需注意情况?

sizeof只计算数据成员的大小;不计算static数据成员的大小;继承时需要考虑基类数据成员问题,考虑虚函数问题,无论有多少个虚函数,计算空间时虚函数表只计算一次。 
补充:当派生类存在多重继承时,sizeof运算结果?

class A
{
    int a;
    int b;
    virtual void fun()
    {
    }
};
class B
{
    int c;
    virtual void fun1()
    {
    }
    virtual void fun2()
    {
    }
};
class C:public A,public B
{
    int d;
};

int main( ) 
{    
    cout<<sizeof(A)<<endl;//输出12
    cout<<sizeof(B)<<endl;//输出8
    cout<<sizeof(C)<<endl;//输出24
    return 0;    
}

对于同一个类的不同虚函数,只需考虑一次即可,而对于不同类的虚函数,派生类需要都考虑(每一个类虚函数只需考虑一次)。可参考虚函数表里的内存分配原则。

(7)对struct进行sizeof操作 

对于一个struct取sizeof,要考虑对界的问题。对界是取struct中最大的数据作为对界值。对于以下三个struct,有以下解答:

struct s1
{
  char a;
  double b;
  int c;
  char d; 
};
//sizeof(struct s1)=1+7+8+4+1+3=24;对界值取8(7,3是为满足对界填充的空间)
struct s2
{
  char a;
  char b;
  int c;
  double d;  
};
//sizeof(struct s2)=1+1+2+4+8=16;对界值取8(2是为满足对界填充的空间)
struct X 
{ 
    short s; 
    int i; 
    char c;
};
//sizeof(struct X)=2+2+4+1+3=12;对界值取4(第二个2和3是为满足对界填充的空间)

(8)sizeof多维数组

double* (*a)[3][6];//类型为double*;a是一个指针,*a表示一个数组指针,指向一个2维数组
cout<<sizeof(a)<<endl; //4;
cout<<sizeof(*a)<<endl;  // 72=3*6*sizeof(double *)
cout<<sizeof(**a)<<endl; // 24=6*sizeof(double *)
cout<<sizeof(***a)<<endl; // 4=sizeof(double *)
cout<<sizeof(****a)<<endl; // 8=sizeof(double)

 a是一个很奇怪的定义,他表示一个指向 double*[3][6]类型数组的指针,此3×6数组中存储的是指向double的指针。既然是指针,所以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

3.静态分配与动态分配的区别? 
静态分配是指在编译期间就能确定内存的大小,由编译器分配内存。动态分配是指在程序运行期间,由程序员申请的内存空间。堆和栈都可以动态分配,但静态分配只能是栈。

内存的静态分配和动态分配的区别主要是两个

      一是时间不同。静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候。

      二是空间不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由函数malloc进行分配。不过栈的动态分配和堆不同,他的动态分配是由编译器进行释放,无需我们手工实现。    

对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区静态数据区动态数据区

    动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。

4.深拷贝与浅拷贝的区别? 

深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同),对其中任何一个对象的改动都会影响另外一个对象。 
换种解释:浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。 
浅拷贝会出现什么问题呢? 
其一,浅拷贝只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构时,会造成同一份资源析构2次,即delete同一块内存2次,造成程序崩溃。 
其二,浅拷贝使得源对象和拷贝对象指向同一块内存,任何一方的变动都会影响到另一方。 
其三,在释放内存的时候,会造成拷贝对象原有的内存没有被释放造成内存泄露。(源对象内存被释放后,由于源对象和拷贝对象指向同一个内存空间,拷贝对象的空间不能再被利用了,删除拷贝对象不会成功,无法操作该空间,所以导致内存泄露) 
类的静态成员是所有类的实例共有的,存储在全局(静态)区,只此一份,不管继承、实例化还是拷贝都是一份。因此类的静态成员不允许深拷贝。

5.memcpy的用法与strcpy之间的区别?

memcpy函数的功能是从源指针所指的内存地址的起始位置开始拷贝n个字节到目标指针所指的内存地址的起始位置中。 
void *memcpy(void *dest, const void *src, size_t n); //函数返回指向dest的指针 
strcpy和memcpy主要有以下3方面的区别。 
1)复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。 
2)复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符”\0”才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。 
3)用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。

6.malloc、alloc、calloc、realloc的区别? 
alloc:唯一在栈上申请内存的,无需释放; 
malloc:在堆上申请内存,最常用; 
calloc:malloc+初始化为0; 
realloc:将原本申请的内存区域扩容,参数size大小即为扩容后大小,因此此函数要求size大小必须大于ptr内存大小。

7.智能指针

   由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。用智能指针便可以有效缓解这类问题。auto_ptr、unique_ptr和shared_ptr这几个智能指针背后的设计思想。我简单的总结下就是:将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。对于shared_ptr:赋值,拷贝,向函数传递一个智能指针,或函数返回一个智能指针都会增加当前智能指针的计数;向一个shared_ptr赋予一个新值或者一个shared_ptr被销毁时,计数器就会递减。

8.类型转换有哪些? 

static_cast:任何具有明确定义的类型转换,只要不包含const,都可以使用。如: 

static_cast<double>(int);//将一个int型数据转换为double 

const_cast:用于去掉指针或引用的const属性如:

const int i=10;
int *k=const_cast<int*>(&i);
*k=5;//去除指针的const属性后,可以从新对其赋值

dynamic_cast:支持父类指针(或引用)到子类指针(或引用)之间的多态类型转换,并根据父类指针是否真的转换到子类做相应的处理。对于指针,若cast成功,返回指向子类对象的指针,否则返回NULL;对于引用,若cast成功,返回指向子类对象的引用,否则抛出一个异常。 
注意:dynamic_cast在将父类cast到子类时,父类必须要有虚函数,否则会报错。 
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。 
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的; 
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

reinterpret_cast:一个类型的指针转换为其他类型的指针。 
允许将任何指针类型转换为其它的指针类型;听起来很强大,但是也很不靠谱。它主要用于将一种数据类型从一种类型转换为另一种类型。它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针,在实际开发中,先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原来的指针值。

9.内联函数的优点以及和宏定义的区别? 
内联函数是指用inline关键字修饰的简单短小的函数,它在编译阶段会在函数调用处替换成函数语句,从而减少函数调用的开销。在类内定义的函数被默认成内联函数。 
1.宏定义是在预处理阶段进行简单替换,而内联函数是在编译阶段进行替换 
2.编译器会对内联函数的参数类型做安全检查或自动类型转换,而宏定义则不会; 
3.内联函数在运行时可调试,而宏定义不可以 
4.在类中声明同时定义的成员函数,自动转化为内联函数。 
5.宏定义会出现二义性,而内联函数不会 
6.内联函数可以访问类的成员变量,宏定义则不能


10.字符串和字符数组

char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;//0  分别指向各自的栈内存
cout << ( str3 == str4 ) << endl;//0  分别指向各自的栈内存
cout << ( str5 == str6 ) << endl;//1指向文字常量区地址相同
cout << ( str7 == str8 ) << endl;//1指向文字常量区地址相同

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值