目录
6.1 内存
存储器:存储数据的器件
1、外存:外存又叫外部存储器,长期存放,掉电不丢失。常见的有硬盘、flash、rom、光盘、磁带等
2、内存:内存又叫内部存储器,暂时存放,掉电丢失。常见的有ram、DDR(电脑的内存条)
(1)物理内存:实在存在的存储设备
(2)虚拟内存:操作系统虚拟出来的内存
操作系统会在物理内存和虚拟内存之间做映射,32位系统下,每个进程的寻址范围是4G,地址是0x00 00 00 00~ 0x ff ff ff ff
数据存放在物理内存,我们看到的是虚拟地址。
运行程序时操作系统会将虚拟内存4G进行分区,方便对内存进行管理
(1)堆区:在动态申请内存的时候,在堆里开辟内存
(2)栈区:主要存放局部变量
(3)静态全局区:
a.未初始化的静态全局区:静态变量(定义时用static修饰的变量),或全局变量,没有初始化的放在此区;
b.初始化的静态全局区:全局变量、静态变量、赋过初值的放在此区。
(4)代码区:存放程序代码
(5)文字常量区:存放常量
内存以字节为单位存储数据,可以将程序中的虚拟寻址空间看成一个很大的一维数组。
6.2 指针的相关概念
系统给虚拟内存的每个存储单元分配了一个编号,从0x 00 00 00 00 ~ 0x ff ff ff ff,这个编号称为地址,指针就是地址.
指针变量:用一个变量来存储一个地址编号。
32位平台下地址总线是32位的,所以地址是32位编号,指针变量要存放32位数据,所以是4个字节的。
注意:1、32位平台下任何类型的指针变量都是4个字节;
2、某一类型的指针变量只能对应类型的变量的地址,比如整形的指针变量,只能存放整形变量对应的地址。
字符变量char ch = 'b';ch占1个字节,它有一个地址编号:比如0x 00001fff,这个地址编号就是ch的地址。
整形变量 int a = 0x12 34 56 78;a占4个字节,故占有4个字节的存储单元,所以有4个地址编号,认为编号最小的那个是其地址,也就是说0x00002000是a的地址。
6.3 指针的定义方法
1、简单的指针变量
数据类型* 指针变量名;
比如:int* p; //定义了一个指针变量p,这个p指向的是一个整形数据
*是用来修饰变量的,表示变量p是一个指针变量。
注意:如果一行定义多个指针变量,每个指针变量前面都要加*来修饰:
int *p,*q;//定义了两个指针变量p和q
int *p,q;//定义了指针变量p和整形变量q
int a = 0x1234abcd;
int* p;//定义了一个指针变量p,*修饰变量p,表示它是一个指针变量
p = &a;//把a的地址赋给p,&是一个取址符,可以说p指向a
int num;
num = *p;
在调用的时候,*是取值的意思,*p也就是p指向的变量,也就是a,所以num = *p和num = a的效果是一样的。
a的值是0x1234abcd,假设a的地址是0xbfe89868。
int main()
{
int a = 100, b = 200;
int* p_1, * p_2 = &b;
p_1 = &a;//p_1保存了a的地址,p_2保存了b的地址
printf("a = %d ,p_1 = %p, *p_1 = %d\n", a,p_1, *p_1);//*在调用的时候是取值的意思,*p_1也就是取p_1对应的值,也就是a
printf("b = %d ,p_2 = %p, *p_2 = %d\n", b, p_2, *p_2);
return 0;
}
运行结果是:
a = 100, p_1 = 00A8FBB0, * p_1 = 100
b = 200, p_2 = 00A8FBA4, *p_2 = 200
注意:p_1是局部变量,如果不赋初值,他的初值是随机的,称为野指针。
2、指针的大小:32位平台下任何类型的指针变量都是4个字节。
int main()
{
char* p1;
short int* p2;
int* p3;
long int* p4;
float* p5;
double* p6;
printf("sizeof(p1) = %d, ", sizeof(p1));
printf("sizeof(p2) = %d, ", sizeof(p2));
printf("sizeof(p3) = %d, ", sizeof(p3));
printf("sizeof(p4) = %d, ", sizeof(p4));
printf("sizeof(p5) = %d, ", sizeof(p5));
printf("sizeof(p6) = %d, ", sizeof(p6));
return 0;
}
sizeof(p1) = 4, sizeof(p2) = 4, sizeof(p3) = 4, sizeof(p4) = 4, sizeof(p5) = 4, sizeof(p6) = 4,
6.4 指针的分类
按指针指向的数据的类型来分
1、字符指针
字符型数据的地址,只能存放字符型变量的地址编号
char *p;
char ch;
p = &ch;
2、短整型指针
只能存放短整型变量的地址编号
short int *p;
short int si;
p = &si;
3、整型指针
只能存放整型变量的地址编号
int *p;
int i;
p = &i;
注意:多字节变量会占用多个存储单元,而每个存储单元都有对应的地址编号,C语言规定存储编号最小的那个编号是多字节变量的地址编号。
4、长整型指针
只能存放长整型变量的地址编号
long int *p;
long int li;
p = &li;
5、float型指针
只能存放float型变量的地址编号
float *p;
float f;
p = &f;
6、double型指针
只能存放double型变量的地址编号
double int *p;
double int df;
p = &df;
7、函数指针
8、结构体指针
9、指针的指针:保存指针变量的地址
10、数组指针
11、通用指针 void*:可以保存任何类型变量的地址
6.5 指针和变量的关系
1、指针可以存放变量的地址编号
int a = 100;
int* p;
p = &a;
p保存了a的地址,也可以说p指向了a。
2、在程序中引用变量的方法:
(1)直接通过变量的名称
(2)通过指针变量来引用变量
int *p;//在定义的时候,*不是取值的意思,而是修饰的意思,表示p是个指针变量
p = &a;//取a的地址给p赋值,p保存了a的地址
*p = 100;//在调用的时候*是取值的意思,等价于指针指向的变量a
int*p = &a;//可以在定义指针变量的同时给其赋值
//交换两个数的值
int main()
{
int a, b, tmp;
int* p1 = &a, * p2 = &b;
printf("输入a和b的值\n");
scanf_s("%d %d", &a,&b);
tmp = *p1;//tmp = a
*p1 = *p2;
*p2 = tmp;
printf("a = %d, b = %d\n", a, b);
return 0;
}
3、对应类型的指针只能保存对应类型数据的地址,如果想让不同类型的指针相互赋值时,需要强制类型转换,或者使用void*,可以保存任意类型数据的地址.
(1)*p 取值,如果是字符指针取1个字节,整形指针取4个字节,double类型指针取8个字节。
(2)p++指向下一个对应类型的数据,字符指针++指向下一个字符数据,指针存放的地址编号+1,而整形指针++指向下一个整形数据,指针存放的地址编号+4,double类比。
int main()
{
int a = 0x12345678, b = 0xabcdef66;
char* p1, * p2;
int* p = &a;
printf("%0x %0x\n", a, b);
p1 = (char*)&a; //将a的地址(整形数据的地址)转换为char*(字符数据的地址)
p2 = (char*)&b; //将b的地址(整形数据的地址)转换为char*(字符数据的地址)
printf("%0x %0x\n", *p1, *p2);
p1++;
p2++;
printf("%0x %0x\n", *p1, 0xff & (*p2));
printf("%0x", *p);
}
运行的结果是:
12345678 abcdef66
78 66 因为p1是字符指针,而p1指向的是a的最小编号地址,对应最后的一个字节,也就是78
56 ef
12345678
6.6 指针和数组元素之间的关系
6.6.1 指针变量可以存放数组元素的地址
变量存放在内存中,有地址编号,我们定义的数组是多个相同类型的变量的集合,而每个变量都占用内存空间,都有地址编号,所以指针变量也可以存放数组元素的地址
int a[5] = { 1,5,7,9,11 };
int* p;
int main()
{
int i;
for (i = 0; i < 5; i++)
{
p = &a[i];
printf("& a[%d] = %p\n", i, p);
p++;
}
return 0;
}
运行的结果是:
& a[0] = 0075A034
& a[1] = 0075A038
& a[2] = 0075A03C
& a[3] = 0075A040
& a[4] = 0075A044
可见,数组中的元素在内存中都是连续存储,地址是相邻的。整形数据占用4个字节的存储空间,编号最小的地址编号是它的地址,所以相邻的整形数据的地址编号相差4。
整形指针++指向下一个整形变量,指针存放的地址编号+4。
6.6.2 数组元素的引用方法
1、数组名[下标]
2、指针名+下标
c语言规定,数组的名字就是数组的首地址,即第0个元素的地址,即&a[0],是个常量。
int a[5]; //a 就是a[5]这个数组的首地址。
int *p;
p = a;
p[2] = 100; //相当于a[2] = 100;注意:p和a不同,p是个指针变量,a是个常量(a[0]的地址)
int main()
{
int a[5];
printf("a = %p\n", a);
printf("a = %p\n", &a[0]);
return 0;
}
a = 00CFFE70
a = 00CFFE70
可见,a 就是 a[0]的地址。
3、指针变量的运算加取值的方法
int a[5];
int *p;
p = a; //p指向a[0]
*(p+2) = 100; //相当于a[2] = 100;p是第0个元素的地址,p+2是第2个元素的地址
4、通过数组名加取值的方法
int a[5];
*(a+2) = 100; //相当于a[2] = 100;a是第0个元素的地址,a+2是第2个元素的地址
int main()
{
int a[5] = { 1,4,6,9,11 };
int* p;
p = a; //p指向a[0]
printf("a[2] = %d\n", a[2]);
printf("a[2] = %d\n", p[2]);
printf("a[2] = %d\n", *(p+2));
printf("a[2] = %d\n", *(a+2));
printf("p = %p a = %p\n", p,a);
printf("p+2 = %p a+2 = %p", p + 2,a+2);
return 0;
}
a[2] = 6
a[2] = 6
a[2] = 6
a[2] = 6
p = 004FFC4C a = 004FFC4C
p + 2 = 004FFC54 a + 2 = 004FFC54
6.6.3 指针的运算
1、指针可以加一个整数,往下指几个它指向的变量,结果还是个地址
char a[5];
char* p;
p = a;
p+2; //p是a[0]的地址,那么p+2是a[2]的地址
如果p存放的地址编号是2000,那么p+2存放的地址编号是2002
2、两个相同类型的指针可以比较大小,但只有同时指向一个数组的元素时比较大小才有意义,下标小的元素的地址比下标大的元素的地址要小
int main()
{
int a[10];
int* p, * q;
p = &a[1];
q = &a[6];
printf("& p = %p, & q = %p\n", p, q);
if (p < q) printf("p < q ");
else if (p = q) printf("p = q");
else printf("p > q");
return 0;
}
运行的结果是:
&p = 012FFD54, & q = 012FFD68
p < q
3、两个相同类型的指针可以做减法,但只有同时指向一个数组的元素时做减法才有意义,减法的结果是两个指针指向的中间有多少个元素
int main()
{
int a[5];
int* p, * q;
p = &a[1];
q = &a[4];
printf("&p = %p, &q = %p\n", p, q);
printf("q - p = %d\n", q - p);
return 0;
}
运行的结果是:
&p = 006FF8E8, & q = 006FF8F4
q - p = 3
注意:q和p之间相隔了12个字节,但做减法的结果是得到中间的元素个数,是整形而不是地址编号。
4、两个相同类型的指针可以相互赋值,如果类型不同的元素想要相互赋值,必须进行强制类型转换
int *p;
int *q;
int a;
p = &a; //p指向a
q = p; //用p的值给q赋值,那么q也指向a了