1.指针的基础
内存地址:
字节: 字节是内存容量的一个单位, byte , 一个字节byte 有 8个位 bit
地址: 系统位了方便区分每一个字节的数据, 而对内存进行了逐一编号, 而该编号就是内存地址
基地址:
单字节的数据: char 它所在地址的编号就是该数据的地址
多字节的数据:int 它拥有4个连续的地址的编号 , 其中地址值最小的称为该变量的地址
取地址符号:
每一个变量其实都对应了片内存, 因此都可以通过 & 取地址符号把其地址获得。
int a;
char c;
printf("a的地址为:%p\n" , &a);
printf("c的地址为:%p\n" , &c);
//运行结果:a的地址为:0x7fffd5595d84 c的地址为:0x7fffd5595d83
注意:
①虽然不同的数据类型所占用的内存空间不同, 但是他们的地址所占用的内存空间(地址的大小= 指针的大小)是恒定的, 由系统的位数来决定 32位 / 64位
②不同的地址他从表面上看似乎没有什么差别,但是由他们所代表的内存的尺寸是不一样的(由内存中所存放的数据类型相关),因此我们在访问这些地址的时候需要严格区分它们的逻辑关系。
指针的概念:
&a 就是a的地址 , 实质上也可以理解为他是一个指针 指向 a的地址。
专门用来存放地址的一个变量,因此指针的大小是恒定的 ,由系统的位数来决定。
指针的定义语法:
int a ; // 定义一片内存名字叫 a , 约定好该内存用来存放 整型数据
int * p ; // 定义一片内存名字叫 p , 约定好该内存用来存放 整型数据的地址
char * p1 ; // 定义一片内存名字叫 p1 , 约定好该内存用来存放 字符数据的地址
double * p2 ; // 定义一片内存名字叫 p2 , 约定好该内存用来存放 双精度数据的地址
注意:
指针的类型,并不是用来决定该指针的大小,而是用来告诉编译器如果我们通过来指针来访问内存时需要访问的内存的大小尺寸。
指针的赋值以及初始化:
int a = 100 ;
int * p = &a ; // 定义并初始化
double d = 1024.1234 ;
double * p1 = &d ;// 定义并初始化
float f ;
float * p2 ;// 定义并没有初始化
p2 = &f ; // 给指针赋值
注意:
不同类型的指针,应该用来指向与其相对应的类型的变量的地址。
指针的索引:
通过指针获得它所指向的数据(解引用/取目标)
int a = 100 ;
int * p = &a ;
*p = 250 ; // *p <==> a
printf("*p:%d\n" , *p) ;
2.野指针
概念: 指向一块未知内存的指针, 被称为野指针。
int *p;
危害:
①引用野指针的时候,很大概率我们会访问到一个非法内存,通常会出现段错误(Segmentation fault (core dumped))并导致程序崩溃。
②更加严重的后果,如果访问的时系统关键的数据,则有可能造成系统崩溃
产生原因:
① 定义时没有对他进行初始化
②指向的内存被释放,系统已经回收, 后该指针并没有重新初始化
③指针越界
如何防止:
①定义时记得对他进行初始化
②绝对不去访问被回收的内存地址, 当我们释放之后应该重新初始化该指针。
③确认所申请的内存的大小,谨防越界
3.空指针
在很多的情况下我们一开始还不确定一个指针需要指向哪里,因此可以让该指针先指向一个不会破坏系统关键数据的位置, 而这个位置一般就是NULL (空)。因此指向该地址的指针都称之为空指针。
概念:
空指针就是保存了地址值为零的一个地址, 也就零地址NULL
int * p1 = NULL ; // 定义一个指针, 并初始化为空指针( 指向 NULL )
*p1 = 250 ; // 段错误 , 该地址不允许写入任何东西
printf("%p -- %d \n" , NULL , NULL ); // (nul--0)
4.指针运算
指针的加法:意味着地址向上移动若干个目标 (指针的类型)
指针的减法:意味着地址向下移动若干个目标 (指针的类型)
long l ;
long *p = &l ;
int i ;
int * p1 = &i;
printf("p:%p\n" , p );
printf("p+1:%p\n" , p+1 );
printf("p1:%p\n" , p1 );
printf("p1+1:%p\n" , p1+1 );
// 运行结果:
p:0x7fffcb862610
p+1:0x7fffcb862618
p1:0x7fffcb86260c
p1+1:0x7fffcb862610
注意:
指针在加减运算的过程中, 加/减的大小取决于该指针他自己的类型,与它所执行的数据实际的类型没有关系。
5.char型指针
从本质上讲,字符指针其实就是一个指针而已,只不过该指针用来指向一个字符串、字符串数组
char* msg = "Hello World";
6.多级指针
① 如果一个指针p1 它所指向的是一个普通变量的地址,那么p1就是一个一级指针
② 如果一个指针p2 它所指向的是一个指针变量的地址,那么p2就是一个二级指针
③ 如果一个指针p3 它所指向的是一个指向二级指针变量的地址,那么p3就是一个三级指针
int a = 100 ;
int * p1 = &a ; // 那么p1就是一个一级指针
int ** p2 = &p1 ; // 那么p2就是一个二级指针
int *** p3 = &p2 ; // 那么p3就是一个三级指针
7.指针的万能拆解方法
对于任何的指针都可以分为两部分:
第一部分: 说明他是一个指针 (*p)
第二部分: 说用它所指向的内容的类型 (*p)以外的东西
char * p1 ; // 第一部分: * p1 , 第二部分 char 说明p1 指向的类型为char
char **p2 ; // 第一部分: * p2 ,第二部分 char * 说明p2 指向的类型为char *
int **p3 ; // 第一部分: * p3 ,第二部分 int * 说明p3 指向的类型为int *
char (*p4) [3] ; // 第一部分: * p4 , 第二部分 char [3] , 说明p4 指向一个拥有3个元素的char 数组
char (*p5) (int , float) ; // 第一部分: * p5, 第二部分char (int , float) , 说明
// 说明该指针指向一个 拥有char类型返回, 并需要 一个int 和 float 参数的函数
void *(*p6) (void *); //第一部分: * p6, 第二部分 void * (void *)
// 说明p6 指向一个 拥有 void * 返回并需要一个void * 参数的函数 函数指针)
总结:
① 以上指针 p1 p2 p3 p4 p5 p6 本质上都是指针,因此它们的大小都是 8字节(64位系统)
② 以上指针 p1 p2 p3 p4 p5 p6 本质上都是指针, 唯一的不容是它们所指向的内容的类型不同
8.void型指针
概念: 表示该指针的类型暂时是不确定
要点:
void 类型的指针,是没有办法直接索引目标的。必须先进行强制类型转换。
void 类型指针,无法直接进行加减运算。
void关键字的作用:
①修饰指针,表示该指针指向了一个未知类型的数据。
②修饰函数的参数列表, 则表示该函数不需要参数。
③修饰函数的返回值,则表示该函数没有返回值
void * p = malloc(4) ; // 使用malloc 来申请 4个字节的内存, 并让p来指向该内存的入口地址
*(int *)p = 250 ; // 先使用(int*) 来强调p是一个整型地址 ,然后再解引用
printf("*p:%d\n", *(int*)p);// 输出时也应该使用对应的类型来进行输出
*(float*)p = 3.14 ;
printf("*p:%f\n", *(float*)p);
int * a ;
char * b ;
float * f ;
void * k ;
注意:
以上写法 void * p , 在实际开发中不应该出现。以上代码只是为了说明语法问题。
9.const指针
const修饰指针有两种效果:
(1)常指针 修饰的是指针本身, 表示该指针变量无法修改、
char * const p ;
char arr [] = "Hello" ;
char msg [] = "Even" ;
char * const p = arr ;
// p = msg ; // p 被const 所修改,
//说明P是一个常量 ,他的内容(所指向的地址)无法修改
*(p + 1 ) = 'E' ; // p所指向的内容是可以通过p 来修改 (只要保持P所指向的地址不变即可)
printf("%s\n" , p );
(2)常目标指针 修饰的是指针所指向的目标,表示无法通过该指针来改变目标的数据
char const * p ;
const char * p ;
char arr [] = "Hello"
char msg [] = "Even" ;
const char * p1 = arr ;
p1 = msg ; // p1 的指向是可以被修改的
// *(p1+1) = 'V' ; // 常目标指针, 不允许通过该指针来它所指向的内容
*(msg+1) = 'V' ; // 虽然p1不能修改所指向的内容, 但是内容本身是可以被修改的
printf("%s\n" , p1 );
总结:
① 常指针并不常见。
② 常目标指针,在实际开发过程中比较常见,用来限制指针的权限为 只读