1. 内存和地址
1.1内存和地址
1.1内存
我们可以通过一个小案例来了了解:
假设有一栋宿舍楼,把你放在楼里,楼上有100个房间号,但房间里没有编号,刚好你的一个朋友找你玩,如果想要找到你就得挨个房间找,这样子效率很低,但是如果我们根据楼层和楼层的房间号的情况,给每个房间编上号,如:
1 一楼:101 102 103......
2 二楼: 201 202 203.......
.........
有了房间号,你的朋友就可以快速找到你。
把上面的问题放到计算机里,那会怎么样呢?
我们知道cpu在处理数据的时候,需要的数据是在内存中读取的,处理后的数据又会放在内存里。
把内存划分为一个个内存单元,每个内存单元的大小取1个字节
一个比特位可以储存一个2进制的位1或者0
bit 比特位
byte 字节
KB
MB
GB
TB
PB
它们之间的关系
1 byte=8 bit
1 KB=1024 byte
1 MB=1024 KB
1 GB=1024 MB
1 TB=1024 GB
1 PB=1024 TB
其实每个内存单元,相当于一个学生宿舍,一个人字节空间 里面能放8个比特位,就好比一间宿舍里住八个人。
每个内存空间里也有编号(相当于宿舍门牌号),有了编号,CPU就能快速找到一个内存空间。生活中我们把门牌号也叫地址,在计算机中内存单元的编号也称为地址。C语言给地址去了一个新的名称叫:指针。
我们可以理解为:
内存单元的编号==地址==指针
2. 指针变量和地址
2.1取地址操作符(&)
在C语言中创建变量其实就是向内存里申请空间,比如
#include<stdio.h>
int main()
{
int a = 10;//创建变量a的本质是向内存里申请空间
return 0;
}
该代码就i是创建了整型变量a,内存中申请了四个字节,用来存放整型10,其实每个字节都有地址
那我们如何得到a的地址呢?
在这里我们就只需要一个操作符(&)--取地址操作符
#include<stdio.h>
int main()
{
int a = 10;//创建变量a的本质是向内存里申请空间
&a;//取出a的地址
printf("%p\n", &a);
return 0;
}
2.2指针变量和解引用操作符(*)
2.2.1指针变量
那我们通过取地址操作符(&)拿到的地址是一个数值,比如:006FF7C8,这个数值有时候也是需要储存起来,方便后期使用,那我们把这样的地址存放在那里,那就是指针变量中
#include<stdio.h>
int main()
{
int a = 10;//创建变量a的本质是向内存里申请空间
int* pa = &a;//取出a的地址并存储到指针变量pa中
return 0;
}
指针变量也是变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。
2.2.2 如何拆解指针类型
int a = 10 ; //创建变量的本质是向内存申请一个空间
int * pa = &a ;//取出a的地址并储存到指针变量pa中
当我们看到这代码的时候,该怎么理解它呢?
pa左边写的是int*,* 说明pa是指针变量 ,而前面的int是在说明pa指向的类型是(int)类型的对象。
2.2.3 解引用操作符(*)
在C语言中我们只要拿出地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,在这里我们要学习一个操作符--解引用操作符(*)
#include<stdio.h>
int main()
{
int a = 100;
int* pa = &a;
*pa = 0;
printf("%d\n",a);
return 0;
}
上面代码第6行中就使用了解引用操作符,*pa的意思是通过pa中存放地址,找到指向的空间,*pa其实就是a的变量;所以*pa=0, 这个操作符是把a改为0。
那可能有疑惑,为什么不直接把a改为0呢,写成a=0;不就可以了吗?非得要用指针呢?
其实我们把a的修改直接交给pa来操作,这样对a的修改,就多了一条路径,这样写的代码更加灵活。
2.3 指针变量的大小
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(double *));
return 0;
}
结论:
32位平台下地址是32个bit位,指针变量大小是4个字节
64位平台下地址是64个bit位,指针变量大小是8个字节
指针变量的大小和类型无关,只要指针类型的变量,在相同的平台下,大小都是相同的
3. 指针变量类型的意义
指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,为什么还要有各种各样的指针类型呢?
其实指针类型是有特殊意义的
3.1指针的解引用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
//代码1
int n = 0x11223344;
int* pi = &n;
*pi = 0;
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
//代码2
int n = 0x11223344;
char * pi =(char *) &n;
*pi = 0;
return 0;
}
我i们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字符改为0
结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字符)
比如:char *的指针解引用就只能访问一个字节,而int *的指针的解引用就能访问4个字节
3.2 指针+-整数
通过一段代码,更好了解指针的+-法
#include<stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("n =%p\n", &n);
printf("pc =%p\n", &pc);
printf("n+1 =%p\n", &n+1);
printf("pc+1=%p\n", &pc+1);
return 0;
}
我们可以看出,char *类型的指针变量+1跳过1个字节,int *类型的指针变量+1跳过了4个字节
这就是指针变量类型差异带来的变化。
结论:指针类型决定了指针向前或者向后走一步有多大距离。
3.3 void*指针
在指针中void类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以接受任意指针类型地址。但是有局限性,void *类型的指针不能进行指针的+-整数和解引用的运算。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a = 10;
int * pa = &a;
char * pc = &a;
return 0;
}
像这种,将一个int类型的变量的地址赋给一个char*类型的指针变量,编译器会给出警报的
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a = 10;
void * pa = &a;
void * pc = &a;
return 0;
}
而这样就不会给出警报。
4. const修饰指针
4.1 const修饰变量
变量是可以修改的,如果把变量的地址交给一个指针,通过指针变量的也可以修改这个变量。
但是如果我们希望一个变量加上一些限制,不能被修改呢,该怎么做呢?这时候const就起作用了
#include<stdio.h>
int main()
{
int m = 0;
m = 20;//可以被修改
const int n = 0;
n = 20;//n是不能被修改
printf("%d\n", m);
printf("%d\n", n);
return 0;
}
n是不能被修改的,其实n的本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n修改,就不符合语法规则,就报错,致使无法直接修改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;
}
我们可以看到这里确实修改了,为什么n要被const修饰呢?就是为了不能被修改,如果p拿到n的地址就能修改,这样就打破了const的限制,就不合理了,所以应该让p拿到n的的地址也不能修改n,那接下来该怎么做呢?
#include<stdio.h>
void test1()
{
int n = 10;
int m = 20;
int* p = &n;
*p = 20;//ok?
p = &m;//ok?
}
void test2()
{
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()
{
test1();
test2();
test3();
test4();
return 0;
}
结论:const修饰指针变量的时候
const如果放在*左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
const如果放在*的右边,修饰的是指针变量的本身,保证指针变量的内容不能修改,但是指针直线的内容可以修改,可以通过指针改变。
5. 指针运算
指针的基本运算:
指针+-整数
指针-指针
指针的关系运算
5.1指针+-整数
因为数组在内存中是连续存放的,只要知道第一个元素的地址,就可以知道后面的所有元素
#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]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
5.2 指针-指针
得到的是一个整数,计算的前提是两个指针指向了同一块空间
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
5.3 指针的关系运算
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int * p = &arr[0];//arr[0]相等于int *p=arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
while (p < arr + sz)
{
printf("%d ", * p);
p++;
}
return 0;
}
6 野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不确定的、没有明确限定的)
6.1 野指针的成因
1.指针未初始化
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int* p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
int i = 0;
for ( i = 0; i <=11; i++)
{
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放
#include<stdio.h>
int* test()
{
int n=100;
return n;
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
6.2 如何避免野指针
6.2.1 指针初始化
#include<stdio.h>
int main()
{
int num = 10;
int* p1 = #
int* p2 = NULL;
return 0;
}
6.2.2 小心指针越界
一个程序向内存申请了那些空间,通过指针也就只能访问那些空间,不能超出范围,超出范围就是越界访问。
标签:return,变量,探索,int,地址,深入,include,指针 From: https://blog.csdn.net/2201_76027234/article/details/139129325