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

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

1、阅读以下程序,当输入数据的形式为12a345b789↙,正确的输出结果为()。

void main()
{
    char c1,c2;
    int a1,a2;

    c1=getchar();
    scanf("%2d",&a1);
    c2=getchar();
    scanf("%3d",&a2);
    printf ("%d,%d,%c,%c\n",a1,a2,c1,c2);
}

KEY:2,345,1,a

解答:这里的"%2d"表示向缓冲区中放入2个输入。如果放入的输入都符合输入的条件,那么都一起接受了;如果放入的输入不符合输入的条件,那么也不丢弃掉,看接下来的输入条件。

2、Math.round(11.5) 等于()。

KEY:12

解答:取近似值,如果有两个数距离它的距离相等,那么就取较大的那个值。

3、如下程序:

#include <iostream>
using namespace std;
 
class A
{
public:
    A()
    {
        printf("A");
    }
};
 
int main()
{
    A *p1 = new A;
    A *p2 = (A *)malloc(sizeof(A));
     
    return 0;
}

该程序运行结果为()。

KEY:A

解答:这题主要是考的new和malloc的区别,new会分配内存,并调用类的构造函数创建对象,而malloc只是分配内存,不调用类的构造函数创建对象。同时这个程序应该用delete p1;free(p2);。

4、以下程序用来统计文件中字符的个数(函数feof 用以检查文件是否结束,结束是返回非零):

#include<stdio.h>

main()
{ 
    FILE *fp; 
    long num=0;
    fp=fopen("fname.dat", "r" );
    while (________) 
    { 
        fgetc( fp );
        num++ ;
    }
    printf( " num= % d\n",num);
    fclose( fp );
}

下面选项中,填入横线处不能得到正确结果的是?

feof( fp )= =NULL

!feof( fp )

feof(fp)

feof( fp ) == 0

KEY:C

解答:feof()函数,如果遇到文件结束,函数值为非零值,否则函数值为0。文件结束符EOF,Windows下为组合键Ctrl+Z,Unix/Linux下为组合键Ctrl+D。

5、若有以下类W说明,则函数fConst的正确定义是(  )。

class W
{
    int a;

    public:
        void fConst(int&) const;
};

void W::fConst( int &k )const  { k = a; }

void W::fConst( int &k )const  { k = a++; }

void W::fConst( int &k )const  { cin >> a; }

void W::fConst( int &k )const  { a = k; }

KEY:A

解答:这道题目考的是const关键字,类的方法后面加了const后,该方法的实现中不能修改类的成员。即不能修改类成员a选项中,只有选项A,没有修改类成员a的值。

注意:const如此用法,可以在该成员函数中引用成员数据的值,但不能修改对象的数据成员,在函数体内只能调用const成员函数,不能调用其他成员函数。

6、C++内存分配中说法错误的是:______。

对于栈来讲,生长方向是向上的,也就是向着内存地址增加的方向

对于堆,大量的 new/delete 操作会造成内存空间的不连续

堆容易产生 memory leak(内存泄漏)

堆的效率比栈要低得多

栈变量引用容易逃逸

栈区一般由编译器自动分配释放,堆区一般由程序员分配释放

KEY:A

解答:栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将 提示overflow。因此,能从栈获得的空间较小;

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 

7、函数func的定义如下:

void func(const int& v1, const int& v2)
{
    std::cout << v1 << ' ';
    std::cout << v2 << ' ';
}

以下代码在vs中输出结果为____。

int main (int argc, char* argv[])
{
    int i=0;
    func(++i,i++);
    return 0;
}

0 1

1 2

2 1

2 0

KEY:D

解答:在C语言中,函数的参数是从右到左的顺序压入到栈的,函数执行时再从栈弹出弹出来执行。

参数i先入栈0,然后执行i++此时i为1,接着参数i先执行++i,i此时为2,后入栈进行输出2 0。

8、下面有关回调函数的说法,错误的是?

回调函数就是一个通过函数指针调用的函数

回调函数可能被系统API调用一次,也可能被循环调用多次

回调函数本身可以是全局函数 ,静态函数和某个特定的类的成员函数

回调函数可用于通知机制

KEY:C

解答:所谓的回调函数,就是预先在系统的对函数进行注册,让系统知道这个函数的存在,以后,当某个事件发生时,再调用这个函数对事件进行响应。 定义一个类的成员函数时在该函数前加CALLBACK即将其定义为回调函数,函数的实现和普通成员函数没有区别。

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

类的成员函数需要隐含的this指针,而回调函数没有办法提供。

9、下列情况中,不会调用拷贝构造函数的是()

用一个对象去初始化同一个类的另一个新对象时

将类的一个对象赋值给该类的另一个对象时

函数的形参对象,调用函数进行形参和实参结合时

函数的返回值是类的对象,函数执行返回调用时

KEY:B

解答:关于拷贝构造函数的意义:

class Base{
    public:
        Base(){};        //构造函数
        Base(int i)();        //构造函数
        Base(Base &b){};        //拷贝构造函数
};

Base w1;     //构造函数
Base w2(w1);   //拷贝构造函数
Base w3 = w1; //拷贝构造函数 ,与上式等价
w1 = w2;     //赋值运算符

Base w4(100);        //构造函数
Base w5 = 100;    //构造函数,与上式等价
w5 = 200;        //构造函数,实现了类型转换功能

可知:将类的一个对象赋值给该类的另一个对象时,是赋值运算,不是构造函数。

10、函数外部访问x等于什么?

enum string{    
    x1,    
    x2,    
    x3=10,    
    x4,    
    x5,    
} x;

5

12

0

随机值

KEY:C

解答:在函数外部定义,为全局变量,默认为0;若在函数内部定义,则在编译时会显示变量未初始化,是一个随机值。

11、若变量已正确的定义为float类型,要通过输入函数scanf(“%f%f%f”,&a,&b,&c)给a赋值10,b赋值22,c赋值33,以下不正确的输入形式是()。

10  22  33

10.0,22.0,33.0

KEY:B

解答:空格、换行和制表符都可以用来分割,但是逗号不行。

12、若 a 是 float 型变量,b 是 unsigned 型变量,以下输入语句中合法的是()。

scanf("%6.2f%d",&a,&b);

scanf("%f%n",&a,&b);

scanf("%f%3o",&a,&b);

scanf("%f%f",&a,&b);

KEY:BC

解答:%n用于接受一个uint,代表到%n为止所输入的字符数,其本身不消耗字符。也就是说,此时的b不是命令行输入进去的,而是自动生成的!命令行输入一个float数,然后将这个数赋值给a,同时将这个数的字符数赋值给b。

而u、o、x分别代表10、8、16进制的无符号数输入。

13、两个线程并发执行以下代码,假设a是全局变量,那么以下输出___哪个是可能的?

int a=1;
void foo(){
    ++a;
    printf("%d",a);
}

3 2

2 3

3 3

2 2

KEY:ABCD

解答:选项D是,假设线程A先执行++a操作但没有写回到内存,这时线程B执行++a操作写回内存并printf,输出2,线程A继续执行,++a操作写回内存,a的值保持2,再printf。

这里关键有两点: 

  • 两个线程可随时被抢占 ;
  • ++a和printf不是原子指令,可随时被打断;特别注意函数printf,a作为参数压栈后,a再变化则不会影响输出(printf实际打印的是压栈的参数,是值拷贝的栈变量)。

14、vector和list的区别:

  • vector数据结构:vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进行随机存取,时间复杂度为o(1)。但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝;
  • list数据结构:list是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n)。但由于链表的特点,能高效地进行插入和删除。

在对它们的迭代器iterator进行讨论:

  • vector拥有一段连续的内存空间,能很好的支持随机存取,因此vector<int>::iterator支持“+”,“+=”,“<”,“[]”等操作符。
  • list的内存空间可以是不连续,它不支持随机访问,因此list<int>::iterator则不支持“+”、“+=”、“<”,“[]”等
  • vector<int>::iterator和list<int>::iterator都重载了“++”运算符。

总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;如果需要大量的插入和删除,而不关心随机存取,则应使用list。

15、代码生成阶段的主要任务是:

把高级语言翻译成机器语言

把高级语言翻译成汇编语言

把中间代码变换成依赖具体机器的目标代码

把汇编语言翻译成机器语言

KEY:C

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

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

参考文章:谈谈c语言程序如何变成可执行文件

16、以下程序的打印结果是()。

#include<iostream>
using namespace std;

void swap_int(int a , int b)
{
    int temp = a;
    a = b;
    b = temp;
}

void swap_str(char* a , char* b)
{
    char* temp = a;
    a = b;
    b = temp;
}

int main(void)
{
    int a = 10;
    int b = 5;
    char* str_a = "hello world";
    char* str_b = "world hello";

    swap_int(a , b);.
    swap_str(str_a , str_b);
    printf("%d %d %s %s\n",a,b,str_a,str_b);

    return 0;
}

KEY:10 5 hello world world hello

解答:要交换字符串,需要将两个char*的地址发送过去,而不是发送一个char的地址过去!其实两个交换函数都犯得相同的一个错误,在函数中形成了局部变量。正确的字符串交换的方法为:

void swap_str(char **a , char **b)
{
    char *temp = *a;
    *a = *b;
    *b = temp;
}

或者,还是使用引用:

void swap_str(char* &a , char* &b)
{
    char *temp = a;
    a = b;
    b = temp;
}

17、如下程序用于输出“Welcome to Huawei Test”,请指出其中的两处错误。

char * GetWelcome(void){
    char * pcWelcome;
    char * pcNewWelcome;
    pcWelcome="Welcome to Huawei Test";
    pcNewWelcome=(char *)malloc(strlen(pcWelcome));    //1
    if(NULL==pcNewWelcome){
        return NULL;        //2
    }
    strcpy(pcNewWelcome, pcWelcome);    //3
    return pcNewWelcome;            //4
}

KEY:1 3

解答:1处,正确形式是:pcNewWelcome=(char*)malloc(strlen(pcWelcome)*sizeof(char)) ,当然char的大小为1,形式上可以算是对了。但是逻辑上呢?strlen()统计字符个数,不含结尾符'\0',所以这样子分配会少一个字节。3处,由1处,既然新分配的空间少了一个字节,你用原来的来复制到新的里?能装下吗?不能。

同样看一下strlen和sizeof的区别:

char *a = "hello";
char b[] = "hello";
cout << sizeof(a) << ' ' << strlen(a) << endl;
cout << sizeof(b) << ' ' << strlen(b) << endl;

这个的答案是4 5 6 5。需要注意的是sizeof(a)判断的是一个指针的大小,不是字符串的大小!需要使用数组才行。

18、编译运行如下程序会出现什么结果?

#include <iostream>
using namespace std;

class A
{
    A()
    {
        printf("A()");
    }
};

void main()
{
    A a;
}

A()

编译错误

链接错误

以上都不对

KEY:B

解答:在类中,其成员的缺省的存取权限是私有的;而在结构体类型中,其成员的缺省的存取权限是公有的。也就是说,使用一个私有的构造函数进行初始化。

19、下面代码的输出是什么?

class A  
{  
public:  
    A()  {     }  
    ~A() {    cout<<"~A"<<endl;   }  
};  
   
class B:public A  
{  
    public:  
        B(A &a):_a(a)  
        {  
             
        }  
        ~B()  
        {  
            cout<<"~B"<<endl;  
        }  
    private:  
        A _a;  
    };  
       
int main(void)  
 {  
        A a;
        B b(a); 
}

KEY:~B ~A ~A ~A

解答:对象成员的构造函数、基类构造函数、派生类构造函数的调用顺序:先调用基类的构造函数,再调用对象成员的构造函数,最后执行派生类的构造函数。

  • 多个基类的条件下:取决于在类继承中说明的顺序,与它们在构造函数的初始化成员列表的先后顺序无关;
  • 多个对象成员的条件下:取决于它们在派生类中说明的顺序。

本题:A a;,调用一次构造函数。B b(a);,先基类的构造函数,再对象成员的构造函数,再派生类的构造函数。析构的时候,顺序正好相反。

20、关于CSingleLock,下面说法错误的是?

主要是同步多个线程对一个数据类的同时访问。

CSingleLock有RAII的好处。

CSingleLock对象需要有一个从CSyncObject派生的对象存在。

CSingleLock必须要全部显示的进行unLock操作

KEY:D

解答:RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。 RAII的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处: 不需要显式地释放资源。 采用这种方式,对象所需的资源在其生命期内始终保持有效。

CSingleLock主要是同步多个线程对一个数据类的同时访问。在创建CSingleLock对象时类对象会自动根据参数赋值,而且会lock,不用显式lock,即,只需创建对象就可lock。CSinglelock退出时自动调用析构函数,析构时自动unlock。

21、不考虑任何编译器优化(如:NRVO),下述代码的第10行会发生:

#include <stdio.h>                       //1

class B                                  //2
{                                        //3
};                                       //4

B func(const B& rhs){                    //5
    return rhs;                          //6
}                                        //7

int main(int argc, char **argv){         //8
    B b1,b2;                             //9
    b2=func(b1);                         //10
}                                        //11

一次默认构造函数,一次拷贝构造函数,一次析构函数,一次(拷贝赋值运算符)operator=

二次拷贝构造函数,一次析构函数

一次(拷贝赋值运算符)operator=,一次析构函数

一次拷贝构造函数,一次析构函数,一次(拷贝赋值运算符)operator=

KEY:D

解答:答案是D。但是次序并不是,正确的顺序是一次拷贝构造函数,一次(拷贝赋值运算符)operator= ,一次析构函数。

一次拷贝构造函数发生在func函数调用完成,返回B类型的对象时,因为返回的不是引用类型,所以会生成一个对象,不妨称为TEMP,将返回的对象通过拷贝构造函数复制给TEMP,同时由于拷贝构造函数的参数是const B&,rhs并不会在函数结束时候被析构,这时并不调用析构函数(也就是return这一句,由b&的引用类型,来生成TEMP);

赋值运算符在func函数执行完成后,将上面提到的TEMP,通过赋值运算符赋值给b2;

表达式的最后将临时对象TEMP进行析构。

22、下面程序的输出结果是:

char *p1= “123”, *p2 = “ABC”, str[50]= "xyz";
strcpy(str+2,strcat(p1,p2));
cout << str;

xyz123ABC

z123ABC

xy123ABC

出错

KEY:D

解答:首先,c++的内存分配地址有小到大分别是:动态内存区,包括栈和堆;代码内存区;静态内存区,包括全局变量,静态变量,只读变量(就是常量),按照定义的先后顺序分配地址;

本题中,p1,p2都是指针,是局部变量,在分配内存时会将其分配到栈区,且只会分配p本身所需要的内存空间,即p所指向的内容的地址大小的空间,并没有为字符串分配内存,所以*p1="123"和*p2="ABC"中 123和ABC都被分配在了文字常量区,既然是常量,大小就是不可变的,而strcat的第一个变量必须是可变的,所以程序会出错。

也就是说:

char* strcat(char *,const char*);    //第一个参数所指向的内容必须可以修改,可以赋值为在栈上分配的数组
strcat(p1,p2);        //试图修改p1的内容,p1指向文字常量区,其指向的内容无法修改

23、如果x=2014,下面函数的返回值是()。

int fun(unsigned int x)
{
     int n=0;
     while((x+1))
     {
         n++;
         x=x|(x+1);
     }
     return n;
}

KEY:C

解答:x&(x-1)统计1的个数,x|(x+1)统计0的个数。

2014对应的二进制为:0000 0000 000 0000 0000 0111 1101 1110

  • x|(x+1)的结束终止条件:直到变成全1的时候x+1就溢出为全0,循环结束;
  • x&(x-1)的结束终止条件:直到变成全0的时候,循环结束。

24、若有说明:int *p,m=5,n;以下正确的程序段是()。

p=&n;scanf("%d",&p);

p=&n;scanf("%d",*p)

scanf("%d",&n);*p=n;

p=&n;*p=m;

KEY:D

解答:scanf的一般格式下,放入的应该为地址,也就是说,正确的写法应该是:

int i;
scanf("%d", &i);

int* p=&i;
scanf("%d", p);

25、下列表达式正确的是:

9++

(x+y)++

c+++c+++c++

++(a-b--)

KEY:C

解答:a++表达式的本质是编译器翻译成a=a+1。

那么:A、9=9+1?;B、x+y=x+y+1? C++左值表达式不能有运算符;D、选项与B类似。也就是主要考的就是,C++的等号左边不能有运算符!

26、下面程序运行结果为()。

void main()
{
    char c=’a’;
 
    if ('a'<c<='z') 
        printf ("Low”);
    else 
        printf("UP");
}

LOW

UP

LOWUP

程序语法错误

KEY:A

解答:尽管我们都知道表示一个数是否处于一个范围之间时,这样使用时达不到效果的。但是,这句话本身是没有语法错误的。不要突然就脑子转不过来弯了。

27、下面代码会输出什么?()

class A{
    public:
        int m;
        void print()  { cout << "A\n"; }
};

A *p = 0;
p->print();

KEY:A

解答:初始化为NULL(或者0)的类指针可以安全的调用不涉及类成员变量的类成员函数而不出错,但是如果类成员函数中调用了类成员变量则会出错。

原因:调用成员函数的时候,函数地址是编译期间确定的,成员函数不通过对象指针去调用,对象指针仅仅作为参数传入函数然后去调用成员变量。而如果是虚函数,需要通过this去计算vptr,然后找到vtable,而this为NULL,那么就会报错。

28、C语言和C++的默认缺省大比较:

返回值方面

  • C中:如果函数未指定返回值类型,则默认为int;
  • c++中:如果一个函数没有返回值,返回值类型必须指定为void。

参数列表方面

  • C中:如果函数没有指定参数列表,则默认可以接受任意多个参数;
  • C++中:有严格的类型检测,没有参数列表的函数默认为void,不接受任意参数。

29、看以下代码:

A *pa = new A[10];
delete pa;

则类A的构造函数和析构函数分别执行了几次()。

KEY:10 1

解答:应该改成 delete[] pa;才对,这样会调用10次A的构造函数和10次A的析构函数。

但是这还没有完。如果A是简单类型,比如 int ,char ,double 或者结构体,那么动态创建简单类型的数组,是可以调用delete pa;这样来析构的,效果是和 delete[] pa的效果一样,不会报错。

但是如果,A是自己定义的一个类,那么动态创建了对象数组,用delete pa;就会使程序崩溃。因为动态创建一个对象的内存实际上比A要大,有一些附加的内存需要存放附加的信息。

30、若 ch 为 char 型变量,k 为 int 型变量(已知字符 a 的 ASCII 十进制代码为97),则以下程序段的执行结果是()。

char ch='a';
int k=12;
printf("%x,%o,", ch, ch, k);
printf("k=%%d\n", k);

因变量类型与格式描述符的类型不匹配,输出无定值

输出项与格式描述符个数不符,输出为零值或不定值

61,141,k=%d

61,141,k=%12

KEY:C

解答:若输出项多于格式描述符的个数,输出输出项中对应格式描述符的内容,剩余输出项则丢弃;若输出项少于格式描述符的个数,输出输出项中对应格式描述符的内容,缺少的输出项则输出不定值。

printf("%x,%o,", ch, ch, k);    //将ch以16进制输出为61,八进制为141,k参数被忽略。
printf("k=%%d\n", k);           // %是控制符,用 %% 表示输出一个百分号

31、switch后面的“表达式”,可以是int、char和枚举型中的一种,不能是float型变量;同时,case后面必须是“整型常量表达式”,表达式中不能包含变量。

注意:字符型常量也是属于整型常量表达式!

32、下列关于数组与指针的区别描述正确的是?

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。

用运算符sizeof 可以计算出数组的容量(字节数)

指针可以随时指向任意类型的内存块。

用运算符sizeof 可以计算出指针所指向内容的容量(字节数)

KEY:B

解答:A.堆上创建动态数组;B.sizeof(数组名)就是数组的容量;C.const指针不可以;D. char* str = "hello"; sizeof(str)不能计算出内容的容量,只是指针的容量。

33、下面哪种情况下,B不能隐式转换为A?

class B:public A{}

class A:public B{}

class B { operator A(); }

class A { A(const B&); }

KEY:B

解答:答案A,表示A是基类,B是派生类,向上级类型转换是隐式的,因为部分元素丢弃可以自动完成,向下转型是显式的因为不知道应该增加的值是什么。所以B不能。

答案C,转换函数(又称为类型转换函数)。

答案D,拷贝构造函数, B b = A 肯定是可以的。

转换函数(又称为类型转换函数)是类中定义的一个成员函数,其一般格式为:

类名::operator 转换后的类型 (){        //将“类名”类型转换为“转换后的类型”
    ...
}

其中:operator和“转换后的类型”一起构成转换函数名。该函数不能带有参数,也不能指定返回值类型。因为它的返回值类型就是“转换后的类型”。转换函数的作用就是将对象内的成员数据转换成某种特定的类型。

34、C++语言本身没有输入输出语句。说法是否正确?

正确

错误

KEY:A

解答:输入和输出并不是C++语言中的正式组成成分。C和C++本身都没有为输入和输出提供专门的语句结构。输入输出不是由C++本身定义的,而是在编译系统提供的I/O库中定义的。 C++的输出和输入是用“流”(stream)的方式实现的。

35、下面有关C++中为什么用模板类的原因,描述错误的是?

可用来创建动态增长和减小的数据结构

它是类型无关的,因此具有很高的可复用性

它运行时检查数据类型,保证了类型安全

它是平台无关的,可移植性

KEY:C

解答:(1)可用来创建动态增长和减小的数据结构 (2)它是类型无关的,因此具有很高的可复用性。 (3)它在编译时而不是运行时检查数据类型,保证了类型安全 (4)它是平台无关的,可移植性 (5)可用于基本数据类型。

36、以下哪些做法是不正确或者应该极力避免的:【多选】( )

构造函数声明为虚函数

派生关系中的基类析构函数声明为虚函数

构造函数中调用虚函数

析构函数中调用虚函数

KEY:ACD

解答:构造函数和析构函数都不能调用虚函数:

先析构子类再析构父类,如果父类析构函数有虚函数,会导致调用子类的已经析构的内容。先构造父亲类再构造子类,如果父类构造函数有虚函数,会导致调用子类还没构造的内容。

构造函数不能为虚函数:

虚函数对应一个vtable,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。

基类的析构函数要定义为虚函数:

我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。也就是说,基类的虚构函数如果不声明成为虚函数,那么销毁派生类时有可能造成资源泄漏,出现指向基类的派生类指针在销毁时,只能销毁基类对象而无法销毁派生类对象的现象。

参考文章:析构函数可以为virtual,构造函数则不能。原因?为什么构造函数不能为虚函数,而析构函数可以为虚函数

37、关于以下代码,哪个说法是正确的?

myClass::foo(){
    delete this;
}

void func(){
    myClass *a = new myClass();
    a->foo();
}

它会引起栈溢出

都不正确

它不能编译

它会引起段错误

KEY:B

解答:在类的成员函数中能不能调用delete this?答案是肯定的,能调用。那么这个对象在调用release方法后,还能进行其他操作,如调用该对象的其他方法么?答案仍然是肯定 的,调用release之后还能调用其他的方法,但是有个前提:被调用的方法不涉及这个对象的数据成员和虚函数。

根本原因在于delete操作符的功能和类对象的内存模型。当一个类对象声明时,系统会为其分配内存空间。在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含代码内容,类的成员函数单独放在代码段中。在调用成员函数时,隐含传递一个this指针,让成员函数知道当前是哪个对象在调用它。当调用delete this时,类对象的内存空间被释放。在delete this之后进行的其他任何函数调用,只要不涉及到this指针的内容,都能够正常运行。一旦涉及到this指针,如操作数据成员,调用虚函数等,就会出现不可预期的问题。

大致明白在成员函数中调用delete this会发生什么之后,再来看看另一个问题,如果在类的析构函数中调用delete this,会发生什么?实验告诉我们,会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存” (来自effective c++)。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。

38、下列格式控制符,既可以用于输入,又可以用于输出的是(  )。

setbase

setfill

setprecision

setw

KEY:A

解答:C++提供了许多的预定义格式控制函数,可直接用于控制输入/输出数据的格式,如下表:

C++中预定义的格式控制函数
格式控制函数名 功能 适用于输入/输出流
dec 设置为十进制 IO
hex 设置为十六进制 IO
oct 设置为八进制 IO
ws 提取空白字符 I
endl 插入一个换行符 O
flush 刷新流 O
resetioflags(long) 取消指定的标志 IO
setioflags(long) 设置指定的标志 IO
setbase(int) 将数字转换为 n 进制 IO
setfill(int) 设置填充字符 O
setprecision(int) 设置实数的精度 O
setw(int) 设置宽度 O
ends 插入一个表示字符串结束的字符  

这些格式控制函数的标头都是:<iomanip>,std命令空间。

39、请问运行Test 函数会有什么样的结果?

char* getmemory(void)
{
    char p[]="hello world";
    return p;
}

void test(void)
{
    char *str=NULL;
    str=getmemory();
    printf(str);
}

出错

输出"hello world"

输出空""

输出乱码

KEY:D

解答:getmemory,返回的指针,是内部变量(动态数据区),调用之后会被回收。 所以输出是不确定的。

假如修改为:

char* getmemory(void)
{
	char *p = "hello world";
	return p;
}

void test(void)
{
	char *str = NULL;
	str = getmemory();
	printf(str);
}

就可以输出"hello world"啦!

返回“字符串常量的指针”和“返回数组名”的区别在于,一个返回常量区的地址,一个返回栈内存(动态数据区)的地址。动态数据区的内容出了getmemory()函数范围就被释放了,但常量区则不会被释放。

40、注意区分一下指针函数和函数指针:

  • char *p (): 是指针函数,函数;
  • char (*p) ():是函数指针,指针。

41、若调用fputc函数输出字符成功,则其返回值是()。

EOF

1

0

输出的字符

KEY:D

解答:fputc函数有一个返回值,如写入成功则返回写入的字符, 否则返回一个EOF。可用此来判断写入是否成功。

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

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

void main()
{
    char a[80] = "AB", b[80] = "LMNP", i=0;
    strcat(a, b);
    while (a[i] != '\0')
    {
        i++;
        b[i] = a[i];
    }
    puts(b);
}

KEY:LBLMNP

解答:主要是介绍一下C++string类的几个函数:

string &append(const char *s);            //把c类型字符串s连接到当前字符串结尾
string &append(const char *s,int n);        //把c类型字符串s的前n个字符连接到当前字符串结尾

int compare(const string &s) const;        //比较当前字符串和s的大小
int compare(int pos, int n,const string &s)const;        //比较当前字符串从pos开始的n个字符组成的字符串与s的大小

string substr(int pos = 0,int n = npos) const;    //返回pos开始的n个字符组成的字符串

void swap(string &s2);        //交换当前字符串与s2的值

int find(char c, int pos = 0) const;        //从pos开始查找字符c在当前字符串的位置
int find(const char *s, int pos = 0) const;        //从pos开始查找字符串s在当前串中的位置
int find(const char *s, int pos, int n) const;        //从pos开始查找字符串s中前n个字符在当前串中的位置

int rfind(char c, int pos = npos) const;        //从pos开始从后向前查找字符c在当前串中的位置
int rfind(const char *s, int pos = npos) const;
int rfind(const char *s, int pos, int n = npos) const;

string &replace(int p0, int n0,const char *s);    //删除从p0开始的n0个字符,然后在p0处插入串s
string &replace(int p0, int n0,const char *s, int n);    //删除p0开始的n0个字符,然后在p0处插入字符串s的前n个字符

string &insert(int p0, const char *s);        //在p0位置插入字符串s
string &insert(int p0, const char *s, int n);    //在p0位置插入字符串s中pos开始的前n个字符

string &erase(int pos = 0, int n = npos);        //删除pos开始的n个字符,返回修改后的字符串

再介绍一下C语言标准库<string.h>的几个函数:

void *memchr(const void *str, int c, size_t n);        //在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字符)的位置。

int memcmp(const void *str1, const void *str2, size_t n);        //把 str1 和 str2 的前 n 个字节进行比较。

void *memcpy(void *dest, const void *src, size_t n);        //从 src 复制 n 个字符到 dest。

void *memmove(void *dest, const void *src, size_t n);        //另一个用于从 src 复制 n 个字符到 dest 的函数。

void *memset(void *str, int c, size_t n);        //复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。

char *strchr(const char *str, int c);        //在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。
char *strrchr(const char *str, int c);        //在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。

char *strcat(char *dest, const char *src);        //把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
char *strncat(char *dest, const char *src, size_t n);        //把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。

int strcmp(const char *str1, const char *str2);        //把 str1 所指向的字符串和 str2 所指向的字符串进行比较。
int strncmp(const char *str1, const char *str2, size_t n);        //把 str1 和 str2 进行比较,最多比较前 n 个字节。

char *strcpy(char *dest, const char *src);        //把 src 所指向的字符串复制到 dest。
char *strncpy(char *dest, const char *src, size_t n);        //把 src 所指向的字符串复制到 dest,最多复制 n 个字符。

size_t strlen(const char *str);        //计算字符串 str 的长度,直到空结束字符,但不包括空结束字符

43、在16位IBM-PC上使用C语言,若有如下定义:

struct data 
{ 
    int i; 
    char ch;
    double f;
} b;

则结构变量b占用内存的字节数是()。

1

2

8

11

KEY:D

解答:16位下,int 2字节,char 1字节,double 8字节。这个版本的编译器太旧了,没有内存对齐。

44、以下描述错误的是:

函数的形参在函数未调用时不分配存贮空间

若函数的定义出现在主函数之前且仅被主函数使用,则可以不必再说明

若一个函数(非主函数)没有return语句,返回类型是void

一般来说,函数的形参和实参的类型应该一致

KEY:C

解答:构造函数和析构函数都没有返回类型,也没有return语句。

45、当一个类A 中没有声明任何成员变量与成员函数,这时sizeof(A)的值是多少?

1

0

4

运行时错误

KEY:A

解答:深度探索c++对象模型中是这样说的:那是被编译器插进去的一个char ,使得这个class的不同实体(object)在内存中配置独一无二的地址。也就是说这个char是用来标识类的不同对象的。

再深究一下:

class A {
	
};                            //sizeof(A)=1

class A {
	void fun() {};
};                            //sizeof(A)=1

class A {
	virtual void fun() {};
};                            //sizeof(A)=4

46、以下哪项不属于STL container?(    )

stack

queue

multimap

string

KEY:D

解答:STL container分为三大类:

  • 序列容器:动态数组vector,双端队列deque(本质是动态数组加索引),链表list。
  • 关联容器:set,map,multiset,multimap,bitset(叫bit_array更合适)。
  • 容器适配器:stack,queue,priority_queue。

47、执行"int x=1;int y=~x;"语句后,y的值为?

1

0

-1

-2

KEY:D

解答:x的补码为00000000 00000000 00000000 00000001,则y为11111111 11111111 11111111 11111110。化为源码就是:10000000 00000000 00000000 00000010,也就是-2。

48、设有如下说明:

typedef struct ST
{
    long a;
    int  b;
    char  c[2];
} NEW;

则下面叙述中正确的是(  ) 。

以上的说明形式非法

ST是一个结构体类型

NEW是一个结构体类型

NEW是一个结构体变量

KEY:C

解答:经过typedef定义之后,struct ST和NEW等价。

49、this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。静态成员函数不拥有this指针。

可以这么理解:对于一个类myclass,this指针的类型应该是myclass *,而对其的解引用*this就应该是一个myclass类型的变量。也就是说,this指针指向该类的实例对象,而不是该类本身。

50、以下 C 语言指令:

int a[5] = { 1,3,5,7,9 };
int *p = (int *)(&a + 1);
printf("%d, %d", *(a + 1) , *(p - 1));

运行结果是什么?

2,1

3,1

3,9

运行时崩溃

KEY:C

解答:区分一下&a、a、&a[0]:

  • &a和a做右值时的区别:&a是整个数组的首地址,而a是数组首元素的首地址。这两个在数字上是相等的,但是意义不相同。意义不相同会导致他们在参与运算的时候有不同的表现;
  • a和&a[0]做右值时意义和数值完全相同,完全可以互相替代。

&a表示一个指向大小为5数组的指针,那么(&a+1)就是表示一个指向大小为5的下一个数组的指针,也就是数组a最后一个元素的下一个位置,-1则指向a中最后一个元素;a是保存数组的首地址,*(a+1)保存的是数组第二个地址的值,故为3.p是保存数组a最后一个位置的下一个位置的地址,*p是指向数组a最后一个位置的下一个位置,值为-1,*(p-1)是指向数组a最后一个位置,值为9。

猜你喜欢

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