一、一级指针
1.作用
用来存放变量在内存中的地址
使用指针可以间接访问地址中的变量值
2.定义指针
类型名 *指针的名字;
指针本身也是个变量,只是该变量比较特殊,它里面存放别人的地址
下面是初始化指针,以及给指针赋值
int a = 1;
int b = 2;
int *p = &x; //定义了指针p,p里面存放变量a在内存中地址(p指向a的地址,p指向a)
p = &y;
int *q;
q = &a;
3.使用指针
解引用:获取地址中保存的内容
取地址:获取变量在内存中的地址
解引用和取地址互为逆运算
第一种:通过指针访问变量的值 --》解引用
第二种:通过指针修改变量的值
野指针
定义了指针,但是指针没有明确的指向(指针指向哪里不知道),这种指针就叫做野指针
危害:段错误
解决方法:
方法一:定义指针的时候给指针初始化
int a = 1;
int *p; 指针悬空
p = &a;
方法二:定义指针的时候先让指针指向NULL,后面再改变指针的指向
int a = 1;
int *p = NULL; //NULL里面是不可以存放任何数据的 不太让指针悬空
p = &a;
方法三:定义指针的时候给指针分配堆空间(动态分配内存)
栈空间(栈内存)
堆空间(堆内存)
内存泄漏:一直申请堆内存,但是都没有释放,导致堆内存越来越少,最后没有可以使用的堆内存
堆内碎块(磁盘碎片):频繁地分配和释放不同大小的堆空间会导致堆内碎块
#include <stdlib.h>
void *malloc(size_t size); //分配堆空间
返回值:分配的堆空间的首地址
参数:size --》需要分配多少字节的堆空间
void free(void *ptr); //释放堆空间
参数:ptr --》之前申请的堆空间首地址
void *calloc(size_t nmemb, size_t size); //申请分配nmemb块内存,每个的大小是size
返回值:分配堆空间的首地址
参数:nmemb --》申请多少块内存区域
size --》申请的内存区域每一个的大小
void *realloc(void *ptr, size_t size); //重新分配堆内存
返回值:分配的新的堆空间的首地址
参数:ptr --》原来分配的堆空间首地址
size --》新的堆空间的大小
int *p = malloc(10*sizeof(int))
int *q = calloc(10,sizeof(int)); //申请10个内存,每个大小是4个字节
free(p);
NULL介绍(空指针)
#define NULL 0x0000 0000
NULL表示的内存中的0x0000 0000
4.指针的大小
跟指针的类型没有任何关系,跟操作系统的位数有关
在32位系统上,所有的指针大小都是4个字节
在64位系统上,所有的指针大小都是8个字节
指针用来存放变量的首地址,而在64位系统上所有的地址长度大小都是64位
5.指针的运算
- 取地址
- 解引用
- 加法
一个指针加上另外一个指针没有任何实际意义
用指针去加一个整数,指针做加法,加的是类型的大小
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int n1 = 11;
int n2 = 22;
double n3 = 12.3;
char n4 = '@';
int *p1 = &n1;
int *p2 = &n2;
double *p3 = &n3;
char *p4 = &n4;
//p1+p2; //编译没有错,但是毫无意义,就是把地址值当成数字相加
//有意义:指针加法
//printf("p1原本存放的地址值是: %p\n",p1);
//printf("p2原本存放的地址值是: %p\n",p2);
printf("p3原本存放的地址值是: %p\n",p3);
printf("p4原本存放的地址值是: %p\n",p4);
//printf("p1+1存放的地址值是: %p\n",p1+1);
//printf("p2+3存放的地址值是: %p\n",p2+3);
printf("p3+1存放的地址值是: %p\n",p3+1);
printf("p4+1存放的地址值是: %p\n",p4+1);
return 0;
}
/*
执行结果:
p3原本存放的地址值是: 0x7fffce42acf0
p4原本存放的地址值是: 0x7fffce42ace7
p3+1存放的地址值是: 0x7fffce42acf8
p4+1存放的地址值是: 0x7fffce42ace8
*/
- 减法
一个指针减去另外一个指针有实际意义
公式: p2-p1结果是: (p2-p1)/sizeof(指针类型)
本质上p2-p1表示p2和p1之间间隔了多少个元素
用指针去减一个整数,指针做减法,减的是类型的大小
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
//一个指针减去另外一个指针有实际意义
//跟数组结合,指针减去另外一个指针有意义
int buf[5]= {33,44,55,66,777};
double buf1[5] = {56.3,56.9,73.1,58.9,3656.8};
char buf2[10] = "nihao";
//定义两个指针,分别指向数组不同元素的首地址
int *p1 = &buf[0];
int *p2 = &buf[4];
printf("p1存放的地址值是: %p\n",p1);
printf("p2存放的地址值是: %p\n",p2);
printf("p2-p1 is: %ld\n",p2-p1);
printf("p1-1存放的地址值是: %p\n",p1-1);
// double *p1 = &buf1[0];
// double *p2 = &buf1[4];
// printf("p1存放的地址值是: %p\n",p1);
// printf("p2存放的地址值是: %p\n",p2);
// printf("p2-p1 is: %ld\n",p2-p1);
// printf("p1-1存放的地址值是: %p\n",p1-1);
// char *p1=&buf2[0];
// char *p2=&buf2[8];
// printf("p1存放的地址值是: %p\n",p1);
// printf("p2存放的地址值是: %p\n",p2);
// printf("p2-p1 is: %ld\n",p2-p1);
// printf("p1-1存放的地址值是: %p\n",p1-1);
return 0;
}
- ++和解引用配合使用
*p++ 先解引用p,然后p加1
(*p)++ 先解引用p,然后把解引用的值加1
*++p 先将++用于p,然后解引用
++*p 先解引用,然后把结果加1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int buf[5]={345,75,576,45,78};
int *p = &buf[0];
//*p++; //拆分两步 第一步 *p 第二步:p=p+1
//printf("没有计算之前p存放的地址: %p\n",p);
//printf("验证*p++计算过程: %d\n",*p++);
//printf("计算之后p存放的地址: %p\n",p);
//(*p)++; //拆分两步 第一步 *p 第二步:*p=(*p)+1
// printf("没有计算之前p存放的地址: %p\n",p);
// printf("验证(*p)++计算过程: %d\n",(*p)++);
// printf("计算之后p存放的地址: %p\n",p);
// printf("buf[0] is: %d\n",buf[0]);
//*++p; //拆分两步 第一步 p=p+1 第二步:*p
// printf("没有计算之前p存放的地址: %p\n",p);
// printf("验证*++p计算过程: %d\n",*++p);
// printf("计算之后p存放的地址: %p\n",p);
//++*p; //拆分两步 第一步 *p 第二步: ++第一步的值
printf("没有计算之前p存放的地址: %p\n",p);
printf("验证*++p计算过程: %d\n",++*p);
printf("计算之后p存放的地址: %p\n",p);
printf("buf[0] is: %d\n",buf[0]);
return 0;
}
- 指针比较大小
用来判断两个指针是否指向同一块内存区域
#include <stdio.h>
int main()
{
int n1 = 45;
int n2 = 45;
int *p1 = &n1;
int *p2 = &n2;
if(p1 == p2) //判断p1和p2存放的地址值是否一样
{
printf("地址相同!\n");
}
else
{
printf("地址不相同!\n");
}
if(*p1 == *p2) //判断p1和p2地址中对应的值是否相同
{
printf("值是相同的!\n");
}
if(p1 > p2)
printf("n1存储的位置是在n2的靠后面!\n");
else if(p1 < p2)
printf("n1存储的位置是在n2的靠前面!\n");
return 0;
}
6.void *类型的指针
万能指针,通用类型的指针
特点:可以跟任何其它类型的指针强转
比如:应用举例:C语言的库函数的使用
例子一:void *作为函数的返回值
void *malloc(size_t size); //通用性,随便你定义什么指针都可以
char *malloc(size_t size); //瞎编的,局限性,返回char类型指针
int *malloc(size_t size); //瞎编的,局限性,返回int类型指针
char *p=(char *)malloc(10);
double *q=(double *)malloc(80);
例子二:void *作为函数的参数
ssize_t read(int fd, void *buf, size_t count);
参数:buf --》存放读取到的内容
你从文件读取数据,是不是有可能读取到各种各样不同类型的数据
比如:读取文件数据是个整数--》应该保存到整型变量的地址中
int a;
read(int fd, &a, size_t count);
读取文件数据是个字符串--》应该保存到数组
char buf[20];
read(int fd, buf, size_t count);
读取文件数据是个小数--》应该保存到浮点型变量的地址中
float b;
read(int fd, &b, size_t count);
总结:好处在于把指针类型的选择权留给了程序员
二、二级指针
1.概念
也是个指针,该指针用来存放另外一个一级指针在内存中的地址
二级指针解引用一次,变成一级指针
2.定义二级指针
int a=88;
int *p=&a;
int **q=&p;
char b='#';
char *p=&b;
char **q=&p;
3.使用二级指针
*q : 二级指针解引用一次,结果是*&p-->p
**q: 二级指针解引用两次,结果是**&p-->*p
4.二级指针的运算(考察最多的是一级指针的运算,偶尔会出现二级指针的运算)
加法:二级指针加一个整数
char **p; p+1;
int **q; q+1;
减法:二级指针减一个整数
二级指针做加减法,加减的是指针(一级指针)的大小(64位系统加减8的倍数 32系统加减4的倍数)
5.数组名需要注意的地方
第一点:数组名是个指针,但是sizeof求大小又不当成指针
int buf[10];
sizeof(buf); //40字节
第二点:数组名前面取地址,是个数组指针
char buf[10]="hello";
char *p1=buf;
char **q1=&p1; //写成char **q1=&buf;
第三点: 数组名不可以作为左值,但是普通指针可以左值
char buf[10]="hello"
char *p=buf;
p=p+1; //没有问题
buf=buf+1; //有问题