const修饰,野指针成因与规避,assert断言

今天,我们继续来讲指针的内容

1.const修饰

1.1const修饰变量

如果我们给定一个整型变量,那么这个变量的值是可以被修改的。为了使它的值不能被修改,那我们就可以使用const来修饰这个变量,如图:

#include 
int main()
{
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}
上述代码中,我们给n前面加上const之后,它的值就不能被修改了。其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n
但是, 但是如果我们绕过n使⽤n的地址,去修改n就能做到了,虽然这样做是在打破语法规则:
#include 
int main()
{
const int n = 0;
1
2
3
4
printf("n = %d\n", n);
int* p = &n;
*p = 20;
printf("n = %d\n", n);
return 0;
}

如图中代码,不能直接修改n,那么就利用指针获取它的地址,再把地址里的值改掉,间接地改变n的值。


1.2const修饰指针变量

既然可以通过地址修改n,那我们也可以利用const使指针p得到n的地址,但是不能修改n的值.

那如何做到呢?也是利用const修饰它.⼀般来讲const修饰指针变量,可以放在*的左边,也可以放在*的右边,意义是不⼀样的。

int * p;                          // 没有 const 修饰
int const * p; //const 放在 * 的左边做修饰, 或者    const int* p;
这种情况下,const修饰的是p所指向的内容,此时const能保证该内容不会被修改
int * const p; //const 放在 * 的右边做修饰
这种情况下,const修饰指针变量本身,此时const能保证指针的指向不会被改变;
const int* const p;   //const在*两边都存在
这种情况下,const修饰指针变量和指针指向的内容,即两个都不能改变;
需要注意 :单个const时,总是只能锁定一个,另一个可以正常被修改!

2.野指针成因


概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

2.1野指针的成因

1.指针未初始化:
#include 
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}

如图,局部指针变量未初始化,默认是随机值;

2.指针越界访问:
#include 
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i = 0; i <= 11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}

当指针指向的范围超出数组arr的范围时,p就是野指针;

3.指针指向的空间释放
#include 

int* test()
{
    int n = 100;
    return &n;
}

int main()
{
    int*p = test();
    printf("%d\n", *p);
    return 0;
}

在主函数中,上述代码想实现利用*p调用函数,再打印出该函数的返回值&n,问题是test函数运行完后,n的生命周期结束,空间被释放,导致printf中解引用p时,访问的是一块已经被释放,不再有效的内存区域,这导致p成为了野指针;


2.2野指针规避

                                             只要是NULL指针就不去访问

1.指针的初始化

如果明确知道指针该指向哪里,那就直接赋地址,若不知道,则赋值NULL,初始化如图:


int num = 10;
int*p1 = #
int*p2 = NULL;//赋值NULL
2.避免指针访问越界

一个程序中申请了哪些内存空间,那就只能指向哪些空间,不能访问没有申请的空间;

3.指针变量不再使⽤时置NULL,使用前检查有效性
当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL,同时,使用一个指针时可以检查它是否为NULL,是的话不可以直接使用,不是的话可以使用
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已经越界了,可以把p置为NULL
    p = NULL;
                                //下次使⽤的时候,判断p不为NULL的时候再使⽤
      //...
                                p = &arr[0];//重新让p获得地址
    if(p != NULL) //判断
{
    //...
}
    return 0;
}
4.避免返回局部变量的地址

例子如野指针成因3中描述,尽量避免返回局部变量的地址,返回也需要在其生命周期内;


3.assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
assert(p != NULL );
这句的作用是检测p是否为NULL,当程序走到这一步时,会判断,若NULL,则会终止运行
assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣
任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错
        
果已经确认程序没有问题不需要再做断⾔,就在 #include 语句的前⾯,定义⼀个 NDEBUG 
#define NDEBUG
#include 

进行此操作后,重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句,这样省掉了一条条断言注释掉的功夫,反之一样。

assert() 的缺点:引⼊了额外的检查,增加了程序的运⾏时间

还有,这玩意程序员们经常使用,用来检查bug,在VS集成开发环境中,我们可以在debug中使用(为了排查bug),而在release版本中,这功能被优化掉了(为了效率)。

ok,今天的内容就讲到这里,期待我的下一章指针内容吧,请各位还不忘点个小小的赞!

你可能感兴趣的:(算法)