【牛客网】C/C++牛客网专项刷题(02)

以下为牛客网C/C++专项刷题:

1、虚函数不可以内联,因为虚函数是在运行期的时候确定具体调用的函数,内联是在编译期的时候进行代码展开,两者冲突,所以没有一起使用的做法。

2、C++中构造函数和析构函数可以抛出异常吗?

都不行

都可以

只有构造函数可以

只有析构函数可以

KEY:C

解释:不建议在构造函数中抛出异常;构造函数抛出异常时,析构函数将不会被执行,需要手动的去释放内存。

析构函数不应该抛出异常;当析构函数中会有一些可能发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外;析构函数异常相对要复杂一些,存在一种冲突状态,程序将直接崩溃:异常的被称为“栈展开(stack unwinding)”

【备注】的过程中时,从析构函数抛出异常,C++运行时系统会处于无法决断的境遇,因此C++语言担保,当处于这一点时,会调用 terminate()来杀死进程。因此,当处理另一个异常的过程中时,不要从析构函数抛出异常, 抛出异常时,其子对象将被逆序析构。

3、若运行时从键盘上输入9876543210l,则上面程序在gcc编译器下的输出结果是()

int main(){
    int a;
    float b,c;
    scanf("%2d%3f%4f",&a,&b,&c);
    printf("a=%d,b=%d,c=%f\n",a,b,c);
}

a=98,b=765,c=4321.000000

a=98,b=0,c=0.000000

a=98,b=765.000000,c=4321.000000

a=98,b=765.0,c=4321.0

KEY:B

解释:printf函数执行的时候,会先把这三个数字压入栈里,然后再执行打印。压入栈的时候按照数据本身的长度来,首先把c和b压入,并且每一个都是8个字节(printf下自动转化为double)。然后再压入a是4个字节。然后再执行打印。打印的时候按照用户指定的格式来出栈。首先打印a,a打印正常。然后又打印4个字节长度的b,在栈里面由于b长度是八个字节,并且b目前是64位的表示方式,数据的后面全是0.(float 变double),电脑是小端存储方式,0存储在距离a近的地方。打印b的时候,打印的4个字节都是0。然后再打印c,c用正常的方式打印,会一下子读取8个字节,正好,读出来的八个字节前面四个字节全是0,自己可以算一下,实在太小了,因此为0。

由于栈是向低地址扩展的数据结构,因为这样可以准确把握栈的大小限度。

  • printf的%f说明符既可以输出float型又可以输出double型。根据“默认参数提升”规则(在printf这样的函数的可变参数列表中,不论作用域内有没有原型,都适用这一规则)float型会被提升为double型。因此printf()只会看到双精度数。
  • scanf对于float类型必须用%f,double必须用%lf。对于scanf,情况就完全不同了,它接受指针,这里没有类似的类型提升。(通过指针)向float存储和向double存储大不一样,因此,scanf区别%f和%lf。

在float变成double的时候,其实可以理解是在它的尾数加0,也就是在内存后面加0。注意一下理解,此处在数字后面加0,不是把数字加了很多倍,而是用来提升尾数。

栈底                                          栈顶

高地址                                        低地址

高字节。。。。。。。。。。。低字节

4321     0000      765     0000         98

4字节   4字节    4字节    4字节      4字节

             打印c                 打印b      打印a

附:浮点数(单精度的float和双精度的double)在内存中以二进制的科学计数法表示,表达式为N = 2^E * F;其中E为阶码(采用移位存储),F为尾数。

float和double都由符号位、阶码、尾数三部分组成,float存储时使用4个字节,double存储时使用8个字节。各部分占用位宽如下所示:

             符号位     阶码      尾数     长度

float              1           8         23      32

double          1         11        52       64

总结:

  • 如果是double型,读入时一定要用scanf("%lf",&xx);否则变量值会为0;
  • 如果是float型,一定要用%f,否则变量值会变成很大的未知数字;
  • 输出的时候,double和float都用printf("%f")。

4、选出像printf一样支持变长参数的函数调用约定()。

cdecl

stdcall

pascal

fastcall

KEY:A

解释:什么是函数调用约定?

当一个函数被调用时,函数的参数会被传递给被调用的函数,同时函数的返回值会被返回给调用函数。函数的调用约定就是用来描述参数(返回值)是怎么传递并且由谁来平衡堆栈的。也就是说:函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数还是被调用函数负责清除栈中的参数,还原堆栈。

常见的函数调用约定有:__stdcall,__cdecl(默认),__fastcall,__thiscall,__nakedcall,__pascal。

它们按参数的传递顺序对这些约定可划分为:

  • 从右到左依次入栈:__stdcall,__cdecl,__thiscall;
  • 从左到右依次入栈:__pascal,__fastcall。

支持参数变长的函数调用约定:__cdecl,带有变长参数的函数必须是cdecl调用约定,由函数的调用者来清除栈,参数入栈的顺序是从右到左。由于每次函数调用都要由编译器产生清除(还原)堆栈的代码,所以使用__cdecl方式编译的程序比使用__stdcall方式编译的程序要大很多,但是__cdecl调用方式是由函数调用者负责清除栈中的函数参数,所以这种方式支持可变参数,比如printf和windows的API wsprintf就是__cdecl调用方式。

5、在一台主流配置的PC机上,调用f(35)所需的时间大概是_______。

int f(int x) {
    int s=0;
    while(x-- >0)   s+=f(x);
    return max(s,1);
}

几毫秒

几秒

几分钟

几小时

KEY:C

解释:先分析一下函数的复杂度: 

f(n) = f(n-1)+f(n-2)+....+f(2)+f(1) +f(0) 

      = 2( f(n-2)+ f(n-3)+....+f(2)+f(1) +f(0))

      =4( f(n-3) +f(n-4)+....+f(2)+f(1) +f(0))

      ...

      = 2^(n-1)*f(0)

复杂度为O(2^(n-1)),n = 35时,计算量为2^34 。

主流PC机的每秒钟计算量约为10^7~10^8次(几百万次),因此计算时间大约在几分钟。

6、派生类的构造函数的初始化列表中,不能包含(       )。

基类的构造函数

派生类中子对象的初始化

基类的子对象初始化

派生类中一般数据成员的初始化

KEY:C

解析:首先需要理解一下派生类的构造函数的初始化列表是什么?

派生类不能在成员初始化列表中直接初始化基类的成员。

构造函数是不可继承的。因此,派生类的构造函数必须通过调用基类的构造函数初始化基类成员,不能够在派生类初始化列表直接初始化基类的成员,“越级初始化”。派生类的构造函数的一般格式为:

派生类名(形参表) : 基类名(形参表) , 派生类的对象成员(形参表) {
    ...
}

注意事项:

  • 在创建派生类对象时,先调用基类的构造函数,然后调用派生类的构造函数;撤销对象时,析构函数被调用的顺序则相反;
  • 若派生类中包含对象成员,则派生类的构造函数初始化成员列表中既要列出基类的构造函数也要列出对象的构造函数。派生类定义对象时,先调用基类的构造函数,再调用对象的构造函数,最后调用派生类的构造函数。
  • 综合以上可知:基类的子对象初始化必须是在基类的初始化列表中进行初始化才行。

7、以下说法正确的是:

void swap_int(int *a,int *b){
  *a=*a+*b;
  *b=*a-*b;
  *a=*a-*b;
}

结果不正确,因为会溢出,用位与的方式就没问题

结果正确,即使会溢出

结果正确,不会溢出

其他选项都不对

KEY:B

解答:关于这个的证明非常繁琐,这里就不介绍了,只需记住结论就可以了。

但是这里要强调一点:这道题其实也是有问题的,首先得保证输入的两个参数不是指向同一个地址,否则,无论两个数如何,交换后的结果都将变为零,题目中没说明这一点。

8、下面有关空指针和未初始化指针,说法错误的是?

对0x0这个地址取值是非法的

空指针可以确保不指向任何对象或函数; 而未初始化指针则可能指向任何地方。

空指针与任何对象或函数的指针值都不相等

malloc在其内存分配失败时返回的是一个未初始化的指针

KEY:D

解答:空指针与野指针的区别,空指针也就是通常指向为NULL的指针;野指针就是指向一块未知的内存区域(可以是通过malloc或new申请空间后,释放后没有将指针置为空),也有可能定义了一个指针没有初始化,由于内存空间中的值在未赋值之前是随机数,所以也有可能诞生野指针。

但是对于本题的A选项,要注意一下,对0x0这个地址取值是非法的!

9、以下程序段的执行结果是()。

double  x;
x=218.82631;
printf("%-6.2e\n",x);

KEY:2.19e+02

解答:“-6.2e”的意思:“6”表示输出的宽度为6个字符宽,如果整数部分超出,则按实际倍数输出。“.2”表示输出2位小数(四舍五入),不足时后面添0;对字符串,表示截取2个字符。“e”表示用指数形式输出。“-”表示在数据不足指定位数时,采用左对齐方式,即数字(字符)往左靠。默认为右对齐。

10、char p1[] = “Tencent”, void*p2 = malloc((10)在32位机器上sizeof(p1)和sizeof(p2)对应的值是?

KEY:8 4

解答:当参数分别如下时,sizeof返回的值表示的含义如下: 

  • 数组——编译时分配的数组空间大小; 
  • 指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,一般为4); 
  • 类型——该类型所占的空间大小; 
  • 对象——对象的实际占用空间大小; 
  • 函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。

也就是说:当数组名作为sizeof、decltype、&运算符的运算对象时不会向指针转换,用此方式给字符数组赋值编译器会自动在字符串结尾添加‘\0'。

11、假定CSomething是一个类,执行下面这些语句之后,内存里创建了____个CSomething对象。

CSomething a();
CSomething b(2);
CSomething c[3];
CSomething &ra = b;
CSomething d=b;
CSomething *pA = c;
CSomething *p = new CSomething(4);

KEY:6

解答:这里的各个定义如下:

CSomething a();// 没有创建对象,这里不是使用默认构造函数,而是定义了一个函数,在C++ Primer393页中有说明。

CSomething b(2);//使用一个参数的构造函数,创建了一个对象。

CSomething c[3];//使用无参构造函数,创建了3个对象。

CSomething &ra=b;//ra引用b,没有创建新对象。

CSomething d=b;//使用拷贝构造函数,创建了一个新的对象d。

CSomething *pA = c;//创建指针,指向对象c,没有构造新对象。

CSomething *p = new CSomething(4);//新建一个对象。

12、C++语言中,下面描述中,正确的是()

一个基类的声明中有纯虚函数,该基类派生类一定不再是抽象类

函数类型不能作为重载函数的调用的依据

静态数据成员不是所有对象所公有的

内联函数在运行时是将该函数的目标代码插入每个调用该函数的地方

KEY:B

解答:A选项,如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。所以A错;C选项,静态数据成员是所有对象所共有的,所以C错;D选项,应该是编译时,而不是运行时。通常重载函数调用的依据是函数名、参数类型、参数个数。

13、下面模板声明中,哪些是非法的()

template<class Type>class C1{};

template<class T, U, class V>class C2{};

template<class C1, typename C2>class C3{};

template<typename myT,  class myT>class C4{};

KEY:BD

解答:B选项的U参数没有指定类型, D选项的 2个形参名同名。函数模板的格式:

template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)
{
    ...
}

类模板的格式为:

template<class   形参名 ,class 形参名,…>   class 类名
{
    ...
};

14、从本质上看,引用是被应用变量的(        )

拷贝

别名

复制

克隆

KEY:B

解答:引用的本质为变量的别名。这就不多介绍了。主要看一下指针和引用的区别:

相同点:

它们都是地址的概念,其中指针指向一块内存,它的内容是所指内存的地址;而引用是某块内存的别名。

不同点:

  • 指针是一个实体,它在栈中有自己使用的空间,但是系统不为引用类型变量分配内存空间,只是使得引用类型变量与其相关联的变量使用同一个内存空间;
  • 引用必须初始化,指针不必;
  • 指针使用时必须加*,引用不用;
  • 引用只能在定义时被初始化一次,之后不可变;指针可以改变所指的对象;
  • 不存在指向空值的引用,但是存在指向空值的指针,即引用不能为空,指针可以为空;
  • 可以有const指针,但是没有const引用;
  • “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
  • 指针和引用的自增(++)运算意义不一样;
  • 指针可以有多级,但是引用只能是一级,例如int **p是合法的,而 int &&a是不合法的。

15、下面选项中关于编译预处理的叙述正确的是()

预处理命令行必须使用分号结尾

凡是以#号开头的行,都被称为编译预处理命令行

预处理命令行不能出现在程序的最后一行

预处理命令行的作用域是到最近的函数结束处

KEY:B

解答:对于C,反例就是#ifndef...#define...#endif。

16、下面有关虚函数和非虚函数的区别说法错误的是?

子类的指针访问虚函数访问的是子类的方法

子类的指针访问非虚函数访问的是子类的方法

父类的指针访问虚函数访问的是父类的方法

父类的指针访问非虚函数访问的是父类的方法

KEY:C

解答:派生类的指针可以赋给基类指针,而通过基类指针调用基类和派生类中的同名虚函数时:

  • 若该指针指向一个基类的对象,那么被调用是基类的虚函数;
  • 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。

这种机制就叫做多态。

17、下面有关继承、多态、组合的描述,说法错误的是?

封装,把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展

隐藏是指派生类中的函数把基类中相同名字的函数屏蔽掉了

覆盖是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同

KEY:D

解答:重载、覆盖与隐藏 的区别:

  • 重载(overload):函数名相同 、函数参数不同、 必须位于同一个域(类)中;
  • 覆盖(override):函数名相同 、函数参数相同、 分别位于派生类和基类中、virtual(虚函数);
  • 隐藏(hide):函数名相同、 函数参数相同、 分别位于派生类和基类中、非virtual(即跟覆盖的区别是基类中函数是否为虚函数);函数名相同、 函数参数不同、 分别位于派生类和基类中(即与重载的区别是两个函数是否在同一个域(类)中)。

覆盖达到的效果:

  • 在子类中重写了父类的虚函数,那么子类对象调用该重写函数,调用到的是子类内部重写的虚函数,而并不是从父类继承下来的虚函数;
  • 在子类中重写了父类的虚函数,如果用一个父类的指针(或引用)指向(或引用)子类对象,那么这个父类的指针或用引用调用该重写的虚函数,调用的是子类的虚函数;相反,如果用一个父类的指针(或引用)指向(或引用)父类的对象,那么这个父类的指针或用引用调用该重写的虚函数,调用的是父类的虚函数。

隐藏,即:派生类中函数隐藏(屏蔽)了基类中的同名函数。

关于隐藏的理解,在调用一个类的成员函数时,编译器会沿着类的继承链逐级向上查找函数的定义,如果找到了则停止查找;所以如果一个派生类和一个基类都有一个同名函数(不论函数参数是否相同),而编译器最终选择了在派生类中的函数,那么就说这个派生类的成员函数“隐藏”了基类的同名函数,即它阻止了编译器继续向上查找函数的定义。

18、面向对象的三个基本元素是什么?

封装

继承

重载

多态

KEY:ABD

19、数组说明 :int array[][4]是否正确?

正确

错误

KEY:B

解答:如果对全部元素都赋初值,则定义数组时第一维可以忽略,但是第二维必须存在。同时:在定义的时候也可以对部分元素赋初值而忽略第一维的长度,但应该分行赋初值。

可见:可以忽略第一维的长度,但是是有条件的:要么你把全部元素都写出来,或者你分行写出元素(可以不全),但是不可以不赋值。

20、以下程序的输出结果为()。

main()
{
    int i=010,j=10;
    printf("%d,%d\n",++i,j--);
}

KEY:9,10

解答:0开头是八进制,八进制的10是十进制的8。

21、以下程序的输出结果为()。

using namespace std;

struct Item {
    char c;
    Item *next;
};
Item *Routine1(Item *x) {
    Item *prev = NULL, *curr = x;
    while (curr) {
        Item *next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}
void Routine2(Item *x) {
    Item *curr = x;
    while (curr) {
         cout << curr->c << ” “;
         curr = curr->next;
    }
}

int main(void) {
    Item *x, d = {‘d’, NULL}, c = {‘c’, &d}, b = {‘b’, &c}, a = {‘a’, &b};
    x = Routine1(&a);
    Routine2(x);
    return 0;
}

KEY:dcba

解答:Item *Routine1(Item *x) 函数实现了把x为头结点的链表倒置,x 指向 NULL,返回 新的头结点 void Routine2(Item *x) 输出每个结点的值 。

22、下面四个选项中,均是不合法的转义字符的选项是()。

’\’”  ‘\\’  ‘\xf\

’\1011’  ‘\’  ‘\ab’

’\011’  ‘\f’  ‘\}’

’\abc’  ‘\101’  ‘xlf’

KEY:B

解答:A中均为合法的转义字符;B中’\1011'的\后面多于3位八进制数是非法的,’\’不能标识\字符,是非法的,’\ab’的\后面漏掉了x是非法的;C中’\011’是合法的;D中’\101’是合法的。

转义字符分三种,一般转义字符,八进制转移字符和十六进制转移字符:

一般转义字符,如‘\b’,由两个字符表示,其实代表一个字符,这个代表退格字符;

八进制转义字符,如‘\007’,三位数字是八进制的,ASCII码为7的表示响铃,此处的0开头可以省略,写成'\7'也是正确的;

十六进制,如'\x09',同样后面数字是所表示意思的AscII码的十六进制表示,注意一定要有x,大小写都行。并且,后面的取值范围必须在0-255之间。

23、有如下一段代码(unit16_t为2字节无符号整数,unit8_t位1字节无符号整数);

union X
{
    unint16_t a;
    struct Z
    {
        unint8_t m;
        unint8_t n;
    }z;
};
  
union X x;
x = 0x1234;

请问x.z.n在大字节序和小字节序机器上的值分别为多少()?

KEY:0x34,0x12

解答:

在C Programming Language 一书中对于联合体是这么描述的:

  • 联合体是一个结构;
  • 它的所有成员相对于基地址的偏移量都为0;
  • 此结构空间要大到足够容纳最"宽"的成员;
  • 其对齐方式要适合其中所有的成员。

文章参考:【技巧】用union验证机器为大端还是小端

24、若有以下程序:

#include <stdio.h>

main( )
{ 
    FILE * fp;
    int i,a[6]={1,2,3,4,5,6},k;
    fp = fopen("data.dat","w+");
    for (i=0;i<6;i++)
    { 
        fseek(fp,0L,0); 
        fprintf(fp,"%d\n",a[i]); 
        rewind (fp);
        fscanf(fp,"%d",&k);
    }
       fclose(fp);
       printf("%d\n",k);
}

则程序的输出结果是?

KEY:6

解答:如果来看解析的话,估计这道题难点在于fseek和rewind两个函数不太了解。

  • fseek(文件,偏移量,类别),其中类别为文件开头0,文件当前位置1,以及文末2。所以,fseek(fp,0L,0)就是把文件指针fp移到里开头0字节的地方,即开始位置;
  • rewind相当于fseek(fp,0L,0),定位到文件开始位置(功能是将文件内部的指针重新指向一个流的开头)。

最后文件只有一个数字6。

代码循环内流程如下:移到开始位置->写一个数字->移到开始位置->读一个数字(恰恰是刚才写的那个) 循环。

25、若有以下程序:

void func()
{
   char b[2]={0};
   strcpy(b,"aaaa");
}

以下说法那个正确()。

Debug版崩溃,Release版正常

Debug版正常,Release版崩溃

Debug版崩溃,Release版崩溃

Debug版正常,Release版正常

KEY:A

解答:assert 含义是断言,它是标准C++的cassert头文件中定义的一个宏,用来判断一个条件表达式的值是否为ture,如果不为true, 程序会终止,并且报告出错误,这样就很容易将错误定位 。

通常我们开发的程序有2种模式:Debug模式和Release模式

  • 在Debug模式下,编译器会记录很多调试信息,也可以加入很多测试代码,比如加入断言assert,方便我们程序员测试,以及出现bug时的分析解决;
  • Release模式下,就没有上述那些调试信息,而且编译器也会自动优化一些代码,这样生成的程序性能是最优的,但是如果出现问题,就不方便分析测试了。

而这个程序会出现越界的情况。

26、math.h的abs()返回值()

不可能是负数

不可能是正数

都有可能

不可能是0

KEY:C

解答:因为负数的范围比正数大一个,比如8位的二进制,可以表示范围为-128~127。所以abs(-128)可能并不能表示为128,所以只能返回原值。

c中的函数申明为 int abs(int num);也就造成了:

  • num为0或正数时,函数返回num值;
  • 当num为负数且不是最小的负数时,函数返回num的对应绝对值数,即将内存中该二进制位的符号位取反,并把后面数值位取反加一;
  • 当num为最小的负数时(即0x80000000),由于正数里int类型32位表示不了这个数的绝对值,所以依然返回该负数。

为什么负数的范围比正数大一个?

这里有一个0值的差别。以最简单的单字节char型为例。占8位,最高位为符号位。这样0值就有了:0000 0000 (正零)、1000 0000 (负零)两种。

从数学角度上,是没区别的,可是用两种形式表示一个数,明显是浪费了。于是计算机存储就约定,当符号位为0,即正零时才是0。符号位为1时,让它去表示另外一个数好了。

那这个数是什么呢,按照补码的方式求一下,

1000 0000首先符号位为1,是个负数,取反,0111 1111加一,1000 0000又回来了... 但这时代表的就是值了,注意这里的1已经不是符号位了。计算其值就是128。于是 1000 0000就表示成了-128。

27、下面的说法那个正确

#define NUMA 10000000
#define NUMB 1000
int a[NUMA], b[NUMB];
  
void pa()
{
    int i, j;
    for(i = 0; i < NUMB; ++i)
        for(j = 0; j < NUMA; ++j)
            ++a[j];
}
void pb()
{
    int i, j;
    for(i = 0; i < NUMA; ++i)
        for(j = 0; j < NUMB; ++j)
            ++b[j];
}

pa 和 pb 运行的一样快

pa 比 pb 快

pb 比 pa 快

无法判断

KEY:C

解答:测试时pb比pa快,数组a比数组b大很多,可能跨更多的页,缺页率高或者缓存命中更低,所以pb快。

28、什么函数不能声明为虚函数?

静态成员函数

内联函数

构造函数

析构函数

KEY:ABC

解答:常见的不能声明为虚函数的有:普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数。原因:

  • 普通函数(不能被覆盖) 
  • 友元函数(C++不支持友元函数继承)
  • 内联函数(编译期间展开,虚函数是在运行期间绑定)
  • 构造函数(没有对象不能使用构造函数,先有构造函数后有虚函数,虚函数是对对象的动作) 
  • 静态成员函数(没有this指针,只有一份大家共享) 

29、下面说法错误的是()

在组合时,为了保证成员对象被正确清除,在组合类的析构函数中需要显式调用其成员对象的析构函数

在类的继承层次中,可以自动进行向上和向下类型转换.而且都是安全的

构造函数可以重载,析构函数不能重载

C++的派生类如果要覆盖一个继承到的成员函数,在基类中需要将该函数声明为virtual

KEY:AB

解答:A:成员类对象在离开作用域的时候会调用其自身的析构函数,并不需要我们手动delete,就将其看做一个基本的局部对象即可。除非在构造的时候用了动态类存分配,这又是另外一回事了。

B:明显错误,参考《Effective C++》第39条,不要向下转型。

C:正确,构造函数可以根据参数的不同实现函数重载,而因为析构函数没有参数,对于一个类来说也是唯一的,所以是不能重载的;

D:正确,也是《Effective C++》第37条,如果子类要重写父类方法,需要将父类该方法声明为virtual,实现RTTI。当然你可以不这样干,结果就是静态绑定。补充一点,重写就叫覆盖。如果没有virtual就是隐藏。

30、下列关于虚函数的说法正确的是()

在构造函数中调用类自己的虚函数,虚函数的动态绑定机制还会生效

在析构函数中调用类自己的虚函数,虚函数的动态绑定机制还会生效

静态函数不可以是虚函数

虚函数可以声明为inline

KEY:C

解答:由于类的构造次序是由基类到派生类,所以在构造函数中调用虚函数,这个虚函数不会呈现出多态; 相反,类的析构是从派生类到基类,当调用继承层次中某一层次的类的析构函数时往往意味着其派生类部分已经析构掉,所以也不会呈现出多态。

31、通用多态是指()。

强制多态和包含多态

重载多态和强制多态

参数多态和重载多态

包含多态和参数多态

KEY:D

解答:在c++语言中,多态性可以通过强制多态、重载多态、参数多态、包含多态4种形式来实现。

参数多态和包含多态统称为通用多态,用来系统地刻画语义上相关的一组类型;

重载多态和强制多态统称为特定多态,用来刻画语义上无关联的类型间的关系。

例子:

//1.参数多态

//包括函数模板和类模板

 
//2.包含多态  virtual
 
class A{
 
    virtual void foo() {        printf("A virtual void foo()");        }
 
};
 
class B : public A {
 
    void foo() {        printf("B void foo()");        }
 
};
 
void test() {
 
    A *a = new B();
 
    a->foo(); // B void foo()
 
}
 
//3.重载多态
 
//重载多态是指函数名相同,但函数的参数个数或者类型不同的函数构成多态
 
void foo(int);
 
void foo(int, int);

 
//4.强制多态
 
//强制类型转换

32、下列关于运算符重载的叙述中,正确的是(        )

通过运算符重载,可以定义新的运算符

有的运算符只能作为成员函数重载

若重载运算符+,则相应的运算符函数名是+

重载二元运算符时,必须声明两个形参

KEY:B

解答:不能重载的运算符有:::、.、.*、?:、sizeof()。

只能使用成员函数重载的运算符有:=、()、[]、->、new、delete;(类型转换函数也是)

单目运算符最好重载为成员函数;

对于复合的赋值运算符如+=、-=、*=、/=、&=、!=、~=、%=、>>=、<<=建议重载为成员函数;

对于其它运算符,建议重载为友元函数。

类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。 C++提供4个类型转换函数:reinterpret_cast(在编译期间实现转换)、const_cast(在编译期间实现转换)、stactic_cast(在编译期间实现转换)、dynamic_cast(在运行期间实现转换,并可以返回转换成功与否的标志)。

33、给出以下定义, 则对两个数组的strlen正确的叙述为:

char x[]="abcdefg";
char y[]={'a','b','c','d','e','f','g'};

数组X和数组Y等价

数组X和数组Y长度相同

数组X的长度大于等于数组Y的长度

数组X的长度小于等于数组Y的长度

KEY:D

解答:定义一个数组,如何获取数组的长度有时是我们必须所用到的。

我们知道:x数组会在末尾添加字符串结束符'\0',但y数组不会。所以x的数组长度比y的数组长度是要大1的。但是,本题说利用strlen来比较结果!

C语言是没有直接求数组长度的方法的,只能间接使用sizeof和strlen。看一下sizeof与strlen()比较:

  • strlen计算字符数组的字符数,以"\0"为结束判断,不计算为'\0'的数组元素;
  • sizeof计算数据(包括数组、变量、类型、结构体等)所占内存空间,用字节数表示(当然在计算字符数组时包括结束符)。

需要注意的是:本题y并不确定'\0'的位置究竟在哪里,所以长度会长一点。

关于'\0'的问题。对字符数组,有以下几种定义方法:

char str ="abcdefg";,或给字符串加上大括号:char str[]={"abcdefg"};:这种方法定义时,系统会自动在字符串的末尾加上字符串结束符,即 ‘\0’;

char str[10]={'a','b','c','d','e','f','g'};:系统会自动从未初始化的元素开始,将之后的元素赋为'\0',如上面的数组str中的元素实际上是:'a','b','c','d','e','f','g','\0','\0','\0';

char str[]={'a','b','c','d','e','f','g'};:系统不会自动在字符串的末尾加上字符串结束符。

最后提一下,sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用。

34、下面重载乘法运算符的函数原型声明中正确的是:

MyClass operator *(double ,MyClass);

MyClass operator *(MyClass ,MyClass);

MyClass operator *(double ,double);

MyClass operator *(MyClass ,double);

KEY:ABD

解答:C++中规定,重载运算符必须和用户定义的自定义类型的对象一起使用。

35、以下关于运算符优先顺序的描述中正确的是()。

关系运算符<算术运算符<赋值运算符<逻辑运算符

逻辑运算符<关系运算符<算术运算符<赋值运算符

赋值运算符<逻辑运算符<关系运算符<算术运算符

算术运算符<关系运算符<赋值运算符<逻辑运算符

KEY:C

解答:括号,算术,关系,逻辑,赋值(算术关罗父)。

36、输出多少?

unsigned short A = 10; 
printf("~A = %u\n", ~A); 

char c = 128; 
printf("c=%d\n", c);

KEY:4294967285   -128

解答:第一个没什么问题,主要来看第二个:

短数据类型扩展为长数据类型:

要扩展的数据类型为有符号类型,用短数据的符号位填充长数据多出来的高字节 ,128(10000000)扩展为int(方便转换为十六进制)即(符号位是1)11111111 11111111 11111111 10000000(0xffffff80);

要扩展的数据类型为无符号的 ,用0来填充长数据类型的高字节,此时128在内存的二进制存储(10000000)扩展为unsigned int即00000000 00000000 00000000 10000000(0x80)。

本题11111111 11111111 11111111 10000000为补码,转换成源码10000000 00000000 00000000 01111111(-128)。

37、return 后面括号里的表达式的值即是此函数的值。请问这句话的说法是正确的吗?

KEY:错误

解答:函数返回时会根据定义的返回类型对返回值进行强制类型转换。

38、在 c++ 语言中,对函数参数默认值描述正确的是()

函数参数的默认值只能设定一个

一个函数的参数若有多个,则参数默认值的设定可以不连续

函数参数必须设定默认值

在设定了参数的默认值后,该参数后面定义的所有参数都必须设定默认值

KEY:D

解答:从编译器来看,如果一个有默认值的参数后面是一个没有默认值的参数,那么调用时编译器不好判断实参和形参的对应关系。更具体地说,在定义函数的时候,如果具有缺省值的参数有多个,那么,在函数定义的时候,缺省参数必须位于参数表中的最右边。这是因为:在C++中处理函数调用时,参数是自右向左依次入栈的。比如:

int fun(int a, int b=1, int c=1);            //正确
int fun(int a=1, int b=1, int c);            //错误
int fun(int a=1, int b, int c=1);            //错误

这里也要注意一下函数调用约定:

  • __cdecl:是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后也是由调用者负责清除栈的内容 (可变参数);
  • __stdcall:是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后由被调用者负责清除栈的内容。

39、下列程序段执行后,输出d的值为()

void main()
{
   int a=1,b=0,c=-1,d=0;
   d=++a||++b&&++c;
   cout<<d<<endl;
   return;
}

KEY:1

解答:d=++a||++b&&++c;,首先&&的优先级是高于||优先级的,也就可以表达成d=++a||(++b&&++c);,但是由于||的短路原则,右边时不计算的。也就表示,最终结果d=1,a=2,b=0,c=-1。

40、#include <filename.h>  和 #include“filename.h” 有什么区别?

没有区别

前者用来包含开发环境提供的库头文件,后者用来包含自己编写的头文件

前者用来包含自己编写的头文件,后者用来包含开发环境提供的库头文件

KEY:B

解答:#include<filename.h>系统检索头文件时 会先从系统文件里开始找 ,再找其他地方。用于系统文件;

#include"filename.h"系统检索头文件时先从程序所处目录开始查找。用于自定义文件较快。

41、下面选项中关于 " 文件指针 " 概念的叙述正确的是()

文件指针是程序中用FILE定义的指针变量

文件指针就是文件位置指针,表示当前读写数据的位置

文件指针指向文件在计算机中的存储位置

把文件指针传给fscanf函数,就可以向文本文件中写入任意的字符

KEY:A

解答:文件指针是程序中用FILE定义的指针变量。文件指针指向的是一块内存区域,这块区域存储着打开的文件的相关信息,包括文件读取指针当前位置、文件读取缓冲区大小等信息,并不是指向文件的。fscanf是从文件中格式化读取,fprintf才是向文件中格式化写入。

42、下面这段程序的输出()?

int a=5, b=-5;
printf(“%d,%d”, a%(-4), b%(-4));

1,-1

1,1

-1,1

-1,-1

KEY:A

解答:负数求余主要看的是被除数,与除数无关。如果被除数是负数,那么其结果一定为负;如果被除数是正数,那么其结果一定为正。也就是说:如果被除数和除数都为负,则结果还是为负;如果被除数为正,除数为负,结果为正。

m%(-n) 等于m%n、 (-m)%n 等于-(m%n)

和求余数不同的是:除法的结果与被除数和除数的正负性都有关。(-m)/n 和 m/(-n) 等于-(m/n)。

43、1<<3+2的值是()。

KEY:32

解答:运算符+的优先级高于左移<<。

这是一道阿里的题,直接让我这种觉得优先级没那么重要的人吃瘪了。还是总结一下优先级:

C++运算符及优先级
优先级 运算符 结合性
1 ()、.、->、[]、::、&(引用)
2 *(指针)、&(取址)、new、delete、!、~、++、--
3 *(乘)、/、%
4 +、-
5 <<、>>
6 <、<=、>、>=
7 ==、!=
8 &(位运算符)
9 ^(位运算符)
10 |
11 &&
12 ||
13 ?:
14 =
15 ,(逗号运算符)

44、定义网络传输数据包为 :

class packet{
     int size;
     void data[0];
}

其中data的作用是?

维护数据包空间的连续性

数据分割位

指向独立的数据空间

无任何作用

KEY:A

解答:data[0]表示柔性数组,是为了减少内存碎片保持属于连续性,连续的地址并非是独立的地址空间

该数组的内存地址就和它后面的元素地址相同,意味着无需初始化,数组名就是后面元素的地址,直接就能当指针使用。例如,制作动态buffer,可以这样分配空间malloc(sizeof(structXXX) + buff_len); 直接就把buffer的结构体和缓冲区一块分配了。这样使用释放一次即可,如果使用指针,则需要释放两次。

考虑到可移植性的原因, 可以写成data[1]或data[],因为有些编译器不支持0数组。

参考文章:C/C++ 中的0长数组(柔性数组)

45、阅读以下代码:

class parent  
{  
    public:  
    virtual void output();  
}; 
 
void parent::output()  
{  
    printf("parent!");  
}  
       
class son : public parent  
{  
    public:  
    virtual void output();  
};  

void son::output()  
{  
    printf("son!");  


son s; 
memset(&s , 0 , sizeof(s)); 
parent& p = s; 
p.output(); 

执行结果是()。

parent!

son!

son!parent!

没有输出结果,程序运行出错

KEY:D

解答:memset()函数的作用为,将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值。而本题,因为为了实现多态机制,C++对有虚函数的对象会包含一个指向虚函数表(V-Table)的指针,当使用memset时,会把该虚函数表的指针也初始化为0。也就是说,虚函数链表地址也清空了。

总结一下memset()函数:

memset是以字节为单位,初始化内存块。当初始化一个字节单位的数组时,可以用memset把每个数组单元初始化成任何你想要的值。

int data[10];
memset(data, 0, sizeof(data));    // right
memset(data, -1, sizeof(data));    // right
memset(data, 1, sizeof(data));    // wrong, data[x] would be 0x0101 instead of 1

为什么赋值1就错的呢?

因为一个int类型的变量占4个字节,而memset是将每一个字节初始化成1,所以一个int类型的变量被初始化成了0x01010101。而这个数是16843009。

当结构体类型中包含指针、类中存在虚函数时,在使用memset初始化时需要小心。前者是memset只会初始化指针,并不会初始化指针指向的内容,如果初始化为0的话,就会丢失指针指向的内容造成内存泄漏;后者是可能会清空虚函数表的指针。

参考文章:老生常谈,正确使用memset

46、C++语言函数可以嵌套声明,嵌套调用,但不能进行嵌套定义。

47、有以下程序:

#include <stdio.h>
#include <stdlib.h>

void fun ( int *pl,int *p2,int *s )
{
    s = (int*) malloc(sizeof(int));
    *s = *pl + *(p2++ );
}
main( )
{
    int a [2] = {l,2},b [2] = {10,20},*s = a;
    fun (a,b,s); 
    printf ( "%d \n", *s);
}

程序运行后的输出结果是?

KEY:1

解答:这道题坑就坑在s = (int*) malloc(sizeof(int));这一句上。在fun()函数内部重新申请了一块新的内存空间,然后修改了那块内存空间的值(11)。但是fun()函数运行结束之后,回到主程序,内部无法修改外部,s指针指向的区域还会变成a的位置,也就是1。如果将s = (int*) malloc(sizeof(int));去掉,结果就是11。

48、下面不是面向对象的基本原则的是?

单一职责原则(Single-Resposibility Principle)

开放封闭原则(Open-Closed principle)

抽象类原则(Abstract-Class principle)

依赖倒置原则(Dependecy-Inversion Principle)

接口隔离原则(Interface-Segregation Principle)

KEY:C

解答:面向对象的基本原则:

  • 单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因;
  • 开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的;
  • 里氏替换原则(Liskov-Substituion Principle):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础;
  • 依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象;
  • 接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口。

面向对象的三个基本特征是:封装、继承、多态。

49、下列关于对象数组的描述中,(     )是错误的。

对象数组的下标是从 0 开始的

对象数组的数组名是一个常量指针

对象数组的每个元素是同一个类的对象

对象数组只能赋初值,而不能被赋值。

KEY:B

解答:数组名永远不可能是指针,这是两个类型,只不过函数传递时数组名会退化成指针。数组只能初始化赋初值,可以对数组中的成员赋值,但不能对数组赋值。

50、下面的语句是正确的定义吗?

int *p = null;

KEY:错误。

解答:null是java中的写法,C/C++中的空是NULL。

猜你喜欢

转载自blog.csdn.net/qq_38410730/article/details/80776277