int x = 5; int* p = &x; 关于变量 p 中存储的内容,正确的是( )。
考点:指针存放地址(A1)。
(A1)考点:指针存放地址——指针变量里存的是另一个变量的内存地址,不是那个变量的值。
解析:本题考查指针存放地址。定义 int* p = &x; 后,p 这块内存里写的是 x 所在位置的编号(地址),所以要访问 这个值必须先顺着地址找过去(解引用)。
排除法:B 以为存的是 的人混淆了「地址」与「地址里的内容」;C 以为存的是变量名的把内存想象成按名字存取;D 以为与 x 共内存的是把指针当成了引用。
已知 int x = 3;,表达式 &x 的含义是( )。
考点:取地址运算符(A2)。
(A2)考点:取地址运算符——用在已有变量前面的 & 是取地址运算,得到该变量的地址。
解析:本题考查取地址运算符。&x 的结果是一个指向 x 的地址值,可以直接初始化指针 int* p = &x;。取地址只能作用于变量(有存储位置的对象),不能对字面量取地址。
排除法:D 以为取值的把 & 和 * 的功能弄反了;A 以为按位与的人混淆了运算符重载场景,按位与需要两个整数操作数;C 以为声明引用的人没注意引用声明只出现在类型后的定义处。
01int x = 6; 02int* p = &x; 03*p = *p * 3; 04cout << x;
输出是( )。
考点:解引用读写(A3)。
(A3)考点:解引用读写——*p 出现在赋值号左边是往目标里写,出现在右边是从目标里读。
解析:本题考查解引用读写。*p * 3 先从 p 指向的 x 里读出 ,乘 得 ,再写回 x。读写都发生在 x 那块内存上,p 本身不变。
排除法:D 选 的人以为 *p 只读不写,赋值没有生效;C 选 的人把 *p * 3 误解成 *p 自身或忽略了读出的初值;B 选编译错误的人不知道 *p 可以放在赋值号左边。
关于 int* p = nullptr;,下列说法正确的是( )。
考点:空指针(A4)。
(A4)考点:空指针——nullptr 表示明确的不指向任何对象的状态,解引用它是致命错误。
解析:本题考查空指针。空指针是刻意设定的「无指向」标记,可以安全地赋值、比较、判断,唯独不能解引用。常用写法是先判空再用:if (p != nullptr) cout << *p;。
排除法:D 以为可以读写地址 的人不了解地址 被系统保留;A 以为不能比较的人把「不能解引用」扩大成了「什么都不能做」;C 以为与未初始化指针等价的人混淆了「明确的空」与「随机的野」,后者连指向哪里都不知道。
在 64 位系统中,int* p1; double* p2; char* p3; 三个指针变量各自占用的字节数是( )。
考点:指针的大小(A5)。
(A5)考点:指针的大小——64 位系统中所有指针都占 8 字节,与它指向什么类型无关。
解析:本题考查指针的大小。指针存的是地址,地址的宽度由系统位数决定(64 位 = 字节)。int*、double*、char* 的差别在于走一步跨多远(步长),不在指针本身的大小。
排除法:D 答 、、 的人以为指针大小要跟着所指类型变,只记住了「char 小」;A 答 、、 的人同样把「所指类型的大小」当成了「指针的大小」;B 答都是 的人用的是 32 位系统的旧印象,64 位下地址宽度翻倍。
已知 double d = 3.5;,下列定义指针的语句中正确的是( )。
考点:指针类型匹配(A6)。
(A6)考点:指针类型匹配——指针的类型必须与所指变量的类型一致,double 变量只能用 double* 指。
解析:本题考查指针类型匹配。类型一致才能保证解引用时按正确的字节数和格式解释内存。double* p = d; 把一个数当地址用,int* p = *d; 对非指针解引用,都是编译错误。
排除法:A 选 int* 接 &d 的人以为指针可以随便指,实际类型不匹配编译器直接拦截;C、B 其余两个选项混淆了「变量的值」与「变量的地址」,数值 不是地址。
01int* p; 02*p = 10;
这段代码的问题是( )。
考点:野指针危害(A7)。
(A7)考点:野指针危害——未初始化的指针值不确定,解引用它是未定义行为。
解析:本题考查野指针危害。局部指针变量与局部普通变量一样不自动清零,里面是残留的随机值;*p = 10 等于往一个随机地址写数,轻则数据错乱重则崩溃。习惯是把指针初始化为 nullptr 或有效地址。
排除法:A 以为自动指向安全地址的人高估了编译器,它只保证「定义了这块内存」,不保证内容;B 以为编译报错的人不知道这类错误要等到运行时才爆发,野指针能通过编译;C 以为问题只是 10 该写成 10.0 的人完全没抓住要害,数值格式与指针指向哪里无关。
已知 int x = 5;,表达式 *&x 的值是( )。
考点:取址与解引用互逆(A8)。
(A8)考点:取址与解引用互逆——& 取地址、* 顺地址取内容,两者叠加正好抵消。
解析:本题考查取址与解引用互逆。*&x 从里往外算:&x 得到 x 的地址,* 顺这个地址取回内容,所以值就是 本身 。同理 &*p 对有效指针等于 p。
排除法:A 选地址的人只做了一层运算就停;D 选 的人以为还会顺带加一;B 选不合法的人不知道这两个运算符可以叠加使用。
int a[5]; 下列关于数组名 a 的说法正确的是( )。
考点:数组名即首地址(B1)。
(B1)考点:数组名即首地址——数组名在表达式中代表首元素的地址,等价于 &a[0]。
解析:本题考查数组名即首地址。数组是一整块连续内存,a 就是这块内存的起点,所以 *a 就是 a[0]。数组长度并不随数组名存储,sizeof 是编译期运算才有全长。
排除法:C 以为代表所有元素的值的把数组名当成了打包的集合;D 以为值就是 a[0] 的人混淆了「首地址」与「首元素」;B 以为存长度 的人把数组想象成自带长度字段的结构。
int a[] = {1, 3, 5, 7, 9}; 表达式 *(a + 3) 与下列哪一项完全等价( )。
考点:星号加偏移等价下标(B2)。
(B2)考点:星号加偏移等价下标——*(a + i) 与 a[i] 完全等价,下标访问就是指针算术的语法糖。
解析:本题考查星号加偏移等价下标。a + 3 从起点向后移 个元素(不是 字节),解引用得 即 。注意 *a + 3 是先解引用再加 ,值为 ,与 *(a+3) 不同。
排除法:B 选 *a + 3 的人被运算符优先级坑了,星号先算但只作用于 a,得到 ;C 选 a[2] 的人偏移数错一格;A 选 &a[3] 的人多套了一层取地址,那是地址不是值。
01int a[] = {2, 4, 6, 8, 10}; 02int* p = a; 03p = p + 2; 04cout << *p;
输出是( )。
考点:指针算术按类型移动(B3)。
(B3)考点:指针算术按类型移动——p + 2 按所指类型的步长移动 个元素。
解析:本题考查指针算术按类型移动。p 是 int*,p + 2 在地址上跨 个 int( 字节),指向 a[2],*p 为 。若 p 是 char*,同样加 只跨 字节——步长随类型变。
排除法:C 选 的人把偏移数错算一格指到了 a[1];D 选 的人以为加 要指到 a[3];B 选地址的人忘了最后要解引用,题目问的是值。
在 C++ 中,指向同一个数组的两个指针 p、q 相减(q - p),结果是( )。
考点:同数组指针相减(B4)。
(B4)考点:同数组指针相减——两指针相减的结果是相隔的元素个数,不是地址差。
解析:本题考查同数组指针相减。指针相减按元素计数:&a[6] - &a[2] 是 (隔 个元素),若按字节算还要乘上每个元素的字节数,但语言规定结果就是元素个数。相减只在同一数组内才有意义。
排除法:C 选字节数的人自己多做了一步乘法;B 选一定不小于 的人忘了 q - p 与 p - q 可正可负,取决于谁减谁;D 选非法运算的人不了解这是合法且常用的距离计算。
void f(int a[]) { } 在 64 位系统中,函数 f 内部 sizeof(a) 的值是( )。
考点:数组传参退化(B5)。
(B5)考点:数组传参退化——数组传进函数后形参只是指针,函数内再也拿不到数组长度。
解析:本题考查数组传参退化。void f(int a[]) 里的 a 实际是 int*,64 位下 sizeof(a) 为 。所以数组长度必须靠额外参数(如 f(a, n))传进去,这是「传数组必传长度」惯例的根源。
排除法:D 以为取决于实际长度的人不知道退化规则,把形参当成了真数组;B 选 的人算的是 int 的大小;A 选 的人混淆了指针与 char。
int a[5]; 下列选项中,两个表达式的值完全相同的一组是( )。
考点:取址形式等价(B6)。
(B6)考点:取址形式等价——&a[i] 与 a + i 是同一个地址的两种写法。
解析:本题考查取址形式等价。&a[2] 对第 号元素取地址,a + 2 从起点偏移 个元素,两者都是同一位置。其余选项把值与地址混在了一起比较。
排除法:B 选 a[2] 与 a + 2 的人拿值跟地址比;C 选 &a[2] 与 *a + 2 的人左边是地址右边是算式值;A 选 a 与 a[0] 的人一边是地址一边是整数。
用指针 p 从头到尾依次访问 int a[N] 的每个元素,循环条件写成 p < a + N 的原因是( )。
考点:指针遍历边界(B7)。
(B7)考点:指针遍历边界——a + N 指向末尾元素之后,p < a + N 恰好覆盖全部元素且不越界。
解析:本题考查指针遍历边界。数组有效下标是 到 ,a + N 是「尾后位置」,它是可以计算和比较的界碑但不可访问。循环条件用 p < a + N,访问完 a[N-1] 后 p 到达界碑即停止。
排除法:D 以为 a + N 指向最后一个元素的人会漏掉或多算;C 以为指针只能与整数比较的人不知道同类型指针之间可以直接比较;A 以为 p <= a + N 相同的人会让循环多跑一次解引用尾后位置,越界。
int a[5]; 关于 a++ 或 a = a + 1 这类写法,正确的是( )。
考点:数组名不可自增(B8)。
(B8)考点:数组名不可自增——数组名不是指针变量,是编译期就固定的地址常量,不能被赋值或自增。
解析:本题考查数组名不可自增。a 代表那块内存的起点,编译器不允许改它;想移动要用真正的指针变量:int* p = a; p++; 是合法的。这就是「数组名像指针但不是指针」的关键区别。
排除法:B 以为合法后移的人把它当成了指针变量;A 以为只影响 *a 不影响 a[1] 的人不知道元素位置由地址决定,地址动了下标全乱;C 以为运行时崩溃的人不知道这是编译期错误,根本生成不了程序。
const char* s = "hello"; 此时 s 中存放的是( )。
考点:字符指针指向字面量(C1)。
(C1)考点:字符指针指向字面量——char* 存的是字符串首字符的地址,不是字符串的内容。
解析:本题考查字符指针指向字面量。"hello" 存放在程序的只读数据区,s 里只放 'h' 的地址。const 说明这串内容不准备通过 s 修改。后续 s[1]、*s 都从这个地址出发访问。
排除法:D 以为存整个内容的人混淆了「地址指向内容」与「内容存在指针里」;C 以为存 'h' 的人少了「地址」这一层;B 以为存长度 的人把指针当成了带长度的串对象。
01char s[] = "exam"; 02char* p = s; 03cout << p;
输出是( )。
考点:输出字符指针(C2)。
(C2)考点:输出字符指针——cout << 字符指针 输出的是整个字符串而不是地址。
解析:本题考查输出字符指针。流输出对 char* 有特殊约定:从指向的字符开始逐个输出直到 '\0'。所以输出 exam。想看地址本身要转成 void* 输出。
排除法:C 以为输出地址的人对比的是 int* 的行为,字符指针被特殊对待;D 以为输出 e 的人以为只取首字符;A 以为编译错误的人不知道这是合法且常用的输出方式。
看下面两段代码:
01// 第 1 段 02char s[] = "hi"; 03s[0] = 'H'; 04 05// 第 2 段 06const char* p = "hi"; 07// 想通过 *p = 'H' 把内容的首字符改成 'H'
正确的判断是( )。
考点:数组可改与字面量只读(C3)。
(C3)考点:数组可改与字面量只读——char s[] = "hi" 把内容拷进自己的数组可改;指针直指字面量则内容在只读区不可改。
解析:本题考查数组可改与字面量只读。第一种写法是「抄一份」,改的是自己的副本;第二种写法是「指过去」,字面量在只读区,写它会导致崩溃。所以「内容要改」用字符数组,「只读」用字符指针即可。
排除法:D 以为两段都合法的人不知道只读区的存在;A 以为第 1 段不合法的人把数组初始化误解成了绑定只读区;C 以为两段都不合法的人过度保守,数组自己的元素当然能改。
const char* p = "abc"; 当 p 从头走到字符串末尾时,*p 的值是( )。
考点:结尾判零(C4)。
(C4)考点:结尾判零——C 风格字符串以 '\0' 字符结尾,遍历走到它就到头。
解析:本题考查结尾判零。'\0' 是值为 的字符,它是字符串的一部分但不算内容。while (*p != '\0') 是字符指针遍历的标准终止写法,"abc" 实际占 个字节。
排除法:D 选 'c' 的人把「最后一个可见字符」当成了结尾标记;C 选 nullptr 的人混淆了「指针为空」与「字符为 」,*p 是字符不是指针;B 选地址的人又把内容和地址搞混。
01const char* p = "ok"; 02int b[2] = {1, 2}; 03int* q = b; 04cout << p << endl; 05cout << q << endl;
关于两行输出,正确的是( )。
考点:两种指针输出差异(C5)。
(C5)考点:两种指针输出差异——cout 对 char* 输出字符串、对其他指针输出地址。
解析:本题考查两种指针输出差异。这是流输出的历史约定:字符串太常用,char* 直接印内容;int* 没有这种约定,印出的是十六进制地址值。所以第一行 ok 第二行地址。
排除法:C 以为输出 'o' 和 的人两边都按「取首值」猜;B 以为都输出地址的人不知道 char* 的特殊性;A 以为第二行输出 12 的人以为指针会展开数组内容。
01char s[] = "abc"; 02char* p = s; 03cout << sizeof(s) << " " << sizeof(p);
在 64 位系统中输出是( )。
考点:字符数组与字符指针的大小(C6)。
(C6)考点:字符数组与字符指针的大小——sizeof 数组得整块大小含结尾零,sizeof 指针得 8 字节。
解析:本题考查字符数组与字符指针的大小。char s[] = "abc" 抄了 个字节( 个字母加 '\0'),所以 sizeof(s) 是 ;p 只是指针,64 位下 sizeof(p) 是 。数一下内容长度要用走步法,那是 K2 的考点。
排除法:B 答 、 的人漏算了结尾的 '\0';D 答 、 的人把指针大小当成了 int 或内容长;C 答 、 的人两边概念都没分清。
01int x = 5; 02int& r = x; 03r = 20; 04cout << x;
输出是( )。
考点:引用即别名(D1)。
(D1)考点:引用即别名——引用是已存在变量的另一个名字,对引用的一切操作都落在原变量上。
解析:本题考查引用即别名。int& r = x; 之后 r 和 x 是同一块内存的两个名字,r = 20 就是 x = 20,输出 。引用不像指针那样需要解引用符号。
排除法:A 选 的人以为引用只记住初始值、赋值不生效;C 选 的人以为引用会新建一块清零的内存;D 选编译错误的人不了解引用定义的基本语法。
下列定义引用的语句中,合法的是( )。
考点:引用必须初始化(D2)。
(D2)考点:引用必须初始化——引用定义时必须绑定目标,不能先声明后绑定,也不能绑字面量或空值。
解析:本题考查引用必须初始化。引用的生命规则:定义即绑定、绑定不更换、不孤立存在。所以 int& r; 缺目标、int& r = 5; 字面量没有内存位置可绑、int& r = nullptr; 更是无目标可指,都不合法。
排除法:B 选裸声明的人把引用当成了可以先建后接的指针;C 选绑 的人不知道字面量是临时值,普通引用绑不上;D 选绑空值的人把「指针可空」的错误印象带进了引用。
01int x = 1, y = 2; 02int& r = x; 03r = y; 04cout << x;
输出是( )。
考点:赋值不换绑定(D3)。
(D3)考点:赋值不换绑定——对引用赋值是给目标变量赋值,引用的绑定关系从定义起终生不变。
解析:本题考查赋值不换绑定。r = y 编译器理解为「把 的值 写进 r 绑定的 x」,输出 。想让「换指向」只能用指针(p = &y;),引用没有这个能力。
排除法:D 选 的人以为赋值对原变量无效;C 选 的人凭空想象了清零动作;B 选编译错误的人不知道引用间赋值完全合法。
与指针相比,关于引用的说法正确的是( )。
考点:无空引用(D4)。
(D4)考点:无空引用——引用必须绑定已存在的对象,不存在「空引用」,这也让它比指针更安全。
解析:本题考查无空引用。引用天生绑定实体,用之前不需要判空;指针则可能为空,解引用前常要检查。这是「能用引用就不用指针」这句工程格言的依据。
排除法:D 以为可以先不初始化的人把指针的延迟绑定套给了引用;C 以为能绑空指针的人没想过绑定的必须是一个对象;A 以为引用存地址能做指针算术的人把引用当成了「自动解引用的指针」,它没有独立的地址运算能力。
01int x = 7; 02int& r = x; 03r += 3; 04cout << x;
输出是( )。
考点:操作转发目标(D5)。
(D5)考点:操作转发目标——对引用做算术、赋值,效果等同于对目标变量做同样操作。
解析:本题考查操作转发目标。r += 3 就是 x += 3,,输出 。引用不是副本,也不截获操作,所有操作原样转发。
排除法:B 选 的人以为引用把操作拦在了自己身上;C 选 的人以为 += 会替换原值;A 选编译错误的人不知道复合赋值对引用完全适用。
int x = 1; int& r = x; 在 64 位系统中 sizeof(r) 的值是( )。
考点:引用的大小(D6)。
(D6)考点:引用的大小——sizeof 引用得到的是目标类型的大小,引用没有自己独立的大小。
解析:本题考查引用的大小。sizeof(r) 编译器直接按目标 int 算,得 。引用不是对象,只是名字的别名关系,不占独立的存储概念。
排除法:D 选 的人以为引用底层是指针就按指针大小算,语言层面 sizeof 已经替你穿透到目标;C 选 的人混淆了 char;B 选不确定的人不了解这规则是确定的。
01int x = 5; 02int* p = &x; 03int& r = x;
两个 & 的含义分别是( )。
考点:与号双重身份(D7)。
(D7)考点:与号双重身份——& 在表达式中是取地址运算,在类型声明中是引用标志。
解析:本题考查与号双重身份。判断方法看位置:int* p = &x; 里 &x 出现在表达式里,取地址;int& r = x; 里 & 紧跟类型出现在声明里,标明引用。同一个符号两副面孔,读代码要先分清它戴的是哪顶帽子。
排除法:B 以为两个都是取地址的人把声明里的 & 误读成了运算;A 以为都是引用声明的人把表达式里的 &x 误读成了类型;D 以为第一个是引用第二个是取址的人把两个位置正好弄反。
下列关于引用和指针区别的说法,正确的是( )。
考点:引用与指针区别(D8)。
(D8)考点:引用与指针区别——引用免解引用、不可空、不可换绑;指针全相反。
解析:本题考查引用与指针区别。三对差异要背牢:使用上引用直接用名字、指针要 *;存在上引用必绑实体、指针可空;绑定上引用从一而终、指针随时改指。参数传递中两者都能让函数改动外部,引用写法更简洁。
排除法:A 以为引用可空的人把指针的隐患错安在引用头上;D 以为引用能换绑的人没掌握 D3 的「赋值不换绑定」;B 以为指针必须初始化的人弄反了,可空可不初始化的恰是指针、必须初始化的恰是引用。
01void f(int x) { 02 x = x * 2; 03} 04int main() { 05 int a = 6; 06 f(a); 07 cout << a; 08 return 0; 09}
输出是( )。
考点:传值改不动(E1)。
(E1)考点:传值改不动——传值参数是实参的副本,函数内怎么改都影响不到外部。
解析:本题考查传值改不动。f(a) 把 拷一份给形参 x,函数内 x 变 ,函数返回副本销毁,a 原封不动输出 。想要「改回去」必须换成引用或指针。
排除法:B 选 的人以为函数内的修改会带出来;D 选 的人以为 x * 2 是 x / 2;C 选 的人以为未初始化或被清零。
要写一个能真正交换两个 int 变量值的函数 mySwap,正确的签名与调用方式是( )。
考点:交换函数签名(E2)。
(E2)考点:交换函数签名——要交换外部变量,引用形参 int& x 配调用 mySwap(a, b) 才配对。
解析:本题考查交换函数签名。引用版形参直接绑定实参,函数内交换即外部交换。值传版只交换副本;指针版可以工作但必须传地址且函数内解引用,配对错了任一半都不能编译或工作。
排除法:B 选值传版的人不了解副本隔离;C 选「指针形参配直接传变量」的人弄混了调用约定,指针形参要收地址,传变量名类型对不上;A 选「引用形参配传地址」的人犯了同样的配对错误,&a 是地址不是 int。
结构体 Stu 数据量较大,函数需要修改传入变量的成员,最合适的形参写法是( )。
考点:大结构体传引用(E3)。
(E3)考点:大结构体传引用——数据量大又要修改时,引用形参免拷贝且改动生效,一举两得。
解析:本题考查大结构体传引用。Stu& s 直接绑定调用处的变量:没有整块拷贝的开销,函数内的修改也直接写回原对象。值传版拷贝整块白费时间且改不动原件。
排除法:A 选值传的人既丢了效率又丢了修改能力;C 选只传学号的人答非所问,函数需要的是整份数据;B 选「指针形参但不取地址」的人配对错误,指针形参必须收到地址。
一个函数要把两个计算结果同时带回调用处,且不使用返回值,正确的做法是( )。
考点:引用参数带出结果(E4)。
(E4)考点:引用参数带出结果——返回值只有一个,多个结果靠引用参数直接写回调用处变量。
解析:本题考查引用参数带出结果。惯用法:void solve(int in, int& out1, int& out2),函数内给 out1、out2 赋值,调用处变量随之更新。经典例子如一次遍历同时求最大值和最小值。
排除法:A 选给传值参数赋值的人忘了副本改不动;D 选打印输出的不算把数据交给调用者,只是给人看;C 选存局部变量的人没意识到函数一返回局部变量就销毁。
void f(int& x) { x = 99; } 调用 f(a) 后,关于 a 的说法正确的是( )。
考点:传引用改实参(E5)。
(E5)考点:传引用改实参——引用形参的赋值直接写在实参变量上,调用方立刻可见。
解析:本题考查传引用改实参。f(a) 后 a 就是 ,这是引用的本职功能。它也是把双刃剑:不希望被改的参数不该用引用传。
排除法:C 以为只有函数内部的副本变成 的人把引用传参当成了值传;B 以为编译报错的人语法完全合法,x = 99 通过引用落到实参与普通赋值无异;D 以为「有时变有时不变」的人不了解引用行为是确定的。
函数头部 void add(int& c, int v) 中,参数 c 的性质是( )。
考点:形参引用解读(E6)。
(E6)考点:形参引用解读——读函数签名时,int& c 一眼判定:函数内对 c 的改动会落到实参。
解析:本题考查形参引用解读。看签名就能预判函数行为:void add(int& c, int v) 中 c 是「带出」参数、v 是「带入」参数。竞赛代码里计数器、答案变量常用引用形参累加。
排除法:A 以为是副本的人读签名时漏看了 &;C 以为是对实参取地址的人混淆了声明与运算场景;B 以为只能传常量的人正好说反,int& c 要求传可修改的变量。
要定义一个学生类型,包含字符串姓名 name 和整数年龄 age,正确的定义是( )。
考点:结构体定义语法(F1)。
(F1)考点:结构体定义语法——struct 类型名 { 成员列表 };,成员各自带类型声明,结尾分号不能丢。
解析:本题考查结构体定义语法。每个成员都是完整的「类型 名字;」声明,多个成员要分行或用逗号连接同类型变量。定义末尾的分号是初学者最常丢的字符。
排除法:C 把两种类型用逗号连在一行的人混淆了「同类型多变量」与「不同类型多成员」;A 把关键字写在名字后面的写反了语法顺序;D 不写成员类型的人不知道成员必须有类型。
下列选项中,不属于 C++ 基本数据类型的是( )。
考点:struct 非基本类型(F2)。
(F2)考点:struct 非基本类型——基本数据类型是语言内置的 int、float、char 等,struct 是构造自定义类型的关键字。
解析:本题考查 struct 非基本类型。struct 本身不是一种数据类型,而是「用来定义类型」的工具,由它造出来的 Stu、Pt 是用户自定义类型。C++ 基本类型清单里没有 struct 的位置。此问法在近年真题中考过,属概念送分点但要求对「基本」的边界有清楚认识。
排除法:C 选 int 的人把「构造自定义类型的关键字」误当成了内置类型名,int 恰是最基本的整数类型;B 选 float 的人没抓住「基本」二字的含义,浮点型同样在基本类型清单里;D 选 char 的人忘了字符型也是语言内置的基本类型。
Stu s; 已定义,要读取 s 的成员 age,正确的表达式是( )。
考点:点号成员访问(F3)。
(F3)考点:点号成员访问——普通结构体变量用点号取成员,s.age。
解析:本题考查点号成员访问。点号是「对象取成员」的运算符;箭头 -> 是「指针取成员」的运算符,两者分工明确。s[age] 是数组下标语法,结构体没有这种用法。
排除法:D 选箭头的人没注意 s 是普通变量不是指针;A 选方括号的人把成员当成了数组元素;C 选反写 age.s 的人方向弄反,成员跟在对象后面。
Pt a = {2, 9}; Pt b; b = a; 关于 b = a 这条赋值,正确的是( )。
考点:结构体整体赋值(F4)。
(F4)考点:结构体整体赋值——同类型结构体之间可以用 = 整体赋值,效果是成员逐个拷贝。
解析:本题考查结构体整体赋值。b = a 把 和 分别拷给 b 的两个成员,之后 b 与 a 是两块独立内存互不影响。这与数组不同——数组不能整体赋值,结构体可以。
排除法:A 以为成为引用的人把赋值当成了绑定;C 以为只拷第一个成员的人低估了默认赋值行为;B 以为不能用 = 的人把数组的限制错记到了结构体头上。
Stu a[3]; 要访问第 2 个学生的 age 成员,正确的表达式是( )。
考点:结构体数组访问(F5)。
(F5)考点:结构体数组访问——先下标后点号:a[i].member,两步连写。
解析:本题考查结构体数组访问。第 2 个学生下标为 (从 数起),取其 age 成员写 a[1].age。顺序是「先从数组里选出元素,再从元素里选出成员」。
排除法:B 用箭头的人不知道箭头只属于指针写法;D 写 a.age[1] 的人把点号和下标顺序颠倒,a 是数组没有 age 成员;A 写圆括号的人把访问语法当成了函数调用。
01struct Pt { int x, y; }; 02struct Line { Pt a, b; }; 03Line l;
要把 l 中端点 b 的 y 坐标改为 ,正确的语句是( )。
考点:嵌套结构体访问(F6)。
(F6)考点:嵌套结构体访问——多层成员从外向内用点号逐层进入:l.b.y。
解析:本题考查嵌套结构体访问。Line 的成员本身又是结构体,访问路径像剥洋葱:先 l.b 拿到端点结构体,再 .y 拿到坐标成员。全程点号,因为每一层都是普通变量。
排除法:C 用箭头的人不知道沿途没有任何指针;B 写 l.y.b 的人跳层乱序,l 没有成员 y;A 写 b.l.y 的人把内层成员当成入口,方向反了。
01typedef struct { 02 int x, y; 03} Pt; 04Pt p;
关于 Pt p; 这行,正确的是( )。
考点:typedef 别名(F7)。
(F7)考点:typedef 别名——typedef 给匿名结构体类型起别名,之后直接用别名定义变量,不必带 struct 字样。
解析:本题考查 typedef 别名。Pt 成为该结构体的正式名字,Pt p; 合法且自然。在 C++ 中即使不用 typedef,struct Pt { }; 也能直接 Pt p;,但 typedef 写法在竞赛代码中仍常见,读得懂即可。
排除法:D 以为 Pt 是变量的人没看到 typedef 关键字改变了整条语句的性质;C 以为必须写 struct Pt p; 的人用的是 C 语言的老规则;A 以为 typedef 只管基本类型的人低估了它的适用范围。
在 C++ 中,关于 struct 的说法正确的是( )。
考点:成员函数支持(F8)。
(F8)考点:成员函数支持——C++ 的 struct 可以包含成员函数,是面向对象程序设计的直接支持。
解析:本题考查成员函数支持。把数据和处理数据的函数包在一起,就是「类」的雏形;C++ 中 struct 与 class 的能力基本相同,构造一个 struct 或 class 类型实例都被视为涉及面向对象特性的运用。
排除法:A 以为只能装 int 和 char 的人低估了成员的丰富性,数组成员、字符串成员都合法;B 以为与面向对象无关的人没见过成员函数写法;C 以为不能含数组成员的人把某种想象的限制当了真。
01struct T { 02 char a; 03 char b; 04 char c; 05};
sizeof(T) 的值是( )。
考点:大小为成员之和(G1)。
(G1)考点:大小为成员之和——同类型小成员排在一起时,结构体大小就是各成员大小之和。
解析:本题考查大小为成员之和。三个 char 各占 字节,共 字节。结构体把成员「平铺」在自己内部,不像联合体那样叠放。若成员类型混杂,编译器可能插入对齐空隙使实际大小略大,入门阶段先掌握「和」的下限口径。
排除法:C 选 的人把结构体当成了联合体按最大成员取;D 选 的人以为每个 char 都被撑成 int 的 字节;A 选不确定的人没掌握基本规则——纯 char 成员的和是确定的。
01struct Arr { 02 int a[5]; 03};
sizeof(Arr) 的值是( )。
考点:数组成员的大小(G2)。
(G2)考点:数组成员的大小——结构体里的数组成员整块计入,int a[5] 贡献 20 字节。
解析:本题考查数组成员的大小。数组成员是实实在在的 个 int 共 字节,全部算进结构体大小,不是只存一个地址或一个长度。这为「结构体装下大块数据」提供了直接支撑。
排除法:A 选 的人以为成员只算一个 int;D 选 的人以为数组退化成指针只占地址宽度——成员数组不会退化;B 选 的人数的是元素个数不是字节数。
Pt a = {1, 2}, b = {1, 2}; 使用表达式 a == b 的结果是( )。
考点:结构体不可整体比较(G3)。
(G3)考点:结构体不可整体比较——编译器不自动生成 ==,成员相等也不能直接比。
解析:本题考查结构体不可整体比较。a == b 编译不通过,因为语言不替结构体定义默认的相等规则;要比就得逐成员 a.x == b.x && a.y == b.y,或自己写比较函数。这与「可以整体赋值」形成鲜明对比:赋值有明确的拷贝语义,相等却没有统一的定义标准。
排除法:C 以为结果为真的人以为编译器会逐成员比;B 以为比的是地址的人把数组名的比较规则错套过来;D 以为成员是整数就能比的人不知道限制在语法层面,与成员类型无关。
01struct A { int x; }; 02struct B { double x; };
关于两个成员名都叫 x,正确的是( )。
考点:不同结构体成员同名(G4)。
(G4)考点:不同结构体成员同名——每个结构体的成员在自己的作用域内命名,两个结构体各有一个 x 完全合法。
解析:本题考查不同结构体成员同名。访问时通过所属对象区分:A 的变量取 x 是 int,B 的变量取 x 是 double,互不干扰。这种「成员跟着类型走」的规则让命名可以贴切重复。
排除法:B 以为重复定义的人把全局变量不许重名的规则套到了成员上;D 以为是同一个变量的人没理解成员必须经对象访问;A 以为要类型相同才行的人虚构了一条不存在的限制。
在函数内部定义 Stu s; 且未初始化就输出 s.age,结果是( )。
考点:未初始化成员随机(G5)。
(G5)考点:未初始化成员随机——函数内定义的结构体变量,其成员不会自动清零。
解析:本题考查未初始化成员随机。局部结构体与局部数组一样躺在栈上,里面是残留数据。要确定值就初始化:Stu s = {0}; 或逐成员赋值。全局区定义的结构体才会被清零。
排除法:B 以为一定是 的人把全局变量的清零待遇错记到局部变量头上;C 以为编译报错的人不知道未初始化是合法但危险的状态;A 以为是类型最大值的人想象出了一个不存在的默认填充规则。
01union Data { 02 int num; 03 char symbol; 04};
关于成员 num 与 symbol 的存储关系,正确的是( )。
考点:成员共用存储(H1)。
(H1)考点:成员共用存储——联合体所有成员叠放在同一段内存上,写任何一个都在改这块公共内存。
解析:本题考查成员共用存储。num 和 symbol 从同一地址开始,占宽的成员决定这段内存多大,占窄的成员只用其中一部分。这是联合体省空间的原理,也是它「互相干扰」的根源。
排除法:C 以为各占独立内存的人把联合体当成了结构体;A 以为是引用的人混淆了「共用内存」与「别名」两个概念;D 以为不能同时声明的人没见过联合体定义,多个成员共存正是它的形态。
01union U { 02 int num; 03 float value; 04 char symbol; 05};
sizeof(U) 的值是( )。
考点:联合体大小(H2)。
(H2)考点:联合体大小——联合体大小等于最大成员的大小,不是成员之和。
解析:本题考查联合体大小。成员叠放,需要的内存只要装得下最宽的即可:int 与 float 各 字节、char 字节,最大 ,故 sizeof(U) 为 。对比:同样成员写成 struct 就是 的级别,这正是省空间的体现。
排除法:C 选 的人按结构体的求和逻辑算;A 选 的人反向按最小成员取;B 选 的人既求了和又凑了对齐,方向全错。
关于联合体变量在某一时刻能保存的值,正确的是( )。
考点:一次只存一个值(H3)。
(H3)考点:一次只存一个值——公共内存同一时刻只保留最后一次写入的成员的解释。
解析:本题考查一次只存一个值。联合体是「一室一用」:写入新成员就覆盖了旧成员的位型。程序员要自己记住「现在里面放的是哪种」,语言不提供这个标记。
排除法:C 以为所有成员同时有效的人把叠放当成了平铺;B 以为轮流保存的人想象出了一种不存在的轮换机制;D 以为必须全写一遍的人没理解覆盖写入的用法,写一个即可。
01union Data { int num; char symbol; }; 02Data d; 03d.num = 65; 04d.symbol = 'B';
关于此后 d.num 的值,正确的是( )。
考点:后写覆盖先写(H4)。
(H4)考点:后写覆盖先写——写入一个成员后,之前写入的其他成员的数据已被破坏。
解析:本题考查后写覆盖先写。d.symbol = 'B' 落在公共内存的低字节上,num 原来的 (其二进制低字节为 )已被改写,数值面目全非,再当 用就是错误。要恢复必须重写 d.num。
排除法:C 以为仍是 的人没意识到两个成员共用内存;B 以为自动变成 的人给语言虚构了复位行为;D 以为编译器拦截的人不知道这种覆盖正是联合体的正常用法,编译器不报错。
01union Data { 02 int num; 03 float value; 04 char symbol; 05}; 06union Data data;
要修改 data 的 value 成员让它存储 ,正确的语句是( )。
考点:联合体成员访问(H5)。
(H5)考点:联合体成员访问——联合体普通变量用点号取成员,与结构体完全一致。
解析:本题考查联合体成员访问。data 是普通变量(不是指针),取成员用 data.value。箭头 data->value 只在 data 是指针时才成立。此考法对应近年真题,考的就是点号与箭头的选择。
排除法:A 写 value.data 的人把成员名放在了前面,主语弄反;D、C 写箭头的两个选项没检查 data 的身份——它不是指针,箭头无权使用。
成员列表完全相同时,union 与 struct 的本质区别是( )。
考点:联合体与结构体区别(H6)。
(H6)考点:联合体与结构体区别——叠放共用内存与平铺各占内存,是两者的分水岭。
解析:本题考查联合体与结构体区别。同一组 {int num; char symbol;}:写成 union 只占 字节且互相覆盖;写成 struct 占 字节级别且各自独立。访问符(点号还是箭头)只取决于变量本身是不是指针,与 union、struct 无关。
排除法:B 以为一个存数字一个存文字的人凭用途瞎猜类型分工;A 以为访问符不同的人把「变量是否指针」的决定权错安在类型种类上;C 以为 struct 总更小的人正好说反,平铺的 struct 不小于叠放的 union。
联合体最典型的用途是( )。
考点:联合体用途(H7)。
(H7)考点:联合体用途——让同一块内存按不同类型解释,典型场景是省空间与多格式解读。
解析:本题考查联合体用途。例如报文解析:同一段字节流有时按 int 整读、有时按 char[4] 拆读,用联合体一处存储两种视角。它的价值是「省」与「灵活」,不是速度也不是容量。
排除法:C 以为让程序更快的人把省内存错当成了提速;B 以为替代数组的人没注意联合体通常只有一个「活跃成员」,装不下批量数据;D 以为同时保存所有成员值的人回到了结构体的语义。
01int x = 10, y = 20; 02int* p = &x; 03*p = *p + 5; 04p = &y; 05*p = *p * 2; 06cout << x << " " << y;
输出是( )。
考点:换指向追踪(I1)。
(I1)考点:换指向追踪——指针改指后,之前的解引用痕迹留在旧目标上,新的解引用作用于新目标。
解析:本题考查换指向追踪。三步走:*p + 5 使 x 从 变 ;p = &y 把指针挪到 y;*p * 2 使 y 从 变 。输出 15 40。指针的「搬家」不影响旧目标的现状。
排除法:D 答 10 20 的人以为解引用改不动原变量;A 答 15 20 的人漏了第三步对 y 的翻倍;C 答 10 40 的人漏了第一步对 x 的加 。
01int a = 3, b = 7; 02int* p = &a; 03int* q = &b; 04*p = *q; 05p = q; 06*q = 10; 07cout << a << " " << b << " " << *p;
输出是( )。
考点:两指针同址(I2)。
(I2)考点:两指针同址——p = q 之后两个名字管同一地址,动一处两处都看得见。
解析:本题考查两指针同址。*p = *q 把 的 写进 a;p = q 让 p 也指向 b;*q = 10 写的是 b,此时 *p 读的也是 b,故输出 7 10 10。指针赋值只搬地址,不动数据。
排除法:A 答 3 10 10 的人以为 *p = *q 只是拷贝比较、没改到 a;C 答 7 7 10 的人以为 *q = 10 把 也改了,a 与 b 并不共用内存;B 答 3 7 7 的人全程方向混乱。
01int x = 5; 02int* p = &x; 03cout << *&x << " " << *p + 1;
输出是( )。
考点:互逆组合运算(I3)。
(I3)考点:互逆组合运算——*& 相互抵消,*p + 1 是先取值再加一。
解析:本题考查互逆组合运算。*&x 里取地址与解引用抵消得 ;*p + 1 是 (*p) + 1 得 ,输出 5 6。注意与 *(p + 1) 的区别——后者先挪指针再取值。
排除法:D 答 6 5 的人把两个输出算反;A 答 5 5 的人以为加 不生效;C 答 6 6 的人以为第一个输出也要加一。
01int x = 1; 02int* p = &x; 03int* q = NULL; 04if (p) cout << "P"; 05if (q) cout << "Q"; 06cout << "!";
输出是( )。
考点:指针作条件(I4)。
(I4)考点:指针作条件——非空指针在条件中为真,空指针为假,可直接当布尔值用。
解析:本题考查指针作条件。p 指向 x 非空,打印 P;q 是 NULL,条件为假跳过;最后补 !,输出 P!。这是「先判空再用」防御式写法的原理。
排除法:C 答 PQ! 的人以为空指针也为真;A 答 ! 的人以为指针不能直接作条件;B 答 QP! 的人把判断顺序弄反,代码从上往下执行。
01int a = 4, b = 4; 02int* p = &a; 03int* q = &b; 04if (p == q) cout << "SAME"; 05else cout << "DIFF"; 06if (*p == *q) cout << "-EQ";
输出是( )。
考点:地址比较与值比较(I5)。
(I5)考点:地址比较与值比较——比指针比的是「是不是同一个位置」,比解引用比的是「值是否相等」。
解析:本题考查地址比较与值比较。p、q 分别指 a、b 两块内存,地址不同打印 DIFF;*p 与 *q 都是 ,打印 -EQ,合成 DIFF-EQ。两个变量可以值相同但绝不共用地址。
排除法:B 答 SAME-EQ 的人以为值相等地址就相等;C 答 DIFF 的人漏看了第二个条件;D 答 SAME 的人以为指针比较会自动深入到值。
01int x = 100; 02int* p = &x; 03int* q = p; 04*q = *q - 60; 05cout << *p << " " << x;
输出是( )。
考点:别名指针链(I6)。
(I6)考点:别名指针链——多个指针指向同一变量时,经任何一个修改,其余全部可见。
解析:本题考查别名指针链。q = p 后 q 也指 x,*q -= 60 把 改成 ,*p 与 x 读到的都是 ,输出 40 40。这与引用的别名效应同理,只是引用天生绑定、指针可以换绑。
排除法:D 答 100 40 的人以为 *p 还留着旧值;C 答 40 100 的人以为 x 本体不受影响;B 答 100 100 的人以为赋值发生在拷贝上。
01int a[] = {3, 1, 4, 1, 5}; 02int s = 0; 03for (int* p = a; p < a + 5; p++) 04 s += *p; 05cout << s;
输出是( )。
考点:指针遍历求和(J1)。
(J1)考点:指针遍历求和——for (int* p = a; p < a + 5; p++) 是指针走步访问数组的惯用法。
解析:本题考查指针遍历求和。p 从首元素走到尾后位置,每步 s += *p 取当前元素:。循环变量本身是指针,这是与下标遍历并列的标准写法。
排除法:B 答 的人漏加了一个 (数组里有两个 );A 答 的人只算了 p 最后停的位置对应的末元素值;C 答 的人只算了首元素,以为循环只跑一次。
01int a[] = {2, 4, 6, 8, 10}; 02int* p = a; 03cout << *(p + 3) << " " << p[1];
输出是( )。
考点:星号偏移访问(J2)。
(J2)考点:星号偏移访问——*(p + 3) 从 p 出发偏移 个元素再解引用;p[1] 是同一逻辑的下标写法。
解析:本题考查星号偏移访问。p 指在 a[0],*(p + 3) 取 a[3] 为 ;p[1] 即 *(p + 1) 取 a[1] 为 ,输出 8 4。两种写法可以混用,含义完全一致。
排除法:D 答 8 2 的人把 p[1] 误解成「第 个元素 a[0]」,下标从 起;A 答 10 4 的人把偏移数成了 ;B 答 6 4 的人偏移数错成 取了 a[2]。
01int a[] = {5, 10, 15, 20}; 02int* p = a; 03cout << *p << " "; 04p++; 05cout << *p << " " << *p + 1;
输出是( )。
考点:自增前后取值(J3)。
(J3)考点:自增前后取值——p++ 移动的是指针本身,*p + 1 加的是取出的值,两者互不干扰。
解析:本题考查自增前后取值。第一次输出 *p 为 ;p++ 后 p 指 a[1],输出 ;*p + 1 是 ,输出 5 10 11。要区分 *p + 1(值加一)与 *(p + 1)(指针挪一位再取值)。
排除法:C 答 5 10 10 的人以为加一不生效或把 *p + 1 看成了 *p;D 答 5 5 6 的人以为 p++ 没移动指针;B 答 10 15 16 的人以为第一输出前指针已经移动过。
01int a[] = {10, 20, 30, 40, 50}; 02int* p = &a[1]; 03int* q = &a[4]; 04cout << *q - *p << " " << q - p;
输出是( )。
考点:值差与元素差(J4)。
(J4)考点:值差与元素差——*q - *p 是两个元素值的差,q - p 是两个位置隔几个元素,量纲完全不同。
解析:本题考查值差与元素差。*q - *p 即 ;q - p 是 &a[4] 与 &a[1] 之间隔的元素数 ,输出 30 3。前者是算术,后者是指针运算,同一条输出语句里两码事。
排除法:C 答 20 3 的人把值差错算成 ,拿 a[2] 减了;A 答 30 12 的人把元素差又乘上字节数 ;B 答 20 16 的人两处都算错。
01void f(int a[]) { 02 cout << sizeof(a); 03} 04int main() { 05 int a[10]; 06 cout << sizeof(a) << " "; 07 f(a); 08 return 0; 09}
在 64 位系统中输出是( )。
考点:传参前后的大小(J5)。
(J5)考点:传参前后的大小——数组在定义处 sizeof 得整块大小,传参后形参退化成指针 sizeof 得 8。
解析:本题考查传参前后的大小。main 里 int a[10] 占 字节;传入 f 后 a 只是 int*,64 位下 sizeof 为 ,输出 40 8。这道题是「数组传参必传长度」的铁证。
排除法:B 答 40 40 的人以为函数内还是真数组;A 答 8 8 的人以为两边都退化了,定义处的数组并不退化;D 答 40 4 的人把指针大小记成了 int 的宽度。
01int a[] = {1, 2, 3, 4, 5}; 02int* p = a; 03int* q = a + 4; 04while (p < q) { 05 int t = *p; 06 *p = *q; 07 *q = t; 08 p++; 09 q--; 10} 11for (int i = 0; i < 5; i++) cout << a[i];
输出是( )。
考点:双指针逆置(J6)。
(J6)考点:双指针逆置——头尾两个指针相向而行,交换所指元素直至相遇,是逆置数组的双指针惯用法。
解析:本题考查双指针逆置。第一轮换 a[0] 与 a[4],第二轮换 a[1] 与 a[3],a[2] 居中不动,结果 54321。p < q 保证对称位置只换一轮。
排除法:A 答 12345 的人以为交换没有生效;C 答 52341 的人只完成了一次交换就以为结束;D 答编译错误的人不知道两个指针可以直接比较。
01const char* s = "banana"; 02int cnt = 0; 03for (const char* p = s; *p != '\0'; p++) 04 if (*p == 'a') cnt++; 05cout << cnt;
输出是( )。
考点:统计字符次数(K1)。
(K1)考点:统计字符次数——字符指针走步到 '\0' 为止,逐个比对目标字符。
解析:本题考查统计字符次数。banana 共 个字符,其中 'a' 出现在第 、、 位共 次。循环条件 *p != '\0' 精确覆盖有效字符。
排除法:A 答 的人把串长当成了次数;C 答 的人以为重复字符只计一次;B 答 的人漏数了一个 'a'。
01char s[] = "hello"; 02int n = 0; 03for (char* p = s; *p != '\0'; p++) n++; 04cout << n << " " << sizeof(s);
输出是( )。
考点:走步求串长(K2)。
(K2)考点:走步求串长——走步计数得到内容长度,sizeof 得到含结尾零的整块大小。
解析:本题考查走步求串长。hello 走 步遇到 '\0',n 为 ;char s[] = "hello" 实占 字节( 字符加 个 '\0'),输出 5 6。两个数字相差恰是结尾零。
排除法:B 答 5 5 的人漏算了 '\0' 占位;A 答 6 6 的人把串长多数一步把结尾零也计入内容;D 答 6 5 的人两个概念正好算反。
01char s[] = "AbCdE"; 02for (char* p = s; *p != '\0'; p++) 03 if (*p >= 'a' && *p <= 'z') *p = *p - 32; 04cout << s;
输出是( )。
考点:指针改写大写(K3)。
(K3)考点:指针改写大写——通过指针直接改写数组内容,小写减 32 变大写。
解析:本题考查指针改写大写。AbCdE 中 b、d 是小写,各减 变 B、D,其余不动,输出 ABCDE。能用指针改是因为 s 是字符数组(自己的内存),若是只读字面量就会出事。
排除法:B 答 abcde 的人把转换方向弄反;D 答 AbCdE 的人以为数组内容不能通过指针修改;A 答交替输出的人给 printf 想象了不存在的行为。
01char src[] = "copy"; 02char dst[10]; 03char* p = src; 04char* q = dst; 05while (*p != '\0') { 06 *q = *p; 07 p++; 08 q++; 09} 10*q = '\0'; 11cout << dst;
输出是( )。
考点:指针复制字符串(K4)。
(K4)考点:指针复制字符串——源指针读、目标指针写、双双向前走,最后补 '\0'。
解析:本题考查指针复制字符串。四个字符依次搬过去,循环因源到 '\0' 结束;末尾 *q = '\0' 给目标补上结尾零,输出干净的 copy。漏补结尾零是字符串操作的头号坑。
排除法:C 选「跟着乱码」的人正说中了不补 '\0' 的后果,但本题代码补了;B 选 cpyo 的人以为指针会乱序搬运,复制是按序的;A 选空串的人以为循环没执行,首字符不是 '\0' 就会进循环。
01char s[] = "programming"; 02char* p = s; 03while (*p != 'g' && *p != '\0') p++; 04cout << (p - s);
输出是( )。
考点:扫描定位下标(K5)。
(K5)考点:扫描定位下标——指针走到目标字符停住,p - s 即它的下标。
解析:本题考查扫描定位下标。programming 的字符依次是 p(0) r(1) o(2) g(3),第一个 g 在下标 ,p - s 指针相减按元素计数得 。这是「指针差即距离」的直接应用。
排除法:A 答 的人找的是最后一个 g;B 答 的人把下标从 数起;C 答 的人以为第一个字符就命中,扫的是 'g' 不是 'p'。
01void mySwap(int& x, int& y) { 02 int t = x; 03 x = y; 04 y = t; 05} 06int main() { 07 int x = 3, y = 8; 08 mySwap(x, y); 09 cout << x << " " << y; 10 return 0; 11}
输出是( )。
考点:引用交换执行(L1)。
(L1)考点:引用交换执行——引用形参版 mySwap 的三行临时变量交换直接落在实参上。
解析:本题考查引用交换执行。t=x 存 ,x=y 使 x 变 ,y=t 使 y 变 ,输出 8 3。换成值传形参这组操作只交换了副本,输出仍是 3 8。
排除法:D 答 3 8 的人以为交换发生在副本上;B 答 8 8 的人以为 y=t 没执行;A 答 3 3 的人以为 x=y 后两者同值且不再变。
01void f(int x, int& y) { 02 x = x + 1; 03 y = y + 1; 04} 05int main() { 06 int a = 10, b = 10; 07 f(a, b); 08 cout << a << " " << b; 09 return 0; 10}
输出是( )。
考点:传值传引用对比(L2)。
(L2)考点:传值传引用对比——同一函数里值传参数纹丝不动、引用参数应声而变。
解析:本题考查传值传引用对比。x 是 a 的副本,x+1 改不动 a,a 保持 ;y 是 b 的引用,y+1 使 b 变 ,输出 10 11。签名里有无 & 一字之差,行为天壤之别。
排除法:D 答 11 11 的人以为值传也能改外部;B 答 11 10 的人把两个参数的角色看反;A 答 10 10 的人以为引用也隔离,两个都没改成。
01void add(int& c, int v) { 02 c += v; 03} 04int main() { 05 int total = 0; 06 add(total, 5); 07 add(total, 7); 08 add(total, 9); 09 cout << total; 10 return 0; 11}
输出是( )。
考点:引用累加计数(L3)。
(L3)考点:引用累加计数——引用形参当累加器用,多次调用的效果叠加到同一个外部变量。
解析:本题考查引用累加计数。total 依次加 、、 得 。若 c 是值传参数,每次加法都消失在副本里,total 恒为 。计数器经引用带出是函数设计的基本功。
排除法:C 答 的人以为只保留了最后一次;A 答 的人把引用当成了值传;D 答 的人以为只有第一次生效。
01void mySwap(int& x, int& y) { 02 int t = x; 03 x = y; 04 y = t; 05} 06int main() { 07 int a[] = {1, 2, 3, 4}; 08 mySwap(a[0], a[3]); 09 for (int i = 0; i < 4; i++) cout << a[i]; 10 return 0; 11}
输出是( )。
考点:引用交换数组元素(L4)。
(L4)考点:引用交换数组元素——引用形参照样能接数组元素,交换直接写进数组。
解析:本题考查引用交换数组元素。mySwap(a[0], a[3]) 交换 与 ,数组变为 {4, 2, 3, 1},输出 4231。数组元素是有地址的变量,引用绑它毫无障碍。
排除法:A 答 1234 的人以为数组元素不能被引用修改;C 答 4213 的人交换后把顺序抄错;B 答 3214 的人随意挪动了元素位置。
01struct Pt { int x, y; }; 02void move(Pt& p) { 03 p.x += 2; 04 p.y -= 1; 05} 06int main() { 07 Pt a = {5, 5}; 08 move(a); 09 move(a); 10 cout << a.x << " " << a.y; 11 return 0; 12}
输出是( )。
考点:结构体引用参数(L5)。
(L5)考点:结构体引用参数——Pt& p 让函数对结构体成员的修改直接生效,可反复调用叠加。
解析:本题考查结构体引用参数。每次 move 使 x 加 、y 减 ,两次后 x 为 ,y 为 ,输出 9 3。结构体不拷贝、修改留痕,正是 E3 的场景兑现。
排除法:B 答 9 5 的人漏算了 y 的两次变化;A 答 7 3 的人漏算了 x 的一次加 ;D 答 7 4 的人两个方向各漏一步。
01void upd(int& mx, int v) { 02 if (v > mx) mx = v; 03} 04int main() { 05 int m = 0; 06 int d[] = {3, 9, 2, 7}; 07 for (int i = 0; i < 4; i++) upd(m, d[i]); 08 cout << m; 09 return 0; 10}
输出是( )。
考点:引用维护最大值(L6)。
(L6)考点:引用维护最大值——引用参数当「滚动更新」的目标,遍历中不断刷新外部变量。
解析:本题考查引用维护最大值。m 从 起,依次遇 、 更新为 ,之后 、 都不比它大不再更新,输出 。引用让 upd 内的每次刷新直接写回 main 的 m。
排除法:C 答 的人以为只记第一次;D 答 的人把维护最大值做成了累加;B 答 的人把「最后看到的较大值」与「最大值」混淆, 只是末次比较的胜者之一。
01struct Pt { int x, y; }; 02int main() { 03 Pt a = {3, 4}, b = {6, 8}; 04 Pt c; 05 c.x = (a.x + b.x) / 2; 06 c.y = (a.y + b.y) / 2; 07 cout << c.x << " " << c.y; 08 return 0; 09}
输出是( )。
考点:中点坐标计算(M1)。
(M1)考点:中点坐标计算——结构体成员参与算术,两个整数相加除以 2 注意整除截断。
解析:本题考查中点坐标计算。c.x 为 (整除精确),c.y 为 ,输出 4 6。若和为奇数,整数除法会截掉 ,需要精度时得转 double。
排除法:D 答 4.5 6 的人以为 (3+6)/2 会产生小数,整数运算结果还是整数;B 答 9 12 的人只做了加法忘了除以 ;A 答 6 4 的人把两个坐标的顺序弄反。
01struct Stu { string name; int score; }; 02int main() { 03 Stu a[3] = {{"Li", 85}, {"Wang", 92}, {"Zhao", 78}}; 04 int s = 0; 05 for (int i = 0; i < 3; i++) s += a[i].score; 06 cout << s << " " << s / 3; 07 return 0; 08}
输出是( )。
考点:结构体数组统计(M2)。
(M2)考点:结构体数组统计——循环里 a[i].score 逐个累加成员,总分再除以人数。
解析:本题考查结构体数组统计。总分 ;平均 (整除恰好除尽),输出 255 85。结构体数组把「每人一条记录」组织得清清楚楚。
排除法:B 答 255 84 的人把整除错算成向下多截了一格;C 答 170 85 的人总分漏加了一人的分数;A 答 85 92 的人输出成了两个原始成绩。
01struct Rect { int w, h; }; 02int main() { 03 Rect r = {7, 3}; 04 cout << r.w * r.h; 05 return 0; 06}
输出是( )。
考点:初始化列表求值(M3)。
(M3)考点:初始化列表求值——花括号按声明顺序填成员,{7, 3} 对应 w 和 h。
解析:本题考查初始化列表求值。r.w 为 、r.h 为 ,面积 。列表顺序必须与成员声明顺序一致,错位会张冠李戴。
排除法:B 答 的人把乘法做成了加法;A 答 的人把两个数字拼在了一起;D 答 的人只取了第一个成员。
01struct Pt { int x, y; }; 02int main() { 03 Pt a = {2, 9}; 04 Pt b; 05 b = a; 06 b.x = 100; 07 cout << a.x << " " << b.x; 08 return 0; 09}
输出是( )。
考点:赋值拷贝独立(M4)。
(M4)考点:赋值拷贝独立——结构体赋值是完整深拷贝到另一块内存,之后各自修改互不影响。
解析:本题考查赋值拷贝独立。b = a 后 b 的成员也是 和 ;b.x = 100 只改 b 那块,a.x 保持 ,输出 2 100。这与引用绑定形成对照。
排除法:D 答 100 100 的人以为赋值建立了联动;B 答 2 2 的人以为对 b 的修改无效;A 答 100 2 的人把输出顺序看反。
01struct Pt { int x, y; }; 02struct Line { Pt a, b; }; 03int main() { 04 Line l = {{1, 2}, {5, 6}}; 05 cout << l.b.x - l.a.x << " " << l.b.y - l.a.y; 06 return 0; 07}
输出是( )。
考点:嵌套成员读值(M5)。
(M5)考点:嵌套成员读值——嵌套初始化逐层展开,访问路径 l.b.x - l.a.x 对应相减。
解析:本题考查嵌套成员读值。l 的两个端点是 与 ,横向差 ,纵向差 ,输出 4 4。初始化列表的层级与结构体层级一一对应。
排除法:D 答 5 6 的人直接输出了 b 端点坐标没做差;B 答 6 4 的人横向差用错了成员;C 答 4 6 的人纵向差用错了成员。
01struct Pt { int x, y; }; 02void reset(Pt p) { 03 p.x = 0; 04 p.y = 0; 05} 06int main() { 07 Pt a = {8, 3}; 08 reset(a); 09 cout << a.x << " " << a.y; 10 return 0; 11}
输出是( )。
考点:值传结构体改不动(M6)。
(M6)考点:值传结构体改不动——结构体按值传入时整个被拷贝,函数内清零清的是副本。
解析:本题考查值传结构体改不动。reset 收到 a 的完整拷贝并把副本清零,函数返回副本销毁,a 保持 {8, 3},输出 8 3。想让函数改到原件,形参要写 Pt& p(对照 L5)。
排除法:A 答 0 0 的人以为清零带回了外部;D、C 答 8 0 与 0 3 的人以为拷贝只覆盖一个成员,值传是整块拷贝、清零也是整个副本。
01struct Stu { string name; int score; }; 02bool cmp(const Stu& x, const Stu& y) { 03 return x.score > y.score; 04} 05int main() { 06 Stu a[] = {{"Li", 85}, {"Zhao", 92}, {"Wang", 78}}; 07 sort(a, a + 3, cmp); 08 cout << a[0].name; 09 return 0; 10}
输出是( )。
考点:按成员降序排序(N1)。
(N1)考点:按成员降序排序——cmp 返回 x.score > y.score,sort 后分数高的排前面。
解析:本题考查按成员降序排序。三人的分数是 、、,降序后 Zhao( 分)居首,输出 Zhao。比较函数收两个结构体的引用、只比关心的成员,这是结构体排序的标准姿势。
排除法:D 答 Li 的人把降序当成了升序, 排第一的是升序里的第二名;C 答 Wang 的人取了最低分;A 答 LiWangZhao 的人把输出看成了整串名字,只输出 a[0].name 一个。
01struct Item { int w, id; }; 02bool cmp(const Item& x, const Item& y) { 03 if (x.w != y.w) return x.w < y.w; 04 return x.id > y.id; 05} 06int main() { 07 Item a[] = {{3, 1}, {2, 5}, {3, 2}}; 08 sort(a, a + 3, cmp); 09 for (int i = 0; i < 3; i++) cout << a[i].id; 10 return 0; 11}
输出是( )。
考点:双关键字排序(N2)。
(N2)考点:双关键字排序——第一关键字升序、相同时第二关键字降序,cmp 里先比主再比次。
解析:本题考查双关键字排序。三个元素按 w 排: 在前,两个 并列;并列者按 id 降序, 号在 号前,最终 id 序列为 521。双关键字的思想是「先分主次,逐级比较」。
排除法:D 答 125 的人做成了双关键字全升序;B 答 152 的人主关键字对了但并列时没降 id;A 答 215 的人主关键字就排错了。
01struct P { int x, y; }; 02bool cmp(const P& u, const P& v) { 03 if (u.x != v.x) return u.x < v.x; 04 return u.y < v.y; 05} 06int main() { 07 P a[] = {{1, 2}, {3, 4}, {1, 2}, {5, 6}, {3, 4}}; 08 sort(a, a + 5, cmp); 09 int cnt = 1; 10 for (int i = 1; i < 5; i++) 11 if (a[i].x != a[i - 1].x || a[i].y != a[i - 1].y) 12 cnt++; 13 cout << cnt; 14 return 0; 15}
输出是( )。
考点:结构体去重(N3)。
(N3)考点:结构体去重——先按双关键字排序使相等元素相邻,再逐一与前一元素比较计数。
解析:本题考查结构体去重。排序后序列为 {1,2},{1,2},{3,4},{3,4},{5,6},与前一元素完全相同的不计数, 个元素里去重后剩 组。排序加相邻比较是结构体去重的标准套路。
排除法:A 答 的人没去重,直接数了元素个数;C 答 的人按 x 坐标聚类却漏数了一类;D 答 的人只去掉了一组重复,另一组 {3,4} 也重复了。
01bool cmp(const Stu& x, const Stu& y) { 02 return x.score < y.score; 03}
用 sort(a, a + n, cmp) 对结构体数组排序,结果是( )。
考点:比较函数方向(N4)。
(N4)考点:比较函数方向——return x.score < y.score 表示「x 排在 y 前面当且仅当 x 更小」,即升序。
解析:本题考查比较函数方向。cmp(x, y) 返回真就意味着 sort 会把 x 放在 y 前面;小于号即从小到大。要降序把小于号换成大于号即可(对照 N1)。记「返回真者在前」就不会迷路。
排除法:C 选降序的人把不等号方向读反了;B 选按名字排的人没注意比较的是 score 成员;D 选不确定的人以为没有 return 布尔值就乱序,sort 的结果是确定的。
01struct Stu { int id; string name; }; 02int main() { 03 Stu a[] = {{101, "Li"}, {102, "Wang"}, {103, "Zhao"}}; 04 int target = 102; 05 int found = -1; 06 for (int i = 0; i < 3; i++) 07 if (a[i].id == target) found = i; 08 cout << found << " " << a[found].name; 09 return 0; 10}
输出是( )。
考点:按成员查找(N5)。
(N5)考点:按成员查找——循环里用 a[i].id == target 匹配关键字成员,记下下标再取信息。
解析:本题考查按成员查找。102 在数组下标 处( 起),found 为 ,a[found].name 是 Wang,输出 1 Wang。结构体数组的「按学号查姓名」是最常见的应用之一。
排除法:B 答 2 Wang 的人把下标从 数起;A 答 1 Li 的人下标对了却取错了另一个成员;D 答 102 Wang 的人把学号当成了下标输出。
01struct Node { int data; Node* next; }; 02int main() { 03 Node* p = new Node; 04 p->data = 7; 05 p->next = NULL; 06 cout << p->data << " " << (p->next == NULL); 07 return 0; 08}
输出是( )。
考点:新节点箭头赋值(O1)。
(O1)考点:新节点箭头赋值——new Node 申请节点后用 -> 逐个填成员,p->next = NULL 标记表尾。
解析:本题考查新节点箭头赋值。p->data 为 ;p->next 被置 NULL,p->next == NULL 为真即输出 ,合起来 7 1。new 从堆里分配节点,箭头是指针取成员的专用写法。
排除法:D 答 7 0 的人以为 NULL 判断为假,空指针判等恰好为真;A 答 0 1 的人以为 data 没写成功;C 答编译错误的人不知道 -> 就是为结构体指针准备的。
01struct Node { int data; Node* next; }; 02int main() { 03 Node* head = NULL; 04 for (int i = 1; i <= 3; i++) { 05 Node* t = new Node; 06 t->data = i; 07 t->next = head; 08 head = t; 09 } 10 for (Node* p = head; p != NULL; p = p->next) 11 cout << p->data; 12 return 0; 13}
输出是( )。
考点:头插法建链(O2)。
(O2)考点:头插法建链——新节点先接上当前表头再让 head 挪到自己身上,先来的被压到后面。
解析:本题考查头插法建链。依次插入 、、:插 后链为 1;插 $2 后为 2->1;插 $3 后为 3->2->1,遍历输出 321。头插的核心两句是 t->next = head; head = t;,顺序不能反。
排除法:D 答 123 的人以为插入发生在尾部,这是尾插法的结果;C、A 答 312 与 213 的人对「新节点总在头部」的规则把握不稳,链接顺序一旦理解错就会拼出这两种序列。
01struct Node { int data; Node* next; }; 02int main() { 03 Node* head = NULL; 04 int vals[] = {2, 9, 4}; 05 for (int i = 2; i >= 0; i--) { 06 Node* t = new Node; 07 t->data = vals[i]; 08 t->next = head; 09 head = t; 10 } 11 int s = 0; 12 for (Node* p = head; p != NULL; p = p->next) 13 s += p->data; 14 cout << s; 15 return 0; 16}
输出是( )。
考点:箭头遍历求和(O3)。
(O3)考点:箭头遍历求和——for (Node* p = head; p != NULL; p = p->next) 沿链接逐节点累加。
解析:本题考查箭头遍历求和。建链部分按下标 、、 头插 vals 的值,链为 2->9->4,遍历求和 。p = p->next 是「走到下一个节点」的固定步进。
排除法:A 答 的人只算了尾节点;D 答 的人只算了中间节点;B 答 的人把建链顺序理解错后逐位又算错,链中根本没有那么多节点可加。
01struct Pt { int x, y; }; 02int main() { 03 Pt a = {8, 2}; 04 Pt* p = &a; 05 (*p).x = (*p).y + 1; 06 cout << p->x << " " << a.x; 07 return 0; 08}
输出是( )。
考点:括号星号等价写法(O4)。
(O4)考点:括号星号等价写法——(*p).x 与 p->x 完全等价,箭头是它的简写。
解析:本题考查括号星号等价写法。(*p).y 取出 ,加 得 写入 x 成员;p->x 与 a.x 读的是同一块内存,输出 3 3。括号必须有:*p.x 会先算点号导致编译错误。
排除法:C 答 9 8 的人把「读 y 加一」做成了「读 x 加一」还忽略了写回;D 答 3 8 的人以为改动只体现在指针一侧,指针与变量共用内存;B 答 2 8 的人以为赋值根本没生效。
01struct Node { int data; Node* next; }; 02Node* p = new Node; 03p->data = 5; 04delete p; 05p->data = 10;
对最后一条语句的评价,正确的是( )。
考点:释放后悬垂(O5)。
(O5)考点:释放后悬垂——delete 把内存还回去,指针本身还在,再用它就是悬垂指针。
解析:本题考查释放后悬垂。delete p 后 p 里存的地址没变但那块内存已不属于你,p->data = 10 是向已归还内存写入,行为未定义。好习惯是释放后立刻 p = NULL;。
排除法:A 以为 delete 只清零数据的人低估了它的彻底性,内存使用权都没了;B 以为自动指向下一节点的人把 delete 当成了链表步进;D 以为编译器报错的人不知道这是运行期才爆发的隐患。
01int* p = nullptr; 02cout << *p;
这段代码的结果是( )。
考点:空指针解引用(P1)。
(P1)考点:空指针解引用——对 nullptr 解引用是未定义行为,典型表现是程序当场崩溃。
解析:本题考查空指针解引用。地址 位置不可访问,*p 触发段错误。要害在于编译器不拦:语法完全合法,炸在运行期。防御写法是先判空再解引用。
排除法:B 以为输出 的人把「指针值为空」当成了「指向的内容是 」;D 以为输出地址的人把解引用当成了直接打印指针;A 以为编译报错的人不了解编译期不做这种检查。
double d = 3.5; int* p = &d; 这条语句( )。
考点:指针类型不匹配(P2)。
(P2)考点:指针类型不匹配——int* 指向 double 变量是编译错误,指针类型必须与目标一致。
解析:本题考查指针类型不匹配。&d 的类型是 double*,塞给 int* p 编译器直接拒绝。这是 A6 的易错强化:类型一致才保证解引用时按正确格式解释内存,编译期的严格检查正是在保护你。
排除法:C 以为指向整数部分的人以为指针会自动截取,不存在这种智能转换;D 以为写 **p 的人对二级解引用的适用对象有误解;A 以为自动变 double* 的人把 C++ 想成了自动推断语言,类型转换须显式进行。
函数 void read(int& x) 需要修改传入的变量,调用时写成了 read(&a)(a 是 int 变量),结果是( )。
考点:引用参数传地址(P3)。
(P3)考点:引用参数传地址——引用形参要收变量本身,传 &a 类型对不上,编译不通过。
解析:本题考查引用参数传地址。read(int& x) 的实参必须是 int 变量,&a 是 int*,类型不匹配直接编译失败。这是「引用调用与指针调用写法混搭」的高频笔误:两边要么都引用风格,要么都指针风格。
排除法:D 以为效果相同的人没注意类型系统在签名处就分了岔;A 以为合法但改不动的人描述的是值传的行为,类型不符连编译都过不了;B 以为运行时崩溃的人把它当成了运行期问题,编译期就被拦截。
01union Data { int num; char symbol; }; 02int main() { 03 Data d; 04 d.num = 100; 05 cout << d.symbol; 06 return 0; 07}
关于这个输出,正确的说法是( )。
考点:读非活跃成员(P4)。
(P4)考点:读非活跃成员——联合体里最后写入的是 num,此时读 symbol 是把同一内存按另一类型重新解释,结果不可依赖。
解析:本题考查读非活跃成员。 的二进制低字节是 $01100100,按 char 解释时在小端机器上会得到字符 d`——看起来「能用」,但字节序和实现细节不同结果就不同,竞赛与工程代码都不应依赖它。记住规则:读之前必须先写同类型成员。
排除法:B 以为一定输出 d 的人恰好撞见了常见的小端结果就当成保证;A 以为一定输出空字符的人把「未写」与「零值」画了等号;D 以为编译报错的人不知道这种读取语法合法、风险在语义层。
下列说法中正确的是( )。
考点:综合判断(P5)。
(P5)考点:综合判断——解引用写入等于改原变量、引用不可换绑、结构体可整体赋值、联合体共用内存。
解析:本题考查综合判断。*p = 3 通过地址写 x,与直接 x = 3 同效,正确。其余三项都是反向错误:引用绑定终生不变(D3)、结构体之间可用 = 赋值(F4)、联合体成员恰恰共用内存(H1)。四个知识点各考一处,做错哪条就回看对应组。
排除法:C 选可换绑的人没掌握「赋值不换绑定」;A 选不能用 = 赋值的人把数组的限制错记到结构体头上;D 选成员独立内存的人把联合体当成了结构体。