C语言学习笔记——指针(初识指针)

指针可以说是整个C语言学习过程中最为重要同时难度也较大的部分,因此整个指针内容我分为三部分进行记录,分别是初识指针、进阶认识和相关扩展


 一、指针是什么?

在了解指针之前我们需要了解一下计算机的一些组成原理。

1、内存和地址

(1)内存

计算机中的内存通常有8GB/16GB/32GB/64GB,为了计算机的高校运行,通常将内存划分为一个个内存单元,每个内存单元取一个字节,一个字节有八个比特位。为了方便理解,下面补充一下关于计算机常见单位的知识。

比特位是计算机中最小的内存单元,一个比特位可以存储一个二进制位的0或1。

bit - 比特位
byte - 字节
KB
MB
GB
TB
PB
1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB

 计算机将内存以字节为单位划分成一个个内存单元后,对每个内存单元进行编号,通过这个编号计算机可以迅速提取到对应内存单元中存储的数据,这个编号在计算机中称为地址,而在C语言中称为指针。

所以可以认为内存单元的编号==地址==指针

0XFFFFFFFF一个字节
0XFFFFFFFE一个字节
一个字节
一个字节
0X00000001一个字节

(2)编址

在CPU处理数据时需要访问内存单元,这就要知道内存单元在什么位置,所以要给内存进行编排地址。

在计算机中有很多的硬件单元,而计算机要工作时就需要这些硬件单元协同工作,它们协同工作的渠道就是“线”,通过“线”进行数据交互。在编址中需要用到三个线,分别是地址总线、数据总线、控制总线。

在32位的机器上有32根地址总线,每根线的状态有1和0,分别代表电脉冲有无。一根线代表两种含义,那么32根线就代表了2^32种含义,每种含义代表一个地址。

在CPU需要访问内存时,通过控制总线发送一个“读”的指令给内存,再从地址总线传输该内存的地址到内存,就可以从内存上读取该地址的数据,然后从数据总线传输该内存的数据给CPU;当CPU需要保存数据到内存时,CPU将会通过控制总线发送一个“写”的指令给内存,同时使用数据总线将需要存储的数据传输给内存,此时内存会分发一个内存单元用于存储数据,并将该内存的地址通过地址总线传输给CPU。

2、指针变量和地址的关系

在C语言创建变量的时候会向内存申请一块空间,这块空间就需要有对应的地址。比如申请一个整型变量a,内存会分配四个字节的空间,每个字节的地址如下:

0x006FFD70

0x006FFD71

0x006FFD72

0x006FFD73

要获得变量a的地址就需要使用取地址操作符&,下面会详细介绍

int a = 10;
int* p = &a;
printf("%p\n",p);

这里将a的地址使用取地址操作符&取出来后需要存储起来,以便后续使用,而在上面代码中a的地址就存储在变量p中。

这个p就是指针变量,p前面的int代表p指针的地址所指向的对象类型是int,*代表这个变量p是个指针变量。

打印结果是0x006FFD70,打印地址需要使用%p。

二、指针的相关操作

1、解引用与取地址

正如上面所说一个变量的地址使用取地址操作符&获得之后需要放到一个指针变量之中,那么在后续使用中如何获得这个指针变量所指向的对象呢,这就需要使用解引用操作符。

 int a = 100;
 int* pa = &a;
 *pa = 0;
 return 0;

上面这个代码中*pa就是通过pa所存放的地址找到该地址指向的变量a,也就是*pa相当于a变量,所以*pa=0,就是把a改成了0。

大家都使用过快递柜,在这里a就相当于快递,地址就相当于快递所在柜子的编号,而取地址操作符 * 就相当于取件码。在快递柜输入取件码得到柜号,就能拿到快递。

2、指针运算

(1)指针+-整数

指针+-一个整数会跳过  整数*指针指向对象的类型大小  个字节,下面直接使用代码演示

#include <stdio.h>

int main()
{
	int n = 10;
	char *pc = (char*)&n;
	int *pi = &n;

	printf("%p\n", &n);
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	printf("%p\n", pi);
	printf("%p\n", pi + 1);
	return 0;
}

代码的运行结果如下:

我们可以看出char*类型的指针变量+1跳过了一个字节,int*类型的指针变量+1跳过4个字节,如果+2就对应翻倍。

使用画图更为直观理解

总结:指针的类型决定了指针向前或者向后跳过多少空间

(2)指针-指针

指针-指针得到的绝对值是两个指针所指向的空间内(必须是同一空间)他们的元素个数

使用代码演示:

#include <stdio.h>

int my_strlen(char *s)
{
     char *p = s;
     while(*p != '\0' )
     p++;
     return p-s;
}

int main()
{
     printf("%d\n", my_strlen("abc"));
     return 0;
}

上面代码运用了指针相减获得两个指针之间元素个数的原理 模拟实现了strlen函数,运行结果如下

(3)指针的关系比较

使用>,<号对指针进行大小比较,这里的大小表示指针的实际数值,事实上指针是按高低进行比较的,也就是地址数值大的指针较高,相反地址数值低的指针较低。

运用指针的关系比较可以获得指针的位置关系,使用代码举例:

#include <stdio.h>
int main()
{
     int arr[10] = {1,2,3,4,5,6,7,8,9,10};
     int *p = &arr[0];
     int i = 0;
     int sz = sizeof(arr)/sizeof(arr[0]);
     while(p<arr+sz) //指针的⼤⼩⽐较
     {
         printf("%d ", *p);
         p++;
     }
     return 0;
}

 上述代码可以打印出数组arr的全部元素。

3、const修饰指针

变量本身是可以被修改的,但使用const修饰变量之后,变量就变得不可修改了,对变量进行限制,这样的变量称为常变量。

#include <stdio.h>

int main()
{
     int m = 0;
     m = 20;//m是可以修改的
     const int n = 0;
     n = 20;//n是不能被修改的
     return 0;
}

比如上述代码中的m未添加修饰,可以被修改;而n添加了const修饰,就不可被修饰,代码中对n进行修改不符合语法规则,会报错。

但是如果绕过n,用n的地址就可以修改n。

#include <stdio.h>
int main()
{
     const int n = 0;
     printf("n = %d\n", n);
     int*p = &n;
     *p = 20;
     printf("n = %d\n", n);
     return 0;
}

为了防止这种绕开变量修改内容的行为,我们可以使用const修饰指针

#include <stdio.h>
//代码1
void test1()
{
     int n = 10;
     int m = 20;
     int *p = &n;
     *p = 20;//ok?
     p = &m; //ok?
}
void test2()
{
     //代码2
     int n = 10;
     int m = 20;
     const int* p = &n;
     *p = 20;//ok?
     p = &m; //ok?
}
void test3()
{
     int n = 10;
     int m = 20;
     int *const p = &n;
     *p = 20; //ok?p = &m; //ok?
}
void test4()
{
     int n = 10;
     int m = 20;
     int const * const p = &n;
     *p = 20; //ok?
     p = &m; //ok?
}
int main()
{
     //测试⽆const修饰的情况
     test1();
     //测试const放在*的左边情况
     test2();
     //测试const放在*的右边情况
     test3();
     //测试*的左右两边都有const
     test4();
     return 0;
}

 

从上面代码是运行结果可以知道:

1.const如果放在 * 的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。

2.const如果放在 * 的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。

3.如果const同时放在 * 的两边,既保证指针指向的内容不能通过指针来改变,又保证了指针变量的内容不能修改。

4、传址调用

在刚才提到的strlen模拟实现的函数中传的参数s就是指针,这种调用函数的方式就称为传址调用。

#include <stdio.h>

int my_strlen(char *s)
{
     char *p = s;
     while(*p != '\0' )
     p++;
     return p-s;
}

int main()
{
     printf("%d\n", my_strlen("abc"));
     return 0;
}

当创建函数时 使用传值调用无法达到目标功能时,可以使用传址调用,下面举一个例子。

#include <stdio.h>

//写⼀个函数,交换两个整型变量的值
void Swap1(int x, int y)
{
     int tmp = x;
     x = y;
     y = tmp;
}

int main()
{
     int a = 0;
     int b = 0;
     scanf("%d %d", &a, &b);
     printf("交换前:a=%d b=%d\n", a, b);
     Swap1(a, b);
     printf("交换后:a=%d b=%d\n", a, b);
     return 0;
}

运行代码结果如下:

我们打开监视窗口查看变量的内存分布情况

发现没产生交换的效果 ,这是因为在main函数内部,创建了a和b,a的地址是0x0135fafc,b的地址是0x0135faf0,在调⽤ Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是 x的地址是0x0135fa18,y的地址是0x0135fa1c,x和y确实接收到了a和b的值,不过x的地址和a的地址不 ⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap1函数内部交换x和y的值, ⾃然不会影响a和b,当Swap1函数调⽤结束后回到main函数,a和b的没法交换。Swap1函数在使⽤ 的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这 种叫传值调⽤。

结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。

要将a、b的值传递给函数并对a、b的值进行修改,这个时候我们就可以使用传址调用了

#include <stdio.h>

void Swap2(int*px, int*py)
{
     int tmp = 0;
     tmp = *px;
     *px = *py;
     *py = tmp;
}

int main()
{
     int a = 0;
     int b = 0;
     scanf("%d %d", &a, &b);
     printf("交换前:a=%d b=%d\n", a, b);
     Swap1(&a, &b);
     printf("交换后:a=%d b=%d\n", a, b);
     return 0;
}

 运行结果:

传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤。

5、二级指针

 指针也是一种变量,创建指针变量时内存也会分配内存单元和地址,而指针变量的地址就存放在二级指针中。

#include <stdio.h>

int main()

{
    int a = 10;
    int* pa = &a;
    int** ppa = &pa;
    return 0;
}

将a、pa、ppa之间的关系画图表示

*ppa就是对ppa中存储的地址解引用,找到pa

**ppa就是对ppa中存储的地址解引用,找到pa,再对pa解引用找到a 

  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值