C语言—指针(1)

1.内存和地址

1.1 内存

计算机中CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放到内存中。那么内存中的数据是如何高效管理的呢?
计算机中的内存如同酒店中每个房间的编号,也是把内存划分为一个个的单元,每个单元的大小取1个字节.

1byte(字节) = 8bit(比特)
1kb = 1024byte
1mb = 1024kb
1gb = 1024mb
1tb = 1024gb
1pb = 1024tb

每个内存单元相当于一个学生宿舍,每个内存单元为1字节,存放8个bit位。就好比如同学们住的是8人间,每个同学表示1个比特位。1个bit位可以储存二进制的0或1.

每个内存单元都有一个编号(这个编号就相当于每个宿舍的门牌号),有了这个编号,CPU就能很快的找到一块内存空间。
在这里插入图片描述
生活中我们把门牌号称作这个房间的地址,计算机中每个内存编号也是地址。
我们把这个地址叫做:指针
内存单元的编号 == 地址 == 指针

1.2 编址

CUP访问内存中的某块字节空间,就必须知道这个字节空间在内存中的哪块位置,因为内存中的字节有很多,所以就要给内存进行编址(就如同宿舍有很多需要给每个宿舍编号)
计算机内有很多的硬件单元,而硬件单元之间是要协同工作的,互相之间要进行数据的传递。
硬件与硬件是相互独立的,那么 如何进行数据的传递呢?答案是用线连起来。
而CPU和内存之间也有大量的数据进行交互,所以两者也必须用线连起来。
在这里插入图片描述
控制总线: 输入要干什么(比如读数据,写数据)
地址总线: 通过地址线找到内存单元中数据
数据总线: 进行数据传递

2. 指针变量和地址

2.1 取地址操作符(&)

C语言中创建变量就是向内存中申请空间
比如:

在这里插入图片描述
&: 取地址操作符,是个单目操作符
&a:表示取出a的地址
比如:
在这里插入图片描述
int 型的变量占4个字节,所以创建a变量向内存中申请了4个字节的空间
然而&a取出的是00EFFF950
&a取出的是a所占4个字节中地址较小的字节的地址

2.2 指针变量

指针变量: 指针变量是用来存放地址的,存放在指针变量中的值都会被理解为地址.
比如:
在这里插入图片描述

2.2.1拆分指针类型

p的类型是"int * ",我们如何理解指针的类型呢?

int a = 10;
int* p = &a;

p的左边是int* ,*说明p是指针变量,int说明p指向的类型是整型(int)类型的对象。
在这里插入图片描述

2.2.2解引用操作符

将地址保存起来如何使用呢?
现实生活中我们通过门牌号可以找到某个房间,即可以拿到房间中的某个东西。
C语言中我们拿到了地址(指针),就可以找到地址(指针)指向的对象。
这里我们就要介绍一种操作符解引用操作符:*
在这里插入图片描述
*p的意思是通过p中存放的地址找到指向的空间。
*p就是表示a变量,*p=0;就是把a改成了0;
在这里插入图片描述

2.3指针变量的大小

指针变量的大小取决于地址的大小
32位平台下的指针变量的大小为32个bit位,即4字节
64位平台下的指针变量的大小为64个bit位,即8个字节

比如:32位平台下
在这里插入图片描述
64位平台下
在这里插入图片描述
所以:指针变量的大小和类型是无关的,相同的平台下 指针变量的大小是相同的.

3. 指针±整数

先上一段代码,调试观察地址变化

#include<stdio.h>
int main()
{
	int a = 10;
	char* p =(char*)&a;
	int* pc = &a;
	printf("%p\n",&a);
	printf("%p\n",p);
	printf("%p\n",p+1);
	printf("%p\n",pc);
	printf("%p\n",pc+1);
	return 0;
}

可以看到代码运行结果如下:
在这里插入图片描述
char *类型的指针+1跳过一个字节
int * 类型的指针+1跳过4个字节
结论:指针的类型决定了指针+1向前或者向后走多大的距离

4. void * 指针

void * 类型的指针是一种特殊类型的指针,可以叫作无具体类型指针,也可以叫做泛型指针
优点:
可以接受任何类型的地址
局限性:
不能进行±处理和解引用运算

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

	*p = 20;
	*pc = 0;

	return 0;
}

用VS运行上述代码会报错,无法直接进行指针运算
在这里插入图片描述
那么void * 类型的指针有什么用呢?
其实void * 类型的指针一般是用在函数的参数部分,用来接收不同类型数据的地址。

5. const 修饰指针

5.1 const修饰变量

变量是可以被修改的,如果我们不希望变量被修改该如何做呢?
先上一段代码:

#include<stdio.h>
int main()
{
	int a = 10;
	a = 20;   //a是可以被修改的
	const int p = 20;
	p = 30;  //p是不能被修改的

	return 0;
}

上述代码中的p是不能被修改的,p本身是变量,只是被const修饰加上的语法限制,我们若对p进行修改,就不符合语法规则,编译就会报错。

5.2 const 修饰指针变量

#include<stdio.h>
int main()
{
	int a = 10;
	a = 20;   //a是可以被修改的
	const int p = 20;
	p = 30;  //p是不能被修改的

	return 0;
}

如果我们非要将p的值进行修改怎么办呢?
答案是:
我们可以创建一个指针变量,将p的地址传过去,然后通过解引用对p进行修改。
这就相当于高启强对老默说:“我想吃鱼了”,高启强不会主动出手,而是让老默出手。
下来我们拿一段代码演示一下:

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

上述代码运行的结果:
在这里插入图片描述
这样做确实修改了p,但const修饰的本质就是希望变量不能被修改,所以我们也应该让pc拿到p的地址后对p也不能修改,该怎么做呢?

#include<stdio.h>
void test1()
{
	int n = 10;
	int m = 20;
	int* p = &n; //ok
	p = &m;  //ok
}
void test2()
{
	int n = 10;
	int m = 20;
	const int* p = &n;
	*p = 20; //err
	p = &m;  //ok
}
void tset3()
{
	int n = 10;
	int m = 20;
	int* const p = &n;
	*p = 20; //ok
	p = &m; //err
}
void test4()
{
	int n = 10;
	int m = 20;
	int const* const p = &n;
	*p = 20; //err
	p = &m; //err
}
int main()
{
	//测试无const修饰的情况
	test1();
	//测试const放在*左边的情况
	test2();
	//测试const放在*右边的情况
	test3();
	//测试*的左右都有const
	test4();

	return 0;
}

结论:const修饰指针变量的时候
(1)const放在* 的右边限制的是指针变量本身,指针变量不能再指向其他变量了,但是可以通过指针变量修改指针变量所指向的内容
(2)const放在* 的左边限制的是指针指向的内容,不能通过指针修改指针指向的内容,但是可以修改指针变量本身的值(修改的指针变量的指向)

6.指针运算

指针的基本运算有三种:
指针±整数
指针-指针
指针的关系运算

6.1 指针±整数

我们都知道数组在内存中是连续存放的,只要知道首元素的地址就可以顺藤木瓜知道后面元素的地址

int a[10] = {1,2,3,4,5,6,7,8,9,10};

在这里插入图片描述
先上代码:

#include<stdio.h>
int main()
{
	int arr[10]={1,2,3,4,5,6,7,8,9,10};
	int* p =&arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	for(int i = 0;i < sz; i++)
	{
		printf("%d ",*(p + i));//p +i 就是指针+整数
    }

	return 0;
}

6.2指针 - 指针

我们都知道日期-日期得到的是两个日期之间的天数
所以指针-指针的绝对值得到的是两指针之间的元素个数
指针-指针的前提条件是:指针与指针在同一块内存空间
应用一:

#include<stdio.h>
int main()
{
	int arr[10] = {0};
	printf("%zd\n",&arr[9]-&arr[0]);
	printf("%zd\n",&arr[0]-&arr[9]);
	return 0;
}

上述代码运行后的结果为:

在这里插入图片描述
应用二:
模拟strlen函数的实现

#include<stdio.h>
int my_strlen(char* str)
{
	char* start = str;
	while(*str!='\0')
	str++;
	return str - start;
}

int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);//数组名表示数组首元素的地址
	printf("%d\n",len);
	return 0;
}

6.3指针的关系运算

指针的关系运算其实就是指针和指针比较大小即地址和地址比较大小
比如:

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];
	while(p < arr + sz) //p小于起始地址+sz
	{
		printf("%d ",*p);
		p++;
	}
	return 0;
}

本节就讲到这吧!欲知后事如何,请听下回分解

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值