指针强化与提高

1.指针变量

一些概念:

p++和p+i不同,p+1只是指针向后移一位,此时p本身指向的值不变,p++则是p指向它后一位指针指向的值。而传递进函数的数组地址a[2]则代表*(p+2)指针原本地址没变。

指针是一种数据类型,占用内存空间,用来保存内存地址。

void test01(){

int* p1 = 0x1234;

int*** p2 = 0x1111;

printf("p1 size:%d\n",sizeof(p1));

printf("p2 size:%d\n",sizeof(p2));

//指针是变量,指针本身也占内存空间,指针也可以被赋值

int a = 10;

p1 = &a;

printf("p1 address:%p\n", &p1);

printf("p1 address:%p\n", p1);

printf("a address:%p\n", &a);

}

2.野指针和空指针

不允许向NULL和非法地址操作内存:俩种非法操作(拷贝内存,取地址等)

void test(){//不能向空指针中拷贝内存

char *p = NULL;

//给p指向的内存区域拷贝内容

strcpyp, "1111"); //err(strcpy本质就是首地址到\0然后黏贴到另一个地址)

char *q = 0x1122;//不能向野指针中拷贝内存

//给q指向的内存区域拷贝内容

strcpy(q, "2222"); //err

}

2.1 空指针

概念:标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可以给它赋值一个零值。为了测试一个指针百年来那个是否为NULL,你可以将它与零值进行比较。

对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未指向任何东西,因为对一个NULL指针因引用是一个非法的操作,在解引用之前,必须确保它不是一个NULL指针。

 如果对一个NULL指针间接访问会发生什么呢?结果因编译器而异。

2.2 野指针(野指针具体情况得看编译器,有时候会小幅度允许编译通过,但是尽量规避)

概念:野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。在使用指针时,要避免野指针的出现

野指针类型:什么情况下回导致野指针?

1.指针变量未初始化

任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存

非空:

内存不合法:

void test(){

int* p = 0x001; //未初始化

printf("%p\n",p);

*p = 100;

}

2.指针释放后未置空

有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。不能对野指针内存进行操作。

3.指针操作超越变量作用域

不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

空指针可以再free释放

野指针不能释放

解决方法:操作野指针是非常危险的操作,应该规避野指针的出现:

        1.初始化时置 NULL

指针变量一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。

2.释放时置 NULL

        当指针p指向的内存空间释放时,没有设置指针p的值为NULL。delete和free只是把内存空间释放了,但是并没有将指针p的值赋为NULL。通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。

        3.不要返回指向栈内存的指针或引用

        因为栈内存在函数结束时会被释放。

3.指针的步长

概念:

指针是一种数据类型,是指它指向的内存空间的数据类型。指针所指向的内存空间决定了指针的步长。指针的步长指的是,当指针+1时候,移动多少字节单位。

思考如下问题

int a = 0xaabbccdd;

unsigned int *p1 = &a;

unsigned char *p2 = &a;

//为什么*p1打印出来正确结果

printf("%x\n", *p1);

//为什么*p2没有打印出来正确结果?

printf("%x\n", *p2);

//为什么p1指针+1加了4字节?

printf("p1  =%d\n", p1);

printf("p1+1=%d\n", p1 + 1);

//为什么p2指针+1加了1字节?

printf("p2  =%d\n", p2);

printf("p2+1=%d\n", p2 + 1);

1.指针变量+1后跳跃的地址字节数(根据数据类型跳) 

 2.步长是解引用取出的字节数(根据数据类型取地址)可以强转类型跳步取值

 

 

练习:

打印函数偏移量

 

小结:

        对自定义数据类型进行练习

  1. 如果获取自定义数据类型中属性的偏移
  2. offsetof( 结构体 , 属性 )
  3. 头文件  #include<stddef.h>

4.指针的意义   ---间接赋值

4.1 间接赋值的三大条件

通过指针间接赋值成立的三大条件:

1. 2个变量(一个普通变量一个指针变量、或者函数一个实参一个形参) 建立关系 

2.通过 * 操作指针指向的内存

void test(){

int a = 100; //两个变量

int *p = NULL;

//建立关系

//指针指向谁,就把谁的地址赋值给指针

p = &a;

//通过*操作内存

*p = 22;

}

4.2 如何定义合适的指针变量

void test(){

int b;  

int *q = &b; //0级指针

int **t = &q;

int ***m = &t;

}

4.3 间接赋值:从0级指针到1级指针

int func1(){ return 10; }

void func2(int a){

a = 100;

}

//指针的意义_间接赋值

void test02(){

int a = 0;

a = func1();

printf("a = %d\n", a);

//为什么没有修改?

func2(a);

printf("a = %d\n", a);

}

//指针的间接赋值

void func3(int* a){

*a = 100;

}

void test03(){

int a = 0;

a = func1();

printf("a = %d\n", a);

//修改

func3(&a);

printf("a = %d\n", a);

}

4.4 间接赋值:从1级指针到2级指针

void AllocateSpace(char** p){

*p = (char*)malloc(100);

strcpy(*p, "hello world!");

}

void FreeSpace(char** p){

if (p == NULL){

return;

}

if (*p != NULL){

free(*p);

*p = NULL;

}

}

void test(){

char* p = NULL;

AllocateSpace(&p);

printf("%s\n",p);

FreeSpace(&p);

if (p == NULL){

printf("p内存释放!\n");

}

}

4.5 间接赋值的推论

  1. 用1级指针形参,去间接修改了0级指针(实参)的值。
  2. 用2级指针形参,去间接修改了1级指针(实参)的值。
  3. 用3级指针形参,去间接修改了2级指针(实参)的值。
  4. 用n级指针形参,去间接修改了n-1级指针(实参)的值。

5.指针做函数参数(了解即可)

概念:指针做函数参数,具备输入输出特性:

  1. 输入:主调函数分配内存
  2. 输出:被调用函数分配内存

5.1 输入特性

在主调函数中分配内存空间,被调函数传参,进行内存使用(被调函数形参用一级指针修饰)

分为栈上分配和堆上分配

void fun(char *p /* in */)

{

//给p指向的内存区域拷贝内容

strcpy(p, "abcddsgsd");

}

void test(void)

{

//输入,主调函数分配内存

char buf[100] = { 0 };

fun(buf);

printf("buf  = %s\n", buf);

}

5.2 输出特性

在被调函数中分配内存空间,内存使用(被调函数形参用二级指针修饰)

void fun(char **p /* out */, int *len)

{

char *tmp = (char *)malloc(100);

if (tmp == NULL)

{

return;

}

strcpy(tmp, "adlsgjldsk");

//间接赋值

*p = tmp;

*len = strlen(tmp);

}

void test(void)

{

//输出,被调用函数分配内存,地址传递

char *p = NULL;

int len = 0;

fun(&p, &len);

if (p != NULL)

{

printf("p = %s, len = %d\n", p, len);

}

 小结:

 

6.指针易错点

 6.1 一级指针易错点

 1.越界

一共四个只存了3,\0没存,造成打印错误乱码

void test(){

char buf[3] = "abc";

printf("buf:%s\n",buf);一共四个只存了3,\0没存,造成打印错误乱码

}

2.指针叠加会不断改变指针指向

释放掉的指针位置已经改变了,不是原先的了,释放编译不通过报错

要使用临时指针进行偏移操作

void test(){

char *p = (char *)malloc(50);

char buf[] = "abcdef";

int n = strlen(buf);

int i = 0;

for (i = 0; i < n; i++)

{

*p = buf[i];

p++; //修改原指针指向

}

free(p);//释放掉的指针位置已经改变了,不是原先的了,释放编译不通过

}

更改正确写法: 再引出一个指针去实现操作,原指针就不动了

 

3.返回局部变量地址

当函数执行完,时候后,局部变量不存在,地址变成乱码

char *get_str()

{

char str[] = "abcdedsgads"; //栈区,

printf("[get_str]str = %s\n", str);

return str;当函数执行完,时候后,局部变量不存在,地址变成乱码

}

4.同一块内存释放多次

(不可以释放野指针,空指针释放完是野指针,不能重复释放,释放报错)

(结果不重要,乱码和报错看编译器)

void test(){

char *p = NULL;

p = (char *)malloc(50);

strcpy(p, "abcdef");

if (p != NULL)

{

//free()函数的功能只是告诉系统 p 指向的内存可以回收了

// 就是说,p 指向的内存使用权交还给系统

//但是,p的值还是原来的值(野指针),p还是指向原来的内存

free(p);

}

if (p != NULL)

{

free(p);

}

}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打酱油的;

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值