指针是啥

指针

说指针,就不可避免的要和*这个运算符打交道.因此,我们首先要说的就是*的意思.

*的意思

第一种用法:

1
int * a = &b;

仅仅表示a是一个指针,无其他含义

第二种用法:

1
*a = 2;

*运算符,表示从指针取值.此处表示将a这个指针指向的变量的值设置为2.

编程语言中,广义的值分为左值和右值.

变量名字都是左值,意思是可以放在等号左边的值,也就是可以被赋值的值.

常量(123)以及一些运算结果(b+1)右值,只能放在等号右边​,不能被赋值.

此处的*a是一个左值,可以被赋值.

编译错误中提到的lvaluervalue指左值和右值.

第三种用法:

1
b = 2 * 3;

仅表示乘法运算符.

可以看到,int * a*a中的*的意义完全不同.

指针与变量

  1. 所有变量都存储在内存上的某个地址中,每一个变量都有一个地址
  2. &运算符可以获取变量的地址, *运算符可以从地址获取值
  3. 地址按照字节编码(:地址0x100的值表示内存上的第0x100个字节),而不是比特.
  4. 变量的值就是普通的值
  5. 指针的值是别的变量的地址
  6. 因此指针的值也可以是指针的地址(指针的指针)
  7. 因为指针存的是地址,因此具有特殊性,如果不初始化就访问会产生极其严重的后果,因此要么初始化为某变量地址要么初始化为NULL.

指针=地址+类型

: 0x开头的数字是16进制表示的数字,0x01 == 1; 0x10 == 16
一个字节是8个比特,一个16进制数是4个比特(2^4==16),因此一个16进制的两位数占用的空间大小是一个字节.

如何理解指针=地址+类型?举个例子,假设内存中的值是这样的:

: 0x12 0x34 0x56 0x78
地址: 0x100 0x101 0x102 0x103

同时,假设以下指针的值(指针中存储的地址)均为0x100:

1
2
3
int * a;
short * b;
char * c;

则有

1
2
3
*a == 0x12345678; //从0x100开始的4个字节
*b == 0x1234; //从0x100开始的2个字节
*c == 0x12; // 从0x100开始的1个字节

因为指针=地址+类型,以上三个指针指向的地址相同,但是因为类型不同,读取到的值也不同. int指针读取到了4个字节的值, short指针读取到了2个字节的值, char指针读取了1个字节的值.

:以上均假设程序在运行在大端序的环境下,如果你不知道啥是大端序请忽略.

再比如,对于指针的运算,指针的类型也起到决定性作用.众所周知,对于以下代码:

1
int data[100];

data是指向数组首元素的指针, data的值是数组首元素的地址.

data+1是指向数组第2个元素的指针, data+1的值是数组第二个元素的地址.

那么,假设data=0x100, data+1等于多少?

显然,一个int类型变量占用4个字节, data[0]占用了0x100, 0x101, 0x102, 0x1034个字节,所以data+1应该等于0x104.

这就体现了一个问题:指针运算需要知道指向的变量的大小.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int idata[10];
short sdata[10];
char cdata[10];

printf("%p\n", idata);
// 注: printf %p是打印了idata的值, 也就是idata数组的首项的地址.
printf("%p\n", idata + 1);
//会发现, idada+1 比 idata 大4

printf("%p\n", sdata);
printf("%p\n", sdata + 1);
//会发现, sdada+1 比 sdata 大2

printf("%p\n", cdata);
printf("%p\n", cdata + 1);
//会发现, cdada+1 比 cdata 大1

无类型指针

1
void * a = NULL;

a是一个无类型的指针.因为没有类型,所以不知道他的大小,所以无法进行形如a+1的运算.
同样,因为不知道类型,也无法对这个指针"取值"(文章开头提到的*的第二个用法).

(c语言中, &运算符的作用是"取地址", *运算符的作用是"取值".)

malloc函数的返回值是void*类型,这点也很容易理解.他不知道你申请的内存要存什么类型的数据.因此,使用malloc时要进行强制类型转换:

1
int * a = (int*)malloc(10*sizeof(int));