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

版权声明:本文为博主原创文章,允许转载,但希望标注转载来源。 https://blog.csdn.net/qq_38410730/article/details/81284256

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

1、有以下程序:

#include <stdio.h>
#include <string.h>

void fun( char *s )
{
    char a[10];
    strcpy ( a, "STRING" );
    s = a ;
}

main( )
{
    char *p= "PROGRAM" ;
    fun( p );
    printf ( "%s\n ", p) ;
}

程序运行后的输出结果是(此处□代表空格)?

STRING

STRING□□□□

STRING□□□

PROGRAM

KEY:D

解答:改变非指针变量,需要指针,来传递其地址,改变一级指针需要二级指针来接受其地址。

2、以下代码的执行结果是()。

int main(){
   int i=-2147483648;
   return printf("%d,%d,%d,%d",~i,-i,1-i,-1-i);
}

0,2147483648,2147483649,2147483647

0,-2147483648,-2147483647,2147483647

2147483647,2147483648,2147483649,2147483647

2147483647,-2147483648,-2147483647,2147483647

KEY:D

解答:先补充一下:负数的反码: 符号位不变,其余各位按位取反;负数的补码:在其反码的基础上+1(符号位也参与计算)。

首先,-2147483648这个数为32位int所能表示的最小负整数,而如果原码为 1000 0000  0000  0000  0000  0000  0000  0000(表示-0) ,其反码应为数值位取反,符号位不变,即1111  1111  1111  1111  1111  1111  1111  1111;补码为反码+1,即为0000 0000  0000  0000  0000  0000  0000  0000 (最高位溢出,舍去)。

而+0 的原码、反码、补码均为 0 000 0000  0000  0000  0000  0000  0000  0000,

也就是说,如果用 1000 0000  0000  0000  0000  0000  0000  0000作为 -2147483648的原码,则会导致 -2147483648和0的补码表示一样,因此,计算机中规定用 1000 0000  0000  0000  0000  0000  0000  0000来作为 -2147483648的补码,以确保-2147483648~2147483647都有唯一的补码表示;而 -2147483648的原码,对不起,写不出来,这个数只有补码。原码只能表示 -2147483647~ 2147483647之间的数。

总结以上内容:正数的原码、反码、补码形式一致,负数的反码为原码的数值位取反,补码为反码+1也即是原码的数值位取反再+1,计算机中以补码表示数据和运算,而32位最小负整数的补码为 1000 0000  0000  0000  0000  0000  0000  0000。

然后回到本道题目的解答:

首先,求 ~i , i的补码为1000 0000  0000  0000  0000  0000  0000  0000,取反0111 1111 1111 1111 1111 1111 1111 1111,此为补码,符号位为0,表示正数,正数原码补码一致,因而该数即表示2^31-1,即2147483647 。(取反操作连同符号位一起取反)

然后,求 -i ,要对一个数值执行单目运算符 -  表示的是对该数取反然后再+1,也即是我们常说的求补运算,注意这里取反+1与原码求补码的区别!也就是求补运算与求补码是不一样的!

例子:(4位有符号整数)x=-4,原码为:1100;(补码)-x=~x+1,也即是 0011+0001=0100(4);而1100再求补码应是先数值位取反,即1011,然后+1,变成1100!注意这两者(求补与求补码)之间的区别。

本质上其实就是:在求补运算的时候,是按位取反再加1(取反也要包括符号位);求补码的时候,是数据位取反再加1(取反不包括符号位)。

题目中,i的补码为 1000 0000  0000  0000  0000  0000  0000  0000,取反+1,仍为 1000 0000  0000  0000  0000  0000  0000  0000,即    -2147483648。

求 1-i,我们已经求出-i的补码为1000 0000  0000  0000  0000  0000  0000  0000 ,加上1的补码即为 1000 0000  0000  0000  0000  0000  0000  0001,该补码表示的原码为1 111 1111  1111 1111 1111 1111 1111 1111,即为- 2147483647。

最后求-1-i  -1的补码为1 111 1111  1111 1111 1111 1111 1111 1111,加上-i补码 1000 0000  0000  0000  0000  0000  0000  0000,得 0111 1111  1111 1111 1111 1111 1111 1111,即 2147483647。

另外补充一点,计算机中有符号数和无符号数的机器码(补码)是一样的,同一个二进制码按照有无符号输出结果不一样,例如本题中四个答案如果按照无符号整数输出,那么答案就是C。

3、32位机上根据下面的代码,问哪些说法是正确的?

signed char a = 0xe0;
unsigned int b = a;
unsigned char c = a;

a>0 && c>0 为真

a == c 为真

b 的十六进制表示是:0xffffffe0

上面都不对

KEY:C

解答:将char转换为int时,关键看char是unsigned还是signed,如果是unsigned就执行0扩展,如果是signed就执行符号位扩展。跟int本身是signed还是unsiged无关。

也就是说,短数据类型扩展为长数据类型,看的是需要扩展的数据类型,而不是扩展成的数据类型。

4、逻辑运算符两侧运算对象的数据类型()。

只能是0或1

只能是0或非0正数

只能是整型或字符型数据

可以是任何类型的数据

KEY:D

5、7&3+12的值是15。请问这句话的说法是正确的吗?

正确

错误

KEY:B

解答:+优先级高于&(按位与)。乘除运算符>加减运算符>左移右移运算符>关系运算符>位运算符>逻辑运算符>赋值运算符。

6、若有int w=1,x=2,y=3,z=4;则条件表达w<x?w:y<z?y:z的值是3。

正确

错误

KEY:B

解答:三目运算符为左结合,w<x?w:(y<z?y:z)  加个括号应该就好理解了,w<x为真,返回w,即表达式的值为1。

运算符中,只有单目运算符,三目运算符,和赋值运算符为左结合,其余都是右结合。

7、下列哪种方法不能用于文本加密()

RSA

RC4

MD5

DES

KEY:C

解答:RSA:由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的,非对称算法; 

RC2和RC4:对称算法,用变长密钥对大量数据进行加密,比 DES 快;

DES(Data Encryption Standard):对称算法,数据加密标准,速度较快,适用于加密大量数据的场合; 

MD5:严格来说不算加密算法,只能说是摘要算法。

8、将逻辑代码:

int x = ...;

if (x % 2) {
    return x - 1;
} else {
    return x;
}

用表达式:return x & -2; 替代,以下说法中不正确的是( )。

计算机的补码表示使得两段代码等价

用第二段代码执行起来会更快一些

这段代码只适用于x为正数的情况

第一段代码更适合阅读

KEY:C

解答:代码的本意是返回偶数。计算机内部表示的整数的方式为补码,正数为自身,负数为符号位不变其余各位取反加1。理解本体分两部分:

  • -2的补码为1111,1110,正数与-2相与很明显就是使整数变为2的整数倍(相当于减一);
  • 负数时,当负数为偶数时,补码的最后一位比为0,与-2相与仍为自身;

当为奇数时,补码的最后一位比为1,与-2相与时其它位不变最后一位为1,此时根据负数到源码的转换规则,各位取反加1,此时相当于加上-1。

9、定义函数模板add,以下说法正确的有:()

template<typename T>

T add(T &x, T &y)
{
    return x+y;
}

const int (*pfun1)(const int &a, const int &b);
const int (*pfun2)(int &a, int &b);
int (*pfun3)(const int &a, const int &b);
int (*pfun4)(int &a, int &b);

pfun2 = add失败,需要显示定义为pfun2 = add<int>

pfun1 = add

pfun3 = add失败,add无法正确推导

pfun4 = add<int>失败,函数指针不可赋值

KEY:BC

解答:A:pfun2 = add的错误提示不是pfun2 = add,而是 no matches converting function 'add' to type 'const int (*)(int&, int&);

B:pfun1的参数和返回值一致,可以自动推导,正确赋值;

C:pfun3 = add赋值失败,失败原因和A一样;

D:pfun4 = add显示的指定类型T为int型,赋值正确。

10、针对以下代码,

const char str1[] = "abc";
const char str2[] = "abc";
const char *p1 = "abc";
const char *p2 = "abc";

cout << (str1 == str2) << endl << (p1 == p2) << endl;

判断下列说法哪个是正确的(注意是地址):______。

str1和str2地址不同,P1和P2地址相同。

str1和str2地址相同,P1和P2地址相同。

str1和str2地址不同,P1和P2地址不同。

str1和str2地址相同,P1和P2地址不同。

KEY:A

解答:str1和str2毫无干系,他们都是字符常量数组,恰巧存储的值相同而已。

后面的两个“abc”存储在常量区,因为他们是常量,但是p1 p2存储在栈中。所以p1和p2的值不同,但是他们指向同一块静态存储区域,也就是"abc"的地址。

11、T是一个数据类型,关于std::vector::at 和 std::vector::operator[] 描述正确的是:

at总是做边界检查, operator[] 不做边界检查.

at 不做边界检查, operator[] 做边界检查.

at和operator[] 是一样的

KEY:A

解答:(详情参看 《C++ Primer》(第5版) P310 ”下标操作和安全的随机访问“):

提供快速随机访问的容器(如:string、vector、deque 和 array)也都提供下标运算符(operator [ ])。

下标运算符接受一个下标参数,返回容器中该位置的元素的引用。给定下标必须保证”在范围内“(即,大于等于0,且小于容器的大小)。保证下标有效是程序员的责任,下标运算符并不检查下标是否在合法范围内。使用越界的下标是一种严重的程序设计错误,而且编译器并不检查这种错误。

也就是说:c[n]:返回c中下标为n的元素的引用,n是一个无符号整数。若n>=c.size(),则函数行为未定义。

如果希望确保下标是合法的,可以使用at成员函数。at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_range异常。

也就是说:c.at(n):返回下标为n的元素的引用。如果下标越界,则抛出一个out_of_range异常。

12、以下对枚举类型名的定义中正确的是()。

enum a={sum,mon,tue};

enum a {sum=9,mon=-1,tue};

enum a={"sum","mon","tue"};

enum a {"sum","mon","tue"};

KEY:B

解答:枚举变量定义的方式和结构体、共用体类似:

enum 枚举名
{
    枚举值[=整型常量],
    枚举值[=整型常量],
    ...
    枚举值[=整型常量],
}枚举变量名列表;

13、通过一个对象调用虚函数时,C++系统对该调用采用(  )。

动态联编

静态联编

不确定是哪种联编

函数重载

KEY:A

解答:静态联编:通过对象名调用虚函数,在编译阶段就能确定调用的是哪一个类的虚函数;

动态联编:通过基类指针调用,在编译阶段无法通过语句本身来确定调用哪一个类的虚函数,只有在运行时指向一个对象后,才能确定调用时哪个类的虚函数。

14、函数fun的声明为int fun(int *p[4]),以下哪个变量可以作为fun的合法参数()

int a[4][4];

int **a;

int **a[4]

int (*a)[4];

KEY:B

解答:一个二维数组相当于一个数组指针。而fun函数是一个指针数组,归根结底就是一个数组,数组元素为指针。所以合法参数就需要是数组首元素的地址就行了,而首元素是一个指针,所以一个二级指针就行了。

也可以通过+1来确定。int a[4][4]和int (*a)[4]表示的数组指针,+1的话表示越过一行,间隔int[4];int **a表示的是指向指针的指针,+1的话表示越过一个指针,间隔int*;而题目的int *p[4]表示指针数组,+1的话表示越过一个指针,间隔int*。很明显,应该选择B。

15、求sizeof(s)。

struct s
{
    int x: 3;
    int y: 4;
    int z: 5;
    double a;
}

16

32

20

24

KEY:A

解答:在存储信息时,不必用一个或多个字节,而只需要占几个二进制位就可以了。例如在存放一个开关量时,只有开和关两个状态,用一个二进制位即可。为了节省存储空间,并使处理简便,C语言允许在一个结构体中以位为单位来指定某成员所占内存长度,这种以位为单位的成员,称为“位段”或“位域”。

位段定义的一般形式为:

struct 位段结构名
{
    类型说明符 位段名1:位段长度1;
    类型说明符 位段名2:位段长度2;
    类型说明符 位段名3:位段长度3;
    类型说明符 位段名4:位段长度4;
};

位段中数据引用的一般形式为:

位段变量.位段成员

需要注意的是:

  • 位段成员的类型只能是unsigned 或int类型;
  • 一个位段必须存储在同一个存储单元中,不能跨两个单元;
  • 位段的长度不能大于存储单元的长度,也不能定义位段数组;
  • 位段可以用整型格式符输出,也可以用%u、%o、%x等格式符输出;
  • 位段可以在数值表达式中引用,它会被系统自动转换成为整型数。

16、下列关于makefile描述正确的有?

makefile文件保存了编译器和连接器的参数选项

主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释

默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件, 找到了解释这个文件

在Makefile不可以使用include关键字把别的Makefile包含进来

KEY:ABC

解答:makefile文件保存了编译器和连接器的参数选项,还表述了所有源文件之间的关系(源代码文件需要的特定的包含文件,可执行文件要求包含的目标文件模块及库等)。

创建程序(make程序),首先读取makefile文件,然后再激活编译器,汇编器,资源编译器和连接器以便产生最后的输出,最后输出并生成的通常是可执行文件。创建程序利用内置的推理规则来激活编译器,以便通过对特定CPP文件的编译来产生特定的OBJ文件。

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。 

  • 显式规则:显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令;
  • 隐晦规则:由于make的自动推导功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的;
  • 变量定义:在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上;
  • 文件指示:其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令
  • 注释:Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。 

默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用 “GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。

include的语法是: include <filename>; filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)。

GNU make 的工作方式:

  • 读入主Makefile (主Makefile中可以引用其他Makefile);
  • 读入被include的其他Makefile;
  • 初始化文件中的变量;
  • 推导隐晦规则,并分析所有规则;
  • 为所有的目标文件创建依赖关系链;
  • 根据依赖关系,决定哪些目标要重新生成;
  • 执行生成命令。

参考文章:Makefile 使用总结教你写Makefile(很全,含有工作经验的)

17、在x86的机器上,int a=0xabcd1234,char b=((char*)&a)[0]请问b是多少()。

0xa

0x4

0xab

0x34

KEY:D

解答:x86是小端存储,即高位存储在高地址,低位存储在低地址。int a = 0xabcd1234;,

内存中 ab  cd  12   34,b作为一个char,右边表达式指针指向为0x34。

            高  -->      低

18、如下代码,result变量的输出结果是多少?

#include<iostream>
using namespace std;

int i=1;

class MyCls{
public:
    MyCls():m_nFor(m_nThd),m_nSec(i++),m_nFir(i++),m_nThd(i++){
        m_nThd=i;
    }

    void echo(){
        cout<<"result:"<<m_nFir+m_nSec+m_nThd+m_nFor<<endl;
    }
private:
    int m_nFir;
    int m_nSec;
    int m_nThd;
    int &m_nFor;
};

int main()
{
    MyCls oCls;
    oCls.echo();

    return 0;
}

KEY:11

解答:首先要明白变量初始化的顺序是其声明的顺序,跟初始化列表中的顺序无关。所以变量的初始化顺序为m_nFir(i++),m_nSec(i++),m_nThd(i++),&m_nFor(m_nThd);

i初始值为1,所以经过初始化列表初始化以后m_nFir=1,m_nSec=2,m_nThd=3,m_nFor为m_nThd的一个引用。并且此时i的值为4。对象成员的初始化结束后,开始执行本类的构造函数。构造函数中执行语句m_nThd=i后,m_nThd=4,m_nFor是它的一个引用,自然值也为4。

输出结果m_nFir+m_nSec+m_nThd+m_nFor=1+2+4+4=11。

19、词法分析器用于识别()

句子

句型

单词

生产式

KEY:C

解答:编译的过程为:扫描(词法分析)、语法分析、语义分析、源代码优化、代码生成和目标代码优化。

源码 ->(扫描)-> 标记 ->(语法分析)-> 语法树 ->(语义分析)-> 标识语义后的语法树 ->(源码优化)-> 中间代码 ->(代码生成)-> 目标机器代码 ->(目标代码优化)-> 最终目标代码。

20、设顺序表的长度为n。下列算法中,最坏情况下比较次数小于n的是()。

寻找最大项

堆排序

快速排序

顺序查找法

KEY:A

解答:顺序表不是、不是、不是元素排列有序的线性表,而是指元素以顺序存储结构存储在相邻的物理存储单元上,元素之间没有直接大小关联。

2个数比较大小 比较1次即可找出最大,3个数比较两次即可找出最大。同理n个数最多比较n-1次即可找出最大。

21、有以下说明语句:

struct  Worker
{
    int  no;
    char name[20];
};

Worker w, *p=&w;

则下列错误的引用是( )。

w.no

p->no

(*p).no

*p.no

KEY:D

解答:.的优先级高于*。

22、能够把指定长度的字节序列插入到输出流中的函数是()。

put

write

cout

print

KEY:B

解答:write函数所在的头文件为 <unistd.h>。write有两种用法:

ssize_t write(int handle, void *buf, int nbyte);
ssize_t write(const char* str, int n);

其中:handle 是文件描述符;buf是指定的缓冲区,即指针,指向一段内存单元;nbyte是要写入文件指定的字节数;返回值:写入文档的字节数(成功);-1(出错)。str是字符指针或字符数组,用来存放一个字符串;n是int型数,它用来表示输出显示字符串中字符的个数。

23、下列关于赋值运算符“=”重载的叙述中,正确的是

赋值运算符只能作为类的成员函数重载

默认的赋值运算符实现了“深层复制”功能

重载的赋值运算符函数有两个本类对象作为形参

如果己经定义了复制拷贝构造函数,就不能重载赋值运算符

KEY:A

解答:默认的赋值运算符、拷贝构造函数都是实现了“浅层复制”功能。

  • 浅层复制:将新建立的对象的指针指向被复制的对象指向的内存单元,即两个对象指向同一块内存单元;
  • 深层复制:两个对象的成员变量都各自拥有自己的内存区域。

例如:

A(const A &a)        //浅层
{
    x = a.x;
}

A(const A &a)        //深层
{
    x = new int;    //开辟新的空间,复制构造对象的同时将成员指针变量保存在新空间中
    *x = *(a.x);    //读取旧对象的成员指针变量x地址处的数据并赋值给新对象的成员指针变量x所指向的内存区域
}

参考文章:C++基础---浅层及深层拷贝构造函数

24、类 CBase 的定义如下: 在构造函数 CDerive 的下列定义中,正确的是()

class CBase
{
    int x;
public:
    CBase(int n) { x=n; }
};

class CDerive:CBase
{
    CBase y;
    int z;
public:
    CDeriver(int a, int b, int c);
};

CDerive::CDerive(int a,int b,int c):x(a),y(b),z(c){}

CDerive::CDerive(int a,int b,int c):CBase(a),y(b),z(c){}

CDerive::CDerive(int a,int b,int c):CBase(a),CDerive(b),z(c){}

CDerive::CDerive(int a,int b,int c):x(a),CBase(b),z(c){}

KEY:B

解答:三个变量分别用于基类构造函数初始化(使用基类)、对象成员构造函数初始化(使用对象成员名称)、派生类普通成员初始化(使用对象成员名称)。

25、下列程序执行后的输出结果是()。

void main()
{
    char x = 0xFFFF;
    printf("%d\n", x--);
}

-32767

FFFE

-1

-32768

KEY:C

解答:因为x是字符型,所以将0xFFFF赋值给x,x的值为0xFF,因为数据在内存中是其补码表示的,OxFF的真值是-1,所以显示的结果是-1,即正确答案为C。

26、假设A为抽象类,下列声明( )是正确的

A fun(int);

A *p;

int fun(A);

A obj;

KEY:B

解答:抽象类不能实例化, 不能参数类型和函数返回类型。但是,抽象类可以用作指针类型,因而B选项正确。

27、某二叉树的前序序列为ABCDEFG,中序序列为DCBAEFG,则该二叉树的后序序列为()。

EFGDCBA

DCBEFGA

BCDGFEA

DCBGFEA

KEY:D

解答:前序序列:根左右;中序序列:左根右;后序序列:左右根。也就是说,左右的顺序是不会变的,根的顺序依次挪位。

前序序列为ABCDEFG 先访问结点,再到左孩子,右孩子 中序序列为DCBAEFG 先左,再结,后右 A是结点 BCD是A的左孩子 EFG是A的右孩子 A下是B结点,接着C是B的左孩子,D是C的左孩子 EFG以此类推,画图辅助更快。

怎么分析比较快呢?

前序ABCDEFG,所以A一定为根结点,在看一下中序DCBAEFG,看A的位置,可得出,DCB三个一定是在A的左边。分析前序BCD,所以B一定为A左边的根结点,看一下中序DCB中B的位置,所以DC一定是在B的左边……依次分析。

28、在32位环境下,以下代码输出的是(    )。

#include <iostream>
using namespace std;

class A
{
public:
	A() { printf("A"); }
	~A() { printf("~A"); }
};

class B :public A
{
public:
	B() { printf("B"); }
	~B() { printf("~B"); }
};

int main()
{
	A *c = new B[2];            //指向基类的对象指针
	delete[] c;

	return 0;
}

ABAB~A~A

ABAB~B~A~B~A

ABAB~B~A

ABAB~A~B~A~B

KEY:A

解答:在C++中,析构函数的作用是:当一个对象被销毁时,调用析构函数对类对象和对象成员进行释放内存资源。

当我们定义一个指向派生类类型对象指针时,构造函数按照从基类到派生类的顺序被调用,但是当删除指向派生类的基类指针时,派生类的析构函数没有被调用,只是调用了基类的析构函数,此时派生类将会导致内存泄漏。

我们需要将基类的析构函数声明为虚函数,此时在调用析构函数的时候是根据ptr指向的具体类型来调用析构函数,此时会调用派生类的析构函数。

也就是说:

#include <iostream>
using namespace std;

class A
{
public:
	A() { printf("A"); }
	~A() { printf("~A"); }
};

class B :public A
{
public:
	B() { printf("B"); }
	~B() { printf("~B"); }
};

int main()
{
	B *c = new B[2];            //指向派生类的对象指针
	delete[] c;

	return 0;
}

此时返回值为:ABAB~B~A~B~A。

或者:

#include <iostream>
using namespace std;

class A
{
public:
	A() { printf("A"); }
	virtual ~A() { printf("~A"); }            //虚函数
};

class B :public A
{
public:
	B() { printf("B"); }
	~B() { printf("~B"); }
};

int main()
{
	A *c = new B[2];                //指向基类的对象指针
	delete[] c;

	return 0;
}

此时返回值为:ABAB~B~A~B~A。

29、有如下程序段:

int i, n = 0;
float x = 1, y1 = 2.1 / 1.9, y2 = 1.9 / 2.1;

for ( i = 1; i < 22; i++ )
 x = x * y1;

while ( x != 1.0 )
{
 x = x * y2; n++;
}

printf( “ %d / n ”, n );

请问执行结果是:

21

22

无限循环

程序崩溃

KEY:C

解答:题中浮点数比较使用的是while( x != 1.0 ),正确方法应该是:判断浮点数a,b是否相等:

fabs(a-b)<epsilon  , epsilon为 一个误差范围。

30、变量 val 的内存地址位于:

void func()
{
  static int val;
    ...
}

已初始化数据段

未初始化数据段

KEY:B

解答:初始化的全局变量和静态变量存放在 data段(全局初始化区);未初始化的全局变量和未初始化的静态变量存放在bss段(全局未初始化区)。

其中BSS段的特点是:在程序执行之前BSS段会自动清0,所以未初始化的全局变量和静态变量在程序执行之前就已经变成0,但是并不是存放在初始化段,是因为存放在未初始化段才初始化为0的。 所以选B。

  • bss段:bss段通常是指用来存放程序中未初始化的全局变量的一块内存区域;
  • data段:数据段通常是指用来存放程序中已初始化的全局变量的一块内存区域;
  • text段:代码段通常是指用来存放程序执行代码的一块内存区域;
  • 堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减;
  • 栈(stack):又称堆栈,是用户存放程序临时创建的局部变量。

text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。

一个程序本质上都是由 bss段、data段、text段三个组成的。

比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。

text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由操作系统初始化清零。

同时,bss段并不给该段的数据分配空间,只是记录数据所需空间的大小;data段则为数据分配空间,数据保存在目标文件中。

31、没用参数的两个函数是不能重载的.说法是否正确?

正确

错误

KEY:B

解答:C++不允许仅根据函数的返回类型重载函数名称;可以编写两个名称相同,参数也相同的函数,其中一个是const,另一个不是。例如:

#include <iostream>
using namespace std;
 
class A
{
public:
    void f()
    {
        cout << 1 << endl;
    }

    void f() const
    {
        cout << 2 << endl;
    }
};
 
 
int main()
{
    A a;
    const A b;
    a.f();              //1
    b.f();              //2

    return 0;
}

32、设有定义:int x = 0,﹡p; 紧接着的赋值语句正确的是?

*p = NULL;

p = NULL;

p = x;

*p = x;

KEY:B

解答:指针P没有指向某个地址,如果P初始化了的话,D就是对的。也就是说,申明*p之后p是一个空指针,只能p = &x,不能给空指针赋值。

33、下面程序应该输出多少?

char *c[] = { "ENTER", "NEW", "POINT", "FIRST" }; 
char **cp[] = { c+3, c+2, c+1, c }; 
char ***cpp = cp; 
 
int main(void)
{ 
    printf("%s", **++cpp); 
    printf("%s", *--*++cpp+3); 
    printf("%s", *cpp[-2]+3); 
    printf("%s\n", cpp[-1][-1]+1); 

    return 0;
}

POINTERSTEW

FERSTEPOINW

NEWPOINTW

POINTFIREST

KEY:A

解答:c是一个指针数组,每个数组单元都是一个指针,指向一个字符串。即c[0]="ENTER"......c[3]="FIRST"

由于[]与*运算本质几乎是一致的,以下用[]替换*更容易理解。也就是:c[i]=*(c+i)

c和c+i都是char*[]类型,它可以退化成char**类型,它正好是一个char**数组,cp[0]=c+3 .....cp[3]=c,引用后cp[0][0]=*(c+3)=c[3]="FIRST"。

cp是char**[]类型,它可以退化成char***类型,正好与cpp类型一致。

1>++ccp的值是cp+1  *++p=*(cp+1)=cp[1]  **++p=*(cp[1])=c[2]="POINT"

2>运算符优先级决定运算先后关系:++ccp的值是cp+2 *++p=*(cp+2)=cp[2]=c+1 --*++p=c *--*++p=*(c+0)=c[0]="ENTER"再+3字符串指向"ER"

3>cpp的值为cp+2 cpp[-2]=*(cpp-2)=*(cp+2-2)=cp[0]=c+3再引用*(c+3)=c[3]="FIRST"字符串指到"ST"

4>cpp的值没变,cpp[-1]=*(cpp-1)=*(cp+2-1)=cp[1]=c+2再[-1]得*(c+2-1)=c[1]="NEW",+1字符创指针指到"EW"。

首先看 C,CP,CPP 之间的存储关系:

计算各个表达式结果的过程如下图:

34、在vs编译环境下,以下代码的运行情况:

int f(int a, int b, int c)
{
    return 0;
}

int main(){
    return  f(printf("a"),printf("b"),printf("c"));
}

编译不过

运行错误

abc

cba

KEY:D

解答:当用函数做实参时,编译器一般会根据参数传递顺序,先计算出函数的返回值,然后将返回值传递给原来的函数。

而函数的参数是通过栈传递的。因此参数从右往左入栈顺序是:printf("c"),printf("b"),printf("a")。依次计算出结果:cba。

35、下面程序的执行结果:

class A{
    public:
        long a;
};

class B : public A {
    public:
        long b;
};

void seta(A* data, int idx) {
    data[idx].a = 2;
}

int main(int argc, char *argv[]) {
    B data[4];
    for(int i=0; i<4; ++i){
        data[i].a = 1;
        data[i].b = 1;
        seta(data, i);
    }
    for(int i=0; i<4; ++i){
         std::cout << data[i].a << data[i].b;
    }

    return 0;
}

11111111

12121212

11112222

21212121

22221111

KEY:E

解答:这道题应该注意:指针类型加减时步长的问题。A大小为4;B大小为8。那么:当调用seta()方法的时候,由于传入的实参为B类型,大小为8,而形参为A类型,大小为4。

data[idx] 取 data + idx 处的元素,这时指针 data加1 的长度不是一个B长度,而是一个A长度,或者说是1/2个B长度。这时该函数中 data[0~3] 指向的是原 data[0].a,data[0].b,data[1].a,data[1].b, 

由于隐式类型转换的缘故,data[0].a, data[0].b,data[1].a,data[1].b 处的值全部由于 data[idx].a = 2; 操作变为 2。

这道题如果改为void seta(B* data, int idx),那么形参中data指针加1步长为8,结果就是21212121。但是由于步长为4,所以结果就是 22221111。

36、有以下程序:

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

void fun ( double *pl,double *p2,double *s )
{
    s = ( double*) calloc ( 1,sizeof(double));
    *s = *pl + *(p2+1);
}

main( )
{
    double a[2] = {1.1,2.2}, b[2] = {10.0,20.0}, *s = a;
    fun (a,b,s);
    printf ("%5.2f\n", *s) ;
}

程序的输出结果是?

21.10

11.10

12.10

1.10

KEY:D

解答:本题考查把数组名作为函数参数,执行fun函数后,s的值并没有发生变化,仍然是指向a,所以输出结果为1.10,选项D正确。

37、std::vector::iterator重载了下面哪些运算符?

++

>>

*(前置)

==

KEY:ACD

解答:迭代器当指针看,++指针自增,*取内容,==判断指针相等,<<没有。

38、有声明:

int fun6(int);
int (*pf)(int) = fun6;

在下列选项中,正确的调用是(  )。

int a=15; int n=fun6(&a);

int a = 15; cout<<(&pf)(a);

cout<<(pf)( 256 );

cout << pf( 256 );

KEY:CD

解答:对于函数指针来说,用它调用函数的方法可以有:

int i;
fun6(i);
(fun6)(i);
(*fun6)(i);

也就是说,fun6之前的指针符号可以使用,也可以不使用。

39、有如下C++代码:

struct A{
  void foo(){printf("foo");}
  virtual void bar(){printf("bar");}
  A(){bar();}
};

struct B:A{
  void foo(){printf("b_foo");}
  void bar(){printf("b_bar");}
};

A *p=new B;
p->foo();
p->bar();

那么输出为:  

barfoob_bar

foobarb_bar

barfoob_foo

foobarb_fpp

KEY:A

解答:一般不建议在构造函数或者析构函数中调用虚函数,因为在构造函数和析构函数中调用虚函数不会呈现多态性,也就是说在构造基类构造函数的时候,派生类的部分还没有构造,怎么谈去用虚函数实现动态绑定派生生类对象呢,所以构造B基类部分的时候,调用的基类的函数bar。

也就是说:在构造函数中调用虚函数时,只调用自己类中定义的函数(若自己类中没有定义,则调用基类中定义的函数),而不是调用派生类中重新定义的虚函数。

40、下面代码的输出结果是()。

int main(){
   int pid;
   int num=1;
   pid=fork();

   if(pid>0){
       num++;
       printf("in parent:num:%d addr:%x\n",num,&num);
   }
   else if(pid==0){
       printf("in child:num:%d addr:%x\n",num,&num);
   }
}

父子进程中输出的num相同,num地址不相同

父子进程中输出的num不同,num地址相同

父子进程中输出的num相同,num地址也相同

父子进程中输出的num不同,num地址不相同

KEY:B

解答:虚拟地址空间。num地址的值相同,但是其真实的物理地址却不一样。linux下实现了一下,发现地址值真的一样。fork之后子进程复制了父进程的数据、堆栈。但是由于地址重定位器之类的魔法存在,所以,看似一样的地址空间(虚拟地址空间),其实却是不同的物理地址空间。同时可以验证c程序中输出的地址空间其实都是虚拟地址空间。

也就是说,本题问题就出在地址相不相同,如果按照两个进程各处在独自的虚拟进程地址空间分析的话,这个题很容易会选择第四个答案,但是Linux中的资源分配都是虚拟机制,也就是说,他们还是会共用一个虚拟的地址,但是映射到物理内存就可能会不一样。

41、下面代码的执行结果是()。

int main(void)
{
    char *p[]={“TENCENT”,”CAMPUS”,”RECRUITING”};
    char **pp[]={p+2,p+1,p};
    char ***ppp=pp;

    printf(“%s”, **++ppp);
    printf(“%s”, *++*++ppp);

    return 0;
}

CAMPUS RECRUITING

RECRUITING CAMPUS

CAMPUS CAMPUS

RECRUITING RECRUITING

KEY:C

解答:本题的逻辑可以画出下面的图:

printf(“%s”,**++ppp);即,ppp当前所指向的位置,再往下移一个位置,即pp的位置2,而pp的位置2指向的是p的位置2,p的位置2指向的是CAMPUS,所以先输出CAMPUS;

printf(“%s”,*++*++ppp);这个语句等价于 printf(“%s”,*++(*++ppp));所以我们首先看,++ppp,第一个printf语句中ppp已经指向了pp的位置2,所以再往下移一个,指向了pp的位置3,而(*++ppp)则代表pp位置3所指向的内容,即p的位置1(pp的位置3指向的是p的位置1),在此基础上前面再加上一个++,则代表指针p在位置1的基础上再往下移动,即指针p的位置2,而p的位置2所指向的内容是CAMPUS,所以第二行输出的也是CAMPUS。

也就是说,p、pp、ppp都是指针,都是指向上一级的指针。加上*就是取上一级的值,不加就是上一级的地址。ppp的值为pp的地址,pp的值为p的地址,p的值就是字符串。

42、以下叙述中正确的是()

在C语言中,逻辑真值和假值分别对应1和0

关系运算符两边的运算对象可以是C语言中任意合法的表达式

对于浮点变量x和y,表达式:x==y 是非法的,会出编译错误

分支结构是根据算术表达式的结果来判断流程走向的

KEY:B

解答:A选项中,在C语言中,逻辑真值对应非0; C选项中,表达式:x==y 是合法的;D选项中,分支结构的流程走向是根据表达式的值,并不仅仅是算数表达式的值。因此B选项正确。

43、在16位C编译系统上,以下程序的输出结果是()。

void main()
{
    long y=-43456;
    printf("y=│%-81d│ y=│%-081d│ y=│%081dl│y=│%+81d│",y,y,y,y);
}

y=│□□-43456│ y=│-□□43456│ y=│-0043456│ y=│-43456□□│

y=│□□-43456│ y=│-43456□□│ y=│-0043456│ y=│-□□43456│

y=│-43456□□│ y=│-43456□□│ y=│-0043456│ y=│□□-43456│

y=│-43456□□│ y=│-4345600│ y=│-0043456│ y=│□□-43456│

KEY:C

解答:注意一下区别:

%-81d表示输出占8个空格的位置,左对齐,右边多余的位置补空格,故第一个y的输出为:│-43456□□│;

%-081d等价于%-81d,因为不可能在右边空位上补0,所以第二个y的输出也为:│-43456□□│;

%081d表示输出占8个空格的位置,右对齐,左边多余的位置补0,所以第三个y的输出为:│-0043456│;

%+81d表示输出占8个空格的位置,右对齐,左边多余的位置补空格,必须输出正负号,所以第四个y的输出为│□□-43456│。正确答案应为C。

44、一张1024×640分辨率的图片,假定每个像素用16位色彩表示,用位图文件(bitmap)格式存储,则这张图片文件需要占用多大的存储空间____。

640KB

1280KB

2560KB

5120KB

10240KB

KEY:B

解答:1024*640*16 bit = 1024*640*16/8 B = 1024*640*16/8/1024 KB = 1280KB。

45、What is the result of the following program?

char* f(char *str, char ch) {
    char *it1 = str;
    char *it2 = str;
    while (*it2 != '\0') {
        while (*it2 == ch) { it2++; }
        *it1++ = *it2++;
     }
    return str;
}

void main(int argc, char *argv[]) {
    char *a = new char[10];
    strcpy(a, "abcdcccd");
    cout << f(a, 'c');
}

abdcccd

abdd

abcc

abddcccd

Access Violation

KEY:D

解答:it1的前两个字符为ab没有异议,当it2的指针指向c时执行 it2++,运行后it2指向d,然后下一个字母不为c,所以it1的指针内复制为d,即此时it1为abd,之后遇到3个c,执行 it2++,直到it2指向d时才将d赋值给it1,也就是此时it1=abdd,但是接下来it2已经为空了,也就是“\0”,所以就不执行循环了,但是it1内本身后面还有cccd,前面的四个字母只是it2覆盖成abdd,所以最终的答案是abddcccd也就是D。

46、运行下面这段代码,会出现的情况是()。

void GetMemory(char *p)
{
    p = (char *)malloc(100);
}

void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}

hello world

显示为乱码

程序崩溃

hello

KEY:C

解答:GetMemory(char *p);,这里*p是形参,是局部变量不能将malloc申请的地址指针返回,造成内存泄露更严重的是执行GetMemory(str);,后str依然为NULL,执行strcpy(str, 'hello world');就会出错。

47、在一个被调用函数中,关于return语句使用的描述,( )是错误的。

被调用函数中可以不用return语句

被调用函数中可以使用多个return语句

被调用函数中,如果有返回值,就一定要有return语句

被调用函数中,一个return语句可以返回多个值给调用函数

KEY:D

解答:被调用函数中,如果有返回值,就一定要有return语句。编译的时候并不出错,运行的时候会提示出错。

48、C++里面如何声明const void f(void)函数为C程序中的库函数。

static "C"

extern "C"

explict "C"

register "C"

KEY:B

解答:当它与 “C” 一起连用时,如:extern “C” void fun(int a , int b);,会告诉C++编译器在编译fun这个函数名是按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非。

49、下面定义语句是不正确的:

float a=b=10.0;

50、以下程序的输出结果是()。

main()
{
    char s[]="123",*p;
    p=s;
    printf("%c%c%c\n",*p++,*p++,*p++);
}

132

321

213

312

KEY:B

解答:printf自右向左压栈,但这种写法是未定义行为!!!

猜你喜欢

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