***内存和地址
1.你可以把它想作每一栋楼里都有一个专属的房间号,按照房间号就可以找到对应的一个人
2.同样,内存的使用和管理:把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节
3.计算机中的单位:
*⼀个⽐特位可以存储⼀个2进制的位1或者0
1Byte(字节) = 8bit(比特)
1KB = 1024Byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
常用的就是上面的,还有其他感兴趣可以去查查
4.计算机中:内存单元的编号----也称为地址----也叫做指针
5.数据是通过数据总线接互的,地址是通过地址总线接互的
6.CPU和内存之间也是有⼤量的数据交互的,所以,两者必须也⽤线连起来,其中一组线叫地址总线 |
7地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器。 |
***指针变量和地址
1.&取地址操作符
如果有一个值a,取地址后,a所占的就是字节在地址最小的字节地址,知道了第一个地址,剩下的有顺其自然知道了。
eg:打印出的值就是四个字节中最小的地址
#include<stdio.h>
int main()
{
int a=10;
&a;
printf("%d",&a);
}
2.指针变量和解引⽤操作符(*)
1)指针变量:取地址值后存在的地方
#include<stdio.h>
int main()
{
int a=10;
int* pa=&a;
return 0;
}
pa是一个变量,是用来存放地址(指针)的
*表示的是说明pa是指针变量
int表示pa指向的变量a的类型的int
同样char类型:
eg:char ch='w';
-->char * pc=&ch ---注意一定要记得&这个符号(初学容易忘记)
为什么要加?指针放的是地址,所以初始化时有&符号
3.解引用操作符*
作用:将地址保存起来,未来是要使⽤的
区别:
a&b是按位与
&a是取地址
a*b乘法
*a //a是指针变量,*就是解引用操作符
#include<stdio.h>
int main()
{
int a=10;
int* pa=&a;
*pa=20;
return 0;
}
这样就可以将a从原来的10变成了20
4.指针变量的大小
32位平台下地址是32个bit位,指针变量⼤⼩是4个字节 (vs中x86)
• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节(vs中x64)
• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的
5.指针的解引用
#include<stdio.h>
{
int a=0x11223344;
int* pa=&a;
*pa=5;
return 0;
}
一个16进制位占4个二进制位
两个16就是8个二进制位,8个一个字节
这样就填满了
结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节
6.指针+-整数
解引用决定了我们通过地址访问这个元素的时候怎么访问
而加一减一是通过向后偏移,这样我们只要找到这一个位置
空间的起始位置来顺藤摸瓜来找到那个元素
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。
int* +1----跳动4个字节
char* +1---跳动1个字节
eg:
int* pa: pa+1----> +1*sizeof(int)
pa+n-----> +n*sizeof(int)
char* pa:pa+1----> +1*sizeof(char)
pa+n-----> +n*sizeof(char)
7.void* 指针
⽆具体类型的指针(也称泛型指针) |
注意::void*不可以进行指针+-整数
#include <stdio.h>
int main()
{
int a=10;
void* pa=&a;
*pa=20 //这样是错误的
return 0;
}
8.const 修饰指针
变量的地址交给⼀个指针变量,加上后就不可以被修改
9.const修饰指针变量
这分为3种情况:*放左边,右边,两边?
int const * p; 左边
int * const p; 右边
int const * const p 两边
在此之前,我们先搞清:关于指针p有3个相关的值
1.p,p里边放着一个地址
2.*p,p指向的那个对象
3.&p,表示的是p变量的地址
放左边:
#include <stdio.h>
//代码2 - 测试const放在*的左边情况
void test1()
{
int n = 10;
int m = 20;
int const *p = &n;
*p = 20;//不可以
p = &m; //ok,可以执行
}
放右边:
#include <stdio.h>
//代码2 - 测试const放在*的右边情况
void test1()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20;//可以
p = &m; //不可以执行
}
两边
#include <stdio.h>
//代码2 - 测试const放在*的两边情况
void test1()
{
int n = 10;
int m = 20;
int const *const p = &n;
*p = 20;//不可以
p = &m; //不可以执行
}
总结:(这里好绕啊啊啊啊啊,比较难分清,谁能更加清晰的理解啊啊)
.const如果放在*的左边,修饰的是指针指向的内容(*p),保证指针指向的内容(p=&m)不能通过指针来改变。
但是指针变量本⾝的内容可变(p=&m)。
• const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容(p=&m)不能修改,但是指针指向的内容(*p),可以通过指针改变。
两边都不可以
10.指针的+-整数
1)因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素
2)指针类型决定了指针+1的步长决定了指针解引用的权限
3)数组在内存中是连续的~
#include<stdio.h>
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int *p=&arr[0];
int sz=sizeof(arr)/sizeof(arr[0]);
int i=0;
for(i=0;i<sz;i++)
{
printf("%d",*(p+i));
}
return 0;
}
11.指针-指针
指针-指针的绝对值是指针和指针之间元素的个数
指针-指针,计算的前提条件是两个指针指向的是同一个空间
eg:*p=arr1[];
*b=arr2[];
*p-*b 这种是不可以的
strlen其实统计的是字符串中\0之前的字符个数
数组名其实是数组首个元素的地址 arr=&arr[0]
12.野指针
1)野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
2)成因:p是局部变量,但是没有初始化,其值是随机值的,如果
将p在存放的值当作地址,解引用操作符就会形成非法访问
3)如何避免:如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL
4)针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性
俗成规定:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL。
13.assert 断⾔
1)使用时要assert.h 头⽂件
⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
2)还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问
题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG 。
#define NDEBUG
#include <assert.h>
14.传值调⽤和传址调⽤
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。
如果函数内部要修改主调函数中的变量的值,就需要传址调⽤。