C语言深入研究指针1
内存和地址
内存
在生活中,一栋楼里面有若干个房间,每个房间有其对应的门牌号。
类似的,在计算机中,会把内存分成若干个内存单元。每个内存单元的大小取一
个字节。每一个内存单元都会有一个唯一的编号。
C语言给内存单元的地址起了一个新名字叫指针。
指针变量和地址
& 取地址符
C语言中创建变量其实就是向内存申请空间。
整型变量是向内存申请了4个字节的。

要拿到这个整型变量的地址就需要用到 & 取地址操作符。
& 操作符取出的是所占字节中地址较小的字节的地址。
指针变量和取地址操作符
指针变量
用于存放指针的变量称为指针变量。
1 | int a = 10; |
存放在指针变量中的值都会被认为是地址。
如何拆解指针类型
1 | int a = 10; |
这里 可以将 int * 分开来看, * 说明变量 p 是一个指针类型变量,int 说明 p 这个 指针变量指向的是 int 类型的变量。

解引用操作符
将地址保存于指针变量中,要使用的话就需要用到解引用操作符 *
1 | int a = 10; |

指针变量的大小
指针变量的大小与平台环境的位数有关。
32位平台下地址是32个bit位(4个字节)
64位平台下地址是64个bit位(8个字节)
1 | printf("%zd\n", sizeof(char*)); |


指针变量的大小和类型是无关的,在相同平台下大小都是相同的。
指针变量类型的意义
指针的解引用
通过调试来看看下面的代码
1 | int a = 0x11223344; |


1 | int a = 0x11223344; |

可以看到前者会将4个字节全部改为0,但是后者只会将第一个字节改为0。
这说明指针变量类型决定了解应用的时候有多大权限(操作几个字节)
指针 + - 整数
1 | int a = 10; |

指针类型决定了指针向前向后走一步的距离
void* 指针
可以接受任意类型的地址。但是不能直接进行指针的 + - 和解引用运算

一般 void* 类型指针多用于函数参数的部分
const 修饰指针
const 修饰变量
const 在修饰变量时,变量是无法被直接修改的
1 | const int n = 10; |

但是可以通过指针变量绕过进行修改
1 | const int a = 10; |

const 修饰指针变量
1 | int n = 10; |

1 | int n = 10; |

1 | int n = 10; |

可以发现
1 | 当 const 放在 * 的左边时,修饰的是指针变量所指向的内容,指针变量本身可以改变,所指向内容不允许修改 |
1 | 当 const 放在 * 的右边时,修饰的是指针变量本身,指针变量本身不允许改变,所指向内容允许修改 |
1 | 当 * 两边都有 const 时,指针变量本身和所指向的内容都不允许修改。 |
指针运算
指针 +- 整数
数组在内存中时连续存放的,只要知道第一个元素的地址,就能获得这个数组的所有元素
1 | int arr[5] = { 1,2,3,4,5 }; |

指针-指针
得到的是两个指针变量之间相隔的元素个数,不是内存字节差
1 | int arr[5] = { 1,2,3,4,5 }; |

指针的关系运算
><:比位置,仅限同数组
== !=:比地址,任意同类型都能用
1 | int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; |

野指针
野指针指向的位置是位置的。
成因
指针变量不进行初始化
1 | int* p; |
数组越界访问
1 | int arr[5] = { 1,2,3,4,5 }; |

指针指向的空间释放
1 | int* Exp() |
这里的 &n 只是函数内部的一个局部变量,当这个函数执行完成,这个局部变量
的地址就被释放了。
assert 断言
当指针变量为 NULL 时,它并不指向任何空间。
assert.h这个头文件定义了宏 assert(),如果不符合,就报错并终止运行。
这个宏常常被称作断言。
1 | assert(p != NULL); |
程序运行到这一步时会判断 p 是否为 NULL, 如果等于 NULL,程序就会直接停止运行并报错。

使用 assert() 不仅能自动标识文件出问题的行号,还有一种无需更改代码就能开启或关闭 assert()的机制。
1 |

编译器读到 NDEBUG 时就会禁用文件中所有的 assert()
传址调用
在前面的文章中提到过形参和实参的关系。
形参是实参的一份临时拷贝。
对形参的操作不影响实参(适用于传值调用)
而传址调用,对形参的操作会影响到实参。
1 | void Swap1(int a, int b) |

调试看到进入 Swap1() 后 x ,y 和 a , b 的地址并不相同。

进入 Swap2() 后 x 和 y 中存放的地址与 a ,b 相同。
所以 Swap1() 交换失败, Swap2()交换成功。
如果想在函数中的操作影响到实参,就用传址调用。
反之就用传值调用。



