指针、数组指针、字符串指针、函数指针、 const指针

指针与数组理论加强:
指针变量的本质是值,这个特殊的值是一个内存地址值,而合法的内存地址包括
定义的变量的地址(栈)、malloc函数申请堆内存返回的地址(但未使用free释放,是在堆空间动态申请);

指针变量的初始化

定义的同时进行初始化
int a = 5;
int *p = &a;
先定义后初始化
int a = 5;
int *p;
p=&a;
把指针初始化为NULL
int *p=NULL;
int *q=0;
不合法的初始化:
//指针变量只能存储地址, 不能存储其它类型
int *p;
p =  250; // 错误写法
//给指针变量赋值时,指针变量前不能再加“*”
int *p;
*p=&a; //错误写法

野指针的产生

通常野指针是因为指针变量中保存的值不是一个合法的内存地址或者指向不可用内存的指针而造成的。
一、局部指针变量没有被初始化

#include <stdio.h>
#include <string.h>

struct Student
{
    char* name; //
    int number;
};
 
int main()
{
    struct Student s;
    strcpy(s.name, "chenxx"); // OOPS!
    s.number = 99;
    return 0;
}

局部变量不像全局变量那样,不赋值会自动初始化为0,指针name指向的内存空间地址是随机的,不能向随机地址空间写数据。我们在定义局部指针变量时应该初始化为NULL,局部变量则初始化为0。
二、使用已经释放过后的指针

#include <stdio.h>
#include <malloc.h>
#include <string.h>
 
void func(char* p)
{
    printf("%s\n", p);
    free(p);
}

int main()
{
    char* s = (char*)malloc(5);
    strcpy(s, "chenxx");//数组越界
    func(s);
    printf("%s\n", s); // OOPS!使用已经释放的指针s
    return 0;
}

malloc申请的堆空间释放后,意味着把这片内存归还到空闲链表,其它程序可以使用这片空间,如果其它程序使用了这个空间,可能导致其它程序莫名其妙的被关闭,所以一定要在释放过后将指针变量的值赋值为NULL。

三、指针所指向的变量在指针之前被销毁

#include <stdio.h>
 
char* func()
{
    char p[] = "chenxx";
    return p;
}
 
int main()
{
    char* s = func();
    printf("%s\n", s); // OOPS!
    return 0;
}

func函数被调用的时候,栈区存放了局部数组p,func返回之后,栈顶指针退出,占用的内存已经被释放掉,此时指针s指向一个被释放掉了栈空间,如果栈空间值被修改了,就不会打印出预期结果,s就变成了一个野指针,所以我们绝对不要在函数中返回局部变量和局部数组的地址。
三、进行了错误指针运算

#include <stdio.h>
void main()
{
 int a[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p;
 
  for (int *p = &a[9];p >= a;){
    *--p = 0;
  }
}

程序中在数组第1个元素a[0]被清除之后,指针p的值还继续减下去,而接下去的一次比较运算是用于结束循环的。但表达式p>= a(p >= &a[0])的值是未定义的。
为避免这种情况,一定要确保字符数组要以‘\0’结尾,为防止内存越界,自己编写的内存相关函数需要指定正确的长度信息。

四、进行了错误的强制类型转换

#include <stdio.h>
#include <string.h>

int main()
{
    int a = 1;
    int p = &a;

    printf("%d\n",*((int*)p));

    /*
    在64位下输出错误
    32位下输出a的值 1
    */

    return 0;
}

上面的程序在64位下输出错误,32位下输出a的值1,在我们写嵌入式程序的时候,会将int类型的一个数据强制转换成一个指针类型用来表示寄存器的地址,这个时候就需要注意了。

野指针的避免

1.定义指针时,同时初始化为NULL
2.在指针解引用之前,先去判断这个指针是不是NULL
3.指针使用完之后,将其赋值为NULL
4.在指针使用之前,将其赋值绑定给一个可用地址空间

指针注意事项

指针的指向是可以改变的:
int a = 5;
int *p = &a;
int b = 10;
p = &b; // 修改指针指向
指针没有初始化里面是一个垃圾值,这时候我们这是一个野指针
野指针可能会导致程序崩溃
野指针访问你不该访问数据
所以指针必须初始化才可以访问其所指向存储区域

运算符&(取地址)、*(取内容)

int a = 5;
int *p = &a; //把a的地址取出来赋值给指针p
printf("a = %d", *p); //访问指针变量,取出p地址的存储内容5

二级指针,指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针”;
在这里插入图片描述

    char c = 'a';
    char *cp;
    cp = &c;
    char **cp2;
    cp2 = &cp;
    printf("c = %c", **cp2);

数组指针

一个变量有地址,一个数组包含若干元素,每个数组元素也有相应的地址, 指针变量也可以保存数组元素的地址
只要一个指针变量保存了数组元素的地址, 我们就称之为数组元素指针。
在这里插入图片描述

数组名a不代表整个数组,只代表数组首元素的地址。
“p=a;”的作用是“把a数组的首元素的地址赋给指针变量p”,而不是“把数组a各元素的值赋给 p”
在指针指向数组元素时,允许以下运算:
加一个整数(用+或+=),如p+1
减一个整数(用-或-=),如p-1
自加运算,如p++,++p
自减运算,如p--,--p
访问数组元素,可用下面两种方法:

下标法, 如a[i]形式
指针法, *(p+i)形式

数组名首地址不可更改

数组名虽然是数组的首地址,但是数组名所所保存的数组的首地址是不可以更改的。
所以可通过指针来实现:

 int x[10];
 x++;  //错误
 int* p = x;
 p++; //正确

字符串指针使用注意事项

不可以修改字符串内容

//使用字符数组来保存的字符串是保存栈里的,保存栈里面东西是可读可写,所有可以修改字符串中的的字符
// 使用字符指针来保存字符串,它保存的是字符串常量地址,常量区是只读的,所以我们不可以修改字符串中的字符
char *str = "cxx";
*(str+2) = 'y'; // 错误,常量区是只读的

不能够直接接收键盘输入

//错误的原因是:str是一个野指针,他并没有指向某一块内存空间
//所以不允许这样写如果给str分配内存空间是可以这样用 的
char *str;
scanf("%s", str);

可以访问查看字符串的每一个字符:

char *str = "cxx";
for(int i = 0; i < strlen(str);i++)
{
  printf("%c-", *(str+i)); // 输出结果:c-x-x
}

函数指针,指向函数的指针

函数作为一段程序,在内存中也要占据部分存储空间,它也有一个起始地址
函数有自己的地址,那就好办了,我们的指针变量就是用来存储地址的。
因此可以利用一个指针指向一个函数。其中,函数名就代表着函数的地址。

指针函数定义技巧
1、把要指向函数头拷贝过来
2、把函数名称使用小括号括起来
3、在函数名称前面加上一个*
4、修改函数名称

应用:
调用函数
将函数作为参数在函数间传递
由于这类指针变量存储的是一个函数的入口地址,所以对它们作加减运算(比如p++)是无意义的
函数调用中"(指针变量名)"的两边的括号不可少,其中的不应该理解为求值运算,在此处它 只是一种表示符号

格式: 返回值类型 (*指针变量名)(形参1, 形参2, …);

    int sum(int a,int b)
    {
        return a + b;
    }
    
    int (*pf)(int,int);
    pf = sum;
    printf("sum: %d\n",sum(3,4));
    printf("pf : %d\n",pf(4,3)); //结果一致

const指针

const意为“只读的”,类型修饰符
常用const 修饰变量则可以让变量的值不能改变。也称之为 const常量。

可以定义const常量,具有不可变性。
便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
可以保护被修饰的东西,防止意外的修改,增强程序的健壮性;
可以节省空间,避免不必要的内存分配。
提高了效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表 中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
const与指针:
//常量指针:指向可变
const int *a;   //const修饰指针a,a指向可变(可重新赋值),a指向的值不能被修改
int const *a;   //const修饰指向的对象,a可变(可重新赋值),a指向的对象值不可变
//指针常量:值可变
int *const a; //const修饰指针a, a指针指向不可变(不能重新赋值),a指向的对象值可变
//还有: 值与指向都不可变
const int *const a;//指针a和a指向的对象,指针指向、对象值都不可变
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程一时爽Cxx

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

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

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

打赏作者

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

抵扣说明:

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

余额充值