1.计算机中所有的数据都要放进内存,而不同类型数据占用空间不同,例如int 4字节
char 1字节
2.指针(地址) :内存中的每个字节都有自己的地址,又或叫指针
演示输出地址:
1. #include <stdio.h>
2.
3. int main(){
4. int a = 100;
5. char str[20] = "c.biancheng.net";
6. printf("%#X, %#X\n", &a, str);
7. return 0;
8. }
运行结果:
0X28FF3C, 0X28FF10
%#X 表示以十六进制形式输出,并附带前缀 0X。a 是一个变量,用来存放整数,需要在前面加&来获得它的地址;
str 本身就表示字符串的首地址,不需要加&。 C 语言中有一个控制符%p,专门用来以十六进制形式输出地址,不过 %p 的输出格式并不统一,有的编译器带 0x前缀,有的不带
1.数据和代码都是以二进制的形式存储在内存中,操作系统会给其相应的权限,给予读取和执行的权限内存块为代码,而给与读取和写入的为数据(也可能只有读取权限)。
2.cpu只能通过地址来获取代码和数据。
3.CPU 读写数据必须要知道数据在内存中的地址,普通变量和指针变量都是地址的助记符,虽然通过
*p 和 a 获取到的数据一样,但它们的运行过程稍有不同:a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接“
4.使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。
5.指针除了可以获取内存上的数据,也可以修改内存上的数据,例如:
1. #include <stdio.h>
2.
3. int main(){
4. int a = 15, b = 99, c = 222;
5. int *p = &a; //定义指针变量
//第 221 页
6. *p = b; //通过指针变量修改内存上的数据
7. c = *p; //通过指针变量获取内存上的数据
8. printf("%d, %d, %d, %d\n", a, b, c, *p);
9. return 0;
10. }
运行结果:
99, 99, 99, 99
*p 代表的是 a 中的数据,它等价于 a,可以将另外的一份数据赋值给它,也可以将它赋值给另外的一个变量。(同时发生)
6.*在不同的场景下有不同的作用:可以用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;使用指针变量时在前面加表示获取指针指向的数据,或者说表示的是指针指向的数据本身。
也就是说,定义指针变量时的*和使用指针变量时的*意义完全不同。以下面的语句为例:
1. int *p = &a;
2. *p = 100;
第 1 行代码中*用来指明 p 是一个指针变量,第 2 行代码中*用来获取指针指向的数据。
需要注意的是,给指针变量本身赋值时不能加*。修改上面的语句:
1. int *p;
2. p = &a;
3. *p = 100;
第 2 行代码中的 p 前面就不能加*。
指针变量也可以出现在普通变量能出现的任何表达式中,例如:
8.P218
假设变量 a、b、c 在内存中的地址分别是 0X1000、0X2000、0X3000,那么加法运算 c = a + b;将会被转换成类似下面的形式:
0X3000 = (0X1000) + (0X2000);
( )表示取值操作,整个表达式的意思是,取出地址 0X1000 和 0X2000 上的值,将它们相加,把相加的结果赋值给地址为 0X3000 的内存
9.本质是通过指针将地址内的数据进行替换,如果不用指针 ,直接用普通变量将没有意义,并且也不能完整的实现 (换皮)
【示例】通过指针交换两个变量的值。
1. #include <stdio.h>
2.
3. int main(){
4. int a = 100, b = 999, temp;
5. int *pa = &a, *pb = &b;
6. printf("a=%d, b=%d\n", a, b);
7. /*****开始交换*****/
8. temp = *pa; //将a的值先保存起来
9. *pa = *pb; //将b的值交给a
10. *pb = temp; //再将保存起来的a的值交给b
11. /*****结束交换*****/
12. printf("a=%d, b=%d\n", a, b);
13. return 0;
14. }
运行结果:
a=100, b=999
a=999, b=100
从运行结果可以看出,a、b 的值已经发生了交换。需要注意的是临时变量 temp,它的作用特别重要,因为执行*pa
= *pb;语句后 a 的值会被 b 的值覆盖,如果不先将 a 的值保存起来以后就找不到了。
10.一个谜题 ,但是要搞清脉络,学习思维
关于 * 和 & 的谜题
假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a 和&*pa 分别是什么意思呢?
*&a 可以理解为(&a),&a 表示取变量 a 的地址(等价于 pa),(&a)表示取这个地址上的数据(等价于 pa),绕来绕去,又回到了原点,**&a 仍然等价于 a。
&*pa 可以理解为&(*pa),*pa 表示取得 pa 指向的数据(等价于 a),&(*pa)表示数据的地址(等价于 &a),所以&*pa 等价于 pa。
星号*主要有三种用途:
1.表示乘法,例如 int a = 3, b = 5, c; c = a * b;,这是最容易理解的。
2.表示定义一个指针变量,以和普通变量区分开,例如 int a = 100; int *p = &a;。
3.表示获取指针指向的数据,是一种间接操作,例如 int a, b, *p = &a; *p = 100; b = *p;
//next 9.3 P359
12.对于指针变量的部分运算 例如 加法 减法 比较等
当我们对指针进行加减时,并不是简单的地址加1整体向后移动,而是指针对数据开头的指向发生偏移
pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的
长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。
数组中的所有元素在内存中是连续排列的,如果一个指针指向了数组中的某个元素,那么加 1 就表示
指向下一个元素,减 1 就表示指向上一个元素。
对于指针的计算3不要!!!
不要尝试通过指针获取下一个变量的地址
不要尝试对指向普通变量的指针进行加减运算
不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义
定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。在 C 语言中,我们将第 0 个元素的地址称为数组的首地址。以整数型数组为例 ,arr的指向
下面的例子演示了如何以指针的方式遍历数组元素:
1. #include <stdio.h>
2.
3. int main(){
4. int arr[] = { 99, 15, 100, 888, 252 };
5. int len = sizeof(arr) / sizeof(int); //求数组长度
6. int i;
7. for(i=0; i<len; i++){
8. printf("%d ", *(arr+i) ); //*(arr+i)等价于arr[i]
9. }
10. printf("\n");
11. return 0;
12. }
运行结果:
99 15 100 888 252
第 5 行代码用来求数组的长度,sizeof(arr) 会获得整个数组所占用的字节数,sizeof(int) 会获得一个数组元素所占用的字节数,它们相除的结果就是数组包含的元素个数,也即数组长度。
第 8 行代码中我们使用了*(arr+i)这个表达式,arr 是数组名,指向数组的第 0 个元素,表示数组首地址, arr+i 指向数组的第 i 个元素,*(arr+i) 表示取第 i 个元素的数据,它等价于 arr[i]。
arr 是 int** 类型的指针,每次加 1 时它自身的值会增加 sizeof(int),加 i 时自身的值会增加 sizeof(int) * i
定义一个指向数组的指针,例如:
1. int arr[] = { 99, 15, 100, 888, 252 };
2. int *p = arr;
也可以写作
int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头!
2.如果一个指针指向了一个;数组指针指向的不是一个完整的数组,而是指向数组的某个元素,数组是什么类型的指针就应当是和数组相同的类型; 上述例子数组是int型,相对的指针p也是int*。
3.更改以上代码
1. #include <stdio.h>
2.
3. int main(){
4. int arr[] = { 99, 15, 100, 888, 252 };
5. int i, *p = arr, len = sizeof(arr) / sizeof(int);
6.
7. for(i=0; i<len; i++){
8. printf("%d ", *(p+i) );
9. }
10. printf("\n");
11. return 0;
12. }
数组在内存中只是数组元素的简单排列,没有开始和结束标志,在求数组的长度时不能使用 sizeof§ / sizeof(int),因为 p 只是一个指向 int 类型的指针,编译器并不知道它指向的到底是一个整数还是一系列整数(数组),所以sizeof§ 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。
13.如果一个指针变量 p 指向了数组的开头,那么 p+i 就指向数组的第 i 个元素;如果 p 指向了数组的第 n 个元素,那么 p+i 就是指向第 n+i个元素;而不管 p 指向了数组的第几个元素,p+1 总是指向下一个元素,p-1 也总是指向上一个元素。
//让 p 指向数组中的第二个元素:
1. #include <stdio.h>
2.
3. int main(){
4. int arr[] = { 99, 15, 100, 888, 252 };
5. int *p = &arr[2]; //也可以写作 int *p = arr + 2;
6.
7. printf("%d, %d, %d, %d, %d\n", *(p-2), *(p-1), *p, *(p+1), *(p+2) );
8. return 0;
9. }
运行结果:
99, 15, 100, 888, 252
现在两种方案来访问数组元素,一种是使用下标,另外一种是使用指针。
1) 使用下标
也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。
2) 使用指针
也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价于 *(p+i)。
14.数组名或数组指针,都可以使用上面的两种方式来访问数组元素。
数组名和数组指针的不同:
数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。
15.借助自增运算符来遍历数组元素:
1. #include <stdio.h>
2.
3. int main(){
4. int arr[] = { 99, 15, 100, 888, 252 };
5. int i, *p = arr, len = sizeof(arr) / sizeof(int);
6.
7. for(i=0; i<len; i++){
8. printf("%d ", *p++ );
9. }
10. printf("\n");
11. return 0;
12. }
运行结果:
99 15 100 888 252
第8 行代码中,*p++ 应该理解为 *(p++),每次循环都会改变 p 的值(p++ 使得 p 自身的值增加),以使 p 指向下一个数组元素。该语句不能写为 *arr++,因为 arr 是常量,而 arr++ 会改变它的值,这显然是错误的
16.p++、++p、(*p)++ 分别是什么意思呢?
1.*p++ 等价于 (p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素
2.++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
3.(*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。
1,空指针
空指针可以等于任何类型的指针 ,变量值是NUL L,实质是((void *)0)
2,坏指针
注意⚠️ 指针变量是NULL 或是未知地址,导致程序意外终止。导致c语言bug的重要原因之一。
3,注意⚠️ 对于指针 int型指向,即使数据一个字节就够了,但是依然会战占据四个字节。 0010 0000 0000 0000。其他类型数据原理相似。
4 ,void * 类型的指针 void * 不能进行解指针操作会进行崩溃。
5,传递指针的重要功能就是避免拷贝大型数据 。因为成本高效率大打折扣。例如1kb等
不允许把一个数赋值给指针变量
例如 int *p; p=1000; *p=&a; 错误的赋值