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语言第八章——指针

取地址运算——&

printf("%p",&i);//输出i的地址

变量取地址

&后面必须有明确的变量,&(i+k)&(i++)都是不行的

相邻的变量取地址

int i;
int p;
printf("%p",&i);
printf("%p",&p);

就可以看到i的地址是6c,p的地址是68,中间差了4,也就是说他们是挨着存放的
进一步的,其实本地变量在内存中的存储是一个堆栈
在这里插入图片描述

数组取地址

在这里插入图片描述
这里会发现,输出a的地址以地址格式输出aa[0]的地址其实是一回事,而且数组的每个元素也是挨着排列的

数组单元的地址

相邻数组的地址

指针——*

指针变量就是保存地址的变量

指针 *是一个单目运算符,用来访问指针的值所表示的那个地址的变量

int i=6;
int *p=&i;

看如上代码,表示

  • 有一个变量,变量名叫 i,这个变量的值为6,这个变量的地址是0x20
  • 另外有一个变量,变量名叫p,这个变量的值为0x20,这个变量的地址是0x36(假设)

是非常好理解的,如下图
在这里插入图片描述

int *p;
int* p;

看如上代码,两行表达的意思是一样的,都表示p是一个指针变量,指向的是一个int变量

  • *p是一个int
  • 而非p是int *类型的变量,不存在int *类型

注意:这里可能会混淆的地方是指针到底是个变量还是个运算,其实这只是看待同一个问题的两个角度,下面分别来阐述

  • 指针是一个变量:即指针也是一个普通的变量,只不过放的不是我们平常的值,而是放的地址,即指针变量就是放着地址的变量,侧重的是p
  • 指针是一种运算:是p是一个变量,里面放的是一个地址,对这个变量进行指针运算,即执行操作——找到这个变量处放的值,侧重的是*

注:作为参数的指针

  • 在函数里面可以通过指针访问外面的变量,拥有了能够访问外面变量的能力
  • 仍以上面的例子来说明:此时p=0x20,而*p=6

之前问题的解释——scanf("%d",&i);

重新来审视这样的一句

scanf("%d",&i);
scanf("%d",i);

同样输入6,但是第二句是错的
解释:

  • 第一句是告诉scanf,把6给i,即把6放在0x20的位置上,所以6把原先i 中的0x55给覆盖掉了
  • 第二句出现了错误是因为,变成了告诉scanf,把6放在0x55的地方上,而如果0x55那个地方恰好不能写,那就崩溃了,如果巧了恰好能写,运行的结果也肯定不对,因为此时把6放在了0x55上,而0x20的位置还是0x55

指针的使用场合

  • 函数要返回多个值,某些值就只能通过指针返回
  • 传入的参数实际上是需要保存带回的结果的变量
    毕竟函数的返回值只能返回一个值,但是python可以返回多个值,所以才没有指针的吗?

例子:交换两个变量的值

以前不用指针的话,是无法用函数来实现交换变量的

void swap(int *a,int*b)
{
	int t=*a;
	*a=*b;
	*b=t;
}

例子:得到最小值最大值

这个例子比较好的体现了参数的作用是为了带出来结果

例子:两个数做除法的函数

函数返回运算的状态,而结果通过指针返回
(其实还是需要返回多个结果)

在这里插入代码片

因此这种指针的应用场景是 程序可能会出错,在C只能这样做,但在后续的语言中采取了异常机制来解决这个问题

指针和数组

函数、指针、数组

如果函数的参数是普通变量,传入的是值
如果函数的参数是指针变量,传入的是也是值,只是这个值是所指向变量的地址
如果函数的参数是数组呢?传入的是什么?

函数参数表里的数组,实际上就是指针,传入的就是数组的首地址

void minmax(int a[],int len,int *min,int *max)

所以就解释的通了

  • 这个参数表里为什么只需要写个a[]
  • 为什么方括号里写数字没用
  • 在函数中用sizeof也不能得到元素个数结果

一切的原因都是因为,传入的是数组的首地址,他是个指针
那你要这么说的话,直接写成指针可以吗?

void minmax(int *a,int len,int *min,int *max)

这样子写当然可以,函数中下面运算a[1]这种东西也是没问题的

  • 是不是下面运算a[1]这种东西,其实就是这个地址往后的那个地址,而只是恰好数组也是这样排列的

四种函数原型 是等价的

则下面 四种函数原型 是等价的便很好理解的

  • int sum(int *ar,int n)
  • int sum(int *,int )
  • int sum(int ar[],int n)
  • int sum(int [],int )

数组是特殊的指针

注意1

  1. 数据变量本身表达地址
  2. 但是数组的单元表达的是变量,需要用&取地址
int a[10];
int *p=a//不需要用&取地址

即写一个数组表达的就是这个地址
a==a[0]

注意2

  1. []可以对数组做,但其实也可以对指针做
int *p=a;

比如p[0]其实也是对的,毕竟p也是指向的那个地址,数组也是指向了一个地址,那p[0]其实就是a[0],本质上来讲还是一回事

  1. 如果p指向是是个普通的变量,也是可以用[]的,要充分的理解
int *p=&i;

这里也是可以用p[0]的,并且表示的就是i,但是p[1]就不行了,毕竟你一个变量,从地址的角度来看,也能看成只有一个元素的数组,即只有p[0]

注意3

  1. *运算符可以对指针做,也可以对数组做
*a=1000;//其实就是a[0]=1000

毕竟表示的都是那个地址,所以也非常好理解

注意4

  1. 实际上,数组是const的指针
    所以之前才说不可以做如下的代码来进行数组的赋值,而是应该利用for循环来对每个单元进行遍历
int b[];
b=a;//这样子不行
int *q=a;//这样子却可以

为什么第三句可以,第二句不行呢?
因为数组是const的指针(再次强调)

int b[]的意思是int * const b,const在这里来说,这个数组被创建出来了,是这个数组,就不能被修改成别的数组了,const的特性 即b[]是一个常量指针

指针和const

指针是const

和上面的数组是const的指针联系起来了

表示一旦得到了某个变量的地址,不能再指向其他变量

int *const q=&i;//q是const
*q=26;//这个可以
q++;//这个是错的

所指是const

表示不能通过这个指针去修改那个变量(并不能使那个变量变成const)
只是不能通过这个指针来修改那个变量了

const int *p=&i;//q是const
*p=26;//这个不行,因为*p是const,不能通过这个指针来修改那个变量
i=26;//这个肯定是没问题的
p=&j;//这个也是可以的,相当于让这个指针换了一个指向

这些是啥意思??

const int *p1=&i;
int const  *p2=&i;
 int * const p1=&i;

判断的标准是const在*的前面还是后面

  • 如果const在*的前面,则是所指不能被修改
  • 如果const在*的后面,则是指针不能被修改

因此前两句是一个意思,是所指不能被修改
第三句是指针不能被修改

const数组来保护数组值

const数组

刚刚已经介绍过,数组变量已经是const的指针了,这里的const表明数组的每个单元都是const,所以必须通过初始化进行赋值

保护数组值

前面的介绍已经发现,由于数组传入函数传的是地址,因此在函数内部可以修改这个数组中的值,但如果我们不能修改呢
可以设置参数为const

int sum (const int a[],int length)

const的总结

注意,这一章一共介绍了四种const,分别是

  1. 指针是const
  2. 所指是const
  3. 数组是const的指针
  4. const数组
    其中3是1的其中一种

这一章的本质是在函数 数组 指针的综合

指针运算

注:之前已经说到,把数组赋给指针以后,可以拿数组像指针一样操作,也可以拿指针像数组一样操作

//数组像指针一样操作
char ac[]={0,1,2,3,4,5,6,7,8,9};
int *p=ac;
int *p1=&ac[5];

//指针像数组一样操作
*p->ac[0]
*(p+1)->ac[1]
*(p+n)->ac[n]
//*(p+1)要加括号是因为*是单目运算符,优先级比+高
  1. 对指针加1的时候,不是在地址上加1,而是在地址上加一个sizeof那个指针所指的类型,即移到下一个单元上
  2. 但如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义(好理解)
  3. int *p=ac;int *p1=&ac[5];int *p=ac;可以不写取地址符,和int *p=&ac[0];是一回事,但是int *p1=&ac[5];必须要写,这个要特别注意的
  4. *(p+1)*p+1不一样,要理解
  5. p1-p是什么?两个指针的相减,最后结果是5,这是两个值相减,再除以sizeof那个类型,即是说中间差了几个数组单元
  6. *p++是什么意思?是取出p所指的那个数据来,完事以后顺便把p移到下一个单元,注意,*的优先级虽然高,但是没有++的优先级高,这常用于数组类的连续空间操作
    以遍历数组为例
#include<stdio.h>
int main()
{
	char ac[]={0,1,2,3,4,5,6,7,8,9};

	//遍历数组 方法1
	int i;
	for(i=0;i<sizeof(ac)/sizeof(ac[0]);i++)
	{
		printf("%d\n",ac[i]);
	}
	
	//遍历数组 方法2 
	//这时候应该在数组中多一个不能达到的值,比如-1 
	char ac1[]={0,1,2,3,4,5,6,7,8,9,-1};
	char *p=ac1;
	while(*p!=-1)
	{
		printf("%d\n",*p++);
	 } 
	return 0;
 }

动态内存分配

动态内存分配在什么时候用?
C99以前,确定需要多大的单元的时候

#include<stdio.h>
int main()
{
	int number;
	printf("输入数量:"); 
	scanf("%d",&number);
	int a[number];//就是为了解决这件事情
	//因为C99以前不能在方括号中加数字 
	
	return 0;
 }

如上代码

但注意:malloc是必须掌握的东西

详细介绍怎么用

三部曲:

  1. #include<stdlib.h>这个头文件要加上

  2. a=(int *)malloc(number*sizeof(int));这句话的意思是,先讲你需要多少个number,然后乘以你需要的数据类型的大小,就是 你所需要的内存的大小,malloc(你所申请的内存的大小(字节)) ,但是由于只是给你了这片内存,即这篇内存现在没有明确的类型,是void *,因此要再强制类型转换成int * 这一块因为是连续的空间,就可以当成a是数组,进行操作了
    在这里插入图片描述

  3. free(a)释放的应该是原来的地址
    在这里插入图片描述

样例:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int number;
	int *a;
	int i;
	printf("输入数量:"); 
	scanf("%d",&number);
	/*
	int a[number];//就是为了解决这件事情
	//因为C99以前不能在方括号中加数字 
	*/
	a=(int *)malloc(number*sizeof(int));

	for(i=0;i<number;i++)
	{
		scanf("%d",&a[i]);
	}
	for(i=number-1;i>=0;i--)
	{
		printf("%d",a[i])
	}
	//数组和指针 能不能混着用取决于是不是一片
	//连续的空间 
	//上面赋值的语句块和逆序输出的语句块完全是按照数组来做的
	
	free(a);
	
	//如果申请失败就会返回0,或者就是NULL 
	
	return 0;
 }

malloc能给我们分配多大的空间

#include<stdio.h>
#include<stdlib.h>
int main()
{
	void *p;
	int cnt=0;
	while((p=malloc(100*1024*1024))) //每次申请100MB的空间,如果最后输出3300MB,就是只能申请33次100MB,也就是只能给你3300个MB的空间 
	{
		cnt++;
	}
	printf("分配了%d00MB的空间",cnt); 
	
	return 0;
 }

这里需要注意的几点

  1. p为什么是void *,因为malloc()本身就是void *,然后再看你的需要来强制类型转换成你需要的
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-26 11:53:50  更:2021-07-26 11:55:40 
 
开发: 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年5日历 -2024/5/5 3:12:41-

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