1.内存的使用和管理
内存被划分为一个一个的内存单元,每个内存单元的大小是1个字节,一个字节又是8个bit位
每个内存单元都有一个编号,虽然没有明确写出编号,但我们给地址一个编号就能自动找到那个内存单元
内存单元的编号==地址==指针
2.指针变量和地址
2.1取地址操作符---&
C语言中创建变量其实就是向内存中申请空间
创建了整型变量a,内存中申请4个字节,用于存放10,每个字节都有地址
&------取地址操作符,拿到变量a的地址。取出的是a所占4个字节中地址较小的字节的地址
变量的名字仅仅是给程序员看的,编译器不看名字,编译器是通过地址找内存单元的
2.2指针变量和解引用操作符(*)
2.2.1指针变量
指针变量也是一种变量,这种变量就是用来存放地址(指针)的,存放在指针变量中的值理解为地址
在指针变量眼里什么都是地址
关于指针pa有三个相关的值
1.pa,pa里面放着一个地址
2.*pa,pa指向的那个对象
3.&pa,表示的是pa变量的地址
2.2.2解引用操作符(间接访问操作符)
pa中存放的是a的地址,对指针变量解引用-----*pa,得到的就是a变量
我们可以通过指针变量改变a的值
2.2.3指针变量的大小
指针变量是用来存放地址的,一个地址的内存需要多大空间,那么指针变量的大小就是多大
指针变量的大小和类型是没有关系的,只要是指针类型的变量在相同环境下,其大小是相同的
32位环境(x86),地址是32个bit位(四个字节)
64位环境(x64),地址是64个bit位(八个字节)
3.指针变量的意义
3.1指针的解引用
int类型会将n的四个字节全部改为0,但是char类型只是将第一个字节改为0
总结:指针的类型决定了对指针解引用的时候有多大的权限(一次能操作几个字节)
3.2指针+-整数
char*类型的指针变量+1跳过1个字节;
int*类型的指针变量+1跳过了4个字节
int*p;
p+i 是跳过i*sizeof(int)个字节
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)
3.3 void*类型的指针
可以接受不同类型的地址,但是void*类型的指针不能直接进行指针的+-整数和解引用的运算
4.const修饰指针
4.1 修饰变量-------变量是可以修改的,如果把变量的一个地址交给一个指针变量,通过指针变量可以修改这个变量,但是我们可以加上一些限制
const修饰变量的时候,叫常变量
这个被修饰的变量本质上还是变量,只是不能被修改
4.2const修饰指针变量
1)const在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变
但是指针变量本身的内容可变。
2)const在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容可以通过指针改变。
5.指针运算
指针+-整数
指针-指针(指针+指针没有意义)
指针的相关运算
5.1指针+-整数
指针类型决定了指针+1的步长,决定了指针解引用的权限
5.2指针-指针
两个指针相减的绝对值是指针和指针之间的元素个数
前提:两个指针指向的是同一个空间
5.3指针的关系运算
6.野指针
野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
6.1野指针的成因
1)指针未初始化
int main()
{
int * p;//局部变量指针未初始化,默认为随机值
*p =20;
return 0;
}
2)指针越界访问
int main()
{
int arr[10]={0};
int * p=&arr[0];
for(int i=0;i<=11;i++)
{
*(p++)=i;//当指针指向的范围超出数组arr的范围时,p就是野指针
}
return 0;
}
3)指针指向的空间释放
int * text()
{
int n=100;
return &n;
}
int mian()
{
int * p=text();//野指针
printf("%d\n",*p)
return 0;
}
6.2怎么规避野指针
1.指针初始化
如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL(C语言中定义的一个标识符常量,其值为0,0也是地址,只是这个地址是无法使用的,读写该地址会报错)
int * p=NULL;
空指针是无法直接访问的
2.小心指针越界
3.指针变量不再使用时,及时置为NULL,指针使用前检查有效性
4.避免返回局部变量的地址
7.assert断言
assert.h头文件定义了宏asser(),用于在运行时确保程序符合指定条件,如果不符合就报错终止运行,这个宏常被称为“断言”。
如果表达式为真,就正常运行什么都不发生。如果表达式为假就会显示没有通过的表达式,以及包含这个表达式的文件名和行号(好处之一)
另外一个好处就是不需要更改代码就可以开启或关闭assert()的机制
确认程序没有问题不需要再做断言,定义一个宏NDEBUG
一样的代码,这时就不会发生错误
我们一般在Debug中使用,在vs中,release中就直接优化掉了
8.指针的使用和传址调用
8.1模拟实现strlen
求字符串\0之前的字符个数
size_t strlen (const char * str)
size_t是一种无符号整型
指向的字符串不期望被修改
8.2传值调用和传址调用
传址调用,可以让函数和主函数之间建立真正的联系,在函数内部可以修改主函数中的变量;所以如果函数中只是需要主调函数中的变量值来实现,就可以采用传值调用。
如果函数内部要修改主调函数中的值,就需要传址调用