指针
指针是什么
内存划分是一个字节一个字节来划分的,其中每个字节都有一个编号来标识它,我们将这个编号称为地址,而指针就是地址
注意:编号是不占内存空间的,(这些编号在内存中用十六进制表示)
指针变量:
指针变量:就是存放指针(地址)的变量!
我们可以用 & 符号 来取出变量的地址,把地址存放到指针变量中 !
通常我们口语中所说的指针其实就是指针变量,在口语中人们把指针跟指针变量都叫做指针,但我们得好好区分指针和指针变量
在指针变量里面存放的是一个变量字节中最低的地址!
总结:指针变量就是用来存放地址的变量,存放在指针变量里的值都被当做地址来处理!
内存中是如何编址的?
在内存中规定一个字节给一个对应的编址(地址)
如何编址:在32喂的机器下我们假设有32根地址线,那么每根地址线在寻址的时候会产生高电压或低电压,我们将这些高低电压用0和1代替!
那么32根地址线产生的地址情况:
00000000 0000000 00000000 00000000
00000000 0000000 00000000 00000001
……
01111111 11111111 11111111 11111111
10000000 00000000 00000000 00000000
……
11111111 11111111 11111111 11111111
在这里总共有 2的 32次方个地址(每个地址标识一个字节)
因为每个地址标识一个字节 所以2的32次方的个地址,就可以给4GB的空间进行编址
同理 在64位机器下,有2的64次方个地址,就可以给16384GB(理论上讲)空间进行编址
指针变量的大小
我们知道 8bit = 1byte
在32位机器下,地址由32个比特位组成,
所以存放地址的指针变量,在32位机器下大小是4个字节
在64位机器下,地址由64个比特位组成
所以存放地址的指针变量,在64位机器下大小是8个字节
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
//在32位机器下都是4
//在64位机器下都是8
printf("%zd\n", sizeof(p));
printf("%zd\n", sizeof(int*));
printf("%zd\n", sizeof(char*));
printf("%zd\n", sizeof(float*));
printf("%zd\n", sizeof(double*));
return 0;
}
指针类型
在c中有很多数据类型,为了通过地址很好的访问这些类型的内容,我们用其对应的指针类型变量来存放这些类型的地址!
eg:char* 、short* 、int* 、float* 、double* ……都是指针的类型
char* 来存放char类型变量的地址
int* 存放int类型变量的地址
……
#include <stdio.h>
int main()
{
int a = 100;
int* pa = &a;//存放整型变量a的地址
float b = 3.867;
float* pb = &b;//存放单精度浮点型变量b的地址
char c = 'A';
char* pc = &c;//存放字符类型变量c的地址
double d = 4.88998;
double* pd = &d;//存放双精度浮点型变量d的地址
//可以将它们的地址打印出来看是否存进去了
printf("%p\n", &a);
printf("%p\n", pa);
printf("%p\n", &b);
printf("%p\n", pb);
printf("%p\n", &c);
printf("%p\n", pc);
printf("%p\n", &d);
printf("%p\n", pd);
//也可以通过将指针变量解引用操作,打印出指向变量的内容
printf("%d\n", a);
printf("%d\n", *pa);
printf("%f\n", b);
printf("%f\n", *pb);
printf("%c\n", c);
printf("%c\n", *pc);
printf("%lf\n", d);
printf("%lf\n", *pd);
return 0;
}
指针的解引用
* 解引用操作符,对指针变量进行解引用操作
对指针变量进行解引用就是取到指针变量所指向空间的内容
指针的类型决定了对指针变量解引用的时候能操作几个字节
比如:char* 指针变量解引用只能访问一个字节的内存空间
int* 指针变量解引用只能访问4个字节的内存空间
#include <stdio.h>
int main()
{
int a = 10;
//让指针变量指向a
int* pa = &a;
printf("%d\n", *pa);
//通过指针解引用操作访问a并改变内容
*pa = 20;
printf("%d\n", a);
//让字符型指针指向a
char* p = (char*)&a;
//通过指针访问a的第一个字节的内容
//因为目前是小端存储第一个字节的值是20
printf("%d\n", *p);
printf("%d\n", *(p + 1));//访问第二个字节
//通过指针解引用操作,访问到a的第一个字节将其改为39
*p = 39;
printf("%d\n", a);
return 0;
}
野指针
野指针的成因
1、指针未进行初始化
2、指针越界访问
3、指针指向的空间被释放
#include <stdio.h>
int* test(void)
{
int a = 10;
int* p = &a;
return p;
}
int main()
{
//未初始化的指针
int* p; //里面放的是随机值,不安全
printf("%p\n", p);//vs编译器会报错
//指针越界访问
int arr[10];
int i = 0;
int* pa = arr;//数组名表示首元素地址
for (i = 0; i <= 10; i++)
{
//当i=10的时候指针就越界访问了
*pa = i + 1;
pa++;
}
//指针指向的空间被释放
int* ps = test();
//函数返回来时a已经被释放,此指针时野指针
printf("%p\n", ps);//ps里面存的地址
printf("%d\n", *ps);//结果不可测
return 0;
}
如何规避野指针
1、指针要初始化
2、小心指针越界
3、指针指向的空间释放,及时把指针置为NULL
4、避免返回局部变量的地址
5、指针使用之前检查有效性
#include <stdio.h>
#include <assert.h>
int* Max(int* p)
{
int a = 10;
int* p1 = &a;
//不要返回局部变量的地址
//return p1;
//返回我们传进来的数组首元素的地址
return p;
}
int main()
{
//指针要初始化
int a = 10;
int* pa = &a;
//当我们不知道该初始化成什么的时候将指针置为NULL
char* pc = NULL;
//要小心指针越界,不能让指针越界访问
int arr[5];
int* pr = arr;
int i = 0;
for (i = 0; i < 5; i++)
{
*(pr+i) = i + 1;
printf("%d ", *(pr + i));
}
printf("\n");
//指针指向的空间被释放,及时将指针置为空
//向堆区申请空间
int* pm = (int*)malloc(sizeof(int) * 20);
//空间进行释放
free(pm);
//及时将指针置为空
pm = NULL;
//避免返回局部变量的地址
int* px = Max(arr);
//指针在使用之前检查有效性
char c = 'B';
char* ptr =NULL;
ptr = &c;
//检查有效性
if (ptr != NULL)
{
*ptr = 'A';
printf("%c\n", c);
}
//也可用assert直接判断指针的有效性
assert(ptr);
*ptr = 'G';
printf("%c\n", c);
return 0;
}
指针运算
对指针进行运算
1、指针 + -整数
2、指针 - 指针
3、指针的关系运算
指针 + - 整数
指针加减整数、加的距离由指针的类型决定,(类型决定步长)
若是char* 的指针给它+1 指针往前走一个字节(地址+1)
若是int* 的指针给它+1 指针的步长为四个字节(地址+4)
同理:指针减多少 就往后走多少,步长由类型决定
总结:指针的类型决定了指针向前或者向后走的步长(距离)
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
char* p1 = (char*)&a;
//让指针 +1、-1 分别看步长
printf("%p\n", pa);
printf("%p\n", pa + 1);//前进4
printf("%p\n", p1);
printf("%p\n", p1 + 1);//前进1
printf("%p\n", pa);
printf("%p\n", pa - 1);//后退4
printf("%p\n", p1);
printf("%p\n", p1 - 1);//后退1
return 0;
}
指针 - 指针
指针相减的前提是指针都指向同一片内存空间
两个指针相减的绝对值是指针之间的元素个数(距离)
#include <stdio.h>
int my_strlen(char* p)
{
char* q = p;//开始的位置
while (*p != '\0')
{
p++;
}
return p - q;
}
int main()
{
//指针 - 指针
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;//首元素地址
int* q = &arr[9];//最后一个元素的地址
//相减
int sz = q - p;
//两个指针之间的元素个数
printf("%d\n", sz);
//求字符串长度
char str[] = "hello";
int len = my_strlen(str);
printf("str的长度为:%d\n", len);
return 0;
}
指针的关系运算
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较
#include <stdio.h>
#define NOW 5
int main()
{
int arr[NOW];
int* p = NULL;
int i = 1;
//这种可行
for (p = &arr[NOW]; p > arr;)
{
/*p--;
*p = i;*/
*--p = i;
}
for (i = 0; i < NOW;i++)
{
printf("%d ", arr[i]);
}
printf("\n");
//尽量避免这样写,规定是不能指向第一个元素之前的那个地址进行比较
for (p = &arr[NOW - 1]; p >= arr; p--)
{
*p = 3;
}//大多数编译器能编过,但规定不能这样写
for (i = 0; i < NOW; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
指针与数组
数组名表示的是首元素地址(只有在 &数组名 和sizeof(数组名)的时候表示整个数组)
arr[i] 等同于 *(数组名+i)
arr[i] 跟 i[arr] 是等价的 因为[] 是一个操作符,i跟arr是它的操作数
跟 a+b 写成 b+a 类似
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
//数组名表示首元素地址
printf("%p\n", arr);
printf("%p\n", &arr[0]);
//arr[i] 等同于 *(arr+i)
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(arr + i));
}
printf("\n");
//也可以将首元素地址用一个指针变量保存起来
int* p = arr;
for (i = 0; i < 5; i++)
{
printf("%d ", *(p+i));//也可用 p[i] 的方式访问
}
printf("\n");
//arr[i] 与 i[arr] 等价
//因为[] 是操作符,它的操作数是 arr和i
// 跟a+b 写成b+a 类似
for (i = 0; i < 5; i++)
{
printf("%d ", i[arr]);//是一种不好的书写方式不推荐
}
return 0;
}
二级指针
我们知道指针变量是存放地址的变量
指针变量本质上也是一个变量,变量是有地址的可以对它进行取地址操作,同理指针变量也有地址,也可对它进行取地址操作
我们知道变量的地址是存放在指针变量中的,那么用来存放指针变量的地址的变量我们叫做二级指针变量(二级指针)
即 二级指针变量就是用来存放一级指针变量的地址!
那么二级指针怎么写呢?
我们定义一个整型指针变量 :int * pa
其中 *号 表示 是一个指针变量,*后面的pa表示变量名,*号前面的 int 表示指针变量所指向空间的类型(也就是指针变量进行访问的时候一次可操作多大的内存空间)
同理:二级指针 也是如此: int* * ppa
其中 *号表示是一个指针变量,*号后面的ppa表示变量名,*号前面的 int* 表示指针变量所指向空间的类型,所指向空间是一个 int*(指针)
解引用操作:
对二级指针解引用,进行两次解引用操作即可
两次解引用:将二级指针先解引用一次取到一级指针
再将一级指针解引用一次得到指针最终指向的内容
#include <stdio.h>
int main()
{
//二级指针
//定义一个整型变量a并赋值成10
int a = 10;
//定义一个一级指针变量,并将整型变量a的地址存放到指针变量p中
int* p = &a;
//定义一个二级指针变量,并将一级指针变量的地址存放到二级指针变量pp中
int** pp = &p;
//解引用操作
//用一级指针p取到变量a的值,将p解引用一次
printf("%d\n", *p);
//用二级指针pp取到变量a的值,将pp解引用两次
//湘江pp解引用取到一级指针p 再将p解引用取到a
printf("%d\n", *(*pp));
return 0;
}
指针数组
指针数组是数组,数组里面存放的每个元素都是一个指针
#include <stdio.h>
int main()
{
//指针数组
int a = 10;
int b = 20;
int c = 30;
//定义一个int* 类型的数组,数组里面存放的是a,b,c的地址
int* arr[3] = { &a,&b,&c };
//打印
int i = 0;
for (i = 0; i < 3; i++)
{
//给指针解引用取到指向的内容
printf("%d ", *arr[i]);
}
// 给指针(地址)解引用就能取到指向的内容
int s = 200;
printf("%d\n", *(&s));
//用一个一维数组表示二维数组
//定义三个一维数组并且给其赋值
int a1[5] = { 1,2,3,4,5 };
int b1[5] = { 6,7,8,9,10 };
int c1[5] = { 11,12,13,14,15 };
//定义一个指针数组,里面存放的是三个一维数组的首元素地址
//数组名表示首元素地址
int* arr1[3] = { a1,b1,c1 };
//通过这个指针数组打印这三个一维数组
int k = 0;//控制行数
int j = 0;//控制列数
for (k = 0; k < 3; k++)
{
for (j = 0; j < 5; j++)
{
//arr[k] 取到的是一维数组的首元素地址
//arr[k][j] 等同于 *(arr[i]+j)
printf("%d ", arr1[k][j]);
}
printf("\n");
}
return 0;
}