牛客网错题整理(C/C++基础知识回顾)(1)

1.如下代码输出结果是什么【生存周期问题】

#include
char *myString()
{
    char buffer[6] = {0};
    char *s = "Hello World!";
    for (int i = 0; i < sizeof(buffer) - 1; i++)
    {
        buffer[i] = *(s + i);
    }
    return buffer;
}
int main(int argc, char **argv)
{
    printf("%s\n", myString());
    return 0;
}
  • A Hello
  • B Hello World!
  • C Well
  • D 以上全部不正确

答案选择D,用VS2017实测是输出烫烫烫。
做题时候想的是没有结束符,但看评论区好像并没有人提到?
参考解析推荐最高回复

函数char *myString()中没有使用new或者malloc分配内存,所有buffer数组的内存区域在栈区,随着char *myString()的结束,栈区内存释放,字符数组也就不存在了,所以会产生野指针,输出结果未知

比较认同的解释:

在c语言层面上,地址空间的值随时可能被覆盖。被覆盖的原因也是这玩意儿在栈空间,而且栈底指针已经向高地址偏移了,那么这片空间在下一个函数执行时,如果有局部变量,将会对应到下一个函数的局部变量上面。下一个函数是啥?printf,所以关键是看printf有没有申请局部变量,并且修改局部变量的值。被调函数返回局部数组是允许的。

返回局部栈变量的地址并引用它,这叫做undefined behavior。语言层面对这种东西不做规定,所以是一个实现相关问题。因此,选择D是自然的结果。

2.以下选项如果可以初始化正确,那么就会初始化正确,那么以下哪种语法在C++中初始化以后编译会错误?其中X为一C++类【const用法】

  • A const X* x
  • B X const* x
  • C const X const* x
  • D X* const x

答案【CD】
const在*前面 都表示*x不可修改
C中*前出现两个const,g++编译器提示duplicate’const’,语法错误
D中const在*后面,表示指针不可修改,需要初始化。const类型的指针声明时必须初始化
p.s*X* const x放在类中当一个成员,是没有错误的。*



const补充

const 限定一个对象为只读属性。
先从一级指针说起吧:
(1)const char p 限定变量p为只读。这样如p=2这样的赋值操作就是错误的。
(2)const char *p p为一个指向char类型的指针,const只限定p指向的对象为只读。这样,p=&a或 p++等操作都是合法的,但如*p=4这样的操作就错了,因为企图改写这个已经被限定为只读属性的对象。
(3)char *const p 限定此指针为只读,这样p=&a或 p++等操作都是不合法的。而*p=3这样的操作合法,因为并没有限定其最终对象为只读。
(4)const char *const p 两者皆限定为只读,不能改写。
有了以上的对比,再来看二级指针问题:
(1)const char **p p为一个指向指针的指针,const限定其最终对象为只读,显然这最终对象也是为char类型的变量。故像**p=3这样的赋值是错误的,而像*p=? p++这样的操作合法。
(2)const char * const *p 限定最终对象和 p指向的指针为只读。这样 *p=?的操作也是错的。
(3)const char * const * const p 全部限定为只读,都不可以改写。
总结一下就是看const后跟的是什么,*为对象,变量为指针(针对const指针情况)


3.字符串作为常量值,存在内存常量区。

char *p="hello"; return p == "hello"; //返回1

进一步:

char *p1 = "hello";char *p2= "hello"; //p1 == p2
//但是&p1!=&p2 

4.四种转换方式

  • reinterpret_cast:
    一个指针转化为其他类型的指针时,不做类型检测,操作结果是一个指针指向另一个指针的值的二进制拷贝;
  • static_cast:
    允许执行隐式转换和相反的转换操作,父类转换为子类是强制转换Son *son=static_cast(father),而子类转换为父类就是隐式转换;
  • dynamic_cast:
    用于对象的指针和引用,当用于多态类型转换时,允许隐式转换及相反的过程中,与static_cast的不同之处在于,在相反的转换过程中,dynamic_cast会检测操作的有效性,如果返回的不是被 请求的 有效完整对象,则返回null,反之返回这个有效的对象,如果是引用返回无效时则会抛出bad_cast异常;
    补充:
    dynamic_cast<>用于C++类继承多态间的转换,分为:
    1.子类向基类的向上转型(Up Cast)
    2.基类向子类的向下转型(Down Cast)
    其中向上转型不需要借助任何特殊的方法,只需用将子类的指针或引用赋给基类的指针或引用即可,dynamic_cast向上转型其总是肯定成功的。dynamic_cast在将父类cast到子类时,父类必须要有虚函数。因为dynamic_cast运行时需要检查RTTI信息。只有带虚函数的类运行时才会检查RTTI。
  • const_cast:
    这个转换操作会操纵传递对象的const属性,或者设置或者移除该属性。
    dynamic_cast 可以做任意不相关的类之间的转换; 而static_cast 需要两个对象之间是有联系的

5.【字符数组的输入】

int b;
char c[10];
//以下两种写法都正确
scanf("%d%s",&b,&c);
scanf("%d%s",&b,c);

一直认为数组是不加&的。参考解答

对于一个在栈上分配的数组,且在创建的代码块中进行访问的话 “c” 实际上有两种含义 : 1. 一个指向十个char类型元素的数组 2. 一个char* 类型的指针 对于1而言 ,在同代码块中 sizeof(c) 输出 10,而不是4或者8 那么何时会是 1 何时 会是2呢? 实际上 2是 1的一种语法糖,是语言设计者为了方便而放下的一个空子,大家可以尝试一下二维数组,这个语法糖就不成立了,因为作者没有留下这种语法糖给程序员。 这就是所谓的上下文语义(编译器读的语义): scanf(“%s”, c); //这里c是一个char* 类型的指针,编译器相信程序员将它指向了一块内存块 scanf(“%s”, &c); //这里c是一个指向十个char元素的数组的指针,这种才是最正统的用法

6.指针函数VS函数指针
指针函数:当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。指针函数返回类型是某一类型的指针。

int* func(int variable);//指针函数

函数指针:指向函数的指针包含了函数的地址,可以通过它来调用函数。

int (*func)(int variable); //函数指针

7.下列代码的输出为:【虚函数】

class CParent 
{
    public: virtual void Intro()
    {
        printf( "I'm a Parent, " ); Hobby();
    }
    virtual void Hobby()
    {
        printf( "I like football!" );
    }
}; 
class CChild : public CParent { 
    public: virtual void Intro()
    {
        printf( "I'm a Child, " ); Hobby();
    }
    virtual void Hobby()
    {
       printf( "I like basketball!\n" );
    }
}; 
int main( void )
{
    CChild *pChild = new CChild(); 
    CParent *pParent = (CParent *) pChild; 
    pParent->Intro(); 
    return(0);
}

输出:

I’m a Child, I like basketball!

解析:CParent *pParent = (CParent *) pChild; 指针指向的内容并没有改变,pChild指针和pParent指针都指向了CChild类,也就是子类,都是CChild的对象。Intro是虚函数,因此调用的是子类里的方法,同理Hobby.

8.写出下列程序在X86上的运行结果【:的作用】

struct mybitfields
{
    unsigned short a : 4;
    unsigned short b : 5;
    unsigned short c : 7;
} test

void main(void)
{
    int i;
    test.a = 2;
    test.b = 3;
    test.c = 0;

    i = *((short *)&test);
    printf("%d\n", i);
}

答案:50
解析:冒号的作用是分配几位空间(int a:5 是分配int中的5位),在这里,给成员a分配4位空间,b5位,c7位,一共十六位,两个字节,也就是short型的数据大小。x86小端模式,高位在前。
c 0000000 b 00011 a 0010

在执行i=((short )&test); 时,取从地址&test开始两个字节(short占两个字节)的内容转化为short型数据,即为0x0032,再转为int型为0x00000032,即50

9.以下所列的 C 语言常量中,错误的是()。

  • A OxFF
  • B 1.2e0.5
  • C 2L
  • D ‘\72’
    -
    答案【B】
    解析:科学计数法,e后面一定是整数。2L表示2是长整型
long int a = 2L;  // 定义一个长整型变量,变量名为a,并进行初始化。初始化的时候将2转化为长整型后再赋给变量a

int a = 2L;  // 定义一个整型变量a,并进行初始化。初始化时2L表示先将2转化为长整型,但由于a是int类型,所以之后又转化为int类型赋值给变量a

10.C++中为什么用模板类的原因

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

11.阅读如下程序,该程序的执行结果? 【虚函数】

#include "stdio.h"
class Base
{
public:
    Base()
    {
        Init();
    }
    virtual void Init()
    {
        printf("Base Init\n");
    }
    void func()
    {
        printf("Base func\n");
    }
};
class Derived: public Base
{
public:
    virtual void Init()
    {
        printf("Derived Init\n");
    }
    void func()
    {
        printf("Derived func\n");
    }
};

int main()
{
    Derived d;
    ((Base *)&d)->func();

    return 0;
}

答案:

Base Init
Base func

解析:Derived d,调用基类构造函数,从而调用Init(此时是Base基类的Init),调用func,由于不是虚函数,不由对象决定,因此仍然是父类的func,输出Base func.

12.int a=5,则 ++(a++)的值是?【自增】

  • A 5
  • B 6
  • C 7
  • D 编译出错
    -
    答案【D】
    解析:自增运算,它只能用于一个变量,即变量值自增1, 不能用于表达式。

13.

#pragma pack(2)
class BU
{
    int number;//4
    union UBffer
    {
        char buffer[13];
        int number;
    }ubuf; //由占用空间最大的成员决定,并且需要对齐。
    //paragma pack(2),因此补齐到14即可,否则需要16,对齐int的4字节
    void foo(){} //0
    typedef char*(*f)(void*); //0
    enum{hdd,ssd,blueray}disk;//enum和int占用空间相同,为4
}bu;

sizeof(bu) == 22
解析见注释。

14.有一个100*90的稀疏矩阵,非0元素有10个,设每个整型数占2字节,则用三元组表示该矩阵时,所需的字节数是(66

每个元素要用行号,列号,元素值来表示,在用三元组表示稀疏矩阵,还要三个成员来记住,矩阵的行数列数,总的元素数,所以所需的字节数是10*(1+1+1)*2+3*2=66

15.类的sizeof总结

- //空类:
class CBase
{ };
空类也会被实例化,编译器会给空类隐含的添加一个字节,sizeof()的结果是1。

- //这个也是空类
class Base
{
public:
Base();
~Base();

- 非空类
class CBase
{
public:
CBase(void); //其他成员函数不归入sizeof统计
virtual ~CBase(void); //虚析构函数 virtual ~Base() 的指针占4子字节
//实际上是一个指向虚函数表的指针,所以sizeof为4
private:
int a; //32位机器上,32 / 8 = 4 字节
char *p; // 指针,4 字节
};//一共 4 + 4 + 4 = 12 字节

- 有static且有继承的非空类
class Derive:public Base //Derive类首先要具有Base类的部分,也就是占12字节
{
public:
Derive():Base(){};
~Derive(){};
private:
static int st; //非实例独占 , static int st;不归入sizeof统计
int d; //占4字节
char *p; //4字节指针

}; //12 + 8 = 20 字节

16.预定义标识符可以做为用户标识符。
预定义标识符:define scanf printf include

17.假定Qiniuome是一个类,执行下面这些语句之后,内存里创建了几个Qiniuome对象。 【5个】

Qiniuome a(); //函数声明
//并不是调用默认构造函数,Qiniuome()才是。
Qiniuome b(2); //1
Qiniuome c[3]; //3
Qiniuome &ra = b; //引用(别名)
Qiniuome *pA = c; //指针
Qiniuome *p = new Qiniuome(4); //1

18.以下程序输出是__

#include  
using namespace std; 
int main(void) 
{ 
    const int a = 10; 
    //至于const a的问题,只是限定了不能对a进行赋值
    //a = 10会报错(等号左边必须为可修改的左值)
    int * p = (int *)(&a); 
    *p = 20; 
    cout<<"a = "<", *p = "<<*p<return 0; 
} 

其实这道题真正的答案是不确定的,他与编译器有关,在C++标准中并没有强制规定将一个const 常量转换为一个非const后应该怎么办,这个是交给编译器设计者来决定的,以VS为例子,如果建立的是.c文件,也就是在纯c的环境下编译该代码,输出的结果将会是20 20,但是如果是C++则是10,20。它真正的处理原理如下,a作用域下在C++中所有的常量a在预编译阶段就已经做了类似于宏替换的工作,所以输出的是它的常量值,但是如果你进去调试就会发现变量a的地址与p地址相同,且p指向的地址的内容已经修改为了20,也就是说其实我们对这部分地址的内容已经做出了修改,所以*p输出的是20。但是在纯C的环境下,它并没有类似于宏替换的那一步,所以它输出的就是修改后的值。

19.构造函数初始化时必须采用初始化列表一共有三种情况,

  1. 需要初始化的数据成员是对象(继承时调用基类构造函数)
  2. 需要初始化const修饰的类成员
  3. 需要初始化引用成员数据

20.以下说法正确的是

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

结果正确,即使会溢出(可以举极端例子说明)
需要注意:

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

21.二叉树不是树的特殊情形,其与度数为二的有序树也不同。在有序树中,若节点只有一个孩子,则无左右孩子之分,而二叉树则不同。

22.无法保证用先序和后序唯一确定一棵二叉树,是因为无法保证区分左右孩子。比如根节点缺失左子树或右子树这样两种不同的拓扑结构,其先序和后序遍历很可能是完全一样的。
对于搜索树来说,只要知道前序遍历就能还原了,第一个是根结点,之后的连续K个小于根节点的为左子树,后面的都是右子树,然后递归
如果二叉树是真二叉树,那么树中各节点有0个或2个孩子,这时就可以确定左右孩子次序,这时先序和后序可以唯一确定一棵二叉树。

23.理解“数组的地址”和“数组首元素的地址”

int a[5];
int *ptr = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1)); //输出25

解析:
&a+1 //偏移一个数组
a+1 //偏移一个int
ptr-1 //偏移一个int

24.缺省构造函数,拷贝构造函数,拷贝赋值函数,以及析构函数这四种成员函数被称作特殊的成员函数。这4种成员函数不能被继承。
赋值运算符重载函数 ” 不是不能被派生类继承,而是被派生类的默认 “ 赋值运算符重载函数 ” 隐藏了。
c++11里可以非默认构造函数可以继承了

25.abs函数相关
num为0或正数时,函数返回num值;
当num为最小的负数时(即0x80000000),由于正数里int类型32位表示不了这个数的绝对值,所以依然返回该负数。
一个补充:牛客网错题整理(C/C++基础知识回顾)(1)_第1张图片

26.该程序的执行结果


#include "stdio.h" 
class A{
public:    
printf("A test\n");    
}
};
class B: public A{
public:    
void func()    {        
Test();    
}    
virtual void Test()    {        
printf("B test\n");    
}
};
class C: public B{
public:    
virtual void Test()    {        
printf("C test\n");    
}
};
void main(){    
C c;    
((B *)(&c))->func();    // C test
((B)c).func();    //B test
}

评论区解析众说纷纭.,挑了一个比较认同的回答

((B)c).func();这句大家都懂,无非就是静态调用嘛(要启用动态调用需要由基类的指针或引用来发起,而且调用的函数得是虚函数);既然调用前c对象被强制转换为B类型,那么调用的自然是B类中的对应函数了。
对于:((B*)(&c))->func(); 首先,func函数在类B中并不是虚函数,所以语句中调用的func是B类中的函数;其次,在func中调用了虚函数, 也就是此时发生了动态调用(调用func是是静态的),相当于在B类的func中发生了如下调用:((B *)(&c))->Test();满足动态调用的要求,调用的是C类的test函数。

27.引用是别名,不是指针,引用参数时候不能用->,可以用”.”

你可能感兴趣的:(基础知识回顾)