概念
电脑内存被划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
计算机中的常见单位:
bit——比特位 1byte=8bit
byte——字节 1KB=1024byte
KB 1MB=1024KB
MB 1GB=1024MB
GB 1TB=1024GB
TB 1PB=1024TB
PB
每个内存单元能存放8个⽐特位的内容,且每个内存单元也都有⼀个编号,有了这个内存单元的编 号,CPU就可以快速找到⼀个内存空间。我们把这样的内存单元编号叫做地址,也叫做指针。
内存单元的编号=地址=指针
CPU访问内存中的某个字节空间,必须知道这个 字节空间在内存的什么位置,⽽因为内存中字节 很多,所以需要给内存进⾏编址。CPU与内存之间存在一组线叫地址总线,我们可以简单理解,32位机器有32根地址总线, 每根线只有两态,表⽰0,1【电脉冲有⽆】,那么 ⼀根线,就能表⽰2种含义,2根线就能表⽰4种含 义,依次类推。32根地址线,就能表⽰2^32种含 义,每⼀种含义都代表⼀个地址。所以32位情况下,指针占32个比特位,也就是4个字节。同理,在64位情况下,指针占8个字节。
指针变量和地址
在C语⾔中创建变量其实就是向内存申请空间,由图可知,现在a变量为整形,地址占4字节的内容,为0x00D3FAB4、0x00D3FAB5、0x00D3FAB6、0x00D3FAB7
c语言中存在一个操作符可以得到变量的地址:&
&a取出的是a所占4个字节中地址较⼩的字节的地 址也就是最下面的那个(在内存中,地址是由下到上依次变大)虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可 ⾏的。(字符型变量地址占一个字节,但它所对应的指针变量仍然是4字节大小)
解引用
int a=10;
int *pa=&a;
这里pa就是a的指针变量,int *pa中int指a的类型,*表示pa定义为指针变量。
那么pa就是a的指针的意思。
“ * ”是解引用符,*pa就是a的内容。我们可以通过*pa对a的内容进行修改。
指针解引用访问的大小取决于指针类型,int*指针能访问4字节,char*指针只能访问1字节,
我们可以在定义指针式使用强制转换:
int n = 0x11223344;
char *pc = (char *)&n;
这里n是个整型变量,对应指针解引用访问4字节,可以用(char*)强制把地址转换成字符指针形式,解引用访问1字节。
指针+-整数
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
运行结果:
说明指针+-整数能控制指针向前、后走一步,而指针类型能决定一步的大小,char*一步1字节,int*一步4字节。
const修饰变量
变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制,不能被修改,那么就用到const。
const int a=10;这里修饰了变量a,所以a就不能被修改了,但还是可以通过指针修改a的内容。
const int *pa=&a;这里修饰了*pa,a的内容无法通过指针更改了,但指针变量pa还是可以修改的。
int * const pa=&a;这里修饰了指针变量pa,所以pa无法被修改,但可以修改pa所指向的变量a。
指针的关系运算
指针+- 整数(指针向前/后移动整数位)
指针-指针(两个指针之间的距离)
野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
成因可能为以下几种:
int *p;//局部变量指针未初始化,默认为随机值
——————————————————————
int arr[10] = {0};
int *p = &arr[0];
int i=0;
for(i=0;i<11;i++)
{
*(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针
}
——————————————————————
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);//指针指向的空间释放
return 0;
}
规避野指针的方法:1.指针初始化 2.⼩⼼指针越界 3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性 4.避免返回局部变量的地址
(当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的 时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使⽤指针之前可以判断指针是否为NULL)
assert断⾔
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏常常被称为“断⾔”。
我们可以通过assert(p != NULL);来限制程序的执行,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序 继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
指针的运用场景
传址调⽤——由于实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参,当我们要通过函数改变实参时,就要运用指针。
——————————————————
使⽤指针访问数组:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
数组名arr本就是数组第一个元素的地址,跟&arr[0]同义。
所以int* p=arr;也可以写成int* p=&arr[0];
arr基本在任何时候都能表示数组首元素的地址,只有两个例外:
sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩, 单位是字节。
&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)此时对应的指针移动单位为一个数组长度。
数组名arr是数组⾸元素的地址,可以赋值 给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,p[i]、*(p+i)也可 以访问数组。甚至i[p]、i[arr].
(一维数组传参的本质传递的是数组⾸元素的地址,所以在函数中能够修改真实的数组数据,而既然传递的是指针,那么在函数中就无法使用sizeof求传入数组的大小)
(同理,二级数组的数组名就是第一排数组的地址)
数组指针变量
定义:存放的是数组的地址,能够指向数组的指针变量。
写法:int (*p1)[10];
与int * p2 [10];的区别:p1是指向能存放10个整形变量的数组的指针
p2是能存放10个整形指针变量的数组
像p1这样的就是数组指针
int arr[10] = {0};
int(*p1)[10] = &arr;//初始化p1
函数指针变量
定义:存放函数地址的变量
(函数的地址就是函数名)
这种变量写起来固然很繁琐,所以我们可以通过typedef关键字进行类型的重命名,比如:
将unsigned int重命名为unit
又比如:将void (*)(int)重命名为pfun_t
函数指针数组
把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组
写法int (*p4[3])();
p4 先和 [] 结合,说明 p4是数组,是 int (*)() 类型的函数指针。