一 , 内存和地址
1.1 内存
从生活出发,如果有一栋房子,里面有100间房间,但是房间没有门牌号,你们朋友就居住在其中一间,如果你要找到你的朋友,就得挨个房间挨个房间的去找,但是如果我们根据楼层,还有楼层房间的排序,给每一个房间编上号码,我们就可以快速定位一个房间,提高查找效率。
一楼 : 101, 102 , 103...
二楼:201,202,203...
....................................................
把上面的例子对应到我们的计算机中:
我们可以知道,CPU(中央处理器)在处理数据的时候,是在内存中读取数据,然后经过处理后,再放回内存中。我们知道,电脑中的内存有8G/16G/32G,内存的空间很大,为了更好的使用和管理内存,我们把这些内存空间被划分成一个个内存单元,每一个内存单元的大小取一个字节。
计算机常见单位:
1个比特位可以存储一个2进制的位1或者0
1 Byte = 8Bit
1 KB = 1024Byte
1 MB = 1024KB
1 GB = 1024MB
1 TB = 1024GB
1 PB = 1024TB
........
每个内存单元,可以看作一个学生宿舍,一个字节的空间可以放8个比特位,就相当于一间宿舍可以住8个人,每一个人是一个比特位。
每个内存单元也有一个编号,通过这个编号,CPU可以快速找到一个内存空间。生活中,我们把门门牌号称为地址,在计算机中,我们也把内存单元的编号成为地址。C语言中给地址取了一个新名字:指针。
内存单元编号 = 地址 = 指针
1.2 编址
CPU访问内存中的某个字节空间,必须知道该字节空间在内存中的什么位置,因为内存中的字节很多,所以需要给内存单元进行编址。计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件完成的。
计算机内是由很多的硬件单元,而硬件单元是要互相协同合作的。所谓协同,至少互相之间能够进行数据传递。但是硬件与硬件之间是互相独立的,如何通信?如何进行数据传递?其实是通过“线”连起来。 而CPU和内存之间也是有大量的数据交互的,所以二者也有通过线连接起来。
二 , 指针变量和地址
2.1 取地址操作符( & )
变量创建的本质: 向内存中申请一块空间
int main()
{
int a = 0x1223344;
return 0;
}
上述代码创建了整形变量a,内存中申请4个字节,用于存放数据,其中每个字节都有地址,如何取出a的地址呢?C语言中提供了一个操作符 & ,用于取地址,取出的地址可以使用 %p 进行打印;
int main()
{
int a = 0x11223344;
&a; //取出a的地址
printf("%p\n", &a);
return 0;
}
经过调试,打印,我们发现 &a 取出的是a所占4个字节中地址较小的字节的地址。
虽然整型变量占4个字节,但是只要知道了第一个地址,一个内存单元占1个字节,就可顺藤摸瓜的可以访问到4个字节的数据。
2.2 指针变量
我们使用 & 取出一个数值的地址,为了方便以后使用,我们需要把它存储起来,用什么存储? 存在指针变量中。指针变量也是一中变量,这种变量是专门用来存放地址的,而存在指针变量中的值相应的会被理解成地址。
int main()
{
int a = 10;
int* pa = &a;//取出a的地址并存储在指针变量pa中
printf("%p\n", &a);
printf("%p\n", pa);
return 0;
}
这里的pa是指针变量,里面的存放的是地址。
2.3 指针变量的类型
int n = 10;
char m = 'a ' ;
int * pa = &a;
我们知晓 n 是 整型变量(int) , m 是字符型变量(char) , 推而得之 ,pa的类型是(int * );
我们如何理解指针类型 int * ?
2.4 解引用操作符( * )
前面我们知道,用指针变量来存储数据地址,方便未来使用,那如何使用呢?
现实生活中,我们是通过地址找到房间,然后再房间里拿物品,存物品
C语言中也一样,我们拿到了地址(指针),通过地址找到地址所指向的对象。
int main()
{
int a = 20;
int* pa = &a;
printf("%d\n", *pa);//通过地址找a
*pa = 30; //通过地址找到a并修改a
printf("%d\n", a);//通过地址找a
return 0;
}
这里我们使用了解引用操作符,*pa的意思就是用过pa中存放的地址,找到指向的空间,而*pa所对应的就是a变量;所以*pa=30;这个操作就是把a改成了30;
int *pa = &a; ------- pa是指针变量,a的地址
*pa -------- 是变量a
使用 *pa ,是把对a的操作交给了pa,这样对a的修改,就有多了一种途径,写代码会更加灵活!
2.5 指针变量的大小
指针变量有多大?
== > 指针放的是地址 == >地址存放要多大空间?
== > 指针变量的大小取决于地址的大小
![](https://i-blog.csdnimg.cn/direct/797d126dc5dd43cb828ab4ae6f0fa4b4.png)
![](https://i-blog.csdnimg.cn/direct/cdfbdb35f1f94689ba7df5c1b5a608b9.png)
注意指针变量的大小和类型是无关的,只要指针类型的变量,再相同平台下,都是相同的;
nt main()
{
printf("%zd\n", sizeof(short*));
printf("%zd\n", sizeof(int*));
printf("%zd\n", sizeof(long*));
printf("%zd\n", sizeof(char*));
printf("%zd\n", sizeof(float*));
printf("%zd\n", sizeof(double*));
return 0;
}
• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节• 注意指针变量的大小和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
三 , 指针变量类型的意义
指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,那么为什么还需要有各种各样的指针类型? 定义不同的指针类型有什么意义?
3.1 指针的解引用
运行如下代码,进入调试界面,观察内存的变化
//代码一:
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
//代码2:
int main()
{
int a = 0x11223344;
char* pa = &a;
*pa = 0;
return 0;
}
通过调试我们可以发现,代码1会把4个字节全部改为0,但是代码2只是把1个字节改为0。
所以,指针类型决定了,对指针解引用时有多大的权限(一次能操作多少个字节)。int *类型的指针解引用可以访问4个字节,但char*类型的指针解引用只能访问以个字节。
3.2 指针+-整数
int main()
{
int a = 10;
int* pa = &a;
char* pb = &a;
//pa
printf("pa-1 = %p\n", pa - 1);
printf("pa = %p\n", pa);
printf("pa+1 = %p\n", pa+1);
printf("\n");
//pb
printf("pb-1 = %p\n", pb - 1);
printf("pb = %p\n", pb);
printf("pb+1 = %p\n", pb + 1);
return 0;
}
我们可以看出,char * 类型的指针变量+1跳过一个字节 。int * 类型的指针变量+1跳过了4个字节。
指针的类型决定了指针向前或者向后走一步有多大(距离)
3.3 void* 指针
在指针类型中,有一种特殊的类型 --- void* 类型 ,可以理解为无具体类型的指针(泛型指针) ,可接受任何类型的地址,但是不可以直接进行指针的+ - 整数和解引用运算!
int main()
{
int a = 10;
int* pa = &a;
char* pb = &a;
return 0;
}
但是当我们使用void * 型来接受地址的话,不兼容的问题解决了:
void* 类型的指针可以接受不同类型的地址,但是不能进行运算!一般我们会把void * 类型的指针用来接受其他人传来的地址,因为不知道地址的类型,然后在使用指针的时候进行强制类型转化即可,使得函数来处理多种类型的数据。
四 ,const修饰操作符
const 常属性 ,被const 修饰后变量就具有常属性,变成了常变量,数值不可被修改了;
1.const 修饰普通变量
2.const 修饰指针变量
4.1 const 修饰普通变量
int main()
{
const int n = 10;
n = 10;
printf("%d\n", n);
return 0;
}
上述代码中n是不可以被修改的,这里的 n 在 C 语言中 n 属于常变量,但是n本质上还是变量,因为有const 的修饰,编译器在语法上是不允许修改这个变量的;
但是如果我们绕过 n , 使用 n 的地址,然后修改 n 就可以做到了,但是这样是打破语法规则;
int main()
{
const int n = 10;
printf("%d\n", n);
int* pn = &n;
*pn = 30;
printf("%d\n", n);
return 0;
}
我们可以发现,这里的n指确实被修改了,但是 const 就失去的使用的意义, 使用const 修饰就是为了让 n 值不被修改 , 如果 pn 拿到 n 的地址 ,就能修改 n ,打破了 const 的限制 , 这是不合理的 , 所以应该改让 pn 可以拿到 n 的地址 , 但是不能修改 n ;
4.2 const 修饰指针变量
一般来讲 const 修饰指针变量 , 可以放在 * 的左边,也可以放在 * 右边 ,放在不同位置,他们的意义是不一样的。
int* p; //没有const 修饰
int const* p;
const int* p; //const 放在*的左边做修饰
int* const p; //const 放在*的右边做修饰
//左边 -- 表示指针指向的内容不能通过指针来改变了,但是指针变量本身的值(地址)是可以改的
int main()
{
const int num = 10;
int n = 100;
const int* p = #
//p= &n;
//*p = n;
//const 放 * 左边修饰的是 *p 但是对p不限制;
return 0;
}
//右边 --- const 限制的是p(指针变量),但是指针指向的内容是可以通过指针变量来改变的
int main()
{
const int num = 10;
int n = 100;
int* const p = #
*p = 20; //ok
//p = &n;
printf("%d\n", num);
return 0;
}
//左右两边 --- 指针变量(p)(地址)不可以被修改,指针变量指向的内容也不可以被修改
int main()
{
const int num = 10;
int n = 100;
const int* const p = #
//p = &n;
//p = 200;
return 0;
}
- 所以,const如果放在 * 的左边,修饰的是指针所指向的内容,保证指针所指向的内容不能通过指针来改变,但是指针变量本身的内容可变。(地址可变,值不变)
- const 放在 * 右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针所指向的内容,可以通过指针改变的。(值可变,地址不可变)
- const 放在 * 左右两边,都不可以改变;
五 ,指针运算
指针的基本运算有三种,
- 指针 + - 整数
- 指针 - 指针
- 指针的关系运算
5.1 指针 + - 整数
数组在内存中是连续存放的,所以只要知道第一个元素的地址,就可以顺藤摸瓜的找到后面的元素。
//指针 +- 整数
//采用指针的方式打印数组元素---通过起始地址
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = &arr[0];
for (i = 0; i < sz; i++)
{
printf("%d ",*(p+i));
}
return 0;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = &arr[0];
for (i = sz-1; i >=0; i--)
{
printf("%d ", *(p + i));
}
return 0;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = &arr[0];
for (i = 0; i < sz; i++)
{
printf("%d ", *p );
p ++;
//缺陷就是,p值改变
}
return 0;
}
5.2 指针 - 指针
//指针 - 指针(地址 - 地址 )得到指针和指针之间的元素个数(绝对值)
//运算的前提条件是,两个指针指向同一块空间;
//int main()
//{
// int arr[10] = { 0 };
// printf("%d\n", &arr[9] - &arr[0]);//9
// printf("%d\n", &arr[0] - &arr[9]);//-9
//
// return 0;
//}
//求字符串长度
//#include <stdio.h>
//int main()
//{
// char arr[] = "abcdef";
// size_t len = strlen(arr);
// printf("%d \n", len);
// //strlen 统计的是字符串中\0之前的字符个数
// return 0;
//}
//size_t my_strlen(char* s)
//{
// int count = 0;
// while (*s != '\0')
// {
// count++;
// s++;
// }
// return count;
//}
//
//int main()
//{
// char arr[] = "abcdef";
// size_t len = my_strlen(arr);
// printf("%zd\n", len);
// return 0;
//}
//size_t my_strlen(char* s)
//{
// char* start = s;
// while (*s != '\0')
// s++;
// return s - start;
//}
//int main()
//{
// char arr[] = "abcdef";
// size_t len = my_strlen(arr);
// printf("%zd\n", len);
// return 0;
//}
5.3 指针的关系运算
//指针关系运算
//int main()
//{
// int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// int* p = arr;
// int sz = sizeof(arr) / sizeof(arr[0]);
// while (p < &arr[sz])
// {
// printf("%d ", *p);
// p++;
// }
// return 0;
//}
//int main()
//{
// int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// int sz = sizeof(arr) / sizeof(arr[0]);
// int* p = &arr[sz - 1];
// while (p >= &arr[0])//p >= arr
// {
// printf("%d ", *p);
// p--;
// }
// return 0;
//}