在定义时:* 只是一个标识,标识是指针变量
在使用时:p 指针变量--->地址
*p 取出地址中的数据
一、指针的概念
1.1 指针变量的定义:数据类型 * 变量名;
char *p;//sizeof(p) = 8;
int *q;//sizeof(q) = 8;
float *w;sizeof(w) = 8;
指针指向空间类型:
char *p; //p指向的空间类型:char
int *q; //q指向的空间类型:int
float *w; //w指向的空间类型:float
① 一级指针
int *p = NULL;
指针大小:sizeof(p) = 8字节
变量名:p;
p的类型:int *;(除去变量名,剩下的就是变量类型)
类型含义:该数据类型,用来定义指针变量,指针变量,用来存储地址。
类型大小:规定,指针大小,在我们64位系统下,固定为8字节;
(64位的二进制数,占8个字节(1字节=8位,64位,就是8字节))
p指向的空间:int类型;
指针偏移:指针+1,地址偏移指针指向空间的大小
p+1;//地址偏移4字节
② 二级指针
含义:指针指向的空间,存放的内容仍然是一个地址。
定义方式:数据类型 **变量名;
int **q = NULL;
指针大小:sizeof(q) = 8字节
变量名:q;
q的类型:int **;
q指向的空间:int *类型;(int *类型8字节)
q+1;//地址偏移 8 字节
1.2 指针初始化
1》int *p = NULL; //NULL就是0号地址
2》int a = 200; int *p = &a;
int *p = NULL;p = &a;//&a:int *
3》野指针:int *p ;//只定义,不赋值,p是随机数 (要避免野指针的出现)(不赋值,容易出现占用内存的情况)
*p = 200;//错误的 //p是存放地址的一个空间,然后解指针,又将200赋值给一个不知地址在哪的空间中
解指针:通过地址,操作地址所对应的空间(来得到或者改变该地址所对应的空间的值)
*p:得到 p 所指向的空间内容
举例:
int num;
int *p = #
操作*p等价于操作num;
4》一般用法
int a = 0;//定义一个普通变量a
int *p = &a;//定义一个指针变量 p,p 中存放的是 a 的地址,也就是 p 指向的是 a;
另外 &a 的类型:a为 int 类型,&a 就为 int * 类型
关于 & 和 * 运算符的进一步说明:
* &:优先级同为2级, 结合性:从右向左。
* & a:先进行&a得a的地址,再对a的地址进行* 运算
& * a:先*a得a的值,在取地址 <-> &a
1.3 指针数组偏移
指针+1,地址偏移指针指向空间的大小(与指针指向的空间类型有关)
int a;
int *p = &a;
p++;//p = p+1;//int型地址偏移4字节
指针和指针的减法(使用不多)
两个指针变量在一定条件下, 可进行减法运算。设 p, q 指向同一数组, 则 p-q 的绝对值表示 p 所指对象与 q 所指对象之间的元素个数
指针 和指针的加法:指针不能和指针相加。
① int num[5] = {1,2,3,4,5};
num+1; //以元素为单位偏移,偏移4字节
如果想要得到2,如何得到? *(num+1)
//num表示首元素地址:num[0];
//num[0]:int类型;
//&num[0]:int *类型;
② char buf[5] = “1234”;
buf+1; //以元素为单位偏移,偏移1字节
如果想要得到’2’,如何得到? *(buf+1)
③ char buf[5] = {‘a’,’b’,’c’,’d’,’e’};
&buf+1; //以整个数组为单位偏移,偏移5个字节,偏移到e的后边
char buf[5] = {‘a’,’b’,’c’,’d’,’e’}; 数组取元素:buf[1],*(buf+1)
int num[5]; //num:变量名,数组名
两个性质:
- 是数组的首元素地址
- 是整个数组的首地址
把数组名当作地址的时候,以上两个性质有区别:
指针偏移:
num: //num表示首元素地址,以元素为单位偏移
num+1;偏移一个元素的大小
&num: //&num表示整个数组的首地址,偏移整个数组的大小
&num+1:偏移整个数组大小:
1.4 指针指向一维数组
char buf[5] = {‘a’,’b’,’c’,’d’,’e’};
char *p = buf;
//buf表示首元素地址,
//首元素:buf[0] -- char
//首元素地址:&buf[0] -- char *
取元素:buf[2],*(buf+2),*(p+2),p[2];
1.5 指针指向二维数组
① int num[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
num
//表示数组首元素地址,偏移:以元素为单位偏移
//首元素:num[0] int [4]
//首元素地址:&num[0] int (*)[4]
num + 1 //指向 num[1];
*(num+1) //得到 num[1];
num[1]:表示一维数组,不加&,以元素为单位偏移,元素:num[1][0]:int ,&num[1][0]:int *
取元素:num[i][j], *(*(num+i)+j)
&num:&num+1//加整个数组的大小:4 *3*4
② int num[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p)[4] = NULL;
p = num;//表示首元素地址
//首元素:num[0] -- int [4]
//首元素地址:&num[0] -- int (*) [4]
取元素:p+1//偏移4 * 4字节
取元素:*(*(p+i)+j),p[i][j],num[i][j],*(*(num+i)+j),*(p[i]+j)
二维数组取元素:注解
int num[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p)[4] = num;
num[1][2];
*(*(num+1) +2)
解释:num不加&,指针偏移以元素为单位,二维数组的元素大小为int [4],num+1,,偏移就是16字节,就指向了二维数组的第二个元素的首地址,依旧是地址,通过解指针可以得到地址中的内容,即*(num+1)可以得到num[1],*(num+1)等价于num[1],但是num[1]依旧是一个数组,此时num[1]就是一个一维数组,num[1]为数组名,数组名不加&,偏移以元素为单位,num[1]的元素为int 类型,因此偏移以int为单位,num[1]+2能得到上面黄色的7的地址,再解指针,*(*(num+1) +2)就能得到7这个值!
1.6 指针数组
int num[5]; //num:变量名,数组名
两个性质:
- 是数组的首元素地址
- 是整个数组的首地址
把数组名当作地址的时候,以上两个性质有区别:
指针偏移:
num://num表示首元素地址,以元素为单位偏移
num+1;偏移一个元素的大小
&num://&num表示整个数组的首地址,偏移整个数组的大小
&num+1:偏移整个数组大小
1.7 C语言的五大区
- 堆区:预留给开发人员的,必须手动开辟,用函数开辟空间,malloc,手动释放free;
- 栈区:由编译器自动开辟的,自动回收 -- 局部变量,函数形参
- 全局区/静态区:全局变量,由static修饰的变量(成为静态变量)
- 常量区:不能改变,只读
- 代码段:存放二进制代码
指针指向常量区:
char *p1 = “123456”;
char *p2 = “asdf”;
char *p3 = “qwe”;
char *arr[3] = {“123456”,“asdf”,“qwe”};
*p 打印出来是1;
*(p+3) 打印出来是4;
*p = 30;//错误的,常量区不能被改变
1.8 const:修饰变量,使其只读
1》修饰普通变量:
const int num = 100;
num = 200;
2》修饰指针(在*p的前边,不能改变内容,在p前边,不能改变指向)
①const int *p:修饰*p:不能通过p改变p指向空间的内容
②int * const p:修饰p:p的指向不能改变
③int const *p:修饰*p:不能通过p改变p指向空间的内容
④const int *const p:修饰p和*p:p的指向不能改变,不能通过p改变p指向空间的内容