CCF编程能力等级认证GESP—C++4级—样题1
单选题(每题 2 分,共 30 分)
1、在 C++中,指针变量的大小(单位:字节)是( )
A. 2
B. 4
C. 8
D. 与编译器有关
正确答案:D
在C++中,指针变量的大小(单位:字节)是依赖于编译器和操作系统的。在32位系统中,指针通常是4字节,而在64位系统中,指针通常是8字节。这是因为指针的大小需要足够大以存储内存地址。因此,正确答案是D。
#include <iostream>
using namespace std;
int main(){
int a = 10;
int *p = &a;
cout << sizeof(*p) << endl; // 8 / 4
cout << *p << endl; // 10
cout << p << endl; // 0x70fe14
return 0;
}
2、以下哪个选项能正确定义一个二维数组( )
A. int a[][];
B. char b[][4];
C. double c[3][];
D. bool d[3][4];
正确答案:D
A选项:这个声明试图定义一个二维数组,但没有为任何一个维度指定大小。在C++中,数组的每个维度都必须有明确的大小,除了第一维度(在函数参数中)可以省略外。因此,这个声明是不合法的。
B选项:这个声明定义了一个二维字符数组,其中第二维度的大小是4(即每行有4个字符)。然而,第一维度的大小没有指定,这意味着数组的大小是不确定的。
C选项:这个声明试图定义一个二维双精度浮点数数组,其中第一维度的大小是3(即3行)。但是,第二维度的大小没有指定,这是不允许的。在C++中,每个维度都必须有明确的大小。
D选项:这个声明定义了一个二维布尔数组,其中第一维度的大小是3(3行),第二维度的大小是4(4列)。这是一个完全合法的声明,因为它为两个维度都指定了明确的大小。
3、在 C++中,以下哪种方式不能用于向函数传递参数()
A. 值传递
B. 引用传递
C. 指针传递
D. 模板传递
正确答案:D
A. 值传递(Value Passing):这是最常见的参数传递方式。当参数通过值传递时,函数接收的是参数值的一个副本,原始值不会被改变。
B. 引用传递(Reference Passing):通过引用传递参数,函数接收的是对实际参数的引用(即别名),而不是副本。因此,对引用的任何修改都会反映到原始参数上。
C. 指针传递(Pointer Passing):指针传递允许函数接收指向实际参数的指针。通过指针,函数可以访问和修改原始参数的值。
D. 模板传递(Template Passing):实际上,这并不是一种参数传递方式。在C++中,模板是一种泛型编程的工具,用于创建可以处理多种数据类型的函数和类。模板不是用来传递参数的,而是用来定义可以处理多种数据类型的函数或类。因此,选项D是不正确的。
#include <iostream>
using namespace std;
// 使用值传递的函数
void printValue(int x) {
cout << "函数内x的值为:" << x << endl;
// 修改副本的值,不会影响原始变量
x = 100;
cout << "函数内x的值被修改后为:" << x << endl;
}
int main() {
int value = 50;
cout << "x的初始值为:" << value << endl;
printValue(value);
cout << "执行完函数后x的值为:" << value << endl;
return 0;
}
/*
特点:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。
从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。
使用注意事项:
对于大型对象或结构,值传递可能会导致不必要的内存开销和性能下降,因为需要复制整个对象或结构。
如果函数需要修改参数的值,并且希望这个改变影响调用者,那么不应该使用值传递。
*/
#include <iostream>
using namespace std;
// 使用引用传递的函数
void printValue(int &x) {
cout << "函数内x的值为:" << x << endl;
// 修改副本的值,会影响原始变量
x = 100;
cout << "函数内x的值被修改后为:" << x << endl;
}
int main() {
int value = 50;
cout << "x的初始值为:" << value << endl;
printValue(value);
cout << "执行完函数后x的值为:" << value << endl;
return 0;
}
/*
特点:
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作。
被调函数对形参的任何操作都影响了主调函数中的实参变量。
使用注意事项:
引用必须被初始化,且一旦初始化后就不能再指向其他对象。
引用传递增加了函数之间的耦合性,因此在使用时需要谨慎考虑。
*/
#include <iostream>
using namespace std;
// 使用引用传递的函数
void printValue(int *x) {
cout << "函数内x的值为:" << *x << endl;
// 修改指针指向的值,会影响原始变量
(*x) += 10;
cout << "函数内x的值被修改后为:" << *x << endl;
}
int main() {
int value = 50;
int *p = &value;
cout << "x的初始值为:" << value << endl;
printValue(p);
cout << "执行完函数后x的值为:" << value << endl;
return 0;
}
/*
特点:
指针传递本质上也是一种值传递,只是传递的是地址值(即指针变量的值)。
指针传递允许函数修改其指向的值,而这个改变会影响实参。
使用注意事项:
指针在传递之前必须被初始化,且必须指向一个有效的内存地址。
指针传递增加了代码的复杂性和出错的可能性(如空指针解引用、野指针等),因此在使用时需要特别小心。
在函数内部,对指针的操作(如解引用、赋值等)必须谨慎,以避免意外的内存访问错误。
*/
4、以下关于 C++函数的形参和实参的叙述,正确的是()
A. 形参是实参的别名
B. 实参是形参的别名
C. 形参和实参是完全相同的
D. 形参用于函数声明,实参用于函数调用
正确答案:D
A选项:这个叙述是不准确的。形参(形式参数)是函数定义中声明的参数,它们用于接收函数调用时传递的实参(实际参数)的值。形参和实参不是别名关系,而是值传递关系(对于基本数据类型)或引用传递关系(对于引用或指针类型)。
B选项:这个叙述也是不准确的。实参是在函数调用时传递给函数的实际值或变量,而形参是函数定义中声明的用于接收这些值的变量。它们之间不是别名关系。
C选项:这个叙述不准确。形参和实参在概念上是相关的,但它们不是“完全相同”的。形参是函数定义的一部分,而实参是函数调用的一部分。形参在函数被调用时接收实参的值,但它们不是同一个实体。
D选项:这个叙述是正确的。形参是在函数声明和定义中使用的,用于指定函数期望接收的参数类型和数量。实参是在函数调用时提供的,用于传递给函数的实际值或变量。
5、排序算法的稳定性是指( )
A. 相同元素在排序后的相对顺序保持不变
B. 排序算法的性能稳定
C. 排序算法对任意输入都有较好的效果
D. 排序算法容易实现
正确答案:A
排序算法的稳定性是指,在排序过程中,相同元素之间的相对顺序保持不变。换句话说,如果排序前有两个相等的元素A
和B
,且A
在B
的前面,那么排序后A
也应该在B
的前面。
A选项:这是排序算法稳定性的定义。
B选项:这与排序算法的稳定性无关,而是指算法在不同输入下的性能表现。
C选项:这描述的是算法的健壮性或适应性,而不是稳定性。
D选项:这与算法的稳定性无关,而是指算法的易实现性。
6、如果有如下二维数组定义,则 a[0][3]的值为()
int a[2][2] = {{0, 1}, {2, 3}};
A. 编译出错
B. 1
C. 3
D. 0
正确答案:C
在C++中,当你声明一个二维数组a[N][M]时,你确实拥有了一个连续的内存块,这个内存块足以存储N*M个整数(或其他类型的数据)。但是,你不能简单地将a[0][3]视为等同于a[1][1],因为它们在逻辑上不是同一个数组元素,并且尝试访问a[0][3]是越界访问,即使它们在物理内存中可能相邻。
在该题中,a[2][2]定义了一个2行2列的二维数组。这意味着有效的下标范围是a[0][0]到a[1][1]。如果你尝试访问a[0][3],你实际上已经越过了数组的边界,并且可能会访问到不属于你的数组的内存区域。这可能会导致未定义行为,包括但不限于访问到另一个变量的内存位置、程序崩溃或数据损坏。
不过从物理内存的角度来看,a[0][3]的地址与a[1][1]的地址相同(或接近),因此答案选C
7、以下哪个选项能正确访问二维数组 array 的元素()
A. array[1, 2]
B. array(1)(2)
C. array[1][2]
D. array{1}{2}
正确答案:C
在C++中,要访问二维数组的元素,你需要使用两个方括号[],并且它们之间不能有逗号或函数调用那样的语法。这是因为二维数组在内存中实际上是连续存储的,但通过两个索引(一个用于行,一个用于列)来访问。
A选项:这是不正确的,因为逗号在方括号内会被解释为逗号表达式,它会计算两个操作数(在这里是1和2),但只返回最后一个操作数的值,即2。因此,这实际上等同于array[2],但尝试将其当作二维索引来用是不正确的。
B选项:这是不正确的,因为这不是访问数组元素的正确语法。在C++中,你不能像调用函数那样调用数组。
C选项:这是正确的。它首先获取array的第1行(在C++中索引从0开始),然后获取该行的第2个元素。
D选项:这是不正确的,因为大括号{}在C++中通常用于初始化列表、结构体或类的聚合初始化,而不是用于索引数组。
8、以下哪个选项是 C++中正确的指针变量声明( )
A. int *p;
B. int p*;
C. *int p;
D. int* p*;
正确答案:A
A选项:这是正确的。它声明了一个名为p的指针变量,该指针指向int类型的对象。
B选项:这是不正确的。在C++中,星号应该紧挨着指针变量名,而不是类型标识符。
C选项:这也是不正确的。星号应该放在类型标识符和变量名之间,而不是类型标识符之前。
D选项:这同样是不正确的。它试图声明一个指向指针的指针,但语法不正确。如果意图是声明一个指向int指针的指针,正确的写法应该是int ** p;。
9、在 C++中,以下哪个关键字或符号用于声明引用()
A. pointer
B. &
C. *
D. reference
正确答案:B
正确答案是 B。
在 C++ 中,用于声明引用的关键字或符号是 &。引用可以被视为一个已存在变量的别名,它提供了对变量的另一种访问方式。一旦引用被初始化为一个变量,它就不能再被重新绑定到另一个变量。
A选项:这不是一个关键字,而是一个普通的单词,通常用于描述指向某个对象的指针。
B选项:这是正确的。在声明变量时,如果我们在类型名称后加上&,我们就声明了一个引用。
C选项:这是指针的声明符号,不是用于声明引用的。
D选项:虽然reference这个词在C++中用于描述引用的概念,但它本身并不是一个关键字或符号,不能直接用于声明引用。
//例如,以下代码声明了一个整数变量 x 和一个引用 ref,该引用被初始化为 x 的别名:
int x = 10;
int& ref = x; // ref 是 x 的引用
//在这个例子中,ref 和 x 指向内存中的同一个位置,对 ref 的任何修改都会影响到 x,反之亦然。
10、以下哪个递推关系式表示斐波那契数列( )
A. F(n) = F(n-1) + F(n-2) + F(n-3)
B. F(n) = F(n-1) + F(n-2)
C. F(n) = F(n-1) * F(n-2)
D. F(n) = F(n-1) / F(n-2)
正确答案:B
斐波那契数列是一个著名的数列,其中每个数字(从第三个数字开始)是前两个数字的和。这个数列的递推关系式是:
F(n) = F(n-1) + F(n-2)
其中 F(0) 和 F(1)(或有时 F(1) 和 F(2))是给定的初始值。通常,F(0) = 0, F(1) = 1。
A选项:这不是斐波那契数列的递推关系式,因为它包含了第三个前项。
B选项:这是斐波那契数列的正确递推关系式。
C选项:这是另一种数列的递推关系式,但不是斐波那契数列的。
D选项:这也不是斐波那契数列的递推关系式,因为它使用了除法而不是加法。
11、以下哪个函数声明在调用时可以传递二维数组的名字作为参数?
A. void BubbleSort(int a[3][4]);
B. void BubbleSort(int a[][]);
C. void BubbleSort(int * a[]);
D. void BubbleSort(int ** a);
正确答案:A
在 C++ 中,当二维数组作为函数参数传递时,实际上传递的是指向数组第一行的指针。因此,函数声明应该接受一个指向整型数组的指针作为参数。
A选项:声明了一个接受 3x4 整型二维数组的函数。虽然这里指定了数组的大小,但实际上在函数内部,这个大小信息是不可用的。函数会将其视为指向整型数组的指针。
B选项:是错误的,因为 C++ 不支持这种不完整的数组类型声明作为函数参数。
C选项:声明了一个接受指向整型指针数组的函数,这实际上是接受一个指向指针的指针,每个指针指向一个整型数组。这与传递二维数组的名字不同。
D选项:声明了一个接受指向整型指针的指针的函数,这通常用于处理动态分配的二维数组或指针数组。它也不是接受二维数组名字的正确方式。
12、在 C++中,以下哪个关键字用来捕获异常( )
A. throw
B. catch
C. try
D. finally
正确答案:B
A选项:这个关键字用于在代码中抛出一个异常。
B选项:这个关键字用于捕获一个异常。当你想要处理被throw抛出的异常时,你会在try块之后使用catch块。
C选项:这个关键字定义一个代码块,该代码块中的异常可以被后续的catch块捕获。
D选项:这不是C++的关键字。在Java等语言中,finally块用于执行无论是否发生异常都必须执行的代码。在C++中,你可以使用其他技术(如RAII或异常安全保证)来确保资源的正确管理,但没有直接的finally关键字。
13、在下列代码的横线处填写( ),可以使得输出是“20 10”。
#include <iostream>
using namespace std;
void xchg(________________) { // 在此处填入代码
int t = x;
x = y;
y = t;
}
int main() {
int a = 10, b = 20;
xchg(a, b);
cout << a << " " << b << endl;
return 0;
}
A. int x, int y
B. int & x, int & y
C. int a, int b
D. int & a, int & b
正确答案:B
为了使得xchg函数能够交换传入的两个变量的值,我们需要使用引用(&)来传递参数,因为按值传递(即传递变量的副本)的话,函数内部对变量的修改不会影响到函数外部的原始变量。
A选项:这是按值传递,函数内部对x和y的修改不会影响到main函数中的a和b。
B选项:这是按引用传递,函数内部对x和y的修改会影响到main函数中的a和b。这正是我们想要的。
C选项:虽然变量名与main函数中的变量名相同,但这只是局部变量名,它们与main函数中的变量没有联系。按值传递会创建新的局部变量a和b。
D选项:虽然使用了引用,但这里的a和b只是局部变量名的引用,与main函数中的变量名冲突。按照C++的规则,局部变量的名字会覆盖外部作用域中同名的变量。这会导致编译错误,因为xchg函数内部无法访问到main函数中的a和b。
14、在下列代码的横线处填写( ),可以使得输出是“21”。
#include <iostream>
using namespace std;
int main() {
int a[5];
a[0] = 1;
for (int i = 1; i < 5; i++)
a[i] = a[i – 1] * 2;
int sum = 0;
for (int i = 0; i < 5; ________) // 在此处填入代码
sum += a[i];
cout << sum << endl;
return 0;
}
A. i++
B. i += 2
C. i += 3
D. i |= 2
正确答案:B
a[5] = {1, 2, 4, 8, 16}
sum | i++ | i += 2 | i += 3 | i |= 2 |
---|---|---|---|---|
第一次循环sum | 1 | 1 | 1 | 1 |
第二次循环sum | 3 | 5 | 9 | 5 |
第三次循环sum | 7 | 21 | 循环结束 | 9 |
第四次循环sum | 15 | 循环结束 | 13 | |
第五次循环sum | 31 | … |
15、在下列代码的横线处填写( ),完成对有 n 个 int 类型元素的数组array由小到大排序。
void BubbleSort(int array[], int n) {
for (int i = n; i > 1; i--)
for (____________________) // 在此处填入代码
if (array[j] > array[j + 1]) {
int t = array[j];
array[j] = array[j + 1];
array[j + 1] = t;
}
}
A. int j = i – 2; j >= 0; j--
B. int j = i - 1; j >= 0; j--
C. int j = 0; j < i - 1; j++
D. int j = 0; j < i; j++
正确答案:C
在冒泡排序算法中,外层循环控制排序的轮数,内层循环则负责进行相邻元素的比较和交换。对于每一轮排序,内层循环应该从数组的第一个元素开始,到倒数第二个元素结束(因为最后一个元素在那一轮中已经是正确的位置了)。
因此,内层循环的初始化应该是 int j = 0,表示从数组的第一个元素开始比较。循环条件应该是 j < i - 1,因为每一轮排序后,最大的元素会“冒”到数组的末尾,所以内层循环不需要再考虑最后一个元素。最后,循环的递增应该是 j++,以便移动到下一个相邻元素进行比较。
判断题(每题 2 分,共 20 分)
1、C++语言中的指针变量可以指向任何类型的数据。( )
正确答案:正确
在C++中,指针变量可以指向任何类型的数据,包括基本数据类型(如int、char等)、结构体、类对象等。指针变量本身存储的是内存地址,而这块内存地址可以存储任何类型的数据。但需要注意,指针必须正确地指向与其类型相匹配的对象,否则可能会引发运行时错误。
2、在 C++语言中,函数的参数默认以地址传递方式进行传递。( )
正确答案:错误
在 C++ 中,函数的参数默认是通过值传递的,即传递的是参数的副本,而不是参数本身的地址。如果想要通过地址传递参数,需要在函数声明和定义时明确指定指针或引用类型。
3、C++语言中的全局变量在整个程序的生命周期内都是有效的。( )
正确答案:正确
全局变量是在函数之外定义的变量,它们在程序开始执行时就被创建,并在整个程序的执行期间一直存在,直到程序结束。因此,全局变量在整个程序的生命周期内都是有效的。
4、递推算法通常有初始值。( )
正确答案:正确
递推算法是一种通过已知条件逐步推导未知条件的算法。在递推过程中,通常需要一些初始值作为递推的开始。这些初始值可以是已知的常数,也可以是用户输入的数据。
5、冒泡排序是一种稳定的排序算法。( )
正确答案:正确
冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。在冒泡排序中,相等的元素在排序过程中不会改变它们的相对位置,因此冒泡排序是一种稳定的排序算法。
6、C++语言中,如果异常发生,但没有处理异常的代码,则程序会由于一直等待处理而死机。( )
正确答案:错误
在C++中,如果异常发生且没有被捕获(即没有相应的异常处理代码),程序不会由于一直等待处理而死机。相反,它会终止执行,并可能输出错误信息。这通常被称为“未捕获的异常”。
7、C++语言中的局部变量在函数调用结束后会被销毁。( )
正确答案:正确
局部变量是在函数内部定义的变量,它们的作用域仅限于该函数。当函数执行完毕并返回时,其内部的局部变量会被销毁,释放其占用的内存空间。
8、&和&&都是 C++语言的运算符,*和**也都是。( )
正确答案:错误
&是C++中的地址运算符,用于获取对象的地址;&&是逻辑与运算符,用于判断两个布尔表达式的逻辑与关系。*是解引用运算符,用于通过指针访问其所指向的对象;但**不是单独的运算符,它通常表示一个指向指针的指针。
9、如果希望设计一个函数 xchg,实现交换两个 int 变量的值,则它的声明可以写为 void xchg(int a, int b);。( )
正确答案:错误
由于C++中函数参数是按值传递的,所以函数xchg内的a和b实际上是调用者提供的两个整数的副本。在函数内部交换a和b的值不会影响调用者提供的原始变量。要实现真正的交换,需要使用指针或引用作为参数。正确的函数声明可以是void xchg(int& a, int& b);。
10、已知数组 a 定义为 int a[100];,则赋值语句 a[‘0’] = 3;会导致编译错误。( )
正确答案:错误
在C++中,数组索引可以是整数或整数字面量(包括字符字面量,它们会被隐式转换为它们对应的ASCII值)。字符’0’的ASCII值是48。因此,a[‘0’]实际上等同于a[48],这个赋值语句是有效的,不会导致编译错误。
编程题 (每题 25 分,共 50 分)
第一题 绝对素数
【问题描述】
如果一个两位数是素数,且它的数字位置经过对换后仍为素数,则称为绝对素数,例如 13。给定两个正整数 A、B,请求出大于等于A、小于等于B 的所有绝对素数。
【输入格式】
输入 1 行,包含两个正整数 A 和 B。保证 10<A<B<100。
【输出格式】
若干行,每行一个绝对素数,从小到大输出。
【样例输入】
11 20
【样例输出】
11
13
17
#include <iostream>
#include <cmath>
using namespace std;
bool is_prime(int num){
for (int i = 2; i <= sqrt(num); i++){
if (num % i == 0){
return false;
}
}
return true;
}
int main() {
int a, b;
cin >> a >> b;
for (int i = a; i <= b; i++){
int ge, shi, newnum;
ge = i % 10;
shi = i / 10 % 10;
newnum = ge * 10 + shi;
if (is_prime(i) && is_prime(newnum)){
cout << i << endl;
}
}
return 0;
}
第二题 填幻方
【问题描述】
在一个 N×N 的正方形网格中,每个格子分别填上从1 到N×N 的正整数,使得正方形中任一行、任一列及对角线的几个数之和都相等,则这种正方形图案就称为“幻方”(输出样例中展示了一个 3×3 的幻方)。我国古代称为“河图”、“洛书”,又叫“纵横图”。
幻方看似神奇,但当 N 为奇数时有很方便的填法:
1)一开始正方形中没有填任何数字。首先,在第一行的正中央填上1。
2)从上次填数字的位置向上移动一格,如果已经在第一行,则移到同一列的最后一行;再向右移动一格,如果已经在最右一列,则移动至同一行的第一列。如果移动后的位置没有填数字,则把上次填写的数字的下一个数字填到这个位置。
3)如果第 2 步填写失败,则从上次填数字的位置向下移动一格,如果已经在最下一行,则移到同一列的第一行。这个位置一定是空的(这可太神奇了!),把上次填写的数字的下一个数字填到这个位置。
4)重复 2、3 步骤,直到所有格子都被填满,幻方就完成了!快来编写一个程序,按上述规则,制作一个 N×N 的幻方吧。
【输入格式】
输入为一个正奇数 N,保证 3≤N≤21。
【输出格式】
输出 N 行,每行 N 个空格分隔的正整数,内容为 N×N 的幻方。
【样例输入】
3
【样例输出】
8 1 6
3 5 7
4 9 2
#include <iostream>
// 重点:
// 1. 需要有两个指针来锁定上一个填数的坐标
// 2. 需要有另外两个指针来查询接下来要填数的位置
using namespace std;
const int maxn = 25;
int arr[maxn][maxn], n, nx, ny, ans_x, ans_y;
int main() {
cin >> n;
for (int i = 1; i <= n * n; i++){
// 在第一行的正中央填上 1, 也可以在循环外设置, 但是循环的次数要少一次
if (i == 1) {
arr[1][n / 2 + 1] = 1;
nx = 1;
ny = n / 2 + 1;
continue;
}
ans_x = nx, ans_y = ny;
// 2. 如果已经在第一行, 则移动到最后一行, 否则移动到最后一行
if (ans_x == 1){
ans_x = n;
}else{
ans_x--;
}
// 2. 如果是最后一列, 则移动到第一列, 否则向右移动一格
if (ans_y == n){
ans_y = 1;
}else{
ans_y++;
}
// 如果没有数字就将i填写到这个位置上
if (arr[ans_x][ans_y] == 0){
arr[ans_x][ans_y] = i;
nx = ans_x, ny = ans_y; // 保存当前位置坐标
}else{ // 3. 如果填写失败, 就从上一次填数的位置重新找
// 重新回到上一次填数的位置
ans_x = nx, ans_y = ny;
if (ans_x == n){
ans_x = 1;
}else{
ans_x++;
arr[ans_x][ans_y] = i;
nx = ans_x, ny = ans_y; // 保存当前位置坐标
}
}
}
// 输出二维数组
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
cout << arr[i][j] << ' ';
}
cout << endl;
}
return 0;
}