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

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 小心指针越界

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

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构是计算机科学中非常重要的一门学科,王红梅是一名学习数据结构的学生,也是一名C语言的爱好者。 王红梅对数据结构产生了浓厚的兴趣,她深知数据结构在程序设计中的重要性。数据结构是组织和存储数据的方法,它能够提高程序的效率和性能。在学习数据结构的过程中,王红梅了解了各种数据结构,如链表、栈、队列、树等。 在C语言方面,王红梅有着扎实的基础。C语言是一种非常广泛应用于计算机程序设计的编程语言,它提供了丰富的数据类型和函数库。C语言的特点是灵活、高效,适合用于实现各种数据结构。 王红梅在学习数据结构的过程中,利用C语言的特性,编写了许多实用的程序和算法。她善于运用C语言的语法和函数,通过编写代码来实现各种数据结构的操作和应用。她懂得如何动态分配内存、如何使用指针来操作数据结构,以及如何优化算法的时间和空间复杂度。 王红梅热衷于学习和探索数据结构和C语言的更深层次的知识。她不仅通过自学深入了解数据结构的原理和应用,还积极参与课堂讨论和实践项目,与其他同学分享经验和思考。她坚信通过不断学习和实践,她可以成为一名优秀的数据结构和C语言的专家。 总之,王红梅是一位热爱数据结构并且精通C语言的学生。她对数据结构和C语言有着深入了解,并通过编写代码和实践项目来提升自己的技能。她相信在不断学习和实践的过程中,她可以成为一位出色的数据结构王者。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值