12-指针02
指针
回顾
1. 指针的定义
- 指针也是一种数据类型,指针变量也是一种变量
- 指针变量指向谁,就把谁的地址赋值给指针变量
- “*”操作符操作的是指针变量指向的内存空间
#include <stdio.h>
int main()
{
int a = 0;
char b = 100;
printf("%p, %p\n", &a, &b); //打印a, b的地址
//int *代表是一种数据类型,int*指针类型,p才是变量名
//定义了一个指针类型的变量,可以指向一个int类型变量的地址
int *p;
p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
printf("%d\n", *p);//p指向了a的地址,*p就是a的值
char *p1 = &b;
printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值
return 0;
}
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。
2.指针大小
- 使用sizeof()测量指针的大小,得到的总是:4或8
- sizeof()测的是指针变量指向存储地址的大小
- 在32位平台,所有的指针(地址)都是32位(4字节)
- 在64位平台,所有的指针(地址)都是64位(8字节)
int *p1;
int **p2;
char *p3;
char **p4;
printf("sizeof(p1) = %d\n", sizeof(p1));
printf("sizeof(p2) = %d\n", sizeof(p2));
printf("sizeof(p3) = %d\n", sizeof(p3));
printf("sizeof(p4) = %d\n", sizeof(p4));
printf("sizeof(double *) = %d\n", sizeof(double *));
1.野指针
野指针的指向的地址是随机的,不能操作野指针
指针指向的地址是系统分配的,定义变量的时候向系统申请,系统进行分配
指针初始化的地址不能由自己给出
#include <stdio.h>
#include <stdLib.h>
#include <string.h>
int main()
{
// 野指针是指没有初始化(赋值)的指针
int *p; // 定义一个指针变量p
// *p 指向的是指针变量存储的地址对应的空间
// 指针变量p没有存储地址,所以p中存储的地址是随机的(指针的指向是随机的)
// 将这样没有初始化的指针称之为野指针
*p = 200;
system("pause");
return 0;
}
2.空指针
- int *p = NULL -- 表明定义了一个指针,并将其初始化为NULL,就是指向0所在的地址0x0000 0000;
- 零指针初始化所指向的位置不用于存储其他的地址,只是表明该指针进行了初始化,可以进行使用的标志
- 习惯:将指针使用完之后,置为NULL,这样在使用指针的时候,判断指针指向为NULL,那么该指针可以进行使用,如果该指针不为NULL,表明该指针不能使用(该指针未初始化或者正在使用)
#include <stdio.h>
#include <stdLib.h>
#include <string.h>
int main()
{
// 定义一个整型变量,如果不知道存储什么值,可以将a初始化为0
int a = 0;
// 定义一个指针变量p,如果不知道指针存储谁的地址可以将其
// 初始化为0指针,将指针指向0地址,0x00000000 -- NULL
int *p = NULL;
// *p = 200; // 也不对,p保存了0x000000000,0x00000000是进行初始化的标记,该地址指向的空间不存储值
// NULL是指针初始化的标记,指针p指向NULL,表示该指针初始化了,该指针可以使用,
// 如果指针 p !== NULL 表明该指针没有初始化,不能使用(或者该指针指向其他地址空间)
// 好习惯:将指针用完之后将指针变量指向NULL
if(p == NULL)
{
p = &a;
}
system("pause");
return 0;
}
野指针和空指针总结
指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。
int a = 100;
int *p;
// p中保存的地址是a的值
p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义
// p中保存的地址是任意给的
p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义
*p = 1000; //操作野指针指向未知区域,内存出问题,err
但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。
int *p = NULL;
NULL是一个值为0的宏常量:
#define NULL ((void *)0)
3. 万能指针
- 可以保存任何地址的指针
- void *p
#include <stdio.h>
#include <stdLib.h>
#include <string.h>
int main()
{
// 万能指针,可以保存任意的地址
int a = 10;
void *p;
p = (void *)&a;
// printf("%d\n",*p); // 编译出错,指针的类型是void *,取出的长度为sizeof(void)
// sizeof(void) -- 编译器不知道多大 -- 编译出错
// p指针变量保存的是a的地址,p的类型是void *类型
// 想要取出p中的变量 -- 需要将p指针类型从void * 转为int *
printf("%d\n",*(int *)p); // *(地址) -- 将地址进行强转 *((int *)地址)
// 不可以定义void类型的变量
// 但是可以定义void *类型,因为指针都是4个字节
// void b; 出错
void *b;
system("pause");
return 0;
}
- 通过万能指针取内容的时候,需要将万能指针类型转变为取值的指针类型
4.const修饰的指针变量
- const -- 修饰变量a之后,不能通过a变量名修改a在内存中的值
- 但是可以通过地址进行修改a的内容
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// const修饰变量a,不能再通过a修改a内存里面的内容
// 但是可以同过地址进行修改a的值
const int a = 10;
int *p = &a;
*p = 100;
printf("%d\n",a); // 100
system("pause");
return 0;
}
- const int p = &a -- const修饰,不能通过*p修改指向空间的内容
- int * const p = &a -- const修饰指针变量p,p中所保存的地址不可被修改
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a = 10;
int b = 20;
// const修饰的是*还是变量p?
// 这里修饰的是*
const int *p = &a; // 不能通过*p修改p所指向的空间的内容
// *p = 100; const修饰*之后,不能通过p修改p指向的空间的内容
// const 修饰指针变量p
// p保存的地址不可以被修改
int * const p = &a;
p = &b; // p保存的地址不可以被修改
// p本身的指向不能改变,不同通过*p修改p所指向的空间的内容
const int *const p = &a;
system("pause");
return 0;
}
5.多级指针
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a = 10;
// 定义指针变量p保存a的地址
// *p int a int *p
int *p = &a;
// 想要定义一个变量保存p的地址
// int *q int *p int *(*q)
int **q = &p;
// 通过q得到a的值
// 通过q得到a的地址 -- *q 表示p的地址的内容就是a的地址
// **q -- 取a地址中的内容
// **q = *(*q) = *(p) = a
// **q = *(*q) = *(&a) = a
// *(地址) 表示取地址中的内容
// *与&相遇 -- 相互抵消
// 定义变量保存q的地址
// *k int **q int ***key
int ***k = &q;
// ***k = *(**K) = *(*(*k)) = *(*(&p)) = *p = *(&a) = a
int **********g;
int ***********h = &g; // 保存指针类型变量地址,多一个*
// ********** *h = ********** *(&g) = *********g
//分析
// *与符号结合,表示变量k是一个指针变量
// k是一个变量
// k的类型
// k用来保存地址的类型,将k与最近的*进行拖黑,剩下的就是保存地址的类型
system("pause");
return 0;
}
int a = 10;
int *p = &a; //一级指针
*p = 100; //*p就是a
int **q = &p;
//*q就是p
//**q就是a
int ***t = &q;
//*t就是q
//**t就是p
//***t就是a
6. 指针与数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
// a 数组名 首元素的地址 - a = &a[0]
// 知道首元素地址 --> a+1 = &a[1]
// 定义指针保存首元素地址 -- 首元素地址类型是int
// a 数组名只能指向首元素地址,通过指针可以指向数组中其他元素地址
int *p = a; // &a[0]
// *p -- 可以取a[0]的值
int *q = &a[5];
// *q 可以取a[5]的值
system("pause");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
// a 数组名 首元素的地址 - a = &a[0]
// 知道首元素地址 --> a+1 = &a[1]
// 定义指针保存首元素地址 -- 首元素地址类型是int
// a 数组名只能指向首元素地址,通过指针可以指向数组中其他元素地址
// 操作数组元素
int *p = a;//指针p保存的是首元素地址&a[0]
for(int i = 0;i<sizeof(a)/sizeof(a[0];i++)
{
// 可以遍历数组的元素
// printf("%d\n",a[i])
printf("%d\n",*(p+i)); // p变量保存的是首地址,地址+1跳过一个元素,4个字节
*(p+i) = i; //数组元素赋值
}
system("pause");
return 0;
}
例题
int main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int *)(&a+1);
printf("%d %d\n",*(a+1),*(ptr-1)); // 2 5
}
- 数组地址加一,跨一个数组
- 元素地址加一跨一个元素
- 指针加一跨元素类型步长
- int *p -- 步长就是sizeof(int)\
- 要得到内存的数据,就要先得到数据的地址 -- *(地址)得到的是地址中的内容
- *(指针变量) -- 指针变量存储的是地址 -- *(地址)-- 得到指针变量存储的地址的内容
7.指针的运算
- 指针的加减运算与步长有关
- 指针变量相加没有意义
- 指针变量相减可以得到之间的数组元素
- 两个指针的变量相减,需要保证指针的类型一致
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = a;
// &a+1跨整个数组
// &a+1的步长是整个数组,想要前一个元素的地址,在减去一个int类型的变量长度
// 将&a+1地址转变为int * -- 表示其步长为sizeof(int)
// (int *)(&a+1) - 1 得到数组最后一个元素地址
// 得到数组的最后一个元素,(int *)(&a+1)
int *q = (int *)(&a+1)-1;
// 指针相减 -- 得到之间元素的个数
printf("%d\n",q-p); // 9
printf("%d\n",*(p+3)); // 取第四个元素的值,4
// c语言中,两个指针变量相加是没有意义的
printf("%d\n",p+q); // 报错
system("pause");
return 0;
}
8.[]使用
- []不一定只给数组使用
- [] == *()
- p[0] == *(p+0);
#include <stdio.h>
int mian()
{
// [] 不是数组的专属
// int a[10] = {1,2,3,4,5,6,7,8,9,10};
int a = 10;
int *p = &a;
// [] == *()
// p[0] == *(p+0);
p[0] = 100;
// 这里a = 100,表示p[0] = 100,对其内容进行操作
printf("a = %d\n",a); // a = 100
system("pause");
return 0;
}
数组中的使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = a;//指针p保存的是首元素地址&a[0]
for(int i = 0;i<sizeof(a)/sizeof(a[0];i++)
{
// 可以遍历数组的元素
// printf("%d\n",a[i])
// printf("%d\n",*(p+i)); // p变量保存的是首地址,地址+1跳过一个元素,4个字节
// printf("%d\n",p[i]); // p[i] == *(p+i);
printf("%d\n",*(a+i)); // a[i] == *(a+i); a是首元素地址,加几跨过几个元素
}
system("pause");
return 0;
}
9.指针数组
指针数组,它是数组,数组的每个元素都是指针类型。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
// 保存三个int类型数据的指针需要定义三个指针
// 如果数据多了之后就比较麻烦
// int *p = &a int *q = &b int *k = &c
// 需求:数组中的每一个元素都是指针(地址)
// 指针变量的类型 数组名[长度] = {初始化值}; 初始化的是地址
int *num[3] = {&a,&b,&c};
// int *占4个字节
printf("%d\n",sizeof(num)); // 12
// 用指针数组或区域元素
// 打印a的地址
printf("a:\n",*num[0]);
printf("b:\n",*num[1]);
printf("c:\n",*num[2]);
for(int i = 0;i<sizeof(num)/sizeof(num[0]);i++)
{
printf("a:\n",*num[i]);
}
// 定义指针保存num首元素的地址
// num[0] -- 是int *类型
// 保存首元素地址 -- 保存int * 类型的元素 -- 多一级* -- 二级指针
// int **
int **k = num;
// 通过k将a的值取出
// **k
// 得到b的值
// k+1得到的是&num[1]
// *[k+1] -- 得到b的地址
// **[k+1] -- b元素
system("pause");
return 0;
}