目录
一. 指针 - 入门
1.1 为什么需要指针?
① 指针的使用使得不同代码区域可以轻松地共享内存数据。尽管你也可以通过复制数据来达到相同的效果,但这往往效率不高。对于占用大量字节的结构体等大型数据,复制操作会消耗很多性能。然而,使用指针就可以有效避免这个问题,因为任何类型的指针所占用的字节数都是一样的(根据平台可能是4字节、8字节或其他)。
② 指针使得构建一些复杂的连续性数据结构成为可能,例如链表、链式二叉树等等。
③ 某些操作必须使用指针,比如操作申请的堆内存。此外,在C语言中,所有函数调用中的值传递都是"按值传递"的,如果我们要在函数中修改被传递过来的对象,就必须使用该对象的指针来实现。
1.2 什么是指针?
指针是一种特定的数据类型,包括int指针类型、char指针类型、double指针类型等等。
指针用于存储变量或数据在内存中的地址。它可以指向其他变量或数据的位置,通过指针可以直接访问或修改这些数据。
指针变量是用来存储内存地址的。
总结:
指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。
1.3 指针变量的定义
指针变量的一般形式为:数据类型 *指针变量名
解释:
① 数据类型表明指针所指向对象的类型
② * 表明声明的变量是指针
例如:
int *p; //定义了一个指针变量p,指向整型变量。
注意:
① 无论何种类型的指针变量,它们都是用来存放地址的,因此指针变量自身所占内存的大小和它所指向的变量数据类型无关,尽管不同类型的变量所占内存空间不同,但是不同类型指针变量所占内存空间大小相同。
② "*" 在指针声明时表示该变量是一个指针变量,而在使用指针时用于间接引用指针所指向的数据。
③ 指针的类型和它所指向变量的类型必须相同。
1.3.1 指针的大小
指针是一个用于存储内存地址的变量,它本身也需要在内存中占用一定的空间。
指针变量的大小通常是由编译器和所运行的计算机体系结构决定的。 指针的大小与所指向的数据类型无关。无论是指向整型、字符型、结构体还是其他类型的指针,在相同的系统和编译器下,它们的大小都是相同的,但是步长不相同。
在大多数32位系统上,指针通常占用4个字节的内存空间。而在64位系统上,指针通常占用8个字节的内存空间。
1.4. 指针的运算
1.4.1 赋值运算
指针的赋值运算是将一个地址赋给指针变量,使其指向该地址。
赋值运算使用赋值操作符"=",将一个地址或另一个指针赋给指针变量。
int num = 10; // 声明一个整型变量num,并赋值为10
int *p = # // 声明一个指向整型数据的指针p,并将num的地址赋给p
int *q; // 声明一个指向整型数据的指针q
q = p; // 将指针p的值(即num的地址)赋给指针q
&运算符用于获取变量的地址,&num代表变量num的地址。
指针变量p通过赋值运算符,将&num赋给它,使得p指向了变量num所在的内存地址。
需要注意的是,指针的类型必须与它所指向的数据类型相匹配。
在上述示例中,p和q都是指向int类型数据的指针,因此可以进行赋值运算。
指针的赋值运算是将一个地址赋给指针变量,从而使指针指向特定的内存位置。
通过指针,我们可以访问或修改该内存位置上存储的数据
1.4.2 算术运算
指针的算术运算是基于指针所指向的数据类型的大小来计算的,并且要求指针变量指向的是数组或是动态分配的内存块,而非指向单个变量的指针,所以在使用指针算术运算时,需要保证指针变量指向的是合法的内存地址。
指针加法运算:
当一个指针加上一个整数时,指针的值会增加相应的偏移量,偏移量取决于指针所指向的数据类型大小
例如,指针变量p指向一个int类型的数组,则通过p + N得到的是数组中第N个元素的地址(即&p[N])
指针减法运算:
当一个指针减去一个整数时,指针的值会减少相应的偏移量,偏移量取决于指针所指向的数据类型大小
例如,如果一个指针p指向一个int类型的数组,那么通过p - N可以得到指向数组中第N个元素前一个元素的指针,即p向前移动了N个元素的大小
1.5 空指针
意义:
空指针是不指向任何有效内存地址的指针,在C语言中,可以使用宏定义NULL或整数常量 0 来表示空指针,这样就标志此指针为空指针,没有指向任何空间。
注意:
对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未指向任何东西,因为对一个NULL指针解引用是一个非法的操作,所以在解引用之前,必须确保它不是一个NULL指针。
1.6 野指针
概念:
野指针就是指向的内存地址是未知的(随机的,不正确的,没有明确限制的)。
说明:指针变量也是变量,是变量就可以任意赋值。但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。
注:野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。int a = 10; int *p; p = a; //把a的值赋值给指针变量p,p为野指针,不会有问题,但没有意义 p = 0x555; //给指针变量p赋值,p为野指针,不会有问题,但没有意义 *p = 10; //对野指针进行赋值操作就出错了
1.6.1 野指针的成因
1. 指针未初始化:指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,所以它所指的空间也是随机的。
int main() { int * p; *p = 100; return 0; }
2. 指针越界访问:指针指向的范围超出了合理范围,或者调用函数时返回指向栈内存的指针或引用,因为栈内存是在函数结束时会被释放。
int main() { int arr[5] = {0}; int *p = arr; for(int i = 0; i <= 6; i++) { *(P++) = i; //当指针指向的范围超出数组arr的范围,p变成野指针。 } return 0; }
3 .指针释放后未置空:有时指针在free或delete后未赋值 NULL,并没有把指针本身忘记。此时指针指向的就是无效内存。
int main() { int *p = NULL; p = (int *)malloc(sizeof(int) * 5); free(p); return 0; }
1.6.2 规避野指针
1. 初始化指针
int main() { /*1.系统分配的内存*/ int a; int *p = &a; /*2.用户申请内存(堆内存)*/ char *q = (char *)malloc(128); return 0; }
2. 避免指针越界
int main() { int arr[5] = {0}; int *p = arr; for(int i = 0; i < 5; i++) { *(P++) = i;//严格遵守有效范围。 } return 0; }
3. 避免返回局部变量的地址
int * test() { int a = 20; return &a; //局部变量存在栈区,当被调函数结束后 ,栈区上局部变量的内存空间被释放, //若再去访问该空间就不合理了 } int main() { int *p = NULL; p = test(); printf("%d\n", *p); return 0; }
4. 开辟的指针释放后置为NULL
int main() { int *p = NULL; p = (int *)malloc(sizeof(int) * 128); //成功开辟内存,可以操作内存。 free(p); p = NULL;//避免野指针 return 0; }