目录
一、关于内存那点事
1.1 存储器:存储器件
外存
外存 = 外部存储器,长期存放数据,掉电不丢失数据
常见的外存设备:硬盘、flash、rom、u盘、光盘、磁带
内存
内存 = 内部存储器。暂时存放数据。掉电数据丢失
常见的内存设备:ram、DDR
物理内存:实实在在的存储设备
虚拟内存:操作系统虚拟出来的内存,当一个进程被创建的时候,或c程序运行时都会分配虚拟内存,虚拟内存和物理内存之间存在映射关系
32bit 物理内存空间——0x0000 0000 - 0x ffff ffff
操作系统会在物理内存和虚拟内存之间做映射。
在32位系统下,每个进程(运行着的程序)的寻址范围是4G,0x0000 0000 - 0x ffff ffff
在写应用程序,看到的都是虚拟地址
1.2 在运行程序时,操作系统将虚拟内存进行分区
在运行程序时,操作系统会将虚拟内存进行分区:
1、堆
在动态申请内存时,在堆里开辟内存
2、栈
主要存放局部变量(在函数内部,或复合语句内部定义的变量)
3、静态全局区
1):未初始化的静态全局区
静态变量(定义的时候,前面加static修饰)、或全局变量,没有初始化的,存在此区
2):初始化的静态全局区
静态变量、全局变量,赋过初值,存在此区
4、代码区
存放程序代码
5、文字常量区
存放常量
内存以字节为单位的,可以将程序中的虚拟寻址空间,看成一个很大的 一维的字符数组
提示:本章所接触的内容,涉及到的内存都是虚拟内存,更准确来说是虚拟内存的用户空间
二、指针的相关概念
操作系统给每个存储单元分配一个编号,从0x0000 0000 - 0x ffff ffff,这个编号称为i地址,
指针就是地址
指针变量:这个变量用来存放一个地址编码——通过地址找到地址里面的内容
在32位平台下,地址总线是32位,所以地址是32位编号,即指针变量是32位即4个字节
注意:
1、无论什么类型的地址,都是存储单元的编号,在32位平台下都是4个字节
即任何类型的指针变量都是4个字节大小
2、对应类型的指针变量,只能存放对应类型变量的地址
如:整型的指针变量,只能存放整型变量的地址
扩展
字符变量char ch; ch 占一个字节,有一个地址编号,这个编号就是ch地址
整型变量int a; a占4个字节,占有4个字节的存储单元,有4个地址编号。
三、指针的定义方法
3.1 简单指针
数据类型 * 指针变量名
int *p; //定义一个指针变量p
在定义指针变量时 * 是用来修饰变量,说明变量p是指针变量,变量名p。
3.2 关于指针运算符
&取地址、 *取值
&:获取一个变量的地址
* :在定义一个指针变量时,起到标识作用,标识定义的是一个指针变量
以及其他地方表示获取一个指针变量保存的地址里面的内容
void main()
{
//定义普通变量
int a = 100;
//定义指针变量
int *p;
//给指针变量 赋值
//将a地址保存在p中
p = &a;
printf(" a = %d %d\n", a, *p);
printf(" &a = %d %d\n", &a, p);
}
3.3 扩展
如果一行中定义多个指针变量,每个指针变量前面都需要加 * 修饰
int *p, *q; //定义2个整型的指针变量p和q
int *p, q; // 定义一个整型指针变量p , 和整型变量q
3.4 指针大小
在32位系统下,所有类型的指针都是4个字节,
而在64位系统下,所有类型的指针都是8个字节,
不管地址内的空间多大,但地址编号的长度都是一样的,所以32位操作系统中,地址都是4个字节;64位系统,地址都是8个字节
void main()
{
char *a;
short *b;
int *c;
long *d;
float *e;
double *f;
printf(" sizeof(a) = %d \n", sizeof(a));
printf(" sizeof(b) = %d \n", sizeof(b));
printf(" sizeof(c) = %d \n", sizeof(c));
printf(" sizeof(d) = %d \n", sizeof(d));
printf(" sizeof(e) = %d \n", sizeof(e));
printf(" sizeof(f) = %d \n", sizeof(f));
}
四、指针分类
按指针指向的数据类型来分:
1 字符指针
字符型数据地址
char *p;//定义一个字符指针变量,只能存放字符型数据的地址编号
char ch;
p = &ch;
2 短整型指针
short int *p;//定义一个短整型指针变量,只能存放短整型数据的地址
short int a;
p = &a;
3 整型指针
int *p;//定义一个整型指针变量,只能存放整型数据的地址
int a;
p = &a;
//注意:
/*
多字节变量,占用多个存储单元,每个存储单元都有地址编号,
c中,存储单元编号最小的那个 —— 是多字节变量的地址编号
*/
4 长整型指针
long int *p;//定义一个长整型指针变量,只能存放长整型数据的地址
long int a;
p = &a;
5 float型 double型指针
float *p;//定义一个float 型指针变量,只能存放float 型数据的地址
float a;
p = &a;
double *p;//定义一个double指针变量,只能存放double数据的地址
double a;
p = &a;
6 函数指针
7 结构体指针
8 指针的指针
9 数组指针
总结:无论什么类型的指针变量,在32位操作系统下,地址都是4个字节;64位系统,地址都是8个字节,只能存放对应类型的变量地址编号
五、指针和变量的关系
指针可以存放变量的地址编号
在程序中,可以引用变量的方法。
1 直接通过变量的名称
int a;
a = 100;
2 可以通过指针变量来引用变量
int *p;
p = &a;
*p = 100;
注意:指针变量在定义的时候可以初始化
int a;
int *p = &a;
变量a 必须在指针调取前开辟空间。
注意:指针变量只能保存已经开辟好空间的地址,不能自己直接给随便赋地址
void main()
{
int *p1, *p2, temp, a, b;
p1 = &a;
p2 = &b;
printf("请输入:a b的值:\n");
scanf("%d %d", p1, p2);
temp = *p1;
*p1 = *p2;
*p2 = temp;
printf("a = %d b = %d\n", a, b);
printf("*p1 = %d *p2 = %d \n", *p1, *p2);
}
扩展:
对应类型的指针,只能保存对应类型数据的地址。
如果想让不同类型的指针相互赋值时,需要强制类型转换
void *p;
void main()
{
int a = 0x1234, b = 0x5678;
char *p1, *p2;
printf("a = %#x b = %#x\n", a, b);
p1 = (char *)&a;
p2 = (char *)&b;
printf("*p1 = %#x *p2 = %#x \n", *p1, *p2);
p1++;
p2++;
printf("*p1 = %#x *p2 = %#x \n", *p1, *p2);
}
注意:
1、* + 指针取值,取几个字节,由指针类型决定的
- 指针为字符指针则取一个字节,
- 指针为整型指针则取4个字节,
- 指针为double型指针则取8个字节。
2、指针++ 指向下个对应类型的数据
字符指针++, 指向下个字符数据,指针存放的地址编号加1
整型指针++,指向下个整型数据,指针存放的地址编号加4
六、 指针和数组元素之间的关系
6.1 数组元素与指针的基本关系
变量存放在内存中,有地址编号,定义的数组,是多个相同类型的变量的集合,每个变量都占内存空间,都有地址编号,指针变量也可以存放数组元素的地址
int a[10];
int *p;
p = &a[0];
6.2 数组元素的引用方法
- 方法一: 数组名[下标]
int a[10];
a[2] = 100;
- 方法二:指针名加下标
int a[10];
int *p;
p = a;
p[2] = 100;
补充:数组名字就是数组的首地址,即第0个元素的地址,是个常量。
注意:p和a是不同的,p 是指针变量,而a是常量,等号可以给p赋值,但不能给数组a赋值
例如:int a[10]; a++就是错误的,因为a是数组名是一个地址常量
- 方法三:通过指针运算加取值方法来引用数组的元素
int a[10];
int *p;
p = a;
*(p + 2) = 100; // 相当于a[2] = 100
//p是第0个元素的地址, p +2是a[2]这个元素的地址。
void main()
{
int a[5] = {0, 1, 2, 3, 4};
int *p;
p = a;
//只要将数组名赋值给同类型的指针变量,则此时指针变量与数组名可以用相同方法操控数组
printf("a[2] = %d \n", a[2]);
printf("p[2] = %d \n", p[2]);
//*(a + n ) <==> *(p + n) <==> a[n] <==> p[n]
printf("*(p + 2) = %d \n",*(p + 2));
printf("*(a + 2) = %d \n", *(a + 2));
printf("p = %p \n", p);
printf("p + 2 = %p \n", p + 2);
printf("&a[0] = %p \n", &a[0]);
printf("&a[2] = %p \n", &a[2]);
}
七、指针的运算
7.1 指针可以加一个整数,
往下指几个它指向的变量,还是地址
前提:指针指向数组时,加一个整数才有意义
void test1()
{
int a[10];
int *p,*q;
p = a;
q = p + 2;
//p和q间隔 8个字节,意味加一个整数最终移动的字节数与指针变量的类型有关
printf("p = %p \n", p);
printf("q = %p \n", q);
return;
}
void main()
{
test1();
}
7.2 两个相同类型的指针可以比较大小
前提:只有两个相同类型的指针指向同一数组的元素时,比较大小才有意义。
指向前面元素的指针小于指向元素后面的指针
void test2()
{
int a[10];
int *p,*q;
p = &a[1];
q = &a[2];
if(p < q)
{
printf("p < q\n");
}
else if(p > q)
{
printf("p > q\n");
}
else
{
printf("p = q\n");
}
return;
}
void main()
{
test2();
}
7.3 两个相同类型的指针还可以做减法
前提:必须是两个相同类型的指针指向同一数组的元素时,做减法才有意义
做减法的结果:两个指针指向的中间有多少个元素
void test3()
{
int a[10];
int *p,*q;
p = &a[0];
q = &a[3];
printf("q - p = %d \n", q - p);
return;
}
void main()
{
test3();
}
7.4 两个相同类型的指针可以互相赋值
注意:只有相同类型的指针才可以相互赋值(void *类型除外)
void test4()
{
int a = 100;
int *p,*q;
p = &a;
printf("a = %d *p = %d \n", a, *p);
q = p;
printf("*q = %d \n", *p);
*q = 999;
printf("a = %d \n", a);
return;
}
void main()
{
test4();
}
八、指针数组
8.1 指针和数组关系
- 1 . 指针可以保存数组元素的地址
- 2.可以定义一个数组,数组中有若干个相同类型指针变量,这个数组被称为指针数组
指针数组:指针数组本身是个数组,是个指针数组,是若干个相同类型的指针变量构成的集合
注意:一般遇到这种叠词,本质就是后者
8.2 指针数组的定义方法:
int *p[10]; //定义一个整型的指针数组p,有10个元素p[0]-p[9],每个元素都是int *类型的变量
8.3 指针数组的分类
不同类型定义不同指针数组
void main()
{
//大多数情况下,指针数组都用来保存多个字符串
char *name[5] = {"follow me", "hello", "BASIC", "FORTRAN", "BYE"};
int i;
for(i = 0; i < 5; i++)
{
printf("%s\n",name[i]);
}
}
九、指针的指针 -- 二级指针
指针的指针,即指针的地址
定义一个指针变量本身指针变量占4个字节,指针变量也有地址编号
int a;
int *p;
p = &a;
*p === a
int **q;
q = &p;
*q === p
**q === *p === a
int ***m;
m = &q;
*(*(*(m))) === a
注意:
p q m都是指针变量,都占4个字节,都存放地址编号,只不过类型不一样。
void main()
{
int a = 100;
//定义一个一级指针,用于保存普通变量的地址
int *p = &a;
//定义一个二级指针
//二级指针用于保存一级指针的地址
int **q = &p;
printf("a = %d %d %d\n", a, *p, **q);
printf("&a = %p %p %p\n", &a, p, *q);
printf("&p = %p %p\n", &p, q);
printf("&q = %p\n ", &q);
}