指针(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 = #
*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 = #
p = &n;//没有错误
*p = 20;//发生错误
}
意思:表示指针指向的内容,不能通过指针来改变了。但是指针变量本身的值是可以改的,p是可以改的。
第二种:
int main()
{
const int num = 10;
int n = 100;
int* const p = #
p = &n;//错误
*p = 20;//没有错误
}
意思:表示指针变量p本身不可以修改了,但是指针指向的内容,是可以通过指针变量来改变的。
第三种:
int main()
{
const int num = 10;
int n = 100;
const int* const p = #
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;
}