【c语言指针精选题】
c语言指针精选题
- 一、概念易错题
- 1.1🚀交换两值
- 1.2🚀野指针
- 1.3 🚀字符数组与指针语句判断
- 1.5 🚀字符串赋值
- 1.6 🚀指针的移动
- 1.7 🚀数组和指针内容
- 1.8🚀printf函数的格式控制字符串
- 1.9🚀scanf函数将用户输入的字符串
- 1.10🚀strcpy函数
- 1.11🚀常量指针
- 1.12🚀不同类型指针
- 1.13🚀二维指针
- 二、数组指针
- 2.1🚀二维数组的地址
- 2.2🚀指向包含 n个int类型元素的数组的指针
- 2.3🚀指向包含 n个int类型元素的数组的指针访问二维数组
- 2.4🚀指针数组
- 2.5🚀二维数组名代表的首地址和某行的首地址
- 三、函数指针
- 3.1🚀函数指针的调用
🤾♂️感觉指针难的可以看看这些题,肯定收获很大!
一、概念易错题
1.1🚀交换两值
A 选项: 程序采用值传递,不能实现变量值的交换,所以该选项错误。
B 选项:将swap(&a,&b); 中的参数改为a,b,就变成了值传递,更无法实现交换,该选项错误。
C 选项: 仅将形参定义为指针,而执行语句不变,依然没有通过指针去修改指向的变量的值,不能实现交换,该选项错误。
🎈答案:D
swap(int *p,int *q)//选项C
{ int *t;t = p; p = q; q = t;
}/*这段代码只是在函数内部对指针变量 p、q 自身的值进行了交换,也就是改变了它们指向的位置,但并没有通过指针去修改主函数中 a、b这两个变量的值 。*/
swap(int *p,int *q) //这样才对
{ int t;t = *p; *p = *q; *q = t;
}
1.2🚀野指针
A 选项:p = &n; 让指针 p 指向变量 n,scanf(“%d”, &p); 中 &p 是取指针 p 本身的地址,而 scanf 需要的是存储输入值的变量的地址,应该是 p 而不是 &p,所以该选项错误。
B 选项:p = &n; 让指针 p 指向变量 n,但 scanf(“%d”, *p); 中 *p 表示指针 p 所指向的变量 n,scanf 需要的是地址,这里传递的是变量值,所以该选项错误。
C 选项:scanf(“%d”, &n); 正确读取一个整数存入 n 中,但 *p = n; 时,指针 p 未初始化指向,直接对 *p 赋值会导致访问非法内存,所以该选项错误。
D 选项:p = &n; 让指针 p 指向变量 n,*p = m; 即把 m 的值赋给 p 所指向的变量 n,是合理的操作,该选项正确。
🎈答案:D
重点看C选项: *p = n;:这行代码存在问题。在 C 语言中,指针在使用前需要明确其指向,p虽然被声明为指向整型的指针int p,但在执行p = n;之前,并没有让p指向一个有效的内存地址(比如通过p = &某个已声明的整型变量;的方式)。此时p的值是不确定的,也就是它指向的内存位置是未知的。 对*p进行赋值操作,相当于向一个不确定的内存位置写入数据,这可能会覆盖其他重要的数据,甚至导致程序崩溃,这种行为被称为 “野指针” 操作,是非常危险且错误的。
1.3 🚀字符数组与指针语句判断
A 选项:
char *a = “china”; 是定义一个字符指针 a ,并让它指向字符串常量 “china” 的首地址。
而 char *a; *a = “china”; 中,char *a; 声明了指针 a ,但 *a = “china”; 是错误的。*a 表示指针 a 所指向的字符,不能直接将一个字符串赋值给一个字符,所以这两个语句不等价,A 选项错误。
B 选项:
char str[10] = {“china”}; 是正确地定义并初始化一个字符数组 str,长度为 10,初始化为字符串 “china”。
char str[10]; str[] = {“china”}; 中,str[] 这种写法在初始化时是错误的,数组名 str 本身代表数组首地址,不能这样使用来初始化,B 选项错误。
C 选项:
char *s = “china”; 定义字符指针 s 并指向字符串常量 “china”。
char *s; s = “china”; 先声明字符指针 s ,然后让 s 指向字符串常量 “china” ,这两个语句效果等价,C 选项正确。
D 选项:
char c[4] = “abc”, d[4] = “abc”; 是分别定义并初始化两个字符数组 c 和 d。
char c[4] = d[4] = “abc”; 是错误的,在 C 语言中不能这样连续对数组进行初始化赋值,D 选项错误。
🎈答案:C
✨B正确的方式:
//先声明后逐个字符赋值
#include <stdio.h>int main() {char str[10];str[0] = 'c';str[1] = 'h';str[2] = 'i';str[3] = 'n';str[4] = 'a';str[5] = '\0'; // 字符串结束标志不能忘return 0;
}
//先声明后使用strcpy函数赋值
#include <stdio.h>
#include <string.h>int main() {char str[10];strcpy(str, "china"); return 0;
}
1.5 🚀字符串赋值
A 选项:
在 C 语言中,使用字符串常量初始化字符数组时,系统会自动在字符串末尾添加 ‘\0’ 作为结束标志。这里 char s[5] = {“ABCDE”}; ,“ABCDE” 实际长度为 5,加上系统自动添加的结束符 ‘\0’ ,共需要 6 个字节的空间,而 s 数组大小为 5,空间不足,会导致数组越界,所以该选项错误。
B 选项:
char s[5] = {‘A’, ‘B’, ‘C’, ‘D’, ‘E’}; 这样只是初始化了一个包含 5 个字符的字符数组,它并不是一个字符串,因为缺少字符串结束标志 ‘\0’,不符合字符串赋值操作的要求,所以该选项错误。
C 选项:
char *s; s = “ABCDE”; 定义了一个字符指针 s ,然后让指针 s 指向字符串常量 “ABCDE” ,这是合法的操作,实现了将字符串的首地址赋给指针 s ,所以该选项正确。
D 选项:
char *s; scanf(“%s”, s); 中,指针 s 未初始化,它指向的地址是不确定的。使用 scanf 函数向一个未确定指向的指针所指向的内存区域写入数据,会导致访问非法内存,可能引发程序崩溃,所以该选项错误。
🎈答案C
本题也是野指针问题,s就是个野指针。
1.6 🚀指针的移动
首先,char *s = “abcde”; 定义了一个字符指针 s ,并让它指向字符串常量 “abcde” 的首地址,也就是字符 ‘a’ 的地址。
然后,s += 2; 这一操作使指针 s 向后移动了 2 个字符的位置(因为 s 是字符指针,移动单位是字符大小),此时 s 指向字符串中的字符 ‘c’ 的地址。
最后,printf(“%d”, s); 这里使用 %d 格式控制符输出 s 。在 C 语言中,指针本质上存储的是地址,以整数形式表示。所以这里输出的是字符 ‘c’ 的地址。
🎈答案C
🥇指针的移动:
算术运算:可通过+、-、++、–等运算符实现移动。如p++使指针指向下一个同类型数据的地址;p + n(n为整数)让指针向后移动n个同类型数据的位置;p - n则向前移动n个同类型数据的位置。
数组场景:数组名可视为常量指针,指向数组首元素。对数组名进行指针移动操作,能遍历数组元素,如arr[i]和*(arr + i)等价。
1.7 🚀数组和指针内容
A 选项:
s 是字符数组名,它是一个常量指针,代表数组首地址,其值不能被改变;p 是一个字符指针变量,可以指向不同的地址,两者本质不同,并非完全相同,该选项错误。
B 选项:
数组 s 中存储的是字符串 “china” 的各个字符以及结束符 ‘\0’ ;指针变量 p 中存储的是数组 s 的首地址,内容不相等,该选项错误。
C 选项:
数组 s 的长度为 6(包含字符串结束符 ‘\0’ ),而 p 所指向的字符串 “china” 长度为 5(不包含结束符),两者不相等,该选项错误。
D 选项:
p = s; 使指针 p 指向数组 s 的首地址,*p 表示取指针 p 所指向地址处的值,也就是 s[0] 的值,所以 *p 与 s[0] 相等,都为字符 ‘c’ ,该选项正确。
🎈答案:D
注意B选项!
字符数组s:当定义 char s[] = “china”; 时,系统会开辟一段连续的内存空间,按照顺序依次存储字符’c’、‘h’、‘i’、‘n’、‘a’以及字符串结束标志’\0’ 。虽然数组名s在表达式中会被转换为首元素的地址,但数组本身的内存空间里存的是具体的字符数据。
1.8🚀printf函数的格式控制字符串
在printf(format, “a+=b”, a, b);中:
format的值为"%s,a=%d,b=%d\n",是printf函数的格式控制字符串。
按照printf函数的规则,%s需要一个字符串类型的参数来替换,这里传入的是"a+=b";%d需要整数类型的参数来替换,这里依次传入a和b,但在执行a += b;后,a的值变为11,b的值是10 。
所以最终printf函数会按照格式控制字符串将对应参数输出,即输出a+=b,a=11,b=10。
🎈答案C
//如下也是一样的
#include <stdio.h>
int main() {int a = 1, b = 10;a += b;printf("%s,a=%d,b=%d\n", "a+=b", a, b); return 0;
}
/*两者区别在于,使用指针变量可以更灵活地在程序中动态地改变
所指向的格式控制字符串内容,而字符串常量是固定不变的。*/
1.9🚀scanf函数将用户输入的字符串
A 选项:
char str[20];声明了一个能存储 20 个字符的数组str。scanf(“%s”, &str);存在错误,str本身就代表数组首地址,相当于&str[0],对数组名取地址&str得到的是指向整个数组的指针,类型和scanf期望接收的字符数组首地址(指向字符的指针)不匹配,所以该选项错误。
B 选项:
char *p;声明了一个字符指针p,但p未初始化,是野指针。scanf(“%s”, p);试图往一个不确定的内存地址写入数据,会导致程序出现未定义行为,所以该选项错误。
C 选项:
char str[20];声明字符数组str。scanf(“%s”, &str[2]);表示从数组的第 3 个元素(数组下标从 0 开始)处开始存储输入的字符串,只要输入的字符串长度合适,不会导致数组越界,这种用法是正确的,所以该选项正确。
D 选项:
char str[20], p = str;声明了字符数组str并让指针p指向str的首地址。scanf(“%s”, p[2]);存在错误,p[2]等价于(p + 2),表示指针p所指向位置偏移 2 个字符后的那个字符,是一个字符变量,不是地址,而scanf需要的是地址参数,所以该选项错误。
🎈答案C
scanf存在的问题及限制:
无法处理包含空格的字符串:scanf函数遇到空格、制表符或换行符时会认为字符串输入结束,因此不能完整接收包含这些字符的字符串。例如输入hello world,scanf只会将hello存入字符数组。
可能导致缓冲区溢出:如果输入的字符串长度超过字符数组的大小,scanf不会自动检查边界,会导致数据写入到数组之外的内存区域,造成缓冲区溢出,引发程序错误甚至安全问题。
1.10🚀strcpy函数
首先,char *p = “abcdefgh”; 定义了字符指针p并使其指向字符串常量 “abcdefgh”。
然后,p += 3; 让指针p向后移动 3 个字符位置,此时p指向字符’d’。
接着,strcpy(p, “ABCD”) 是将字符串 “ABCD” 复制到p所指向的位置。因为p指向原字符串 “abcdefgh” 中的’d’,所以从’d’开始覆盖,原字符串变为 “abcABCD” 。strcpy函数返回目标字符串的首地址,即p。
最后,strlen(strcpy(p, “ABCD”)) 计算复制后的字符串长度。strlen函数计算字符串长度时不包含字符串结束符’\0’,字符串 “ABCD” 的长度为 4。
答案C
注意:strcpy函数会把原来的字符串覆盖,并把’\0’也复制过去。
1.11🚀常量指针
A 选项:
char a[10]=“china”; 定义一个长度为 10 的字符数组a,并使用字符串 “china” 对其初始化,系统会自动在 “china” 后面加上字符串结束符’\0’,这种方式是正确的。
B 选项:
char a[10], *p=a; 先定义字符数组a和字符指针p,并让p指向数组a的首地址。然后p=“china”; 使指针p指向字符串常量 “china”,这是合法的操作,指针p指向的内容发生了改变,该选项正确。
C 选项:
char *a; a=“china”; 定义字符指针a,然后让a指向字符串常量 “china”,即将字符串常量的首地址赋给指针a,这是正确的操作,该选项正确。
D 选项:
char a[10], *p; p=a=“china”; ,在 C 语言中,赋值表达式从右向左计算,a=“china”; 是错误的,因为数组名a是一个常量指针,代表数组的首地址,其值不能被改变,不能直接将字符串常量赋值给数组名,该选项错误。
答案:D
注意:数组名是常量指针,不能再进行赋值操作。
1.12🚀不同类型指针
A 选项:*p = 100; ,指针p在使用前没有指向一个有效的内存地址,属于野指针操作,直接对其解引用赋值是不安全的,会导致未定义行为,该选项错误。
B 选项:p = &ch; s = p; ,p是指向int类型的指针,s也是指向int类型的指针,ch是char类型变量,将char类型变量的地址赋给指向int类型的指针p,虽然不会报错,但会出现类型不匹配的问题;而且后续s = p也延续了这种潜在风险,该选项错误。
C 选项:p = &i; q = &ch; p = q; ,p是指向int类型的指针,q是指向char类型的指针,不同类型的指针之间不能直接赋值,这样会导致类型不匹配,该选项错误。
D 选项:p = &i; q = &ch; *p = 40; *q = *p; ,先让p指向int类型变量i,q指向char类型变量ch,然后给i赋值40,再将p所指向的值(即i的值40)赋给ch。在 C 语言中,int类型的值可以隐式转换为char类型(截取低 8 位),这种操作在类型转换和指针使用上是合理的,该选项正确。
答案D
注意:int类型值和char类型值可以互相赋值,但是int类型指针和char类型指针不可以。
1.13🚀二维指针
首先,int x[5] = {2, 4, 6, 8, 10}, *p, **pp; 定义了一个整型数组 x,一个指向整型的指针 p,以及一个指向指针的指针 pp。
p = x; 让指针 p 指向数组 x 的首地址,即 p 指向 x[0]。
pp = &p; 让二级指针 pp 指向指针 p 的地址。
接着看 printf(“%d”, *(p++));:
p++ 是后自增运算符,先使用 p 的当前值,再对 p 进行自增操作。此时 p 指向 x[0],*p 就是 x[0] 的值,即 2,所以第一个 printf 输出 2。输出后,p 自增,指向 x[1]。
最后看 printf(“%3d\n”, **pp);:
pp 指向 p,*pp 就是 p,此时 p 已经指向 x[1],再进行一次解引用 **pp 就相当于 *p,也就是 x[1] 的值,为 4。所以第二个 printf 输出 4(%3d 表示输出宽度为 3,不足 3 位左边补空格)。
答案B
注意:在代码中,pp = &p; 这一步执行时,pp 就指向了指针 p 的地址 。
之后执行 printf(“%d”, *(p++)); ,虽然 p 发生了自增,但 pp 存储的是 p 的地址这一事实并没有改变,它依然指向 p 对应的内存单元。不管 p 所指向的位置如何变化,pp 始终指向 p 本身所在的内存地址,所以后续通过 **pp 访问时,获取的是自增后 p 所指向的值。
二、数组指针
2.1🚀二维数组的地址
A 选项:*(a[i]+j) ,a[i] 相当于第 i 行的首地址,a[i]+j 表示第 i 行中第 j 个元素的地址,再对其进行解引用 *(a[i]+j) 得到的是第 i 行第 j 列的元素值,而不是地址,所以该选项错误。
B 选项:(a + i) ,a 是二维数组名,代表数组首地址,也就是第 0 行的首地址,a + i 表示第 i 行的首地址,并非第 i 行第 j 列元素的地址,所以该选项错误。
C 选项:*(a + j) ,a 是二维数组首地址,a + j 由于二维数组在内存中按行存储,这里j超过了行的范围概念(没有正确对应到i行j列),且再解引用*(a + j)得到的也不是第i行第j列元素地址,所以该选项错误。
D 选项:a[i]+j ,a[i] 表示第 i 行的首地址,a[i]+j 就是在第 i 行首地址的基础上向后偏移 j 个元素的位置,即第 i 行第 j 列元素的地址,所以该选项正确。
答案D
2.2🚀指向包含 n个int类型元素的数组的指针
在 C 语言中,int (*p)[4] 是一种特殊的指针声明形式。
A 选项:如果是指向整型变量的指针,应该是 int *p 的形式,而不是 int (*p)[4],所以 A 选项错误。
B 选项:指针数组的声明形式是 int *p[4],这里 [] 的优先级高于 *,表示 p 是一个数组,数组中的元素是指向 int 类型的指针。而 int (*p)[4] 中 () 使得 * 先与 p 结合,说明 p 是一个指针,并非指针数组名,所以 B 选项错误。
C 选项:int (*p)[4] 表示 p 是一个指针,它指向的是一个包含四个整型元素的一维数组。这种指针也被称为行指针,当用于二维数组时,可以方便地操作数组的行,该选项正确。
D 选项:int (*p)[4] 这种声明在 C 语言中是合法的,所以 D 选项错误。
答案C
2.3🚀指向包含 n个int类型元素的数组的指针访问二维数组
已知定义int a[2][3], (p)[3];,p是一个指向包含 3 个int类型元素的数组的指针,且p = a,此时p指向二维数组a的首地址(即第 0 行的地址)。
A 选项:(p + 2) ,p + 2表示指针p向后移动 2 个 “包含 3 个int元素的数组” 的位置,但数组a只有 2 行,p+2超出了数组范围,并且*(p + 2)是对移动后的指针进行解引用,得到的是一个int类型的值,不是地址,该选项错误。
B 选项:p[2] ,等价于*(p + 2),同样超出了数组范围,并且得到的是一个int类型的值,不是地址,该选项错误。
C 选项:p[1]+1 ,p[1]等价于*(p + 1),表示指向数组a的第 1 行,p[1]+1表示在第 1 行首地址的基础上向后移动 1 个int类型元素的位置,即第 1 行第 1 列元素的地址,该选项正确。
D 选项:(p + 1)+2 ,p + 1指向数组a的第 1 行,(p + 1)+2表示在第 1 行的基础上再向后移动 2 个 “包含 3 个int元素的数组” 的位置,超出了数组范围,该选项错误。
答案C
认真理解这句话:p[1]等价于*(p + 1),表示指向数组a的第 1行*斜体样式
2.4🚀指针数组
A 选项:strp 是一个指针数组名,它代表指针数组的首地址,即 strp[0] 的地址,并不是对字符串的引用,所以该选项不正确。
B 选项:str[k] ,str 是一个二维字符数组,str[k] 表示第 k 行的首地址,指向第 k 个字符串,是对字符串的正确引用,该选项正确。
C 选项:strp[k] ,通过 for 循环 strp[n]=str[n]; ,strp[k] 指向了二维数组 str 中的第 k 个字符串,是对字符串的正确引用,该选项正确。
D 选项:*strp 等价于 strp[0] ,strp[0] 指向了二维数组 str 中的第一个字符串,是对字符串的正确引用,该选项正确。
答案A
2.5🚀二维数组名代表的首地址和某行的首地址
A 选项:q[i]=b[i]; ,q 是指针数组,q[i] 是指向 int 类型的指针;b 是二维数组,b[i] 表示第 i 行的首地址,也是指向 int 类型的指针,将 b[i] 赋值给 q[i] 是合法的,该选项正确。
B 选项:p = b; ,p 是指向 int 类型的指针,b 是二维数组名,代表二维数组首地址,其类型是指向包含 6 个 int 元素的一维数组的指针(即行指针),与 p 的类型不匹配,不能直接赋值,该选项错误。
C 选项:p = b[i]; ,b[i] 是二维数组 b 第 i 行的首地址,类型是指向 int 类型的指针,与 p 的类型一致,可以赋值,该选项正确。
D 选项:q[i]=&b[0][0]; ,&b[0][0] 是二维数组 b 首元素的地址,类型是指向 int 类型的指针,q[i] 也是指向 int 类型的指针,可以进行赋值,该选项正确。
答案B
注意:b 是二维数组名,代表二维数组首地址,其类型是指向包含 6 个 int 元素的一维数组的指针(即行指针),b[i] 是二维数组 b 第 i 行的首地址,类型是指向 int 类型的指针
三、函数指针
3.1🚀函数指针的调用
在 C 语言中,当使用函数指针调用函数时,有两种常见的正确形式:
(*函数指针变量)(实参列表):这里p是指向函数max的指针,(*p)(a,b)是通过解引用p,并传入参数a和b来调用max函数。
函数指针变量(实参列表):即可以直接使用函数指针变量p像函数名一样调用,写成p(a,b) 。
A 选项:(*p)max(a,b); ,这种写法是错误的,(*p)是对函数指针解引用,后面应跟的是参数列表,而不是函数名max,该选项错误。
B 选项:*pmax(a,b); ,这里pmax不是有效的函数指针调用形式,并且容易让人误解,该选项错误。
C 选项:(*p)(a,b); ,符合通过函数指针调用函数的正确形式,该选项正确。
D 选项:*p(a,b); ,*p是对指针p解引用,这里的写法相当于先对p解引用再传入参数,不符合函数指针调用规则,该选项错误。
答案C