解释指针知识(一)--指针变量、内存、地址与const

系列文章目录


        在我们生活中,我们在找寻一个房间时我们一般都需要知道他在几楼第几个房间,这样非常麻烦,于是我们就弄了个门牌号比如402,这样我们一看就知道在哪;那在c语言中是否有类似门牌号这样的东西呢?答案是有的,在c语言中我们数据就是以类似门牌号的方式来存储数据的,可以理解成我们所说的地址,那我们怎么来存储这个地址呢?接下来让我来介绍指针。我写这篇文章为了让读者更好的理解指针,希望读者可以对我的文章做出评价以及对需要更改的不足的地方进行修改,同时复习自己所学到的指针知识。希望读者进行反思,总解,复盘。


一、内存与地址.

          在计算机里我们是怎么用内存来储存数据的?和我们前言一样,在计算机里我们有一个硬件叫做处理器cpu(中央处理器),我们知道在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那这些内存空间如何⾼效的管理呢?

          其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。

        ⼀个⽐特位可以存储⼀个2进制的位1或者0。其中,每个内存单元,相当于⼀个学⽣宿舍,⼀ 个⼈字节空间⾥⾯能放8个⽐特位,就好⽐同学们 住的⼋⼈间,每个⼈是⼀个⽐特位。

        每个内存单元也都有⼀个编号,⽣活中我们把⻔牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。C语⾔中给地址起 了新的名字叫:指针。

                                        所以我们可以理解为: 内存单元的编号 == 地址 == 指针

         

  变量是“保存数值的盒子”,它并不是如下图1那样无序存放的。   

        图1:

        它是有序存放的,如图2

        图2:

        变量具有多个属性。比如其中一个属性就是数据类型的长度,如图1中int和double有不同的长度,都可以通过sizeof来计算。

        在广阔的内存空间上,存在着许多的对象,这就需要一个方式来对这些空间进行标记“位置”,这就是我们说所的地址

二、指针是什么?        

        在c语言学习中,指针是一个非常重要的概念,它的作用是“指示对象”。那么到底什么是指针?实际上我们口头说的指针是指地址,在c语言中指针应该是应该指针变量,这个变量是用来储存我们所需要的地址的。

        我们给出一下代码2-1(错误程序):

#include <stdio.h>
//计算俩个整数的和与差。
int sum_sub(int n1,int n2,int sum ,int sub)
{
	sum = n1 + n2;
	sub = n1 > n2 ? n1 - n2: n2 - n1;
}
int main()
{
    //俩个整数
	int n1 = 0;
	int n2 = 0;
    //初始化和与差为0;
	int sum = 0; int sub = 0;

	puts("请输入俩个数");
	printf("整数n1:"); scanf("%d", &n1);
	printf("整数n2:"); scanf("%d", &n2);
    //使用函数来进行操作
	sum_sub(n1, n2,sum,sub);

	printf("俩个整数的和是%d,俩个整数的差是%d\n", sum, sub);

	return 0;
}

        程序的结果是

        结果为什么会这样呢?因为main调用函数sum_sub()时实参传给形参的这个过程是单向的,这种传递方式叫做传值运算,改变形参的值是不会使原来的sum,sub发生改变的。

        那我们怎么通过函数来改变sum,sub的值呢?答案就是指针。通过指针改变主函数里n1,n2的值,在进入别的函数时就可以进行正常的运算。

三、指针变量与地址

        3.1 取地址符号(&)

        每个对象都有地址,那么我们怎么来查看地址的数值呢?%p占位符就是取出地址。

我们给出代码清单3-1:

#include <stdio.h>
int main()
{
    int a = 0;
    double b = 0.0;
    int c[3] = {0};

    printf("a 的地址是:%p",&a);
    printf("b 的地址是:%p",&b);
    printf("c[0] 的地址是:%p",&c[0]);
    printf("c[1] 的地址是:%p",&c[1]);
    printf("c[2] 的地址是:%p",&c[2]);

    return 0;
}

        再vs2022的x86环境下运行如下:

        

        对象的地址通常是用十六进制的数值进行表示的。但是在不同的编译器中不同的环境下基数,位数以及具体数值都会不同

        因为每次创建变量我们都会在mian函数所在的空间中进行a的空间的声明,地址的结果是不确定的。

        

        我们一直称单目运算符&取地址运算符。在对象名前面加上&就可以得到这个变量的地址如图3

        如图有一个数组对象a[],它占了3个单元格,每一个元素都有地址,那么整个对象的地址就是它的首地址222号。

  注 :在scanf 函数的实参中应用了取址运算符。另外,双目运算符&(按位与)要注意。

           &地址符号与其说取得地址不如说生成地址。


        只显示地址没有什么明显的意思,我们通过下面的程序来具体的应用一下。

代码清单3-2:我们使用lovewan表示喜欢wan的人

#include <stdio.h>

int main()
{
    int hon = 20;//表示小红的年龄
	int tian = 34;//表示大田的年龄
    

	int* wanlove = &hon;//表示小王喜欢小红的年龄
	int* guilove = &tian;//表示小贵喜欢小田的年龄

	printf("小王喜欢的年龄:%d\n", *wanlove );
	printf("小贵喜欢的年龄:%d\n", *guilove );

	wanlove = &tian;	//移情别恋,表示小王喜欢小田的年龄了

	*guilove = 40;     //将guilove指向的对像赋值为40//修改了小贵的喜欢的人的年龄

	putchar('\n');
	printf("小红的年龄:%d\n", hon);
	printf("小田的年龄:%d\n", tian);
	printf("小王喜欢的人的年龄:%d\n", *wanlove );
	printf("小贵喜欢的人的年龄:%d\n", *guilove );


	return 0;
}

通过这个代码我们介绍一下指针变量和解引用符号.

3.2 指针变量和解引用

        在对wanlove和guilove变量的声明中,变量名前带有*int*表示这个变量是“指向int型变量的指针变量”,它指向的int型对象。

        解释一下int型变量int*型变量有什么区别。可以把它们俩比作俩个不同的盒子

        int型变量:

               保存“整数”的盒子。

        int*型变量:

                保存“存放int型对象的地址”的盒子。

        在图中如果int* wanlove;wanlove= &ming执行,那么我们ming所在的内存空间的地址就会被储存到我们的wanlove变量里,之后我们对wanlove解引用*wanlove就可以取出这个地址所指向的空间的数据,我们并且可以修改

        *wanlove= 10;

        如上将wanlove所指向的地址的数据给赋值为10;可以理解为下图。

        相信你已经懂了解引用*的用法,这时有人就会想为啥不直接改ming的值,而要使用指针,

其实在c语言中,间接的使用是有好处的,而且我们有些操作只有通过指针来解决。

        3.4指针变量的大小

        指针变量的大小是根据硬件的位数大小来决定的,譬如在32位机器里一般都是32bit的大小,即4个字节,在64位的机器里就是64bit即8个字节。

        想要查看机器的指针变量大小可以通过代码3-3来查看(在vs2022的x86环境下,即32位下)

#include <stdio.h>
int main()
{
	printf("%zd\n", sizeof(char *));
	printf("%zd\n", sizeof(short *));
	printf("%zd\n", sizeof(int *));
	printf("%zd\n", sizeof(double *));

	return 0;
}

(在64位下的结果)

结论:

• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节

• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节

• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

3.5 不同类型的指针变量解引用

        我们直接来看俩段代码

        代码3-4

#include <stdio.h>
int main()
{
 int a = 0x11223344;
 int *pa = &a; 
 *pa = 0; 
 return 0;
}

        调试:

        代码3-5

#include <stdio.h>
int main()
{
 int c = 0x11223344;
 char *pc = (char *)&c;
 *pc = 0;
 return 0;

        调试:

         通过调试我们可以知道代码1-5会将n的4个字节全部改为0,但是代码1-6只是将n的第⼀个字节改为0。

        因此我们得出结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。比如char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

        3.6 指针变量加减整数

        不同类型的指针变量加减整数是否不同?我们通过下面代码3-6来查看是否不同。

        

#include <stdio.h>
int main()
{
	int a = 0;
	char b = 0;

	int* pa = &a;
	char* pb = &b;

    //看a的地址
	printf("%p\n", pa);
	printf("%p\n", pa + 1);
	printf("%p\n", pa - 1);

	printf("\n\n");
    //看b的地址
	printf("%p\n", pb);
	printf("%p\n", pb + 1);
	printf("%p\n", pb - 1);
	return 0;
}

        可以得出结论char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。即指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

        3.7 void*指针

        在学过的知识我们知道void表示表示空类型,那么指针是否有void*类型呢?答案是有的,再指针中我们把void*这个特殊的指针叫做泛型指针(可以理解为无具体类型的指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的+-整数和解引⽤的运算

        代码3-7

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;

	char* pc = &a;

	return 0;
}

        编译器会发出警告(如下图),因为类型不兼容。而是用void*则不会。

        代码3-8

#include <stdio.h>
int main()
{
	int a = 10;
	void* pa = &a;
	void* pc = &a;

	*pa = 10;
	*pc = 0;
	return 0;
}

虽然我们可以兼容但是,如果对void*类型的变量解引用则会报错。⽆法直接进⾏指针运算。

        那么 void* 类型的指针到底有什么⽤呢? ⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以 实现泛型编程的效果。之后在指针与函数的应用我们讲解。

        指针变量我们就讲到这。通过上面的知识我们会发现,指针是可以改变直接地址的数值的,当地址的值发生改变之后,想要修改起来就会很麻烦,那当我们不想让一个值修改怎么办呢?下面我们介绍一个东西,叫做const。

四、const修饰指针

        4.1 const修饰变量

        代码4-1

#include <stdio.h>
int main()
{
 int m = 0;
 m = 20;//m是可以修改的
 const int n = 0;
 n = 20;//n是不能被修改的
 return 0;
}

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

        既然指针是可以改变地址,那我们使用指针会怎么样,代码4-2

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

这样n的值就被修改了。

如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让 p拿到n的地址也不能修改n,那接下来怎么做呢?

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

        如果再vs下输入这种代码

        

        通过这三个我们就可以得出结论:

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

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

        

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值