1.C++语言是从早期的C语言逐渐发展演变而来的,与C语言相比,它在求解问题方法上进
行的最大改进是(B)
A.面向过程
B.面向对象
C.安全性
D.复用性
2.C++语言的跳转语句中,对于break和continue说法正确的是( )
A.break语句只应用与循环体中
B.continue语句只应用与循环体中
C.break是无条件跳转语句,continue不是
D.break和continue的跳转范围不够明确,容易产生问题
break语句用于终止循环,并跳出循环体,它只应用于循环体中。当执行到break语句时,程序将立即退出当前循环,并继续执行循环后面的语句。
continue语句用于跳过当前循环中剩余的语句,然后继续进行下一次循环的执行。它也只应用于循环体中。当执行到continue语句时,程序将跳过当前循环体中剩余的语句,直接进入下一次循环的执行。
3.for(int x=0,y=0; !x && y<=5; y++)语句执行循环的次数是( )
A.0
B.5
C.6
D.无数次
根据给定的循环条件
!x && y<=5
,初始时x=0
,所以!x
的值为真(即1)。当y=0
时,满足y<=5
的条件,所以循环会执行。循环中每次迭代执行的顺序是:首先进行
y++
操作,然后判断循环条件。每次循环中,y
的值会增加1。当y
递增到y=5
时,仍满足y<=5
的条件,继续执行循环。当
y
递增到y=6
时,不再满足y<=5
的条件,循环终止。因此,循环会执行6次。选项C. 6 是正确的答案。
4.考虑函数原型void test(int a,int b=7,char="*");,下面的函数调用中,属于不合法调用的是( )
A.test(5);
B.test(5,8);
C.test(6,”#”);
D.test(0,0,”*”);
在考虑函数原型
void test(int a, int b=7, char c="*");
的情况下,下面的函数调用是合法或不合法的:A. test(5);
这个调用是合法的。它省略了其中的参数b
和c
,但由于这两个参数有默认值,所以可以使用默认值。B. test(5, 8);
这个调用是合法的。它提供了参数a
和b
的值,同时省略了参数c
,但由于c
有默认值,所以可以使用默认值。C. test(6, “#”);
这个调用是不合法的。在原型中,参数c
的类型是char
,但传递的是一个字符串字面值"#"
,这不符合函数的预期类型。D. test(0, 0, “*”);
这个调用是合法的。它提供了参数a
、b
和c
的值,符合函数的原型。所以,选项C. test(6,“#”) 是不合法的函数调用。
5.下面有关重载函数的说法中正确的是( )
A.重载函数必须具有不同的返回值类型
B.重载函数形参个数必须不同
C.重载函数必须有不同的形参列表
D.重载函数名可以不同
正确的说法是:
C. 重载函数必须有不同的形参列表
重载函数是指在同一个作用域中,有多个具有相同函数名但参数列表不同的函数。它们可以有不同的返回值类型,也可以具有相同或不同数量的参数。
在C++中,函数的重载是通过参数列表的不同来实现的。这意味着重载函数必须具有不同的形参列表,也就是参数类型、参数个数、参数顺序至少有一项不同。
关于选项的说明:
A. 重载函数的返回值类型可以相同也可以不同。重载函数的返回值类型不是区分重载的条件。
B. 重载函数的参数个数可以相同也可以不同。重载函数的参数个数不是区分重载的条件。
D. 重载函数的函数名必须相同,只有参数列表不同。因此,选项C. 重载函数必须有不同的形参列表是正确的说法。
6.下列关于构造函数的描述中,错误的是( )
A.构造函数可以设置默认参数
B.构造函数在定义类对象时自动执行
C.构造函数可以是内联函数
D.构造函数不可以重载
错误的描述是:
D. 构造函数不可以重载
构造函数是一种特殊类型的成员函数,用于创建和初始化类的对象。与其他函数一样,构造函数可以根据不同的参数列表进行重载。通过重载构造函数,可以使用不同的参数来创建不同初始化方式的对象。
因此,选项D. 构造函数不可以重载是错误的描述。构造函数是支持重载的。其他选项A、B和C的描述都是正确的。构造函数可以设置默认参数、在定义类对象时自动执行,并且可以是内联函数。
7.下面描述中,表达错误的是( )
A.公有继承时基类中的public成员在派生类中仍是public的
B.公有继承是基类中的private成员在派生类中仍是private的
C.公有继承时基类中的protected成员在派生类中仍是protected的
D.私有继承时基类中的public成员在派生类中是private的
一般都不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员
8.应在下列程序划线处填入的正确语句是( )
#include <iostream.h>
class Base
{
public:
void fun() { cout<<"Base fun called."<<ENDL; }
};
class Derived : public Base
{
void fun()
{
_____________ // 显示调用基类的函数fun()
cout<<"Derived::fun"<<ENDL;
}
};
A.fun();
B.Base.fun();
C.Base::fun();
D.Base->fun();
9.有如下程序:
#include <iostream>
using namespace std;
class BASE
{
char c;
public:
BASE(char n) : c(n) {}
virtual ~BASE() { cout << c; }
};
class DERIVED: public BASE
{
char c;
public:
DERIVED(char n) : BASE(n+1), c(n) {}
~DERIVED() { cout << c; }
};
int main()
{
DERIVED("X");
return 0;
}
执行上面的程序将输出( )
A.XY
B.YX
C.X
D.Y
这段代码定义了一个基类
BASE
和一个派生类DERIVED
。在main
函数中创建了一个DERIVED
对象,并传入字符 “X”。接着在程序结束时,会依次调用DERIVED
类和BASE
类的析构函数,并输出结果 “YX”。
10.在进行完任何C++流的操作后,都可以用 C++流的有关成员函数检测流的状态;其中只
能用于检测输入流状态的操作函数名称是( )
A.fail
B.eof
C.bad
D.good
下面是这些函数的具体含义:
- “good"函数:检查流的状态是否是"goodbit”,即流的状态正常,无错误发生。
- “eof"函数:检查流的状态是否是"eofbit”,即已到达文件末尾。
- “fail"函数:检查流的状态是否是"failbit”,即流操作失败。
- “bad"函数:检查流的状态是否是"badbit”,即流遇到了严重的故障。
11.分析以下程序的执行结果(考察构造、析构函数的调用顺序)
#include <iostream>
class base
{
int n;
public:
base(int a)
{
std::cout << "constructing base class" << std::endl;
n = a;
std::cout << "n=" << n << std::endl;
}
~base()
{
std::cout << "destructing base class" << std::endl;
}
};
class subs : public base
{
base bobj;
int m;
public:
subs(int a, int b, int c) : base(a), bobj(c)
{
std::cout << "constructing sub class" << std::endl;
m = b;
std::cout << "m=" << m << std::endl;
}
~subs()
{
std::cout << "destructing sub class" << std::endl;
}
};
int main()
{
subs s(1, 2, 3);
return 0;
}
这段代码的目的是创建一个包含基类和派生类的继承关系,并在构造和析构函数中输出相关信息。
在代码中,
base
类是一个基类,其构造函数接受一个整数参数。派生类subs
继承自base
,同时还包含一个成员变量bobj
,类型为base
。subs
类的构造函数接受三个整数参数,分别用于初始化基类base
、成员变量bobj
和派生类自身的成员变量m
。在
main
函数中,创建了一个subs
类的对象s
,传入参数分别为 1、2、3。程序运行时,会按照构造函数的顺序输出相应的信息,然后再按照析构函数的顺序输出相应的信息,即先构造基类对象,再构造成员变量对象,最后构造派生类对象,析构的顺序与构造相反。运行代码后,输出结果应为:
constructing base class n=1 constructing base class n=3 constructing sub class m=2 destructing sub class destructing base class destructing base class
其中,构造和析构函数的输出信息分别以 “constructing” 和 “destructing” 开头,后跟对应的类名。
12.分析以下程序的执行结果
class A
{
...
virtual void
display_infor()
{
printf("class A output");
}
};
class B : public A
{
...
virtual void
display_infor()
{
printf("class B output");
}
};
void displayClass(A *pA)
{
pA->display_infor();
}
main()
{
B b;
displayClass(&b);
}
这段代码演示了多态性的概念。其中,基类 `A` 和派生类 `B` 之间存在继承关系,且基类中的虚函数 `display_infor()` 被派生类重写。
在代码中,`displayClass()` 函数接受一个指向基类对象的指针 `pA`,然后调用该指针所指向对象的 `display_infor()` 函数。由于 `display_infor()` 是虚函数,并且在派生类 `B` 中进行了重写,因此通过传入 `B` 类对象的指针,会调用 `B` 类中重写后的 `display_infor()` 函数。
在 `main()` 函数中,首先创建了一个 `B` 类对象 `b`,然后调用 `displayClass()` 函数,并将 `b` 的地址作为参数传入。由于 `displayClass()` 函数接受的是基类指针,但实际传入的是派生类对象的地址,所以会发生多态性,即会根据实际对象的类型调用相应的虚函数。
因此,运行程序会输出 "class B output",这是因为在 `displayClass()` 函数中,通过基类指针调用了派生类 `B` 中重写的虚函数。
13.分析以下程序的执行结果(考察虚继承)
#include <iostream>
class A
{
public:
int n;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C
{
public:
int getn()
{
return B::n;
}
};
int main()
{
D d;
d.B::n = 10;
d.C::n = 20;
std::cout << d.B::n << "," << d.C::n << std::endl;
return 0;
}
在类
B
和类C
中,它们都通过虚继承方式继承了基类A
,这意味着类D
继承了B
和C
时,只会保留一个基类A
的子对象。这种虚继承方式可以避免在D
中存在两个不同的A
子对象的问题。在
main()
函数中,创建了一个D
类的对象d
。然后给d
对象的B
子对象的n
成员变量赋值为 10,C
子对象的n
成员变量赋值为 20。最后通过cout
输出d.B::n
和d.C::n
的值,以,
分隔。由于
D
类中的getn()
函数返回的是B::n
的值,因此输出的结果会是10,20
。
14.在下面程序的横线处填上适当的语句,使该程序执行结果为10
#include <iostream>
class MyClass
{
public:
MyClass(int a)
{
x = a;
}
//________________________________//取 x 值
int GetNum()
{
return x;
}
private:
int x;
};
int main()
{
MyClass my(10);
std::cout << my.GetNum() << std::endl;
return 0;
}
15.以下为Windows NT下的 32 位C++程序,请计算 sizeof 的值
void Func( char str[100] )
{
sizeof( str ) = 4
}
void *p = malloc( 100 );
sizeof ( p ) = 4
在
Func()
函数中,参数str
被声明为char str[100]
,但在函数内部使用sizeof(str)
并不会返回存储的字符串的大小,而是返回指针的大小(在 32 位系统中通常为 4 字节)。这是因为数组的维度在传递给函数时会被忽略,str
实际上被视为指向char
的指针。在
malloc()
函数中,分配的内存块的大小为 100 字节,但对指针p
使用sizeof(p)
同样不会返回分配的内存块的大小,而是返回指针的大小。
16.函数void swap()完成p1与p2的互换,请指出两者的异同:
void swap(int *p1, int *p2) { int *p; *p = *p1; *p1 = *p2; *p2 = *p; } 左框 | void swap(int *p1, int *p2) { int p; p = *p1; *p1 = *p2; *p2 = p; } 右框 |
- 左框函数中使用的变量
int *p
是一个指针变量,而右框函数中使用的变量int p
是一个普通的整数变量。- 左框函数中的指针变量
p
没有初始化,而右框函数的整数变量p
初始化为*p1
。- 左框函数中的赋值操作是
*p = *p1
,而右框函数中的赋值操作是p = *p1
。- 左框函数和右框函数的交换逻辑是一样的,即将
*p1
的值赋给*p
(或p
),将*p2
的值赋给*p1
,再将*p
(或p
)的值赋给*p2
。
17.请分析以下代码,找出其中的问题。
void test1()
{
char string[10];//string[11]
char* str1 = "0123456789";
strcpy( string, str1 );
}
问题在于
string
数组的大小为10个字符,而字符串常量"0123456789"
包含了10个字符及结尾的空字符,总共11个字符。因此,使用strcpy
将"0123456789"
复制到string
数组时,会导致缓冲区溢出,可能破坏邻近的内存。为了修复这个问题,你需要将
string
数组的大小增加到至少11个字符,以确保足够存储复制的字符串及其结尾的空字符。
18.分析以下代码,写出屏幕应该输出的结果,并简要说明为什么。(忽略内存泄漏问题)
// test.cpp . // #include "stdio.h" struct STRUCTA{ int n; }; void test1(STRUCTA st); void test2(STRUCTA* st); void test3(STRUCTA* st); void test4(STRUCTA& st); void test5(STRUCTA*& st); void test6(STRUCTA** st); int main(int argc, char* argv[]) { STRUCTA *pst = new STRUCTA; pst->n = 1; test1(*pst); printf("%d\n", pst->n); pst->n = 1; test2(pst); printf("%d\n", pst->n); pst->n = 1; test3(pst); printf("%d\n", pst->n); pst->n = 1; test4(*pst); printf("%d\n", pst->n); pst->n = 1; test5(pst); printf("%d\n", pst->n); pst->n = 1; test6(&pst); printf("%d\n", pst->n); return 0; } | void test1(STRUCTA st) { st.n = 100; } void test2(STRUCTA* st) { st = new STRUCTA; st->n = 100; } void test3(STRUCTA* st) { st->n = 100; } void test4(STRUCTA& st) { st.n = 100; } void test5(STRUCTA*& st) { st = new STRUCTA; st->n = 100; } void test6(STRUCTA** st) { *st = new STRUCTA; (*st)->n = 100; } |
void test1(STRUCTA st)
:该函数接受一个STRUCTA
结构体对象作为参数,通过传值方式将参数副本用于修改,并将n
成员赋值为 100。
void test2(STRUCTA* st)
:该函数接受一个STRUCTA
结构体指针作为参数,通过传址方式传递指针,并在函数内部使用new
运算符为指针分配新的STRUCTA
对象,并将n
成员赋值为 100。
void test3(STRUCTA* st)
:该函数接受一个STRUCTA
结构体指针作为参数,通过传址方式传递指针,并将指针所指向的STRUCTA
对象的n
成员赋值为 100。
void test4(STRUCTA& st)
:该函数接受一个STRUCTA
结构体的引用作为参数,通过引用方式传递对象,并将n
成员赋值为 100。
void test5(STRUCTA*& st)
:该函数接受一个指向STRUCTA
结构体指针的引用作为参数,通过引用方式传递指针的引用,并在函数内部使用new
运算符为指针分配新的STRUCTA
对象,并将n
成员赋值为 100。
void test6(STRUCTA** st)
:该函数接受一个STRUCTA
结构体指针的指针作为参数,通过传址方式传递指针的指针,并在函数内部使用new
运算符为指针分配新的STRUCTA
对象,并将n
成员赋值为 100。现在我们来分析代码的输出结果和原因:
test1(*pst)
:参数传递是按值传递,所以函数内部对副本的修改不会影响原始对象。因此,pst->n
的值仍为 1。输出结果应该是 1。
test2(pst)
:参数传递是按址传递,通过传递指针地址使得指针的指向可以在函数内被修改。所以,指针pst
指向了一个新的STRUCTA
对象,而原始对象的n
值没有改变。输出结果应该是 1。
test3(pst)
:参数传递是按址传递,直接传递指针的地址,所以函数内部可以修改指向的对象的值。原始对象的n
值被修改为 100。输出结果应该是 100。
test4(*pst)
:参数传递是按引用传递,所以函数内部对引用的修改会直接影响原始对象。因此,原始对象的n
值被修改为 100。输出结果应该是 100。
test5(pst)
:参数传递是按引用传递,通过传递指针的引用使得指针的指向可以在函数内被修改。所以,指针pst
被修改指向了一个新的STRUCTA
对象,而原始对象的n
值没有改变。输出结果应该是 1。
test6(&pst)
:参数传递是按址传递,直接传递指针的指针的地址,所以函数内部可以通过指针的指针修改指向的对象。原始对象指针pst
被指向一个新的STRUCTA
对象,而新的STRUCTA
对象的n
值为 100。因此,输出结果应该是 100。总结输出结果:
1
1
100
100
1
100
19.描述一下多线程和单线程的优缺点,各自在什么环境下用最为合适?简单举例说明。
单线程的优点:
- 简单:单线程程序通常比较简单,因为只有一个执行流程。
- 资源消耗低:单线程不需要额外的线程管理开销,因此在资源有限的环境下更为适用。
- 调试方便:单线程程序的调试相对简单,因为只需关注单个执行流程。
单线程的缺点:
- 响应时间较慢:当单线程程序执行耗时任务时,整个程序会被阻塞,导致用户体验不佳。
- 低效率:单线程无法利用现代多核处理器的多个计算核心,无法充分发挥硬件资源的优势。
- 难以实现并发:单线程只能顺序执行任务,无法同时处理多个任务。
单线程场景:一个简单的计算器应用,用户可以输入一个数值进行加法运算,由于这是一个简单的任务,不涉及复杂的计算或并发操作,使用单线程即可满足需求,不需要引入多线程的复杂性。
多线程的优点:
- 高效利用资源:多线程可以同时执行多个任务,充分利用多核处理器的并行计算能力。
- 提高响应性:多线程可以在后台执行耗时任务,不影响主线程的响应时间,从而提高用户体验。
- 实现并发:多线程可以同时处理多个任务,实现并发执行,提高系统吞吐量和效率。
多线程的缺点:
- 复杂性增加:多线程编程需要考虑并发访问共享数据、避免竞态条件等复杂的问题,增加程序设计和调试的难度。
- 线程间同步问题:多个线程并发访问共享数据可能会导致数据不一致性或竞争条件,需要额外的同步机制来处理。
多线程适用的场景:一个图片处理应用,用户可以同时对多张图片进行滤镜处理,每张图片的处理可以在一个单独的线程中执行,从而实现图片处理的并行计算,提高处理性能。
20.套接字I/O模型有哪些?这些模型中你用过那些模型?简单举例说明。
阻塞式(Blocking)I/O模型
非阻塞式(Non-Blocking)I/O模型
事件驱动(Event-Driven)I/O模型
多路复用(Multiplexing)I/O模型
异步(Asynchronous)I/O模型
多路复用I/O模型的优点是可以有效地处理大量的并发连接,减少了线程或进程切换的开销。它适用于需要高并发、同时监听多个套接字的服务器应用。一些使用多路复用模型的系统包括高性能的Web服务器、聊天服务器和实时通信系统。