
一.void*指针
1.定义:void*类型在指针类型中比较特殊,可以理解为无具体类型指针,这种类型的指针可以用来接收任意类型地址,但它不能直接进行指针的+-整数和解引用运算。
2.举例:
#include<stdio.h>
int main()
{
int a = 5;
int* pa = &a;
char* pc = &a;
return 0;
}
上面代码中,将一个int*类型的变量的地址赋给一个char*类型的指针变量,编译器会报错,因为类型不兼容。
此时我们可以运用void*类型指针来避免此问题发生。
void*类型代码如下:
#include<stdio.h>
int main()
{
int a = 5;
void* pa = &a;//void*类型指针//
void* pb = &a;//void*类型指针//
return 0;
}
因此,void*类型可以用来接接收任意类型地址。
3.局限性:
void*不能用来解引用和 指针+-整数的运算,都会使编译器报错
#include<stdio.h>
int main()
{
int a = 5;
void* pa = &a;
void* pb = &pa;
*pa = 10;
*pb = 0;
return 0;
}
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
void* p = &arr[0];//void*类型
int i = 0;
int sz = (sizeof(arr) / sizeof(arr[0]));
for (i = 0; i < sz; i++)
{
printf("%d", *(p + i));//+-整数
}
return 0;
}
4.作用:
一般void*类型指针使用在函数的参数部分,用来接收不同类型的数据地址,这样的设计可以实现泛型编程的效果,使得一个函数用来处理多种类型的数据。(后面我也会对它进行举例)
二.const修饰指针
1.const修饰变量:
变量是可以修改的,如果我们希望它不能被修改,此时就需要const来修饰此变量了。
#include<stdio.h>
int main()
{
int m = 0;
m = 20;//m可以被修改
const int n = 0;
n = 20;//n不能被修改
return 0;
}
上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n修改,就不符合语法规则,会报错,致使没法直接修改n。
但同时它也有局限性,我们绕过n,使用n的地址,去修改n就能做到了,虽然这是在打破语法规则:
#include<stdio.h>
int main()
{
const int a = 5;
int* p = &a;
*p = 10;
printf("%d", a);
return 0;
}
2.const修饰指针变量
(1)分类
int* const p;//const在*的右边
int const* p; const int* p;//const在*的左边
const int* const p ;//const同时在*的左边和右边
#include<stdio.h>
int main()
{
int n = 1;
int m = 2;
const int* p = &n;
*p = 10;//const在*左边时,不能对指针指向内容改变
p = &m;//const在*左边时,可以对指针(地址)进行改变
return 0;
}
#include<stdio.h>
int main()
{
int n = 1;
int m = 2;
int* const p = &n;
*p = 10;//const在*右边时,可以对指针指向内容改变
p = &m;//const在*右边时,不能对指针(地址)进行改变
return 0;
}
#include<stdio.h>
int main()
{
int n = 1;
int m = 2;
const int* const p = &n;
*p = 10;//const在*左右两边时,不能对指针指向内容改变
p = &m;//const在*左右两边时,不能对指针(地址)进行改变
return 0;
}
总结:const修饰指针变量时,
const如果在*左边:指针所指向的内容不能改变,但指针本身可以;
const如果在*右边:指针所指向的内容可以改变,但指针本身不可以;
const如果在*左右两边:指针所指向的内容和指针本身都不可以改变。
三.野指针
1.定义:顾名思义,指针十分“野",那也就是指针指向的位置时不可知的/随机的/不正确的/没有限制的。
2.常见形式:
(1)指针未初始化
#include<stdio.h>
int main()
{
int* p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
(2)指针越界访问
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
int i = 0;
for (i = 0; i <= 11; i++)
{
*(p++) = i;/*当指针指向的范围超出数组arr的范围时,
p就是野指针,即i=11时*/
}
return 0;
}
(3)指针指向的空间释放
#include<stdio.h>
int* test()
{
int n = 100;
return &n;//创建的是局部变量,进入该函数创建该变量,出去函数后销毁,将空间还给操作系统
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
3.规避野指针
(1)指针初始化
如果明确指针指向哪个地址就直接赋值地址,不知道明确的地址的话可以先赋值NULL。(NULL是C语言中定义的一个标识符常量,值是0,0也是地址,但这个地址无法使用,读写该地址会报错。)
#include<stdio.h>
int main()
{
int num = 10;
int* p1 = #
int* p2 = NULL;
return 0;
}
(2)小心指针越界
注意指针访问空间不要超过访问的范围
(3)指针变量不再使用时,及时置NULL:使用指针前判断指针是否为NULL,若是NULL就不去访问
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
int i = 0;
for (i = 0; i < 10; i++)
{
*(p++) = i;
}
p = NULL;//暂且后面指针不需要,及时置NULL
//.......(省略n条代码)
p = &arr[0];//后面需要使用时,重新让p获得地址
if (p != NULL)//判断p是否为NULL(空指针)
{
//......(省略n条代码)
}
return 0;
}
(4)避免返回局部变量的地址
四.assert断言
1.定义:assert.h头文件定义了宏assert(),在程序运行时如果符合指定条件就继续运行,若不符合就报错终止运行。这个宏常常被称为断言。
#include<assert.h>//头文件
assert(p!=NULL);
上述代码验证p指针是否等于NULL(空指针),若不等于NULL(符合assert里面的条件),程序继续运行,若不符合,则运行终止。
2.优势:
(1)自动标识文件和出问题的行号
(2)可以自动开启/关闭assert()语句
3.自动开启/关闭assert()语句
#define NDEBUG
#include <assert.h>
在#include <assert.h>前写上#define NDEBUG,此时会禁用文件中所有的assert(),若要使用assert(),则将#define NDEBUG删掉或注释掉
一般在Debug版本中使用,在Release版本中禁用assert(),有利于程序员在Debug版本中排查问题,在Release版本中不影响用户使用程序时的效率。
文章到这里就结束了,后面还会有对指针更深入的讲解,创造不易,如果喜欢的话点个关注,点个赞,谢谢大家

525

被折叠的 条评论
为什么被折叠?



