博客主页:江池俊的博客
收录专栏:C语言——探索高效编程的基石
如果觉得博主的文章还不错的话,请点赞收藏 三连支持一下博主
目录
一、指针是什么?
1.1指针与内存间的关系
内存
1.2指针变量存放地址
二、指针和指针类型
2.1指针有哪些类型?
2.2指针类型的意义是什么?
2.2.1指针的类型决定了指针向前或者向后走一步有多大(距离)。
2.2.2指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
三、野指针
3.1野指针成因
3.1.1. 指针未初始化
3.1.2. 指针越界访问
3.1.3. 指针指向的空间释放
3.2 如何规避野指针
四、指针运算
4.1 指针+-整数
4.2 指针-指针
4.3 指针的关系运算
五、 指针和数组
六、二级指针
6.1二级指针与多级指针
6.2二级指针运算
七、指针数组
一、指针是什么?
在计算机科学中,指针是一种变量类型,它存储了一个内存地址。这个内存地址指向计算机内存中的一个特定位置,该位置存储了实际的数据。通过使用指针,可以直接访问和修改存储在特定内存位置的数据,而无需复制或移动数据本身。(本质上指针就是地址)
1.1指针与内存间的关系
这里我们知道char类型占1个字节的内存空间,short类型占2个字节的内存空间,int类型占4个字节的内存空间,long类型占4个字节的内存空间,long long类型占8个字节的内存空间,float类型占4个字节的内存空间,double类型占8个字节的内存空间。
内存
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 0000000000000000 00000000 00000000 00000001
……..
01111111 11111111 11111111 11111111
10000000 00000000 00000000 00000000
10000000 00000000 00000000 00000001
……..
11111111 11111111 11111111 11111110
11111111 11111111 11111111 11111111
一共有种可能的编号,每一种编号表示一个内存单元的地址,所以共有个地址。每个地址表示一个字节,那我们就可以给 (2^32Byte=2^32/1024KB=2^32/1024/1024MB=2^32/1024/1024/1024GB = 4GB)4G的空闲进行编址。
同样的方法,那64位机器,如果给64根地址线,那么就有个地址。
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储(因为一个字节等于八个比特位,而一个0或1占一个比特位),所以一个指针变量的大小就应该是4个字节。
同理,那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
1.2指针变量存放地址
注意:这里a的值以16进制的形式存储在内存中的并且是倒着存放的(这里涉及到大小端问题)。
总结:指针变量是用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
二、指针和指针类型
2.1指针有哪些类型?
char *pc = NULL;int *pi = NULL;short *ps = NULL;long *pl = NULL;float *pf = NULL;double *pd = NULL;
不同类型的指针其实是为了存放对应类型变量的地址。
2.2指针类型的意义是什么?
2.2.1指针的类型决定了指针向前或者向后走一步有多大(距离)。
这里我们不难看出,char*类型的指针加一时,它的地址增加了1个字节,而int*类型的指针加一时,它的地址增加了4个字节。
总结:指针变量加减,跳过多少字节(地址加减多少)取决于指针类型
例如:
如果是 char 类型指针,指针变量加 1或减1实际上地址加了或减了 1 个字节
如果是 short 类型指针,指针变量加 1或减1 实际上地址加了或减了 2 个字节
如果是 int 类型指针,指针变量加 1或减1 实际上地址加了或减了 4 个字节
2.2.2指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
char*指针
这里是将int类型的变量n的地址强制转换赋给了char类型指针pc
int*指针
三、野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1野指针成因
3.1.1. 指针未初始化
#include int main(){int *p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;}
3.1.2. 指针越界访问
#include int main(){int arr[10] = {0};int *p = arr;int i = 0;for(i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;}return 0;}
3.1.3. 指针指向的空间释放
动态开辟一块空间时返回的值存放到一个指针中,当使用完这个指针后没有释放时,该指针就会变为野指针。
3.2 如何规避野指针
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
#include int main(){int *p = NULL;int a = 10;p = &a;//检查指针的有效性if(p != NULL){*p = 20;}return 0;}
四、指针运算
4.1 指针+-整数
指针加+-运算用于将指针移动指定的偏移量,以便访问其他地址处的数据。例如,如果有一个指向整型数组的指针p,可以使用p+n或p-n将指针移动n个整型长度的位置。
#define N_VALUES 5float values[N_VALUES];float *vp;//指针+-整数;指针的关系运算for (vp = &values[0]; vp < &values[N_VALUES];){*vp++ = 0;}
4.2 指针-指针
可以对两个指针进行减法运算,得到它们之间的距离(以元素个数为单位)。这在处理数组或动态内存分配时非常有用。例如,假设有两个int类型的指针ptr1和ptr2,我们可以进行如下操作:
int distance = ptr2 - ptr1; // 计算ptr2和ptr1之间的距离(以int元素个数为单位)
需要注意的是,指针运算应该谨慎使用,确保指针指向的内存是有效的。否则,可能会导致未定义行为或内存错误。此外,不同类型的指针之间不应进行直接算术运算,因为它们可能引用不同大小的数据。只有在指向同一数组的元素或有效分配的内存区域时,指针之间的运算才是有效的。
4.3 指针的关系运算
在C、C++等编程语言中,指针的关系运算用于比较指针的地址或者判断指针是否为NULL。下面是指针的关系运算符及其含义:
相等(==):用于判断两个指针是否指向同一个内存地址。如果两个指针指向相同的地址,则关系表达式为真,否则为假。
int* ptr1;int* ptr2;if (ptr1 == ptr2) {// 指针ptr1和ptr2指向相同的地址}
不相等(!=):用于判断两个指针是否指向不同的内存地址。如果两个指针指向不同的地址,则关系表达式为真,否则为假。
int* ptr1;int* ptr2;if (ptr1 != ptr2) {// 指针ptr1和ptr2指向不同的地址}
大于(>)、小于(=)、小于等于(<=):这些关系运算符用于比较两个指针所指向的地址之间的顺序关系。只有当指针指向同一个数组中的元素或者在有效的内存范围内时,这些关系运算符才有定义。
int* ptr1;int* ptr2;if (ptr1 > ptr2) {// ptr1指向的地址在ptr2指向的地址之后}if (ptr1 = ptr2) {// ptr1指向的地址在ptr2指向的地址之后或相同}if (ptr1 <= ptr2) {// ptr1指向的地址在ptr2指向的地址之前或相同}
空指针检查:指针的关系运算还可以用于检查指针是否为空(指向NULL)。在C和C++中,NULL是一个预定义的宏,用于表示一个空指针。
int* ptr = NULL;if (ptr == NULL) {// 指针ptr是空指针}if (ptr != NULL) {// 指针ptr不是空指针}
需要注意的是,对于关系运算符(>, =, <=),只有在指针指向同一数组的元素或有效分配的内存区域时,比较的结果才是有效的。在进行指针比较之前,最好确保指针指向的内存是有效的,否则可能会导致未定义行为。
五、 指针和数组
我们来看一个例子:
#include int main(){int arr[10] = {1,2,3,4,5,6,7,8,9,0};printf("%p\n", arr);printf("%p\n", &arr[0]);return 0;}
运行结果:
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。(一般情况下)
那么这样写代码是可行的:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};int *p = arr;//p存放的是数组首元素的地址
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:
#include int main(){int arr[] = {1,2,3,4,5,6,7,8,9,0};int *p = arr; //指针存放数组首元素的地址int sz = sizeof(arr)/sizeof(arr[0]);for(int i=0; i<sz; i++){printf("&arr[%d] = %p p+%d = %p\n", i, &arr[i], i, p+i);}return 0;}
运行结果:
所以 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组。
如下:
#includeint main(){int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };int *p = arr; //指针存放数组首元素的地址int sz = sizeof(arr) / sizeof(arr[0]);int i = 0;for (i = 0; i<sz; i++){printf("%d ", *(p + i));}return 0;}
运行结果:
六、二级指针
二级指针(double pointer)是指指向指针的指针。我们可以使用指向指针的指针来处理指针的引用或修改。它们在一些情况下非常有用,特别是在涉及到函数参数传递和动态内存分配等方面。
定义二级指针的语法为在指针类型名称前加上两个星号(**):
int** doublePtr;
6.1二级指针与多级指针
#includeint main(){int a = 10;int* pa = &a;int** ppa = &pa;int*** pppa = &ppa;printf("%p\n", a);printf("%p\n", &a);printf("\n%p\n", pa);printf("%p\n", &pa);printf("\n%p\n", ppa);printf("%p\n", &ppa);printf("\n%p\n", pppa);printf("%p\n", &pppa);return 0;}
6.2二级指针运算
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
int b = 20;*ppa = &b;//等价于 pa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
**ppa = 30;//等价于*pa = 30;//等价于a = 30;
七、指针数组
指针数组是一个数组,其中的每个元素都是指针。我们可以创建指针数组来存储一组指向不同数据类型的指针。这些指针可以指向数组、字符串、函数等各种数据类型,从而实现更灵活的数据结构和操作。
定义指针数组的语法为在指针类型名称后加上方括号([]):
int* ptrArray[5]; // 定义一个包含5个int指针的数组
在上述示例中,ptrArray
是一个包含5个指向int类型数据的指针的数组。我们可以通过数组下表来访问和操作这些指针:
int num1 = 10;int num2 = 20;int num3 = 30;int* ptrArray[3]; // 定义一个包含3个int指针的数组ptrArray[0] = &num1; // 第一个指针指向num1的地址ptrArray[1] = &num2; // 第二个指针指向num2的地址ptrArray[2] = &num3; // 第三个指针指向num3的地址printf("Value at index 0: %d\n", *ptrArray[0]); // 输出:Value at index 0: 10printf("Value at index 1: %d\n", *ptrArray[1]); // 输出:Value at index 1: 20printf("Value at index 2: %d\n", *ptrArray[2]); // 输出:Value at index 2: 30
指针数组非常适用于存储字符串数组。在C语言中,字符串被表示为字符数组,并且每个字符串都以空字符(’\0’)结尾。使用指针数组可以更方便地管理字符串的集合:
const char* names[] = { "Alice", "Bob", "Charlie" };
在上面的示例中,我们创建了一个包含3个指针的指针数组,每个指针都指向一个字符串常量。
需要注意的是,指针数组中的每个指针应该指向有效的内存地址。否则,当我们尝试通过这些指针访问数据时,可能会导致未定义行为或者出现野指针的问题。确保在使用指针数组之前,为每个指针分配合适的内存或者让它们指向有效的数据。同时,记得在不再需要这些指针数组时,及时释放其所指向的内存,避免内存泄漏。