C/C++错题集

1、在成员函数中调用delete this会发生什么?

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

myClass::foo(){
    delete this;
}
..
void func(){
    myClass *a = new myClass();
    a->foo();
}

它会引起栈溢出
都不正确
它不能编译
它会引起段错误

答案:都不正确。
它能编译,在语法上没问题。
它不会引起段错误,因为没有看到越界访问的代码。
它不会引起栈溢出,因为没有无限循环调用。

  • 在类的成员函数中能不能调用delete this?
    能调用,但要求是delete之后,后续的程序不能再访问虚函数或成员数据变量。

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

  • 为什么这里foo里delete了后,当func退出的时候,没有导致第二次delete呢?
    当在foo里delete了后,就已经调用了析构函数,所以后面当func出作用域的时候,不会再次析构,所以程序运行正常。

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

2、new一个对象数组返回什么?父类指针指向子类对象的情况下delet父类指针会发生什么?

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

#include<stdio.h>
  
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????
  • new[]返回的是对象数组的首个对象指针,而删除需要使用运算符delete[]

  • delete为什么没有调用到派生类的析构函数?
    在C++中,析构函数的作用是:当一个对象被销毁时,调用析构函数对类对象和对象成员进行释放内存资源。
    当我们定义一个指向派生类类型对象指针时,构造函数按照从基类到派生类的顺序被调用,但是当删除指向派生类的基类指针时,派生类的析构函数没有被调用,只是调用了基类的析构函数,此时派生类将会导致内存泄漏
    我们需要将基类的析构函数声明为虚函数,此时在调用析构函数的时候是根据ptr指向的具体类型来调用析构函数,此时会调用派生类的析构函数。

  • 基类指针指向派生类对象时,调用delete要注意转换类型,或声明虚析构函数。

  • delete[] 是对数组的执行,delete是对单个对象执行,能不能delete arr[0]呢?
    不能。

3、在C++程序中,对象之间的相互通信通过( )

答案:调用成员函数实现。
对象属于某个已知的类,是类的实例,对象之间通信实际上就是通过函数传递信息,封装是把数据和操作结合在一起,继承是对于类的方法的改变和补充,重载是多态性之一。

4、假定x和y为double型,则表达式x=2,y=x+3/2的值是

答案:3.000000
为什么不是3.500000?因为3/2返回的是整型数1

5、printf的格式符号

以下程序的运行结果是()

int main(void)
{
printf("%s , %5.3s\n","computer","computer");
return 0}

computer , puter
computer , com
computer , computer
computer , compu.ter

参考答案:
%5.3s 输出占5列,但只取字符串中左端3个字符。这3个字符输出在5列的右侧,左补空格。
所以输出前三个字符"com",答案是B

6、浮点数(1e20)不支持加法交换律、加法结合律,对吗?

正确。
float类型不支持加法交换 律、结合结合律。
比如(1e20+(-1e20))+3.14=3.14,前两项很明显得0,所以结果为3.14。
但如果交换顺序(1e20+((-1e20)+3.14)),很明显后两项里3.14比-1e20的绝对值小很多,后两项结果为-1e20,最终整个式子的结果为0。

7、多继承的情况下,子类对象的内存结构

下列等式正确的是()?

class A{virtual ~A()};
class B{virtual ~B()};
class C:public A, public B{};
C* pC = new C();
A* pA = (A*)pC;
B* pB = (B*)pC;

pA==pC&&pB!=pC
pA==pC&&pB==pC
pA!=pC&&pB!=pC
pA!=pC&&pB==pC

答案:pA==pC&&pB!=pC
在多继承中,派生类和被继承的第一个基类的地址相同,和第二个基类的地址不同,故选A。

8、printf的格式化符号

下面程序段执行后的输出结果是()(□表示一个空格)。

int a=3366printf("│%-08d│",a)

【解释】%-08d表示输出占8个空格的位置,-表示左对齐,+右对齐,0没有意义,所以正确的答案是│3366□□□□│。

9、以下数字在表示为double(8字节的双精度浮点数)时存在舍入误差的有()。

2的平方根
10的30次方(提示:尾数位)
0.1(提示:小数部分能否)
0.5
100

参考答案:这道题的答案应该是ABC

8字节的共64位,按照标准的浮点数表示方法,应该是1位符号位11位指数位52位尾数位(第1个不为0到最后一个不为0)

  • A. 2的平方根,本身就是无限小数,因此肯定有舍入误差

  • B. 10的30次方,对于 B(2^90 < B < 2^100)来说,指数位是够了,但是尾数位会不会够呢? B = 230*530 也就是说将B表示成二进制后,其后30位全为0,从其第一个不为0到最后一个不为0的二进制表示位中,至少需要90-30 = 60位来存储,而其尾数位只有52位,明显超出了double的数据存储范围,故有舍入误差

  • C. 整数部分=0,小数部分=0.1,由于小数部分不能用2的负整数次方来表示,因此有误差

  • D. 0.5 = 2^(-1),因此没有误差

  • E. 100的二进制表示是:0110 0100,因此没有误差

  • 小技巧:判断小数部分能不能使用2-n来表示,n为正整数。若能则说明没有舍入误差。

10、如果downcast是安全的(也就是,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果downcast不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。这个是指C++里的()

dynamic_cast
reinterpret_cast
static_cast
const_cast

c++的cast
是借用的冶金中铸造注模的概念,把液态的金属倒入成型的模范这内个过程叫cast。编程中把一段数据装容成某个数据类型,让数据具有某个类型的过程叫做cast。
比如让4个字节变成一个int类型,把int变成4个char这种过程。
基本上和“类型转换”同义,不过cast在c++语言中是从对象封装的视角看这个动作。 所以有动态cast,静态cast等多种cast。

答案:dynamic_cast

  • dynamic_cast将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理, 即会作一定的判断。 对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针; 对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。
  • reinterpret_cast这个转换是最“不安全”的,两个没有任何关系的类指针之间转换都可以用这个转换实现。
  • static_cast静态转换是最接近于C风格转换,很多时候都需要程序员自身去判断转换是否安全。
  • const_cast这个转换好理解,可以将常量转成非常量
11、能实现删除文件功能的语句是( )。
ofstream fs("date.dat", ios::trunc );
ifstream fs("date.dat", ios::trunc );
ofstream fs("date.dat", ios::out );
ifstream fs("date.dat", ios::in );

答案:AC
在这里插入图片描述

12、数组名、指针、引用的sizeof

在32位机器上,有如下代码:其从上到下,每个printf其输出结果依次为?

char array[] = "abcdefg";
printf("%d\n", sizeof(array));
char *p = "abcdefg";
printf("%d\n", sizeof(p));
void func(char p[10])  
{
    printf("%d\n", sizeof(p));   
}
void func(char (& p)[10])  
{
    printf("%d\n", sizeof(p));
      
}
int main(void)  
{
    printf("%d\n", sizeof(char[2]));
    printf("%d\n", sizeof(char &));
    return 0;   
}

8 4 4 10 2 4(错误)
8 4 4 10 2 1(正确)
解释:
第1个printf输出:8,是字符串数组大小,末尾带了终止字符
第2个printf输出:4,是指针大小
第3个printf输出:4,数组作为函数形参,那么数组名退化成指针
第4个printf输出:10,p是 int[10]的引用
第5个printf输出:2,sizeof(char[2])等价于2*sizeof(char)
第6个printf输出:1,sizeof(char &)等价于sizeof(char)

13、怎么区分数组指针、指针数组??

int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,p也叫行指针。使用时,必须2次解引用。第一次解引用得到行,第二次解引用才能得到元素。

int main(){
	int a[4][5] = { {1,1,1,1,1},{ 2,2,2,2,2 } }, (*p)[5];
	p = a;
	printf("%d\n", *(*(p + 1) + 1));
}
错误示范:
int b[5]={0,0,0,0,0}, (*p)[5];
p = b;编译不通过,b与p的类型不匹配

int *p[n];
[]优先级高,先与p结合成为一个数组,那么p是一个数组名,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。

14、在派生类的函数中 能够直接访问基类的 公有成员和保护成员。这句话是否正确?

错误。因为子类可以直接访问父类的公有和保护成员,但是孙子类无法直接访问爷爷类的保护成员

15、类的对象为NULL时,怎么调用类的成员?

下列哪个函数调用会有问题()

using namespace std;
class A{
public:
void FunctionA(){cout << "FunctionA" << endl;}
virtual void FunctionB(){cout << "FunctionB" << endl;}
static void FunctionC(){cout << "FunctionC" << endl;}
}
class B:public A{
public:
void FunctionB(){cout << "FunctionB" << endl;}
int void FunctionD(){cout << "FunctionD" << endl;}
}
int main(){
	B *b = NULL;
	b->FunctionA();
	b->FunctionB();
	b->FunctionC();
	b->FunctionD();
}
以上程序中,下列哪个函数调用会有问题()
	b->FunctionA();
	b->FunctionB();
	b->FunctionC();
	b->FunctionD();

答案:b->FunctionB();
因为调用的是虚函数。因为虚函数本质上是通过类对象的虚表进行访问,而且类的成员函数除了虚函数,其他都不存储在类当中,因此类对象不存在的情况下,无法使用虚函数。
b->FunctionA();相当于 FunctionA(this),而FunctionA内部没有对this指向的内存操作,因此不会出错。
b->FunctionD();是静态函数,b->FunctionD()相当于 FunctionD(this),而静态函数是不需要this指针的,因此可以正常运行。

16、二维数组、数组指针的用法

若有以下说明和语句,int c[4][5],(*p)[5];p=c;能正确引用c数组元素的是( )。
p+1
*(p+3)
*(p+1)+3
*(p[0]+2)

答案:*(p[0]+2)

c是一个二维数组,c存放的内容是c[0]的首地址;而c[0]存放的内容是c[0][0]的首地址。
p是一个数组指针,指向的是一个长度为5的一维数组。
p=c,表示c[0]的首地址复制给p,那么*p表示c[0][0]的首地址,**p表示元素c[0][0]的数据内容。

  • p+1,p指向下一个一维数组(长度为5)
  • *(p+3),p跨过3个一维数组(长度为5),解引用,那么得到c[3][0]的首地址
  • *(p+1)+3p跨过1个一维数组(长度为5),解引用,得到c[1][0]的首地址,再跨过3个元素,得到c[1][3]的首地址
  • *(p[0]+2)p[0]得到c[0][0]的首地址,再跨过2个元素,解引用,得到元素c[0][2]的数据内容
17、拷贝构造函数,与赋值函数在实例化对象的调用时机

有如下程序段,程序输出为:

#include "stdio.h"

class A
{
    public:
    A()
    {
        printf("1");
    }
    A(A &a)
    {
        printf("2");
    }
    A &operator=(const A &a)
    {
        printf("3");
        return *this;
    }
};
int main()
{
    A a;
    A b = a;
}

答案:12

A a;,定义一个对象,毫无疑问调用构造函数
A b=a;,这是定义了对象b,且以a对b进行初始化,这个时候需要调用拷贝构造函数。等价于A b = A(a);
如果写成A a;A b;b=a;则是调用后面重载的赋值函数,这种情况应该输出113。
这个题主要考察赋值和初始化的区别。

18、int带符号整数加减运算中的溢出情况

以下说法正确的是:

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

结果不正确,因为会溢出,用位与的方式就没问题
结果正确,即使会溢出
结果正确,不会溢出
其他选项都不对

解释:带符号整数要溢出的话两种情况,2个大正数相加,2个负数相加

负数交换-5, -7。 以4bit为例。
-5 = 1011 (补码) -7 = 1001 (补码)
(-5)+ (-7)= 10100=0100=4 (溢出后为4)
4-(-7)= 4 +7=0100 + 0111 = 1011 = -5的补码
4-(-5)= 4 + 5 = 0100 + 0101 = 1001 = -7的补码

正数交换7,6
7=0111,6=0110
7+6=1101=-3的补码
-3-6=(-3)+(-6)=1101+1010=10111=0111=7
-3-7=-3+(-7)=1101+1001=10110=0110=6

答案:结果正确,即使会溢出

19、下面有关C++的类和C里面的struct的描述,正确的有?

在C++中,来自class的继承默认按照private继承处理,来自struct的继承默认按照public继承处理
class的成员默认是private权限,struct默认是public权限
c里面的struct只是变量的聚合体,struct不能有函数
c++的struct可有构造和析构函数

答案ABCD
1.c++中,class和struct的区别:

  • 成员访问权限->class的成员访问权限为private,而struct的成员访问权限为public
  • 默认的继承方式->class的默认继承方式为private,而struct的默认继承方式为public

2.struct在C和C++之间的区别

  • c中,struct是用户自定义数据类型,而c++中,struct是抽象数据类型,支持成员定义函数;
  • c中的struct是没有权限设置的,但是在c++中,给strcut添加了权限设置,增加了访问权限;
  • c中的struct只是变量的聚合体,可以封装数据,但是不可以隐藏,不可以定义函数成员;但是C++中的struct可以定义函数成员
20、关于虚函数和纯虚函数,以下说法正确的是()?

子类必须重载父类里的虚函数
子类必须重载父类里的纯虚函数
虚基类的构造函数在非虚基类之前调用
带有纯虚函数的类不能直接实例化

多重继承存在二义性,为了消除二义性在访问相同名称的属性时需要加上类名,加以区分。虽然这样可以解决二义性,但是相同的属性出现在多个基类中,为了解决数据冗余,c++引入了虚基类。

答案CD
解释:

  • 子类实现父类虚函数叫重写,不叫重载;
  • 父类有纯虚函数,子类可以不实现,此时子类仍是抽象类;
  • 虚基类的构造函数先与非虚基类的构造函数,并且会忽略以后对同一个虚基类的构造函数,保证只调用一次。
  • 带有纯虚函数的类是抽象类,成员函数不完整,不能直接实例化。
21、循环队列的实现

设循环队列为Q(1:m),其初始状态为front=rear=m。经过一系列入队与退队运算后,front=20,rear=15。现要在该循环队列中寻找最小值的元素,最坏情况下需要比较的次数为( )
5
6
m-5
m-6

答案:m-6
解释:Q(1:m)代表循环队列的大小为m
front:指向准备出队的位置,该位置有数据
rear:指向准备入队的位置,该位置没有数据
队列空:front==rear
队列满:(rear + 1) % m == fornt,要先判断如果入队,rear移动,是否会与front相遇。因此,循环队列至少有1个位置是空着的。
元素个数: (rear - front + m) % m
出队操作:

if (front == rear){
	return; 队列为空,不能执行出队操作
} else {
	出队操作
	front = (front + 1) % m;
}

入队操作:

if ((rear + 1) % m == fornt){
	return; 队列满,不能执行入队操作
} else {
	入队操作
	rear = (rear + 1) % m;
}

元素数量:

if (front == rear){
	return 0; 队列为空
} else {
	return (rear - front + m) % m;
}

在本题中,元素数量num=(rear-front+m)%m=m-5
寻找最小值需要比较num-1次,则答案为m-6

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

可用来创建动态增长和减小的数据结构
它是类型无关的,因此具有很高的可复用性
它运行时检查数据类型,保证了类型安全
它是平台无关的,可移植性

答案:C

C++中使用模板类的原因:
(1) 可用来创建动态增长和减小的数据结构;比如vector
(2) 它是类型无关的,因此具有很高的可利用性;
(3) 它在编译时检查数据类型,保证了类型安全;
(4) 它是平台无关的,具有可移植性;
(5) 可用于基本数据类型。

23、new与malloc的使用

该程序运行结果为
A
AA
崩溃
不可预测

#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;
}

答案:A
这题主要是考的new和malloc的区别,new会分配内存,并调用类的构造函数创建对象,而malloc只是分配内存,不调用类的构造函数创建对象,这个程序应该用delete p1;显示释放掉p1所指的堆上的内存,这样才会调用类的析构函数,否则不会调用。

24、下列语句中错误的是()
int * p=new int(10);
int*p=new int[10];
int*p=new int;
int*p=new int[40](0);

答案:D
int *p=new int[40](); 在分配空间后,对每个int进行了默认的初始化操作。即p[n]的值(n的取值范围是0-39)都是为0的。
int *p=new int[40](0); 这样是起不到对数组初始化为0的效果的,而且语法是错误的。
正常的应该是用大括号,int *p=new int[40]{0,1,2,3,4};

25、char->unsigned char

有如下程序段输出:

char ch=-1;
printf(%02x,%02x”,ch,(unsigned char)ch);

-1,-1
ff,ff
ffffffff,ff
ff,ffffffff

%x默认输出unsigned int;
所以char会被自动扩展至unsigned int;
因此会扩展符号位;
而unsigned char扩展至unsigned int;
会直接用0填充;
答案 C

26、在一台主流配置的PC机上,调用f(35)所需的时间大概是_______。
int f(int x) {
    int s=0;
    while(x-- >0)   s+=f(x);
    return max(s,1);
}

几毫秒
几秒
几分钟
几小时

答案 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机的每秒钟计算量约为107~108
因此计算时间大约在几分钟

27、类中的静态成员的访问方式

若有以下说明,则对n的正确访问语句是( )。

class Y
{
//… ;
public:
static int n;
};
int Y::n = 0;
Y objY;

n=1;
Y::n=1;
objY::n=1;
Y>n

答案:Y::n=1;

2个知识点

  • 类中的静态变量只作声明,必须在类外部定义一次(初始化分配内存),如上例的int Y::n = 0;
  • 访问类的静态变量时,不能依赖对象访问,而需要依赖类名访问,如上例的Y::n=1;
28、下面对友元的错误描述是( )。

(A)关键字friend用于声明友元

(B)一个类中的成员函数可以是另一个类的友元

(C)友元函数访问对象的成员不受访问特性影响

(D)友元函数通过this指针访问对象成员

答案:D。注意:友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

29、new仅仅是分配内存而没有初始化吗?

对于内置类型而言new仅仅是分配内存,除非后面显示加小括号(),相当于调用它的构造函数,对于自定义类型而言,只要一调用new,那么编译器不仅仅给它分配内存,还调用它的默认构造函数初始化,即使后面没有加()

int *p1 = new int[10]; 
int *p2 = new int[10]();

正确答案:
p1申请的空间里的值是随机值,p2申请的空间里的值已经初始化

30、枚举变量的值,函数外与函数内的区别
enum string{    
    x1,    
    x2,    
    x3=10,    
    x4,    
    x5,    
} x;

错误答案:0
正确答案:随机值
如果是函数外定义(属于全局变量),那么是0。
如果是函数内定义,那么是随机值,因为没有初始化。

31、有一个如下的结构体:请问在64位编译器下用sizeof(struct A)计算出的大小是多少?

8+2+(2+4)+8=24

struct A{
	long a1;		//8
	short a2;		//2,前面8,是2的整数倍,不需要补齐,直接填入a2
	int a3;			//4,前面10,先补齐变12,再填入a3
	int *a4;		//8,前面16,是8的整数倍,不需要补齐,直接填入a4
					// 结尾,总长24,是最长数据类型8的整数倍,尾部不需要补齐。
};

编译器不同,宽度是不相同,分别如下:
short 至少16位,int至少与short一样长,long至少32位,且至少和int一样长,long long至少64位,且至少与long一样长
//—64位编译器—//
char :1个字节
char*(即指针变量): 8个字节

short int : 2个字节
int: 4个字节
unsigned int : 4个字节

float: 4个字节
double: 8个字节

long: (Linux64位系统是8字节)
long long: 8个字节
unsigned long: 8个字节

//—32位编译器—//(看这里就行了,与16位机,64位机比较,粗体为不同的,其余的都是相同)
char :1个字节
char(即指针变量)*: 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)(16位机,32位机,64位机各不相同)

short int : 2个字节
int: 4个字节(16位机是2B,32位&64位是4B)
unsigned int : 4个字节(16位机是2B,32位&64位是4B)

float: 4个字节
double: 8个字节

long: 4个字节
long long: 8个字节
unsigned long: 4个字节(16&32位是4B,64位是8B)

32、结构体struct与union区别?struct与class区别?

struct与union:
   最大的区别在于内存利用。 struct各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。union 各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。
struct与class:
   C++中struct与class关键字一般可以通用,只有一个很小的差别。struct成员默认属性是public,而class默认为private。

33、结构体使用需要注意什么?

字节对齐,需要字节对齐的根本原因在于CPU访问数据的效率问题。
如果一个int型(假设为32位系统)如果存放在0x00000004,那它就是自然对齐的。

如果变量在0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据。

如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。

规则一:结构体中元素按照定义顺序依次置于内存中,但并不是紧密排列。从结构体首地址开始依次将元素放入内存时,元素会被放置在其自身对齐大小的整数倍地址上。

struct A{
     int a;
     double b;
     float c;
    };

struct B{
     char e[2];
     int f;
     double g;
     short h;
     struct A i;
    };
       sizeof(A) = 24; 这个比较好理解,int4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24sizeof(B) = 48; 看看B的内存布局。
            e         f       g         h                         i 
B的内存布局:11* *,   1111,   11111111, 11 * * * * * *,        1111* * * *, 11111111, 1111 * * * *

i其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。

我们可以按照自己设定的对齐大小来编译程序,GNU使用__attribute__选项来设置,比如我们想让刚才的结构按一字节对齐,我们可以这样定义结构体

struct stu{
   char sex;
   int length;
   char name[10];
}__attribute__ ((aligned (1)));
struct stu my_stu;

则sizeof(my_stu)可以得到大小为15。

34、有一个函数,这个函数是在中断和主程序都有调用,请问这个函数有什么要求?

引发的问题:
①结果改变。此函数不是一个可重入函数,而当此函数已经在执行时它可能被另一个ISR所调用。这样就会导致结果是可变的而且很可能会导致一些参数的错误。
②内存错误。如果函数中执行malloc时被中断所调用又开始执行malloc。这会引起某些内存错误。
解决办法:
使此函数可重入

  • 1)在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量);

  • 2)对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。

35、是否可以对数组名进行赋值?

以下程序有什么问题?

void test2()
{
	 char string[10], str1[10];
	 int i;
	 for(i=0; i<10; i++)
	 {
		 str1  = 'a';
	 }
	strcpy( string, str1 );
}

  • 代码根本不能通过编译。因为数组名str1也是地址,其数据类型是 char[10]类型,只能在定义时赋值。因此str1 = 'a';不通过。
  • 即使想对数组的第一个元素赋值,也要使用 *str1 = ‘a’;
    其次,对字符数组赋值后,使用库函数strcpy进行拷贝操作,strcpy会从源地址一直往后拷贝,直到遇到’\0’为止。所以拷贝的长度是不定的。如果一直没有遇到’\0’导致越界访问非法内存,程序就崩了。

以下程序有什么问题?

void test3(char* str1)
{
	 if(str1 == NULL){
	        return ;
	 }
	 char string[10];
	 if( strlen( str1 ) <= 10 )
	 {
	 	strcpy( string, str1 );
	 }
}

if(strlen(str1) <= 10)应改为if(strlen(str1) < 10),因为strlen的结果未统计’\0’所占用的1个字节。

36、写出完整版的strcpy、memcpy 函数

assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行。

  • 源src应声明为const char *
  • 判断输入是否为空
  • 返回指针实现链式操作
/* strcpy */
char * strcpy( char *strDest, const char *strSrc ) 
{
 assert( (strDest != NULL) && (strSrc != NULL) );
 char *address = strDest; 
 while( (*strDest++ = * strSrc++) != '\0' ); 
 return address;//为了实现链式操作,将目的地址返回
} 
  • 源src应声明为const void *
  • 判断输入是否为空
  • 判断是否内存重叠
  • 返回指针实现链式操作
/* memcpy */
char * memcpy( void *strDest, const void *strSrc ,size_t count) 
{//将源字符串加const,表明其为输入参数
 char *src = strSrc, *dest = strDest;	//将任意输入类型强制转换
 assert(((strDest + count) < strSrc) || ((strDest > (strSrc + count)));//考虑内存重叠
 assert( (strDest != NULL) && (strSrc != NULL) );//考虑空指针
 while( count-- ){
	*dest++ = *src++;
 }; 
 return strDest;//为了实现链式操作,将目的地址返回
} 
/* strcmp */
int strcmp(const char *a, const char *b){
	assert((a != NULL) && (b != NULL));
	/* 相等才循环 */
	while((*a) == (*b) && (*a) != '\0'){
		a++;b++;
	};
	return (*a)-(*b);
}
/* strcat */
char * strcat(char *strDest, const char *strSrc ){
	char *dest = strDest;
	assert(strDest != NULL && strSrc != NULL);
	while(*strDest != '\0'){
		strDest++;
	}
	while((*strDest ++) = (*strSrc ++) != '\0');
	return dest;

}
37、二级指针的用法

下面代码会出现什么问题?

void GetMemory( char **p, int num )
{
 *p = (char *) malloc( num );
}
void Text( void)
{
 char *str = NULL;
 GetMemory( &str, 100 );
 strcpy( str, "hello" ); 
 printf( str ); 

}
int main(){
	Text();
	return 0;
}
  • 没有判断malloc是否成功
  • 在Text函数中没有free(str),str= NULL;
  • 最好增加对num的取值判断

修改后的程序如下

void GetMemory(char **p, int num){
    if(num<=0)
        printf("申请的内存空间要大于零!\n");
    *p = (char*)malloc(num);
    if(*p==NULL)
        printf("申请内存失败!\n");
}
 
void Text( void)
    char *str = NULL;
    GetMemorty(&str, 100);
    strcpy(str, "hello world");
    printf("%s\n", str);
    free(str);
    str = NULL;
}

int main(){
	Text();
	return 0;
}
38、数组名退化成指针

以下为Windows NT下的32位C++程序,请计算sizeof的值

void Func ( char str[100] )
{
 sizeof( str ) = ?
}
void *p = malloc( 100 );
sizeof ( p ) = ?

sizeof( str ) = 4
sizeof ( p ) = 4

  • (1)数组名指代一种数据结构,这种数据结构就是数组;
    char str[10]; cout << sizeof(str) << endl;
    输出结果为10,str指代数据结构char[10]。
  • (2)数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
    char str[10]; str++;
    编译出错,提示str不是左值
  • (3)数组名作为函数形参时,沦为普通指针。
    Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) 、sizeof ( p ) 都为4。
39、为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh 
#ifdef __cplusplus
extern "C" {
#endif 
/*...*/ 
#ifdef __cplusplus
}
#endif 
#endif /* __INCvxWorksh */
  • 头文件中的编译宏的作用是防止被重复引用。

  • 为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。

  • C++的编译方式与C完全不同,只能部分兼容C
    作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:
    void foo(int x, int y);
    该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的

40、编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:
class String
{ 
 public: 
 String(const char *str = NULL); // 普通构造函数 
 String(const String &other); // 拷贝构造函数 
 ~ String(void); // 析构函数 
 String & operator =(const String &other); // 赋值函数 
 private: 
 char *m_data; // 用于保存字符串 
};
//普通构造函数
String::String(const char *str) 
{
 if(str==NULL) 
 {
 	m_data = new char[1]; 
 	//加分点:对m_data加NULL 判断
 	*m_data = '\0'; 
 	// 得分点:对空字符串自动申请存放结束标志'\0'的空
 } 
 else
 {
 	int length = strlen(str); 
 	m_data = new char[length+1]; 
 	strcpy(m_data, str); 
 }
}
// String的析构函数
String::~String(void) 
{
 	delete [] m_data; 
}
//拷贝构造函数
String::String(const String &other)    // 得分点:输入参数为const型
{ 
/* 类String拷贝构造函数与普通构造函数的区别是:
 * 在函数入口处无需与NULL进行比较,这是因为“引用”不可能是NULL,
 * 而“指针”可以为NULL。 */
 	int length = strlen(other.m_data); 
 	m_data = new char[length+1];     
 	strcpy(m_data, other.m_data); 
}
//赋值函数
String & String::operator =(const String &other) // 得分点:输入参数为const型
{ 
	 if(this == &other)   //得分点:检查自赋值
	 return *this; 
	 delete [] m_data;     //得分点:释放原有的内存资源
	 int length = strlen( other.m_data ); 
	 m_data = new char[length+1];  
	 strcpy( m_data, other.m_data ); 
	 return *this;         //得分点:返回本对象的引用
}

类String的赋值函数比构造函数复杂得多,分四步实现:

  • (1)第一步,检查自赋值。你可能会认为多此一举,难道有人会愚蠢到写出a =a 这样的自赋值语句!的确不会。但是间接的自赋值仍有可能出现,例如:
    第二步的delete,自杀后还能复制自己吗?所以,如果发现自赋值,应该马上终止函数。注意不要将检查自赋值的if语句if(this == &other)错写成为if( *this == other)
  • (2)第二步,用delete释放原有的内存资源。因为将要赋新值给它,内存大小不一定匹配。
  • (3)第三步,分配新的内存资源,并复制字符串。注意函数strlen返回的是有效字符串长度,不包含结束符‘\0’。函数strcpy则连‘\0’一起复制。
  • (4)第四步,返回本对象的引用,目的是为了实现象a= b =c 这样的链式表达。注意不要将return *this 错写成return this

每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A产生四个缺省的函数,如

A(void); // 缺省的无参数构造函数
A(const A &a); // 缺省的拷贝构造函数
~A(void); // 缺省的析构函数
A & operate =(const A &a); // 缺省的赋值函数

这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写?

原因如下:

(1)如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会,C++发明人Stroustrup的好心好意白费了。

(2)“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。
倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。以类String的两个对象a,b为例,假设a.m_data的内容为“hello”,b.m_data的内容为“world”。

现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data=a.m_data。这将造成三个错误:一是b.m_data 原有的内存没被释放,造成内存泄露;二是b.m_data和a.m_data指向同一块内存,a或b任何一方变动都会影响另一方;三是在对象被析构时,m_data被释放了两次。

如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,怎么办?

偷懒的办法是:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。

本题的知识点:4个重要函数,以及拓展知识:构造函数的初始化表,构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表)

41、关键字static,volatile,const,register的作用?
  • 一、static:
    0、初始默认值为0。
    1、限制作用域。在函数内的静态变量仅函数内可以访问,在模块内(函数外)的静态变量仅模块内可以访问,在模块内的静态函数仅在模块内可以调用,那么就允许同名静态函数存在不同模块内了。
    2、延长生命周期。静态变量的生命周期跟随整个程序。
    3、修饰类的属性成员,使之变成共有属性,归类所有。static修饰的属性成为该类下所有对象的共有属性,只占一份内存。
    4、修饰方法,变为共有方法,归类所有。static修饰的方法属于整个下所有对象共有,因而不接受this指针,就只能访问类中的静态变量而已。
    5、基类与派生类也共享static成员。

  • 二、volatile:
    告知编译器不对变量优化,每次都到其源地址取值。
    在多线程、多交互的程序中应用可防止数据出错。

  • 三、const:
    1、const可以修饰引用、指针、变量、形参、返回值,防止数据被修改, 提高函数的健壮性。
    2、const修饰成员函数:该成员函数不能修改任何成员变量

  • 四、register
    请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。
    注意是尽可能,不是绝对。你想想,一个CPU 的寄存器也就那么几个或几十个,你要是定义了很多很多register 变量,它累死也可能不能全部把这些变量放入寄存器吧。

42、大小端:请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1

大端模式:符合人类阅读习惯,即高位存放在低地址,低位反而存放在高地址;
小端模式:反人类,即高位存放在高地址,低位存放在低地址。

int checkCPU()
{
	 union w
	 { 
		 int a;
		 char b;
	 } c;
	 c.a = 1;
	 return (c.b == 1);
}
43、说一下C++和C的区别

设计思想上:
C++是面向对象的语言,而C是面向过程的结构化编程语言

语法上:

C++具有封装、继承和多态三种特性

C++相比C,增加多许多类型安全的功能,比如强制类型转换

C++支持范式编程,比如模板类、函数模板等

44、说一说c++中四种cast转换

参考回答:

C++中四种类型转换是:const_cast, static_cast, dynamic_cast, reinterpret_cast
1、const_cast

用于将const变量转为非const

2、static_cast

用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;

3、dynamic_cast

用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。

向上转换:指的是子类向基类的转换

向下转换:指的是基类向子类的转换

它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。

4、reinterpret_cast

几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

5、为什么不使用C的强制转换?

C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

45、请你说一下你理解的c++中的smart pointer四个智能指针:

为什么要使用智能指针:

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

  • 1、auto_ptr(c++98的方案,cpp11已经抛弃)
    缺点是:当auto_ptr使用’='赋值语句时,存在内存所有权混乱问题!
  • 2、unique_ptr(替换auto_ptr)
    优点:弥补了auto_ptr的问题。unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它定义了移动语义转移所有权,对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。
  • 3、shared_ptr
    shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
  • 4、weak_ptr(弥补shared_ptr中循环引用的麻烦)
    weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
46、请回答一下数组和指针的区别?
指针数组
数据内容变量的地址变量
占用空间sizeof与处理器寻址位数有关,32位处理器的指针是4byte数组长度*数组元素的数据类型长度
赋值方式同类型指针变量可以相互赋值除了初始化时,其他时候只能逐个元素赋值
47、什么叫野指针?

野指针就是非空且指向一个已删除的对象或者未申请访问受限内存区域的指针

48、请你回答一下为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数

考点:虚函数 析构函数

  • (1)为什么父类的析构函数必须是虚函数?

当我们动态申请一个子类对象时,使用基类指针指向该子类对象,如果不用虚函数,子类的析构函数不能得到调用,也就是为了在释放基类指针时可以释放掉子类的空间,防止内存泄漏.

  • (2)为什么C++默认的析构函数不是虚函数?

因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。

49、请你来说一下函数指针?

1、定义
函数指针是指向函数的指针变量。

函数指针本身首先是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。

C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。

2、用途:

调用函数和做函数的参数,比如回调函数。

3、示例:

char * fun(char * p)  {}       // 函数fun

char * (*pf)(char * p);             // 函数指针pf,指针类型:char* (char*)

pf = fun;                        // 函数指针pf指向函数fun

pf(p);                        // 通过函数指针pf调用函数fun
50、~ 析构函数的作用?

析构函数与构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。
析构函数名也应与类名相同,只是在函数名前面加一个位取反符 ’ ~ ',例如 '~stud( ) ',以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。

如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。

如果一个类中有指针,且在使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏。

类析构顺序:1)派生类本身的析构函数;2)成员的析构函数;3)基类析构函数。

51、C++之访问控制(public、private、protected以及friend)?

https://blog.csdn.net/a3192048/article/details/82191795
在这里插入图片描述

52、请你来说一下C++中struct和class的区别?

在c++中,struct和class都能够声明含有各种变量和函数的数据结构,能够单继承、多继承从而实现多态,struct和class声明的类型还可以作为对方的父类,它们的区别在于

  1. 默认权限,struct默认的成员变量和函数、继承方式是public的,class的则默认是private的。
  2. struct关键字不能用于定义模板参数
53、请你来说一下一个C++源文件从文本到可执行文件经历的过程?

对于C++源文件,从文本到可执行文件一般需要四个过程:

  • 预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。

  • 编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件

  • 汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件。

  • 链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件

54、什么是重定位?为什么需要重定位?
  • 链接时重定位:

汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件。这些子模块的符号地址基本都是从零开始排列的,等价于偏移量。但是,还有一些非本模块内定义的符号,不知道它们的地址。

基于以上文件的符号地址偏移都从0开始,多个模块肯定会地址重叠,链接器便按照链接顺序为这些符号修改地址;然后再对非本模块定义的符号进行重写(因为此时链接器操作多个模块,知道所有符号的定义位置)

小结:
多模块拼接时,符号地址排列和更新;
引用外部符号时,引用处地址更新。

  • 装载时重定位:

当调用了.so文件时,就需要对.so文件的符号重定位。因为链接时并没有包括.so文件,是在程序运行时才加载.so,因此不知道.so会被加载到哪个地址。

  • bootloader启动时重定位:

当一块芯片启动的时候,依靠内部的iRAM,可以初始化外部RAM并拷贝程序到外部RAM上去执行。即执行一段位置无关码,这段位置无关码的作用就是将原来的那份代码全部复制到外部RAM(链接地址),然后自己再长跳转到新的那份代码的刚刚执行的那个位置。这样就实现了链接地址跟运行地址一致的情况了,也即重定位。

55、什么是位置有关码?
  • 链接地址,链接脚本里指定的,理论上程序运行时所处的地址。在编译时,编译器会根据链接地址来翻译位置有关码。
  • 加载地址,程序运行时,实际所处的地址。
  • 位置无关码,依赖于程序当前运行的PC值,进行相对的跳转,导致的结果就是,无论代码在哪,总能达到指令的正常目的,因此是位置无关的。举例:B、BL相对跳转指令,从当前PC位置往前后跳转。bl close_watch_dog
  • 位置有关码,不依赖当前PC值,是绝对跳转,只有程序运行在链接地址处时,才能达到指令的正常目的,因此是位置有关系的。举例:ldr r0, =SMRDATA /* 获取标号地址,位置有关 */
  • https://blog.csdn.net/lizuobin2/article/details/52049892
56、请你来回答一下include头文件的顺序以及双引号””和尖括号<>的区别?
  • 编译器预处理阶段查找头文件的路径不一样。
  • ""表示.h文件的查找顺序为:
    当前头文件目录
    编译器设置的头文件路径(编译器可使用-I显式指定搜索路径);
    系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径;
  • <>表示.h文件的查找顺序为:
    编译器设置的头文件路径(编译器可使用-I显式指定搜索路径);
    系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径;
57、请你回答一下malloc的原理,另外brk系统调用和mmap系统调用的作用分别是什么?

malloc函数用于动态分配内存。为了减少内存碎片和系统调用的开销,malloc其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块

malloc采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块;同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连接起来,每一个空闲块记录了一个连续的、未分配的地址。

当进行内存分配时,malloc会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc采用边界标记法,将2块连续的空闲块合并成一块大的空闲块。

malloc在申请内存时,一般会通过brk或者mmap系统调用进行申请。其中当申请内存小于128K时,会使用系统函数brk在堆区中分配;
而当申请内存大于128K时,会使用系统函数mmap在映射区(位于堆区与栈区中间)分配。

58、请你说一说C++的内存管理是怎样的?

描述内存分配方式及它们的区别?

(1)静态存储区分配。内存在程序编译时已分配好,在整个运行器件都存在。
(2)栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动释放。
(3)堆上分配。动态内存分配生存期由程序员决定。

在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区以及栈区六部分。
text段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。

data段:存储程序中已初始化的全局变量和静态变量

bss 段:Block Started by Symbol,存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。

heap区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。

映射区:存储动态链接库等文件映射、申请大内存(malloc时调用mmap函数)

stack:使用栈空间存储函数的返回地址、参数、局部变量、返回值

59、new与malloc的区别?

参考回答:

1、new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小分配;

2、new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化。

3、new不仅分配一段内存,而且会调用构造函数,malloc不会。

4、new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会。

5、new是一个操作符可以重载,malloc是一个库函数。

6、malloc分配的内存不够的时候,可以用realloc扩容。new没用这样操作。

7、new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。

8、申请数组时: new[]一次分配所有内存,多次调用构造函数,搭配使用delete[],delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n。

60、定义一个空的类型,里面没有任何成员变量和成员函数。对该类型求sizeof,得到的答案是多少?

答案是1 空类型的实例中不包含任何信息,本来求sizeof应该> > 是0,但当我们声明该类型实例的时候,他必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。一般编译器空类型占用1个字节。

  • 那如果在类中声明了一个静态成员变量呢?
class A
{
public:
	static int i;
};
/* 静态数据必须在类中声明,在类外定义初始化
除非是const才能在类中初始化 */
int A::i = 1;

答案:还是1
因为静态数据不会与非静态数据存放在一起,不会占据类对象的内存。

61、如果在该类型中添加一个构造函数和析构函数,再对该类型求sizeof,得到的答案是多少?

和前面一样是1 调用析构函数和构造函数只需要知道函数的地址即可,这些函数的地址与类型相关,而与类型的实例无关,编译器也不会因为这两个函数而在实例内添加任何信息。

  • 如果把析构函数标记为虚函数呢?

c++编译器一旦发现一个类型中有虚函数,就回为该类型生成虚函数表,并在该类型的实例中添加一个指向虚函数表的指针。所以是一个指针的大小,在32位机器上sizeof是4个字节,64位8个字节。

62、我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数(写在函数的最后表示不能修改成员变量,不是指写在前面表示返回值为常量)。请问:能不能同时用static和const修饰类的成员函数?

答案:不可以。C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时static的用法和const是冲突的

我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。

  • 那const和static可以同时修饰成员变量吗?可以。该变量只能在类中声明同时初始化,生命周期是整个程序,作用域是该类以及所有子类。
63、空对象能调用成员变量或成员函数吗?

运行下面中的代码,得到的结果是什么?

class A
{
private:
        int m_value;
public:
        A(int value)
        {
                m_value = value;
        }
        void Print1()
        {
                printf("hello world");
        }
        void Print2()
        {
                printf("%d", m_value);
        }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
        A* pA = NULL;
        pA->Print1();
        pA->Print2();
 
        return 0;
}

分析:答案是Print1调用正常,打印出hello world,但运行至Print2时,程序崩溃。调用Print1时,并不需要pA的地址,因为Print1的函数地址是固定的。编译器会给Print1传入一个this指针,该指针为NULL,但在Print1中该this指针并没有用到。只要程序运行时没有访问不该访问的内存就不会出错,因此运行正常。在运行print2时,需要this指针才能得到m_value的值。由于此时this指针为NULL,因此程序崩溃了。

.

65、怎么求成员的偏移量?

运行下列C++代码,输出什么?

struct Point3D
{
        int x;
        int y;
        int z;
};
 
int _tmain(int argc, _TCHAR* argv[])
{
        Point3D* pPoint = NULL;
        int offset = (int)(&pPoint->z);
 
        printf("%d", offset);
        return 0;
}

答案:输出8。由于在pPoint->z的前面加上了取地址符号,运行到此时的时候,会在pPoint的指针地址上加z在类型Point3D中的偏移量8。由于pPoint的地址是0,因此最终offset的值是8。

&(pPoint->z)的语意是求pPoint中变量z的地址(pPoint的地址0加z的偏移量8),并不需要访问pPoint指向的内存。只要不访问非法的内存,程序就不会出错。

66、构造函数带初始化列表,则初始化顺序是什么?

运行下图中的C++代码,输出是什么?

#include <iostream>
 
class A
{
private:
        int n1;
        int n2;
public:
        A(): n2(0), n1(n2 + 2)
        {
        }
 
        void Print()
        {
                std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;
        }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
        A a;
        a.Print();
 
        return 0;
}

答案:输出n1是一个随机的数字,n2为0。在C++中,成员变量的初始化顺序与变量在类型中的申明顺序相同,而与它们在构造函数的初始化列表中的顺序无关。因此在这道题中,会首先初始化n1,而初始n1的参数n2还没有初始化,是一个随机值,因此n1就是一个随机值。初始化n2时,根据参数0对其初始化,故n2=0。

67、复制构造函数的形参应该是什么类型?

编译运行下图中的C++代码,结果是什么?(A)编译错误;(B)编译成功,运行时程序崩溃;(C)编译运行正常,输出10。请选择正确答案并分析原因。

#include <iostream>
class A
{
private:
        int value;
 
public:
        A(int n)
        {
                value = n;
        }
 
        A(A other)
        {
                value = other.value;
        }
 
        void Print()
        {
                std::cout << value << std::endl;
        }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
        A a = 10;
        A b = a;
        b.Print();
 
        return 0;
}

答案:编译错误。在复制构造函数A(A other)的实现中没有使用引用而是值拷贝,A a = 10;把10拷贝到形参列表的othoer会调用复制构造函数。因此会形成永无休止的递归调用复制构造函数,并造成栈溢出。因此C++的标准不允许复制构造函数传值参数,而必须是传引用或者常量引用。在Visual Studio和GCC中,都将编译出错。

  • 定义复制构造函数的过程中,为什么可以访问该类下的其他对象的私有成员??
    类的定义过程中,只要是同一类,就可以借助对象访问私有和公有成员,n没有访问限制。
    而在程序中使用类对象时,就只能访问该对象的公有成员了。
69、宏的展开

运行下图中的C++代码,输出是什么?

#include <stdio.h>
#define N 2
#define M N+ 1
#define NUM ( M+1 )*M/2
main()
{ 
    printf( "%d\n" ,NUM ); 
}

正确答案: 8
本题考查宏定义,宏定义只是做个简单的替换,执行NUM=(N+1+1)*N+1/2=8。

70、下面四个选项中,均是正确的数值常量或字符常量的选项是()。

0.0 0f 8.9e ‘&’
“a” 3.9e-2.5 1e1 ‘\”’
’3’ 011 0xff00 0a
+001 0xabcd 2e2

0f必须带小数点,即.0f或0.0f
e后面必须有一个整数,不可以是非整数
“a”是字符串常量,而非字符常量
0a 中的0是表示8进制,a不能出现在8进制的数字里

只有D正确。

71、以下选项中不属于C语言标识符的是?

常量
用户标识符
关键字
预定义标识符

常量是指在程序运行过程中其值不能被改变的量,如5、1.0、字符a等。C语言的标识符分为3类:关键字、预定义标识符和用户标识符。常量不属于标识符,所以选择A。

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

可用来创建动态增长和减小的数据结构
它是类型无关的,因此具有很高的可复用性
它运行时检查数据类型,保证了类型安全
它是平台无关的,可移植性

答案C,关键点是 运行时检查,错啦。应该是编译时检查

73、下面关于迭代器失效的描述哪个是错误的

vector的插入操作不会导致迭代器失效
map的插入操作不会导致迭代器失效
vector的删除操作只会导致指向被删除元素及后面的迭代器失效
map的删除操作只会导致指向被删除元素的迭代器失效

答案A
在这里插入图片描述

  • 对于序列式容器(如vector,deque),删除一个元素导致后面所有的元素会向前移动一个位置。所以不能使用erase(iter++)的方式,还好erase方法可以返回下一个有效的iterator。
    vector 动态增加大小时,并不是在原空间后增加新空间,而是以原大小的两倍在另外配置一个较大的新空间,然后将内容拷贝过来,接着再原内容之后构造新元素,并释放原空间,由于插入操作改变了空间,故迭代器会失效
  • 对于关联容器(如map, set,multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。erase迭代器只是被删元素的迭代器失效,但是返回值为void,所以要采用erase(iter++)的方式删除迭代器。
74、结构体中的柔性数组

编译器对以下数据结构中data的处理方式是()

struct Node
{
   int size;
   char data[0];
};

编译器会认为这就是一个长度为0的数组,而且会支持对于数组data的越界访问。
知识点:柔性数组。位于结构体末尾,长度为0或1的数组。

75、什么是柔性数组?

柔性数组(Flexible Array)也叫伸缩性数组,其实就是变长数组。只要申请到的内存管够,那么柔性数组的伸缩延长就不会超出预料之外。
C99使用不完整类型来实现柔性数组,标准形式如下:

struct MyStruct
{
	int a;
	double b;
	char c[]; // or char c[0]; 也可以用其他数据类型;
	
};

有些编译器无法识别char c[0];的语法,可以用char c[]char c[1]代替。
特性:

  • c不占用MyStruct的空间,只是作为一个符号地址存在,使用sizeof(ms)会发现,结果为12=4+8,c没有占空间。
  • 而且必须是结构体的最后一个成员。
#include <iostream>
#include <malloc.h>
using namespace std;

typedef struct MyStruct
{
    int a;
    double b;
    char c[];
} ms,*pms; // 自定义类型,指针类型

int main()
{
    char c1[] = "Short string.";
    char c2[] = "This is a long string.";
    /* 申请空间时要考虑增加内存给柔性数组 */
    pms pms1 = (pms)malloc( sizeof(ms) + strlen(c1) + 1 );
    if( NULL != pms1 )
    {
        pms1->a = 1;
        pms1->b = 11;
        /* 调用cpy对柔性数组进行赋值 */
        strcpy(pms1->c, c1);
    }
    cout<<"pms1: "<< pms1->a
        <<" "<<pms1->b
        <<" "<<pms1->c<< endl;
    /* 申请空间时要考虑增加内存给柔性数组 */
    pms pms2 = (pms)malloc( sizeof(ms)  +strlen(c2) + 1 );
    if( NULL != pms2 )
    {
        pms2->a = 2;
        pms2->b = 22;
        strcpy(pms2->c, c2);
    }
    cout<<"pms2: "<<pms2->a
        <<" "<<pms2->b
        <<" "<<pms2->c<< endl;
    free( pms1 );
    free( pms2 );
    return 0;
}
  输出:

    pms1: 1 11 Short string.
    pms2: 2 22 This is a long string.

很多人其实会有这种疑惑,就是为什么不用指针去代替变长结构体,比如:

structNode
{
   int size;
   char *data;
};

用指针和用变长结构体的区别

  • 1.在位置方面:指针可以放在任何地方,但是变长结构体的变长部分一定要放在结构体的最后

  • 2.在内存占用方面:指针会占一个指针的大小的内存空间,但是变长数组是不占内存的,它只是一个占位符

  • 3.在内存布局方面:指针指向的内存和结构体的内存可以是不连续的,但是变长部分和结构体的内存必须是连续

  • 4.在内存释放方面:使用指针,就要先释放指针所指的内存,再释放整个结构体的内存,否则会照成内存泄露
    但是使用变长结构体直接释放整个结构体的空间就可以了

  • 5.一个限制:指针可以用在C++的类中,但是柔性数组就不可以了(C++不提倡)

    因为有些编译器会将一些额外的信息放在类的最后,
    比如vptr或者虚基类的内容,使用了变长的类,就会把这部分的值改变,这种行为是未定义的,谁也不知道会发生什么。

76、函数参数的入栈顺序

在gcc编译器下,对于 int i = 3; printf("%d %d", ++i, ++i),运行输出为()

5,5

解释:
函数的参数是从右向左压栈的,输出时从栈顶开始,相当于:

int i = 3;  
++i; // i=4
++i; // i=5
printf("%d,%d",i,i);

所以是 5,5;
再举一个例子,int i = 1; printf("%d,%d", i += 2, i *= 3); 在输出i之前先进行了i *= 3和 i += 2;最终i = 5;所以结果是5,5;

77、不同编译器对++i与i++的处理顺序是不同的

在gcc编译器下,下列函数的结果是()

#include <stdio.h>

int main(){
	int i = 8;
	printf("%d,%d\n", i++, i++);
	i = 8;
	printf("%d,%d\n",++i, ++i);

	return 0;
}

9,8
10,10

  • 调用printf函数时,参数的入栈顺序是从右往左
  • 而对gcc编译器, ++i与i++的在栈中的存储方式不同。

++i,对i+1,然后将i入栈,不是将i的值直接压入,大概意思是压入的是i所在的内存地址,具体i的值是多少要在输出前重新读取(你可以理解成最后输出前还要去看下当初压入的那个地址里面存的数到底是多少然后再输出),而与当初压入时i的值无直接关系。

i++,先把i的实际值入栈,再对i自增。比如i=8,i++入栈时,把8入栈,然后对i+1,那么i=9,而栈内值是8

记住,这里讨论的++i与i++都是在gcc编译器下的。

77、如何判断double类型的数是否为0?

思路,定义个容许误差范围,比如0.0000001~-0.00000001之间就是0
可以用abs去取绝对值,再与容许误差比较。

#define MIN_VALUE 1e-8  //根据需要调整这个容许误差值
#define IS_DOUBLE_ZERO(d) (abs(d) < MIN_VALUE)
78、二维数组与数组指针

下列函数的输出结果是?

#include<iostream.h>
void main(){
    int n[][3]={10,20,30,40,50,60};
    int (*p)[3];//该语句是定义一个数组指针,指向含3个元素的一维数组。
    //p++意味着跨过3个元素指向下一行。*(p+1)表示第二行第一个元素;
    p=n;
    cout<<p[0][0]<<","<<*(p[0]+1)<<","<<(*p)[2]<<endl;
}

p[0][0]指的是首个元素;
p[0]+1指的是第0行第1个元素;
(*p)[2]数组指针,表示p指向的行的第2个元素;

p怎么切换所指向的行?
p+1或p[1]

一维数组:
a <=> &a[0] a+1 <=> &a[1]
*a <=> a[0] *(a+1) <=> a[1]
二维数组:
a[0] <=>&a[0][0] a[1] <=> &a[1][0] a[1]+1 <=> &a[1][1]
*a[0] <=>a[0][0] *a[1] <=> a[1][0] *(a[1]+1 ) <=> a[1][1]

79、关于C++的异常处理,下列说法正确的有

A 函数指针与该指针所指的函数必须具有相同的noexcept异常说明
B 异常对象的初始化是通过拷贝初始化进行的
C 若析构函数中含有throw语句,而其声明中不包含noexcept(false)的显式声明,则编译无法通过
D 异常对象和异常声明的匹配规则中可以进行类型转换,遵循函数调用的形参、实参的转换规则

参考答案:B
解析:

A项,函数指针与该指针所指的函数必须具有一致的noexcept异常说明,而非相同。特别的,隐式声明为noexcept(false)的函数指针可以指向noexcept(true)的函数,A错误。
C项,编译器并不会在编译时检查noexcept声明,编译可以通过,C错误。
D项,恰恰相反,在异常声明的匹配规则中,绝大多数类型转换都不被允许,除了以下3种例外:1)允许从非常量到常量的转换;2)允许派生类到基类的转换;3)数组转数组元素指针,函数转函数指针。除此之外,包括标准算术类型和类类型转换在内,其他的转换规则不能在异常catch匹配中使用,D错误。

80、用户双击鼠标时产生的消息序列,下面正确的是()

双击即点击左键两下,第一次触发LBUTTONDOWN和LBUTTONUP,第二次点击时触发双击事件LBUTTONDBLCLK(doubleclick),放掉再触发LBUTTONUP

81、以下逗号表达式(x=4 * 5, x * 5), x+25的值为( )。

考点:逗号表达式先算左边,再算右边,然后返回右边的值。
先算括号里的x=20,括号内的逗号表达式的值为100,但整个表达式的值是20+25=45

82、以下哪种语法在C++中是错误的?

const X * x
X const * x
const X const * x
X * const x

  • const对左边的量起作用,如果左边没有量那就对右边的量起作用。

  • const X * x:(const X)* x,表示指针x指向的内容是一个const X类

  • X const * x:(X const)* x,表示指针x指向的内容是一个const X类

  • const X const * x:(const X const)* x,由于2个const重复修饰了X,编译报错

  • X * const x:(X* const)x,表示指针x指向了一个X类,由于x是const,没有在定义时初始化,编译报错。

83、通用多态是指
  • 特定多态。
    • 重载多态是指函数重载
    • 强制多态是指 强制类型转换
  • 通用多态。
    • 参数多态是类模板、函数模板
    • 包含多态是指虚函数
84、C 语言中,函数值类型的定义可以缺省,此时函数值的隐含类型是()。

c语言中如果函数没有指定返回类型,则默认为int .
c++中如过无返回值类型,必须写void

85、设整型变量n的值为2,执行语句“n+=n-=n*n”后,n的值是()

该题主要考察运算符的优先级。基本的优先等级需要牢记:
指针最优,单目运算高于双目运算,先算数运算,后移位,最后位运算,逻辑运算最最后。相同优先级按结合顺序计算,大多数从左向右,只有单目运算符、条件运算符、赋值运算符是自右向左结合。

86、下列关于构造函数的描述中,错误的是( )。

构造函数可以重载
构造函数名同类名
带参数的构造函数具有类型转换作用
构造函数是系统自动调用的

选C
【分析】

A选项:构造函数可以被重载,析构函数不可以被重载。正确
B选项:C++构造函数的函数名必须和类名相同。正确
C选项:转换构造函数只有一个参数,如果有多个参数的,它就不是转换构造函数。至于类型转换函数 ,它的作用是将一个类的对象转换成另一类型的对象。错误
D选项:类是一种用户自定义的类型,声明一个类对象时,编译程序需要为对象分配存储空间,进行必要的初始化,C++中这项工作由构造函数来完成。所以每创建一个对象,系统自动调用一次构造函数。正确

87、虚继承与虚函数与类对象的内存分布

下面程序打印结果为()

#include<iostream>
using namespace std;
 
class A
{
char a[3];
public:
virtual void fun1(){};
};
 
class B : public virtual A
{
char b[3];
public:
virtual void fun2(){};
};
 
class C : public virtual B
{
char c[3];
public:
virtual void fun3(){};
};
 
int main ()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
return 0;
}

考点:虚继承时,子类会增加一个虚基类表指针4个字节。
答案是:8,20,32

  • 为什么会有虚继承?
  • 答:编程时考虑到以后可能会出现菱形继承结构,那么就需要虚继承。
88、父类指针指向子类对象,调用了非虚函数

有如下程序段,打印结果为:

#include "stdio.h"
 
class A
{
public:
    int _a;
    A()
    {
        _a = 1;
    }
    void print()
    {
        printf("%d", _a);
    }
};
class B: public A
{
public:
    int _a;
    B()
    {
        _a = 2;
    }
};
int main()
{
    B b;
    b.print();
    printf("%d", b._a);
}

答案:12
因为在继承的时候,允许子类存在与父类同名的成员变量,但是并不覆盖父类的成员变量,他们同时存在。 因为给孩子类中没有定义print函数,所以会按照就近原则去寻找父类中是否有print函数。恰好父类中有这个函数,于是调用父类的print函数b.print(),而这个函数会调用父类的a变量。

在继承中,虽然子类继承了父类的函数,但父类的函数只能操作父类那个块域的数据,这个题中print函数子类是通过继承父类才拥有该函数,但你要是想让这个函数操作数据它只能操作父类中拥有的数据。

89、数组名取地址

执行如下代码后输出结果为( )

int main()
{
int a[5]  = {1, 2, 3, 4, 5};
int *ptr = (int*)(&a + 1);
printf("%d, %d", *(a + 1), *(ptr - 1));
return;
}

int a[5];
(&a + 1) 与 (a + 1)的区别:

  • &a 是代表数组的地址, 数组的内存是5 * size(int) 。 因此&a + 1 是对数组整块内存加1, 即 &a + 5*size(int),即+1操作会时&a的值+20。
  • a是数组名,代表数组首元素的地址, 数组首元素的大小为size(int), 因此a+1是对整个数组的内存加1, 即 a + size(int)。

为更好的理解数组名,思考下面的程序输出结果是什么?

#include <stdio.h>

int main(){
	int array[5] = {1, 2, 3, 4, 5};
	int *ptr = (int *)((&array) + 1);
	
	for (int i = 0; i < 5; ++i){
		printf("[%d] = %d,", i, array[i]);
	}
	printf("\n");

	printf("array = :%ld\n", array);
	printf("array[0] = %ld\n", array[0]);
	printf("&array[0] = %ld\n", &array[0]);
	printf("&array = %ld\n", &array);


	printf("sizeof(array) = %ld\n", sizeof(array));
	printf("sizeof(&array) = %ld\n", sizeof((&array)));
	printf("sizeof(&array[0]) = %ld\n", sizeof((&array[0])));


	printf("*(array + 1) = %ld\n", *(array + 1));
	printf("*(&array + 1) = %ld\n", *((&array + 1)));
	return 0;
}

结果如下

jw@pc:~/w$ ./test
[0] = 1,[1] = 2,[2] = 3,[3] = 4,[4] = 5,
array = :140728847088816
array[0] = 1
&array[0] = 140728847088816
&array = 140728847088816
sizeof(array) = 20
sizeof(&array) = 8
sizeof(&array[0]) = 8
*(array + 1) = 2
*(&array + 1) = 140728847088836

*(&array + 1) = 140728847088836比array 增加了20.

90、C++中,能作为函数重载判断依据的是?

参数类型
const
参数个数
返回类型

答案:ABC

  • const可以作为重载判断依据,它们被调用的时机为:如果定义的对象是常对象,则调用的是const成员函数;如果定义的对象是非常对象,则调用重载的非const成员函数。
  • 参数类型和个数是显然的,注意不能以返回值判断重载,return可能会进行类型转换。
91、以下程序的输出是
class Base {
    public:
    Base(int j): i(j)  {}
    virtual~Base() {}
    void func1() {
        i *= 10;
        func2();
    }
    int getValue() {
        return  i;
    }
    protected:
    virtual void func2() {
        i++;
    }
    protected:
    int i;
};
class Child: public Base {
    public:
    Child(int j): Base(j) {}
    void func1() {
        i *= 100;
        func2();
    }
    protected:
    void func2() {
        i += 2;
    }
};
int main() {
    Base * pb = new Child(1);
    pb->func1();
    cout << pb->getValue() << endl; delete pb; }

11
101
12
102

正确答案: C
Base * pb = new Child(1), 首先创建子类对象,初始化为1;
func1()不是虚函数,所以pb->func1()执行的是Base::func1()执行的是函数,i= 10,然后调用func2()函数;
这里的func2是虚函数,要往下派生类寻找,Child::func2(),此时,i = 12;
最后执行pb->getValue(),即Base::getValue()结果为12
故选C

92、位运算符的优先级

设字符型变量x的值是064,表达式“~ x ^ x << 2 & x”的值是()
char x = 064,为八进制表示,对应二进制码为00110100(32位系统下,char类型长度为1字节,8个二进制位)
几个运算符的优先级分别是~ << & ^ |,依次运算得到以下结果:
(以下结果为当步运算的值,不是x的当前值,小伙伴们擦亮眼睛哈_

  1. ~x 按位取反,结果为 11001011
  2. x << 2 对x左移2位,结果为11010000
  3. x << 2 & x 即第2步的结果与x做按位与操作,结果为 00010000
  4. 最后第3步的结果与~x做异或运算,结果为11011011,即333。
    因为按八进制输出,需要前置0,所以选A(有点牵强)
93、C++ int 型负数除法与求模运算
   1、除法运算:向零取整

   比如:10/(-4) = -2;10/4 = 2;

   2、求模运算:  (1)  |小| % |大| = |小| ,符号同前;  (2)  |大   | % |小| = |余| ,符号同前 ; (|n|指n的 绝对值, 求模时参照该公式: “余数=被除数-商*除数 )

   比如:        3%4 = 3, (-3)%(-4) = -3,(-3)%4 = -3,3%(-4) = 3;    5%3 = 2, (-5)%(-3) = -2,(-5)%3 = -2,5%(-3) = 2;
94、输入参数为197时,函数返回多少?
int Function(unsigned int n) {
     
        n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
        n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
        n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);
        n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff);
        n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff);
 
        return n;
}

答案:5
计算二进制的一的个数,这个算法叫做平行算法。以217(11011001)为例
在这里插入图片描述

95、Math.round(11.5) 等于多少 ()、Math.round(-11.5) 等于多少 ( )

答案:12 ,-11
Math类中提供了三个与取整有关的方法:ceil,floor,round,这些方法的作用于它们的英文名称的含义相对应:

  • ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.6)的结果为-11;
  • floor的英文是地板,该方法就表示向下取整,Math.floor(11.6)的结果是11,Math.floor(-11.4)的结果-12;
  • 最难掌握的是round方法,他表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果是12,Math.round(-11.5)的结果为-11
96、以下选项中合法的实型常量是?

0
3.13e-2.1
.914
2.0*10

A选项为整型数据。B选项中e后面必须为整数。D选项是表达式,不是常量,所以选择C。

实型常量又称浮点数常量。在C语言中可以用两种形式表示:

  • 小数形式
    小数形式是由数字和小数点组成的一种实数表示形式,例如0.123、.123、123.、0.0等都是合法的实型常量。
    注意:小数形式表示的实型常量必须要有小数点。

  • 指数形式
    在C语言中,则以“e”或“E”后跟一个整数来表示以“10”为底数的幂数。2.3026可以表示为0.23026E1、2.3026e0、23.026e-1。C语言语法规定,字母e或E之前必须要有数字,且e或E后面的指数必须为整数。如e3、5e3.6、.e、e等都是非法的指数形式。注意:在字母e或E的前后以及数字之间不得插入空格。

程序运行的过程中,其值不能被改变的量称为常量。常量有不同类型,其中12、0、-5为整型常量。‘a’'b’为字符常量。而4.6、-8.7则为实型常量。
一个实型常量可以赋给一个 float 型、double 型或 long double 变量。根据变量的类型截取实型常量中相应的有效位数字。

97、编译器对宏#define的处理是从上到下的

以下代码的输出结果是?

#define a 10
 
void foo(); 
main(){
 
  printf("%d..",a);
   foo();
   printf("%d",a);
}
void foo(){
   #undef a
   #define a 50
}

10…10
10…50
Error
0

答案:10…10
define只是在预处理阶段将a替换为相应数值,具体替换的值只与define在文件中的位置有关,与是否在函数内无关。
如果调整一下foo的位置的话,

#define a 10
void foo();
void prin();
 
int main()
{
    prin();
    printf("%d ", a);
    foo();
    printf("%d ", a);
     
}
void foo()
{
#undef a
#define a 50
}
void prin()
{
    printf("%d ", a);
}

上面代码输出 50 10 10

98、追赶问题

下面两段代码中for循环分别执行了多少次?

1.
unsigned short i,j;
for(i=0, j=2; i!=j; i+=5, j+=7)
{}
 
2.
unsigned short i,j;
for(i=3,j=7;i!=j;i+=3,j+=7)
{}

跑圈追赶问题:
unsigned short 2个字节,2的16次方 = 65536
看成一个圈
1.
可认为j比i落后65536-2=65534距离,j比i每次多跑2距离
所以赶上需要65534/2=32767次

j比i落后65536-4=65532距离,j比i每次多跑4距离
所以赶上需要65532/4=16383次

99、如果A,B,C为布尔型变量,“^”和“v”分别代表布尔类型的“与”和“或”,下面那一项是正确的()
I.A^(BvC)=(A^B)v(A^C)
II.Av(B^C)=(AvB)^(AvC)
III.(B^A)vC=Cv(A^B)

123都正确

100、看如下代码:若要输出数组中数值为偶数的元素,下列哪些方法可行?
int array[8] = {1,6,3,3,8,0,7,4};
int *p = array;

A

for( int i=0; i<8; i++)
if(arr[i] % 2 == 0)
printf("%d", arr[i]);

B

for( int i=0; i<8; i++)
if(p[i] % 2 == 0)
printf("%d", p[i]);

C

int i=0;
while(i<8)
{
if(*(p+i) % 2 == 0)
printf("%d", *(p+i));
i++;
}

D

int i=0;
while(i<8)
{
if((array+i) % 2 == 0)
printf("%d", array+i);
i++;
}

正确答案: A B C
考点:数组名与指针。
两者是两种不同的数据结构,用sizeof可以区分。

但是,指针通常可以完成数组名的一些功能,比如指针可以使用运算符"[ ]"模拟数组。

数组名也可以使用*解引用。

101、对C++中重载(overload)和重写(override)描述正确的有()
  • 函数重载(overload)
    函数重载是指在一个类中声明多个名称相同但参数列表不同的函数,这些的参数可能个数或顺序,类型不同,但是不能靠返回类型来判断。特征是:
    (1)相同的范围(在同一个作用域中);
    (2)函数名字相同;
    (3)参数不同;
    (4)virtual 关键字可有可无(注:函数重载与有无virtual修饰无关);
    (5)返回值可以不同;

  • 函数重写(也称为覆盖 override)
    函数重写是指子类重新定义基类的虚函数。特征是:
    (1)不在同一个作用域(分别位于派生类与基类);
    (2)函数名字相同;
    (3)参数相同;
    (4)基类函数必须有 virtual 关键字,不能有 static 。
    (5)返回值相同,否则报错;
    (6)重写函数的访问修饰符可以不同;

  • 重定义(也称隐藏)
    (1)不在同一个作用域(分别位于派生类与基类) ;
    (2)函数名字相同;
    (3)返回值可以不同;
    (4)参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载以及覆盖混淆);
    (5)参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆);

102、请问刚进入func函数时,参数在栈中的形式可能为 (左侧为栈地址,右侧为栈数据)
int i=0x22222222char szTest[]=”aaaa”;  //a的ascii码为0x61 
func(I, szTest);    //函数原型为void func(int a,char *sz); 

A
0x0013FCF0 0x61616161
0x0013FCF4 0x22222222
0x0013FCF8 0x00000000

B
0x0013FCF0 0x22222222
0x0013FCF4 0x0013FCF8
0x0013FCF8 0x61616161

C
0x0013FCF0 0x22222222
0x0013FCF4 0x61616161
0x0013FCF8 0x00000000

D
0x0013FCF0 0x0013FCF8
0x0013FCF4 0x22222222
0x0013FCF8 0x61616161

选D。
1,对于x86,栈的增长方向是从大地址到小地址
2,对于函数调用,参数的入栈顺序是从右向左
3,函数调用入栈顺序是 右边参数–>左边参数–>函数返回地址
符合的只有D

104、该程序的执行结果
#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

解析:

  • 在构造函数不要调用虚函数。
    在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。

  • 在析构函数中也不要调用虚函数。
    在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。

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

即使不进行强制类型转换,在进行指针赋值运算时,指针变量的基类型也可以不同
如果企图通过一个空指针来访问一个存储单元,将会得到一个出错信息
设变量p是一个指针变量,则语句p=0;是非法的,应该使用p=NULL;
指针变量之间不能用关系运算符进行比较

解析 :
A选项中, 指针通常需要显示转换。除非目标类是(void*)可以隐式转换。
因为指针同时包括所指对象(基类型)地址和类型的描述,所以不同基类型的指针无法直接指向。
所以使用long *a指向int b是不行的,指针a的长度超越b的长度会产生不可预料的结果。
C 选项中, p=NULL ;和 p=0 ;是等价的;
D 选项中,指向同一数组的两指针变量进行关系运算可表示它们所值数组元素之间的关系。
因此 B 选项正确。

106、下面程序段输出结果是()。
int i=3,k;
 
k=(++i)+(++i)+(++i)printf("%d,%d",k,i);

不同的编译器有不同的结果,可能是16(gcc),也可能是18(vc)。

不同编译器结果不同,因为i的自增操作位于2个顺序点之间,因此无法确定k的值。

顺序点:https://blog.csdn.net/scorpio16/article/details/1556235

107、关于CPU load的说法,哪个是正确的:()

是当前系统的进程数量
是当前系统中所有ready和running状态的进程数量
是当前系统中running状态的进程数量

答案B

108、什么是位域,为什么要用位域,位域的好处

在计算机中,有些信息存储时并不需要占用一个完整的字节,而只需占用一个或几个二进制位。比如在存放一个只有0和1两种状态的开关量时,用一位二进制位即可表示。因此,为了节省存储空间,C语言提供了一种称为“位域”的数据结构来充分利用存储空间。

struct k     
{     
	int a:1     
	int :2 		 /* 该2位不能使用 */     
	int b:20     /* 拼接在前面的int类型中 */
	int c:16     /* 32位int不够了,重新开一个int,所以该结构体长度8 */
};  
109、有如下程序段,请问运行Test函数结果是:

程序崩溃

void GetMemeory(char* p)
{
    p = (char*) malloc (100);void test()
{
    char *str=NULL;
    GetMemory(str);
    strcpy(str,”Thunder”);
    strcat(str+2, “Downloader”);
    printf(str);
}

因为调用GetMemory(str);时只传入了str内容的副本。而不是str的地址或引用,因此str仍然为NULL。

换个角度理解,void GetMemeory(char* p)函数内部操作的都是局部变量,函数结束后局部变量都被回收,不会对外部的str变量造成任何改变。

所以执行strcpy(str,”Thunder”);程序崩溃

110、两个等价线程并发的执行下列程序,a为全局变量,初始为0,假设printf、++、–操作都是原子性的,则输出肯定不是哪个?

原子性;单次执行没有循环,因此只有初值为0的情况。

void foo() {
    if(a <= 0) {
        a++;
    }
    else {
        a--;
    }
    printf("%d", a);
}

A、01
B、10
C、12
D、22

A、如果要第一个值输出0,线程1进入判断并实现++,线程2此时进入判断实现–,a=0,两个线程下一句都是print,结果将是00;
B、线程1判断,执行++,打印1,线程2判断,执行–,打印0,结果10;
C、线程1进入判断,线程2进入判断,线程1执行++,打印1,线程2执行++,打印2,结果12;
D、线程1进入判断,线程2进入判断,线程1执行++,线程2执行++,各自打印出2,结果22。

111、下面有关c++线程安全,说法错误的是?

A、线程安全问题都是由全局变量及静态变量引起的
B、若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全
C、c++标准库里面的string保证是线程安全的
D、POSIX线程标准要求C标准库中的大多数函数具备线程安全性

  • C:因为标准库里面的string在多线程下并不保证是都是安全的,只提供两种安全机制:
    1.多个线程同时读取数据是安全的。
    2.只有一个线程在写数据是安全的。
112、volatile能保证全局整形变量是多线程安全的么?

不能。 volatile仅仅是告诫compiler不要对这个变量作优化,每次都要从memory取数值,而不是从register读取。

113、设int a=3,b=5,执行表达式m=a<=3&&a+b<8后,m的值为()

m=((a<=3) && ((a+b)<8))=0
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符

114、对于继承方式和可见性,下面哪一种说法是错误的?
  • A、继承方式有3种选择:私有、公有和保护,继承方式决定了基类成员在派生类中的可见性
  • B、默认为私有继承,但常用的却是公有继承
  • C、无论哪一种继承方式,派生类中都不能访问其基类中的私有成员,但能访问基类中的保护成员和公有成员
  • D、在公有继承方式中,派生类没有继承基类的私有成员

选D
公有继承意味着继承派生类的类能访问基类的公有和保护成员。
私有继承意味着继承派生类的类也不能访问基类的成员。如果一个派生类要访问基类中声明的私有成员,可以将这个派生类声明为友元。
保护继承意味着继承派生类的类能访问基类的公有和保护方法。

公有继承时,同样继承了基类的私有成员**,对基类的公有成员和保护成员的访问属性不变,派生类的新增成员可以访问基类的公有成员和保护成员,但是访问不了基类的私有成员。派生类的对象只能访问派生类的公有成员(包括继承的公有成员),访问不了保护成员和私有成员。

115、关于对象成员的访问,下面哪一种说法是错误的?
  • A、对于一个对象,可用“.”运算符来访问其成员
  • B、对于一个对象引用,可用“一>”运算符来访问其成员
  • C、如果被访问成员是公有的,该访问表达式可以出现在main函数中
  • D、如果被访问成员是私有的,该访问表达式只能出现在类中

本题选B和D
B对于一个对象引用,也是用“.”运算符来访问其成员
D如果类A把类B声明为自己的友元类,那么在类B中的所有函数中都可以访问类A的私有和保护成员。

116、对于默认构造函数,下面哪一种说法是错误的?
  • A、一个无参构造函数是默认构造函数
  • B、只有当类中没有显式定义任何构造函数时,编译器才自动生成一个公有的默认构造函数
  • C、默认构造函数一定是一个无参构造函数
  • D、一个类中最多只能有一个默认构造函数

本题选C。考察对C++构造函数的理解。
A选项,无参构造函数一定是默认构造函数,正确。
B选项,当类中没有显式定义任何构造函数时,编译器自动生成一个公有的默认构造函数。如果一个类显式地声明了任何构造函数,编译器不生成公有的默认构造函数。在这种情况下,如果程序需要一个默认构造函数,需要由类的设计者提供。正确。
C选项,无参构造函数一定是默认构造函数, 而默认构造函数可能是无参构造函数,也可能是所有参数都有默认值的构造函数。容易遗漏参数缺省时还可以预设默认值的机制!因此C选项错误。
D选项,一个类只能有一个默认构造函数,一般选择 testClass(); 这种形式的默认构造函数 ,因此D选项描述正确。

class Sample {
public:
    /* 默认构造函数。虽然有形参,但有默认值,
     * 调用的时候可以不显示的传入实参。 */
    Sample(int m = 10) {
        // do something
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值