当前位置: 首页 > article >正文

指针之旅:从基础到进阶的全面讲解

大家好,这里是小编的博客频道
小编的博客:就爱学编程

很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!!

本文目录

  • 引言
  • 正文
    • (1)内置数据类型指针
      • (2) 自定义类型指针
        • 1.数组指针与指针数组
        • 2. 结构体指针
        • 3.联合体指针
          • (1)联合体指针的定义
          • (2)联合体指针的使用
          • (3)联合体指针的注意事项
      • (3)函数指针
        • 1.函数指针的定义
        • 2.函数指针的赋值
        • 3.函数指针的使用
        • 4.函数指针作为参数
        • 5.函数指针作为返回值
        • 6.函数指针的注意事项
      • (4)空指针(`NULL`)
          • (1)空指针的使用
          • (2)空指针与空字符
          • (3)空指针的注意事项
      • (5)二级指针
        • (1)二级指针的定义
        • (2)二级指针的内存分配
        • (3)二级指针的使用
        • (4)二级指针作为函数参数
        • (5)二级指针的注意事项
      • (6)常量指针与指向常量的指针(const 的用法)
        • 1.指向常量的指针(Pointer to a Constant)
        • 2.常量指针(Constant Pointer)
        • 3.常量指针指向常量(Constant Pointer to a Constant)
        • 4.使用场景和注意事项
  • 题集
    • (1)指针笔试题1
    • (2)指针笔试题2
    • (3)指针笔试题3
    • (4)指针笔试题4
    • (5)指针笔试题5
    • (6)指针笔试题6
    • (7)指针笔试题7
    • (8)指针笔试题8
    • (9)指针笔试题9
  • 快乐的时光总是短暂,咱们下篇博文再见啦!!!不要忘了,给小编点点赞和收藏支持一下,在此非常感谢!!!

引言

指针作为C语言语法学习中的一块既极重要又极难懂的知识点,让初学C语言的我们常常苦不堪言。而本文就是为了让像曾经的作者一样的宝子们深刻理解指针这一章节的内容而作,那接下来就跟随作者的视角,一起把各种类型的指针理解透彻!

在这里插入图片描述


那接下来就让我们开始遨游在知识的海洋!

正文

指针的类型繁复,为了避免出现知识点的遗漏,这里小编采根据指向的内容进行分类,把指针分为了以下6种。让我们一一来学习吧!!


内置类型数据是我们最为常用的数据类型,所以指针的学习我们就从它开始。

(1)内置数据类型指针

顾名思义,这些指针指向内置类型的变量,如整型指针,字符指针等。

详见:

类型内涵及应用
整型指针int*指向整数的指针
浮点型指针float*double*指向浮点数的指针
字符(型)指针char*指向字符的指针,常用于字符串处理
布尔型指针bool*指向布尔值的指针

其次就是自定义类型指针,一起来看看吧!!

(2) 自定义类型指针

自定义类型数据类型有:数组,结构体,枚举,联合体。但是枚举没有对应的枚举指针变量。


这里小编先从数组与指针开始讲起。

1.数组指针与指针数组

数组指针和指针数组是C编程中两个重要的概念,它们虽然名字相似,但含义和用法却有很大的不同。我们先来看看这两个的定义:

  • 数组指针指的是指向数组的指针。它是一个指针,指向一个数组类型的数据。声明一个数组指针时,需要指定数组的元素类型和大小。例如,int (*arrayPtr)[10]表示一个指向包含10个整数的数组的指针。使用数组指针时,可以通过(*arrayPtr)[index]来访问数组中的元素。
  • 指针数组则是指存储指针的数组。它是一个数组,其中的每个元素都是指针。声明指针数组时,需要指定数组的大小和指针指向的类型。例如,int *pointerArray[10]表示一个包含10个指向整数的指针的数组。访问指针数组中的元素,可以直接使用pointerArray[index],然后通过解引用来访问指针指向的数据。

主要区别在于它们的使用场景和内存布局

数组指针通常用于函数参数,以传递多维数组,而指针数组则常用于创建动态的数据结构,如链表和树。

在内存中,数组指针指向的是连续的内存块,而指针数组中的每个指针可以指向任意位置的内存。因此,数组指针在内存访问上更为高效,而指针数组则在数据组织上更为灵活。

  • 总之,理解数组指针和指针数组的区别对于编写高效的C语言程序至关重要。掌握它们各自的特性和适用场景,可以帮助我们更好地设计和实现程序。

2. 结构体指针

定义:结构体指针允许我们通过指针来访问和操作结构体中的数据。结构体是一种复合数据类型,它允许我们将多个不同类型的数据项组合成一个单一的类型。结构体指针则是指向这种复合数据类型的指针。

在C中,结构体指针的声明方式是在结构体类型的前面加上一个星号()。例如,如果有一个名为Person的结构体,声明一个指向该结构体的指针可以写作Person *ptr;。这个指针可以用来指向一个Person类型的结构体实例。

我们以一个代码为例:

#include<stdio.h>
struct book {
	char name[20];
	char author[20];
	int prince;
};
void print(struct book* p) {
	printf("%s %s %d\n", (*p).name, (*p).author, (*p).prince);
	printf("%s %s %d\n", p->name, p->author, p->prince);    //“->”操作符可用在:结构体指针指向我们想要访问的结构体中的元素;
}

int main() {
	struct book b1 = {"C语言", "张三", 10};
	printf("%s %s %d\n", b1.name, b1.author, b1.prince);     //“.”操作符可用在:找到我们想要访问的结构体的元素。
	print(&b1);
	return 0;
}

这里的struct book* p就是一个结构体指针变量

主要优点:是它们提供了一种通过内存地址间接访问和修改结构体成员的方法。这在处理大型数据结构时尤其有用,因为它们可以减少内存复制,提高程序的效率。

使用结构体指针时,我们可以通过箭头操作符(->)来访问结构体的成员。例如,如果Person结构体有一个名为name的成员,我们可以通过ptr->name来访问或修改它。

结构体指针也常用于动态内存分配。使用new关键字可以动态创建结构体实例,并返回指向该实例的指针。例如,Person *ptr = new Person;会创建一个新的Person结构体,并使ptr指向它。当不再需要这个结构体时,应该使用delete来释放内存,避免内存泄漏。

结构体指针还可以作为函数参数,允许函数直接修改传入的结构体实例。这在设计模块化和可重用代码时非常有用,因为它允许函数与调用者共享数据,而无需复制整个结构体。

  • 总之,结构体指针提供了一种高效、灵活的方式来访问和操作结构体数据,是编写高效、模块化代码的关键。理解结构体指针的工作原理和正确使用它们,对于任何C/C++程序员来说都是一项基本技能。

3.联合体指针

联合体(Union)指针是指向联合体类型的指针。联合体是一种特殊的数据结构,允许在相同的内存位置存储不同的数据类型。这使得联合体成为一种节省空间的数据类型,因为联合体的大小等于其最大成员的大小,而不是所有成员的总和。

(1)联合体指针的定义

在C/C++中,联合体指针的声明方式是在联合体类型的前面加上一个星号()。例如,如果有一个名为Data的联合体,声明一个指向该联合体的指针可以写作Data *ptr;。这个指针可以用来指向一个Data类型的联合体实例。

(2)联合体指针的使用

使用联合体指针时,我们可以通过箭头操作符(->)来访问联合体的成员。例如,如果Data联合体有一个名为num的成员,我们可以通过ptr->num来访问或修改它。

联合体指针的使用特点:

  • 内存共享:联合体指针允许我们通过指针访问联合体成员,这些成员共享相同的内存位置。这意味着对一个成员的修改会影响其他成员。
  • 类型安全:尽管联合体可以存储不同类型的数据,但使用联合体指针时,编译器会根据联合体的声明来检查成员访问的类型安全。
  • 动态内存分配:与结构体类似,联合体指针也常用于动态内存分配。使用new关键字可以动态创建联合体实例,并返回指向该实例的指针。
  • 灵活性:联合体指针提供了一种灵活的方式来处理不同类型的数据,尤其是在需要节省空间或者需要通过同一个内存位置存储不同类型数据的场景中。
(3)联合体指针的注意事项
  • 初始化:在使用联合体指针之前,应该确保指针已经被正确初始化,指向一个有效的联合体实例。
  • 内存释放:如果使用new动态分配了联合体实例,那么在不再需要时,应该使用delete来释放内存,避免内存泄漏。
  • 成员访问:在使用联合体指针访问成员时,必须确保访问的是当前存储在联合体中的成员类型,否则可能会导致未定义行为。
  • 联合体指针提供了一种节省空间且灵活的方式来处理不同类型的数据。

(3)函数指针

函数指针是C语言中一个强大的特性,它允许将函数的地址赋给一个变量,使得可以通过这个变量来调用函数。这种机制提供了一种灵活的方式来处理函数,使得函数可以像数据一样被传递和操作。

1.函数指针的定义

函数指针的声明需要指定函数的返回类型、指针类型(即*),以及函数的参数列表。例如,如果有一个返回int类型、接受两个int参数的函数add,那么对应的函数指针声明如下:int (*funcPtr)(int, int);

这里,funcPtr是一个指向函数的指针,它可以存储add函数的地址。

2.函数指针的赋值

要将函数的地址赋给函数指针,可以直接使用函数名。

这是因为:在C语言中,函数名本身就是一个指向函数的指针,因此可以直接赋值给函数指针。

3.函数指针的使用

使用函数指针调用函数时,需要使用括号来包围函数指针和参数。

例如,int result = funcPtr(5, 3); // 调用add函数

4.函数指针作为参数

函数指针常用作其他函数的参数,这使得函数可以接收另一个函数作为输入,从而提供高度的灵活性。例如,可以定义一个接受函数指针作为参数的函数:

void applyFunction(int x, int y, int (*func)(int, int)) {
    int result = func(x, y);
    // 处理结果
}

在这个例子中,applyFunction接受两个int参数和一个函数指针参数,然后调用这个函数指针指向的函数。

5.函数指针作为返回值

函数指针也可以作为函数的返回值,这允许函数返回一个函数。这种技术可以用来实现回调函数和策略模式。

小编已经在之前的函数篇提及。

6.函数指针的注意事项
  • 类型匹配:函数指针必须指向与声明匹配的函数类型,否则会导致编译错误或运行时错误。
  • 空函数指针:函数指针可以被初始化为NULL,表示它不指向任何函数。
  • 内存管理:如果函数指针用于动态分配的函数对象,需要确保正确管理内存,避免内存泄漏。

在C语言中,空指针(Null Pointer)是一个特殊的指针值,它不指向任何有效的对象或函数。空指针的主要作用是表示“没有指向任何东西”或“没有有效的地址”。在C语言中,空指针常被用来表示一个指针变量尚未被分配具体的内存地址,或者用来表示某个指针变量不再指向任何对象。

(4)空指针(NULL

定义:在C语言中,空指针被定义为NULL,它是一个宏,在标准库<stddef.h>中定义。NULL的具体值是0,这意味着在大多数平台上,空指针和数值0是等价的。然而,NULL的使用更加明确,因为它专门用来表示空指针,而0可能在其他上下文中有其他含义。

(1)空指针的使用

空指针通常用于以下几种情况:

  • 初始化指针:在声明指针变量时,如果没有立即分配内存,可以将指针初始化为NULL,以表明该指针当前不指向任何对象。

函数返回值:当一个函数需要返回一个指针,但没有有效的对象可以返回时,可以返回NULL

  • 检查指针有效性:在使用指针之前,检查它是否为NULL是一个好习惯,这可以防止解引用空指针导致的程序崩溃。
(2)空指针与空字符

需要注意的是:空指针(NULL)和空字符('\0')是两个完全不同的概念。空指针是一个指针值,表示没有指向任何对象,而空字符是一个字符值,通常用来表示字符串的结束。

(3)空指针的注意事项
  • 空指针赋值:不要将NULL赋值给非指针类型的变量,这会导致编译错误。
  • 平台依赖性:虽然在大多数平台上NULL被定义为0,但在某些系统上,NULL可能有不同的定义。因此,最好始终使用NULL而不是直接使用0
  • 空指针与空数组:不要将空指针与空数组混淆。空数组是指一个长度为零的数组,而空指针是一个不指向任何对象的指针。

结论:

  • 空指针可以帮助程序员处理指针变量的未初始化状态和错误情况。

(5)二级指针

在C语言中,二级指针(Double Pointer)是指指向指针的指针它是一个指针变量,存储的值是另一个指针变量的地址。二级指针在处理动态内存分配、多维数组、函数参数传递等方面非常有用。理解二级指针对于深入掌握C语言的指针操作至关重要。

(1)二级指针的定义

二级指针的声明涉及到两个星号()。例如,int **ptr;声明了一个指向int类型指针的指针。这里,ptr是一个二级指针,它可以存储一个int*类型的地址。

(2)二级指针的内存分配

二级指针常用于动态分配多维数组。例如,创建一个二维数组可以通过分配一个指针数组(一级指针),然后为每个指针分配一个一维数组(二级指针)。

(3)二级指针的使用

使用二级指针时,可以通过连续的解引用来访问最终指向的数据。例如,*arr会得到一个int*类型的指针,而**arr会得到一个int类型的值。

(4)二级指针作为函数参数

二级指针也常用于函数参数,特别是需要修改指针指向的值或者需要传递多维数组时。

(5)二级指针的注意事项
  • 初始化:在使用二级指针之前,应该确保它们已经被正确初始化,指向有效的内存地址。
  • 内存释放:如果使用malloccalloc分配了内存,应该在不再需要时使用free来释放内存,避免内存泄漏。
  • 解引用:在使用二级指针时,必须确保已经正确解引用,否则可能会导致访问无效内存,引起程序崩溃。

在C语言中,常量指针和指向常量的指针是两个不同的概念,它们在声明和使用上有所区别,但都与指针和常量的关系有关。

(6)常量指针与指向常量的指针(const 的用法)

1.指向常量的指针(Pointer to a Constant)

指向常量的指针是指指针本身可以被修改,但其指向的数据(常量)不能被修改。这种指针的声明方式是在指针的声明中,将const关键字放在指针的后面,紧挨着指针的类型前面。例如:const int *ptr;

这里,ptr是一个指向int类型的常量的指针。这意味着你可以通过ptr来改变它所指向的地址,但是不能通过ptr来改变所指向地址处的值。

2.常量指针(Constant Pointer)

常量指针是指指针本身的值不能被修改,即一旦指针被初始化后,就不能指向另一个地址。这种指针的声明方式是将const关键字放在指针的声明中,紧挨着指针变量的前面。例如:int value = 10; int *const ptr = &value;

这里,ptr是一个常量指针,它被初始化为指向value的地址,之后你不能再让ptr指向另一个地址,但可以通过ptr来修改value的值。

3.常量指针指向常量(Constant Pointer to a Constant)

这种指针既不能改变指向的地址,也不能通过这个指针来改变指向地址处的值。声明时,const关键字同时放在指针类型和指针变量之间,以及指针类型和指针指向的类型之间。例如:const int *const ptr = &value;

在这个例子中,ptr是一个常量指针,指向一个int类型的常量。这意味着ptr的值(即它所指向的地址)不能被改变,同时ptr所指向的数据也不能被修改。

4.使用场景和注意事项
  • 指向常量的指针:当你需要一个指针来读取但不能修改某些数据时使用,例如,函数参数中传递的只读数据。
  • 常量指针:当你需要一个指针的地址固定不变时使用,例如,指向全局变量或静态变量的指针。
  • 常量指针指向常量:当你需要一个既不能改变指向地址,也不能通过指针改变数据的指针时使用,例如,防止函数内部修改传入的参数。
  • 常量指针与指向常量的指针提供了不同的保护级别,帮助程序员控制数据的访问和修改。

题集

(1)指针笔试题1

判断代码运行结果

#include<stdio.h>
#include<string.h>
int main() {
	char  arr[] = "abcd";
	//char arr[] = {'a', 'b', 'c', 'd', '\0'}
	printf("%d\n", sizeof(arr)); 
	printf("%d\n", sizeof(arr + 0 ));  
	printf("%d\n", sizeof (*arr));   
	printf("%d\n", sizeof(arr[1]));   
	printf("%d\n", sizeof(&arr));    
	printf("%d\n", sizeof(&arr + 1));   
	printf("%d\n", sizeof(&arr[0] + 1));   

	printf("%d\n", strlen(arr));   
	printf("%d\n", strlen(arr + 0));    
	printf("%d", strlen (*arr));     
    printf("%d", strlen(arr[1]));     
	printf("%d\n", strlen (&arr));     
	printf("%d\n", strlen (&arr + 1));    
	printf("%d\n", strlen (&arr[0] + 1));    
	
	return 0;
}

建议思考写下自己的答案再看后文进行核对与理解

答案解析
5sizeof()内仅有arr(数组名), arr代表整个数组,所以sizeof计算的是整个数组的大小——5(单位:字节)
4/8除了仅有arr&arrarr代表的是整个数组,其他arr代表的都是数组首元素的地址,地址的大小就是4/8(至于4/8取决于32位机器或64位机器)
1*arr就是数组首元素'a'(int),大小为1
1arr[1] == *(arr + 1),就是数组的第二个元素,大小为1
4/8&arr中的arr代表的是整个数组,所以&arr代表的是整个数组的地址,大小就是4/8
4/8&arr + 1中的arr代表的是整个数组,所以&arr + 1代表的是整个数组后的和数组一样大小的连续元素的地址,大小就是4/8
4/8第二个元素的地址
4strlen(arr)arr代表的是数组首元素的地址,所以strlen()从数组首元素开始数到至’\0’,结果就为:4
4strlen(arr+0)arr代表的是数组首元素的地址,所以strlen()从数组首元素开始数至’\0’,结果就为:4
非法访问*arr表示数组首元素,而strlen()要的是地址,所以非法访问
非法访问同上
4&arr取出了整个数组的地址,就数整个数组,结果就为:4
随机&arr + 1 就跳过了整个数组,不知道什么时候出现’\0’,也不知道任何元素的信息,所以打印的是个随机数
3&arr[0] + 1 就跳过了首元素,结果就为:3

考察:一维数组的数组名——特殊与一般
涉及:一维数组,strlen(),sizeof()


(2)指针笔试题2

判断代码运行结果

#include<stdio.h>
#include<string.h>
int main() {
	char* p = "abcd";
	//这个代码的意思:把首元素的地址(a的地址)放到指针变量p中
	//p就相当于一般的arr(除了两种特殊情况除外)(首元素的地址)
	printf("%d\n", sizeof(p)); //地址的大小就是4/8(字节)(至于4/8取决于32位机器或64位机器)
	printf("%d\n", sizeof(p + 0 ));   //同上
	printf("%d\n", sizeof (*p));   //*p就是数组首元素'a'(char),大小为1(字节)
	printf("%d\n", sizeof(p[1]));   //p[1] == *(p + 1),就是数组的第二个元素,大小为1(字节)
	printf("%d\n", sizeof(&p));    //&p中的p代表的是数组首元素的地址,所以&p代表的是存储指针变量p的地址,大小就是4/8
	printf("%d\n", sizeof(&p + 1));   //&p + 1中的p代表的是数组首元素,所以&p + 1代表的是存储指针变量p的地址处后一位的地址,大小就是4/8
	printf("%d\n", sizeof(&p[0] + 1));   //b的地址,大小就是4/8

	printf("%d\n", strlen(p));   //strlen(p)中p代表的是数组首元素的地址,所以strlen函数从数组首元素开始数至'\0',结果就为:4
	printf("%d\n", strlen(p + 0));    //strlen(p + 0)中p代表的是数组首元素的地址,所以strlen函数从数组首元素开始数至'\0',结果就为:4
	//printf("%d", strlen (*p));     //*p表示数组首元素,而strlen函数要的是地址,所以非法访问
	//printf("%d", strlen(p[1]));     //同上
	printf("%d\n", strlen (&p));     //&p取出了数组首元素的地址的地址,不知道什么时候出现'\0',也不知道任何元素的信息,所以打印的是个随机数
	printf("%d\n", strlen (&p + 1));    //&p + 1 就跳过了整个数组,不知道什么时候出现'\0',也不知道任何元素的信息,所以打印的是个随机数
	printf("%d\n", strlen (&p[0] + 1));    //&p[0] + 1 就跳过了首元素,结果就为:3

	return 0;
}

考察:字符指针——把首元素的地址(a的地址)放到指针变量p中
涉及:字符指针,strlen()


(3)指针笔试题3

判断代码运行结果


#include<stdio.h>
struct test {
	int Num;
	char* pcname;
	short sDate;
	char cha[2];
	short sBa[4];
}* p;
//假设 * p = 0x00000000;
//已知结构体变量test的大小为20字节;
int main() {
	printf("%p\n", p + 1);
	//指针加1跳过整个指针权限的内容(步长)(解引用权限),所以这里跳过了整个结构体变量,也就是20个字节,结果用十六进制表示就是00000014
	printf("%u\n", (unsigned long)p + 1);
	//先把结构体指针变量p强转成无符号长整型变量,再加1就是让一个整型变量加1,结果就是加1,用十进制表示就是1
	printf("%p\n", (unsigned int*)p + 1);
	//先把结构体指针变量p强转成整型指针变量,再加1就是让一个整型指针变量加1,指针加1跳过整个权限的内容,所以这里跳过了整个整型变量,也就是4个字节,结果用十六进制表示就是00000004
}

考察:指针加1的意义,是跳过一个步长的地址
涉及:结构体指针,基本指针


(4)指针笔试题4

判断代码运行结果

//x86,小端
#include<stdio.h>
   int main() {
	int a[4] = { 1, 2, 3, 4 };
	int* p = (int*)(&a + 1);
	int* p1 = (int*)((int)(a + 1) + 1);   //如果我们定义一个:int* pa = a;   则a + 1就等价于*(pa + 1)
	printf("%x\n", p[-1]);  
	//1.p[-1] == *(p - 1),因为p的类型为int*,所以p-1就是向前挪动一个整型(4个字节)的长度,指向了第4个元素的第一个字节的最左端
	//2.再进行解引用,根据p的访问权限可知从当前位置向后访问一个整型(4个字节),就得到了数组的第4个元素——4
	//3.又因为%x是用来打印十六位进制数且会去掉前面的0,所以打印就是4
	printf("%x\n", * p1); 
	//1.a + 1: 
	             //低地址                                       高地址//低地址                                       高地址                                                         
	             //01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00    //01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
	   //地址设为:00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f    //00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f    
	   //指针位置:pa                                              -->              pa    
	//2.(int)(a + 1): 把 地址04 强转成一个大小等于 04 的整型数据
	//3.(int)(a + 1) + 1:一个整型数据加1,就是数值加1,此时((int)(a + 1) + 1)就得到了一个数值大小为 05 的整型数据
	//4.((int*)((int)(a + 1) + 1)):把数值大小为 05 的整型数据强转为 地址05
	//5.int* p1 = (int*)((int)(a + 1) + 1):把上面得到的 05的地址 赋给 类型为int*的 整形指针变量p1
	//6.printf("%x\n", * p1):再进行解引用,根据p的访问权限可知从当前位置向后访问一个整型(4个字节),就得到了00 00 00 03(小端存储),转换为03 00 00  00(原值)(小端存储是以字节为单位的)
	//7.又因为%x是用来打印十六位进制数且会去掉前面的0,所以打印就是3000000
	return 0;
}

考察并涉及:小端存储,%x的作用,指针运算


(5)指针笔试题5

判断代码运行结果

#include<stdio.h>
int main() {
	int arr[] = { 1, 2, 3, 4 };
	int* p = (int*)(&arr + 1);
	printf("%d\n", *(p - 1));
	//没什么好讲的
	printf("%d\n", *(arr + 1));
	//同上
	return 0;
}

考察并涉及:一维数组数组名


(6)指针笔试题6

判断代码运行结果

#include<stdio.h>
int main() {
	int arr[2][3] = { (1, 2), (3, 4), (5, 6)};
	//逗号表达式的值就是','右边的表达式的结果(但要注意:','左边的表达式也会执行,且是先执行的)
	//所以该二维数组的元素为://2 4
	                          //6 0
	                          //0 0
	int* p;
	p = arr[0];
	printf("%d\n", p[1]);
	//两种理解p[1]的方法
	//1.用数组的格式理解:p = arr[0],则p[1] = arr[0][1] = 4;
	//2.用数组的本质理解:arr[0]作为二维数组的第一行的数组元素的数组名,代表的是数组第一行的首元素的地址,即——&arr[0][0],
	                    //p[1] == *(p + 1) == *(&arr[0][0] + 1) == 4
	return 0;
}

考查并涉及:指针与二维数组的关系和逗号表达式的作用


(7)指针笔试题7

判断代码运行结果

#include<stdio.h>
int main() {
	int arr[5][5];
	//数组元素:|0 0 0 0 0 | 0 0 0 0 0 | 0 0 0 0 0 | 0 0 0 0 0 | 0 0 0 0 0 | 
	//             arr[0]      arr[1]     arr[2]      arr[3]      arr[4]
	//地址设为:|0 1 2 3 4 | 5 6 7 8 9 | a b c d e | f 0 1 2 3 | 4 5 6 7 8 |
	int(*p)[4] = (int(*)[4])arr;
	//arr作为二维数组的数组名,类型为:int(*)[5],与数组指针变量p(类型为:int(*)[4])类型基本一致,所以强转之后可以和数组名一样理解
	printf("%p,%d\n", &p[4][2] - &arr[4][2], &p[4][2] - &arr[4][2]);
	//以p为二维数组的数组名的二维数组:
	//    元素:| 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 
	//             p[0]      p[1]      p[2]      p[3]       p[4]     p[5]     p[6]
	//地址设为:| 0 1 2 3 | 4 5 6 7 | 8 9 a b | c d e f | 0 1 2 3 | 4 5 6 7 | 8
	//所以 &p[4][2] - &arr[4][2] == 12 - 16 == -4,再根据占位符的功能打印的结果为:FFFFFFFC(十六进制),-4(十进制)
	return 0;
}

考察:%p打印的是无符号十六进制的地址值
涉及:二维数组,占位符%p,数组指针。


(8)指针笔试题8

判断代码运行结果

#include<stdio.h>
int main() {
	char* arr[3] = { "hello", "world", "bite" };
	//arr[0] == &'h' , arr[1] == &'w', arr[2] == &'b'
	//即数组元素:&'h'| &'w' | &'b'
	//元素名:  arr[0]|arr[1]|arr[2]

	char** pa = arr;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

考查:字符指针在接收字符串时,接受的是字符串的首元素的地址
涉及:指针数组,占位符%s。


(9)指针笔试题9

判断代码运行结果

#include<stdio.h>
int main(){
	char* c[4] = { "hello", "world", "bite", "pengge" };
	//一级字符指针数组c:
	//       数组元素值:| &'h'| &'w'| &'b'| &'p'|
	//         元素类型:|char*|char*|char*|char*|
	//         元素意义:|指向"hello"的指针(一开始指向'h')|指向"world"的指针(一开始指向'w')|指向"bite"的指针(一开始指向'b')|指向"pengge"的指针(一开始指向'p')
	//           元素名:| c[0]| c[1]| c[2]| c[3]|
	//         地址设为:   0      1    2     3
	char** cp[4] = {c + 3, c + 2, c + 1, c};
	//二级字符指针数组cp:
	//         数组元素:|  3  |  2  |  1  |  0  |
	//           元素名:|cp[0]|cp[1]|cp[2]|cp[3]|
	//         地址设为:   a     b    c     d
	char*** cpp = cp;
	//    *cpp == &cp[0] == a
	printf("%s\n", **++cpp);
	//都只能先从cpp开始分析:
	//1.++cpp:cpp先自增1——*cpp = a + 1 == b;
	//2.**(++cpp):两次解引用——(1)*(++cpp) == b --> (2)**(++cpp) == 2;
	//3.%s的作用:从所给地址开始,一直打印字符至'\0'处,所以打印的结果为:bite
	printf("%s\n", *-- *++cpp + 3);
	//1.++cpp:cpp再自增1——*cpp = b + 1 == c;
	//2.*++cpp:解引用——*cpp == c;
	//3.-- *++cpp:c自减1——*c = 1 - 1 ==0;
	//4.*-- *++cpp:解引用——*c == 0;
	//5.*-- *++cpp + 3:一级字符指针 + 3——*0 = 'h' + 3 == 'l'(临时,不是真的加);
	//6.打印结果为:lo
	printf("%s\n", *cpp[-2] + 3);
	//1.cpp[-2]:cpp指向的值先减2再解引用——(1)cpp - 2——*cpp = c - 2 == a(临时,不是真的加);(2)*(cpp - 2)——*(cpp - 2) = a;
	//2.*cpp[-2]:再解引用——**(cpp - 2) == *a == 3;
	//3.*cpp[-2] + 3:一级字符指针 + 3——*3 = 'p' + 3 =='g'(临时,不是真的加);
	//4.打印结果为:gge
	printf("%s\n", cpp[-1][-1] + 1);
	//1.cpp[-1]:cpp指向的值先减1再解引用——(1)cpp - 1——*cpp = c - 1 == b(临时,不是真的加);(2)*(cpp - 2)——*(cpp - 2) = b;
	//2.cpp[-1][-1]:b指向的值先减1再解引用——(1)b - 1——*b = 2 - 1 == 1(临时,不是真的加);(2)*1——*1 = 'w';
	//3.cpp[-1][-1] + 1:一级字符指针 + 1——*1 = 'w' + 1 == 'o'(临时,不是真的加);
	//4.打印为:orld
	return 0;
}

考察:操作符的优先级只在操作数的相邻位置才考虑;++ 和-- 会真实改变变量存的数据
涉及:二级指针,操作符++和–,解引用。



快乐的时光总是短暂,咱们下篇博文再见啦!!!不要忘了,给小编点点赞和收藏支持一下,在此非常感谢!!!


http://www.kler.cn/a/512323.html

相关文章:

  • AI发展困境:技术路径与实践约束的博弈
  • Agent AI: 强化学习,模仿学习,大型语言模型和VLMs在智能体中的应用
  • VLAN基础理论
  • Java开发提速秘籍:巧用Apache Commons Lang工具库
  • OpenMP并行编程实例系列2 —— 并行结构
  • Python制作简易PDF查看工具PDFViewerV1.0
  • 新手上路:Anaconda虚拟环境创建和配置以使用PyTorch和DGL
  • Vmware无法打开虚拟机(网络资料)
  • Xcode :给模拟器 创建桌面 快捷方式
  • 使用Java爬虫获取微店商品详情实践指南
  • SparkSQL数据源与数据存储
  • kafka学习笔记4-TLS加密 —— 筑梦之路
  • 探秘 Linux 进程状态:解锁系统运行的密码
  • 算法6(力扣148)-排序链表
  • lvm快照备份
  • 【优选算法】----移动零
  • Grafana 统一可视化了,告警如何统一?
  • 【银河麒麟高级服务器操作系统】业务访问慢网卡丢包现象分析及处理过程
  • Python----Python高级(文件操作open,os模块对于文件操作)
  • LLM大语言模型的分类
  • 通信协议—WebSocket
  • 电子应用设计方案97:智能AI投影仪系统设计
  • uniapp时间组件
  • 回归算法、聚类算法、决策树、随机森林、神经网络
  • Kafka面试题----Kafka都有哪些特点
  • Lightning初探