指针之旅:从基础到进阶的全面讲解
大家好,这里是小编的博客频道
小编的博客:就爱学编程
很高兴在
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)二级指针的注意事项
- 初始化:在使用二级指针之前,应该确保它们已经被正确初始化,指向有效的内存地址。
- 内存释放:如果使用
malloc
或calloc
分配了内存,应该在不再需要时使用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;
}
建议思考写下自己的答案再看后文进行核对与理解
答案 | 解析 |
---|---|
5 | sizeof() 内仅有arr (数组名), arr 代表整个数组,所以sizeof计算的是整个数组的大小——5(单位:字节) |
4/8 | 除了仅有arr或&arr 中arr 代表的是整个数组,其他arr 代表的都是数组首元素的地址,地址的大小就是4/8(至于4/8取决于32位机器或64位机器) |
1 | *arr 就是数组首元素'a' (int),大小为1 |
1 | arr[1] == *(arr + 1) ,就是数组的第二个元素,大小为1 |
4/8 | &arr 中的arr 代表的是整个数组,所以&arr 代表的是整个数组的地址,大小就是4/8 |
4/8 | &arr + 1 中的arr 代表的是整个数组,所以&arr + 1 代表的是整个数组后的和数组一样大小的连续元素的地址,大小就是4/8 |
4/8 | 第二个元素的地址 |
4 | strlen(arr) 中arr 代表的是数组首元素的地址,所以strlen() 从数组首元素开始数到至’\0’,结果就为:4 |
4 | strlen(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/8printf("%d\n", sizeof(&p + 1)); //&p + 1中的p代表的是数组首元素,所以&p + 1代表的是存储指针变量p的地址处后一位的地址,大小就是4/8printf("%d\n", sizeof(&p[0] + 1)); //b的地址,大小就是4/8printf("%d\n", strlen(p)); //strlen(p)中p代表的是数组首元素的地址,所以strlen函数从数组首元素开始数至'\0',结果就为:4printf("%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 就跳过了首元素,结果就为:3return 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个字节,结果用十六进制表示就是00000014printf("%u\n", (unsigned long)p + 1);//先把结构体指针变量p强转成无符号长整型变量,再加1就是让一个整型变量加1,结果就是加1,用十进制表示就是1printf("%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,所以打印就是4printf("%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,所以打印就是3000000return 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 0int* 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) == 4return 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 3char** 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 dchar*** cpp = cp;// *cpp == &cp[0] == aprintf("%s\n", **++cpp);//都只能先从cpp开始分析://1.++cpp:cpp先自增1——*cpp = a + 1 == b;//2.**(++cpp):两次解引用——(1)*(++cpp) == b --> (2)**(++cpp) == 2;//3.%s的作用:从所给地址开始,一直打印字符至'\0'处,所以打印的结果为:biteprintf("%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.打印结果为:loprintf("%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.打印结果为:ggeprintf("%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.打印为:orldreturn 0;
}
考察:操作符的优先级只在操作数的相邻位置才考虑;++ 和-- 会真实改变变量存的数据
涉及:二级指针,操作符++和–,解引用。
快乐的时光总是短暂,咱们下篇博文再见啦!!!不要忘了,给小编点点赞和收藏支持一下,在此非常感谢!!!
相关文章:

指针之旅:从基础到进阶的全面讲解
大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 本文目录 引言正文(1)内置数…...

FPGA与ASIC:深度解析与职业选择
IC(集成电路)行业涵盖广泛,涉及数字、模拟等不同研究方向,以及设计、制造、封测等不同产业环节。其中,FPGA(现场可编程门阵列)和ASIC(专用集成电路)是两种重要的芯片类型…...
PostgreSQL 中进行数据导入和导出
在数据库管理中,数据的导入和导出是非常常见的操作。特别是在 PostgreSQL 中,提供了多种工具和方法来实现数据的有效管理。无论是备份数据,还是将数据迁移到其他数据库,或是进行数据分析,掌握数据导入和导出的技巧都是…...
SDL2基本的绘制流程与步骤
SDL2(Simple DirectMedia Layer 2)是一个跨平台的多媒体库,它为游戏开发和图形应用提供了一个简单的接口,允许程序直接访问音频、键盘、鼠标、硬件加速的渲染等功能。在 SDL2 中,屏幕绘制的流程通常涉及到窗口的创建、渲染目标的设置、图像的绘制、事件的处理等几个步骤。…...
面试-业务逻辑2
应用 给定2个数组a、b,若a[i] b[j],则记(i,j)为一个二元数组,求具体的二元数组及其个数。 实现 a input("请输入数组a的元素个数:") # print(a) a_list list(map(int, input("请输入数组a的元素,…...

HTML之拜年/跨年APP(改进版)
目录: 一:目录 二:效果 三:页面分析/开发逻辑 1.页面详细分析: 2.开发逻辑: 四:完整代码(不多废话) index.html部分 app.json部分 二:效果 三:页面…...
嵌入式硬件篇---ADC模拟-数字转换
文章目录 前言第一部分:STM32 ADC的主要特点1.分辨率2.多通道3.转换模式4.转换速度5.触发源6.数据对齐7.温度传感器和Vrefint通道 第二部分:STM32 ADC的工作流程:1.配置ADC2.启动ADC转换 第三部分:ADC转化1.抽样2.量化3.编码 第四…...

每打开一个chrome页面都会【自动打开F12开发者模式】,原因是 使用HBuilderX会影响谷歌浏览器的浏览模式
打开 HBuilderX,点击 运行 -> 运行到浏览器 -> 设置web服务器 -> 添加chrome浏览器安装路径 chrome谷歌浏览器插件 B站视频下载助手插件: 参考地址:Chrome插件 - B站下载助手(轻松下载bilibili哔哩哔哩视频)…...
Access数据库教案(Excel+VBA+Access数据库SQL Server编程)
文章目录: 一:Access基础知识 1.前言 1.1 基本流程 1.2 基本概念?? 2.使用步骤方法 2.1 表【设计】 2.1.1 表的理论基础 2.1.2 Access建库建表? 2.1.3 表的基本操作 2.2 SQL语句代码【设计】 2.3 窗体【交互】? 2.3.1 多方式创建窗体 2.3.2 窗体常用的控件 …...
09、PT工具用法
目录 1、PT工具原理 2、在线修改表结构 3、使用pt-query-diges分析慢查询 4、使用pt-kill来kill掉一些垃圾SQL 5、pt-table-checksum进行主从一致性排查和修复 6、pt-archiver进行数据归档 7、其他一些pt工具 1、PT工具原理 创建一张与原始表结构相同的临时表 然后对临时…...
华为OD机试E卷 --矩形相交的面积--24年OD统一考试(Java JS Python C C++)
文章目录 题目描述输入描述输出描述用例题目解析JS算法源码Java算法源码python算法源码题目描述 给出3组点坐标(x, y, w, h),-1000<x,y<1000,w,h为正整数。 (x,y, w, h)表示平面直角坐标系中的一个矩形:x, y为矩形左上角坐标点,w, h向右w,向下h。(X, y, w, h)表示x轴…...
C++ 内存分配和管理(八股总结)
C是如何做内存管理的(有哪些内存区域)? (1)堆,使用malloc、free动态分配和释放空间,能分配较大的内存; (2)栈,为函数的局部变量分配内存,能分配…...
如何使用 JSONP 实现跨域请求?
以下是使用 JSONP 实现跨域请求的步骤: 实现步骤: 1. 客户端设置 在客户端,你需要创建一个 <script> 标签,并将其 src 属性设置为跨域请求的 URL,并添加一个 callback 参数。这个 callback 参数将包含一个函数…...

【机器学习实战入门】基于深度学习的乳腺癌分类
什么是深度学习? 作为对机器学习的一种深入方法,深度学习受到了人类大脑和其生物神经网络的启发。它包括深层神经网络、递归神经网络、卷积神经网络和深度信念网络等架构,这些架构由多层组成,数据必须通过这些层才能最终产生输出。…...

Flowable 管理各业务流程:流程设计器 (获取流程模型 XML)、流程部署、启动流程、流程审批、流程挂起和激活、任务分配
文章目录 引言I 表结构主要表前缀及其用途核心表II 流程设计器(Flowable BPMN模型编辑器插件)Flowable-UIvue插件III 流程部署部署步骤例子:根据流程模型ID部署IV 启动流程启动步骤ACT_RE_PROCDEF:流程定义相关信息例子:根据流程 ID 启动流程V 流程审批审批步骤Flowable 审…...

Kafka 日志存储 — 日志索引
每个日志分段文件对应两个索引文件:偏移量索引文件用来建立消息偏移量到物理地址之间的映射;时间戳索引文件根据指定的时间戳来查找对应的偏移量信息。 1 日志索引 Kafka的索引文件以稀疏索引的方式构造消息的索引。它并不保证每个消息在索引文件中都有…...

【大模型】ChatGPT 高效处理图片技巧使用详解
目录 一、前言 二、ChatGPT 4 图片处理介绍 2.1 ChatGPT 4 图片处理概述 2.1.1 图像识别与分类 2.1.2 图像搜索 2.1.3 图像生成 2.1.4 多模态理解 2.1.5 细粒度图像识别 2.1.6 生成式图像任务处理 2.1.7 图像与文本互动 2.2 ChatGPT 4 图片处理应用场景 三、文生图操…...

OceanBase 社区年度之星专访:北控水务纪晓东,社区铁杆开发者
编者按:作为开源数据库,社区的发展和持续进步,来自于每一位贡献者的智慧与支持。2024年度,OceanBase社区特别设立了“年度之星”奖,以表彰和感谢在过去一年中,为社区发展作出突出贡献的朋友。 今日&#x…...

Docker 实现MySQL 主从复制
一、拉取镜像 docker pull mysql:5.7相关命令: 查看镜像:docker images 二、启动镜像 启动mysql01、02容器: docker run -d -p 3310:3306 -v /root/mysql/node-1/config:/etc/mysql/ -v /root/mysql/node-1/data:/var/lib/mysql -e MYS…...

农业农村大数据应用场景|珈和科技“数字乡村一张图”解决方案
近年来,珈和科技持续深耕农业领域,聚焦时空数据服务智慧农业。 珈和利用遥感大数据、云计算、移动互联网、物联网、人工智能等先进技术,搭建“天空地一体化”监测体系,并创新建设了150的全球领先算法模型,广泛应用于高…...

动态生成element-plus的scss变量;SCSS中实现动态颜色变体生成
文章目录 一、动态css变量1.生成内容2.动态生成css变量2.1新增_color-utils.scss(不推荐)2.2新增_color-utils.scss(推荐)2.3theme.scss引入使用 一、动态css变量 1.生成内容 在我们修改element-plus主题色时候,会自…...

人工智能学习09-变量作用域
人工智能学习概述—快手视频 人工智能学习09-变量作用域—快手视频...

2025-05-01-决策树算法及应用
决策树算法及应用 参考资料 GitHub - zhaoyichanghong/machine_learing_algo_python: implement the machine learning algorithms by p(机器学习相关的 github 仓库)决策树实现与应用决策树 概述 机器学习算法分类 决策树算法 决策树是一种以树状结构对数据进行划分的分类…...
使用高斯朴素贝叶斯算法对鸢尾花数据集进行分类
高斯朴素贝叶斯算法通常用于特征变量是连续变量,符合高素分布的情况。 使用高斯朴素贝叶斯算法对鸢尾花数据集进行分类 """ 使用高斯贝叶斯堆鸢尾花进行分类 """ #导入需要的库 from sklearn.datasets import load_iris from skle…...

数据集-目标检测系列- 猴子 数据集 monkey >> DataBall
贵在坚持! * 相关项目 1)数据集可视化项目:gitcode: https://gitcode.com/DataBall/DataBall-detections-100s/overview 2)数据集训练、推理相关项目:GitHub - XIAN-HHappy/ultralytics-yolo-webui: ultralytics-yo…...
瀚文机械键盘固件开发详解:HWKeyboard.cpp文件解析与应用
🔥 机械键盘固件开发从入门到精通:HWKeyboard模块全解析 作为一名嵌入式开发老司机,今天带大家拆解一个完整的机械键盘固件代码。即使你是单片机小白,看完这篇教程也能轻松理解机械键盘的工作原理,甚至自己动手复刻一…...

iview-admin静态资源js按需加载配置
iview-admin2.0版本默认加载所有组件的JS,实际情况下,用户访问后台并不会每个页面都浏览。这样就会造成流量及带宽的浪费。可通过修改配置文件vue.config.js来实现按需加载,具体配置如图 image © 著作权归作者所有,转载或内容合作请联系…...
RISC-V 开发板 + Ubuntu 23.04 部署 open_vins 过程
RISC-V 开发板 Ubuntu 23.04 部署 open_vins 过程 1. 背景介绍2. 问题描述3. 解决过程3.1 卸载旧版本3.2 安装 Suitesparse v5.8.03.3 安装 Ceres Solver v2.0.03.4 解决编译爆内存问题 同步发布在个人笔记RISC-V 开发板 Ubuntu 23.04 部署 open_vins 过程 1. 背景介绍 最近…...
量子计算突破:新型超导芯片重构计算范式
2024年IBM 1281量子比特超导芯片实现0.001%量子错误率,计算速度达经典超算2.5亿倍。本文解析: 物理突破:钽基超导材料使量子相干时间突破800μs(提升15倍)架构革命:十字形…...

做题笔记(ctfshow)
一。ctfshow web13 文件扫描 存在upload.php.bak <?php header("content-type:text/html;charsetutf-8");$filename $_FILES[file][name];$temp_name $_FILES[file][tmp_name];$size $_FILES[file][size];$error $_FILES[file][error];$arr pathinfo($fi…...