计算机存储信息的最小单位是 位(又称比特,写作 bit,简写 b,一位为一个 0 或 1),计算机 内存 中的基本存储单位是 字节(写作 Byte,简写 B,1B = 8b,比如 00000000、11111111、01101001)
1B 可以表示 二进制 00000000 到 11111111 的数(即十进制的 0~255)
1GB = 1024MB,1MB = 1024KB,1KB = 1024B, 210 = 1024(2 的 10 次方为 1024)
所以 1GB = 1024MB = 1024 * 1024KB = 1024 * 1024 * 1024B = 10243 B = 230 B
计算机的内存是由 一个个字节(Byte)组成的,每个字节可保存的数据为 8b(即十进制的 0-256)。计算机内存的字节数可以有很多,2GB 的内存就有 2147483648(2 * 10243、231)个字节,把这些内存用 十六进制编号:
第 0 个字节编为为 0x0 号(前缀 0x 表示十六进制,也可写成 0X),
第 15 个字节编为 0xF 号,
第 16 个字节编为 0x10 号,
第 6421984 个字节编为 0x61FDE0
第 2147483648 个字节编为 0x80000000(将十六进制换算成二进制的话等价于 8 * 167,等价于 23 * 24*7,等价于 231 即 2147483648)
以上计算机内存中字节的十六进制编号就称为地址,地址也就是指针存放的数据(即十六进制编号)
指针的值就是地址(指针存放的数据就是地址),地址就是编号,也就是内存中字节的编号
下面一图更直观的看 指针(即 指针指存放的地址)、指针地址、指针指向的地址的值(不要与指针的值搞混,指针的值 是地址,该地址上存放着数据)
指针 p | int 类型数据 a | |
---|---|---|
数值 | 0x666 | 123321 |
地址 | 0xF00 | 0x111 |
在 c/c++ 中 char 是整数类型 的,因为字符常量存储在计算机存储单元中时,并不是存储字符(如 a,z,# 等)本身,而是以其代码(一般采用 ASCII 代码)存储的,例如字符 ‘a’ 的 ASCII 代码时 97,因此,在存储单元中存放的是 97(以二进制形式存放),而不是字符 ‘a’。
另一方面 char 可表示 -128 ~ 127 的数,int 可表示 -2147483648 ~ 2147483647,char 本身就是值类型的,所以说 char是整数型的,不要因为 char 可以用来表示字符就把 char 不当整型,整数和符号之间是可以转换的:
#include
#include
/*char为整型*/
int main()
{
char a ='a';
int b = 97;
char c = '#'; /*同理,这个字符也可以转换成整数35,或用35表示字符'#'*/
printf("字符a = %c, 整数 b 转换成字符表示 = %c, 字符 a 转换成整数表示 = %d\n", a, b, a);
printf("用整数 97 表示字符 = %c, 90 + 7 = %c\n", 97, 90+7);
return 0;
}
输出结果为
字符 a = a, 整数 b 转换成字符表示 = a, 字符 a 转换成整数表示 = 97
用整数 97 表示字符 = a, 90 + 7 = a
言归正传,平时保存的数据类型一般都是基本类型的数据,如整型 int / long / shore / char、实型 float / double,如果要保存地址怎么办呢?
地址不能被保存到普通变量中,这时候就到指针变量出场了,指针变量这种变量是专门用来保存地址的,指针变量也可简称为指针
如何定义指针变量呢,如果有定义整型变量 a int a = 1;
,为了保存 a 的地址,我们可以定义这样一个指针 p :
定义指针变量和定义普通变量的形式类似,只需要在变量名前加 * 号
#include
#include
/*定义指针*/
int main()
{
int a = 1;
int *p; /*定义指针,也可以写成
int* p;
int * p;*/
p = &a; /*将a的地址保存到指针p中*/
printf("整数a=%d的地址是%#x,\n指针p的地址是:%#x\n", a, p, &a);
return 0;
}
输出结果:
整数 a = 1 的地址是 0x61fe14,
指针 p 的地址是:0x61fe14
注意这里的 *号 不是 *指针运算符 ,* 号是一个标志,有了 * 号才表示所定义的是指针变量,才能保存地址
1.变量名是 p,不是 *p
2.变量 p 的类型是 int * ,不是 int
指针变量 p 保存了普通变量 a 的地址,就是 “对准了” 普通变量 a ,因为可以通过 p 中所保存的这个地址来访问变量 a(就是存取变量 a)。我们称:
或称:
在一条定义语句中可以同时定义多个指针变量、普通变量
double *m, n;
int *x, *y, z; /*定义指针时必须有* 号,否则就是普通变量*/
int* a, b, c; /*定义了一个指针 a,和两个普通变量 b、c,并非定义3个指针*/
上面说到指针变量时,提到过:
2.变量 p 的类型是 int *
“ int * 类型 ” 是什么含义呢?它表示指针变量 p 所指向的数据类型是 int 型,也就是说将来 p 要保存一个地址,但这个地址有讲究,必须是一个 int 型数据的地址才能被保存
int * 中 int 表示该指针变量将保存何种类型数据的地址,换句话说是指针变量所能指向的数据类型,该类型称为指针变量的基类型
指针变量要保存的地址必须是基类型这种类型数据的地址,指针变量只能指向同基类型的数据
void 基类型的指针:
void *p; /*该指针能指向任何基类型的数据*/
int a = 1;
float b = 3.14;
double c = 3.333;
char d = 'd';
p = &a; /*p指向a*/
p = &b; /*p指向b*/
p = &c; /*p指向c*/
p = &d; /*p指向d*/
变量定义了不赋初值,其值就会是随机数
指针也一样,定义了不赋初值,其保存的地址就会是随机地址,即指向内存中随机的数据,这很危险,说不定指向的是系统运行所必须的一个很重要的数据,如果通过指针修改指向的数据,可能系统就崩溃了(虽然并不会这样,系统的文件一般会有保护的,没那么容易修改),所以为了避免悲剧,指针必须赋初值
int a = 100;
/*以下赋值都是正确的*/
int *p = &a; /*第一种,其含义是p=&a而不是*p=&a,*是和 int结合的 */
int *p;
p = &a; /*第二种*/
int *p = 0;
int *p = NULL; /*第三种,不允许把整数赋值给指针,0除外,字符NULL的ASCII为0*/
针对第三种赋值,系统规定,如果一个指针变量里保存的地址为0,则说明这个指针变量不指向任何内容,叫做空指针。
1. & 取址运算符,获取变量地址
2. * 指针运算符(或称间接访问运算符),获取或改写以指针 p( p 指向的地址)为地址的内存单元的内容(注意别和定义时的 * 号 搞混了),如:
int a = 101;
int *p = &a;
printf("*p 的值为 %d, a 的值为 %d", *p, a); /*输出两个101*/
因为 p 保存的是 a 的地址,* p 就是取 a 地址保存的数据(101),* p 等价于 a
& 和 * 运算符的运用:
#include
#include
/*&和*的运用*/
int main()
{
int a = 1, x = 2, *p;
p = &a;
x = *p; /*等价于x=a*/
*p = 5; /*等价于a=5*/
printf("%d %d %d", a, x, *p); /*输出5 1 5*/
return 0;
}
int a = 5, *p = &a;
p 表示指针 p 保存的值( a 的地址)
&p 表示指针 p 本身的地址
* p 表示指针 p 指向的数据,是变量 a
&、* 互为逆运算,即一个 & 和 * 可以相互抵消如:*&p 等价于 p 等价于 &a
、*&*&*&*p 等价于 *p 等价于 *a
利用指针比较两数大小:
#include
#include
/*利用指针比较两数大小*/
int main()
{
int a = 0, b = 1, *p, *q, *t;
p = &a;
q = &b;
scanf("%d %d", p, q); /*p,q也可以用&a,&b代替*/
if(a < b)
t = p, p = q, q = t;
printf("最大值:%d, 最小值%d", *p, *q);
return 0;
}
a、b 本身的值没变,变的只是指针 p、q 指向的数据,p 指针指向最大的数,q 指向最小的数
如有定义一维数组 int a[5] = {1, 3, 0, 5, 9};
那么指针 int *p = &a[0]
则指向 a[0],指针指向的数组类型也必须同基类型的数组
此时,如果 p + 1; printf("%d", *p);
,会输出什么?答案是输出3
如果有定义指针 int *q = &a[3];
,那么 printf("%d", q - p);
会输出什么?答案是 2
#include
#include
/*指针加减整数、指针相减*/
int main()
{
int a[5] = {1, 3, 0, 5, 9};
int *p = &a[0], *q=&a[3];
/*指针加整数*/
p++;
printf("p + 1:%d\n", *p); /*输出 a[0+1],下标 +1,即 a[1],值为 3*/
/*指针之间相减*/
printf("q - p:%d", q-p); /*输出 2,下标之间相减 3-1=2*/
return 0;
}
输出结果:
p + 1:3
q - p:2
1.指针 p ± 整数 n,不是简单的将指针 p 保存的地址加减整数 n,而是加减 n 个“单位”,即 n 个“数组元素”的字节数,也可以理解成数组下标的加减,运算法则:
2.两指针相减,结果为两个地址间相差的单位个数(数组元素个数),运算法则:
通过两指针相减,可以求出指针 p 指向的数组元素的下标:
int a[10] = {1, 3, 2, 4, 5, 7, 9, 6, 8, 0};
int *p = &a[4];
printf("指针 p 指向的数组元素的下标是:%d", p - &a[0]); /*输出4 */
输出结果:
指针 p 指向的数组元素的下标是:4
int a[5];
对于一维数组名 a 有:我们可以用 a 来表示数组 a 的首地址(即 &a[0]),a 是一个假想的“指针变量”,它本质上是数组 a 的名字且它表示的地址不能被改变(也有人称数组名 a 是指针常量),所以在对指针变量赋值时可以这样:
int a[5] = {1, 3, 5, 7, 9};
int *p;
/*以下两种写法都可以*/
p = &a[0]; /*写法一*/
p = a; /*写法二*/
/*对于数组元素,a[i]等价于p[i]*/
printf("%d %d", a[3], p[3]); /*输出两个7*/
输出结果:
7 7
数组名 a 是 “指针变量” ,具有指针变量的所有特性(只要不改变 a 的值 )
语法糖也叫糖衣语法,在不改变原语法意思的基础上用另一种更简单、可读性更好的语法表示,这种可读性更好的语法就叫语法糖,例如:
int a[5], *p = a;
,前面学习了 间接访问运算符、指针加减法,*(a + i)
和*(p + i)
实际上都是表示数组元素 a[i] ,a[i]
就是 *(a+i)
和 *(p+i)
的语法糖
公式 1:
对公式 1 两边同时做 & 运算,得到公式 2:
公式 1、2 不仅适用于数组名,也适用于所有类型指针变量(一级指针、二级指针乃至更高级别的指针变量,也就是说公式 1、2 也可以写成 *(p + i) 等价于 p[i])
、 p + i 等价于 &p[i]
int a[] = {1, 3, 5, 7}, *p = a + 1;
printf("%d ", *p++); /*p++是在语句结束后才p=p+1的,所以相当于*(p++);*/
printf("%d ", ++*p); /*此时p指向a[2]值为5,先做*p,然后(*p)+1,不影响 a[2]值*/
printf("%d ", (*p)++); /*跟上一句相同,是*p+1,不是*(p+1) */
printf("%d ", *++p ); /*这个就是*(p+1) */
输出结果为
3 6 6 7
#include
#include
/*使用指针将数组头变尾、尾变头,然后顺序输出数组*/
int main()
{
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p, *q, t;
p = a; /*或p=&a; p=&a[0]*/
q = &a[9]; /*或q=&a+9;不能写成a[9],a[9]表示的是值9*/
/*交换头尾元素*/
while(p<q)
{
t = *p, *p = *q, *q = t;
p++, q--;
}
/*输出数组*/
for(p = a; p <= &a[9]; p++)
printf("%d ", *p);
return 0;
}
输出结果为
9 8 7 6 5 4 3 2 1 0