IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言基础-指针 -> 正文阅读

[C++知识库]C语言基础-指针

1. 指针是什么

指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

指针是个变量,存放内存单元的地址(编号)。

#include <stdio.h>
int main()
{
	int a = 10;//在内存中开辟一块空间,这块空间占4个字节,将10存入这块空间
	int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
	//将a的地址存放在p变量中,p就是一个之指针变量。
	return 0;
}

在这里插入图片描述

总结:指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。

那么存在两个问题:
1.一个小的单元到底是多大?(1个字节)
2.如何编址?
在初始C语言中,我们知道,一个字节给一个对应的地址是比较合适的,一个地址管理一个字节。

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址时产生一个电信号正电/负电(1或者0),有2^32种可能,这里就有2的32次方个地址。

每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==
232/1024/1024MB==232/1024/1024/1024GB == 4GB) 4G的空间进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址2^64Byte 空间。

注意:
1.在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。

2.那如果在64位机器上,如果有64个地址线,每个地址是一个64位的二进制序列,那一个指针变量的大小是8个字节,才能存放一个地址。

总结:
指针是用来存放地址的,地址是唯一标识一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。

2. 指针和指针类型

在此之前,我们已经学习了C语言的内置数据类型,比如int、float、double等,那么指针有没有类型呢?答案是肯定的。那么指针是怎么定义的呢?

char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL

我们可以看到,指针的定义方式是:指针指向变量的类型+*,例如char*类型的指针指向的是char类型的变量,short*类型的指针指向的是short类型的变量。

int main()
{
	int a = 10;
	int* pa = &a;

	char c = 'a';
	char* pc = &c;

	return 0;
}

既然在同一平台下,指针的大小都是相同的,为什么指针还要有不同的类型呢?下面我们就来聊聊指针类型的意义。

指针类型的意义是什么?

1.指针的解引用

看如下代码:
(1)变量a是int类型,定义int*类型指针p, p = &a,当通过指针p解引用时,访问4个字节空间
在这里插入图片描述

在这里插入图片描述
(2)定义char*类型指针pc,对pc解引用时,能够访问1个字节空间
在这里插入图片描述
2.指针+/-整数

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;

	char* pc = (char*)&a;

	printf("%p\n",pa);
	printf("%p\n",pc);

	printf("%p\n",pa+1);
	printf("%p\n",pc+1);
	return 0;
}

运行结果如下:
在这里插入图片描述
我们发现,pa+1跳过4个字节,pc+1跳过1个字节。

总结:指针类型的意义有两个
1.指针类型决定了指针解引用操作的时候,一次访问几个字节(访问内存的大小)
char*指针解引用访问1个字节、int* 指针解引用访问4个字节

2.指针类型决定了,指针+/-整数的时候的步长(指针+/-整数的时候,跳过的字节数)
跳过的字节数 = 指针指向变量的大小*整数
比如:int*指针 +1 跳过4个字节
char*指针 +1 跳过1个字节

3.野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针成因

1.指针未初始化

int main()
{
	int* p;
	//p没有初始化,里面放的内容是随机值

	*p = 20;
	//将p中存的随机值作为地址,找到一块空间,这个空间不属于我们当前程序,就造成了非法访问,p就是野指针
	return 0;
}

2.指针越界访问

int main()
{
	int arr[10] = 0;
	int i = 0;
	int* p = arr;

	for (i = 0; i <= 10; i++)
	{
		*p = i;
		p++;
	}

	return 0;
}

画图

3.指针指向的空间释放
当一个指针指向的空间释放了,这个指针就变成了野指针。

int* test()
{
	int a = 10;
	return &a;
}

int main()
{
	int* p = test();
	printf("%d\n",*p);

	return 0;
}

程序运行起来:在这里插入图片描述
报出如下警告:在这里插入图片描述
这是为什么呢?我们分析一下这段代码:对于栈空间的生长方向是从高地址到低地址,首先调用main函数,为其分配空间,然后调用test函数,开辟栈帧分配空间,在test函数内部创建局部变量a,test函数调用结束,局部变量a销毁(变量销毁,空间还在),变量a所占用的4个字节空间归还给操作系统,此时把局部变量a的地址返回给指针p,那么指针p就是野指针。此时对p解引用操作就会造成非法访问,因为p指向的空间已经还给操作系统了,我们没有使用权限了。那为什么打印的时候还能打印出来10呢?因为test函数调用结束,函数栈帧销毁后,空间回收,紧接着调用printf函数,然后在main函数栈帧后面重新为printf函数开辟空间(函数调用传参是第一步,传参先于函数体内部代码的执行),此时对于指针p指向的这块空间,内容没有被新的内容覆盖,还是10,并没有改变,所以*p解引用得到的还是10,但是*p这种行为本身是非法的。
在这里插入图片描述
我们增加一行代码:

int* test()
{
	int a = 10;
	return &a;
}

int main()
{
	int* p = test();

	printf("hehe\n");
	printf("%d\n",*p);

	return 0;
}

此时程序运行结果如下:
在这里插入图片描述
为什么是5,不是10了呢?
第一次调用printf函数时,为其开辟栈帧,先进行传参,此时p指向的那块空间已经被新的内容覆盖了,已经不再是10 了,第一次调用printf函数结束后,空间释放,第二次调用printf函数,*p的内容已经改变了,不再是10。

如何避免野指针

1.指针要初始化

指针初始化分为2种:明确指向和初始化为NULL

int main()
{
	int a = 10;
	int* p = &a;//明确地初始化,确定指向

	int* p2 = NULL;//不知道指针当前应该指向哪里,可以初始化为NULL

	//#define NULL ((void *)0) NULL是将0强制类型转换成指针
	return 0;
}

2.小心指针越界

要避免指针越界访问。

3.当指针指向的空间释放时,要将该指针置为NULL

等到学习C进阶动态内存分配时,再详细学习。

4.避免返回局部变量的地址

因为调用函数,开辟栈帧,函数执行完毕,局部变量销毁,空间还给操作系统,我们就没有这块空间的使用权限了,此时把局部变量的地址返回,那么该指针就变成了野指针。

避免返回局部变量的地址,并不是不准返回局部变量,返回局部变量的值,是临时拷贝了一份,将拷贝的那个值返回,所以局部变量销毁了不影响返回的拷贝的那个值。

int test()
{
	int a = 10;
	return a;
}

int main()
{
	int num = test();
	printf("%d\n",num);
	return 0;
}

在这里插入图片描述

对于函数内部的static变量,是在常量区开辟的,所以函数调用结束,static变量的空间并没有被销毁,还是可以通过指针来访问的。

5.使用指针之前检查有效性

int main()
{
	int* p = NULL;//不知道当前指针应该指向哪里,初始化为NULL

	//*p = 20;//对空指针是不能进行解引用的

	//对指针进行有效性判断
	if (p != NULL)
	{
		*p = 20;
	}
	return 0;
}

既然p为NULL时,我们不能进行解引用,那么为什么还要将p初始化为NULL?
这里只是做一个标记,给p一个指向,p不是野指针,但是p为NULL时,我们是不能进行解引用的,因为在C语言中: #define NULL ((void*)0),NULL指向地址为0的那块空间,对于这块空间是不准我们进行访问的,所以,对NULL是不能进行解引用操作的,所以每次对指针进行解引用操作之前,我们要判断是否为空指针。

4. 指针运算

1.指针±整数

#define N_VALUES 5

int main()
{
	float values[N_VALUES];
	float* vp;
	//指针+-整数;指针的关系运算
	for (vp = &values[0]; vp < &values[N_VALUES];)
	{
		*vp++ = 0;
	}
	return 0;
}

在这里插入图片描述
后置++是先使用再++,当vp++表达式的值为&values[4]时,values[4] = 0;然后vp = &values[5],然后判断vp<&values[5]不成立,所以循环条件不满足,循环结束。

这里vp = &values[5],只是获取了数组values最后一个元素后面的地址,并没有对该地址进行解引用操作,所以不会造成越界访问。

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};

	int* p = &arr[9];
	int i = 9;

	//指针-整数
	printf("%p\n",p);
	printf("%p\n",p -1);

	return 0;
}

在这里插入图片描述

2.指针-指针

有没有指针+指针?也就是地址+地址?没有任何意义。所以不讨论指针+指针。
内存是一块连续的空间。

int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9,10};
	printf("%d\n",&arr[9] - &arr[0]);//9
	printf("%d\n", &arr[0] - &arr[9]);//-9
	return 0;
}

C语言语法规定:指针- 指针的绝对值得到的是两个指针之间元素的个数。
在这里插入图片描述
注意:
指针- 指针 的前提是:两个指针指向的是同一块空间,这样指针-指针才有意义。

指针-指针的一个应用:模拟实现strlen函数

int my_strlen(char* ps)
{
	char* s = ps;
	
	while (*ps != '\0')
	{
		ps++;
	}
	return ps - s;
}

int main()
{
	char arr[] = "hello world";
	int len = my_strlen(arr);
	printf("%d\n",len);//11
	return 0;
}

在这里插入图片描述

3.指针的关系运算

指针关系运算就是比较指针的大小。

//代码1

#define N_VALUES 10

int main()
{
	int values[] = {1,2,3,4,5,6,7,8,9,10};

	int* vp = NULL;
	for (vp = &values[N_VALUES]; vp > &values[0];)
	{
		*--vp = 0;//先--,再使用
	}

	return 0;
}

在这里插入图片描述
代码可以修改为下面代码:

//代码2

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
	*vp = 0;
}

在这里插入图片描述
但是应该避免第二种代码的写法。因为C语言标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

5. 指针和数组

数组是一块连续的空间,放的是相同类型的元素。
数组的大小和元素类型、元素个数有关。

指针(变量)是一个变量,放地址。
指针变量的大小是4/8个字节。

数组名是什么?我们里看下面的例子:

#include <stdio.h>
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	return 0;
}

运行结果如下:
在这里插入图片描述
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。

数组名是首元素地址,但是有两个例外。
1.sizeof(数组名) - 表示整个数组,计算整个数组的大小;
2.&数组名 - 表示整个数组,拿到整个数组的地址。

既然可以把数组名当成地址存放到一个指针中,那么我们可以使用指针来访问数组。

int main()
{
	int arr[10] = {0};

	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; i++)
	{
		printf("%d ",*(p+i));
	}
	return 0;
}

6. 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。
一级指针的地址-----二级指针。

int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	return 0;
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
通过二级指针获取数据:


int main()
{
	int a = 10;

	int* p = &a;

	int** pp = &p;

	**pp = 20;
	printf("%d\n",a);//20

	return 0;
}

二级指针pp解引用得到一级指针p,一级指针p解引用得到变量a。

pp = &p;
*pp = p;
**pp = *p;
**pp = a;

7. 指针数组

指针数组是指针还是数组?是数组,存放指针的数组。

int arr1[5] ;
char arr2[6];

在这里插入图片描述
那指针数组该如何定义?

int* arr3[5];//是什么?

在这里插入图片描述

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int* arr[3] = {&a,&b,&c};

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ",*arr[i]);//[]优先级比*高
	}

	return 0;
}

本章完。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-17 15:11:24  更:2021-08-17 15:13:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:36:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码