C语言—指针1

1.内存和地址

内存其实就相当于我们的宿舍,地址就相当于我们的门牌号,假设我们要就谁谁谁就可以通过门牌号来寻找,C语言中我们就通过地址(C语言中给地址起了一个新名字叫指针)来访问内存。

2.指针变量和地址

我们知道了内存和地址的关系,在C语言中创建变量其实就是向内存申请空间,比如:

上述的代码就是创建了整型变量a,内存中申请4个字节,用于存放整数10,其实每个字节都有地址。

那我们要如何得到a的地址呢?我们就可以通过操作符(&)-取地址操作符。

&a取出的是a所占4个字节中地址较小的字节的地址。

虽然整型变量占用四个字节,我们只要知道了第一个字节的地址,顺藤摸瓜访问到4个字节的数据也是可行的。

2.2指针变量和解引用操作符(*)

2.2.1指针变量

我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x006ffd70,这个数值有时候也是需要储存起来,方便后期的使用,那我们就需要把这样的地址存放在指针变量中。

指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。

2.2.2如何拆解指针类型

我们可以看到pa是int*

这里的pa左边写的是int*,*说明pa是指针变量,而前面的int是在说明pa指向的事整型(int)类型的对象。

那如果有一个char类型的变量ch,ch的地址就要存放在字符类型(char)类型的指针变量中。

2.2.3解引用操作符

我们将地址保存起来,未来是要使用的,那怎么使用呢?这里就要用到解引用操作符(*)。

*pa就是通过pa中存放的地址,找到指向的空间,*pa其实就是a变量;所以*pa=0,这个操作就是把a改成了10.

2.3指针变量的大小

在32位平台下地址是32个bit位,指针变量的大小是4个字节

在64位平台下地址是64个bit位,指针变量的大小是8个字节

指针变量的大小和类型是无关的,只要指针类型的变量在相同的平台下,大小都是相同的。

3.指针变量类型的意义

指针的解引用

1.

2.

调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将第一个字节改为0.

所以我们得出结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。像char*的指针解引用就只能访问一个字节,而int*的指针解引用就能访问4个字节,想一次访问8个字节就可以用double*,一次访问两个字节就可以用short*。

指针+-整数

先看这段代码,调试观察地址的化

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。

结论:指针的类型决定了指针向前或者向后走一步有多大。

void* 的指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指 针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进 ⾏指针的+-整数和解引⽤的运算。

例:

这段代码在运行的时候会报下面这种警告:

在上面的代码中,将一个int类型的变量的地址赋给一个char*类型的指针变量。编译器就会给出这个警告,是因为类型不兼容。而使用void*类型就不会有这样的问题。

使用void*类型的指针接收地址:

结果:

这里我们可以看到,void*类型的指针可以接受不同类型的地址,但是无法直接进行指针运算。

那么void*类型的指针到底有什么用呢?

一般void*类型的指针是使用在函数的参数部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得一个函数可以来处理多种类型的数据。

4.const

变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量也可以修改这个变量。但是如果我们希望一个变量加上一些限制,不能被修改就可以使用const修饰。

上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我 们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n。

但是我们可以绕过n,使用n的地址去修改,但这样打破了const的修饰是不合理的,所以我们就得用const来修饰指针变量。

结论:const修饰指针变量的时候

const放在*号左边,限制的是*pn,意思是不能通过指着变量pn修改pn所指向的空间的内容,但是pn不受限制

const放在*号右边,限制的是pn变量,也就是pn变量不能修改了,没办法在指向其他变量了但是*pn不受限制,还是可以通过pn来修改pn所指向的对象的内容。

5.指针运算

指针的基本运算有三种,分别是:

指针+-整数

指针-指针

指针的关系运算

指针+-整数

因为数组在内存中是连续存放的,只要知道了第一个元素的地址,就能找到后面的元素。

指针-指针

指针-指针的绝对值是指针和指针之间的元素个数,运算的前提是两个指针都指向同一块空间。

指针的运算关系

指针的运算关系就是指针比较大小(地址比较大小)。数组名就是首元素地址

6.野指针

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

野指针的成因:

指针未初始化

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

指针越界访问

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;
}

指针指向的空间释放

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

如何规避野指针呢?

如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.

NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。

初始化如下:

小心指针越界

指针变量不再使用时,及时置为NULL,指针使用之前检查其有效性

避免返回局部变量地址

7.assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏常常被称为“断⾔”。

上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序 继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。

assert() 的使⽤对程序员是⾮常友好的,使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和 出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include 语句的前⾯,定义⼀个宏 NDEBUG 。

然后,重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题,可以移 除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert() 语 句。

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

感谢浏览!

本篇博客代码:test.8.21.c · MonDays/C. - 码云 - 开源中国 (gitee.com)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值