C++笔试面试常考知识点汇总(三)

来源:互联网 发布:mysql inner join优化 编辑:程序博客网 时间:2024/05/01 14:07

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

void main(){    vector<int> vec(10,0);    cout<<sizeof(vec)<<endl;//输出16}

102:

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

d是我们常说的动态数组,但是他实质上还是一个指针,所以sizeof(d)的值是4/8。

103: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

104:union数据类型占用的空间

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;  // 8cout<<sizeof(u2)<<endl;  // 16cout<<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的对齐方式为成员中对齐方式最大的成员的对齐方式。

105:内存对齐的原则?
内存对齐的好处是可以提高CPU访问内存的效率。
以结构体为例,内存对齐的原则如下:
1:结构体变量的首地址能被其他最宽基本类型成员的大小所整除。
2:结构体所占用的总的内存必须是其中(最大数据类型所占内存和按照#pragma pack(编译器设定的内存对齐数目)指定的数值中)较小的那个值的整数倍。
3:结构体中某个数据内存的起始地址与结构体起始地址之间的差值必须是(该数据所占内存和按照#pragma pack指定的数值中)较小的那个值的整数倍。
为了满足以上条件,常常在数据之间和最后一个数据之后补偿空间以使得内存对齐。

106:sizeof带static成员的类时

class A {     private :          int value;          double a;          static int CST;     public:};int main(){       cout << sizeof(A) << endl; //16}

因为static成员是分配在全局区为类的所有对象共享(VC编译器可能为了方便将其放入文字常量表), sizeof时不应该计入static成员。

107:程序运行时间
clock()是C/C++中的计时函数,而与其相关的数据类型是clock_t。在MSDN中,查得对clock函数定义如下:
clock_t clock(void) ;
clock_t为long类型;
CLOCKS_PER_SEC是标准c的time.h头函数中宏定义的一个常数,用于将clock()函数的结果转化为以秒为单位的量,但是这个量的具体值是与操作系统相关的。VC++6.0中time.h下宏定义的常量,其值为1000。
具体使用可参看下例:

#include<iostream>#include<ctime>usingnamespacestd;int main(){long n=0;clock_tstart,finish;start=clock();while(n<1000000)n++;finish=clock();cout<<double(finish-start)/CLOCKS_PER_SEC<<endl;return 0;}

这是一个求时间差的程序,那么为什么要除以CLOCKS_PER_SEC呢?这是因为clock()是以毫秒为单位,要正确输出时间差需要把它换成秒,因此需要除以CLOCKS_PER_SEC。

108:C++的多态性?
多态性可以简单地概括为“一个接口,多种方法”。
分为编译时多态性和运行时多态性。
编译时多态性:通过重载函数实现 。重载允许有多个同名的函数,而这些函数的参数列表不同。编译器会根据实参类型来选定相应的函数。
运行时多态性:通过虚函数实现。虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者重写。

109:动态绑定是如何实现的?
当基类的指针或者引用指向一个派生类的对象时,在调用相应的虚函数时,会发生动态绑定。直到运行时才能够确定调用的是哪个版本的虚函数,判断的依据是引用或指针所绑定的对象的真实类型。

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

111:操作符重载(+操作符),具体如何去定义?
对于系统的所有操作符,一般情况下,只支持基本数据类型和标准库中提供的class,对于用户自己定义的class,如果想支持基本操作,则需要用户自己来定义关于这个操作符的具体实现。那么为什么叫重载呢?这是因为,在编译器实现的时候,已经为我们提供了这个操作符的基本数据类型实现版本,但是现在他的操作数变成了用户定义的数据类型的对象,所以,需要用户自己来提供该参数版本的实现。
上文总结为一句话就是:用户需要为自己定义的数据类型定义操作符。
操作符重载可以定义为类成员函数或者非成员函数(友员或或独立的操作符重载函数),当定义成成员函数时,必须要显示指定左操作数参数。规定赋值(=)、下标([])、调用(())、成员指向(->)操作符必须被定义为类成员操作符。实际应用中,最好把单目操作符定义为类成员函数,把双目运算法定义为非成员函数(赋值=等除外)。
注意:除了成员选择符、成员对象选择符、域名解析操作符、条件操作符外,其他的都可以重载。另外,除了赋值操作符外,基类的重载操作符都可以被派生类继承。
所有的操作符要么以类成员函数形式重载,要么其参数列表中至少有一个类的对象。

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

113:const和#define的区别?
区别:编译器处理方式不同(宏在预处理阶段替换,const在编译阶段替换);
类型和安全检查不同(宏没有类型,不做安全检查,const有类型,在编译阶段进行安全检查);

114:c语言和c++有什么区别?
C语言是结构化的编程语言,它是面向过程的,而C++是面向对象的。
封装:将数据和函数等集合在一个单元中(即类)。被封装的类通常称为抽象数据类型。封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
继承:继承主要实现重用代码,节省开发时间。它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向派生类的基类指针,来调用实现派生类中的方法。有编译时多态和运行时多态。

115:strcpy函数的编写?

char *strcpy(char *strDes,const char *strSrc)//返回char* 的目的是为了实现链式表达式{                     //链式表达式的形式如:int l=strlen(strcpy(strA,strB));    assert(strSrc!=NULL);//若目的地址已知,也要断言目的地址不为空    strDes=(char *)malloc(strlen(strSrc)+1);    char *p=strDes;    while(*strSrc!='\0')    {        *p++=*strSrc++;    }    *p='\0';    return strDes;}

116:继承机制中对象之间是如何转换的?
存在继承关系的类型之间的转换规则:
1)从派生类向基类的类型转换只对指针或引用类型有效。
2)基类向派生类不存在隐式类型转换。
3)和任何其他成员一样,派生类向基类的类型转换也可能会由于访问受限而变得不可行。
基类向派生类的显式类型转换参考dynamic_cast。

对于派生类向基类的类型转换的可访问性,有以下三种情况:(假设D继承自B)
1)只有当D共有的继承自B时,用户代码才可以使用派生类向基类的转换,否则,不可以使用。
2)不论D以何种方式继承自B,D的成员函数及友元都可以使用派生类向基类的转换。
3)如果D继承自B的方式是公有的或受保护的,则D的派生类的成员和友元可以使用D向B的类型转换,否则,不能使用

117: 继承机制中引用和指针之间如何转换?
基类——>派生类:用dynamic_cast转换,首先检查基类指针(引用)是否真正指向一个派生类对象,然后再做相应处理,对指针进行dynamic_cast,成功返回派生类对象,失败返回空指针,对引用进行dynamic_cast,成功返回派生类对象,失败抛出一个异常。
派生类——>基类:可以用dynamic_cast或者直接进行类型转换(直接赋值)。

118:虚函数内存分配?
C++的编译器保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到的虚函数表有最高的性能——如果有多层继承或是多重继承的情况下)。
分为以下几种情况:
对于无虚函数覆盖的继承:虚函数按照其声明顺序放在虚函数表中;父类的虚函数在其子类的虚函数的前面。
对于有虚函数覆盖的继承:派生类中起覆盖作用的虚函数放在原基类虚函数的位置;没有被覆盖的虚函数依旧。
对于无虚函数覆盖的多重继承:每个父类都有自己的虚函数表;派生类的虚函数被放在了第一个父类的虚函数表中(按照声明顺序排序)。
对于有虚函数覆盖的多重继承:派生类中起覆盖作用的虚函数放在原基类虚函数的位置;没有被覆盖的虚函数依旧。
这里写图片描述

119:容器适配器有哪些?它们的默认底层适配容器是什么?
stack:默认是deque,vector和list也可以作为底层适配容器。
queue:默认是deque,list也可以作为底层适配容器。因为queue同时支持front和back操作,所以不能用vector作为底层适配容器。
priority_queue:默认是vector,也可以是deque。因为priority_queue要求能对元素随机访问以便能够排序,所以不能用list。

120:const关键字的作用?
const允许你告诉编译器和其他程序员某值应保持不变,只要“某值”确实是不该被改变的,那就该确实说出来。
其使用包括:定义常量,和指针结合使用(4种表示方式),函数中使用const定义形参变量及修饰函数返回值,类中使用(修饰数据成员和成员函数)。
const成员函数:使类接口比较容易被理解,可以得知哪个函数不允许改动对象;使操作const对象成为可能,因为const对象只能调用const成员函数;两个成员函数如果只是常量性质不同,可以被重载。
在参数传递过程中,最好将确实不需改动的参数设定为const引用,因为const引用相比较普通引用,有更大的实参接受权限(形参设定为const引用,则实参可为变量,const变量,字面值常量等)。如果函数具有普通的非const引用形参,则必须给函数传递类型完全一致的非const对象。给非const引用函数传递一个字面值、一个表达式、或者一个需要进行类型转换的对象都是不允许的。

121:几种容器迭代器删除元素时会发生什么?
对于vector和string,指向其删除点之后位置的迭代器、引用和指针都会失效。
对于deque,删除除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。
对于list,指向容器其他位置的迭代器,引用和指针仍有效。
对于插入操作,影响与删除操作一样。

122:vector中的reserve函数和resize函数的区别?
reserve并不改变容器内实际元素数量,它仅影响vector预先分配多大的内存空间,而且只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量,此时一般将容量扩充1倍;
resize只改变容器中元素的数目,而不是容器的容量。

123:delete与 delete []区别
delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。在More Effective C++中有更为详细的解释:“当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存。”delete与new配套,delete []与new []配套。
对于内置数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。delete[]删除一个数组,delete删除一个指针。简单来说,用new分配的内存用delete删除;用new[]分配的内存用delete[]删除。

124:将“引用”作为函数参数有哪些特点?
1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用”*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

125:描述内存分配方式以及它们的区别?
1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

126:

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指向文字常量区地址相同

127:类成员函数的重载、覆盖和隐藏区别?
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

0 0
原创粉丝点击