C语言笔试面试题
1、实现strcpy的原型:char *strcpy(char *dst,const char *src);为什么要返回char *。并简单说明strcpy和strcpy_s、memcpy函数的区别。
实现strcpy的原型:
c
char *strcpy(char *dst, const char *src) {
char *original_dst = dst; // 保存dst的原始地址,用于返回
while (*src != '\0') { // 当src指向的字符不是null终止符时
*dst = *src; // 将src指向的字符复制到dst指向的位置
dst++; // dst指针向后移动
src++; // src指针向后移动
}
*dst = '\0'; // 在dst的末尾添加null终止符
return original_dst; // 返回dst的原始地址,即目标字符串的起始地址
}
**为什么要返回char ***:strcpy 函数返回目标字符串 dst 的指针(即它的起始地址)有几个原因:
链式调用:返回 dst 的地址允许链式调用其他字符串操作函数。例如,你可以这样写:printf("%s\n", strcpy(buffer, "Hello, world!"));
函数调用的透明性:当 strcpy 被用作其他函数的参数时,返回 dst 允许这些函数像处理原始 dst 指针一样处理 strcpy 的结果。
习惯与一致性:许多标准库函数都遵循这种返回指针的约定,这有助于代码的一致性和可预测性。
strcpy、strcpy_s和memcpy函数的区别:
strcpy:
复制源字符串 src 到目标字符串 dst,直到遇到源字符串的 null 终止符。
如果目标字符串 dst 的空间不足以容纳源字符串,会发生缓冲区溢出,这是一个严重的安全漏洞。
strcpy_s(安全版本,非标准C库函数,但在某些环境中可用):
除了源字符串和目标字符串,还接受一个额外的参数来指定目标缓冲区的大小。
如果源字符串加上 null 终止符超过了目标缓冲区的大小,函数不会进行复制,并可能设置错误标志或返回错误代码。
旨在防止缓冲区溢出攻击。
memcpy:
复制指定数量的字节从源内存区域到目标内存区域。
不关心源内存区域是否包含 null 终止符,只是简单地复制指定数量的字节。
如果目标内存区域不足以容纳复制的字节数,会发生缓冲区溢出。
通常用于非字符串数据的内存复制,但在处理字符串时需要特别小心,确保不会复制超过目标缓冲区大小的字节数。
2、typedef在C语言中频繁使用以声明一个已经存在的数据类型得同义字。也可以用预处理器做类似的事。 例如,思考一下下面的例子: #define DPS struct s* typedef struct s *tps; 以上两种情况的意图都是要定义dps和tps作为一个指向结构s指针。哪种方法更好?(如果有的话)为什么?
在C语言中,使用typedef和#define定义指针类型都可以达到类似的效果,但typedef通常更受推荐。
原因如下:
类型安全:typedef定义的是类型别名,而#define是简单的文本替换。因此,typedef提供的类型检查更严格,有助于避免潜在的类型错误。
可读性和维护性:typedef的语法更接近于C语言的类型声明方式,提高了代码的可读性。此外,如果将来需要修改类型定义,使用typedef会更方便,因为只需要修改一处定义,而#define可能会涉及到多个文本替换的修改。
作用域:typedef定义的类型别名有明确的作用域,而#define定义的宏则没有,这可能导致意外的宏展开或冲突。
因此,对于定义指针类型别名,使用typedef是更好的选择。
3、修饰符volatile
大白话讲一下,volatile 是 C 语言中的一个关键字,用来修饰变量。当你把一个变量声明为 volatile 时,你是在告诉编译器:“这个变量可能会被程序之外的因素(比如硬件或其他线程)改变,所以每次使用它之前,你都应该重新从内存中读取它的值,而不是使用之前可能存储在寄存器中的旧值。”
换句话说,volatile 告诉编译器不要对这个变量做优化,特别是不要把它存储在寄存器中,因为寄存器中的值可能会和内存中的实际值不同步。
举个例子来说,假设你正在编写一个程序,用来读取一个硬件设备上的某个传感器的值。这个传感器的值可能会随时变化,并且你的程序会不断地读取这个值。如果你把这个传感器的值声明为一个普通的变量,编译器可能会认为这个值在一段时间内是不会变的,所以它会把这个值存储在寄存器中,以减少从内存中读取数据的次数,从而提高程序的效率。但是,这样一来,你的程序就可能读取到一个旧的值,而不是传感器当前的实际值。
为了避免这种情况,你可以把这个变量的声明加上 volatile 修饰符。这样,编译器就会知道这个变量的值可能会随时变化,每次使用它之前都会重新从内存中读取。虽然这样做可能会降低程序的效率,但是它可以确保你读取到的是最新的、正确的值。
总结一下,volatile 修饰符的作用就是告诉编译器:“这个变量可能会随时变化,你不要对它做优化,每次使用它之前都要重新从内存中读取。”
4、头文件中的#ifndef/#define/#endif作用
1.防止该头文件被重复引用
2.防止函数声明、变量被重复定义在C语言头文件(.h 文件)中,#ifndef、#define 和 #endif 这三个预处理指令通常一起使用,它们的主要作用是防止头文件的内容在一个编译单元中被多次包含,即防止头文件的重复包含。这种技术通常被称为“包含守卫”(Include Guard)或“头文件保护”。
具体作用如下:
#ifndef:这是一个条件编译指令,用于检查某个宏是否尚未定义。如果该宏尚未定义,则编译器会执行随后直到 #endif 或另一个条件编译指令之间的所有代码。
#define:这个指令用于定义一个宏。在这个上下文中,它用于定义一个唯一的标识符,表示头文件已经被包含。
#endif:这个指令标志着条件编译块的结束。
这种机制的工作原理如下:当头文件第一次被包含时,#ifndef 检查定义的宏是否存在。由于这是第一次包含,宏尚未定义,所以编译器会继续执行,并遇到 #define 指令,该指令定义了这个宏。
如果这个头文件被再次包含(例如,因为两个不同的源文件都包含了同一个头文件),#ifndef 会再次检查这个宏。由于这个宏已经在第一次包含时被定义,#ifndef 后面的代码将不会被执行,从而避免了头文件的重复包含。
#endif 标记了 #ifndef 条件的结束。
这种方法可以有效地防止因头文件重复包含而导致的编译错误或不可预期的行为,例如变量或函数的重复定义。例如,一个典型的头文件保护可能如下所示:
c
#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H
// 头文件的内容,如函数声明、变量定义等
#endif // MY_HEADER_FILE_H
在这个例子中,MY_HEADER_FILE_H 是一个唯一的标识符,用于标识这个头文件。如果 MY_HEADER_FILE_H 尚未定义(即这是头文件的第一次包含),那么 #define MY_HEADER_FILE_H 会定义它,并且头文件的内容会被包含。如果 MY_HEADER_FILE_H 已经定义(即头文件已经被包含过),那么头文件的内容将不会被再次包含。
5、#include<filename.h> 和 #include"filename.h"有什么区别
对于#include<filename.h>,编译器从标准库路径开始搜索filename.h
对于#include“filename.h”,编译器从用户的工作路径开始搜索filename.h
6、 const有什么用途?
1.可以定义const常量
2.const可以修饰函数的参数,返回值,函数定义体
被const修饰的东西都收到强制保护,预防意外的变动,提高程序的健壮性
7、static有什么用途?
1.限制变量的作用域
2.设置变量的存储域
8、堆栈溢出一般是由什么原因导致的?
没有回收垃圾资源
9、如何引用一个已经定义的全局变量?
可以用引用头文件的方式,也可以用extern关键字
1.如果用引用头文件方式来引用某个在头文件中声明的全局变量,假定你将那个变量写错了,那么在编译期间会报错
2.如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在链接期间报错
10、全局变量可不可以定义在可被多个 .c 文件包含的头文件中?为什么?
可以,在不同的 C 文件中以 static 形式来声明同名全局变量。可以在不同的 c 文件中声明同名的全局变量,前提是其中只能有一个 C 文件中对此变量赋初值,此时链接不会出错
11、用宏定义写出 swap(x,y),即两数交换
#define swap(x, y) do { typeof(x) temp = x; x = y; y = temp; } while (0)
12、static局部变量和普通局部变量有什么区别
static局部变量只初始化一次下一次依据上一次结果值
13、static全局变量与普通的全局变量有什么区别
static全局变量只初始化一次,防止在其他文件单元中被引用
14、static函数与普通函数有什么区别?
static在内存中只有一份,普通函数在每个被调用中维持一份拷贝
15、局部变量、全局变量、动态申请分别存储在什么位置
程序的局部变量存在于栈(stack)中
全局变量存在于静态数据区中
动态申请数据存在于堆(heap)中
16、什么是预编译,何时需要预编译
1.总是使用不经常改动的大型代码体
2.程序由多个模块组成,所有模块都使用一组标准的包含头文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头
17、预处理器标识#error的目的是什么?
是在编译时,触发一个错误,如果某个特定的条件或宏未定义
18、变量的声明和定义有什么区别?
1.声明
告诉编译器变量的名称和类型,但不分配存储空间
2.定义
不仅告诉编译器的名称和类型,还分配存储空间,并可能初始化变量的值
.
一个变量只能被定义一次,但可以多次声明
19、解释C语言中的结构体和联合体有什么区别,如何使用?
都是用户自定义的数据类型
区别:
存储方式:
结构体:存储多个不同类型的变量,每个变量占用自己的内存空间
联合体:也存储多个不同类型的变量,但所有变量共享同一块内存空间
内存大小:
结构体:内存大小是其所有成员大小的总和
联合体:内存大小是其成员中最大的那个的大小
使用场景:
结构体:用于组合不同类型的数据为一个单一类型,例如描述一个点的坐标
联合体:通常用于节省空间,或当需要多个不同数据类型的变量,但一次只使用其中一个时
20、C语言的预处理器是什么?
是一种在真正编译开始之前由编译器调用的简单宏处理器,它使用以 # 开头的指令,对源代码进行一系列处理,包括删除注释,包含其他文件以及执行宏替代等,从而修改源代码
21、解释C语言中内存泄露与悬挂指针的区别
内存泄露:
在程序运行期间,动态分配
悬挂指针:
22、指针数组 & 数组指针的区别
23、堆区和栈区的区别
24、堆树和排序二叉树的区别
25、带参宏函数 & 普通带参函数 的区别
26、隐式类型转换?
横着的会转
竖着的不一定会转
27、匿名函数
28、C语言特点
29、怎么样避免假队空
30、GDB错误调试
31、如何判断一个链表里是否有环
32、C语言xml解析
33、bzero与memset的区别
bzero
函数原型:
void bzero(void *s, size_t n);
描述:
bzero
函数是某些系统(如BSD Unix系统)上用于将内存区域清零的函数。它将指针s
指向的内存区域的前n
个字节设置为0。这个函数特别用于初始化内存区域,例如清空一个字符数组或结构体变量。注意:
bzero
函数并不是C语言标准库的一部分,因此在某些系统或编译器上可能不可用。此外,由于bzero
函数已经被标记为废弃函数,因此在新的代码中不再建议使用。使用场景:
虽然不推荐使用,但在某些特定情况下(如需要对socket地址结构执行清零操作时),仍然可能会遇到bzero
函数。memset
函数原型:
void *memset(void *s, int c, size_t n);
描述:
memset
函数是C语言标准库<string.h>
中的一个函数,用于将某一块内存中的内容全部设置为指定的值。函数接受三个参数:一个指向要设置内存的指针
s
,一个表示要设置的值c
(通常是一个字符,但会被转换为无符号字符并扩展为一个int
),以及一个表示要设置的字节数n
。
memset
函数通常用于为新申请的内存做初始化工作,或者将内存区域设置为某个特定的值。优点:
- 可以设置内存区域的值为任意指定的值,而不仅仅是零。
- 存在于所有的C标准库中,因此具有更好的可移植性和可用性。
常见错误:
memset
函数按字节对内存块进行初始化,因此不能用它将int数组初始化为0和-1之外的其他值(除非该值高字节和低字节相同)。memset
中的c
参数实际范围应该在0到255之间,因为该函数只能取c
的后八位赋值给所输入的范围的每个字节。- 搞反了
c
和n
的位置。例如,要将一个char a[20]
清零,应该是memset(a, 0, 20 * sizeof(char));
而不是memset(a, 20 * sizeof(char), 0);
。使用场景:
memset
函数是初始化内存区域的常用方法,特别是在需要对大型结构体或数组进行清零操作时。由于它可以设置任意值,因此比bzero
更加灵活和通用。
大白话描述
bzero
bzero
这个函数名字听起来就像是“把零放进去”的意思。在早期的Unix系统中,如果你想让一段内存里的所有内容都变成0(也就是清空它),你就可以使用这个函数。但是,它并不是C语言标准的一部分,也就是说,不是所有的计算机或编程环境都支持它。而且,因为一些原因,现在人们不太推荐再使用这个函数了。memset
memset
这个函数名字可以理解为“设置内存”。它是一个C语言标准库里的函数,你可以在几乎任何支持C语言的地方使用它。它不仅可以用来把内存里的内容变成0(就像bzero
那样),还可以让你把内存里的内容变成任何你想要的数字或字符。你只需要告诉它你想要设置的值和要设置的内存的大小就可以了。举个例子
假设你有一个很大的房间,你想把房间里的所有灯都关掉(也就是把房间清空)。
- 使用
bzero
就像是找到了一个专门关灯的开关,但这个开关可能只在某些房间里才有。- 使用
memset
就像是找到了一个万能的遥控器,你可以用它来关任何房间里的灯,甚至还可以调节灯光的亮度(也就是设置不同的值)。所以,虽然
bzero
和memset
都能达到类似的效果,但memset
更加灵活和通用,而且更受欢迎。如果你需要清空或设置内存,推荐使用memset
。
34、i++与++i
1. 前缀递增(++i)
- 行为描述:
++i
首先使i
自身增加1,然后返回递增后的i
的值(也就是对表达式的赋值)。
int i = 0;
int j = ++i; // i先递增到1,然后j被赋值为1
在这个例子中,
i
在赋值给j
之前就被递增了。所以,j
的值是1。2. 后缀递增(i++)
- 行为描述:
i++
首先返回i
的当前值(也就是对表达式的赋值),然后i
自身再增加1。
int i = 0;
int j = i++; // i的值(0)先被赋值给j,然后i递增到1
在这个例子中,
i
的值(0)被赋值给j
,然后i
才递增。所以,j
的值是0,而i
现在是1。例子:
i++
(后缀递增)和++i
(前缀递增)
int i = 0;
while (*buf1) {
Message[i++] = *buf1++;
}
这里,
i++
和buf1++
都是后缀递增,意味着它们的值在表达式中使用(这里是数组索引和指针解引用)之后才递增。在这个while
循环中,Message[i]
被赋值为*buf1
的值,然后i
和buf1
都递增。这个过程会一直重复,直到*buf1
为0(也就是字符串buf1
的结束符)。3. 复杂的表达式
注意,在更复杂的表达式中,递增操作可能会产生不同的结果或副作用,因为前缀和后缀递增与表达式的求值顺序(这通常是未指定的)相结合可能会导致难以预料的行为。因此,在编写涉及递增和递减操作的复杂表达式时,应该特别小心。
最后,尽管
++i
和i++
在循环和数组索引中可能看起来相似,但在涉及赋值和表达式的更广泛上下文中,理解它们之间的差异是很重要的。
C语言笔试面试题-指针相关
1、字符指针、浮点指针以及函数指针这三种类型的变量那个占用的内存最大?为什么?
指针变量本身的大小并不取决于它们所指向的数据类型(字符、浮点数或函数)。指针变量的大小通常取决于系统架构和编译器。在32位和64位系统,指针的大小是固定的。
- 32位系统:在32位系统上,指针通常占用4个字节(32位/8位 = 4字节)。
- 64位系统:在64位系统上,指针通常占用8个字节(64位/8位 = 8字节)。
所以,无论是字符指针(
char *
)、浮点指针(float *
或double *
)还是函数指针(void (*)(void)
或其他具体的函数指针类型),它们作为指针变量本身在内存中所占用的空间大小是相同的。总结:字符指针、浮点指针和函数指针这三种类型的变量本身占用的内存大小是相同的,具体大小取决于系统架构(32位或64位)。但它们所指向的数据类型占用的内存大小可能不同。
2、以下对C语言的“指针”的描述不正确的是()
A、32位系统下任何类型的指针的长度都是4个字节
B、指针数据类型声明的是指针实际指向内容的数据类型
C、野指针是指向未分配或者已释放的内存地址
D、当使用free释放掉一个指针内容后,指针变量的值被置为NULL
A、32位系统下任何类型的指针的长度都是4个字节
- 这个描述在大多数情况下是正确的,因为32位系统上的指针通常占用4个字节。但是,这并不绝对,因为某些特殊的硬件架构或编译器可能会使用不同的指针大小,尽管这非常罕见。因此,严格来说,这个描述不是100%准确的。
B、指针数据类型声明的是指针实际指向内容的数据类型
- 这是正确的。例如,
int *p;
声明了一个名为p
的指针,它指向一个整数(int
)类型的数据。C、野指针是指向未分配或者已释放的内存地址
- 这也是正确的。野指针通常指的是一个指针变量,它指向的内存已经被释放,或者从未被分配,但是指针的值(即地址)不是NULL。
D、当使用free释放掉一个指针内容后,指针变量的值被置为NULL
- 这是不正确的。调用
free
函数只是释放了指针所指向的内存,它并不自动将指针变量的值设置为NULL。程序员需要显式地将指针设置为NULL,以避免野指针问题。综上所述,不正确的描述是 D。当使用
free
释放掉一个指针内容后,指针变量的值不会自动被置为NULL。【正确答案:D】
3、有如下定义,则下列符号中均正确地代表x的地址的选项是()
int x,*p;
p = &x;
A、&x、p、&*x
B、*&、x、p
C、&p、*p、x
D、&x、&*p、p
问题考查的是C语言中指针的基本概念和操作符的使用。
地址操作符(&):
&
是一个一元操作符,用于获取变量的地址。例如,&x
就是获取变量x
的内存地址。解引用操作符(*):
*
是一个一元操作符,用于解引用指针,即获取指针所指向的值。但是,当*
前面有&
时(即&*
),它实际上先解引用指针再取地址,这样的操作通常不会改变原地址(除非在解引用时指针指向的内容是一个地址,但在这个问题中不是这种情况)。因此,&*p
仍然代表p
所指向的地址,即&x
。指针变量:指针变量存储的是另一个变量的地址。在这个问题中,
p
是一个指针变量,它被赋值为&x
,即x
的地址。符号的匹配:问题要求找到正确代表
x
地址的符号。因此,需要确保所选符号确实表示x
的地址。【正确答案:D】
4、有基本类型相同的指针p1、p2,则下列运算不合理的是()
A、p1 /= 5
B、p1 - p2
C、p1 = p2
D、p1 == p2
A、
p1 /= 5
:这是不合理的。在C语言中,你不能将指针变量直接除以一个整数(除非这个整数是1,但即使是1也没有意义,因为除以1不会改变值)。指针运算应该限于指针的加减和比较,以及指针与整数的加减(表示在内存中移动指针的位置)。B、
p1 - p2
:这是合理的。如果p1
和p2
指向同一数组(或连续内存块)中的元素,那么p1 - p2
将给出p1
和p2
之间的元素数量差(以数组元素大小为单位)。C、
p1 = p2
:这是合理的。你可以将一个指针变量的值(即地址)赋给另一个同类型的指针变量。D、
p1 == p2
:这也是合理的。你可以比较两个指针变量是否指向同一内存地址。【正确答案:A】
5、以下程序的输出是()
int *p = 0;
p += 6;
printf("%d\n",p);
A、12
B、72
C、24
D、0
E、6
F、任意数
在这个程序中,我们首先定义了一个整数指针
p
并将其初始化为0
(在C语言中,这通常表示空指针或地址0x0
)。然后,我们对指针p
进行了自增操作p += 6
。但是,这个自增操作并没有按照普通整数的方式增加6个单位,而是按照指针的步长(即它所指向类型的大小)来增加的。因为
p
是一个int
类型的指针,所以它的自增步长通常是sizeof(int)
个字节。但是,由于p
的初始值是0
(一个无效的地址),对它进行自增操作是未定义行为(Undefined Behavior,UB),因为你不能对空指针进行算术运算。由于
p
是一个指针,你不能直接用%d
来打印它,因为%d
是用于打印整数的。你应该使用%p
来打印指针的值。但是,在你的代码中,你使用了
%d
来打印p
,这实际上是在尝试将指针p
的值当作整数来解释,这通常会导致不可预测的结果(尽管在某些系统上,它可能会显示指针值的某种表示,但这通常是不可移植的)。【正确答案:F】
因为使用
%d
来打印指针的值是未定义行为,并且之前的p += 6
也是未定义行为(因为p
是一个空指针)。
6、有以下定义:
int a = 248,b = 4;
const int *d = &a;
int *const e = &b;
int const *f = &a;
则下列表达式中()是有问题的。
A、d = &b;
B、*d = 8;
C、*e = 34;
D、e = &a;
E、f = 0x321f;
在分析这些表达式之前,我们需要理解每个指针声明的意义:
int a = 248, b = 4;
- 定义了两个整数变量。const int *d = &a;
-d
是一个指向整数的指针,但它指向的内容(即*d
)是常量,不能被修改。但是d
本身(即指针本身的值)是可以修改的。int *const e = &b;
-e
是一个常量指针,它指向一个整数。e
本身的值(即e
指向的地址)不能被修改,但是*e
(即指针所指向的内容)是可以被修改的。int const *f = &a;
- 这和const int *d = &a;
是等价的,f
是一个指向整数的指针,但它指向的内容是常量,不能被修改。现在,我们逐一分析每个表达式:
A、
d = &b;
- 这是合法的,因为d
本身的值(即指针指向的地址)是可以被修改的。B、
*d = 8;
- 这取决于具体的上下文。如果编译器认为a
的值应该被保护(因为d
指向a
并且a
不是通过const
定义的),那么这可能是一个错误。但在技术上,d
被声明为指向一个const int
,但这并不保证a
本身也是const
。所以,如果a
不是const
,那么这个赋值是合法的。但如果代码中有其他地方尝试修改a
并通过d
来阻止这种修改,则编译器可能会给出警告或错误。C、
*e = 34;
- 这是合法的,因为e
指向的内容(即b
)是可以被修改的。D、
e = &a;
- 这是有问题的,因为e
是一个常量指针,它的值(即e
指向的地址)不能被修改。E、
f = 0x321f;
- 这是合法的,因为f
本身的值(即指针指向的地址)是可以被修改的。尽管f
被声明为指向一个const int
,但这并不影响f
本身的值(即指针的值)可以被修改。所以,有问题的表达式是:
D、e = &a;
7、为什么C语言的精华是指针
C语言的“精华”通常被认为是指针,这主要是因为指针在C语言编程中提供了许多强大的功能和灵活性,使得C语言能够高效、直接地操作内存,并支持各种复杂的数据结构和算法。以下是指针在C语言中一些重要的应用和作用:
直接内存访问:指针允许程序员直接访问和操作内存地址。通过指针,你可以读取或写入特定内存位置的数据,这为底层编程和系统级编程提供了极大的便利。
动态内存分配:在C语言中,使用指针和相关的内存管理函数(如
malloc
、calloc
、realloc
和free
),程序员可以在运行时动态地分配和释放内存。这种能力使得C语言能够处理可变大小的数据结构,如链表、树和图等。函数参数和返回值:通过指针,可以将函数外部的数据传递给函数内部进行修改,或者从函数内部返回多个值。这在许多情况下都是非常有用的。
数据结构:指针是实现复杂数据结构(如链表、栈、队列、树和图)的关键。通过使用指针,我们可以创建指向其他数据元素的引用,从而构建出各种复杂的数据结构。
底层编程:在编写操作系统、嵌入式系统、游戏引擎等底层软件时,指针是必不可少的。它们允许程序员直接操作硬件和内存,实现高效的资源管理和性能优化。
灵活性:由于指针可以指向任何类型的内存地址,因此C语言具有极高的灵活性。这种灵活性使得C语言能够适用于各种不同的应用场景,从嵌入式系统到高性能计算,再到操作系统和编译器开发。
然而,需要注意的是,指针的强大功能也带来了一定的风险。不正确的指针使用可能会导致内存泄漏、野指针、悬挂指针等问题,这些问题可能会引发程序崩溃或数据损坏。因此,在使用指针时,程序员需要格外小心,确保正确地管理内存和指针。
8、函数指针 & 指针函数
进程、线程笔试面试题
1、进程 & 线程的区别
2、Linux进程间通信方式
C++笔试面试题
1、char *p="girl";的含义是定义字符型指针变量 p,p 的值是字符串"girl"。请问这句话的说法是正确的吗?
A、正确
B、错误
解析:
【正确案:B】
2、函数即可以嵌套定义,又可以嵌套调用。请问这句话的说法是正确的吗?
A、正确
B、错误
解析:
函数的定义不能嵌套,但是调用可以嵌套。
例:
void A()
{
int B(int b)
{
}
}
这种嵌套定义不行。
int main()
{
fun();
}
显然函数调用可以嵌套。
还有一种说法:
关于嵌套函数的定义:
1. 严格意义上来说 C++11以后可以用 lambda 形式进行嵌套定义。
2. gcc 支持嵌套函数,但这并不是标准的一部分。
【正确案:B】
3、有以下程序,程序运行后的输出结果是()
int
main() {
char
a[7] =
"a0\0a0\0"
;
int
i, j;
i =
sizeof
(a);
j =
strlen
(a);
printf
(
"%d %d\n"
,i, j);
}
A、2 2
B、7 6
C、7 2
D、6 2
解析:
sizeof(a) 是指a的内存空间大小,strlen() 查找到“\0”就结束。
\0 是 null 的转义,strlen() 查找到 null 就停止了,且不计算 null。
【正确案:C】
4、在c++中,重载、重定义、重写的区别:
重载:在同一个作用域中,函数功能相似,函数名相同,参数不同(参数个数、数据类型、类型的顺序),与返回值无关的一组函数重定义:在继承关系中,子类重定义父类的方法,函数名相同即可.
5、类A被类B虚继承之后,类B占多大;类A被类C虚继承之后,类C占多大
6、下面程序的输出结果是
char
*p1 =
"123"
, *p2 =
"ABC"
, str[50]=
"xyz"
;
strcpy
(str + 2,
strcat
(p1, p2));
cout << str;
A、xyz123ABC
B、z123ABC
C、xy123ABC
D、出错
解析:
1.在 C++ 代码中,char * p1 = "123";会编译报错。
2.即使在 C 代码中,char * p1 = "123";编译通过,由于 p1,p2 都是常量,所以通过 strcat(p1,p2) 会报错。【正确答案:D】
7、关于C++中的友元函数说法正确的是(多选)()
A、友元函数需要通过对象或指针调用
B、友元函数是不能被继承的
C、友元函数没有this指针
D、友元函数破环了继承性机制
解析:
友元本质上是普通函数,不在类范畴中,没有 this、成员的概念。
友元类不具有传递性、继承性、双向性。
友元破坏了封装
【正确答案:BC】
8、下列程序的执行结果是()
A、a=639,b=78,c=12
B、a=12,b=639,c=78
C、a=639,b=12,c=78
D、a=78,b=12,c=639
解析:
fun是交换
exchange是按降序交换
【正确答案:A】
9、下面代码的输出为()
int
function(
int
a,
int
b,
int
c) {
c = a * b;
}
int
main(
void
) {
int
c =
0
;
function(
10
,
20
, c);
printf(
"%d"
, c);
}
A、200
B、100
C、20
D、0
解析:
C++ 参数的传递方式包括传值、传地址和传引用。其中传地址和传引用可以实现对变量的修改。而传值则是传递变量副本,并不会引起对应变量的改变。题目中 function 的参数传递均为传值。因此变量 c 保持原始值。
【正确答案:D】
10、以下程序执行后的输出结果是:
class
A {
public
:
A() { std::cout <<
"A"
; }
};
class
C:
public
A {
public
:
C() { std::cout <<
"C"
; }
};
void
main() {
C cObj;
}
A、CA
B、AC
C、A
D、C
解析:
C++ 派生类的构造顺序为先构造父类,再构造自己。因此先调用 A 的构造函数,输出 A,然后再调用 C 的构造函数,输出 C。
【正确答案:B】
11、对一个指针变量int *p赋值,下面赋值正确的是()
A、float f;p=&f;
B、int k; *p=&k;
C、p=0x3000
D、int k;p=&k;
解析:
A 选项错误,p 类型是 int *,f 类型是 float,&f 类型是 float *,p = &f; 类型不匹配
B 选项错误,p 类型是 int *,*p 类型是 int,k 类型是 int,&k 类型是 int *,*p = &k; 类型不匹配
C 选项错误,p 类型是 int *,0x3000 类型为 int,p=0x3000; 类型不匹配D 选项正确
【正确答案:D】
12、在 gcc 环境下,已知数组 int arr[5]={1,2,3,4,5}; 则 *(*(&arr+1)-1) 的结果为
A、1
B、0
C、4
D、5
解析:
& 是取地址符, &arr 代表整个数组,它的步长是整个数组所占的字节数, &arr 的类型是 int ( * ) [5] ,所以 &arr+1 指向数组 arr 最后一个元素的下一个地址, *(&arr+1)-1 则是指向 arr 最后的元素,所以 *(*(&arr+1)-1) 的值是 5.
【正确答案:D】
13、如果定义 : float a[10], x;则以下叙述中正确的是()
A、语句 a = &x; 是非法的
B、表达式 a+1 是非法的
C、三个表达式 a[1]、*(a+1)、&a[1] 代表的变量互不相同
D、表达式 *&a[1] 是非法的,应该写成 *(&(a[1]))
解析:
B 选项中,表达式 a+1 不是非法的;
C 选项中, *(a+1) 即指 a[1] ;
D 选项中, *&a[1] 不是非法的。因此 A 选项正确。
【正确答案:A】
14、在32位系统中以下函数的返回值是()
int
fun(
void
) {
int
*p, j, i=10;
p = &j;
*p = i++;
i +=
sizeof
(i) +
sizeof
(*p) +
sizeof
(p);
return
i;
}
A、22
B、23
C、25
D、其他结果都不对
解析:
*p = i++;所以*p = 10, i = 11;
int型4个字节 所以sizeof(i)和sizeof(*p) 都为4
系统是32位 所以sizeof(p)为32位4个字节;
所以i = i + 4 + 4 + 4 = 23
【正确答案:B】