文章目录
一、内存和地址
1、内存
内存是电脑上的一块存储区域,可读可写,速度较快,关机时就会清空,可以跟CPU直接进行运算,内存的每个内存单元为一个字节
一个字节由八个比特位存储
一个比特可以存放一个二进制的位1或位0.
内存在使用时分为:
栈区、堆区、静态区。
2、地址
每个内存单元都有一个固定的地址,不需要保存,就像钢琴的每个键固定发出特定的声音,不需要在上面做标记,这是对一个懂钢琴的人来说的。
内存的每个单元都是有对应的地址,电脑自己知道每个地址在哪里。
CPU通过地址地址来编址的,在64位机器中地址用八个字节表示,32位机用4个地址表示。
CPU通过数据总线和控制总线共同和地址总线完成操作。
在C语言中,也引入了地址这个概念,用指针这个词来表示。
地址 <==>指针
二、指针变量和地址
1、指针变量
指针变量的值是地址,指针变量在创建时有:
指针变量类型 * 变量名
* 星号表示这个变量是指针
#include<stdio.h>
int main()
{
int* p;
return 0;
}
创建了一个变量名称为p的int*类型的指针变量
此时指针p没有存放仍会值
2、取地址&
指针变量存放的是地址
用&取地址操作符可以取出地址
#include<stdio.h>
int main()
{
int* p;
int a = 10;
p = &a;
return 0;
}
创建了一个名为a的int类型的整形变量,把a的地址取出来赋值给p,此时p指针存放的就是a的地址
用%p打印地址看到a的地址和p存放的值一样
3、解引用操作符(*)
*这个星号在不同时候的作用不一样:
在只有一个操作符时它的作用是解引用
在类型后面表示的是指针
在两个变量中间使用表示乘法
#include<stdio.h>
int main()
{
int* p;//*表示指针
int a = 10;
p = &a;
*p = 20;//*表示解引用,只有一个操作数
a = 10 * 10;//*表示乘法
printf("&a=%p\n", &a);
printf("p =%p\n", p);
return 0;
}
*为解引用时可以把指针存放地址对应的值解出来
可以通过指针的解引用来改变指针指向对象的值
4、指针的大小
在地址总线相同的情况下,所以类型的指针占用的字符大小相同
在64位下指针的字节数为8
在32位下指针的字节数为4,X86代表32位
因为指针存放的只是地址所以不同类型的指针大小也相同
三、指针变量类型的意义
1、指针类型决定了解引用的大小
因为指针大小都一样,所以指针类型的作用就没有了吗?
并不是的,类型决定了解引用时访问的字节个数
比如:
int类型是4个字节,解引用时从指针存放的地址往后读4个字节
char类型是1个字节,解引用时从指针存放的地址往后读1个字节
2、指针类型决定了指针±整数往后读的字节数
不同类型的指针在加减整数时地址跳过的字节数不同
在int类型下的指针加减
可以看到加减都只跳过了4个字节,因为int类型占4个字节
在char类型下的指针加减
可以看到跳过了1个字节,说明了指针类型存在的意义
3、void*指针
在不确定类型的指针可以用void*
void*指针又被称为泛性指针
但void*指针不能直接解引用,因为不知道要读取几个字节,通常在函数中使用void*
使用时加上强制类型转换
四、const修饰指针
const 是一个关键字,当它修饰的对象具有常属性
1、const修饰变量
当const修饰指针变量时,表示不能改变指针的存放的地址
#include<stdio.h>
int main()
{
int a = 20;
int* const p = &a;
int b = 100;
//p = &b;//此时不会让我们改变指针变量的指向
*p = 100;//但可以改变指针对象的内容
return 0;
}
const 在*星号的右边,const 修饰的是变量p,保证p的内容不变修改
2、const修饰指针变量
当const修饰指针时,指针解引用的内容不能被修改
#include<stdio.h>
int main()
{
int a = 20;
const int* p = &a;
//等价于 int const * p=&a
int b = 100;
p = &b;//此时我们可以改变指针变量的指向
//*p = 100;//但不可以改变指针对象的内容
return 0;
}
当const 在*星号左边时,const修饰的是指针解引用的内容
五、指针的运算
1、指针±整数
运行于数组中,数组中的地址是连续的,可以运用指针加减来方法其他元素
2、指针-指针
指针-指针的绝对值可以算数之间的元素个数
不存在指针+指针
就像日期加天数可以知道几天后的日期
日期-日期可以知道直接差多少天
而日期+日期没有意义
六、野指针
1、指针未初始化就使用
#include<stdio.h>
int main()
{
int* p;
*p =20;
return 0;
}
这样的指针就是野指针
2、指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
*p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3、避免野指针的方法
在初始化时,把指针置NULL
#include<stdio.h>
int main()
{
int* p =NULL;
*p =20;
return 0;
}
七、assert断言
在使用指针时用asser()函数断言看是否满足条件,满足条件就结束,防止后面程序崩溃
使用时引入头文件<assert.h>
#include<stdio.h>
#include<assert.h>
int main()
{
int* p = NULL;
assert(p != NULL);
return 0;
}
它要求不等于NULL,如果还等于NULL就会报错
当确保程序没有问题时可以用关闭assert,让asser失效,
在程序开始加入#define NDEBUG
#define NDEBUG
#include<stdio.h>
#include<assert.h>
int main()
{
int* p = NULL;
assert(p != NULL);
return 0;
}