C++面试题和笔试题(四)

一、intx[6][4],(*p)[4];p=x;则*(p+2)指向哪里?

  • A  X[0][1]
  • B X[0][2]
  • C X[1][0]
  • D X[2][0]

官方解释: D

int x[6][4], (*p)[4];  
p = x;

在这里,x 是一个二维数组,它有6行和4列。p 是一个指向具有4个整数的数组的指针。

当你执行 p = x; 时,p 指向 x 的第一行。

接下来,我们要考虑 *(p + 2)

在C语言中,数组名(如 x)代表其首地址。对于二维数组,它实际上是指向第一行的指针。当你对这样的指针进行加法操作时,你会跳过整行,而不是单个元素。

因此,p + 2 会跳过前两行,指向 x 的第三行。

*(p + 2) 会解引用这个指针,所以它实际上就是 x 的第三行。所以,*(p + 2) 指向 x 的第三行的首地址。

为了更直观地理解,你可以这样想象:

  • p 指向 x[0](第一行)
  • p + 1 指向 x[1](第二行)
  • p + 2 指向 x[2](第三行)

所以,*(p + 2) 就是 x[2],它指向 x 的第三行的首地址。 

自己的理解: 

假设我们有一个叫做x的大表格,这个表格有6行和4列。就像你在纸上画格子一样,每个格子可以放一个数字。

然后,我们有一个指针p,它就像一个小箭头,可以指向这个大表格的某一行。

当我们说p = x;的时候,就是把小箭头指向这个大表格的第一行。

接下来,我们要看*(p + 2)是什么意思。

想象一下,这个小箭头每次移动,不是一格一格地移动,而是整行整行地移动。所以,当箭头移动两次时,它就跳过了两行,指向了第三行。

*(p + 2)的意思就是,箭头移动两次后指向的那一行。所以,*(p + 2)指向的就是大表格的第三行。

简单说,就像你在纸上画了一个大表格,然后有一个小箭头指向第一行,当你让箭头跳两下,它就指向了第三行。这就是*(p + 2)的意思啦!

考点

这道题的考点是C语言中的指针和数组的理解

它主要考察了以下几个方面:

  1. 指针与数组的关系:在C语言中,数组名可以视为指向数组首元素的指针。对于二维数组,数组名实际上是指向第一行的指针。

  2. 指针的加法运算:对于指向数组的指针,进行加法运算时,不是按单个元素的大小来移动,而是按指针指向的整个数组或数据块的大小来移动。例如,对于指向int数组的指针,加1意味着跳过整个int大小的空间;对于指向二维数组(即数组的数组)的指针,加1意味着跳过整个一行的空间。

  3. 指针的解引用:使用*操作符可以解引用指针,即获取指针指向的值。对于指向数组的指针,解引用得到的是数组的首元素或首行的地址。

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

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

官方解释: C

关于C++中模板类的使用原因,我们可以逐项分析这四个选项:

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


这个描述并不准确。模板类主要是用于实现泛型编程,它允许程序员定义与类型无关的函数或类,然后在实例化时指定具体的类型。动态增长和减小的数据结构通常是通过使用如std::vector这样的容器类来实现的,而这些容器类内部可能会使用模板,但模板本身并不直接用于创建这样的数据结构。

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


这是正确的。模板类的主要优势之一就是它们的类型无关性。你可以为任何数据类型(包括用户自定义类型)定义模板,并在需要时实例化它们。这大大提高了代码的复用性。

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


这是错误的。模板的类型检查是在编译时进行的,而不是运行时。当你在代码中实例化一个模板时,编译器会检查你提供的类型是否满足模板的要求,并在编译阶段报告任何类型不匹配的错误。因此,模板确实保证了类型安全,但这种检查是在编译时进行的。

D. 它与平台无关,可移值性


这也是正确的。模板类在编译时被实例化为具体的类型,生成的代码与平台相关,但模板本身与平台无关。你可以在不同的编译器和平台上使用相同的模板代码,只要这些平台支持C++模板即可。这增加了代码的可移植性。

综上所述,描述错误的是选项C:它运行时检查数据类型,保证了类型安全。

自己的理解:

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


这个选项的意思是说,模板类可以用来创建一种特殊的数据结构,这种数据结构可以变大也可以变小,就像我们的书包一样,可以装很多东西,也可以只装一点点。但是,模板类并不是专门用来做这个的,它更多的是用来帮助我们写一些可以重复使用的代码。

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


这个选项是说,模板类就像是一个万能的工具,不管我们要处理的是数字、文字还是其他什么东西,它都可以帮忙。因为它不关心具体是什么类型,所以我们可以把同一个模板类用在很多地方,这就是它的复用性很高。

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


这个选项其实是错的。模板类是在我们写代码的时候就检查数据类型,而不是等到程序运行的时候。如果数据类型不对,它会在我们写代码的时候就告诉我们,而不是等到程序运行时才报错。

D. 它与平台无关,可移值性


这个选项是说,不管我们是在什么样的电脑或手机上使用模板类,它都可以正常工作。就像我们玩的游戏,不管是用电脑还是手机,只要游戏支持,我们都可以玩。这就是模板类的可移植性。

所以,描述错误的是选项C,它其实是在我们写代码的时候就检查数据类型,而不是运行时

考点:

  1. 模板类的定义和用途:模板类是C++中实现泛型编程的基础工具,它允许程序员定义与类型无关的代码,从而增加代码的复用性。 

  2. 模板的类型检查时机:模板的类型检查是在编译时进行的,而不是运行时。这是模板的一个重要特性,它确保了在实例化模板时,提供的类型参数符合模板的要求。

  3. 模板的可移植性:模板代码本身与平台无关,只要目标平台支持C++模板,就可以使用相同的模板代码。这体现了模板的可移植性。

  4. 模板与动态数据结构的区别:模板类虽然可以用于定义数据结构,但它本身并不直接创建动态增长和减小的数据结构。这种数据结构的实现通常依赖于特定的容器类,如std::vector等。

 三、在32位机器上用gcc编译以上代码,sizeof(),sizeof()分别是多少

Class A
{
int a;
short b;
int c;
char d;
};
class B
{
double a;
short b;
int c;
char d;
};
  •  A 12,16
  •  B 12,12
  •  C 16,24
  •  D 16,20

官方解释:C

以下是对每个数据成员的大小和可能的对齐要求的简要说明:

  • int 通常大小为4字节,并且通常需要4字节对齐。
  • short 通常大小为2字节,并且通常需要2字节对齐。
  • double 通常大小为8字节,并且通常需要8字节对齐。
  • char 通常大小为1字节,对齐要求通常最小。

现在,让我们考虑类AB的内存布局:

 对于类A

class A  
{  
    int a;   // 4字节,可能需要对齐到4字节边界  
    short b; // 2字节,可能需要对齐到2字节边界(但因为前面是int,所以这里可能不需要额外的填充)  
    int c;   // 4字节,需要对齐到4字节边界(这里可能需要填充)  
    char d;  // 1字节,通常不需要额外的对齐或填充  
};
int a (4 bytes)  
short b (2 bytes)  
填充 (2 bytes) // 为了使下一个int c对齐到4字节边界  
int c (4 bytes)  
char d (1 byte)  
填充 (3 bytes) // 为了使整个结构体的大小是4的倍数(取决于编译器是否这样做)

 总大小可能是 4 + 2 + 2 + 4 + 1 + 3 = 16 字节。

由于int之后紧跟着short,并且int已经满足了short的2字节对齐要求,所以这里可能不需要填充。但是,short之后是另一个int,这通常要求4字节对齐。如果short之后没有足够的空间来满足下一个int的4字节对齐要求,编译器可能会在shortint之间插入填充字节。

对于类B

class B  
{  
    double a; // 8字节,需要对齐到8字节边界  
    short b;  // 2字节,可能需要对齐到2字节边界(但因为前面是double,所以这里可能需要填充)  
    int c;    // 4字节,需要对齐到4字节边界(这里可能需要填充)  
    char d;   // 1字节,通常不需要额外的对齐或填充  
};
double a (8 bytes)  
填充 (6 bytes) // 为了使short b对齐到下一个合适的地址(可能是2字节边界)  
short b (2 bytes)  
填充 (2 bytes) // 为了使int c对齐到4字节边界  
int c (4 bytes)  
char d (1 byte)  
填充 (1 bytes) // 为了使整个结构体的大小是合适的对齐值(取决于编译器)

B的大小可能是 8(double) + 6(填充) + 2(short) + 4(int) + 1(char) + x(填充),其中x取决于编译器是否添加额外的填充以及添加多少。如果编译器决定让整个结构体的大小是8的倍数(以匹配double的对齐要求),那么总大小可能是24字节。

自己的理解: 

你有一个大盒子(这就是我们的结构体或类),你要把一些不同大小的东西放进去。但是,你不能随便放,有些东西需要放在特定的位置,这样拿起来才会更方便(这就是对齐的意思)。

对于类A

  1. 你首先放了一个大玩具(int a),它占了4个格子的空间。
  2. 然后你放了一个小玩具(short b),它占了2个格子的空间。因为它紧挨着大玩具,所以不需要额外的格子。
  3. 接下来,你想再放一个大玩具(int c),但是你需要确保这个大玩具从新的4个格子的位置开始放,这样拿起来更方便。所以,你在小玩具和大玩具之间放了2个空格子。
  4. 最后,你放了一个非常小的玩具(char d),只占了1个格子。
  5. 但是,为了让整个盒子的大小是4的倍数(这样放起来更整齐),你又在盒子的最后面放了几个空格子。

对于类B

  1. 你首先放了一个非常大的玩具(double a),它占了8个格子的空间,并且从8个格子的位置开始放。
  2. 接着你想放一个小玩具(short b),但是因为这个大玩具占了8个格子,你需要在大玩具和小玩具之间放6个空格子,这样小玩具才能从合适的位置开始。
  3. 然后你又放了一个大玩具(int c),它占了4个格子。这次不需要额外的空格子,因为小玩具后面已经有足够的空间了。
  4. 最后,你放了一个非常小的玩具(char d),只占了1个格子。
  5. 和类A一样,为了让整个盒子的大小是8的倍数,你又在盒子的最后面放了几个空格子。

 四、下面对静态数据成员的描述中,正确的是

  • A.静态数据成员可以在类体内进行初始化
  • B.静态数据成员不可以被类的对象调用 
  • C.静态数据成员不受private控制符号的作用
  • D.静态数据成员可以直接用类名调用

官方解释:AD

  • 选项 A:静态数据成员可以在类体内进行初始化,使用static关键字声明后,可以在类声明中直接初始化静态数据成员,例如static int val = 42;。所以选项 A 是正确的。
  • 选项 B:静态数据成员可以被类的对象调用,静态数据成员是类的所有对象共享的成员,可以通过类的对象来访问静态数据成员,例如MyClass obj; obj.val = 24;。所以选项 B 是错误的。
  • 选项 C:静态数据成员受private控制符号的作用,即使是静态数据成员,如果被声明为private,也只能在类的内部访问,例如private static int val;。所以选项 C 是错误的。
  • 选项 D:静态数据成员可以直接用类名调用,这是静态数据成员的一个重要特性,可以使用类名和点运算符来访问静态数据成员,例如MyClass.val = 64;。所以选项 D 是正确的。

 自己的理解:

  • 选项 A:静态数据成员可以在类体内进行初始化。

     

    生活例子:假设有一个人类类Person,其中有一个静态数据成员totalPopulation表示全球总人口。我们可以在类声明中初始化这个静态数据成员,例如:

    class Person {
    public:
        static int totalPopulation;
    };
    int Person::totalPopulation = 7000000000; // 初始化静态数据成员
  • 选项 D:静态数据成员可以直接用类名调用。

     

    生活例子:继续上面的人类类Person的例子,我们可以直接使用类名和点运算符来访问静态数据成员totalPopulation,例如:

    cout << Person::totalPopulation << endl; // 输出静态数据成员的值
     在这个例子中,我们直接使用类名Person和点运算符来访问静态数据成员totalPopulation,并将其值输出到标准输出流中。这样可以方便地获取全球总人口的信息,而不需要创建Person对象。

 考点:考察了 C++ 中类的静态数据成员的特性和使用方法,包括静态数据成员的初始化方式、访问方式以及与类对象的关系。

五、填空题(一)

#include<iostream>  
using namespace std;  
  
class A {  
public:  
    A() { cout << "1" << endl; }  // 构造函数  
    A(const A& a) { cout << "2" << endl; }  // 拷贝构造函数  
    A& operator=(const A& a) { cout << "3" << endl; return *this; }  
};  
  
int main() {  
    A a;   
    A b = a;   
    return 0;  
}

运行结果为( 1 ,2);

  1. 当你在 main 函数中创建 A 类的实例 a 时,会调用其构造函数,因此会输出 1
  2. 接下来,当你创建 b 并用 a 初始化它时,会调用拷贝构造函数,因此会输出 2
  • 1 对应构造函数 A()
  • 2 对应拷贝构造函数 A(const A& a)

自己的理解: 

假设我们有一个玩具工厂,这个工厂可以生产一种叫做“A”的玩具。

当我们告诉工厂开始生产一个“A”玩具时,工厂就会按照“A”的模板来制作,这就像是在代码中调用构造函数,输出“1”表示玩具制作完成了。

现在,我们已经有了一个“A”玩具,叫做“a”。如果我们想再要一个和“a”完全一样的玩具,我们不需要再让工厂从头开始制作,因为那样太麻烦了。我们可以直接让工厂复制一个“a”玩具,这样我们就得到了一个新的玩具,叫做“b”。这个过程就像是代码中的拷贝构造函数,输出“2”表示我们已经通过复制得到了一个新的玩具。

所以,整个过程的步骤和输出就是:

  1. 工厂按照模板制作了一个“A”玩具(输出“1”)。
  2. 工厂复制了一个已有的“A”玩具,得到了一个新的玩具(输出“2”)。

六、填空题(二) 

 

#include <iostream>
using namespace std
#define M(A,B,C) A*B+C
int main()
{
int a=1;
int b=2;
int c=3;
cout<<M(a+b,b+c,c+a)<<endl;
return 0;
}

运行的结果输出是(19)

官方解释

代码中定义了一个宏 M(A,B,C),它接受三个参数 AB 和 C,并将它们替换为表达式 A*B+C。这意味着在代码中每次使用 M(x, y, z) 时,它都会被预处理器替换为 x*y+z

在 main 函数中,定义了三个整数变量 ab 和 c,并分别赋值为 1、2 和 3。

接下来,代码中使用宏 M 来计算一个表达式,并将结果输出到控制台。这里,M(a+b,b+c,c+a) 会被替换为 (a+b)*(b+c)+(c+a)。将 ab 和 c 的值代入,我们得到 (1+2)*(2+3)+(3+1),即 3*5+4,最终结果为 19。

自己的理解

M(A,B,C)与M(a+b,b+c,c+a)等价的话,A=a+b,B=b+c,C=c+a;

  1. 先算a+b,也就是1加2,等于3。
  2. 再算b+c,也就是2加3,等于5。
  3. 然后根据A*B+C的例子替换为 (a+b)*(b+c),得到15。
  4. 最后加上c+a,也就是3加1,得到4。
  5. 把15和4加起来,得到最终的结果19
  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值