- 指针是什么
- 指针和指针类型
- 野指针
- 指针运算
- 指针和数组
- 二级指针
- 指针数组
第一章 指针是什么
指针其实是一个变量,存放内存单元的地址。(存放在指针中的值都被当成地址处理)
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a; //指针变量
return 0;
}
- 一个小单元的大小是一个字节
- 如何编址?
对于32位的机器,有32跟地址线,那么假设每根地址线在寻址的是产生一个电信号正点/负电(1/0)那么32根地址线产生的地址就会是:
这样就产生了2的32次方个地址。
每个地址标识一个字节,那我们就可以给(2^32byte == 2^32/1024KB == 2^32/1024/1024MB == 2^32/1024/1024/1024GB == 4GB)4G的空闲进行编址。
小结:
- 在32位机器上,地址是32个0/1组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针的变量大小就应该是4个字节。
- 那如果在64位机器上,就有64个0/1组成的二进制序列,那一个指针的变量大小就是8个字节。
第二章 指针和指针类型
指针类型的意义
如果我们依次打印char*,int*,short*,double*所占用的空间大小,会发现全部都是一样的结果,那是否就意味着指针类型无意义呢?其实不然。
分别对pa,pc进行调试排查,可以发现,在用pa改变a中内容时,所有字节都发生了变化,而pc只有一个字节发生变化。只是因为替换了指针类型,就发生如此变化,那这怎么回事呢?
1. 指针类型决定指针进行解引用操作时,能够访问空间的大小。
- int* p:*p能够访问4个字节
- char* p : *p能够访问1个字节
- double* p:*p能够访问8个字节
2. 指针类型决定了指针的步长(走一步走多远)
指针类型在±整数时的意义:根据以下例子的编译结果可以发现,指针类型位为整型,那走一步就是4个字节;指针类型为字符型,那走一步就是1个字节。
int a = 0x11223344;
int* pa = &a;
char* pc = &a;
printf("%p\n",pa);
printf("%p\n",pa+1);
printf("%p\n",pc);
printf("%p\n",pc+1);
- int* p: p+ 1 -- > 4
- char* p : p + 1 -- > 1
- double* p : p + 1 -- > 8
第三章 野指针
野指针:指针指向的位置不可知(随机的、不正确的、没有明确限制的)
野指针成因:
- 指针未初始化
//指针未被初始化
int a; //局部变量未被初始化,默认是随机值
int* p; //局部的指针变量,就被初始化随机值
*p = 20;
- 指针越界访问
//指针越界访问
int arr[10] = {0};
int* p = arr;
int i = 0;
for ( i = 0; i < 12; i++)
{
p++;
}
- 指针指向的空间释放
如何规避野指针:
- 指针要初始化
- 小心指针越界
- 指针指向空间释放,即使其置NULL - 用来初始化指针,给指针赋值。
- 指针使用之前检查有效性
int* p = NULL; //指针p初始化
int a = 10;
p = &a; //p存放a的地址
//检查指针有效性
if (p != NULL)
{
*p = 20; //改变a的内容为20
}
第四章 指针运算
- 指针±整数
- 指针 - 指针
- 指针的关系运算(比较大小)
指针±整数
//指针±整数
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
int *p = arr; //数组首元素地址
// int *p = &arr[9];
for ( i = 0; i < sz; i++)
{
printf("%d ",*p);
p = p + 1; //p ++
// p --;
}
指针-指针
指针-指针可以理解成就是地址-地址,那实际表示的意义是什么呢?实际指针-指针就是数组的元素个数。
注意:指针-指针一定是需要两个数组是指向同一块空间的。
//指针-指针 地址-地址
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n",&arr[9] - &arr[0]);
了解了这样的思想,我们可以来做一个小练习: 模拟strlen()函数。在前面我们已经用计数器的方式和递归方式都模拟过,现在用第三个方式尝试一下。我们知道一个数组,那既然指针-指针(地址-地址)就是数组的元素个数,那我们就沿用这个思想。假设arr[0]是start,arr[sz - 1]是end,使得end-start就可以得到元素个数,即数组长度。具体程序如下。
#include<stdio.h>
int my_strlen(char* str)
{
char* start = str;
char* end = str;
while (*end != '\0')
{
end ++;
}
return end - start;
}
int main()
{
char arr[] = "bit";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
指针的关系运算
注意:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
简单来说,就是允许p1和p2发生比较,但是p1和p3不能比较。
第五章 指针和数组
进行下面实例的练习,我们可以发现arr表示的就是数组首元素的地址。
int arr[10] = {0};
printf("%d\n",arr);
printf("%d\n",arr + 1);
printf("%d\n",&arr[0]);
printf("%d\n",&arr[0] + 1);
printf("%d\n",&arr);
printf("%d\n",&arr + 1);
但是也有例外,&数组名表示的是整个数组,取出的是整个数组的地址。 sizeof(数组名)中数组名表示的也是整个数组,计算的是整个数组的大小。
那如何把指针和数组结合起来呢?根据下面的例子发现结果都一样,所以指针p指向的是数组首元素的地址。
int arr[10] = {0};
int* p = arr; //p存放的就是arr首元素的地址
int i = 0;
for ( i = 0; i < 10; i++)
{
*(p + i) = i;
printf("%d ",*(p + i));
// printf("%d ",arr[i]);
}
所以,其实p+i其实计算的是数组arr下标为i的地址。
那我们就可以直接通过指针来访问数组。
第六章 二级指针
用一个例子来说明,是比较好理解的。pa是个指针变量,存储的是a的地址;ppa是二级指针变量,存储的是指针变量pa的地址。
//二级指针
int a = 10;
int* pa = &a; //指针变量
int** ppa = &pa; //ppa就是二级指针。
//二级指针解引用
int **ppa = 20; // a = 20;
第七章 指针数组
指针数组:本质上是数组 - 存放指针的数组
数组指针:本质上是指针
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a, &b, &c};
int i = 0;
for ( i = 0; i < 3; i++)
{
printf("%d ",*(arr[i]));
}