目录
指针是什么
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
那我们就可以这样理解:
内存
指针变量
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量。
总结:
1.内存被划分成一个个的内存单元,每个单元的大小是一个字节
2.每个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中称为指针
3.地址要存储的话,存放在指针变量中
4.每个内存单元都有唯一的地址来标识
5.在32位机器上地址的大小是4个字节,所以指针的大小也是4个字节
同理:在64位机器上地址的大小是8个字节,所以指针变量的大小也是8个字节
指针和指针类型
这里我们在讨论一下:指针的类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的
指针变量的类型
这里可以看到,指针的定义方式是: type + * 。
其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
指针类型的意义:指针的类型决定了指针进行解引用操作的时候,访问几个字节。
指针+-整数
指针的类型决定了指针+1/-1跳过几个字节
int* 的指针+1,跳过4个字节
char*的指针+1,跳过1个字节
short*的指针+1,跳过2个字节
double*的指针+1,跳过8个字节
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
指针的解引用
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而int* 的指针的解引用就能访问四个字节。
野指针
指针未初始化
int main()
{
int* p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
指针指向的空间释放
int* test()
{
int a = 10;//0x0012ff40
return &a;
}
int main()
{
//0x0012ff40
int* p = test();
//p就是野指针
printf("%d\n",*p);
return 0;
}
如何规避野指针
1. 指针初始化
1)如果明确指针应该指向哪里,就初始化正确的地址;
int a=10;
int* p=&a;
2)如果指针不知道初始化什么值,为了安全,初始化NULL
int* p = NULL
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址5.指针使用之前检查有效性
int main()
{
int* p = NULL;//初始化为空指针
//....
int a = 10;
p = &a;
if (p != NULL)//指针使用之前检查有效性
{
*p = 20;
}
return 0;
}
指针运算
指针+-正数
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//使用指针打印数组内容
int* p = arr;
int i = 0;
//arr-->p
//arr == p
//arr+i == p+i
//*(arr+i) == *(p+i) == arr[i]
//*(arr+i)==arr[i]
//*(i+arr) == i[arr] 无论那种本质都是利用首元素地址加偏移地址取值
for (i = 0; i < 10; i++)
{
//printf("%d ",*(p+i));
//printf("%d ",*(arr+i));
//printf("%d ",arr[i]));
printf("%d ", i[arr]);
//p指向的是数组首元素
//p+i指向的是数组中下标为i的元素的地址
//p+i其实是跳过了i*sizeof(int)个字节
}
return 0;
}
#define N_VALUES 5
float values[N_VALUES];
float* vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
指针-指针
int main()
{
int arr[10] = { 0 };
//
//指针-指针的前提:两个指针指向同一块区域,指针类型时相同的
//指针-指针差值的绝对值,指针和指针之间的元素个数
//
printf("%d\n", &arr[9] - &arr[0]);
printf("%d\n", &arr[0] - &arr[9]);
return 0;
}
模拟实现strlen
size_t my_strlen(char* p)
{
char* start = p;
while (*p != 0)
{
p++;
}
return p - start;
}
int main()
{
char arr[] = "abcdef";
size_t len =my_strlen(arr);
printf("%zd\n",len);
return 0;
}
指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组
指针就是指针,指针变量就是一个变量,存放的地址,指针变量的大小是4/8
数组就是数组,可以存放一组数,数组的大小是取决于元素的类型和个数
数组的首元素名是数组首元素地址,地址是可以放在指针变量中,通过指针可以访问一个数组的元素!
数组名表示数组首元素地址
但是有两个例外:
1.sizeof(数组名),数组名单独放在sizeof内部,数组名表示整个数组,计算的数组的大小,单位是字节
2.&数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素的地址,值是一样的,但是类型和意义是不一样的。
int main()
{
int arr[10] = {0};
printf("%p\n", arr);//int*
printf("%p\n", arr+1);//跳过4个字节
printf("%p\n", &arr[0]);//int*
printf("%p\n", &arr[0]+1);//跳过4个字节
printf("%p\n", &arr);//
printf("%p\n", &arr+1);//跳过整个数组40个字节
printf("%d\n", sizeof(arr));
return 0;
}
二级指针
二级指针式存放一级指针的地址的
int main()
{
int a = 10;
int* p = &a;//p是指针变量,一级指针变量
int* * pp = &p;//pp指针变量,二级指针变量 表示pp指向的对象是int*
//int** * pp = &p;//ppp指针变量,三级指针变量
return 0;
}
指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
数组我们已经知道整形数组,字符数组。
int arr1[5];
char arr2[6];
那指针数组是什么?
int* arr3[5];//存放整形指针的数组
char* arr4[5];//存放字符指针的数组
arr3是一个数组,有五个元素,每个元素是一个整形指针。
使用指针数组,模拟一个二维数组
int main()
{
//使用指针数组,模拟一个二维数组
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
//指针数组
int* arr[] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
return 0;
}