文章目录
- 一.内存和地址
- 二.指针变量和地址
- 1.指针变量
- 2.解引用操作符
- 3.指针变量的大小
- 三.指针变量类型的意义
- 1.解引用
- 2.指针+-整数
- 3.void* 指针
- 四.指针的运算
- 1.指针+-整数
- 2.指针-指针(没有指针+指针)
- 3.指针的关系运算
- 五.用指针求字符个数
一.内存和地址
现在有一栋宿舍楼,里面有100个房间,每个房间都没有门牌号,有一个朋友要来找你玩,是不是要挨个挨个房间来找你,这样效率很慢,但要是给每个房间都编一个号,并且你把房间号告诉你的朋友,那你的朋友是不是一下就能找到了。
我们买的电脑有8GB/16GB/32GB,这些内存会被划分成一个个内存单元,一个内存单元占1字节,1字节能存放8个二进制位的0/1, 并且每个内存单元都会有一个编码,这个编码就是地址,计算机有了这个地址就能快速找到此位置,对它进行读取,或修改。我们可以把内一个存单元想象成一间宿舍,一个内存单元能存放8位,就好比我们住的8人间,每个人就是一个比特位。
指针就是C语言给地址取的一个新名字
计算机内有很多硬件单元,那这些单元是如何相互协调工作的呢?答案是它们之间用线连接形成联系,比如:数据总线,控制总线,地址总线,而我们就只关心地址总线,电脑有32位操作系统和64位操作系统之分,32位操作系统的电脑就有32根地址总线,每根地址总线有0/1两种状态,一根地址总线就能传达2种信号,两根的话就是4种信号,那么有32根就能传达232种信号,每种信号都是不同的地址。
二.指针变量和地址
int a = 10;
创建了一个整型变量,并向内存中申请了四个字节空间来存储10,如下图:在内存中是以十六进制存储的, 0a就表示10
现在,我们打印a的地址来看看,打印地址需要用到取地址操作符(&)
可以看到只打印了四个地址中较小的地址,我们只要知道了起始地址,就能顺藤摸瓜访问到四个字节的数据。
1.指针变量
当我们得到了地址,那这个地址用什么存储呢?当然是指针变量了
int a = 10;
int * p = &a; //创建一个指针变量存储a的地址
拆解指针类型:
现在有一个char ch = ‘c’;
该用什么类型来存储ch的地址呢?
2.解引用操作符
如果我们需要对地址所指向的值作改变,就要用到解引用操作符*
int a = 10;
int * p = &a;
*p = 20; //相当于 a = 20;
*p就是通过p中存放的地址找到其所指向的空间
3.指针变量的大小
指针变量存放的是地址,因此我们只要知道地址的存放需要多少空间就能知道指针变量的大小。
64位操作系统下:
32位操作系统下:
上面我已经提到了32位机器有32根地址总线,每根地址总线发出来的电信号转换成数字信号后是0或1, 那我们把32根地址总线发出来的信号看成是32个存放0或1的bit位,也就是4个字节,因此指针变量的大小就是4个字节,64位机器同理。
既然指针变量的大小和类型无关,那还要那么多的指针类型干嘛呢?
三.指针变量类型的意义
1.解引用
先看下面两份代码
int a = 0x11223344; //存放的是一个十六进制数
int* pa = &a;
*pa = 0;
int a = 0x11223344;
char* pa = &a;
*pa = 0;
对比两份图,a的地址本来应该用int* 类型的指针变量来存放,但我们改成了char* 类型存放a的地址,可以看到int* 类型的指针变量在解引用时对其所在的4个字节中存放的值都做出了修改,而char* 类型的指针变量解引用时只对1个字节(最小的地址)中存放的值做出修改。
因此,我们可以得出结论:指针变量的类型决定了其解引用时有多大权限,即一次能访问几个字节。
2.指针±整数
先看下述代码
int a = 10;
int* pa = &a;
char* pc = &a;
printf("a:%p\n",&a);
printf("pa:%p\n",pa);
printf("pa+1:%p\n",pa+1);
printf("pc:%p\n",pc);
printf("pc+1:%p\n",pc+1);
首先可以明确的是a,pa,pc打印的地址相同,它们存放的都是变量a的地址。对比一下pa+1和pc+1,可以看到pa+1的结果是a的地址加4,而pc+1的结果是a的地址加1。pa+1就是a的地址往后移动4个字节的地址,pc+1就是a的地址往后移动1个字节的地址。
因此,我们可以得出结论:指针变量类型决定了往后(往前)移动几个字节
3.void* 指针
指针还有一种特殊的类型void* 类型,可以将其理解为无具体类型的指针(或者叫泛指针),void* 类型的指针可以接受任何类型的地址,但它不能够进行指针±整数和解引用的运算。
可以看到用char* 类型存放a的地址时会弹出"类型不兼容"警告,而用void* 类型就不会
那void* 类型的指针有什么用呢? void* 一般用于函数参数部分,用来接收不同类型数据的地址,这样可以实现泛编程的效果。
四.指针的运算
1.指针±整数
上面已经讲过,我就不多说了
2.指针-指针(没有指针+指针)
指针里面存放的是地址,那么指针相减得到的就是它们间存放的元素个数
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p1 = &arr[0];
int* p2 = &arr[9];
int r = p2-p1; // 高地址-低地址
3.指针的关系运算
首先说一下关系操作符有:> < >= <= != ==
因此指针间也可以相互比大小
举例:遍历数组
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr; //数组名是首元素地址while (p < arr + sz){printf("%d ", *p);p++;}return 0;
}
五.用指针求字符个数
#include<stdio.h>
#include<string.h>int main()
{char ch[] = "abcdef";size_t r = strlen(ch);printf("%zd\n", r);return 0;
}
第一种方法:用strlen库函数,求的是\0之前的,返回值类型是size_t, 需要包含头文件string.h
int my_strlen(char* p)
{int count = 0;while (*p != '\0'){count++;p++;}return count;
}
int main()
{char ch[] = "abcdef";int r = my_strlen(ch);printf("%d\n", r);return 0;
}
第二种方法:遍历法,首先明确ch数组里面存放的是a b c d e f \0
而我们只需要计\0之前的,把数组的首元素地址传给my_strlen函数,只要没有遇到\0,我们的地址就往后移一位(p++),指向下一个元素。
int my_strlen(char* p)
{char* start = p; //先用start存放首元素地址while (*p != '\0'){p++;} //循环执行完后p指向\0的地址return p - start; //指针-指针
}
int main()
{char ch[] = "abcdef";int r = my_strlen(ch);printf("%d\n", r);return 0;
}