提示:文章
前言
前期疑问:
本文目标:
一、背景
接上文
二、题目
命名空间
10、(单选)【作用域】对于名字空间(namespace)以下说法错误的是[x(1] [g2]
A. 使用声明(the using-declaration)可用于类的声明中
B. 使用指示(the using-directive)具有传递性
C. 如果某函数访问其自身所在名字空间中成员元素时,可以直接访问
D. 通过使用指示(the using-directive)引入存在有相同成员元素的多个名字空间,在实际存取这些存在冲突的成员元素之前,也会导致当前作用域中元素冲突的错误发生
只有在使用时才会报错
使用声明(the using-declaration)允许从基类中引入成员到派生类的作用域中
11、(单选)【作用域】关于名字空间说法正确的是?B
A. 定义可以不连续,跨越多个文件;
B. 可以嵌套定义;
C. 可以有很多别名,别名和原来的名字可以交替使用;
D. 名字空间必须有名字;
12、(单选)【作用域】命名空间求输出(B)
int a = 1;
namespace Outer {
int a = 2;
}
namespace Outer {
namespace Inner {
int a = 3;
}
}
using namespace Outer;
int main()
{
int a = 4;
cout << a;
cout << ::a;
cout << Outer::a;
cout << Outer::Inner::a;
return 0;
}
A、4231
B、4123
C、4321
D、4132
::是全局作用域
13、(单选)【作用域】下面代码运行结果是(A)
int count = 10;
namespace test {
int count = 1;
int getCount()
{
::count++;
return count;
}
}
int main()
{
int count = 2;
count = test::getCount();
cout<<count<<" "<<::count<<endl;
return 0;
}
A. 1 11
B. 1 10
C. 2 11
D. 2 2
::是全局作用域
getCount返回的是前面的count =1
这个题目好花啊,没做对
14、(单选)【作用域】以下为3个代码文件中的代码片段(D)
// service.h
Struct Service {
static const std::string NAME;
};
// service.cpp
#include “service.h”
const std::string Service::NAME=“SERVICE1”;
// main.cpp
#include “service.h”
#include <iostream>
const std::string MSG=“service” + Service::NAME + “ready”;
int main()
{
std::cout<<MSG<<std::endl;
}
对于程序运行结果,以下说法正确的是
A. 运行输出 service SERVICE1 ready
B. 运行输出 service
C. 运行输出中包含随机字符或乱码
D. 运行结果取决于编译链接过程及std::string的内部实现
static一个文件一份,在当前文件中未初始化就使用,在其他文件中初始化过,未定义行为。
static一个文件一份,在当前文件中未初始化就使用,在其他文件中初始化过,未定义行为。
15、(单选)【作用域】以下程序输出为(D)
#include <iostream>
int i = 3;
namespace A {
int i = 1;
}
namespace B {
int i = 2;
void F1(void);
void F2(void);
void F3(void);
void F4(void);
}
void B::F1(void)
{
using namespace A;
std::cout<<i<<std::endl;
}
void B::F2(void)
{
using A::i;
std::cout<<i<<std::endl;
}
void B::F3(void)
{
std::cout<<A::i<<std::endl;
}
void B::F4(void)
{
std::cout<<i<<std::endl;
}
int main()
{
using namespace B;
F1();
F2();
F3();
F4();
return 0;
}
A. 1 1 1 3
B. 2 3 1 2
C. 1 1 1 2
D. 2 1 1 2
解释
using namespace A是把A中的所有变量提升到其最高的带using关键词的空间下;而用usingA::i,是直接使用,会覆盖当前的i
F1函数中,A提不到B的namespace,可以理解为提升到了一个隐藏的全局namespcae,因此此处还是用B的i
F2函数中,覆盖当前的i,因此是A的i
F3函数中,直接用A的i
F4函数中,默认用B的i
故是2112
16、(单选)【作用域】下述程序将输出什么(A)
#include <iostream>
int count = 10;
class Data {
public: void printCount() {
do {
int count = 30;
std::cout << count << std::endl;
} while (0);
std::cout << count << std::endl;
}
private: int count = 40;
}
int main() {
Data data;
data.PrintCount();
std::cout << count << std::endl;
return 0;
}
A. 30 40 10
B. 30 40 40
C. 30 30 10
D. 40 40 10
限定符
17、(单选)阅读以下程序片段,成员函数setName的访问限定是什么(A)
struct Man
{
void setName(const std::string& strName);
void setAge(unsigned int iAge);
std::string getName() const;
unsigned int getAge() const;
private:
unsigned int m_iAge;
std::string m_strName;
}
A. public
B. private
C. protected
D. friend
struct默认为public,class默认为private
18、(单选)以下关于结构体初始化,效率最低的是()
struct node {
int a , b ,c ;
};
A node s = { 1 ,2 ,3};
B memset_s( s, 0 , xx)
C node s;
解释:
s.a = 1;
s.b = 2;
s.c = 3;
这个题考点是memset_s 初始化效率最低
指令占用数量上,复合字面量 = {0} = memset <= memset_sp <= memset_s
18、(单选)下面代码Test函数执行的输出是(A)
// a.h
static int g_count;
void AddCount(int count);
// a.c
#include “a.h”
void AddCount(int count)
{
g_count += count;
}
// b.c
#include “a.h”
void IncrCount(void)
{
g_count += 1;
}
void Test(void)
{
print(“Count init = %d,”, g_count);
IncrCount();
print(“incr = %d,”, g_count);
AddCount(3);
print(“added = %d”, g_count);
}
A. Count init=0,incr=1,added=1
B. Count init=0,incr=1,added=4
C. 编译报错
D. 不确定与编译器实现有关
解释:静态变量在不同编译单元(.cpp文件)中是相互独立、各自拥有一份的
这个题目的答案是A,我还在Clion上验证一下,但是我并没有完全按照上述代码设计工程,问题在于我把Test函数单独放在main.c文件中了。会报错找不到 IncrCount()函数。我还以为是题目错了。后面才发现test函数是要和IncrCount()函数实现放在一个文件中的。也就是放在b.c文件中。并把test函数改成main函数,执行答案就是A选项。
至于为什么是A选项,因为程序是在b.c文件中执行的。所以使用的g_count都是b.c编译单元的一个单独的副本。所以g_count一开始0,后面自增后为1。经过AddCount函数后a.c编译单元的g_count变成了3,但是b.c中的还是1。
另外一个问题就是静态变量没有初始化是默认初始化为0吗?内置类型(如 int
, float
等)在全局或静态存储中会被初始化为零,在局部作用域中则未定义。
异常(这边的题都没看)
19、(单选)【异常】不宜抛出异常的是(ABCD)
A、移动构造
B、拷贝构造
C、析构函数
D、移动赋值
20、(单选)【异常】在哪些函数或操作符不适合抛出异常(ABCD)
A、 全局变量和线程局部变量的构造函数
B、 delete操作符重载函数
C、 只含一个参数的new操作符重载函数
D、 用于交换两个自定义类型实例的swap函数
解析 P.17-CPP:
这些函数会被隐式地调用,或被标准库调用,调用时不期望异常抛出。在这些地方抛出异常将无法处理,或破坏标准库的某些性质。
一些标准库的类或算法会依赖“移动构造函数、移动赋值操作符、swap 函数不会抛异常”这些性质才能保证正常工作。析构函数常常会在异常处理的过程中被隐式调用,以清理资源。operator delete 函数则常常会被析构函数调用。在已经有异常抛出的情况下抛异常,会导致std::terminate 。
21、(单选)【异常】以下不属于c++异常处理的:(C)
A. try
B. catch
C. finally
D. throw
C++没有finally,只有final关键字修饰虚函数
22、(单选)【异常】下列关于异常使用的说法错误的是:(D)
A. “catch(…)” 语句表示捕获所有异常。
B. 异常会带来效率的一部分损失。
C. 如果函数的声明没有异常规格声明,表示该函数可以抛出任何类型的异常。
D. 可以在类的构造函数和析构函数中抛出异常,以解决构造函数和析构函数失败时不能返回值的问题。
本题考察 C++ 异常的基本知识。 虽然可以在构造函数中抛出异常,但应禁止在析构函数中抛出异常
23、(多选)【异常】关于noexcept描述正确的有(BD)
A. 声明带有noexcept(false)的函数不会抛出异常
B. 声明带有noexcept的函数可以告诉调用者,该函数不会抛出异常
C. 父类虚函数中有noexcept声明,子类重写该虚函数时,可不再重复声明
D. 如果一个函数不应抛出异常,应使用noexcept关键词声明
A true才是不抛异常
C中子类重写父类noexcept函数的时候还是需要加上noexcept标志,并且必须和父类的true/false保持一致,否则会报错https://zh.cppreference.com/w/cpp/language/noexcept_spec
**规则1:**如果一个虚函数承诺它一定不会抛出异常,则后续派生出来的虚函数也必须做出相同的承诺
**规则2:**反之,如果基类的虚函数允许抛出异常,则派生类的对应函数允许抛出异常,也可以不允许抛出异常
24、(多选)【异常】程序中异常处理,说法错误的是(ACD)
A. 可以用异常来控制流程走向,异常的优先级高
B. 不要过多的使用异常,在捕获异常处理时,尽量捕获最小类
C. 根据程序情况决定使用异常还是使用错误码,或者二者混合使用
D. 当需要扔出error异常时,应自定义一个Error子类供使用
A 控制流程走向的是if和for
C 不可以混用
D error要尽可能通用
25、(多选)【异常】哪些函数必须显式声明为noexcept(AB)
A. 移动构造函数
B. swap函数
C. …
D. …
应该noexecpt的函数:
移动构造
移动赋值
析构(隐式noexecpt
26、(多选)【异常】cast类型转换以下说法正确的是:(BD)
A.dynamic_cast只在编译时校验
B.static_cast只在编译时校验
C.dynamic_cast不会抛出异常
D.static_cast不会抛出异常
可以记只选static的
A dynamic_cast 程序运行时期,主要用来父类和子类直接的转换
dynamic_cast比static_cast安全。如果转换指针,失败后返回空指针,如果转换引用,失败后抛出bad_cast异常
27、(单选)【字符串】对于以下代码,当用户输入gamma并按回车时,输出的字符是(B)
#include <iostream>
using namespace std;
void Rev()
{
char c = cin.get();
if (c != '\n') {
Rev();
}
cout << c;
}
int main()
{
Rev();
return 0;
}
A. gamma
B. ammag
C. g
D. a
28、(单选)【字符串】gamma为键盘输入加换行,问输出?(D)
void rec(void)
{
char c = cin.get();
while(c != '\n') {
rec();
cout<<c;
}
return;
}
int main()
{
rec();
return 0;
}
A 输出 gamma
B 输出 ammag
C 输出 g
D 输出 a
注意函数里是while,单步调可以发现输出a后就卡住一直在等待再输入状态。如果这里改为 if (c != ‘\n’) 输出的就会是 ammag。
29、(单选)【字符串】对于以下代码,当用户输入gamma并按回车时,输出的字符是(B)
#include <iostream>
using namespace std;
void Rev()
{
char c = cin.get();
if (c != '\n') {
Rev();
}
cout << c;
}
int main()
{
Rev();
return 0;
}
A. gamma
B. ammag
C. g
D. a
30、(单选)【字符串】gamma为键盘输入加换行,问输出?(D)
void rec(void)
{
char c = cin.get();
while(c != '\n') {
rec();
cout<<c;
}
return;
}
int main()
{
rec();
return 0;
}
A 输出 gamma
B 输出 ammag
C 输出 g
D 输出 a
注意函数里是while,单步调可以发现输出a后就卡住一直在等待再输入状态。如果这里改为 if (c != ‘\n’) 输出的就会是 ammag
31、(单选)【字符串】以下代码的输出是什么?(C)
#include <iostream>
#include <string>
void Alpha(string beta) {
cout << beta << endl;
}
int main()
{
Alpha("gamma");
return 0;
}
A.编译错误
B.g
C.gamma
D.无输出
32、(单选)【字符串】运行结果(C)
string getString();
void main() {
string str = "gamama";
cout << getString(str) << endl;
}
string getString(string &str) {
return str;
}
A. gamama
B. g
C. 编译错误
D. 运行错误
GetString(String &str)定义在后面,main函数找不到匹配的函数
33、(单选)【字符串】以下输出?(C)
cout << “Alpha\0and\0Gamma”;
A. Alpha
B. Alpha\0and\0Gamma
C. Gamma
D. 无输出
cout 遇到/0就会停止输出
34、(单选)【字符串】以下输出结果为 (A)
string s1 = “1”
string s2 = “2”
s1 = s2 + s1;
cout << s1
A. 21
B. 12
C. 1
D. 2
35、(单选)using String = char[255];
String s;
则s是(B)
A.字符变量
B.字符数组变量
C.字符指针数组变量
D.字符指针变量
36、typedef char STRING [255];STRING*s;,则s是( C )
A.字符数组
B. 字符变量
C.字符指针数组
D.字符指针变量
typedef unsigned long ulong;
// 为 unsigned long 类型创建别名 ulong
typedef int arrayType[10];
// 为包含10个整数的数组创建别名 arrayType
typedef void (*funcPtr)();
// 为指向函数的指针创建别名 funcPtr
37、(单选)阅读下面代码,输入“abcdef” 以下代码的输出是什么(A)
void main(){
char a[20];
std::cin.get(a,5,"d");
cout << a << endl;
}
A.abc
B.abcd
C.abcde
D.abcdef
cin.get(数组名,长度,结束符)。其中结束符为可选参数,读入的字符个数最多为(长度**-1)**个,结束符规定结束字符串读取的字符,默认为ENTER。虽然要读取5个长度,但先遇到结束字符d所以结束。如果结束符是’f’结果是abcd
38、(单选)X64 Linux(小端顺序)下(C)
int main()
{
unsigned int value = 0x10000;
unsigned short *p = (unsigned short *)&value;
*p += 10;
printf("0x%x", value);
return 0;
}
输出为(C)
A.0x10
B.0x10010
C.0x1000a
D.0xa
short类型 *p = 0x000a = 0xa
printf(“0x%x”,*p); 是0xa
printf(“0x%x”, value); 是0x1000a
39、(单选)自增相关,运行结果是?(9 6)
int Muti(int a, int b) {
return a*b;
}
int main()
{
int num = 2;
cout<<Muti(++num,num)<<endl;
num = 2;
cout<<Muti(num++,num)<<endl;
return 0;
}
输出是9 6
因为++num先计算,再传参
num++先传参再计算
这个题目感觉也要像下面的题目一样,得看汇编代码才能看懂。
40、(单选)以下代码输出(5,6,8,5,6)
不同编译器结果不一样
cl.exe编译运行:5,6,7,4,5
g++.exe编译运行5,6,8,5,6
#include <iostream>
using namespace std;
int AddNums(int a,int b){
cout << a <<" "<< b << endl;
return a + b;
}
int main(){
int num = 2;
cout << AddNums(num, num++) << endl; //输出a = 3 b = 2 输出5
num = 2;
cout << AddNums(++num,num++) <<endl; //输出a = 4;b = 2 输出6
num = 2;
cout << AddNums(++num,++num)<<endl; //输出a = 4;b = 4 输出8
num = 2;
cout << AddNums(num++,num)<<endl; //输出a = 2;b = 3; 输出5
num = 2;
cout<<AddNums(++num,num)<<endl; //输出a = 3;b = 3; 输出6
}
原题找不到,但这是个很好例子
知识点:传参从右往左传参(入栈)
函数在有运算时需要区分先调用还是先计算(++i i++)
https://blog.csdn.net/lwwl12/article/details/108521816
这个题目我也尝试使用Clion进行反汇编查看。
查看下述两行代码
num = 2;
cout << AddNums(++num,num++) <<endl; //输出a = 4;b = 2 输出6
汇编代码如下
movl $0x2,-0x4(%rbp)
mov -0x4(%rbp),%eax
lea 0x1(%rax),%edx
mov %edx,-0x4(%rbp)
addl $0x1,-0x4(%rbp)
mov -0x4(%rbp),%ecx
mov %eax,%edx
call 0x7ff7e47d2298 <AddNums(int, int)>
看不懂上述汇编代码,使用copilot解读
让我们逐行分析这段汇编代码:
movl $0x2, -0x4(%rbp):将立即数 0x2(即 2)存储到相对于基指针 rbp 的偏移量 -0x4 处的内存位置。
mov -0x4(%rbp), %eax:将相对于基指针 rbp 的偏移量 -0x4 处的内存位置的值加载到寄存器 eax 中。此时,eax 的值为 2。
lea 0x1(%rax), %edx:计算 rax 寄存器的值加 1 的地址,并将结果存储到寄存器 edx 中。此时,edx 的值为 3(因为 eax 的值为 2)。
mov %edx, -0x4(%rbp):将寄存器 edx 的值存储到相对于基指针 rbp 的偏移量 -0x4 处的内存位置。此时,内存位置 -0x4(%rbp) 的值为 3。
addl $0x1, -0x4(%rbp):将立即数 0x1(即 1)加到相对于基指针 rbp 的偏移量 -0x4 处的内存位置的值。此时,内存位置 -0x4(%rbp) 的值为 4。
mov -0x4(%rbp), %ecx:将相对于基指针 rbp 的偏移量 -0x4 处的内存位置的值加载到寄存器 ecx 中。此时,ecx 的值为 4。
mov %eax, %edx:将寄存器 eax 的值复制到寄存器 edx 中。此时,edx 的值为 2。
call 0x7ff7e47d2298 <AddNums(int, int)>:调用函数 AddNums,传递两个参数:edx(值为 2)和 ecx(值为 4)。
总结一下,这段代码的主要作用是对内存位置 -0x4(%rbp) 进行一系列操作,最终调用 AddNums 函数,传递的参数为 2 和 4。
解释的还不错,但是eax,ebx,ecx是什么意思,还是得看下。
其中不好理解的就是这一行:lea 0x1(%rax), %edx:计算 rax 寄存器的值加 1 的地址,并将结果存储到寄存器 edx 中。此时,edx 的值为 3(因为 eax 的值为 2)。【疑问:为什么edx 的值为 3(因为 eax 的值为 2】
%RAX
和 %EAX
是 x86-64 架构中的两个寄存器,它们之间有着密切的关系:
%RAX
是 64 位寄存器,用于存储和操作 64 位数据。%EAX
是%RAX
的低 32 位部分,用于存储和操作 32 位数据。
当你对 %EAX
进行操作时,实际上是对 %RAX
的低 32 位进行操作。例如,如果你将一个值存储到 %EAX
中,%RAX
的高 32 位将被清零,而低 32 位将存储你指定的值。
上述表述解决了上述的疑问。还有一个问题,就是copilot所述中call 0x7ff7e47d2298 <AddNums(int, int)>:调用函数 AddNums,传递两个参数:edx(值为 2)和 ecx(值为 4)。为啥知道edx和ecx传入了函数。
这个问题暂时搁置待解决。
题目中有个代码是这样的:
num = 2;
cout << AddNums(++num,++num)<<endl; //输出a = 4;b = 4 输出8
这个看汇编代码就很好理解了。
$0x2,-0x4(%rbp)
addl $0x1,-0x4(%rbp)
addl $0x1,-0x4(%rbp)
mov -0x4(%rbp),%edx
mov -0x4(%rbp),%eax
mov %eax,%ecx
call 0x7ff7e47d2298 <AddNums(int, int)>
这种代码还是看汇编更好理解。
总结
未完待续