由上述,指针是存储内存的地址,我们首先理解内存和地址的关系:
1. 基本概念:
内存:内存是计算机中的一个组件,用于临时存储数据和程序指令。它由一系列可存储数据的单元组成,每个单元都有其唯一的位置。
地址:内存中每个单元的位置都通过一个唯一的编号或地址来识别。这个地址用于定位和访问存储在内存中的数据。
2. 定位和访问数据:
当程序运行时,它需要存储和访问数据。每个数据片段都存储在内存的某个位置,并通过地址来定位。
例如,如果有一个变量存储在内存中,这个变量的具体位置就是它的内存地址。程序通过这个地址来读取或修改变量的值
而如何访问地址,就需要指针来实现
指针变量:指针变量存放地址,而取出地址就需要取地址符“&
”
例如我们定义了一个变量a,并赋值为零,在创建a变量时向内存申请空间,整形类型占四个字节,每个字节都有地址,“&a”
即取出a的地址,可以通过%p
,打印出a的地址。
创建一个指针变量,int *p=&a,p即存放了a的地址,int代表p指向的类型为整形,这里我们可以通过p来对a进行改变,拿到了a的地址,就可以找到指向的对象,这里通过解引用符“ *
”实现,
eg: ,解引用后,*pa就是a变量了。
指针变量的大小:
指针变量的大小通常取决于操作系统和硬件架构,主要是因为它需要能够表示内存中任意位置的地址。在不同的系统和架构上,指针变量的大小可能有所不同:
32位系统:在32位操作系统上,指针变量通常是32位(4字节)的。这是因为32位地址可以表示 (2^{32}) 个不同的地址,覆盖了最多4GB的内存空间。
64位系统:在64位操作系统上,指针变量通常是64位(8字节)的。64位地址可以表示 (2^{64}) 个不同的地址,这对于现代计算机的大内存需求来说是必要的。
需要注意的是,指针的大小并不取决于它所指向的数据类型的大小。无论是指向 int、char、float 或任何其他类型的指针,它在特定的系统和架构上的大小都是固定的。
这意味着,在32位系统上,所有类型的指针(如 int*、char* 等)都是4字节大小,而在64位系统上则都是8字节。这种设计使得指针的操作在不同数据类型之间保持一致,同时确保了足够的空间来存储任意的内存地址。
我们可以通过打印来确定这一点:
void*指针 :在创建指针变量中,前面的int ,float代表指针指向内容的类型,那当无法确定类型时,这里用void*来创建指针,这个指针可以接受任意类型地址,但不能直接进行解引用操作
这种的解决办法可以进行强制转换,在后续qsort中我们还会遇到
const修饰指针变量 :当一个变量被const修饰后,后续我们无法再对其进行改变,那么我们来看一下const修饰指针变量的几种类型:
const int *pa/int const*pa:这里const修饰的是*pa,即*pa无法修改
int *const pa
:即pa无法改变
• const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。
• const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。
指针的运算:数组在内存中连续存放,只要知道第一个元素的地址,就能找到后面元素。
野指针:野指针是指向位置不可知的,一般 指针未初始化或越界访问可能造成野指针
避免指针越界,可以对指针初始化,或者当指针不使用是,及时置为NULL.
assert断言:assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏常常被称为“断⾔”。assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣ 任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错.
传值调用与传址调用;当我们想向函数传递参数的时候,形参会单独创建一份临时空间来接受实参,对形参的修改不会影响实参
想要改变,这里需要传递a和b的地址
关于数组与指针:数组名代表首元素的地址,有两个例外:
sizeof(arr),&arr,这里的数组名都表示整个数组
,这里的数组名都表示整个数组
下来对这一组代码进行分析
&arr[0],为首元素地址,加一为整形,跳过四个字节,arr也为首元素地址,加一跳过四个字节,而&arr为首元素地址,但加一跳过整个数组,有四十个字节
使用指针访问数组:
在开始学习时我们输入数组会以&arr[ i ]的形式输入,这里p为首元素地址,+i即第i-1个元素的地址,与&arr[ i ],效果相同,打印的时候,我们可以通过解引用来打印,*(p+i) 。
数组传参的本质也是首元素地址的传入。
二级指针:
指针变量也是变量,是变量就有地址,
这里,我们将指针pa的地址放在ppa指针中,这个指针就是二级指针。
对耳机指针进行解引用,我们访问的就是pa,再次解引用,访问a 。
指针数组:从名字上看,是数组,但数组的元素类型为指针,
整形数组,int arr【10】,字符数组,char arr 【10】,
那么指针数组可以写成,int *arr【10】,float *arr【10】,这里的int ,float代表指针指向的数据类型
指针数组模拟二维数组
parr【i】即每一行第一个元素的地址,可以模拟出二维数组的效果。
字符指针变量,
这里其实本质是把字符串的第一个元素的地址传给pstr,但是特殊的是,printf会自动处理后续的字符,一直打印直到遇到‘\0’为止。
数组指针变量:由名字可以知道,数组指针本质是指针,但是指针指向的数组,下面来介绍标准的格式:int (*p)[10],指针p为数组指针,指向的数组含有十个元素,且每个元素类型为int.
在二维数组传参中,就需要数组指针
函数指针变量:即指针存放函数的地址,可以通过指针来调用函数。
我们可以得出,函数名即为函数的地址
那么函数指针的书写格式
函数指针变量的应用:
这里pf3即为函数的地址,函数在调用时效果相同。
typedef关键字:typedef可以将复杂的类型简单化
比如,我们可以将unsighed int 重命名为unint
typedef unsigned int uint;
指针也可以重命名
比如将int *重命名为 ptr:typedef int* ptr
⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr:
typedef int(*parr)[5]
函数指针数组:是存放函数指针的数组,每一个元素指向一个函数
应用:下面是一个简单的示例,假设有两个函数 add 和 subtract,它们都接受两个整数并返回它们的和或差。我们可以创建一个函数指针数组,并根据用户的选择调用相应的函数。
在这个示例中,我们首先定义了两个函数 add 和 subtract,它们的函数签名(参数列表和返回类型)相同。然后我们创建了一个名为 operation 的函数指针数组,它包含了指向这两个函数的函数指针。在 main 函数中,我们要求用户输入选择,然后根据选择调用相应的函数。
这个示例展示了如何使用函数指针数组来实现动态选择不同的函数。这种方法在某些情况下非常有用,比如执行不同的操作或者选择不同的算法,而不需要写重复的代码。
qsort函数:
void qsort(void *base, size_t num, size_t size, int (*compare)(const void *, const void *));
其中,参数说明如下:
base:指向需要排序的数据序列的起始地址
num:数据序列中的元素个数
size:每个元素的大小(以字节为单位)
compare:用于比较两个元素的函数指针,该函数必须接受两个const void *类型的参数,并返回一个整数,表示两个元素的大小关系
这里举一个简单的例子
关于qsort中比较函数,通过p1,p2先后顺序来确定顺序或逆序