首页 > 其他分享 >探索c语言:深入了解指针

探索c语言:深入了解指针

时间:2024-05-25 15:58:30浏览次数:22  
标签:return 变量 探索 int 地址 深入 include 指针

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 = &num;
	int* p2 = NULL;
	return 0;
}

 6.2.2 小心指针越界

一个程序向内存申请了那些空间,通过指针也就只能访问那些空间,不能超出范围,超出范围就是越界访问。

标签:return,变量,探索,int,地址,深入,include,指针
From: https://blog.csdn.net/2201_76027234/article/details/139129325

相关文章

  • 深入浅出Viewport设计原理2
    逻辑像素、逻辑分辨率对于同一个元素,DPR越大,渲染时需要的物理像素就越多。这是我们上面得出的结论。那么,在软件开发中,元素的大小到底应该写成多少px?为了解决这个问题,我们引入“逻辑像素”的概念。平时我们在css中写的px指的就是逻辑像素,而不是物理像素,一个逻辑像素可以......
  • Linux命令探索:深入了解which命令
    Linux命令探索:深入了解which命令在Linux系统中,which命令是一个非常有用的工具,用于定位并显示给定命令的绝对路径。本文将详细介绍which命令的用法,帮助读者更好地理解和运用这个命令。了解which命令which命令用于查找并显示系统中某个命令的绝对路径。它的基本语法如下:which......
  • 探索Linux中的神奇工具:深入了解find命令
    探索Linux中的神奇工具:深入了解find命令在Linux系统中,find命令是一个强大且灵活的工具,用于在文件系统中查找符合条件的文件和目录。本文将详细介绍find命令的基本用法和一些常见选项,帮助读者更好地理解和运用这个命令。了解find命令find命令用于在指定目录及其子目录中查找......
  • 030 指针学习—指针定义与指针变量
    目录1指针定义(1)指针(2)存储单元的地址和存储单元的内容(3)指针访问形式(4)注意事项2指针变量(1)定义(2)一般形式(3)引用指针(4)函数参数——指针(5)注意事项1指针定义(1)指针        指针是一个变量,其值为另一个变量的地址。通过指针,你可以直接访问和操作存储在内存中......
  • 探索Go语言的原子操作秘籍:sync/atomic.Value全解析
    引言​在并发编程的世界里,数据的一致性和线程安全是永恒的话题。Go语言以其独特的并发模型——goroutine和channel,简化了并发编程的复杂性。然而,在某些场景下,我们仍然需要一种机制来保证操作的原子性。这就是sync/atomic.Value发挥作用的地方。原子性:并发编程的基石​......
  • 深入理解Java的垃圾回收机制(GC)实现原理
    深入理解Java的垃圾回收机制(GC)实现原理Java的垃圾回收机制(GarbageCollection,GC)是其内存管理的核心功能之一。通过GC,Java自动管理对象的生命周期,回收不再使用的对象所占的内存空间。本文将详细探讨GC的实现原理、不同算法的细节以及其在JVM中的应用。1.垃圾回收的基本......
  • NFT数藏平台开发:探索数字艺术的新领域
    随着区块链技术的飞速发展和普及,NFT(非同质化通证)作为一种独特的数字资产形式,正逐渐改变我们对数字艺术、收藏品和知识产权的认知。NFT数藏平台作为连接创作者、收藏家和市场的桥梁,其开发不仅为数字艺术提供了新的展示和交易渠道,更为整个数字艺术生态带来了深远的影响。基于基......
  • 【简单探索微软Edge】
    ......
  • AI大模型探索之路-实战篇5: Open Interpreter开放代码解释器调研实践
    系列篇章......
  • 深入解析Python并发的多线程和异步编程
    在Python编程中,多线程是一种常用的并发编程方式,它可以有效地提高程序的执行效率,特别是在处理I/O密集型任务时。Python提供了threading模块,使得多线程编程变得相对简单。本文将深入探讨threading模块的基础知识,并通过实例演示多线程的应用。1.多线程基础概念在开始之前,让我们......