1、内存地址:
- 每个地址占一个字节,系统为了区分每一个字节,给它们逐一编号,称为内存地址,简称地址。
- 字节为内存的容量单位,一个字节8位。
2、基地址
- 单字节数据:单个字节数据而言,其地址就是基地址;
- 多个字节数据:多个字节数据而言,其编号最小的地址是基地址;
3、取地址(&):
- 每个变量都有一个内存,都可以通过&来获取其地址信息;
int a=100;
printf("%p\n",&a);
输出:
a的地址
4、指针的定义:
- 指针变量存放的数据类型是指针变量的地址;
- int *p1; //用于存储 int型数据的地址,p1被称为int型指针;
- char *p2; //用于存储char型数据的地址,p2被称为char型指针;
- double *p3; //用于存储double型数据的地址,p3被称为double型指针;
5、指针的赋值:
#include <stdio.h>
int main()
{
int a=100;
int d=11;
int e=111;
int b[4]={1,2,3,4};
char c='a';
char *p=&c;
int *p1=&a; //p1指向a的地址
int *p2=&b[3];
int *p2_1=b; //将数组b的首元素地址赋给指针p2_1
int *p3;
p3=&a;
int *p4=&d;
*p4=e; //指针的索引,将e的值赋给指针p4所指向的变量d
printf("%c\n",*p); //p指向c的地址,在解引用a的值
printf("%p\n",p1);
printf("%d\n",*p2);
printf("%d\n",*p2_1);
printf("%d\n",*p3);
printf("%d\n",*p4);
return 0;
}
输出:
6、特殊指针:
①野指针:指向一个未知区域的指针,称为野指针。野指针是危险的;
危险:
- :引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault);
- :引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果;
产生原因:
- 指针定义之后,未初始化;
- 指针所指向的内存,被系统回收;
- 指针越界;
如何防止:
- 指针定义时,及时初始化(不明确时指向NULL);
- 绝不引用已被系统回收的内存(回收后令指针指向NULL);
- 确认所申请的内存边界,谨防越界;
7、空指针(NULL):
空指针是保存零地址的指针;
为了避免野指针,通常可将指针指向零地址;
int *p=NULL; //指针指向零地址;
8、指针的运算:
- 两个指针相加没有意义,两个指针相减可以得到两个指针地址的偏移量;
int a,b;
int *p=&a;
int *q=&b;
q+p; //得到一个新地址,但没有实际意义
q-p; //得到两个地址的距离
- 指针加法(自增或加上某个数值)意味着地址向上移动若干个目标(数据类型的字节单位<步长>)
- 指针减法(自减或减去某个数值)意味着地址向下移动若干个目标(数据类型的字节单位<步长>)
#include <stdio.h>
int main()
{
int a[10]={1,2,3,4,5,6,7};
int *p=&a[0]; //指针p为数组a[0]的地址
int *p1=&a[5];
printf("p:%d\n",*(p+1)); //指针p指向a[1]的地址在解引用得a[1]的值
printf("p1:%d\n",*(p1-2));
return 0;
}
输出结果:
9、指针语法:
- 任意的指针,不管有多复杂,其定义都由两部分组成;
- 第1部分:指针所指向的数据类型,可以是任意的类型
- 第2部分:指针的名字
char (*p1); //第2部分 :* p1;第1部分:char; 字符指针
char *(*p2); //第2部分 :* p2;第1部分:char *; 指向指针的指针(二级指针)
char **(*p3); //第2部分 :* p3;第1部分:char **; 三级字符指针
char (*p4)[3]; //第2部分 :* p4;第1部分:char [3];数组指针
char *p5[3]; //第2部分 :p5[3];第1部分:char *;数组存储的都是指针地址,指针数组
char (*p6)(int ,char ); //第2部分 :* p5;第1部分:char(int,float);函数指针
①一维数组,内存地址连续,数组名可当指针使用;
int a[10]={1,2,3,4,5,6};
int *p=a;
printf("p:%d\n",p[2]);
输出:
3
指针与数组从键盘获取字符:
char *p;
char *p1;
p=calloc(1,20); //申请堆地址1块大小为20的内存
p1=calloc(1,20); //不申请地址会段错误
scanf("%s",p);
scanf("%s",p1);
char str1[20];
char sre2[20];
scanf("%s",str1);
scanf("%s",str2);
② 二位数组的指针
#include <stdio.h>
int main()
{
int a[3][3]={1,2,3,4,5,6,7};
int (*p)[3]=a;
int *p1=a[0];
printf("p:%d %d\n",*p[1],*(*a+1));
printf("p1:%d\n",*(p1+2));
return 0;
}
输出:
p:4 2
p1:3
③char型指针:
char型指针实质上跟别的类型的指针并无本质区别,但由于C语言中的字符串以字符数组的方式存储,而数组在大多数场合又会表现为指针,因此字符串在绝大多数场合就表现为char型指针。
#include <stdio.h>
int main()
{
char *p="hello"; //只能读取
//*p='x';不可以对p进行值修改
printf("p:%c\n",*p);
printf("p:%c\n",*(p+1));
printf("p:%c\n",p[2]);
}
④多级指针:
- 如果一个指针变量p1存储的地址,是另一个普通变量a的地址,那么称p1为一级指针
- 如果一个指针变量p2存储的地址,是指针变量p1的地址,那么称p2为二级指针
- 如果一个指针变量p3存储的地址,是指针变量p2的地址,那么称p3为三级指针
- 以此类推,p2、p3等指针被称为多级指针
示例:
int a=100;
int *p=&a; //一级指针,指向普通变量
int **p1=&p; //二级指针
int ***p2=&p1; //三级指针
⑤void型万能指针:
- 概念:无法明确指针所指向的数据类型时,可以将指针定义为void型指针
- 要点:
a.void 型指针无法直接索引目标,必须将其转换为一种具体类型的指针方可索引目标
b.void 型指针无法进行加减法运算
- void关键字的三个作用:
a.修饰指针,表示指针指向一个类型未知的数据。
b.修饰函数参数列表,表示函数不接收任何参数。
c.修饰函数返回类型,表示函数不返回任何数据。
⑥const型指针:
- const型指针有两种形式:
- ①常指针
常指针:const修饰指针本身,表示指针变量本身无法修改。
char a;
char *const p=&a; //常指针:指针指向的目标无法修改,定义常指针时要赋初始值;
char a1;
//p=&a1; //不能在给p赋新的地址
*p='A'; //可以修改值
printf("%c\n",*p); //常指针除了不能修改指向目标,其他与普通指针用法相同
printf("%c\n",a);
- ②常目标指针
常目标指针:const修饰指针的目标,表示无法通过该指针修改其目标。
char ch;
char const *p=&ch; //常目标指针:指针对其指向的目标只读
char ch1;
*p=ch1; //可以修改指向
//*p='A'; //不能修改目标内容
⑦函数指针:
- 概念:指向函数的指针,称为函数指针。
- 特点:函数指针跟普通指针本质上并无区别,只是在取址和索引时,取址符和星号均可省略
示例:
void (*func)(int,char); //指针函数func的参数有一个整形一个字符,没有返回值
- 要点:
a.函数指针是一类专门用来指向某种类型函数的指针。
b.函数的类型不同,所需要的函数指针也不同(函数指针的类型必须和其指向的函数的类型(返回值及形参列表类型)一致)。
c.函数的类型,与普通变量的类型判定一致,即去除声明语句中的标识符之后所剩的语句。