C语言 指针

一.指针的定义

其本质是地址,平常口头说的指针是指针变量,是用来存放地址的指针。

 我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是内存中读取的,处理后的数据也会放回内存中。

电脑上内存是8CB/16GB/32GB等,这些内存空间如何高效的管理?

其实就是把内存划分成一个一个的内存单元,就像一个单元的门牌号,每个内存单元的大小取1字节,每个内存单元都有一个编号。

有了内存单元的编号,CPU就可以快速找到一个内存空间。

内存单元的编号 == 地址 == 指针

究竟该如何理解编址

CPU访问内存中的某个字节空间,必须知道这个
字节空间在内存的什么位置,⽽因为内存中字节
很多,所以需要给内存进⾏编址(就如同宿舍很
多,需要给宿舍编号⼀样)。
计算机中的编址,并不是把每个字节的地址记录
下来,⽽是通过硬件设计完成的。
钢琴、吉他 上⾯没有写上“剁、来、咪、发、
唆、拉、西”这样的信息,但演奏者照样能够准
确找到每⼀个琴弦的每⼀个位置,这是为何?因
为制造商已经在乐器硬件层⾯上设计好了,并且
所有的演奏者都知道。本质是⼀种约定出来的共
识!
⾸先,必须理解,计算机内是有很多的硬件单 元,⽽硬件单元是要互相协同⼯作的。所谓的协
同,⾄少相互之间要能够进⾏数据传递。 但是硬件与硬件之间是互相独⽴的,那么如何通
信呢?答案很简单,⽤"线"连起来。 ⽽CPU和内存之间也是有⼤量的数据交互的,所 以,两者必须也⽤线连起来。
不过,我们今天关⼼⼀组线,叫做地址总线。 硬件编址也是如此 我们可以简单理解,32位机器有32根地址总线, 每根线只有两态,表⽰0,1【电脉冲有⽆】,那么 ⼀根线,就能表⽰2种含义,2根线就能表⽰4种含 义,依次类推。32根地址线,就能表⽰2^32种含 义,每⼀种含义都代表⼀个地址。 地址信息被下达给内存,在内存上,就可以找到 该地址对应的数据,将数据在通过数据总线传⼊ CPU内寄存器。

指针变量和地址

取地址操作符(&)

理解了内存和地址的关系,我们再回到C语⾔,在C语⾔中创建变量其实就是向内存申请空间,⽐如:
我们看到pa的类型是 int* ,我们该如何理解指针的类型呢
这⾥pa左边写的是 int* * 是在说明pa是指针变量,⽽前⾯的 int 是在说明pa指向的是整型(int)
类型的对象。

解引⽤操作符

我们将地址保存起来,未来是要使⽤的,那怎么使⽤呢?
在现实⽣活中,我们使⽤地址要找到⼀个房间,在房间⾥可以拿去或者存放物品。
C语⾔中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)
指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。
上⾯代码中第7⾏就使⽤了解引⽤操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间,
*pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0.
有同学肯定在想,这⾥如果⽬的就是把a改成0的话,写成 a = 0; 不就完了,为啥⾮要使⽤指针呢?
其实这⾥是把a的修改交给了pa来操作,这样对a的修改,就多了⼀种的途径,写代码就会更加灵活,
后期慢慢就能理解了。

指针类型变量的意义

指针解引用

任务:上面两段代码,调试观察内存的变化,对比发现了什么?

发现:第一段代码会将n的4个字节全部改为0;但是第二段代码只是将n的第一个字节改为0。

结论:指针类型决定了对指针解引用的时候有多大权限(一次可以操作几个字节)。

比如:char*的指针解引用只能访问一个字节,而int*的指针解引用就可以访问4个字节。

指针++(--)跳过多少字节

  

由此可见char*指针加一跳过一个字节,int*指针加一跳过4个字节; 结论:指针类型决定了指针向前或者向后走一步有多大(距离)。

void* 指针

void* 类型,无具体类型的指针(泛型指针),void* 类型的指针大部分使用在函数参数的部分,用来接收不同类型数据的地址。

但是void* 类型的指针不能直接进行指针的+-整数和解引用运算。

 字符指针与数组指针

在指针的类型中我们知道有⼀种指针类型为字符指针 char*
代码 const char* pstr = "hello bit."; 特别容易让同学以为是把字符串 hello bit
到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。

const修饰指针

在C语言中,const是constant的缩写,翻译是“恒定不变的”。它是定义只读变量的关键字。或者说const是定义常变量的关键字

const修饰的变量被称为常变量,具有常属性,但是本质上还是变量。

保护被修饰的东西,防止被意外修改,增强程序的健壮性。

提高程序的运行效率。编译器通常不为普通const常量分配存储空间,而是将他们保护在符号表中,使得它成为一个编译期间的常量,没有存储和读取内存的操作,使得它的运行效率也很高。

 const int* p (int const *p)和int *const p的区别

 总结:

①const int* p (int const *p):const放在*的左边。修饰的是*p,*p(指针指向的内容)不能修改;p(指针变量本身)可以修改

②int * const p :const放在*的右边。修饰的是p,p(指针变量本身)不可以修改;*p(指针指向的内容)可以修改。

③int const * const p (const int * const p):*的左右两边都有const,修饰的是*p和p,所以*p和p都不能被修改。

指针运算 

指针 +- 整数

数组在内存中是连续存放的,只要知道第一个元素的地址,顺藤摸瓜就可以找到后面所有元素。

指针与数组

什么是指针数组

之前我们学习了指针数组,是⼀种数组,数组中存放的是地址(指针)。
数组指针变量是指针变量?还是数组?
答案是:指针变量。
我们已经熟悉:
整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
int (*p)[ 10 ];
解释:p先和*结合,说明p是⼀个指针变量,然后指针指向的是⼀个⼤⼩为10个整型的数组。所以p是 ⼀个指针,指向⼀个数组,叫 数组指针。
这⾥要注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。

⼆维数组传参的本质

本质穿的是地址;

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
如下图:

所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀
维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类
型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀
⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
1 # include <stdio.h>
2
3 void test ( int (*p)[ 5 ], int r, int c)
4 {
5 int i = 0 ; 6 int j = 0 ;
7 for (i= 0 ; i<r; i++)
8 {
9 for (j= 0 ; j<c; j++)
10 {
11 printf ( "%d " , *(*(p+i)+j));
12 }
13 printf ( "\n" );
14 }
15 }
16
17 int main ()
18 {
19 int arr[ 3 ][ 5 ] = {{ 1 , 2 , 3 , 4 , 5 }, { 2 , 3 , 4 , 5 , 6 },{ 3 , 4 , 5 , 6 , 7 }};
20 test(arr, 3 , 5 );
21 return 0 ;
22 }
总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

sizeof 与strlen对比

sizeof 

在学习操作符的时候,我们学习了 sizeof sizeof 计算变量所占内存内存空间⼤⼩的,单位是
字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
⽐如:
1 #inculde <stdio.h>
2 int main ()
3 {
4 int a = 10 ;
5 printf ( "%d\n" , sizeof (a));
6 printf ( "%d\n" , sizeof a);
7 printf ( "%d\n" , sizeof ( int ));
8
9 return 0 ;
10 }

strlen

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:

1 size_t strlen ( const char * str );
统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。
strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。
# include <stdio.h>
int main ()
{
char arr1[ 3 ] = { 'a' , 'b' , 'c' };
char arr2[] = "abc" ;
printf ( "%d\n" , strlen (arr1));
printf ( "%d\n" , strlen (arr2));
printf ( "%d\n" , sizeof (arr1));
printf ( "%d\n" , sizeof (arr2));
return 0 ;
}
这样可以解释为啥这里n=1;

sizeof 和 strlen的对⽐

数组和指针笔试题解析

⼀维数组

int a[] = { 1 , 2 , 3 , 4 };
printf ( "%d\n" , sizeof (a));
printf ( "%d\n" , sizeof (a+ 0 ));
printf ( "%d\n" , sizeof (*a));
printf ( "%d\n" , sizeof (a+ 1 ));
printf ( "%d\n" , sizeof (a[ 1 ]));
printf ( "%d\n" , sizeof (&a));
printf ( "%d\n" , sizeof (*&a));
printf ( "%d\n" , sizeof (&a+ 1 ));
  printf ( "%d\n" , sizeof (&a[ 0 ]));
  printf ( "%d\n" , sizeof (&a[ 0 ]+ 1 ));

字符数组

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值