废话不多说直接上干货!
探究指针的奥秘之前,我们需要先了解内存是什么。
内存就是存放CPU上的数据的地方,就是我们所说的多少GB.内存被划分为一个个内存单元,一个内存单元的大小为1个字节(8个bit位)。
在C语言中,我们常说的指针其实就是地址,地址也就是内存单元编号。
指针 <=> 地址 <=> 内存单元编号
&:取地址操作符 用%p打印地址(取的是第一个字节的地址)
*:解引用操作符(间接访问操作符)
指针变量
指针变量是用来存放地址的,指针变量的大小取决于地址
eg. int a = 20;
int * pa = &a;
(pa是一个变量,叫做指针变量;*表示pa是指针变量;int * 是pa的类型;int表示pa指向的变量a的类型是int)
* pa = a(即通过pa的地址找a)
指针变量的大小与类型无关,只要指针的变量在相同的平台下,大小都是相同的。
X86环境下:指针变量大小为4个字节空间;
X64环境下:指针变量大小为8个字节空间。
解引用
char * 的指针解引用只能访问一个字节
int *的指针解引用只能访问四个字节
+-整数
char * 类型的指针变量+1跳过一个字节
int * 类型的指针变量+1跳过四个字节
eg. int * pa :pa + 1 => +1 * sizeof(int)
pa + n => +n * sizeof(int)
char * pc : pc + 1 => 1 * sizeof(char)
pc + n => n * sizeof(char)
void*
无具体类型的指针。(也叫泛型指针,可以接受任意类型地址,但是不能直接进行指针的+-整数和解引用运算)
const修饰指针变量
int * p //没有const修饰
int * const p //const在*的右边,即限制p,不限制*p(限制指针变量本身,指针不能改变它的指向(即p里存放的地址不能改变),但可通过指针变量修饰它所指向的内容(即值可变))
int const * p 或 const int * p //const在*的左边,即限制*p,不限制p(不能通过指针变量来修改它所指向的内容,但指针变量本身可以被改变)
int const * const p <=> p和*p都被限制了
指针-指针
无指针+指针(无意义)
指针-指针 计算的前提条件:两个指针指向的是同一块空间。
指针- 指针相当于地址相减:大地址-小地址为+;小地址-大地址为-
指针-指针的绝对值是指针之间的元素个数
野指针
指针指向的位置不可知(随机/不正确/无明确限制)
野指针会造成内存非法访问,甚至篡改内存中的内容。
野指针成因:1.指针未初始化,系统会默认为随机值。
2.指针越界访问。
3.指针指向的空间释放。例如,函数中的局部变量出了函数后空间已经不能使用了,但是main函数中的变量又运用了局部变量这块空间,则此时该变量为野指针)
如何避免野指针
1.指针初始化。
如果不知道指针指向哪里时,可以用NULL给指针赋值
NULL是C语言中定义的一个标识符常量,值为0,0也是地址,但是该地址无法使用,读写该地址会报错。
2.小心指针越界。
3.指针变量不再使用时,及时置为NULL,指针使用之前检查有效性。
4.避免返回局部变量的地址。
assert断言
assert()头文件为assert.h
assert(p != NULL)验证变量p是否等于NULL
如果不等于NULL,程序继续运行;否则就会终止运行,并且给出报错信息提示
传值调用和传址调用
传值调用
传址调用
数组名的理解
数组名是数组首元素的地址
但有两个例外:
1. sizeof(数组名)——这里的数组名是整个数组,计算的是整个数组的大小,单位字节
2. &数组名——这里的数组名也表示整个数组,取出的是整个数组的地址
使用指针访问数组
arr[i] <=> *(arr + i) <=> *(i + arr) <=> i[arr]
二级指针
用来存放一级指针的地址
int a = 20;
int *pa = &a;
int *ppa = &pa;//先通过*ppa找到pa,然后对pa进行解引用操作——*pa=a
注意!二级指针和二维数组没有对应关系!!!
指针数组
是存放指针的数组。是一个数组,该数组的每个元素都是一个指针
char arr[ ]——存放字符的数组
int arr[ ]——存放整型的数组
char * arr[ ]——存放字符指针的数组
int * arr[ ]——存放整型指针的数组
字符指针变量
char * p = “abcdef” = const char * p
此为是常量字符串,不能被修改
这里的赋值是将字符串中首字符的地址赋给p
使用%s打印字符串时,只需提供首字符的地址即可
数组指针变量
是指向数组的指针。是指针,数组指针变量中存放数组变量的地址
char * ——指向字符的指针
int * ——指向整型的指针
eg.int arr[10] = {1,2,3,4,5};
int (*p) [10] = &arr;
p是数组指针,p中存放的是数组的地址
数组指针类型为int (*)[10] (即把数组名去掉就是数组类型)
函数指针变量
函数名和&函数名都是函数的地址
函数指针类型:
解析上面代码:
int (*pf3)(int x,int y)
int :pf3指向函数的返回类型
pf3:函数指针变量名,必须有括号(pf3)
int x:pf3指向函数的参数个数
int y:pf3指向函数的个数
int (*)(int x,int y) //是pf3函数指针变量的类型
typedef关键字
typedef是用来类型重命名的
eg. typedef unsigned int uint
//将unsigned int 重命名为uint
如果重命名指针类型,例如把int * 重命名为 ptr_t
typedef int * ptr_t
如果重命名数组指针类型,例如把int(*)[5]重命名为parr_t
typedef int (*parr_t) [5] //新的类型命名必须在*的右边
int (*p)[6] = &arr;
parr_t p2 = &arr //这时的p2和p是一个东西
如果重命名函数指针类型,例如把void(*)(int)重命名为pf_t
typedef void(*pf_t)(int) //新的类型命名必须在*的右边
函数指针数组
把函数的地址存到一个数组中,这个数组就叫函数指针数组
在函数指针基础上定义函数指针数组
int (*parr1[3])()//parr1先和[ ]结合,说明parr1时数组;是int (*)()类型的函数指针数组
指针的分享就到这里!友友们,下次再见!