指针是啥
指针
说指针, 就不可避免的要和*
这个运算符打交道. 因此, 我们首先要说的就是*
的意思.
*的意思
第一种用法:
1 | int * a = &b; |
仅仅表示a是一个指针, 无其他含义
第二种用法:
1 | *a = 2; |
*运算符, 表示从指针取值.此处表示将a这个指针指向的变量的值设置为2.
编程语言中, 广义的值分为左值和右值.
变量名字都是左值, 意思是可以放在等号左边的值, 也就是可以被赋值的值.
常量(如123)以及一些运算结果(如b+1)是右值, 只能放在等号右边, 不能被赋值.
此处的*a
是一个左值, 可以被赋值.
编译错误中提到的lvalue
和rvalue
指左值和右值.
第三种用法:
1 | b = 2 * 3; |
仅表示乘法运算符.
可以看到, int * a
和 *a
中的*的意义完全不同.
指针与变量
- 所有变量都存储在内存上的某个地址中, 每一个变量都有一个地址
- &运算符可以获取变量的地址, *运算符可以从地址获取值
- 地址按照字节编码(如: 地址0x100的值表示内存上的第0x100个字节), 而不是比特.
- 变量的值就是普通的值
- 指针的值是别的变量的地址
- 因此指针的值也可以是指针的地址(指针的指针)
- 因为指针存的是地址, 因此具有特殊性, 如果不初始化就访问会产生极其严重的后果, 因此要么初始化为某变量地址要么初始化为NULL.
指针=地址+类型
注: 0x开头的数字是16进制表示的数字, 如0x01 == 1; 0x10 == 16
一个字节是8个比特, 一个16进制数是4个比特(2^4==16), 因此一个16进制的两位数占用的空间大小是一个字节.
如何理解指针=地址+类型
? 举个例子, 假设内存中的值是这样的:
值: | 0x12 | 0x34 | 0x56 | 0x78 |
---|---|---|---|---|
地址: | 0x100 | 0x101 | 0x102 | 0x103 |
同时, 假设以下指针的值(指针中存储的地址)均为0x100:
1 | int * a; |
则有
1 | *a == 0x12345678; //从0x100开始的4个字节 |
因为指针=地址+类型
, 以上三个指针指向的地址相同, 但是因为类型不同, 读取到的值也不同. 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, 0x103 这4个字节, 所以data+1应该等于0x104.
这就体现了一个问题: 指针运算需要知道指向的变量的大小.
1 | int idata[10]; |
无类型指针
1 | void * a = NULL; |
a
是一个无类型的指针. 因为没有类型, 所以不知道他的大小, 所以无法进行形如a+1
的运算.
同样, 因为不知道类型, 也无法对这个指针"取值"(文章开头提到的*的第二个用法).
(c语言中, &运算符的作用是"取地址", *运算符的作用是"取值".)
malloc函数的返回值是void*
类型, 这点也很容易理解. 他不知道你申请的内存要存什么类型的数据. 因此, 使用malloc时要进行强制类型转换:
1 | int * a = (int*)malloc(10*sizeof(int)); |