指针(1)-学习笔记

1.内存

1.1内存

内存就像一个房间号,有了房间号,就可以快速的找到,就可以提高效率。我们知道CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,其实就是把内存划分为一个个的内存单元,每个内存单元的大小取1个字节

计算机常见的单位:

bit - 比特位
Byte - 字节
KB
MB
GB
TB
PB

单位换算:

1Byte = 8bit
1KB = 1024Byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB

其中,每个内存单元,相当于一个学生宿舍一个字节空间里面能放8个比特位,就相当于,一个宿舍有八个人,每个人是一个比特位

每个内存单元也都有⼀个编号(这个编号就相当于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。

在计算机中把内存单元的编号叫做地址,C语言中给地址起了一个新名字指针
所以我们可以理解为内存单元的编号== 地址==指针

2.指针变量和地址

变量创建的本质是什么?
是向内存申请空间
int a=10,是向内存申请4个字节的空间,因为int类型在内存中占四个空间。

2.1取地址操作符(&)

我们可以写一段代码来观察一下地址:
在VS中逐句运行开始后,才可以打开观察内存的页面。
在这里插入图片描述

#include<stdio.h>
int main()
{
	int a = 10;
	printf("%p\n",&a);//&--取地址操作符
	return 0;
}

在这里插入图片描述
比如,上述的代码就是创建了整型a,内存中申请4个字节,用于存放整数10,其中每个字节都是有地址的,上图4个字节的地址分别是:

0x0069FEA0
0x0069FEA1
0x0069FEA2
0x0069FEA3

&a取出的是a所占4个字节中地址较小字节的地址。虽然整型变量占用4个字节,我们只要知道了第一个字节地址,顺藤摸瓜访问到4个字节的数据也是可⾏的。

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

2.2.1 指针变量

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

int main()
{
	int n = 20;
	int* pn = &n;
	return 0;
}

在这里int*是指针变量的类型。pn就被称为指针变量

int n=20;类型是 int
char ch=‘w’;类型是char
int * pn=&n;类型是int *

指针变量是指向了一个变量,比如,上面的代码pn(指针变量)就执行了n(变量)。int中的说明pn是指针变量,int说明pn指向的对象(就是n)是int类型。

2.2.2解引用操作符

在C语言中,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这里就必须来了解一下*(解引用操作符)。

int main()
{
	int n = 20;
	int* pn = &n;
	//解引用操作符-间接访问操作符
	*pn = 30;//*pn就是n
	printf("%d", *pn);
	return 0;
}

这里可以看到n的20被改为了30
在这里插入图片描述
这里是把n的修改交给了pn来操作,这样对n的修改,就多了一种途径,写代码就会更加灵活。
指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。

3 指针变量类型的意义

3.1指针的解引用

指针可以接收与自己不同类型的地址
对比,下面两段代码,观察内存的变化。
代码一:

int main()
{
	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;
	return 0;
}

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

int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;//强制转换类型
	*pc = 0;
	return 0;
}

在这里插入图片描述

在第二段代码中,强制将n的地址从int类型转换到char类型,可以发现,存储在内存中的字节发生了改变,从4个字节变为1个字节。
结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。
比如:char的指针解引用就只能访问一个字节,而int的指针的解引用就能访问四个字节。

3.2指针±整数

观察下面一段代码:

int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	printf("%p\n", pi);
	printf("%p\n", pi + 1);
	return 0;
}

代码运行结果如下:

在这里插入图片描述
我们可以看出,char类型的指针变量+1跳过1个字节,int类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那么也可以-1.
结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。

3.3 void*指针

void* 是无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,void类型的指针不能直接进行指针±整数和解引用的运算
在3.1中说:指针可以接收与自己不同类型的地址,是对的,但是会编译器会报警告,所以这时候就可以使用void
来接收不同类型的地址。
void*的局限性:
在这里插入图片描述

当我们试图改变void类型下的数值,就会报错,所以这里我们就可以看到,void类型的指针可以接受不同类型的地址,但是无法直接进行指针计算。

4.const修饰指针

4.1 const修饰变量

const是常属性-被const修饰后就具有常属性,不能被修改。
1.const修饰普通变量
2.const修饰指针变量
变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量的也可以修改这个变量。

在这里插入图片描述
当const修饰了num后,再改变,编译器提示报错;再C语言中,这里的num是常变量,num的本质还是变量,因为有const修饰,编译器在语法上不允许修改这个变量。
接下来,我们看一段代码:

int main()
{
	const int num = 0;
	int* pn = &num;
	*pn = 20;
	printf("%d", num);
	return 0;
}

结果是:20.
我们绕过num,使用num的地址,去修改num,这样结果是可以的。但是,这是不合理的,我们本来的想法就是希望num不要被修改,接下来应该怎么做呢?那我们可以对pn也做一个修饰,也就是const修饰指针。
const修饰指针可以分为三种情况:
① const放在的左边
②const放在
的右边
③const同时放在*两边

第一种:

int main()
{
	const int num = 10;
	int n = 100;
	const int* p = &num;
	p = &n;//没有错误
	*p = 20;//发生错误
}

意思:表示指针指向的内容,不能通过指针来改变了。但是指针变量本身的值是可以改的,p是可以改的。

第二种:

int main()
{
	const int num = 10;
	int n = 100;
	int* const p = &num;
	p = &n;//错误
	*p = 20;//没有错误
}

意思:表示指针变量p本身不可以修改了,但是指针指向的内容,是可以通过指针变量来改变的。

第三种:

int main()
{
	const int num = 10;
	int n = 100;
	const int* const p = &num;
	p = &n;//错误
	*p = 20;//错误
}

意思:指针变量p不能被修改,指针变量p指向的内容也不能被修改。

5.指针运算

指针的基本运算有三种,分别是:
①指针±整数
②指针-指针
③指针的关系运算

5.1 指针±整数

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

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]);
	for (i = 0; i < sz; i++)//正序
	{
		printf("%d ", *(p + i));//p+i 这里就是指针+整数
	}
	printf("\n");
	for (i = sz-1; i >= 0; i--)//倒叙
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

也可以用下面的形式来写:

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]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *p);
		p++;//p=p+1,p是int类型,一次跳过4个字节
	}
	return 0;
}

上面代码的弊端就是p一直在变化。

5.2 指针-指针

指针-指针后得到的绝对值是指针和指针之间的元素个数。
这种运算的前提条件是:两个指针指向同一块空间
下面的代码就是错误的。

int main()
{//第一个就是好不确定两个数组是不是连续的。
	//第二是不知道是按照char类型来算还是int类型来算
	char arr1[10];
	int arr2[10];
	&arr2[8] - arr2[5];//错误的
	return 0;
}

注释:
sizeof的返回值是size_t;头文件是#include<string.h>,strlen统计的是字符串中“\0”之前的字符的个数
我们现在可以来自己实现以下计算字符串的长度,不使用strlen

#include<string.h>
size_t my_strlen(char* s)
//传过来的是地址,我们也应该用地址来接受,所以就是指针
//要统计字符串的长度,现在就要看s指向的内容如果不等于\0,就++
{
	size_t count = 0;
	while (*s != '\0')
	{
		count++;
		s++;
	}
	return count;
}
int main()
{
	char arr[] = "abcdef";
	//数值名其实是数组首元素地址
	size_t len = my_strlen(arr);//arr==arr[0]
	printf("%d", len);
	return 0;
}

另一种方法:指针-指针得到的是指针和指针之间的元素个数。

size_t my_strlen(char* s)
{
	char* start = s;//将s的起始地址给start
	while (*s != '\0')//找到\0之前的地址
	{
		s++;
	}
	return s - start;
}

5.3 指针的关系运算

其实就是两个地址比较大小。
使用数组的关系运算来打印数组的内容
我们可以拿出来数组中最后一个元素的地址,然后让p取遍历,如果小于最后一个元素的地址,那么就++

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < &arr[sz])
	{
		printf("%d ", *p);
			p++;
	}
	return 0;
}
  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值