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;
}
答案选择D,用VS2017实测是输出烫烫烫。
做题时候想的是没有结束符,但看评论区好像并没有人提到?
参考解析推荐最高回复
函数char *myString()中没有使用new或者malloc分配内存,所有buffer数组的内存区域在栈区,随着char *myString()的结束,栈区内存释放,字符数组也就不存在了,所以会产生野指针,输出结果未知
比较认同的解释:
在c语言层面上,地址空间的值随时可能被覆盖。被覆盖的原因也是这玩意儿在栈空间,而且栈底指针已经向高地址偏移了,那么这片空间在下一个函数执行时,如果有局部变量,将会对应到下一个函数的局部变量上面。下一个函数是啥?printf,所以关键是看printf有没有申请局部变量,并且修改局部变量的值。被调函数返回局部数组是允许的。
返回局部栈变量的地址并引用它,这叫做undefined behavior。语言层面对这种东西不做规定,所以是一个实现相关问题。因此,选择D是自然的结果。
2.以下选项如果可以初始化正确,那么就会初始化正确,那么以下哪种语法在C++中初始化以后编译会错误?其中X为一C++类【const用法】
答案【CD】
const在*前面 都表示*x不可修改
C中*前出现两个const,g++编译器提示duplicate’const’,语法错误
D中const在*后面,表示指针不可修改,需要初始化。const类型的指针声明时必须初始化
p.s*X* const x放在类中当一个成员,是没有错误的。*
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.四种转换方式
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 语言常量中,错误的是()。
long int a = 2L; // 定义一个长整型变量,变量名为a,并进行初始化。初始化的时候将2转化为长整型后再赋给变量a
int a = 2L; // 定义一个整型变量a,并进行初始化。初始化时2L表示先将2转化为长整型,但由于a是int类型,所以之后又转化为int类型赋值给变量a
10.C++中为什么用模板类的原因
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++)的值是?【自增】
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.构造函数初始化时必须采用初始化列表一共有三种情况,
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)); //输出2,5
解析:
&a+1 //偏移一个数组
a+1 //偏移一个int
ptr-1 //偏移一个int
24.缺省构造函数,拷贝构造函数,拷贝赋值函数,以及析构函数这四种成员函数被称作特殊的成员函数。这4种成员函数不能被继承。
赋值运算符重载函数 ” 不是不能被派生类继承,而是被派生类的默认 “ 赋值运算符重载函数 ” 隐藏了。
c++11里可以非默认构造函数可以继承了
25.abs函数相关
num为0或正数时,函数返回num值;
当num为最小的负数时(即0x80000000),由于正数里int类型32位表示不了这个数的绝对值,所以依然返回该负数。
一个补充:
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.引用是别名,不是指针,引用参数时候不能用->,可以用”.”